Skip to content

Commit

Permalink
Fix race between _PyParkingLot_Park and _PyParkingLot_UnparkAll w…
Browse files Browse the repository at this point in the history
…hen handling interrupts

There is a potential race when `_PyParkingLot_UnparkAll` is executing in
one thread and another thread is unblocked because of an interrupt in
`_PyParkingLot_Park`. Consider the following scenario:

1. Thread T0 is blocked[^1] in `_PyParkingLot_Park` on address `A`.
2. Thread T1 executes `_PyParkingLot_UnparkAll` on address `A`. It
   finds the `wait_entry` for `T0` and unlinks[^2] its list node.
3. Immediately after (2), T0 is woken up due to an interrupt. It
   then segfaults trying to unlink[^3] the node that was previously
   unlinked in (2).

To fix this we mark each waiter as unparking before releasing the bucket
lock. `_PyParkingLot_Park` will wait to handle the coming wakeup, and not
attempt to unlink the node, when this field is set. `_PyParkingLot_Unpark`
does this already, presumably to handle this case.

[^1]: https://github.com/python/cpython/blob/f35c7c070ca6b49c5d6a97be34e6c02f828f5873/Python/parking_lot.c#L303
[^2]: https://github.com/python/cpython/blob/f35c7c070ca6b49c5d6a97be34e6c02f828f5873/Python/parking_lot.c#L369
[^3]: https://github.com/python/cpython/blob/f35c7c070ca6b49c5d6a97be34e6c02f828f5873/Python/parking_lot.c#L320
  • Loading branch information
mpage committed Feb 3, 2024
1 parent 78c2545 commit d68bf0d
Showing 1 changed file with 5 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Python/parking_lot.c
Original file line number Diff line number Diff line change
Expand Up @@ -356,14 +356,18 @@ _PyParkingLot_Unpark(const void *addr, _Py_unpark_fn_t *fn, void *arg)
void
_PyParkingLot_UnparkAll(const void *addr)
{
struct llist_node *node;
struct llist_node head = LLIST_INIT(head);
Bucket *bucket = &buckets[((uintptr_t)addr) % NUM_BUCKETS];

_PyRawMutex_Lock(&bucket->mutex);
dequeue_all(bucket, addr, &head);
llist_for_each(node, &head) {
struct wait_entry *waiter = llist_data(node, struct wait_entry, node);
waiter->is_unparking = true;
}
_PyRawMutex_Unlock(&bucket->mutex);

struct llist_node *node;
llist_for_each_safe(node, &head) {
struct wait_entry *waiter = llist_data(node, struct wait_entry, node);
llist_remove(node);
Expand Down

0 comments on commit d68bf0d

Please sign in to comment.