@@ -27,7 +27,8 @@ const os = std.os;
2727const assert = std .debug .assert ;
2828const testing = std .testing ;
2929const Atomic = std .atomic .Atomic ;
30- const Futex = std .Thread .Futex ;
30+ const Thread = std .Thread ;
31+ const Futex = Thread .Futex ;
3132
3233impl : Impl = .{},
3334
@@ -51,7 +52,12 @@ pub fn unlock(self: *Mutex) void {
5152 self .impl .unlock ();
5253}
5354
54- const Impl = if (builtin .single_threaded )
55+ const Impl = if (builtin .mode == .Debug and ! builtin .single_threaded )
56+ DebugImpl
57+ else
58+ ReleaseImpl ;
59+
60+ const ReleaseImpl = if (builtin .single_threaded )
5561 SingleThreadedImpl
5662else if (builtin .os .tag == .windows )
5763 WindowsImpl
@@ -60,22 +66,50 @@ else if (builtin.os.tag.isDarwin())
6066else
6167 FutexImpl ;
6268
69+ const DebugImpl = struct {
70+ locking_thread : Atomic (Thread .Id ) = Atomic (Thread .Id ).init (0 ), // 0 means it's not locked.
71+ impl : ReleaseImpl = .{},
72+
73+ inline fn tryLock (self : * @This ()) bool {
74+ const locking = self .impl .tryLock ();
75+ if (locking ) {
76+ self .locking_thread .store (Thread .getCurrentId (), .Unordered );
77+ }
78+ return locking ;
79+ }
80+
81+ inline fn lock (self : * @This ()) void {
82+ const current_id = Thread .getCurrentId ();
83+ if (self .locking_thread .load (.Unordered ) == current_id and current_id != 0 ) {
84+ @panic ("Deadlock detected" );
85+ }
86+ self .impl .lock ();
87+ self .locking_thread .store (current_id , .Unordered );
88+ }
89+
90+ inline fn unlock (self : * @This ()) void {
91+ assert (self .locking_thread .load (.Unordered ) == Thread .getCurrentId ());
92+ self .locking_thread .store (0 , .Unordered );
93+ self .impl .unlock ();
94+ }
95+ };
96+
6397const SingleThreadedImpl = struct {
6498 is_locked : bool = false ,
6599
66- fn tryLock (self : * Impl ) bool {
100+ fn tryLock (self : * @This () ) bool {
67101 if (self .is_locked ) return false ;
68102 self .is_locked = true ;
69103 return true ;
70104 }
71105
72- fn lock (self : * Impl ) void {
106+ fn lock (self : * @This () ) void {
73107 if (! self .tryLock ()) {
74108 unreachable ; // deadlock detected
75109 }
76110 }
77111
78- fn unlock (self : * Impl ) void {
112+ fn unlock (self : * @This () ) void {
79113 assert (self .is_locked );
80114 self .is_locked = false ;
81115 }
@@ -86,15 +120,15 @@ const SingleThreadedImpl = struct {
86120const WindowsImpl = struct {
87121 srwlock : os.windows.SRWLOCK = .{},
88122
89- fn tryLock (self : * Impl ) bool {
123+ fn tryLock (self : * @This () ) bool {
90124 return os .windows .kernel32 .TryAcquireSRWLockExclusive (& self .srwlock ) != os .windows .FALSE ;
91125 }
92126
93- fn lock (self : * Impl ) void {
127+ fn lock (self : * @This () ) void {
94128 os .windows .kernel32 .AcquireSRWLockExclusive (& self .srwlock );
95129 }
96130
97- fn unlock (self : * Impl ) void {
131+ fn unlock (self : * @This () ) void {
98132 os .windows .kernel32 .ReleaseSRWLockExclusive (& self .srwlock );
99133 }
100134};
@@ -103,15 +137,15 @@ const WindowsImpl = struct {
103137const DarwinImpl = struct {
104138 oul : os.darwin.os_unfair_lock = .{},
105139
106- fn tryLock (self : * Impl ) bool {
140+ fn tryLock (self : * @This () ) bool {
107141 return os .darwin .os_unfair_lock_trylock (& self .oul );
108142 }
109143
110- fn lock (self : * Impl ) void {
144+ fn lock (self : * @This () ) void {
111145 os .darwin .os_unfair_lock_lock (& self .oul );
112146 }
113147
114- fn unlock (self : * Impl ) void {
148+ fn unlock (self : * @This () ) void {
115149 os .darwin .os_unfair_lock_unlock (& self .oul );
116150 }
117151};
@@ -123,19 +157,19 @@ const FutexImpl = struct {
123157 const locked = 0b01 ;
124158 const contended = 0b11 ; // must contain the `locked` bit for x86 optimization below
125159
126- fn tryLock (self : * Impl ) bool {
160+ fn tryLock (self : * @This () ) bool {
127161 // Lock with compareAndSwap instead of tryCompareAndSwap to avoid reporting spurious CAS failure.
128162 return self .lockFast ("compareAndSwap" );
129163 }
130164
131- fn lock (self : * Impl ) void {
165+ fn lock (self : * @This () ) void {
132166 // Lock with tryCompareAndSwap instead of compareAndSwap due to being more inline-able on LL/SC archs like ARM.
133167 if (! self .lockFast ("tryCompareAndSwap" )) {
134168 self .lockSlow ();
135169 }
136170 }
137171
138- inline fn lockFast (self : * Impl , comptime casFn : []const u8 ) bool {
172+ inline fn lockFast (self : * @This () , comptime casFn : []const u8 ) bool {
139173 // On x86, use `lock bts` instead of `lock cmpxchg` as:
140174 // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
141175 // - `lock bts` is smaller instruction-wise which makes it better for inlining
@@ -149,7 +183,7 @@ const FutexImpl = struct {
149183 return @field (self .state , casFn )(unlocked , locked , .Acquire , .Monotonic ) == null ;
150184 }
151185
152- fn lockSlow (self : * Impl ) void {
186+ fn lockSlow (self : * @This () ) void {
153187 @setCold (true );
154188
155189 // Avoid doing an atomic swap below if we already know the state is contended.
@@ -172,7 +206,7 @@ const FutexImpl = struct {
172206 }
173207 }
174208
175- fn unlock (self : * Impl ) void {
209+ fn unlock (self : * @This () ) void {
176210 // Unlock the mutex and wake up a waiting thread if any.
177211 //
178212 // A waiting thread will acquire with `contended` instead of `locked`
@@ -228,7 +262,7 @@ test "Mutex - many uncontended" {
228262
229263 const Runner = struct {
230264 mutex : Mutex = .{},
231- thread : std. Thread = undefined ,
265+ thread : Thread = undefined ,
232266 counter : NonAtomicCounter = .{},
233267
234268 fn run (self : * @This ()) void {
@@ -243,7 +277,7 @@ test "Mutex - many uncontended" {
243277 };
244278
245279 var runners = [_ ]Runner {.{}} ** num_threads ;
246- for (runners ) | * r | r .thread = try std . Thread .spawn (.{}, Runner .run , .{r });
280+ for (runners ) | * r | r .thread = try Thread .spawn (.{}, Runner .run , .{r });
247281 for (runners ) | r | r .thread .join ();
248282 for (runners ) | r | try testing .expectEqual (r .counter .get (), num_increments );
249283}
@@ -265,7 +299,7 @@ test "Mutex - many contended" {
265299 var i : usize = num_increments ;
266300 while (i > 0 ) : (i -= 1 ) {
267301 // Occasionally hint to let another thread run.
268- defer if (i % 100 == 0 ) std . Thread .yield () catch {};
302+ defer if (i % 100 == 0 ) Thread .yield () catch {};
269303
270304 self .mutex .lock ();
271305 defer self .mutex .unlock ();
@@ -277,8 +311,8 @@ test "Mutex - many contended" {
277311
278312 var runner = Runner {};
279313
280- var threads : [num_threads ]std. Thread = undefined ;
281- for (threads ) | * t | t .* = try std . Thread .spawn (.{}, Runner .run , .{& runner });
314+ var threads : [num_threads ]Thread = undefined ;
315+ for (threads ) | * t | t .* = try Thread .spawn (.{}, Runner .run , .{& runner });
282316 for (threads ) | t | t .join ();
283317
284318 try testing .expectEqual (runner .counter .get (), num_increments * num_threads );
0 commit comments