⇐ Notes archive

(This is an entry in my technical notebook. There will likely be typos, mistakes, or wider logical leaps — the intent here is to “let others look over my shoulder while I figure things out.”)

Thready safety has been a weak point for me (my university’s OS class also being taught in pre-1.x Rust didn’t help much, either hah); but, I’m slowly working on it.

Kyle Bashour’s reply to Curtis Herbert’s (Twitter) thread on the thread safety of AnyCancellable.store(in:) (or analogously, for the RangeReplaceableCollection overload) reminded me of a Combine gotcha. Swift’s Standard Library data structures aren’t thread-safe out of the box — which, in turn means we need to be extra careful when storing cancellation tokens across threads. Let’s tee up an example to see why.

If fetchCount is called across multiple threads, .store(in:) will concurrently modify cancellables, possibly leading to race conditions. There’s even a related issue about this over on OpenCombine’s repository. So, we’ll need to lock around the store call and while we could do the usual NSLocking.lock and .unlock dance, I looked around to see if we could do better. And I found a small helper over in TCA and in RxSwift.

extension NSRecursiveLock {

Prior art for this helper seems to be DispatchQueue’s sync(execute:) method. It might be tempting to further roll this logic up into an @Atomic property wrapper, yet it unfortunately won’t help when fencing off collection types — like Set in our case — because each thread would operate on its own copy of the data structure. Donny Wals walks through this in detail in a post on the topic.