{"article":{"slug":"spinlocks-considered-harmful","title":"Spinlocks Considered Harmful","subtitle":null,"summary":"Alex Kladov (matklad) argues that swapping std's Mutex for a spinlock to add no_std support in Rust crates is an anti-pattern, walking through how spinlocks work, priority inversion, CPU interrupts and why a spinning lock can deadlock, and proposes alternatives such as panicking when blocking is impossible.","content_type":"blog_post","language":"en","canonical_url":"https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html","author":{"name":"Alex Kladov","url":"https://matklad.github.io/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"matklad.github.io","url":"https://matklad.github.io/","listing_slug":null,"listing":null},"topics":[{"name":"Rust","slug":"rust","url":"https://listedarticles.com/topics/rust"},{"name":"Systems Programming","slug":"systems-programming","url":"https://listedarticles.com/topics/systems-programming"},{"name":"Performance","slug":"performance","url":"https://listedarticles.com/topics/performance"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":2000,"reading_minutes":9,"published_at":"2020-01-02T00:00:00.000Z","added_at":"2026-10-09T17:20:30.553Z","updated_at":"2026-10-09T17:20:30.553Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/spinlocks-considered-harmful","markdown_url":"https://listedarticles.com/articles/spinlocks-considered-harmful.md","example":false,"citation":"Alex Kladov, matklad.github.io. \"Spinlocks Considered Harmful.\" 2 Jan 2020. https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html"},"body_markdown":"# Spinlocks Considered Harmful\n\nHappy new year 🎉!\n\nIn this post, I will be expressing strong opinions about a topic I have relatively little practical experience with, so feel free to roast and educate me in comments (link at the end of the post) :-)\n\nSpecifically, I’ll talk about:\n\n- spinlocks,\n- spinlocks in Rust with `#[no_std]` ,\n- priority inversion,\n- CPU interrupts,\n- and a couple of neat/horrible systemsy Rust hacks.\n\n## [Context](#Context)\n\nI maintain [`once_cell`](https://github.com/matklad/once_cell/) crate, which is a synchronization primitive.\nIt uses `std` blocking facilities under the hood (specifically, `std::thread::park`), and as such is not compatible with `#[no_std]`.\nA popular request is to add a spin-lock based implementation for use in `#[no_std]` environments: [#61](https://github.com/matklad/once_cell/issues/61).\n\nMore generally, this seems to be a common pattern in Rust ecosystem:\n\n- A crate uses `Mutex` or other synchronization mechanism from`std`\n- Someone asks for `#[no_std]` support\n- `Mutex` is swapped for some variation of spinlock.\n\nFor example, the `lazy_static` crate does this:\n\n[github.com/rust-lang-nursery/lazy-static.rs/blob/master/src/core_lazy.rs](https://github.com/rust-lang-nursery/lazy-static.rs/blob/421669662b35fcb455f2902daed2e20bbbba79b6/src/core_lazy.rs#L10)\n\nI think this is an anti-pattern, and I am writing this blog post to call it out.\n\n## [What Is a Spinlock, Anyway?](#What-Is-a-Spinlock-Anyway)\n\nA `Spinlock` is the simplest possible implementation of a mutex, its general form looks like this:\n\n1. To grab a lock, we repeatedly execute `compare_and_swap` until it succeeds. The CPU “spins” in this very short loop.\n2. Only one thread at a time can be here.\n3. To release the lock, we do a single atomic store.\n4. Spinning is wasteful, so we use an [intrinsic](https://en.wikipedia.org/wiki/Intrinsic_function) to instruct the CPU to enter a low-power mode.\n\nWhy we need `Ordering::Acquire` and `Ordering::Release` is very interesting, but beyond the scope of this article.\n\nThe key take-away here is that a spinlock is implemented entirely in user space: from OS point of view, a “spinning” thread looks exactly like a thread that does a heavy computation.\n\nAn OS-based mutex, like [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) or [`parking_lot::Mutex`](https://docs.rs/parking_lot/0.10.0/parking_lot/type.Mutex.html), uses a **system call** to tell the operating system that a thread needs to be blocked. In pseudo code, an implementation might look like this:\n\nThe main difference is `park_this_thread` — a **blocking** system call.\nIt instructs the OS to take current thread off the CPU until it is woken up by an `unpark_some_thread` call.\nThe kernel maintains a **queue** of threads waiting for a mutex.\nThe `park` call enqueues current thread onto this queue, while `unpark` dequeues some thread. The `park` system call returns when the thread is dequeued.\nIn the meantime, the thread waits off the CPU.\n\nIf there are several different mutexes, the kernel needs to maintain several queues.\nAn address of a lock can be used as a token to identify a specific queue (this is a [futex](http://man7.org/linux/man-pages/man2/futex.2.html) API).\n\nSystem calls are expensive, so production implementations of `Mutex` usually spin for several iterations before calling into OS, optimistically hoping that the `Mutex` will be released soon.\nHowever, the waiting always bottoms out in a syscall.\n\n## [Spinning Just For a Little Bit, What Can Go Wrong?](#Spinning-Just-For-a-Little-Bit-What-Can-Go-Wrong)\n\nBecause spin locks are so simple and fast, it seems to be a good idea to use them for short-lived critical sections. For example, if you only need to increment a couple of integers, should you really bother with complicated syscalls? In the worst case, the other thread will spin just for a couple of iterations…\n\nUnfortunately, this logic is flawed! A thread can be preempted at any time, including during a short critical section. If it is preempted, that means that all other threads will need to spin until the original thread gets its share of CPU again. And, because a spinning thread looks like a good, busy thread to the OS, the other threads will spin until they exhaust their quants, preventing the unlucky thread from getting back on the processor!\n\nIf this sounds like a series of unfortunate events, don’t worry, it gets even worse. Enter **Priority Inversion**. Suppose our threads have priorities, and OS tries to schedule high-priority threads over low-priority ones.\n\nNow, what happens if the thread that enters a critical section is a low-priority one, but competing threads have high priority? It will likely get preempted: there are higher priority threads after all. And, if the number of cores is smaller than the number of high priority threads that try to lock a mutex, it likely won’t be able to complete a critical section at all: OS will be repeatedly scheduling all the other threads!\n\n## [No OS, no problem?](#No-OS-no-problem)\n\nBut wait! — you would say — we only use spin locks in `#[no_std]` crates, so there’s no OS to preempt our threads.\n\n*First*, it’s not really true: it’s perfectly fine, and often even desirable, to use `#[no_std]` crates for usual user-space applications.\nFor example, if you write a Rust replacement for a low-level C library, like zlib or openssl, you will probably make the crate `#[no_std]`, so that non-Rust applications can link to it without pulling the whole of the Rust runtime.\n\n*Second*, if there’s really no OS to speak about, and you are on the bare metal (or in the kernel), it gets even worse than priority inversion.\n\nOn bare metal, we generally don’t worry about *thread* preemption, but we need to worry about [processor interrupts](https://en.wikipedia.org/wiki/Interrupt). That is, while processor is executing some code, it might receive an interrupt from some periphery device, and temporary switch to the interrupt handler’s code.\n\nAnd here comes the disaster: if the main code is in the middle of the critical section when the interrupt arrives, and if the interrupt handler tries to enter the critical section as well, we get a guaranteed deadlock!\nThere’s no OS to switch threads after a quant expires.\nHere are Linux kernel [docs](https://www.kernel.org/doc/Documentation/locking/spinlocks.txt) discussing this issue.\n\n## [Practical Applications](#Practical-Applications)\n\nLet’s trigger priority inversion!\nOur victim is the [`getrandom`](https://github.com/rust-random/getrandom/tree/v0.1.13) crate.\nI don’t pick on `getrandom` specifically here: the pattern is pervasive across the ecosystem.\n\nThe crate uses spinning in the [`LazyUsize`](https://github.com/rust-random/getrandom/blob/v0.1.13/src/util.rs#L54-L82) utility type:\n\nThere’s a `static` instance of `LazyUsize` which caches file descriptor for `/dev/random`:\n\n[https://github.com/rust-random/getrandom/blob/v0.1.13/src/use_file.rs#L26](https://github.com/rust-random/getrandom/blob/v0.1.13/src/use_file.rs#L26)\n\nThis descriptor is used when calling `getrandom` — the only function that is exported by the crate.\n\nTo trigger priority inversion, we will create `1 + N` threads, each of which will call `getrandom::getrandom`.\nWe arrange it so that the first thread has a low priority, and the rest are high priority.\nWe stagger threads a little bit so that the first one does the initialization.\nWe also make creating the file descriptor slow, so that the first thread gets preempted while in the critical section.\n\nHere is the implementation of this plan: [https://github.com/matklad/spin-of-death](https://github.com/matklad/spin-of-death).\n\nIt uses a couple of systems programming hacks to make this disaster scenario easy to reproduce.\nTo simulate slow `/dev/random`, we want to intercept the `poll` syscall `getrandom` is using to ensure that there’s enough entropy.\nWe can use [strace](https://strace.io/) to log system calls issued by a program.\nI don’t know if strace can be used to make a syscall run slow (now, once I’ve looked at the website, I see that it can in fact be used to tamper with syscalls, *sigh*), but we actually don’t need to!\n`getrandom` does not use the syscall directly, it uses the `poll` function from `libc`.\nWe can substitute this function by using `LD_PRELOAD`, but there’s an even simpler way!\nWe can trick the static linker into using a function which we define ourselves:\n\nThe name of the function accidentally ( :) ) clashes with a well-known [POSIX function](http://man7.org/linux/man-pages/man2/poll.2.html).\n\nHowever, this alone is not enough.\n`getrandom` [tries to use](https://github.com/rust-random/getrandom/blob/v0.1.13/src/linux_android.rs) `getrandom` syscall first, and that code path does not use a spin lock.\nWe need to fool `getrandom` into believing that the syscall is not available.\nOur `extern \"C\"` trick wouldn’t have worked if `getrandom` literally used the `syscall` instruction.\nHowever, as inline assembly (which you need to issue a syscall manually) is not available on stable Rust, `getrandom` goes via `syscall` *function* from `libc`.\nThat we can override with the same trick.\n\nHowever, there’s a wrinkle!\nTraditionally, `libc` API used `errno` for error reporting.\nThat is, on a failure the function would return an single specific invalid value, and set the `errno` thread local variable to the specific error code. `syscall` follows this pattern.\n\nThe `errno` interface is cumbersome to use.\nThe worst part of `errno` is that the specification requires it to be a macro, and so you can only really use it from `C` *source code*.\nInternally, on Linux the macro calls `__get_errno_location` function to get the thread local, but this is an implementation detail (which we will gladly take advantage of, in this land of reckless systems hacking!). The irony is that the ABI of Linux syscall just **returns** error codes, so `libc` has to do some legwork to adapt to the awkward `errno` interface.\n\nSo, here’s a strong contender for the most cursed function I’ve written so far:\n\nIt makes `getrandom` believe that there’s no `getrandom` syscall, which causes it to fallback to `/dev/random` implementation.\n\nTo set thread priorities, we use [thread_priority](https://docs.rs/thread-priority/0.1.1/thread_priority/) crate, which is a thin wrapper around `pthread` APIs.\nWe will be using real time priorities, which require `sudo`.\n\nAnd here are the results:\n\nNote that I had to kill the program after two minutes. Also note the impressive system time, as well as load average\n\nIf we [patch](https://github.com/matklad/getrandom/commit/a7dc21fed9b789832702b98807a62de7bf7312d4) `getrandom` to use `std::sync::Once` instead we get a much better result:\n\n1. Note how `real` is half a second, but`user` and`sys` are small.\nThat’s because we are waiting for 500 milliseconds in our`poll`\n\nThis is because `Once` uses OS facilities for blocking, and so OS notices that high priority threads are actually blocked and gives the low priority thread a chance to finish its work.\n\n## [If Not a Spinlock, Then What?](#If-Not-a-Spinlock-Then-What)\n\n*First*, if you only use a spin lock because “it’s faster for small critical sections”, just replace it with a mutex from `std` or `parking_lot`.\nThey already do a small amount of spinning iterations before calling into the kernel, so they are as fast as a spinlock in the best case, and infinitely faster in the worst case.\n\n*Second*, it seems like most problematic uses of spinlocks come from one time initialization (which is exactly what my `once_cell` crate helps with). I think it usually is possible to get away without using spinlocks. For example, instead of storing the state itself, the library may just delegate state storing to the user. For `getrandom`, it can expose two functions:\n\nIt then becomes the user’s problem to cache `RandomState` appropriately.\nFor example, std may continue using a thread local ([src](https://github.com/rust-lang/rust/blob/0ec370670220b712b042ee09aab067ec7e5878d5/src/libstd/collections/hash/map.rs#L2460)) while rand, with `std` feature enabled, could use a global variable, protected by `Once`.\n\nAnother option, if the state fits into `usize` and the initializing function is idempotent and relatively quick, is to do a racy initialization:\n\nTake a second to appreciate the absence of `unsafe` blocks and cross-core communication in the above example!\n~~At worst,~~  (EDIT: this is wrong, thanks to /u/pcpthm for `init` will be called `number of cores` times[pointing this out](https://www.reddit.com/r/rust/comments/eis1tr/blog_post_spinlocks_considered_harmful/fctg66s)!).\n\nThere’s also a nuclear option: parametrize the library by blocking behavior, and allow the user to supply their own synchronization primitive.\n\n*Third*, sometimes you just **know** that there’s only a single thread in the program, and you might want to use a spinlock just to silence those annoying compiler errors about `static mut`.\nThe primary use case here I think is WASM. A solution for this case is to assume that blocking just doesn’t happen, and panic otherwise. This is what [std does](https://github.com/rust-lang/rust/blob/0ec370670220b712b042ee09aab067ec7e5878d5/src/libstd/sys/wasm/mutex.rs) for `Mutex` on WASM, and what is implemented for `once_cell` in this PR: [#82](https://github.com/matklad/once_cell/pull/82).\n\nDiscussion on [/r/rust](https://www.reddit.com/r/rust/comments/eis1tr/blog_post_spinlocks_considered_harmful/).\n\nEDIT: If you enjoyed this post, you might also like this one:\n\nLooks like we have some contention here!\n\nEDIT: there’s now a follow up post, where we actually benchmark spinlocks:\n\n[https://matklad.github.io/2020/01/04/mutexes-are-faster-than-spinlocks.html](https://matklad.github.io/2020/01/04/mutexes-are-faster-than-spinlocks.html)\n","body_html":"<h1 id=\"spinlocks-considered-harmful\">Spinlocks Considered Harmful</h1>\n<p>Happy new year 🎉!</p>\n<p>In this post, I will be expressing strong opinions about a topic I have relatively little practical experience with, so feel free to roast and educate me in comments (link at the end of the post) :-)</p>\n<p>Specifically, I’ll talk about:</p>\n<ul><li>spinlocks,</li><li>spinlocks in Rust with <code>#[no_std]</code> ,</li><li>priority inversion,</li><li>CPU interrupts,</li><li>and a couple of neat/horrible systemsy Rust hacks.</li></ul>\n<h2 id=\"context\"><a href=\"#Context\">Context</a></h2>\n<p>I maintain <a href=\"https://github.com/matklad/once_cell/\" rel=\"nofollow ugc noopener\"><code>once_cell</code></a> crate, which is a synchronization primitive.\nIt uses <code>std</code> blocking facilities under the hood (specifically, <code>std::thread::park</code>), and as such is not compatible with <code>#[no_std]</code>.\nA popular request is to add a spin-lock based implementation for use in <code>#[no_std]</code> environments: <a href=\"https://github.com/matklad/once_cell/issues/61\" rel=\"nofollow ugc noopener\">#61</a>.</p>\n<p>More generally, this seems to be a common pattern in Rust ecosystem:</p>\n<ul><li>A crate uses <code>Mutex</code> or other synchronization mechanism from<code>std</code></li><li>Someone asks for <code>#[no_std]</code> support</li><li><code>Mutex</code> is swapped for some variation of spinlock.</li></ul>\n<p>For example, the <code>lazy_static</code> crate does this:</p>\n<p><a href=\"https://github.com/rust-lang-nursery/lazy-static.rs/blob/421669662b35fcb455f2902daed2e20bbbba79b6/src/core_lazy.rs#L10\" rel=\"nofollow ugc noopener\">github.com/rust-lang-nursery/lazy-static.rs/blob/master/src/core_lazy.rs</a></p>\n<p>I think this is an anti-pattern, and I am writing this blog post to call it out.</p>\n<h2 id=\"what-is-a-spinlock-anyway\"><a href=\"#What-Is-a-Spinlock-Anyway\">What Is a Spinlock, Anyway?</a></h2>\n<p>A <code>Spinlock</code> is the simplest possible implementation of a mutex, its general form looks like this:</p>\n<ol><li>To grab a lock, we repeatedly execute <code>compare_and_swap</code> until it succeeds. The CPU “spins” in this very short loop.</li><li>Only one thread at a time can be here.</li><li>To release the lock, we do a single atomic store.</li><li>Spinning is wasteful, so we use an <a href=\"https://en.wikipedia.org/wiki/Intrinsic_function\" rel=\"nofollow ugc noopener\">intrinsic</a> to instruct the CPU to enter a low-power mode.</li></ol>\n<p>Why we need <code>Ordering::Acquire</code> and <code>Ordering::Release</code> is very interesting, but beyond the scope of this article.</p>\n<p>The key take-away here is that a spinlock is implemented entirely in user space: from OS point of view, a “spinning” thread looks exactly like a thread that does a heavy computation.</p>\n<p>An OS-based mutex, like <a href=\"https://doc.rust-lang.org/std/sync/struct.Mutex.html\" rel=\"nofollow ugc noopener\"><code>std::sync::Mutex</code></a> or <a href=\"https://docs.rs/parking_lot/0.10.0/parking_lot/type.Mutex.html\" rel=\"nofollow ugc noopener\"><code>parking_lot::Mutex</code></a>, uses a <strong>system call</strong> to tell the operating system that a thread needs to be blocked. In pseudo code, an implementation might look like this:</p>\n<p>The main difference is <code>park_this_thread</code> — a <strong>blocking</strong> system call.\nIt instructs the OS to take current thread off the CPU until it is woken up by an <code>unpark_some_thread</code> call.\nThe kernel maintains a <strong>queue</strong> of threads waiting for a mutex.\nThe <code>park</code> call enqueues current thread onto this queue, while <code>unpark</code> dequeues some thread. The <code>park</code> system call returns when the thread is dequeued.\nIn the meantime, the thread waits off the CPU.</p>\n<p>If there are several different mutexes, the kernel needs to maintain several queues.\nAn address of a lock can be used as a token to identify a specific queue (this is a <a href=\"http://man7.org/linux/man-pages/man2/futex.2.html\" rel=\"nofollow ugc noopener\">futex</a> API).</p>\n<p>System calls are expensive, so production implementations of <code>Mutex</code> usually spin for several iterations before calling into OS, optimistically hoping that the <code>Mutex</code> will be released soon.\nHowever, the waiting always bottoms out in a syscall.</p>\n<h2 id=\"spinning-just-for-a-little-bit-what-can-go-wrong\"><a href=\"#Spinning-Just-For-a-Little-Bit-What-Can-Go-Wrong\">Spinning Just For a Little Bit, What Can Go Wrong?</a></h2>\n<p>Because spin locks are so simple and fast, it seems to be a good idea to use them for short-lived critical sections. For example, if you only need to increment a couple of integers, should you really bother with complicated syscalls? In the worst case, the other thread will spin just for a couple of iterations…</p>\n<p>Unfortunately, this logic is flawed! A thread can be preempted at any time, including during a short critical section. If it is preempted, that means that all other threads will need to spin until the original thread gets its share of CPU again. And, because a spinning thread looks like a good, busy thread to the OS, the other threads will spin until they exhaust their quants, preventing the unlucky thread from getting back on the processor!</p>\n<p>If this sounds like a series of unfortunate events, don’t worry, it gets even worse. Enter <strong>Priority Inversion</strong>. Suppose our threads have priorities, and OS tries to schedule high-priority threads over low-priority ones.</p>\n<p>Now, what happens if the thread that enters a critical section is a low-priority one, but competing threads have high priority? It will likely get preempted: there are higher priority threads after all. And, if the number of cores is smaller than the number of high priority threads that try to lock a mutex, it likely won’t be able to complete a critical section at all: OS will be repeatedly scheduling all the other threads!</p>\n<h2 id=\"no-os-no-problem\"><a href=\"#No-OS-no-problem\">No OS, no problem?</a></h2>\n<p>But wait! — you would say — we only use spin locks in <code>#[no_std]</code> crates, so there’s no OS to preempt our threads.</p>\n<p><em>First</em>, it’s not really true: it’s perfectly fine, and often even desirable, to use <code>#[no_std]</code> crates for usual user-space applications.\nFor example, if you write a Rust replacement for a low-level C library, like zlib or openssl, you will probably make the crate <code>#[no_std]</code>, so that non-Rust applications can link to it without pulling the whole of the Rust runtime.</p>\n<p><em>Second</em>, if there’s really no OS to speak about, and you are on the bare metal (or in the kernel), it gets even worse than priority inversion.</p>\n<p>On bare metal, we generally don’t worry about <em>thread</em> preemption, but we need to worry about <a href=\"https://en.wikipedia.org/wiki/Interrupt\" rel=\"nofollow ugc noopener\">processor interrupts</a>. That is, while processor is executing some code, it might receive an interrupt from some periphery device, and temporary switch to the interrupt handler’s code.</p>\n<p>And here comes the disaster: if the main code is in the middle of the critical section when the interrupt arrives, and if the interrupt handler tries to enter the critical section as well, we get a guaranteed deadlock!\nThere’s no OS to switch threads after a quant expires.\nHere are Linux kernel <a href=\"https://www.kernel.org/doc/Documentation/locking/spinlocks.txt\" rel=\"nofollow ugc noopener\">docs</a> discussing this issue.</p>\n<h2 id=\"practical-applications\"><a href=\"#Practical-Applications\">Practical Applications</a></h2>\n<p>Let’s trigger priority inversion!\nOur victim is the <a href=\"https://github.com/rust-random/getrandom/tree/v0.1.13\" rel=\"nofollow ugc noopener\"><code>getrandom</code></a> crate.\nI don’t pick on <code>getrandom</code> specifically here: the pattern is pervasive across the ecosystem.</p>\n<p>The crate uses spinning in the <a href=\"https://github.com/rust-random/getrandom/blob/v0.1.13/src/util.rs#L54-L82\" rel=\"nofollow ugc noopener\"><code>LazyUsize</code></a> utility type:</p>\n<p>There’s a <code>static</code> instance of <code>LazyUsize</code> which caches file descriptor for <code>/dev/random</code>:</p>\n<p><a href=\"https://github.com/rust-random/getrandom/blob/v0.1.13/src/use_file.rs#L26\" rel=\"nofollow ugc noopener\"><a href=\"https://github.com/rust-random/getrandom/blob/v0.1.13/src/use_file.rs#L26\" rel=\"nofollow ugc noopener\">https://github.com/rust-random/getrandom/blob/v0.1.13/src/use_file.rs#L26</a></a></p>\n<p>This descriptor is used when calling <code>getrandom</code> — the only function that is exported by the crate.</p>\n<p>To trigger priority inversion, we will create <code>1 + N</code> threads, each of which will call <code>getrandom::getrandom</code>.\nWe arrange it so that the first thread has a low priority, and the rest are high priority.\nWe stagger threads a little bit so that the first one does the initialization.\nWe also make creating the file descriptor slow, so that the first thread gets preempted while in the critical section.</p>\n<p>Here is the implementation of this plan: <a href=\"https://github.com/matklad/spin-of-death\" rel=\"nofollow ugc noopener\"><a href=\"https://github.com/matklad/spin-of-death\" rel=\"nofollow ugc noopener\">https://github.com/matklad/spin-of-death</a></a>.</p>\n<p>It uses a couple of systems programming hacks to make this disaster scenario easy to reproduce.\nTo simulate slow <code>/dev/random</code>, we want to intercept the <code>poll</code> syscall <code>getrandom</code> is using to ensure that there’s enough entropy.\nWe can use <a href=\"https://strace.io/\" rel=\"nofollow ugc noopener\">strace</a> to log system calls issued by a program.\nI don’t know if strace can be used to make a syscall run slow (now, once I’ve looked at the website, I see that it can in fact be used to tamper with syscalls, <em>sigh</em>), but we actually don’t need to!\n<code>getrandom</code> does not use the syscall directly, it uses the <code>poll</code> function from <code>libc</code>.\nWe can substitute this function by using <code>LD_PRELOAD</code>, but there’s an even simpler way!\nWe can trick the static linker into using a function which we define ourselves:</p>\n<p>The name of the function accidentally ( :) ) clashes with a well-known <a href=\"http://man7.org/linux/man-pages/man2/poll.2.html\" rel=\"nofollow ugc noopener\">POSIX function</a>.</p>\n<p>However, this alone is not enough.\n<code>getrandom</code> <a href=\"https://github.com/rust-random/getrandom/blob/v0.1.13/src/linux_android.rs\" rel=\"nofollow ugc noopener\">tries to use</a> <code>getrandom</code> syscall first, and that code path does not use a spin lock.\nWe need to fool <code>getrandom</code> into believing that the syscall is not available.\nOur <code>extern &quot;C&quot;</code> trick wouldn’t have worked if <code>getrandom</code> literally used the <code>syscall</code> instruction.\nHowever, as inline assembly (which you need to issue a syscall manually) is not available on stable Rust, <code>getrandom</code> goes via <code>syscall</code> <em>function</em> from <code>libc</code>.\nThat we can override with the same trick.</p>\n<p>However, there’s a wrinkle!\nTraditionally, <code>libc</code> API used <code>errno</code> for error reporting.\nThat is, on a failure the function would return an single specific invalid value, and set the <code>errno</code> thread local variable to the specific error code. <code>syscall</code> follows this pattern.</p>\n<p>The <code>errno</code> interface is cumbersome to use.\nThe worst part of <code>errno</code> is that the specification requires it to be a macro, and so you can only really use it from <code>C</code> <em>source code</em>.\nInternally, on Linux the macro calls <code>__get_errno_location</code> function to get the thread local, but this is an implementation detail (which we will gladly take advantage of, in this land of reckless systems hacking!). The irony is that the ABI of Linux syscall just <strong>returns</strong> error codes, so <code>libc</code> has to do some legwork to adapt to the awkward <code>errno</code> interface.</p>\n<p>So, here’s a strong contender for the most cursed function I’ve written so far:</p>\n<p>It makes <code>getrandom</code> believe that there’s no <code>getrandom</code> syscall, which causes it to fallback to <code>/dev/random</code> implementation.</p>\n<p>To set thread priorities, we use <a href=\"https://docs.rs/thread-priority/0.1.1/thread_priority/\" rel=\"nofollow ugc noopener\">thread_priority</a> crate, which is a thin wrapper around <code>pthread</code> APIs.\nWe will be using real time priorities, which require <code>sudo</code>.</p>\n<p>And here are the results:</p>\n<p>Note that I had to kill the program after two minutes. Also note the impressive system time, as well as load average</p>\n<p>If we <a href=\"https://github.com/matklad/getrandom/commit/a7dc21fed9b789832702b98807a62de7bf7312d4\" rel=\"nofollow ugc noopener\">patch</a> <code>getrandom</code> to use <code>std::sync::Once</code> instead we get a much better result:</p>\n<ol><li><p>Note how <code>real</code> is half a second, but<code>user</code> and<code>sys</code> are small.</p><p>That’s because we are waiting for 500 milliseconds in our<code>poll</code></p></li></ol>\n<p>This is because <code>Once</code> uses OS facilities for blocking, and so OS notices that high priority threads are actually blocked and gives the low priority thread a chance to finish its work.</p>\n<h2 id=\"if-not-a-spinlock-then-what\"><a href=\"#If-Not-a-Spinlock-Then-What\">If Not a Spinlock, Then What?</a></h2>\n<p><em>First</em>, if you only use a spin lock because “it’s faster for small critical sections”, just replace it with a mutex from <code>std</code> or <code>parking_lot</code>.\nThey already do a small amount of spinning iterations before calling into the kernel, so they are as fast as a spinlock in the best case, and infinitely faster in the worst case.</p>\n<p><em>Second</em>, it seems like most problematic uses of spinlocks come from one time initialization (which is exactly what my <code>once_cell</code> crate helps with). I think it usually is possible to get away without using spinlocks. For example, instead of storing the state itself, the library may just delegate state storing to the user. For <code>getrandom</code>, it can expose two functions:</p>\n<p>It then becomes the user’s problem to cache <code>RandomState</code> appropriately.\nFor example, std may continue using a thread local (<a href=\"https://github.com/rust-lang/rust/blob/0ec370670220b712b042ee09aab067ec7e5878d5/src/libstd/collections/hash/map.rs#L2460\" rel=\"nofollow ugc noopener\">src</a>) while rand, with <code>std</code> feature enabled, could use a global variable, protected by <code>Once</code>.</p>\n<p>Another option, if the state fits into <code>usize</code> and the initializing function is idempotent and relatively quick, is to do a racy initialization:</p>\n<p>Take a second to appreciate the absence of <code>unsafe</code> blocks and cross-core communication in the above example!\n<del>At worst,</del>  (EDIT: this is wrong, thanks to /u/pcpthm for <code>init</code> will be called <code>number of cores</code> times<a href=\"https://www.reddit.com/r/rust/comments/eis1tr/blog_post_spinlocks_considered_harmful/fctg66s\" rel=\"nofollow ugc noopener\">pointing this out</a>!).</p>\n<p>There’s also a nuclear option: parametrize the library by blocking behavior, and allow the user to supply their own synchronization primitive.</p>\n<p><em>Third</em>, sometimes you just <strong>know</strong> that there’s only a single thread in the program, and you might want to use a spinlock just to silence those annoying compiler errors about <code>static mut</code>.\nThe primary use case here I think is WASM. A solution for this case is to assume that blocking just doesn’t happen, and panic otherwise. This is what <a href=\"https://github.com/rust-lang/rust/blob/0ec370670220b712b042ee09aab067ec7e5878d5/src/libstd/sys/wasm/mutex.rs\" rel=\"nofollow ugc noopener\">std does</a> for <code>Mutex</code> on WASM, and what is implemented for <code>once_cell</code> in this PR: <a href=\"https://github.com/matklad/once_cell/pull/82\" rel=\"nofollow ugc noopener\">#82</a>.</p>\n<p>Discussion on <a href=\"https://www.reddit.com/r/rust/comments/eis1tr/blog_post_spinlocks_considered_harmful/\" rel=\"nofollow ugc noopener\">/r/rust</a>.</p>\n<p>EDIT: If you enjoyed this post, you might also like this one:</p>\n<p>Looks like we have some contention here!</p>\n<p>EDIT: there’s now a follow up post, where we actually benchmark spinlocks:</p>\n<p><a href=\"https://matklad.github.io/2020/01/04/mutexes-are-faster-than-spinlocks.html\" rel=\"nofollow ugc noopener\"><a href=\"https://matklad.github.io/2020/01/04/mutexes-are-faster-than-spinlocks.html\" rel=\"nofollow ugc noopener\">https://matklad.github.io/2020/01/04/mutexes-are-faster-than-spinlocks.html</a></a></p>","headings":[{"level":1,"text":"Spinlocks Considered Harmful","id":"spinlocks-considered-harmful"},{"level":2,"text":"Context","id":"context"},{"level":2,"text":"What Is a Spinlock, Anyway?","id":"what-is-a-spinlock-anyway"},{"level":2,"text":"Spinning Just For a Little Bit, What Can Go Wrong?","id":"spinning-just-for-a-little-bit-what-can-go-wrong"},{"level":2,"text":"No OS, no problem?","id":"no-os-no-problem"},{"level":2,"text":"Practical Applications","id":"practical-applications"},{"level":2,"text":"If Not a Spinlock, Then What?","id":"if-not-a-spinlock-then-what"}]}}