-
-
Notifications
You must be signed in to change notification settings - Fork 357
Add ability to break parking lots, stop locks from stalling #3081
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
3bd67a5
4dfa1ad
543a087
c36cdad
127c5fc
1f75d44
6835e87
3b86e80
94ff9a2
eb7a451
e7d7205
c89fb2a
277c7da
21cf0d6
45f78f4
ec48863
c742a52
7a1ce5b
cc97cca
b81e297
1d7ece3
b826210
92f9799
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,6 +76,7 @@ | |
from typing import TYPE_CHECKING | ||
|
||
import attrs | ||
import outcome | ||
|
||
from .. import _core | ||
from .._util import final | ||
|
@@ -86,6 +87,31 @@ | |
from ._run import Task | ||
|
||
|
||
GLOBAL_PARKING_LOT_BREAKER: dict[Task, list[ParkingLot]] = {} | ||
|
||
|
||
def add_parking_lot_breaker(task: Task, lot: ParkingLot) -> None: | ||
"""Register a task as a breaker for a lot. This means that if the task exits without | ||
having unparked from the lot, then the lot will break and raise an error for all tasks | ||
parked in the lot, as well as any future task that attempt to park in it.""" | ||
if task not in GLOBAL_PARKING_LOT_BREAKER: | ||
GLOBAL_PARKING_LOT_BREAKER[task] = [lot] | ||
else: | ||
GLOBAL_PARKING_LOT_BREAKER[task].append(lot) | ||
|
||
|
||
def remove_parking_lot_breaker(task: Task, lot: ParkingLot) -> None: | ||
"""Deregister a task as a breaker for a lot. See :func:`add_parking_lot_breaker`.""" | ||
try: | ||
GLOBAL_PARKING_LOT_BREAKER[task].remove(lot) | ||
except (KeyError, ValueError): | ||
raise RuntimeError( | ||
"Attempted to remove task as breaker for a lot it is not registered for", | ||
) from None | ||
if not GLOBAL_PARKING_LOT_BREAKER[task]: | ||
del GLOBAL_PARKING_LOT_BREAKER[task] | ||
|
||
|
||
@attrs.frozen | ||
class ParkingLotStatistics: | ||
"""An object containing debugging information for a ParkingLot. | ||
|
@@ -118,6 +144,7 @@ class ParkingLot: | |
# {task: None}, we just want a deque where we can quickly delete random | ||
# items | ||
_parked: OrderedDict[Task, None] = attrs.field(factory=OrderedDict, init=False) | ||
broken_by: Task | None = None | ||
|
||
def __len__(self) -> int: | ||
"""Returns the number of parked tasks.""" | ||
|
@@ -136,7 +163,15 @@ async def park(self) -> None: | |
"""Park the current task until woken by a call to :meth:`unpark` or | ||
:meth:`unpark_all`. | ||
|
||
Raises: | ||
BrokenResourceError: if attempting to park in a broken lot, or the lot | ||
breaks before we get to unpark. | ||
|
||
""" | ||
if self.broken_by is not None: | ||
raise _core.BrokenResourceError( | ||
f"Attempted to park in parking lot broken by {self.broken_by}", | ||
) | ||
task = _core.current_task() | ||
self._parked[task] = None | ||
task.custom_sleep_data = self | ||
|
@@ -234,6 +269,23 @@ def repark_all(self, new_lot: ParkingLot) -> None: | |
""" | ||
return self.repark(new_lot, count=len(self)) | ||
|
||
def break_lot(self, task: Task) -> None: | ||
jakkdl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Break this lot, causing all parked tasks to raise an error, and any | ||
future tasks attempting to park (and unpark? repark?) to error. The error | ||
A5rocks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
contains a reference to the task sent as a parameter.""" | ||
self.broken_by = task | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just now realizing that this relationship is a bit flawed. Lots can only be marked as broken by one task, but multiple tasks could be marked as a parking lot breaker and exit (or a task multiple times, but that's handled already). I'm not sure what's a good idea. We can't raise any error because there's no good place to raise errors, but I suppose we could warn? Or maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having a list feels overkill, and only really being useful in the case of multiple independent coding errors all leading to them wanting to break a lot. It feels like what would more commonly happen is multiple instances of the same task breaking a lot for the same reason, in which case the list would just fill up with tons of duplicates. Or cases where once one task has broken a lot it causes multiple subsequent tasks to re-break it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should warning be raised if the same task breaks a lot multiple times? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that sounds like a fine warning and a task breaking a lot multiple times is probably fine. After all we allow nesting add/remove_parking_lot_breaker |
||
|
||
# TODO: is there any reason to use self._pop_several? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably some thin veneer of thread safety? Or guaranteeing that we'll remove every task we raise an error in, even if somehow Neither is really something we care about but probably worth using cause it's a simple drop in replacement anyways. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
for parked_task in self._parked: | ||
# TODO: weird to phrase this one, we maybe should reraise this error in Lock? | ||
jakkdl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_core.reschedule( | ||
parked_task, | ||
outcome.Error( | ||
_core.BrokenResourceError(f"Parking lot broken by {task}"), | ||
), | ||
) | ||
self._parked.clear() | ||
|
||
def statistics(self) -> ParkingLotStatistics: | ||
"""Return an object containing debugging information. | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.