authorgravatar for sachabarsayuracko@gmail.comsquidy239 <sachabarsayuracko@gmail.com> 2026-03-16 23:05:00+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-16 23:05:00+01:00
log8a8e4a1f04da36258b322aee128f95b9010464c9
tree57b727c1e1a67494de1c21c6889d79e4808dce70
parentd3fcfb0581cdd07c04380eac10b97772155dc4bd

add cancelable rwlock functions (#31497)

Added cancelable exclusive and shared locking to RwLock. I also added a canceling test for both. Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31497 Reviewed-by: Andrew Kelley <andrew@ziglang.org> Co-authored-by: squidy239 <sachabarsayuracko@gmail.com> Co-committed-by: squidy239 <sachabarsayuracko@gmail.com>

1 files changed, 80 insertions(+), 0 deletions(-)

lib/std/Io/RwLock.zig+80
...@@ -48,6 +48,25 @@ pub fn lockUncancelable(rl: *RwLock, io: Io) void {...@@ -48,6 +48,25 @@ pub fn lockUncancelable(rl: *RwLock, io: Io) void {
48 rl.semaphore.waitUncancelable(io);48 rl.semaphore.waitUncancelable(io);
49}49}
5050
51pub fn lock(rl: *RwLock, io: Io) Io.Cancelable!void {
52 _ = @atomicRmw(usize, &rl.state, .Add, writer, .seq_cst);
53 rl.mutex.lock(io) catch |err| switch (err) {
54 error.Canceled => {
55 _ = @atomicRmw(usize, &rl.state, .Sub, writer, .seq_cst);
56 return error.Canceled;
57 },
58 };
59
60 const state = @atomicRmw(usize, &rl.state, .Add, is_writing -% writer, .seq_cst);
61 if (state & reader_mask != 0)
62 rl.semaphore.wait(io) catch |err| switch (err) {
63 error.Canceled => {
64 rl.unlock(io);
65 return error.Canceled;
66 },
67 };
68}
69
51pub fn unlock(rl: *RwLock, io: Io) void {70pub fn unlock(rl: *RwLock, io: Io) void {
52 _ = @atomicRmw(usize, &rl.state, .And, ~is_writing, .seq_cst);71 _ = @atomicRmw(usize, &rl.state, .And, ~is_writing, .seq_cst);
53 rl.mutex.unlock(io);72 rl.mutex.unlock(io);
...@@ -93,6 +112,24 @@ pub fn lockSharedUncancelable(rl: *RwLock, io: Io) void {...@@ -93,6 +112,24 @@ pub fn lockSharedUncancelable(rl: *RwLock, io: Io) void {
93 rl.mutex.unlock(io);112 rl.mutex.unlock(io);
94}113}
95114
115pub fn lockShared(rl: *RwLock, io: Io) Io.Cancelable!void {
116 var state = @atomicLoad(usize, &rl.state, .seq_cst);
117 while (state & (is_writing | writer_mask) == 0) {
118 state = @cmpxchgWeak(
119 usize,
120 &rl.state,
121 state,
122 state + reader,
123 .seq_cst,
124 .seq_cst,
125 ) orelse return;
126 }
127
128 try rl.mutex.lock(io);
129 _ = @atomicRmw(usize, &rl.state, .Add, reader, .seq_cst);
130 rl.mutex.unlock(io);
131}
132
96pub fn unlockShared(rl: *RwLock, io: Io) void {133pub fn unlockShared(rl: *RwLock, io: Io) void {
97 const state = @atomicRmw(usize, &rl.state, .Sub, reader, .seq_cst);134 const state = @atomicRmw(usize, &rl.state, .Sub, reader, .seq_cst);
98135
...@@ -111,6 +148,10 @@ test "internal state" {...@@ -111,6 +148,10 @@ test "internal state" {
111 rl.lockUncancelable(io);148 rl.lockUncancelable(io);
112 rl.unlock(io);149 rl.unlock(io);
113 try testing.expectEqual(rl, Io.RwLock.init);150 try testing.expectEqual(rl, Io.RwLock.init);
151
152 try rl.lock(io);
153 rl.unlock(io);
154 try testing.expectEqual(rl, Io.RwLock.init);
114}155}
115156
116test "smoke test" {157test "smoke test" {
...@@ -123,6 +164,11 @@ test "smoke test" {...@@ -123,6 +164,11 @@ test "smoke test" {
123 try testing.expect(!rl.tryLockShared(io));164 try testing.expect(!rl.tryLockShared(io));
124 rl.unlock(io);165 rl.unlock(io);
125166
167 try rl.lock(io);
168 try testing.expect(!rl.tryLock(io));
169 try testing.expect(!rl.tryLockShared(io));
170 rl.unlock(io);
171
126 try testing.expect(rl.tryLock(io));172 try testing.expect(rl.tryLock(io));
127 try testing.expect(!rl.tryLock(io));173 try testing.expect(!rl.tryLock(io));
128 try testing.expect(!rl.tryLockShared(io));174 try testing.expect(!rl.tryLockShared(io));
...@@ -236,3 +282,37 @@ test "concurrent access" {...@@ -236,3 +282,37 @@ test "concurrent access" {
236 try testing.expect(run.writes == num_writes);282 try testing.expect(run.writes == num_writes);
237 try testing.expect(run.reads.raw >= num_reads);283 try testing.expect(run.reads.raw >= num_reads);
238}284}
285
286test "lock canceling" {
287 const io = testing.io;
288
289 var rl: Io.RwLock = .init;
290
291 rl.lockSharedUncancelable(io);
292 var sfuture = io.concurrent(semaphoreLockCancel, .{ &rl, io }) catch |err| switch (err) {
293 error.ConcurrencyUnavailable => return error.SkipZigTest,
294 };
295 try std.testing.expectEqual(error.Canceled, sfuture.cancel(io));
296 rl.unlockShared(io);
297 try testing.expectEqual(rl, Io.RwLock.init);
298
299 rl.lockUncancelable(io);
300 var mfuture = io.concurrent(mutexLockCancel, .{ &rl, io }) catch |err| switch (err) {
301 error.ConcurrencyUnavailable => return error.SkipZigTest,
302 };
303 try std.testing.expectEqual(error.Canceled, mfuture.cancel(io));
304 rl.unlock(io);
305 try testing.expectEqual(rl, Io.RwLock.init);
306}
307
308fn semaphoreLockCancel(rl: *Io.RwLock, io: Io) !void {
309 try rl.lock(io); //tests semaphore cancelling
310}
311
312fn mutexLockCancel(rl: *Io.RwLock, io: Io) !void {
313 //tests mutex canceling
314 try std.testing.expectEqual(error.Canceled, rl.lockShared(io));
315 io.recancel();
316 try std.testing.expectEqual(error.Canceled, rl.lock(io));
317 return error.Canceled;
318}