| author | |
| committer | |
| log | b4ee54b5804b8afb0387a36c913a2cd8cf595409 |
| tree | ee9dd3e02280bdf424772f097b1fb917bdd89eef |
| parent | 6ed5b620507aa4589d6c6fe3a5e48e166d48b3d2 |
| signature |
This work was partially cherry-picked from Andrew's WIP std.fs branch.
However, I also analyzed and simplified the Mutex and Condition
implementations, and brought them in line with modern Zig style.
Co-authored-by: Andrew Kelley <andrew@ziglang.org>4 files changed, 145 insertions(+), 269 deletions(-)
lib/std/Build/Fuzz.zig+1-1| ... | @@ -124,7 +124,7 @@ pub fn init( | ... | @@ -124,7 +124,7 @@ pub fn init( |
| 124 | .coverage_files = .empty, | 124 | .coverage_files = .empty, |
| 125 | .coverage_mutex = .init, | 125 | .coverage_mutex = .init, |
| 126 | .queue_mutex = .init, | 126 | .queue_mutex = .init, |
| 127 | .queue_cond = .{}, | 127 | .queue_cond = .init, |
| 128 | .msg_queue = .empty, | 128 | .msg_queue = .empty, |
| 129 | }; | 129 | }; |
| 130 | } | 130 | } |
lib/std/Build/WebServer.zig+2-2| ... | @@ -122,8 +122,8 @@ pub fn init(opts: Options) WebServer { | ... | @@ -122,8 +122,8 @@ pub fn init(opts: Options) WebServer { |
| 122 | .update_id = .init(0), | 122 | .update_id = .init(0), |
| 123 | 123 | ||
| 124 | .runner_request_mutex = .init, | 124 | .runner_request_mutex = .init, |
| 125 | .runner_request_ready_cond = .{}, | 125 | .runner_request_ready_cond = .init, |
| 126 | .runner_request_empty_cond = .{}, | 126 | .runner_request_empty_cond = .init, |
| 127 | .runner_request = null, | 127 | .runner_request = null, |
| 128 | }; | 128 | }; |
| 129 | } | 129 | } |
lib/std/Io.zig+142-66| ... | @@ -658,14 +658,6 @@ pub const VTable = struct { | ... | @@ -658,14 +658,6 @@ pub const VTable = struct { |
| 658 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, | 658 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 659 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, | 659 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 660 | 660 | ||
| 661 | mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void, | ||
| 662 | mutexLockUncancelable: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void, | ||
| 663 | mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void, | ||
| 664 | |||
| 665 | conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void, | ||
| 666 | conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void, | ||
| 667 | conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void, | ||
| 668 | |||
| 669 | dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, | 661 | dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, |
| 670 | dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, | 662 | dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void, |
| 671 | dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, | 663 | dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir, |
| ... | @@ -1215,99 +1207,183 @@ pub fn futexWake(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, m | ... | @@ -1215,99 +1207,183 @@ pub fn futexWake(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, m |
| 1215 | } | 1207 | } |
| 1216 | 1208 | ||
| 1217 | pub const Mutex = struct { | 1209 | pub const Mutex = struct { |
| 1218 | state: State, | 1210 | state: std.atomic.Value(State), |
| 1219 | 1211 | ||
| 1220 | pub const State = enum(usize) { | 1212 | pub const init: Mutex = .{ .state = .init(.unlocked) }; |
| 1221 | locked_once = 0b00, | ||
| 1222 | unlocked = 0b01, | ||
| 1223 | contended = 0b10, | ||
| 1224 | /// contended | ||
| 1225 | _, | ||
| 1226 | 1213 | ||
| 1227 | pub fn isUnlocked(state: State) bool { | 1214 | const State = enum(u32) { |
| 1228 | return @intFromEnum(state) & @intFromEnum(State.unlocked) == @intFromEnum(State.unlocked); | 1215 | unlocked, |
| 1229 | } | 1216 | locked_once, |
| 1217 | contended, | ||
| 1230 | }; | 1218 | }; |
| 1231 | 1219 | ||
| 1232 | pub const init: Mutex = .{ .state = .unlocked }; | 1220 | pub fn tryLock(m: *Mutex) bool { |
| 1233 | 1221 | switch (m.state.cmpxchgWeak( | |
| 1234 | pub fn tryLock(mutex: *Mutex) bool { | 1222 | .unlocked, |
| 1235 | const prev_state: State = @enumFromInt(@atomicRmw( | 1223 | .locked_once, |
| 1236 | usize, | ||
| 1237 | @as(*usize, @ptrCast(&mutex.state)), | ||
| 1238 | .And, | ||
| 1239 | ~@intFromEnum(State.unlocked), | ||
| 1240 | .acquire, | 1224 | .acquire, |
| 1241 | )); | 1225 | .monotonic, |
| 1242 | return prev_state.isUnlocked(); | 1226 | ) orelse return true) { |
| 1227 | .unlocked => unreachable, | ||
| 1228 | .locked_once, .contended => return false, | ||
| 1229 | } | ||
| 1243 | } | 1230 | } |
| 1244 | 1231 | ||
| 1245 | pub fn lock(mutex: *Mutex, io: std.Io) Cancelable!void { | 1232 | pub fn lock(m: *Mutex, io: Io) Cancelable!void { |
| 1246 | const prev_state: State = @enumFromInt(@atomicRmw( | 1233 | const initial_state = m.state.cmpxchgWeak( |
| 1247 | usize, | 1234 | .unlocked, |
| 1248 | @as(*usize, @ptrCast(&mutex.state)), | 1235 | .locked_once, |
| 1249 | .And, | ||
| 1250 | ~@intFromEnum(State.unlocked), | ||
| 1251 | .acquire, | 1236 | .acquire, |
| 1252 | )); | 1237 | .monotonic, |
| 1253 | if (prev_state.isUnlocked()) { | 1238 | ) orelse { |
| 1254 | @branchHint(.likely); | 1239 | @branchHint(.likely); |
| 1255 | return; | 1240 | return; |
| 1241 | }; | ||
| 1242 | if (initial_state == .contended) { | ||
| 1243 | try io.futexWait(State, &m.state.raw, .contended); | ||
| 1244 | } | ||
| 1245 | while (m.state.swap(.contended, .acquire) != .unlocked) { | ||
| 1246 | try io.futexWait(State, &m.state.raw, .contended); | ||
| 1256 | } | 1247 | } |
| 1257 | return io.vtable.mutexLock(io.userdata, prev_state, mutex); | ||
| 1258 | } | 1248 | } |
| 1259 | 1249 | ||
| 1260 | /// Same as `lock` but cannot be canceled. | 1250 | pub fn lockUncancelable(m: *Mutex, io: Io) void { |
| 1261 | pub fn lockUncancelable(mutex: *Mutex, io: std.Io) void { | 1251 | const initial_state = m.state.cmpxchgWeak( |
| 1262 | const prev_state: State = @enumFromInt(@atomicRmw( | 1252 | .unlocked, |
| 1263 | usize, | 1253 | .locked_once, |
| 1264 | @as(*usize, @ptrCast(&mutex.state)), | ||
| 1265 | .And, | ||
| 1266 | ~@intFromEnum(State.unlocked), | ||
| 1267 | .acquire, | 1254 | .acquire, |
| 1268 | )); | 1255 | .monotonic, |
| 1269 | if (prev_state.isUnlocked()) { | 1256 | ) orelse { |
| 1270 | @branchHint(.likely); | 1257 | @branchHint(.likely); |
| 1271 | return; | 1258 | return; |
| 1259 | }; | ||
| 1260 | if (initial_state == .contended) { | ||
| 1261 | io.futexWaitUncancelable(State, &m.state.raw, .contended); | ||
| 1262 | } | ||
| 1263 | while (m.state.swap(.contended, .acquire) != .unlocked) { | ||
| 1264 | io.futexWaitUncancelable(State, &m.state.raw, .contended); | ||
| 1272 | } | 1265 | } |
| 1273 | return io.vtable.mutexLockUncancelable(io.userdata, prev_state, mutex); | ||
| 1274 | } | 1266 | } |
| 1275 | 1267 | ||
| 1276 | pub fn unlock(mutex: *Mutex, io: std.Io) void { | 1268 | pub fn unlock(m: *Mutex, io: Io) void { |
| 1277 | const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse { | 1269 | switch (m.state.swap(.unlocked, .release)) { |
| 1278 | @branchHint(.likely); | 1270 | .unlocked => unreachable, |
| 1279 | return; | 1271 | .locked_once => {}, |
| 1280 | }; | 1272 | .contended => { |
| 1281 | assert(prev_state != .unlocked); // mutex not locked | 1273 | @branchHint(.unlikely); |
| 1282 | return io.vtable.mutexUnlock(io.userdata, prev_state, mutex); | 1274 | io.futexWake(State, &m.state.raw, 1); |
| 1275 | }, | ||
| 1276 | } | ||
| 1283 | } | 1277 | } |
| 1284 | }; | 1278 | }; |
| 1285 | 1279 | ||
| 1286 | pub const Condition = struct { | 1280 | pub const Condition = struct { |
| 1287 | state: u64 = 0, | 1281 | state: std.atomic.Value(State), |
| 1282 | /// Incremented whenever the condition is signaled | ||
| 1283 | epoch: std.atomic.Value(u32), | ||
| 1284 | |||
| 1285 | const State = packed struct(u32) { | ||
| 1286 | waiters: u16, | ||
| 1287 | signals: u16, | ||
| 1288 | }; | ||
| 1289 | |||
| 1290 | pub const init: Condition = .{ | ||
| 1291 | .state = .init(.{ .waiters = 0, .signals = 0 }), | ||
| 1292 | .epoch = .init(0), | ||
| 1293 | }; | ||
| 1288 | 1294 | ||
| 1289 | pub fn wait(cond: *Condition, io: Io, mutex: *Mutex) Cancelable!void { | 1295 | pub fn wait(cond: *Condition, io: Io, mutex: *Mutex) Cancelable!void { |
| 1290 | return io.vtable.conditionWait(io.userdata, cond, mutex); | 1296 | try waitInner(cond, io, mutex, false); |
| 1291 | } | 1297 | } |
| 1292 | 1298 | ||
| 1293 | pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void { | 1299 | pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void { |
| 1294 | return io.vtable.conditionWaitUncancelable(io.userdata, cond, mutex); | 1300 | waitInner(cond, io, mutex, true) catch |err| switch (err) { |
| 1301 | error.Canceled => unreachable, | ||
| 1302 | }; | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | fn waitInner(cond: *Condition, io: Io, mutex: *Mutex, uncancelable: bool) Cancelable!void { | ||
| 1306 | var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load | ||
| 1307 | |||
| 1308 | { | ||
| 1309 | const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic); | ||
| 1310 | assert(prev_state.waiters < math.maxInt(u16)); // overflow caused by too many waiters | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | mutex.unlock(io); | ||
| 1314 | defer mutex.lockUncancelable(io); | ||
| 1315 | |||
| 1316 | while (true) { | ||
| 1317 | const result = if (uncancelable) | ||
| 1318 | io.futexWaitUncancelable(u32, &cond.epoch.raw, epoch) | ||
| 1319 | else | ||
| 1320 | io.futexWait(u32, &cond.epoch.raw, epoch); | ||
| 1321 | |||
| 1322 | epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod | ||
| 1323 | |||
| 1324 | // Even on error, try to consume a pending signal first. Otherwise a race might | ||
| 1325 | // cause a signal to get stuck in the state with no corresponding waiter. | ||
| 1326 | { | ||
| 1327 | var prev_state = cond.state.load(.monotonic); | ||
| 1328 | while (prev_state.signals > 0) { | ||
| 1329 | prev_state = cond.state.cmpxchgWeak(prev_state, .{ | ||
| 1330 | .waiters = prev_state.waiters - 1, | ||
| 1331 | .signals = prev_state.signals - 1, | ||
| 1332 | }, .acquire, .monotonic) orelse { | ||
| 1333 | // We successfully consumed a signal. | ||
| 1334 | return; | ||
| 1335 | }; | ||
| 1336 | } | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | // There are no more signals available; this was a spurious wakeup or an error. If it | ||
| 1340 | // was an error, we will remove ourselves as a waiter and return that error. Otherwise, | ||
| 1341 | // we'll loop back to the futex wait. | ||
| 1342 | result catch |err| { | ||
| 1343 | const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic); | ||
| 1344 | assert(prev_state.waiters > 0); // underflow caused by illegal state | ||
| 1345 | return err; | ||
| 1346 | }; | ||
| 1347 | } | ||
| 1295 | } | 1348 | } |
| 1296 | 1349 | ||
| 1297 | pub fn signal(cond: *Condition, io: Io) void { | 1350 | pub fn signal(cond: *Condition, io: Io) void { |
| 1298 | io.vtable.conditionWake(io.userdata, cond, .one); | 1351 | var prev_state = cond.state.load(.monotonic); |
| 1352 | while (prev_state.waiters > prev_state.signals) { | ||
| 1353 | @branchHint(.unlikely); | ||
| 1354 | prev_state = cond.state.cmpxchgWeak(prev_state, .{ | ||
| 1355 | .waiters = prev_state.waiters, | ||
| 1356 | .signals = prev_state.signals + 1, | ||
| 1357 | }, .release, .monotonic) orelse { | ||
| 1358 | // Update the epoch to tell the waiting threads that there are new signals for them. | ||
| 1359 | // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen | ||
| 1360 | // between it observing the epoch and sleeping on it, but this is extraordinarily | ||
| 1361 | // unlikely due to the precise number of calls required. | ||
| 1362 | _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update | ||
| 1363 | io.futexWake(u32, &cond.epoch.raw, 1); | ||
| 1364 | return; | ||
| 1365 | }; | ||
| 1366 | } | ||
| 1299 | } | 1367 | } |
| 1300 | 1368 | ||
| 1301 | pub fn broadcast(cond: *Condition, io: Io) void { | 1369 | pub fn broadcast(cond: *Condition, io: Io) void { |
| 1302 | io.vtable.conditionWake(io.userdata, cond, .all); | 1370 | var prev_state = cond.state.load(.monotonic); |
| 1371 | while (prev_state.waiters > prev_state.signals) { | ||
| 1372 | @branchHint(.unlikely); | ||
| 1373 | prev_state = cond.state.cmpxchgWeak(prev_state, .{ | ||
| 1374 | .waiters = prev_state.waiters, | ||
| 1375 | .signals = prev_state.waiters, | ||
| 1376 | }, .release, .monotonic) orelse { | ||
| 1377 | // Update the epoch to tell the waiting threads that there are new signals for them. | ||
| 1378 | // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen | ||
| 1379 | // between it observing the epoch and sleeping on it, but this is extraordinarily | ||
| 1380 | // unlikely due to the precise number of calls required. | ||
| 1381 | _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update | ||
| 1382 | io.futexWake(u32, &cond.epoch.raw, prev_state.waiters - prev_state.signals); | ||
| 1383 | return; | ||
| 1384 | }; | ||
| 1385 | } | ||
| 1303 | } | 1386 | } |
| 1304 | |||
| 1305 | pub const Wake = enum { | ||
| 1306 | /// Wake up only one thread. | ||
| 1307 | one, | ||
| 1308 | /// Wake up all threads. | ||
| 1309 | all, | ||
| 1310 | }; | ||
| 1311 | }; | 1387 | }; |
| 1312 | 1388 | ||
| 1313 | pub const TypeErasedQueue = struct { | 1389 | pub const TypeErasedQueue = struct { |
lib/std/Io/Threaded.zig-200| ... | @@ -621,14 +621,6 @@ pub fn io(t: *Threaded) Io { | ... | @@ -621,14 +621,6 @@ pub fn io(t: *Threaded) Io { |
| 621 | .futexWaitUncancelable = futexWaitUncancelable, | 621 | .futexWaitUncancelable = futexWaitUncancelable, |
| 622 | .futexWake = futexWake, | 622 | .futexWake = futexWake, |
| 623 | 623 | ||
| 624 | .mutexLock = mutexLock, | ||
| 625 | .mutexLockUncancelable = mutexLockUncancelable, | ||
| 626 | .mutexUnlock = mutexUnlock, | ||
| 627 | |||
| 628 | .conditionWait = conditionWait, | ||
| 629 | .conditionWaitUncancelable = conditionWaitUncancelable, | ||
| 630 | .conditionWake = conditionWake, | ||
| 631 | |||
| 632 | .dirMake = dirMake, | 624 | .dirMake = dirMake, |
| 633 | .dirMakePath = dirMakePath, | 625 | .dirMakePath = dirMakePath, |
| 634 | .dirMakeOpenPath = dirMakeOpenPath, | 626 | .dirMakeOpenPath = dirMakeOpenPath, |
| ... | @@ -721,14 +713,6 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -721,14 +713,6 @@ pub fn ioBasic(t: *Threaded) Io { |
| 721 | .futexWaitUncancelable = futexWaitUncancelable, | 713 | .futexWaitUncancelable = futexWaitUncancelable, |
| 722 | .futexWake = futexWake, | 714 | .futexWake = futexWake, |
| 723 | 715 | ||
| 724 | .mutexLock = mutexLock, | ||
| 725 | .mutexLockUncancelable = mutexLockUncancelable, | ||
| 726 | .mutexUnlock = mutexUnlock, | ||
| 727 | |||
| 728 | .conditionWait = conditionWait, | ||
| 729 | .conditionWaitUncancelable = conditionWaitUncancelable, | ||
| 730 | .conditionWake = conditionWake, | ||
| 731 | |||
| 732 | .dirMake = dirMake, | 716 | .dirMake = dirMake, |
| 733 | .dirMakePath = dirMakePath, | 717 | .dirMakePath = dirMakePath, |
| 734 | .dirMakeOpenPath = dirMakeOpenPath, | 718 | .dirMakeOpenPath = dirMakeOpenPath, |
| ... | @@ -1274,190 +1258,6 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { | ... | @@ -1274,190 +1258,6 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 1274 | } | 1258 | } |
| 1275 | } | 1259 | } |
| 1276 | 1260 | ||
| 1277 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { | ||
| 1278 | if (builtin.single_threaded) unreachable; // Interface should have prevented this. | ||
| 1279 | if (native_os == .netbsd) @panic("TODO"); | ||
| 1280 | if (native_os == .openbsd) @panic("TODO"); | ||
| 1281 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 1282 | const current_thread = Thread.getCurrent(t); | ||
| 1283 | if (prev_state == .contended) { | ||
| 1284 | try current_thread.futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | ||
| 1285 | } | ||
| 1286 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { | ||
| 1287 | try current_thread.futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | ||
| 1288 | } | ||
| 1289 | } | ||
| 1290 | |||
| 1291 | fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { | ||
| 1292 | if (builtin.single_threaded) unreachable; // Interface should have prevented this. | ||
| 1293 | if (native_os == .netbsd) @panic("TODO"); | ||
| 1294 | if (native_os == .openbsd) @panic("TODO"); | ||
| 1295 | _ = userdata; | ||
| 1296 | if (prev_state == .contended) { | ||
| 1297 | Thread.futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | ||
| 1298 | } | ||
| 1299 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { | ||
| 1300 | Thread.futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | ||
| 1301 | } | ||
| 1302 | } | ||
| 1303 | |||
| 1304 | fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { | ||
| 1305 | if (builtin.single_threaded) unreachable; // Interface should have prevented this. | ||
| 1306 | if (native_os == .netbsd) @panic("TODO"); | ||
| 1307 | if (native_os == .openbsd) @panic("TODO"); | ||
| 1308 | _ = userdata; | ||
| 1309 | _ = prev_state; | ||
| 1310 | if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) { | ||
| 1311 | Thread.futexWake(@ptrCast(&mutex.state), 1); | ||
| 1312 | } | ||
| 1313 | } | ||
| 1314 | |||
| 1315 | fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void { | ||
| 1316 | if (builtin.single_threaded) unreachable; // Deadlock. | ||
| 1317 | if (native_os == .netbsd) @panic("TODO"); | ||
| 1318 | if (native_os == .openbsd) @panic("TODO"); | ||
| 1319 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 1320 | const t_io = ioBasic(t); | ||
| 1321 | comptime assert(@TypeOf(cond.state) == u64); | ||
| 1322 | const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state); | ||
| 1323 | const cond_state = &ints[0]; | ||
| 1324 | const cond_epoch = &ints[1]; | ||
| 1325 | const one_waiter = 1; | ||
| 1326 | const waiter_mask = 0xffff; | ||
| 1327 | const one_signal = 1 << 16; | ||
| 1328 | const signal_mask = 0xffff << 16; | ||
| 1329 | var epoch = cond_epoch.load(.acquire); | ||
| 1330 | var state = cond_state.fetchAdd(one_waiter, .monotonic); | ||
| 1331 | assert(state & waiter_mask != waiter_mask); | ||
| 1332 | state += one_waiter; | ||
| 1333 | |||
| 1334 | mutex.unlock(t_io); | ||
| 1335 | defer mutex.lockUncancelable(t_io); | ||
| 1336 | |||
| 1337 | while (true) { | ||
| 1338 | Thread.futexWaitUncancelable(@ptrCast(cond_epoch), epoch); | ||
| 1339 | epoch = cond_epoch.load(.acquire); | ||
| 1340 | state = cond_state.load(.monotonic); | ||
| 1341 | while (state & signal_mask != 0) { | ||
| 1342 | const new_state = state - one_waiter - one_signal; | ||
| 1343 | state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return; | ||
| 1344 | } | ||
| 1345 | } | ||
| 1346 | } | ||
| 1347 | |||
| 1348 | fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void { | ||
| 1349 | if (builtin.single_threaded) unreachable; // Deadlock. | ||
| 1350 | if (native_os == .netbsd) @panic("TODO"); | ||
| 1351 | if (native_os == .openbsd) @panic("TODO"); | ||
| 1352 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 1353 | const current_thread = Thread.getCurrent(t); | ||
| 1354 | const t_io = ioBasic(t); | ||
| 1355 | comptime assert(@TypeOf(cond.state) == u64); | ||
| 1356 | const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state); | ||
| 1357 | const cond_state = &ints[0]; | ||
| 1358 | const cond_epoch = &ints[1]; | ||
| 1359 | const one_waiter = 1; | ||
| 1360 | const waiter_mask = 0xffff; | ||
| 1361 | const one_signal = 1 << 16; | ||
| 1362 | const signal_mask = 0xffff << 16; | ||
| 1363 | // Observe the epoch, then check the state again to see if we should wake up. | ||
| 1364 | // The epoch must be observed before we check the state or we could potentially miss a wake() and deadlock: | ||
| 1365 | // | ||
| 1366 | // - T1: s = LOAD(&state) | ||
| 1367 | // - T2: UPDATE(&s, signal) | ||
| 1368 | // - T2: UPDATE(&epoch, 1) + FUTEX_WAKE(&epoch) | ||
| 1369 | // - T1: e = LOAD(&epoch) (was reordered after the state load) | ||
| 1370 | // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed the state update + the epoch change) | ||
| 1371 | // | ||
| 1372 | // Acquire barrier to ensure the epoch load happens before the state load. | ||
| 1373 | var epoch = cond_epoch.load(.acquire); | ||
| 1374 | var state = cond_state.fetchAdd(one_waiter, .monotonic); | ||
| 1375 | assert(state & waiter_mask != waiter_mask); | ||
| 1376 | state += one_waiter; | ||
| 1377 | |||
| 1378 | mutex.unlock(t_io); | ||
| 1379 | defer mutex.lockUncancelable(t_io); | ||
| 1380 | |||
| 1381 | while (true) { | ||
| 1382 | try current_thread.futexWait(@ptrCast(cond_epoch), epoch); | ||
| 1383 | |||
| 1384 | epoch = cond_epoch.load(.acquire); | ||
| 1385 | state = cond_state.load(.monotonic); | ||
| 1386 | |||
| 1387 | // Try to wake up by consuming a signal and decremented the waiter we | ||
| 1388 | // added previously. Acquire barrier ensures code before the wake() | ||
| 1389 | // which added the signal happens before we decrement it and return. | ||
| 1390 | while (state & signal_mask != 0) { | ||
| 1391 | const new_state = state - one_waiter - one_signal; | ||
| 1392 | state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return; | ||
| 1393 | } | ||
| 1394 | } | ||
| 1395 | } | ||
| 1396 | |||
| 1397 | fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void { | ||
| 1398 | if (builtin.single_threaded) unreachable; // Nothing to wake up. | ||
| 1399 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 1400 | _ = t; | ||
| 1401 | comptime assert(@TypeOf(cond.state) == u64); | ||
| 1402 | const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state); | ||
| 1403 | const cond_state = &ints[0]; | ||
| 1404 | const cond_epoch = &ints[1]; | ||
| 1405 | const one_waiter = 1; | ||
| 1406 | const waiter_mask = 0xffff; | ||
| 1407 | const one_signal = 1 << 16; | ||
| 1408 | const signal_mask = 0xffff << 16; | ||
| 1409 | var state = cond_state.load(.monotonic); | ||
| 1410 | while (true) { | ||
| 1411 | const waiters = (state & waiter_mask) / one_waiter; | ||
| 1412 | const signals = (state & signal_mask) / one_signal; | ||
| 1413 | |||
| 1414 | // Reserves which waiters to wake up by incrementing the signals count. | ||
| 1415 | // Therefore, the signals count is always less than or equal to the | ||
| 1416 | // waiters count. We don't need to Futex.wake if there's nothing to | ||
| 1417 | // wake up or if other wake() threads have reserved to wake up the | ||
| 1418 | // current waiters. | ||
| 1419 | const wakeable = waiters - signals; | ||
| 1420 | if (wakeable == 0) { | ||
| 1421 | return; | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | const to_wake = switch (wake) { | ||
| 1425 | .one => 1, | ||
| 1426 | .all => wakeable, | ||
| 1427 | }; | ||
| 1428 | |||
| 1429 | // Reserve the amount of waiters to wake by incrementing the signals | ||
| 1430 | // count. Release barrier ensures code before the wake() happens before | ||
| 1431 | // the signal it posted and consumed by the wait() threads. | ||
| 1432 | const new_state = state + (one_signal * to_wake); | ||
| 1433 | state = cond_state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse { | ||
| 1434 | // Wake up the waiting threads we reserved above by changing the epoch value. | ||
| 1435 | // | ||
| 1436 | // A waiting thread could miss a wake up if *exactly* ((1<<32)-1) | ||
| 1437 | // wake()s happen between it observing the epoch and sleeping on | ||
| 1438 | // it. This is very unlikely due to how many precise amount of | ||
| 1439 | // Futex.wake() calls that would be between the waiting thread's | ||
| 1440 | // potential preemption. | ||
| 1441 | // | ||
| 1442 | // Release barrier ensures the signal being added to the state | ||
| 1443 | // happens before the epoch is changed. If not, the waiting thread | ||
| 1444 | // could potentially deadlock from missing both the state and epoch | ||
| 1445 | // change: | ||
| 1446 | // | ||
| 1447 | // - T2: UPDATE(&epoch, 1) (reordered before the state change) | ||
| 1448 | // - T1: e = LOAD(&epoch) | ||
| 1449 | // - T1: s = LOAD(&state) | ||
| 1450 | // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch) | ||
| 1451 | // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change) | ||
| 1452 | _ = cond_epoch.fetchAdd(1, .release); | ||
| 1453 | if (native_os == .netbsd) @panic("TODO"); | ||
| 1454 | if (native_os == .openbsd) @panic("TODO"); | ||
| 1455 | Thread.futexWake(@ptrCast(cond_epoch), to_wake); | ||
| 1456 | return; | ||
| 1457 | }; | ||
| 1458 | } | ||
| 1459 | } | ||
| 1460 | |||
| 1461 | const dirMake = switch (native_os) { | 1261 | const dirMake = switch (native_os) { |
| 1462 | .windows => dirMakeWindows, | 1262 | .windows => dirMakeWindows, |
| 1463 | .wasi => dirMakeWasi, | 1263 | .wasi => dirMakeWasi, |