Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/source/reference-hazmat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
5 changes: 5 additions & 0 deletions newsfragments/616.feature.rst
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 8 additions & 9 deletions trio/_core/_parking_lot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))):
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 6 additions & 0 deletions trio/_core/_traps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions trio/_core/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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