| ... | ... | @@ -9,6 +9,7 @@ const RwLock = @This(); |
| 9 | 9 | const std = @import("../std.zig"); |
| 10 | 10 | const builtin = @import("builtin"); |
| 11 | 11 | const assert = std.debug.assert; |
| 12 | const testing = std.testing; |
| 12 | 13 | |
| 13 | 14 | pub const Impl = if (builtin.single_threaded) |
| 14 | 15 | SingleThreadedRwLock |
| ... | ... | @@ -190,7 +191,7 @@ pub const DefaultRwLock = struct { |
| 190 | 191 | _ = @atomicRmw(usize, &rwl.state, .Add, WRITER, .SeqCst); |
| 191 | 192 | rwl.mutex.lock(); |
| 192 | 193 | |
| 193 | | const state = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .SeqCst); |
| 194 | const state = @atomicRmw(usize, &rwl.state, .Add, IS_WRITING -% WRITER, .SeqCst); |
| 194 | 195 | if (state & READER_MASK != 0) |
| 195 | 196 | rwl.semaphore.wait(); |
| 196 | 197 | } |
| ... | ... | @@ -247,3 +248,128 @@ pub const DefaultRwLock = struct { |
| 247 | 248 | rwl.semaphore.post(); |
| 248 | 249 | } |
| 249 | 250 | }; |
| 251 | |
| 252 | test "DefaultRwLock - internal state" { |
| 253 | var rwl = DefaultRwLock{}; |
| 254 | |
| 255 | // The following failed prior to the fix for Issue #13163, |
| 256 | // where the WRITER flag was subtracted by the lock method. |
| 257 | |
| 258 | rwl.lock(); |
| 259 | rwl.unlock(); |
| 260 | try testing.expectEqual(rwl, DefaultRwLock{}); |
| 261 | } |
| 262 | |
| 263 | test "RwLock - smoke test" { |
| 264 | var rwl = RwLock{}; |
| 265 | |
| 266 | rwl.lock(); |
| 267 | try testing.expect(!rwl.tryLock()); |
| 268 | try testing.expect(!rwl.tryLockShared()); |
| 269 | rwl.unlock(); |
| 270 | |
| 271 | try testing.expect(rwl.tryLock()); |
| 272 | try testing.expect(!rwl.tryLock()); |
| 273 | try testing.expect(!rwl.tryLockShared()); |
| 274 | rwl.unlock(); |
| 275 | |
| 276 | rwl.lockShared(); |
| 277 | try testing.expect(!rwl.tryLock()); |
| 278 | try testing.expect(rwl.tryLockShared()); |
| 279 | rwl.unlockShared(); |
| 280 | rwl.unlockShared(); |
| 281 | |
| 282 | try testing.expect(rwl.tryLockShared()); |
| 283 | try testing.expect(!rwl.tryLock()); |
| 284 | try testing.expect(rwl.tryLockShared()); |
| 285 | rwl.unlockShared(); |
| 286 | rwl.unlockShared(); |
| 287 | |
| 288 | rwl.lock(); |
| 289 | rwl.unlock(); |
| 290 | } |
| 291 | |
| 292 | test "RwLock - concurrent access" { |
| 293 | if (builtin.single_threaded) |
| 294 | return; |
| 295 | |
| 296 | const num_writers: usize = 2; |
| 297 | const num_readers: usize = 4; |
| 298 | const num_writes: usize = 10000; |
| 299 | const num_reads: usize = num_writes * 2; |
| 300 | |
| 301 | const Runner = struct { |
| 302 | const Self = @This(); |
| 303 | |
| 304 | rwl: RwLock = .{}, |
| 305 | writes: usize = 0, |
| 306 | reads: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0), |
| 307 | |
| 308 | term1: usize = 0, |
| 309 | term2: usize = 0, |
| 310 | term_sum: usize = 0, |
| 311 | |
| 312 | fn reader(self: *Self) !void { |
| 313 | while (true) { |
| 314 | self.rwl.lockShared(); |
| 315 | defer self.rwl.unlockShared(); |
| 316 | |
| 317 | if (self.writes >= num_writes or self.reads.load(.Unordered) >= num_reads) |
| 318 | break; |
| 319 | |
| 320 | try self.check(); |
| 321 | |
| 322 | _ = self.reads.fetchAdd(1, .Monotonic); |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | fn writer(self: *Self, thread_idx: usize) !void { |
| 327 | var prng = std.rand.DefaultPrng.init(thread_idx); |
| 328 | var rnd = prng.random(); |
| 329 | |
| 330 | while (true) { |
| 331 | self.rwl.lock(); |
| 332 | defer self.rwl.unlock(); |
| 333 | |
| 334 | if (self.writes >= num_writes) |
| 335 | break; |
| 336 | |
| 337 | try self.check(); |
| 338 | |
| 339 | const term1 = rnd.int(usize); |
| 340 | self.term1 = term1; |
| 341 | try std.Thread.yield(); |
| 342 | |
| 343 | const term2 = rnd.int(usize); |
| 344 | self.term2 = term2; |
| 345 | try std.Thread.yield(); |
| 346 | |
| 347 | self.term_sum = term1 +% term2; |
| 348 | self.writes += 1; |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | fn check(self: *const Self) !void { |
| 353 | const term_sum = self.term_sum; |
| 354 | try std.Thread.yield(); |
| 355 | |
| 356 | const term2 = self.term2; |
| 357 | try std.Thread.yield(); |
| 358 | |
| 359 | const term1 = self.term1; |
| 360 | try testing.expectEqual(term_sum, term1 +% term2); |
| 361 | } |
| 362 | }; |
| 363 | |
| 364 | var runner = Runner{}; |
| 365 | var threads: [num_writers + num_readers]std.Thread = undefined; |
| 366 | |
| 367 | for (threads[0..num_writers]) |*t, i| t.* = try std.Thread.spawn(.{}, Runner.writer, .{ &runner, i }); |
| 368 | for (threads[num_writers..]) |*t| t.* = try std.Thread.spawn(.{}, Runner.reader, .{&runner}); |
| 369 | |
| 370 | for (threads) |t| t.join(); |
| 371 | |
| 372 | try testing.expectEqual(num_writes, runner.writes); |
| 373 | |
| 374 | //std.debug.print("reads={}\n", .{ runner.reads.load(.Unordered)}); |
| 375 | } |