Skip to content

Commit

Permalink
Deprecate and replace nursery.children and task.parent_task
Browse files Browse the repository at this point in the history
New API:

- nursery.child_tasks
- nursery.parent_task
- task.child_nurseries
- task.parent_nursery (may be None)

Rationale: python-trio#136 (comment)
  • Loading branch information
njsmith committed Aug 23, 2017
1 parent a1572da commit a1bfd61
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 11 deletions.
9 changes: 9 additions & 0 deletions docs/source/reference-core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -956,9 +956,14 @@ Nursery objects provide the following interface:
other things, e.g. if you want to explicitly cancel all children
in response to some external event.

The last two attributes are mainly to enable introspection of the
task tree, for example in debuggers.

.. attribute:: parent_task

The :class:`~trio.Task` that opened this nursery.

.. attribute:: child_tasks

A :class:`frozenset` containing all the child
:class:`~trio.Task` objects which are still running.
Expand Down Expand Up @@ -1013,6 +1018,10 @@ Task object API

.. autoattribute:: parent_task

.. autoattribute:: parent_nursery

.. autoattribute:: child_nurseries


Working with :exc:`MultiError`\s
++++++++++++++++++++++++++++++++
Expand Down
42 changes: 36 additions & 6 deletions trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,14 +344,14 @@ async def open_nursery():


class Nursery:
def __init__(self, parent, cancel_scope):
def __init__(self, parent_task, cancel_scope):
# the parent task -- only used for introspection, to implement
# task.parent_task
self._parent = parent
parent._child_nurseries.append(self)
self._parent_task = parent_task
parent_task._child_nurseries.append(self)
# the cancel stack that children inherit - we take a snapshot, so it
# won't be affected by any changes in the parent.
self._cancel_stack = list(parent._cancel_stack)
self._cancel_stack = list(parent_task._cancel_stack)
# the cancel scope that directly surrounds us; used for cancelling all
# children.
self.cancel_scope = cancel_scope
Expand All @@ -363,9 +363,18 @@ def __init__(self, parent, cancel_scope):
self._closed = False

@property
@deprecated("0.2.0", instead="child_tasks", issue=136)
def children(self):
return frozenset(self._children)

@property
def child_tasks(self):
return frozenset(self._children)

@property
def parent_task(self):
return self._parent_task

@property
@deprecated("0.2.0", instead=None, issue=136)
def zombies(self):
Expand Down Expand Up @@ -474,7 +483,7 @@ async def _clean_up(self, pending_exc):
exceptions.append(exc)

self._closed = True
popped = self._parent._child_nurseries.pop()
popped = self._parent_task._child_nurseries.pop()
assert popped is self
if exceptions:
mexc = MultiError(exceptions)
Expand Down Expand Up @@ -563,6 +572,7 @@ def result(self):

# For debugging and visualization:
@property
@deprecated("0.2.0", instead="parent_nursery.parent_task", issue=136)
def parent_task(self):
"""This task's parent task (or None if this is the "init" task).
Expand All @@ -571,7 +581,27 @@ def parent_task(self):
if self._parent_nursery is None:
return None
else:
return self._parent_nursery._parent
return self._parent_nursery._parent_task

@property
def parent_nursery(self):
"""The nursery this task is inside (or None if this is the "init"
take).
Example use case: drawing a visualization of the task tree in a
debugger.
"""
return self._parent_nursery

@property
def child_nurseries(self):
"""The nurseries this task contains.
This is a list, with outer nurseries before inner nurseries.
"""
return list(self._child_nurseries)

################
# Monitoring task exit
Expand Down
55 changes: 51 additions & 4 deletions trio/_core/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ async def test_current_task():
parent_task = _core.current_task()

async def child():
assert _core.current_task().parent_task is parent_task
assert _core.current_task().parent_nursery.parent_task is parent_task

async with _core.open_nursery() as nursery:
nursery.start_soon(child)
Expand Down Expand Up @@ -1493,7 +1493,7 @@ def slow_abort(raise_cancel):
assert record == ["sleeping", "abort-called", "cancelled", "done"]


async def test_parent_task():
async def test_Task_parent_task_deprecated(recwarn):
tasks = {}

async def child2():
Expand All @@ -1517,6 +1517,53 @@ async def child1():
t = t.parent_task


async def test_task_tree_introspection():
tasks = {}

tasks["parent"] = _core.current_task()

assert tasks["parent"].child_nurseries == []

async with _core.open_nursery() as nursery1:
async with _core.open_nursery() as nursery2:
assert tasks["parent"].child_nurseries == [nursery1, nursery2]

assert tasks["parent"].child_nurseries == []

nurseries = {}

async def child2():
tasks["child2"] = _core.current_task()
assert tasks["parent"].child_nurseries == [nurseries["parent"]]
assert nurseries["parent"].child_tasks == frozenset({tasks["child1"]})
assert tasks["child1"].child_nurseries == [nurseries["child1"]]
assert nurseries["child1"].child_tasks == frozenset({tasks["child2"]})
assert tasks["child2"].child_nurseries == []

async def child1():
tasks["child1"] = _core.current_task()
async with _core.open_nursery() as nursery:
nurseries["child1"] = nursery
nursery.start_soon(child2)

async with _core.open_nursery() as nursery:
nurseries["parent"] = nursery
nursery.start_soon(child1)

# Upward links survive after tasks/nurseries exit
assert nurseries["parent"].parent_task is tasks["parent"]
assert tasks["child1"].parent_nursery is nurseries["parent"]
assert nurseries["child1"].parent_task is tasks["child1"]
assert tasks["child2"].parent_nursery is nurseries["child1"]

nursery = _core.current_task().parent_nursery
# Make sure that chaining eventually gives a nursery of None (and not, for
# example, an error)
while nursery is not None:
t = nursery.parent_task
nursery = t.parent_nursery


async def test_nursery_closure():
async def child1(nursery):
# We can add new tasks to the nursery even after entering __aexit__,
Expand Down Expand Up @@ -1694,11 +1741,11 @@ async def sleep_then_start(seconds, *, task_status=_core.STATUS_IGNORED):
# to exit.
for seconds in [1, 2]:
async with _core.open_nursery() as nursery:
assert len(nursery.children) == 0
assert len(nursery.child_tasks) == 0
t0 = _core.current_time()
assert await nursery.start(sleep_then_start, seconds) == seconds
assert _core.current_time() - t0 == seconds
assert len(nursery.children) == 1
assert len(nursery.child_tasks) == 1
assert _core.current_time() - t0 == 2 * seconds

# Make sure STATUS_IGNORED works so task function can be called directly
Expand Down
2 changes: 1 addition & 1 deletion trio/tests/test_highlevel_serve_listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ async def connection_watcher(*, task_status=trio.STATUS_IGNORED):
async with trio.open_nursery() as nursery:
task_status.started(nursery)
await wait_all_tasks_blocked()
assert len(nursery.children) == 10
assert len(nursery.child_tasks) == 10
raise Done

with pytest.raises(Done):
Expand Down

0 comments on commit a1bfd61

Please sign in to comment.