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

**URL:** <https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708>\
**Category:** Showcase\
**Tags:** zig-master, io, llm, zig-0-16, headmade, concurrency\
**Created:** [July 14, 2026, 10:47am UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708 "2026-07-14T10:47:45Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [July 14, 2026, 10:47am UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/1 "2026-07-14T10:47:45Z")

</div>

# 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](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.

> **[GitHub - lalinsky/xsync.zig: Synchronization primitives for Zig that work...](https://github.com/lalinsky/xsync.zig)**
>
> Synchronization primitives for Zig that work across std.Io instances

See this for the background:

> [@Threaded and Evented coexistence](https://ziggit.dev/t/threaded-and-evented-coexistence/16652):
>
> Just to clean mess in my head. Let’s suppose that we have two “execution contexts”: under Io.Threaded under Io.Evented And shared struct with Io.Mutex used guard As far as I understand the way of safely access to this struct is use “neitral” 3rd Io - std.Io.Threaded.global\_single\_threaded Or I miss something? And this problem simply doesn’t exist

### 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.

---

<div class="post-metadata">

**Author:** ![g41797](https://ziggit.dev/user_avatar/ziggit.dev/g41797/32/3339_2.png) [@g41797](https://ziggit.dev/u/g41797)\
**Post date:** [July 14, 2026, 11:11am UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/2 "2026-07-14T11:11:40Z")

</div>

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

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

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [July 14, 2026, 1:54pm UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/3 "2026-07-14T13:54:10Z")

</div>

> [@g41797](#):
>
> For that kind of a project I use tag ‘headmade’.

I do like that! 🙂

> [@g41797](#):
>
> Consider adding of wakeup as special operation

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.

---

<div class="post-metadata">

**Author:** ![g41797](https://ziggit.dev/user_avatar/ziggit.dev/g41797/32/3339_2.png) [@g41797](https://ziggit.dev/u/g41797)\
**Post date:** [July 14, 2026, 6:04pm UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/4 "2026-07-14T18:04:48Z")

</div>

> [@lalinsky](#):
>
> synchronize using channels instead of lower-level primitives

Bryan Mills [“Rethinking Classical Concurrency Patterns”](https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view)

My first open source project was based on this paper

My current (zig based) - also borrowed several principles

---

<div class="post-metadata">

**Author:** ![psznm](https://ziggit.dev/user_avatar/ziggit.dev/psznm/32/8377_2.png) [@psznm](https://ziggit.dev/u/psznm)\
**Post date:** [July 14, 2026, 6:27pm UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/5 "2026-07-14T18:27:11Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [July 14, 2026, 6:34pm UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/6 "2026-07-14T18:34:04Z")

</div>

> [@psznm](#):
>
> and I couldn’t find any more swallowed error.Canceled

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.

---

<div class="post-metadata">

**Author:** ![psznm](https://ziggit.dev/user_avatar/ziggit.dev/psznm/32/8377_2.png) [@psznm](https://ziggit.dev/u/psznm)\
**Post date:** [July 14, 2026, 6:38pm UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/7 "2026-07-14T18:38:24Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![npc1054657282](https://ziggit.dev/user_avatar/ziggit.dev/npc1054657282/32/5790_2.png) [@npc1054657282](https://ziggit.dev/u/npc1054657282)\
**Post date:** [July 15, 2026, 6:34am UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/8 "2026-07-15T06:34:51Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![g41797](https://ziggit.dev/user_avatar/ziggit.dev/g41797/32/3339_2.png) [@g41797](https://ziggit.dev/u/g41797)\
**Post date:** [July 15, 2026, 7:11am UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/9 "2026-07-15T07:11:45Z")

</div>

problem is not lack of required functionality in current IO implementation

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

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [July 15, 2026, 8:56am UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/10 "2026-07-15T08:56:34Z")

</div>

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](https://codeberg.org/ziglang/zig/issues/36178)

---

<div class="post-metadata">

**Author:** ![sullivan](https://ziggit.dev/user_avatar/ziggit.dev/sullivan/32/8888_2.png) [@sullivan](https://ziggit.dev/u/sullivan)\
**Post date:** [July 15, 2026, 12:14pm UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/11 "2026-07-15T12:14:46Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [July 17, 2026, 9:11pm UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/12 "2026-07-17T21:11:03Z")

</div>

Found yet another bug in `std.Io.RwLock`, this one particularly nasty, because it doesn’t even require cancellation, and both `tryLock` and `lockShared` can succeed at the same time. Fixed in my library now. I really hope these fixes will make it to Zig 0.17.

[https://codeberg.org/ziglang/zig/issues/36217](https://codeberg.org/ziglang/zig/issues/36217)

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [July 21, 2026, 11:31am UTC](https://ziggit.dev/t/xsync-zig-synchronization-primitives-that-work-across-std-io-instances/16708/13 "2026-07-21T11:31:18Z")

</div>

I’ve done thorough reviews, more tests, and released this as version 1.0. Due to the nature of the code, it’s unlikely there will be another release, unless the `std.Io` vtable changes.
