• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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