Xsync.zig - Synchronization primitives that work across std.Io instances

xsync.zig


This project implements a new set of synchronization primitives that work across std.Io instances.

std.Io.Mutex and friends wake waiters through whichever Io the waker happens to run on. That works when everything shares one parking implementation, but a waiter parked through one Io is invisible to another, so primitives can’t be shared between tasks living on different runtimes. The primitives here remember which Io each waiter parked through and wake it through that one.

Additionally, std.Io.Condition is unsafe to use in cancellation (see https://codeberg.org/ziglang/zig/issues/36139) which in turn makes std.Io.Queue, std.Io.Semaphore and std.Io.RwLock also unsafe. This library avoids the issue and is always cancellation-safe.

See this for the background:

Supported Zig versions

Zig 0.16 and master.

AI / LLM usage disclosure

I used Claude Fable 5 as a coworker, old school XP-style pair programming. Initially had it adapt my previous code from zio, then later we benchmarked it, I had some ideas how to restructure the primitives to avoid the new bottlenecks and once again, we worker on the implementation, me watching every line being written and providing my feedback. I’ve worked like this with real people, years before LLMs existed, so it’s a familiar process to me. I’ve always been the one with ideas and someone else was typing the code.

8 Likes

For that kind of a project I use tag ‘headmade’.

Cancel for Queue may be optional.
Consider adding of
wakeup as special operation

1 Like

I do like that! :slight_smile:

It’s safer to use Queue as a base for implementing custom synchronization. It’s already close to the ideal form. I guess this is why Go developers try to get developers to synchronize using channels instead of lower-level primitives.

Bryan Mills “Rethinking Classical Concurrency Patterns”

My first open source project was based on this paper

My current (zig based) - also borrowed several principles

1 Like

Seems like a godsend. For two days now I have been debugging broken cancellation of fairly simple prototype, where it often deadlocks or just drops the cancellation and continues (and I couldn’t find any more swallowed error.Canceled). This sounds like it could maybe help me solve my issues. Will give it a try tomorrow!

If you are using any std.Io.Reader or std.Io.Writer, be extremely careful about the error handling there. That’s the most common source of cancellation errors in the stdlib, there are still plenty there.

Yes, I am aware about that. As far as I am aware I am properly checking reader/writer implementations on every ReadFailed and WriteFailed… At this point I am having doubts that the issue is in my code (it probably is anyway). So I will see if xsync will help me. Will come out of this with some valuable lessons either way for sure.

Because I have a need for cross-io instances, and I haven’t been able to figure out how to do that under the current io abstraction, this is a major reason why I haven’t migrated my current work from 0.15 to 0.16. I’m really glad that this kind of work exists, and I think it can inspire me.

4 Likes

problem is not lack of required functionality in current IO implementation

problem is - lack of std devs participants in any discussion about this functionality

Found another issue in std.Io.RwLock, which is now fixed in xsync. It’s again race between cancellation and another event. https://codeberg.org/ziglang/zig/issues/36178

This is really cool… xSync is super complementary to Marionette. Especially for testing synchronization across multiple std.Io instances under deterministic scheduling and cancellation. I’m going to add it as an integration target later. Love all the stuff coming from the std.Io family!