1 // Lock implementation
2
3 #include "Python.h"
4
5 #include "pycore_lock.h"
6 #include "pycore_parking_lot.h"
7 #include "pycore_semaphore.h"
8 #include "pycore_time.h" // _PyTime_Add()
9
10 #ifdef MS_WINDOWS
11 # define WIN32_LEAN_AND_MEAN
12 # include <windows.h> // SwitchToThread()
13 #elif defined(HAVE_SCHED_H)
14 # include <sched.h> // sched_yield()
15 #endif
16
17 // If a thread waits on a lock for longer than TIME_TO_BE_FAIR_NS (1 ms), then
18 // the unlocking thread directly hands off ownership of the lock. This avoids
19 // starvation.
20 static const PyTime_t TIME_TO_BE_FAIR_NS = 1000*1000;
21
22 // Spin for a bit before parking the thread. This is only enabled for
23 // `--disable-gil` builds because it is unlikely to be helpful if the GIL is
24 // enabled.
25 #if Py_GIL_DISABLED
26 static const int MAX_SPIN_COUNT = 40;
27 #else
28 static const int MAX_SPIN_COUNT = 0;
29 #endif
30
31 struct mutex_entry {
32 // The time after which the unlocking thread should hand off lock ownership
33 // directly to the waiting thread. Written by the waiting thread.
34 PyTime_t time_to_be_fair;
35
36 // Set to 1 if the lock was handed off. Written by the unlocking thread.
37 int handed_off;
38 };
39
40 static void
_Py_yield(void)41 _Py_yield(void)
42 {
43 #ifdef MS_WINDOWS
44 SwitchToThread();
45 #elif defined(HAVE_SCHED_H)
46 sched_yield();
47 #endif
48 }
49
50 PyLockStatus
_PyMutex_LockTimed(PyMutex * m,PyTime_t timeout,_PyLockFlags flags)51 _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
52 {
53 uint8_t v = _Py_atomic_load_uint8_relaxed(&m->_bits);
54 if ((v & _Py_LOCKED) == 0) {
55 if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, v|_Py_LOCKED)) {
56 return PY_LOCK_ACQUIRED;
57 }
58 }
59 else if (timeout == 0) {
60 return PY_LOCK_FAILURE;
61 }
62
63 PyTime_t now;
64 // silently ignore error: cannot report error to the caller
65 (void)PyTime_MonotonicRaw(&now);
66 PyTime_t endtime = 0;
67 if (timeout > 0) {
68 endtime = _PyTime_Add(now, timeout);
69 }
70
71 struct mutex_entry entry = {
72 .time_to_be_fair = now + TIME_TO_BE_FAIR_NS,
73 .handed_off = 0,
74 };
75
76 Py_ssize_t spin_count = 0;
77 for (;;) {
78 if ((v & _Py_LOCKED) == 0) {
79 // The lock is unlocked. Try to grab it.
80 if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, v|_Py_LOCKED)) {
81 return PY_LOCK_ACQUIRED;
82 }
83 continue;
84 }
85
86 if (!(v & _Py_HAS_PARKED) && spin_count < MAX_SPIN_COUNT) {
87 // Spin for a bit.
88 _Py_yield();
89 spin_count++;
90 continue;
91 }
92
93 if (timeout == 0) {
94 return PY_LOCK_FAILURE;
95 }
96
97 uint8_t newv = v;
98 if (!(v & _Py_HAS_PARKED)) {
99 // We are the first waiter. Set the _Py_HAS_PARKED flag.
100 newv = v | _Py_HAS_PARKED;
101 if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &v, newv)) {
102 continue;
103 }
104 }
105
106 int ret = _PyParkingLot_Park(&m->_bits, &newv, sizeof(newv), timeout,
107 &entry, (flags & _PY_LOCK_DETACH) != 0);
108 if (ret == Py_PARK_OK) {
109 if (entry.handed_off) {
110 // We own the lock now.
111 assert(_Py_atomic_load_uint8_relaxed(&m->_bits) & _Py_LOCKED);
112 return PY_LOCK_ACQUIRED;
113 }
114 }
115 else if (ret == Py_PARK_INTR && (flags & _PY_LOCK_HANDLE_SIGNALS)) {
116 if (Py_MakePendingCalls() < 0) {
117 return PY_LOCK_INTR;
118 }
119 }
120 else if (ret == Py_PARK_TIMEOUT) {
121 assert(timeout >= 0);
122 return PY_LOCK_FAILURE;
123 }
124
125 if (timeout > 0) {
126 timeout = _PyDeadline_Get(endtime);
127 if (timeout <= 0) {
128 // Avoid negative values because those mean block forever.
129 timeout = 0;
130 }
131 }
132
133 v = _Py_atomic_load_uint8_relaxed(&m->_bits);
134 }
135 }
136
137 static void
mutex_unpark(PyMutex * m,struct mutex_entry * entry,int has_more_waiters)138 mutex_unpark(PyMutex *m, struct mutex_entry *entry, int has_more_waiters)
139 {
140 uint8_t v = 0;
141 if (entry) {
142 PyTime_t now;
143 // silently ignore error: cannot report error to the caller
144 (void)PyTime_MonotonicRaw(&now);
145 int should_be_fair = now > entry->time_to_be_fair;
146
147 entry->handed_off = should_be_fair;
148 if (should_be_fair) {
149 v |= _Py_LOCKED;
150 }
151 if (has_more_waiters) {
152 v |= _Py_HAS_PARKED;
153 }
154 }
155 _Py_atomic_store_uint8(&m->_bits, v);
156 }
157
158 int
_PyMutex_TryUnlock(PyMutex * m)159 _PyMutex_TryUnlock(PyMutex *m)
160 {
161 uint8_t v = _Py_atomic_load_uint8(&m->_bits);
162 for (;;) {
163 if ((v & _Py_LOCKED) == 0) {
164 // error: the mutex is not locked
165 return -1;
166 }
167 else if ((v & _Py_HAS_PARKED)) {
168 // wake up a single thread
169 _PyParkingLot_Unpark(&m->_bits, (_Py_unpark_fn_t *)mutex_unpark, m);
170 return 0;
171 }
172 else if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, _Py_UNLOCKED)) {
173 // fast-path: no waiters
174 return 0;
175 }
176 }
177 }
178
179 // _PyRawMutex stores a linked list of `struct raw_mutex_entry`, one for each
180 // thread waiting on the mutex, directly in the mutex itself.
181 struct raw_mutex_entry {
182 struct raw_mutex_entry *next;
183 _PySemaphore sema;
184 };
185
186 void
_PyRawMutex_LockSlow(_PyRawMutex * m)187 _PyRawMutex_LockSlow(_PyRawMutex *m)
188 {
189 struct raw_mutex_entry waiter;
190 _PySemaphore_Init(&waiter.sema);
191
192 uintptr_t v = _Py_atomic_load_uintptr(&m->v);
193 for (;;) {
194 if ((v & _Py_LOCKED) == 0) {
195 // Unlocked: try to grab it (even if it has a waiter).
196 if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, v|_Py_LOCKED)) {
197 break;
198 }
199 continue;
200 }
201
202 // Locked: try to add ourselves as a waiter.
203 waiter.next = (struct raw_mutex_entry *)(v & ~1);
204 uintptr_t desired = ((uintptr_t)&waiter)|_Py_LOCKED;
205 if (!_Py_atomic_compare_exchange_uintptr(&m->v, &v, desired)) {
206 continue;
207 }
208
209 // Wait for us to be woken up. Note that we still have to lock the
210 // mutex ourselves: it is NOT handed off to us.
211 _PySemaphore_Wait(&waiter.sema, -1, /*detach=*/0);
212 }
213
214 _PySemaphore_Destroy(&waiter.sema);
215 }
216
217 void
_PyRawMutex_UnlockSlow(_PyRawMutex * m)218 _PyRawMutex_UnlockSlow(_PyRawMutex *m)
219 {
220 uintptr_t v = _Py_atomic_load_uintptr(&m->v);
221 for (;;) {
222 if ((v & _Py_LOCKED) == 0) {
223 Py_FatalError("unlocking mutex that is not locked");
224 }
225
226 struct raw_mutex_entry *waiter = (struct raw_mutex_entry *)(v & ~1);
227 if (waiter) {
228 uintptr_t next_waiter = (uintptr_t)waiter->next;
229 if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, next_waiter)) {
230 _PySemaphore_Wakeup(&waiter->sema);
231 return;
232 }
233 }
234 else {
235 if (_Py_atomic_compare_exchange_uintptr(&m->v, &v, _Py_UNLOCKED)) {
236 return;
237 }
238 }
239 }
240 }
241
242 int
_PyEvent_IsSet(PyEvent * evt)243 _PyEvent_IsSet(PyEvent *evt)
244 {
245 uint8_t v = _Py_atomic_load_uint8(&evt->v);
246 return v == _Py_LOCKED;
247 }
248
249 void
_PyEvent_Notify(PyEvent * evt)250 _PyEvent_Notify(PyEvent *evt)
251 {
252 uintptr_t v = _Py_atomic_exchange_uint8(&evt->v, _Py_LOCKED);
253 if (v == _Py_UNLOCKED) {
254 // no waiters
255 return;
256 }
257 else if (v == _Py_LOCKED) {
258 // event already set
259 return;
260 }
261 else {
262 assert(v == _Py_HAS_PARKED);
263 _PyParkingLot_UnparkAll(&evt->v);
264 }
265 }
266
267 void
PyEvent_Wait(PyEvent * evt)268 PyEvent_Wait(PyEvent *evt)
269 {
270 while (!PyEvent_WaitTimed(evt, -1, /*detach=*/1))
271 ;
272 }
273
274 int
PyEvent_WaitTimed(PyEvent * evt,PyTime_t timeout_ns,int detach)275 PyEvent_WaitTimed(PyEvent *evt, PyTime_t timeout_ns, int detach)
276 {
277 for (;;) {
278 uint8_t v = _Py_atomic_load_uint8(&evt->v);
279 if (v == _Py_LOCKED) {
280 // event already set
281 return 1;
282 }
283 if (v == _Py_UNLOCKED) {
284 if (!_Py_atomic_compare_exchange_uint8(&evt->v, &v, _Py_HAS_PARKED)) {
285 continue;
286 }
287 }
288
289 uint8_t expected = _Py_HAS_PARKED;
290 (void) _PyParkingLot_Park(&evt->v, &expected, sizeof(evt->v),
291 timeout_ns, NULL, detach);
292
293 return _Py_atomic_load_uint8(&evt->v) == _Py_LOCKED;
294 }
295 }
296
297 static int
unlock_once(_PyOnceFlag * o,int res)298 unlock_once(_PyOnceFlag *o, int res)
299 {
300 // On success (res=0), we set the state to _Py_ONCE_INITIALIZED.
301 // On failure (res=-1), we reset the state to _Py_UNLOCKED.
302 uint8_t new_value;
303 switch (res) {
304 case -1: new_value = _Py_UNLOCKED; break;
305 case 0: new_value = _Py_ONCE_INITIALIZED; break;
306 default: {
307 Py_FatalError("invalid result from _PyOnceFlag_CallOnce");
308 Py_UNREACHABLE();
309 break;
310 }
311 }
312
313 uint8_t old_value = _Py_atomic_exchange_uint8(&o->v, new_value);
314 if ((old_value & _Py_HAS_PARKED) != 0) {
315 // wake up anyone waiting on the once flag
316 _PyParkingLot_UnparkAll(&o->v);
317 }
318 return res;
319 }
320
321 int
_PyOnceFlag_CallOnceSlow(_PyOnceFlag * flag,_Py_once_fn_t * fn,void * arg)322 _PyOnceFlag_CallOnceSlow(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg)
323 {
324 uint8_t v = _Py_atomic_load_uint8(&flag->v);
325 for (;;) {
326 if (v == _Py_UNLOCKED) {
327 if (!_Py_atomic_compare_exchange_uint8(&flag->v, &v, _Py_LOCKED)) {
328 continue;
329 }
330 int res = fn(arg);
331 return unlock_once(flag, res);
332 }
333
334 if (v == _Py_ONCE_INITIALIZED) {
335 return 0;
336 }
337
338 // The once flag is initializing (locked).
339 assert((v & _Py_LOCKED));
340 if (!(v & _Py_HAS_PARKED)) {
341 // We are the first waiter. Set the _Py_HAS_PARKED flag.
342 uint8_t new_value = v | _Py_HAS_PARKED;
343 if (!_Py_atomic_compare_exchange_uint8(&flag->v, &v, new_value)) {
344 continue;
345 }
346 v = new_value;
347 }
348
349 // Wait for initialization to finish.
350 _PyParkingLot_Park(&flag->v, &v, sizeof(v), -1, NULL, 1);
351 v = _Py_atomic_load_uint8(&flag->v);
352 }
353 }
354
355 static int
recursive_mutex_is_owned_by(_PyRecursiveMutex * m,PyThread_ident_t tid)356 recursive_mutex_is_owned_by(_PyRecursiveMutex *m, PyThread_ident_t tid)
357 {
358 return _Py_atomic_load_ullong_relaxed(&m->thread) == tid;
359 }
360
361 int
_PyRecursiveMutex_IsLockedByCurrentThread(_PyRecursiveMutex * m)362 _PyRecursiveMutex_IsLockedByCurrentThread(_PyRecursiveMutex *m)
363 {
364 return recursive_mutex_is_owned_by(m, PyThread_get_thread_ident_ex());
365 }
366
367 void
_PyRecursiveMutex_Lock(_PyRecursiveMutex * m)368 _PyRecursiveMutex_Lock(_PyRecursiveMutex *m)
369 {
370 PyThread_ident_t thread = PyThread_get_thread_ident_ex();
371 if (recursive_mutex_is_owned_by(m, thread)) {
372 m->level++;
373 return;
374 }
375 PyMutex_Lock(&m->mutex);
376 _Py_atomic_store_ullong_relaxed(&m->thread, thread);
377 assert(m->level == 0);
378 }
379
380 void
_PyRecursiveMutex_Unlock(_PyRecursiveMutex * m)381 _PyRecursiveMutex_Unlock(_PyRecursiveMutex *m)
382 {
383 PyThread_ident_t thread = PyThread_get_thread_ident_ex();
384 if (!recursive_mutex_is_owned_by(m, thread)) {
385 Py_FatalError("unlocking a recursive mutex that is not owned by the"
386 " current thread");
387 }
388 if (m->level > 0) {
389 m->level--;
390 return;
391 }
392 assert(m->level == 0);
393 _Py_atomic_store_ullong_relaxed(&m->thread, 0);
394 PyMutex_Unlock(&m->mutex);
395 }
396
397 #define _Py_WRITE_LOCKED 1
398 #define _PyRWMutex_READER_SHIFT 2
399 #define _Py_RWMUTEX_MAX_READERS (UINTPTR_MAX >> _PyRWMutex_READER_SHIFT)
400
401 static uintptr_t
rwmutex_set_parked_and_wait(_PyRWMutex * rwmutex,uintptr_t bits)402 rwmutex_set_parked_and_wait(_PyRWMutex *rwmutex, uintptr_t bits)
403 {
404 // Set _Py_HAS_PARKED and wait until we are woken up.
405 if ((bits & _Py_HAS_PARKED) == 0) {
406 uintptr_t newval = bits | _Py_HAS_PARKED;
407 if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
408 &bits, newval)) {
409 return bits;
410 }
411 bits = newval;
412 }
413
414 _PyParkingLot_Park(&rwmutex->bits, &bits, sizeof(bits), -1, NULL, 1);
415 return _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
416 }
417
418 // The number of readers holding the lock
419 static uintptr_t
rwmutex_reader_count(uintptr_t bits)420 rwmutex_reader_count(uintptr_t bits)
421 {
422 return bits >> _PyRWMutex_READER_SHIFT;
423 }
424
425 void
_PyRWMutex_RLock(_PyRWMutex * rwmutex)426 _PyRWMutex_RLock(_PyRWMutex *rwmutex)
427 {
428 uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
429 for (;;) {
430 if ((bits & _Py_WRITE_LOCKED)) {
431 // A writer already holds the lock.
432 bits = rwmutex_set_parked_and_wait(rwmutex, bits);
433 continue;
434 }
435 else if ((bits & _Py_HAS_PARKED)) {
436 // Reader(s) hold the lock (or just gave up the lock), but there is
437 // at least one waiting writer. We can't grab the lock because we
438 // don't want to starve the writer. Instead, we park ourselves and
439 // wait for the writer to eventually wake us up.
440 bits = rwmutex_set_parked_and_wait(rwmutex, bits);
441 continue;
442 }
443 else {
444 // The lock is unlocked or read-locked. Try to grab it.
445 assert(rwmutex_reader_count(bits) < _Py_RWMUTEX_MAX_READERS);
446 uintptr_t newval = bits + (1 << _PyRWMutex_READER_SHIFT);
447 if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
448 &bits, newval)) {
449 continue;
450 }
451 return;
452 }
453 }
454 }
455
456 void
_PyRWMutex_RUnlock(_PyRWMutex * rwmutex)457 _PyRWMutex_RUnlock(_PyRWMutex *rwmutex)
458 {
459 uintptr_t bits = _Py_atomic_add_uintptr(&rwmutex->bits, -(1 << _PyRWMutex_READER_SHIFT));
460 assert(rwmutex_reader_count(bits) > 0 && "lock was not read-locked");
461 bits -= (1 << _PyRWMutex_READER_SHIFT);
462
463 if (rwmutex_reader_count(bits) == 0 && (bits & _Py_HAS_PARKED)) {
464 _PyParkingLot_UnparkAll(&rwmutex->bits);
465 return;
466 }
467 }
468
469 void
_PyRWMutex_Lock(_PyRWMutex * rwmutex)470 _PyRWMutex_Lock(_PyRWMutex *rwmutex)
471 {
472 uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits);
473 for (;;) {
474 // If there are no active readers and it's not already write-locked,
475 // then we can grab the lock.
476 if ((bits & ~_Py_HAS_PARKED) == 0) {
477 if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits,
478 &bits,
479 bits | _Py_WRITE_LOCKED)) {
480 continue;
481 }
482 return;
483 }
484
485 // Otherwise, we have to wait.
486 bits = rwmutex_set_parked_and_wait(rwmutex, bits);
487 }
488 }
489
490 void
_PyRWMutex_Unlock(_PyRWMutex * rwmutex)491 _PyRWMutex_Unlock(_PyRWMutex *rwmutex)
492 {
493 uintptr_t old_bits = _Py_atomic_exchange_uintptr(&rwmutex->bits, 0);
494
495 assert((old_bits & _Py_WRITE_LOCKED) && "lock was not write-locked");
496 assert(rwmutex_reader_count(old_bits) == 0 && "lock was read-locked");
497
498 if ((old_bits & _Py_HAS_PARKED) != 0) {
499 _PyParkingLot_UnparkAll(&rwmutex->bits);
500 }
501 }
502
503 #define SEQLOCK_IS_UPDATING(sequence) (sequence & 0x01)
504
_PySeqLock_LockWrite(_PySeqLock * seqlock)505 void _PySeqLock_LockWrite(_PySeqLock *seqlock)
506 {
507 // lock by moving to an odd sequence number
508 uint32_t prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence);
509 while (1) {
510 if (SEQLOCK_IS_UPDATING(prev)) {
511 // Someone else is currently updating the cache
512 _Py_yield();
513 prev = _Py_atomic_load_uint32_relaxed(&seqlock->sequence);
514 }
515 else if (_Py_atomic_compare_exchange_uint32(&seqlock->sequence, &prev, prev + 1)) {
516 // We've locked the cache
517 _Py_atomic_fence_release();
518 break;
519 }
520 else {
521 _Py_yield();
522 }
523 }
524 }
525
_PySeqLock_AbandonWrite(_PySeqLock * seqlock)526 void _PySeqLock_AbandonWrite(_PySeqLock *seqlock)
527 {
528 uint32_t new_seq = _Py_atomic_load_uint32_relaxed(&seqlock->sequence) - 1;
529 assert(!SEQLOCK_IS_UPDATING(new_seq));
530 _Py_atomic_store_uint32(&seqlock->sequence, new_seq);
531 }
532
_PySeqLock_UnlockWrite(_PySeqLock * seqlock)533 void _PySeqLock_UnlockWrite(_PySeqLock *seqlock)
534 {
535 uint32_t new_seq = _Py_atomic_load_uint32_relaxed(&seqlock->sequence) + 1;
536 assert(!SEQLOCK_IS_UPDATING(new_seq));
537 _Py_atomic_store_uint32(&seqlock->sequence, new_seq);
538 }
539
_PySeqLock_BeginRead(_PySeqLock * seqlock)540 uint32_t _PySeqLock_BeginRead(_PySeqLock *seqlock)
541 {
542 uint32_t sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence);
543 while (SEQLOCK_IS_UPDATING(sequence)) {
544 _Py_yield();
545 sequence = _Py_atomic_load_uint32_acquire(&seqlock->sequence);
546 }
547
548 return sequence;
549 }
550
_PySeqLock_EndRead(_PySeqLock * seqlock,uint32_t previous)551 int _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous)
552 {
553 // gh-121368: We need an explicit acquire fence here to ensure that
554 // this load of the sequence number is not reordered before any loads
555 // within the read lock.
556 _Py_atomic_fence_acquire();
557
558 if (_Py_atomic_load_uint32_relaxed(&seqlock->sequence) == previous) {
559 return 1;
560 }
561
562 _Py_yield();
563 return 0;
564 }
565
_PySeqLock_AfterFork(_PySeqLock * seqlock)566 int _PySeqLock_AfterFork(_PySeqLock *seqlock)
567 {
568 // Synchronize again and validate that the entry hasn't been updated
569 // while we were readying the values.
570 if (SEQLOCK_IS_UPDATING(seqlock->sequence)) {
571 seqlock->sequence = 0;
572 return 1;
573 }
574
575 return 0;
576 }
577
578 #undef PyMutex_Lock
579 void
PyMutex_Lock(PyMutex * m)580 PyMutex_Lock(PyMutex *m)
581 {
582 _PyMutex_LockTimed(m, -1, _PY_LOCK_DETACH);
583 }
584
585 #undef PyMutex_Unlock
586 void
PyMutex_Unlock(PyMutex * m)587 PyMutex_Unlock(PyMutex *m)
588 {
589 if (_PyMutex_TryUnlock(m) < 0) {
590 Py_FatalError("unlocking mutex that is not locked");
591 }
592 }
593