Hey all, I have a small question about adopting Mutex. In my codebase, we're basically using a wrapper around OSAllocatedUnfairLock today. Finding and replacing it with Mutex fails because our version is Copyable, and Mutex is not.
Initially, I thought that we should make changes to the feature code to accomodate this, which was pretty easy to do until I arrived at our copy-paste of Observations, and realized that as a copyable struct I couldn't use Mutex inside it. So I went looking at the standard library and found that it's still using a copyable lock type (ManagedCriticalState).
This leads to my question: it seems like it's expected that some copyable struct types in a codebase will want to use a Mutex (AsyncSequences mainly, I think). Does it make sense to provide a ManagedMutex type that's effectively a final class wrapper around a Mutex? Better to have a struct with a ManagedBuffer that contains the Mutex (or a final class in place of the buffer)? And if so, why?
ManagedCriticalState was a work-around for avoiding lockstep builds. I haven't yet tried to remove that lately but the last time I tried it resulted in build failures due to the minimum deployments. Honestly I'd love to remove all of the managed critical state if we can. That is also providing we can replace all the behaviors too.
I'm pretty sure the divergence in APIs means that you won't be able to replace it with Mutex anymore: you can't make Observations noncopyable, so it can't own a Mutex without some sort of reference type to hold onto it.
This question was intended to be broader than just the Observations example, but I guess this is a pretty good proxy: what would you want to use instead of _ManagedCriticalState here?
I've found the need for a Copyable/reference Mutex often enough that I added AllocatedMutex to our codebase. I made it a struct wrapping a private final class wrapping a Mutex for consistency with OSAllocatedUnfairLock (and preventing [weak mutex] which is pretty weird).
It mostly feels kinda unfortunate? Like, with once-callable closures throughout the stdlib, a lot of the reasons for it to exist would vanish. And generally I think it's better to put a Mutex inside a custom reference type, where there's methods you want to add as well as the protected state. Still, it kept coming up, so I eventually succumbed to the slightly bad feelings and did it.
Just curious, what are your use cases? I think the only one I've seen so far is AsyncSequence implementations, because those are the rare structs that aren't merely containers for data and have some logic in them. My impression was that the typical home for a Mutex was a let in a final class and that most consumers wouldn't even notice NonCopyable there.
Yes, AsyncSequences and unfortunately custom Publishers, but also "algorithms that wrap task groups" (e.g. race, timeout) often need a little bit of state captured and copied into each task in the group.