From 98c38cef6f62e731bf8c7190e8756976bface8f0 Mon Sep 17 00:00:00 2001 From: "Nathaniel J. Smith" Date: Mon, 20 Aug 2018 22:30:45 -0700 Subject: [PATCH 1/3] Add Task.custom_sleep_data For discussion see: https://github.com/python-trio/trio/pull/586#issuecomment-414039117 --- docs/source/reference-hazmat.rst | 7 +++++++ newsfragments/XX.feature.rst | 5 +++++ trio/_core/_run.py | 6 ++++-- trio/_core/_traps.py | 6 ++++++ trio/_core/tests/test_run.py | 9 +++++++++ 5 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 newsfragments/XX.feature.rst diff --git a/docs/source/reference-hazmat.rst b/docs/source/reference-hazmat.rst index 054a22f7dc..44ae1730bc 100644 --- a/docs/source/reference-hazmat.rst +++ b/docs/source/reference-hazmat.rst @@ -575,3 +575,10 @@ Task API .. autoattribute:: child_nurseries + .. attribute:: custom_sleep_data + + Trio doesn't assign this variable any meaning, except that it + sets it to ``None`` whenever a task is rescheduled. It can be + used to share data between the different tasks involved in + putting a task to sleep and then waking it up again. (See + :func:`wait_task_rescheduled` for details.) diff --git a/newsfragments/XX.feature.rst b/newsfragments/XX.feature.rst new file mode 100644 index 0000000000..dfa0a49fff --- /dev/null +++ b/newsfragments/XX.feature.rst @@ -0,0 +1,5 @@ +If you're using :func:`trio.hazmat.wait_task_rescheduled` and other +low-level routines to implement a new sleeping primitive, you can now +use the new :data:`trio.hazmat.Task.custom_sleep_data` attribute to +pass arbitrary data between the sleeping task, abort function, and +waking task. diff --git a/trio/_core/_run.py b/trio/_core/_run.py index 6c8651ceb8..bfa60f9edf 100644 --- a/trio/_core/_run.py +++ b/trio/_core/_run.py @@ -486,11 +486,12 @@ class Task: # Invariant: # - for unscheduled tasks, _next_send is None - # - for scheduled tasks, _next_send is a Result object + # - for scheduled tasks, _next_send is a Result object, + # and custom_sleep_data is None # Tasks start out unscheduled. _next_send = attr.ib(default=None) - # ParkingLot modifies this directly _abort_func = attr.ib(default=None) + custom_sleep_data = attr.ib(default=None) # For introspection and nursery.start() _child_nurseries = attr.ib(default=attr.Factory(list)) @@ -722,6 +723,7 @@ def reschedule(self, task, next_send=_NO_SEND): assert task._next_send is None task._next_send = next_send task._abort_func = None + task.custom_sleep_data = None self.runq.append(task) self.instrument("task_scheduled", task) diff --git a/trio/_core/_traps.py b/trio/_core/_traps.py index 0d5f4d711b..573fbb84fc 100644 --- a/trio/_core/_traps.py +++ b/trio/_core/_traps.py @@ -146,6 +146,12 @@ def abort(inner_raise_cancel): In any case it's guaranteed that we only call the ``abort_func`` at most once per call to :func:`wait_task_rescheduled`. + Sometimes, it's useful to be able to share some mutable sleep-related data + between the sleeping task, the abort function, and the waking task. You + can use the sleeping task's :data:`~Task.custom_sleep_data` attribute to + store this data, and Trio won't touch it, except to make sure that it gets + cleared when the task is rescheduled. + .. warning:: If your ``abort_func`` raises an error, or returns any value other than diff --git a/trio/_core/tests/test_run.py b/trio/_core/tests/test_run.py index 49ffb76baf..e4b0e028f0 100644 --- a/trio/_core/tests/test_run.py +++ b/trio/_core/tests/test_run.py @@ -1908,3 +1908,12 @@ async def check_inside_trio(): with pytest.raises(sniffio.AsyncLibraryNotFoundError): sniffio.current_async_library() + + +async def test_Task_custom_sleep_data(): + task = _core.current_task() + assert task.custom_sleep_data is None + task.custom_sleep_data = 1 + assert task.custom_sleep_data == 1 + await _core.checkpoint() + assert task.custom_sleep_data is None From 9f7522e9fc3ab120c1e908f6e54661469452ece8 Mon Sep 17 00:00:00 2001 From: "Nathaniel J. Smith" Date: Mon, 20 Aug 2018 22:37:46 -0700 Subject: [PATCH 2/3] Use Task.custom_sleep_data in ParkingLot This replaces the gross hack where we mutated Task._abort_func --- trio/_core/_parking_lot.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/trio/_core/_parking_lot.py b/trio/_core/_parking_lot.py index e0f254744d..5c333bbcb1 100644 --- a/trio/_core/_parking_lot.py +++ b/trio/_core/_parking_lot.py @@ -116,13 +116,6 @@ def __bool__(self): """ return bool(self._parked) - def _abort_func_for(self, task): - def abort(_): - del self._parked[task] - return _core.Abort.SUCCEEDED - - return abort - # XX this currently returns None # if we ever add the ability to repark while one's resuming place in # line (for false wakeups), then we could have it return a ticket that @@ -135,7 +128,13 @@ async def park(self): """ task = _core.current_task() self._parked[task] = None - await _core.wait_task_rescheduled(self._abort_func_for(task)) + task.custom_sleep_data = self + + def abort_fn(_): + del task.custom_sleep_data._parked[task] + return _core.Abort.SUCCEEDED + + await _core.wait_task_rescheduled(abort_fn) def _pop_several(self, count): for _ in range(min(count, len(self._parked))): @@ -203,7 +202,7 @@ async def main(): raise TypeError("new_lot must be a ParkingLot") for task in self._pop_several(count): new_lot._parked[task] = None - task._abort_func = new_lot._abort_func_for(task) + task.custom_sleep_data = new_lot def repark_all(self, new_lot): """Move all parked tasks from one :class:`ParkingLot` object to From c06c29e1bece8f60eec8180d8109892139bd63ee Mon Sep 17 00:00:00 2001 From: "Nathaniel J. Smith" Date: Mon, 20 Aug 2018 23:14:01 -0700 Subject: [PATCH 3/3] Fix newsfragment name --- newsfragments/{XX.feature.rst => 616.feature.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename newsfragments/{XX.feature.rst => 616.feature.rst} (100%) diff --git a/newsfragments/XX.feature.rst b/newsfragments/616.feature.rst similarity index 100% rename from newsfragments/XX.feature.rst rename to newsfragments/616.feature.rst