Skip to content
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

Add ability to break parking lots, stop locks from stalling #3081

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

jakkdl
Copy link
Member

@jakkdl jakkdl commented Sep 6, 2024

fixes #3035

TODO:

  • newsfragment(s)
  • docstrings
  • docs
  • tests of directly using the parking lot functions
  • lock test that checks that handing over the owner works (this is indirectly tested by a lot of other tests, but good to have a standalone test)
  • reraise the exception in Lock/_LockImpl to get a better error message

Copy link

codecov bot commented Sep 6, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Please upload report for BASE (main@b834f73). Learn more about missing BASE report.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3081   +/-   ##
=======================================
  Coverage        ?   99.60%           
=======================================
  Files           ?      121           
  Lines           ?    18004           
  Branches        ?     3238           
=======================================
  Hits            ?    17933           
  Misses          ?       50           
  Partials        ?       21           
Files with missing lines Coverage Δ
src/trio/_core/__init__.py 100.00% <100.00%> (ø)
src/trio/_core/_parking_lot.py 100.00% <100.00%> (ø)
src/trio/_core/_run.py 99.38% <100.00%> (ø)
src/trio/_core/_tests/test_parking_lot.py 100.00% <100.00%> (ø)
src/trio/_sync.py 100.00% <100.00%> (ø)
src/trio/_tests/test_sync.py 100.00% <100.00%> (ø)
src/trio/lowlevel.py 100.00% <ø> (ø)

src/trio/_core/_parking_lot.py Outdated Show resolved Hide resolved
src/trio/_core/_parking_lot.py Outdated Show resolved Hide resolved
src/trio/_core/_parking_lot.py Outdated Show resolved Hide resolved
@A5rocks
Copy link
Contributor

A5rocks commented Sep 6, 2024

@njsmith cause this is interface design, do you think there's a better way to express all of this?

contains a reference to the task sent as a parameter."""
self.broken_by = task

# TODO: is there any reason to use self._pop_several?
Copy link
Contributor

@A5rocks A5rocks Sep 7, 2024

Choose a reason for hiding this comment

The 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 _core.reschedule raises an error (... well, we'll remove the task where this happens too. Oh well, still better behavior).

Neither is really something we care about but probably worth using cause it's a simple drop in replacement anyways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unpark() above would have the same theoretical issue, if reschedule() failed it could drop tasks too. Probably not something to worry about, if that fails we've got bigger problems.

@jakkdl
Copy link
Member Author

jakkdl commented Sep 10, 2024

@njsmith cause this is interface design, do you think there's a better way to express all of this?

as we don't actually have anybody requesting the functionality on ParkingLot or add_parking_lot_breaker/remove_parking_lot_breaker, we could opt not to make these public for now and document them as an experimental API. Or I suppose make them public but with a warning text in the docstring that they're experimental.

…as it in acquire, update docstrings. docs are failing to build locally but idk wth is wrong
@jakkdl jakkdl marked this pull request as ready for review September 10, 2024 13:14
Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me! I'd appreciate a review by another experienced maintainer but I'd be inclined to merge once minor comments are addressed 😁

src/trio/_sync.py Outdated Show resolved Hide resolved
src/trio/_core/_parking_lot.py Outdated Show resolved Hide resolved
newsfragments/3081.feature.rst Outdated Show resolved Hide resolved
src/trio/_core/_tests/test_parking_lot.py Outdated Show resolved Hide resolved
src/trio/_core/_tests/test_parking_lot.py Show resolved Hide resolved
Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two minor things below, and then unless @njsmith or @oremanj raise any concerns let's merge after the weekend!

src/trio/_core/_run.py Outdated Show resolved Hide resolved
src/trio/_sync.py Outdated Show resolved Hide resolved
Copy link
Contributor

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just nitpicks

Comment on lines +95 to +99
"""Register a task as a breaker for a lot. If this task exits without being removed
as a breaker, the lot will break. This will cause an error to be raised for all
tasks currently parked in the lot, as well as any future tasks that attempt to
park in it.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should have a summary.

I think it might be better to link to the break_lot to explain the effects of a task exiting without removing themselves.

Comment on lines +276 to +280
"""Break this lot, causing all parked tasks to raise an error, and any
future tasks attempting to park to error. Unpark & repark become no-ops as the
parking lot is empty.
The error raised contains a reference to the task sent as a parameter.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary line here too. Also "The error raised contains a reference to the task sent as a parameter." should probably just mention that broken_by is available on parking lots, because I personally missed that previously.

@@ -1820,6 +1821,11 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
return task

def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
if task in GLOBAL_PARKING_LOT_BREAKER:
for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
lot.break_lot(task)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a line comment here like # break parking lots associated with a task that exited because otherwise the warning (from multiple tasks breaking a lot) might point to here in the stack trace.

await lot.park()

lot = ParkingLot()
with RaisesGroup(Matcher(_core.BrokenResourceError, match="Parking lot broken by")):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
with RaisesGroup(Matcher(_core.BrokenResourceError, match="Parking lot broken by")):
with RaisesGroup(Matcher(_core.BrokenResourceError, match="^Parking lot broken by")):

for consistency

Comment on lines 11 to +12
from ._core import Abort, ParkingLot, RaiseCancelT, enable_ki_protection
from ._core._parking_lot import add_parking_lot_breaker, remove_parking_lot_breaker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could import the functions from ._core for a nicer import

await self._lot.park()
except trio.BrokenResourceError:
raise trio.BrokenResourceError(
"Owner of this lock exited without releasing: {self._owner}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Owner of this lock exited without releasing: {self._owner}",
f"Owner of this lock exited without releasing: {self._owner}",


async def test_lock_acquire_unowned_lock() -> None:
"""Test that trying to acquire a lock whose owner has exited raises an error.
Partial fix for https://github.com/python-trio/trio/issues/3035
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about testing the full fix because this PR should be the full fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Problems with trio.Lock() across multiple tasks
4 participants