Skip to content

Commit

Permalink
Nursery docstring
Browse files Browse the repository at this point in the history
  • Loading branch information
bengartner committed Jun 14, 2019
1 parent 15c19fd commit 2840640
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 103 deletions.
107 changes: 8 additions & 99 deletions docs/source/reference-core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -819,112 +819,21 @@ The nursery API
.. autofunction:: open_nursery
:async-with: nursery

Nursery objects provide the following interface:

.. interface:: The nursery interface

.. method:: start_soon(async_fn, *args, name=None)

Creates a new child task inside this nursery, and sets it up to
run ``await async_fn(*args)``.

This and :meth:`start` are the two fundamental methods for
creating concurrent tasks in Trio.

Note that this is *not* an async function and you don't use await
when calling it. It sets up the new task, but then returns
immediately, *before* it has a chance to run. The new task won’t
actually get a chance to do anything until some later point when
you execute a checkpoint and the scheduler decides to run it.
If you want to run a function and immediately wait for its result,
then you don't need a nursery; just use ``await async_fn(*args)``.
If you want to wait for the task to initialize itself before
continuing, see :meth:`start()`.

It's possible to pass a nursery object into another task, which
allows that task to start new child tasks in the first task's
nursery.

The child task inherits its parent nursery's cancel scopes.

:param async_fn: An async callable.
:param args: Positional arguments for ``async_fn``. If you want
to pass keyword arguments, use
:func:`functools.partial`.
:param name: The name for this task. Only used for
debugging/introspection
(e.g. ``repr(task_obj)``). If this isn't a string,
:meth:`start_soon` will try to make it one. A
common use case is if you're wrapping a function
before spawning a new task, you might pass the
original function as the ``name=`` to make
debugging easier.
:raises RuntimeError: If this nursery is no longer open
(i.e. its ``async with`` block has
exited).

.. method:: start(async_fn, *args, name=None)
:async:

Like :meth:`start_soon`, but blocks until the new task has
finished initializing itself, and optionally returns some
information from it.

The ``async_fn`` must accept a ``task_status`` keyword argument,
and it must make sure that it (or someone) eventually calls
``task_status.started()``.

The conventional way to define ``async_fn`` is like::

async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
...
task_status.started()
...

:attr:`trio.TASK_STATUS_IGNORED` is a special global object with
a do-nothing ``started`` method. This way your function supports
being called either like ``await nursery.start(async_fn, arg1,
arg2)`` or directly like ``await async_fn(arg1, arg2)``, and
either way it can call ``task_status.started()`` without
worrying about which mode it's in. Defining your function like
this will make it obvious to readers that it supports being used
in both modes.

Before the child calls ``task_status.started()``, it's
effectively run underneath the call to :meth:`start`: if it
raises an exception then that exception is reported by
:meth:`start`, and does *not* propagate out of the nursery. If
:meth:`start` is cancelled, then the child task is also
cancelled.

When the child calls ``task_status.started()``, it's moved from
out from underneath :meth:`start` and into the given nursery.

If the child task passes a value to
``task_status.started(value)``, then :meth:`start` returns this
value. Otherwise it returns ``None``.

.. attribute:: cancel_scope

Creating a nursery also implicitly creates a cancellation scope,
which is exposed as the :attr:`cancel_scope` attribute. This is
used internally to implement the logic where if an error occurs
then ``__aexit__`` cancels all children, but you can use it for
other things, e.g. if you want to explicitly cancel all children
in response to some external event.
.. autoclass:: Nursery

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

.. attribute:: parent_task
.. automethod:: start

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

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

A :class:`frozenset` containing all the child
:class:`~trio.hazmat.Task` objects which are still running.
.. autoattribute:: parent_task

.. autoattribute:: child_tasks

.. attribute:: TASK_STATUS_IGNORED

Expand Down
7 changes: 4 additions & 3 deletions newsfragments/1021.misc.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Any attempt to inherit from :class:`CancelScope` now raises TypeError.
(Trio has never been able to safely support subclassing here;
this change just makes it more obvious.)
Any attempt to inherit from `CancelScope` or `Nursery` now raises
`TypeError`. (Trio has never been able to safely support subclassing
here; this change just makes it more obvious.)
Also exposed as public classes for type-checking, etc.
109 changes: 108 additions & 1 deletion trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ def __exit__(self): # pragma: no cover

def open_nursery():
"""Returns an async context manager which must be used to create a
new ``Nursery``.
new `Nursery`.
It does not block on entry; on exit it blocks until all child tasks
have exited.
Expand All @@ -762,6 +762,27 @@ def open_nursery():


class Nursery(metaclass=NoPublicConstructor):
"""A context which may be used to spawn (or cancel) child tasks.
Not constructed directly, use `open_nursery` instead.
The nursery will remain open until all child tasks have completed,
or until it is cancelled, at which point it will cancel all its
remaining child tasks and close.
Nurseries ensure the absence of orphaned Tasks, since all running
tasks will belong to an open Nursery.
Attributes:
cancel_scope:
Creating a nursery also implicitly creates a cancellation scope,
which is exposed as the :attr:`cancel_scope` attribute. This is
used internally to implement the logic where if an error occurs
then ``__aexit__`` cancels all children, but you can use it for
other things, e.g. if you want to explicitly cancel all children
in response to some external event.
"""

def __init__(self, parent_task, cancel_scope):
self._parent_task = parent_task
parent_task._child_nurseries.append(self)
Expand All @@ -784,10 +805,13 @@ def __init__(self, parent_task, cancel_scope):

@property
def child_tasks(self):
"""(`frozenset`): Contains all the child :class:`~trio.hazmat.Task`
objects which are still running."""
return frozenset(self._children)

@property
def parent_task(self):
"(`~trio.hazmat.Task`): The Task that opened this nursery."
return self._parent_task

def _add_exc(self, exc):
Expand Down Expand Up @@ -841,9 +865,92 @@ def aborted(raise_cancel):
return MultiError(self._pending_excs)

def start_soon(self, async_fn, *args, name=None):
""" Creates a child task, scheduling ``await async_fn(*args)``.
This and :meth:`start` are the two fundamental methods for
creating concurrent tasks in Trio.
Note that this is *not* an async function and you don't use await
when calling it. It sets up the new task, but then returns
immediately, *before* it has a chance to run. The new task won’t
actually get a chance to do anything until some later point when
you execute a checkpoint and the scheduler decides to run it.
If you want to run a function and immediately wait for its result,
then you don't need a nursery; just use ``await async_fn(*args)``.
If you want to wait for the task to initialize itself before
continuing, see :meth:`start()`.
It's possible to pass a nursery object into another task, which
allows that task to start new child tasks in the first task's
nursery.
The child task inherits its parent nursery's cancel scopes.
Args:
async_fn: An async callable.
args: Positional arguments for ``async_fn``. If you want
to pass keyword arguments, use
:func:`functools.partial`.
name: The name for this task. Only used for
debugging/introspection
(e.g. ``repr(task_obj)``). If this isn't a string,
:meth:`start_soon` will try to make it one. A
common use case is if you're wrapping a function
before spawning a new task, you might pass the
original function as the ``name=`` to make
debugging easier.
Returns:
True if successful, False otherwise.
Raises:
RuntimeError: If this nursery is no longer open
(i.e. its ``async with`` block has
exited).
"""
GLOBAL_RUN_CONTEXT.runner.spawn_impl(async_fn, args, self, name)

async def start(self, async_fn, *args, name=None):
r""" Creates and initalizes a child task.
Like :meth:`start_soon`, but blocks until the new task has
finished initializing itself, and optionally returns some
information from it.
The ``async_fn`` must accept a ``task_status`` keyword argument,
and it must make sure that it (or someone) eventually calls
``task_status.started()``.
The conventional way to define ``async_fn`` is like::
async def async_fn(arg1, arg2, \*, task_status=trio.TASK_STATUS_IGNORED):
...
task_status.started()
...
:attr:`trio.TASK_STATUS_IGNORED` is a special global object with
a do-nothing ``started`` method. This way your function supports
being called either like ``await nursery.start(async_fn, arg1,
arg2)`` or directly like ``await async_fn(arg1, arg2)``, and
either way it can call ``task_status.started()`` without
worrying about which mode it's in. Defining your function like
this will make it obvious to readers that it supports being used
in both modes.
Before the child calls ``task_status.started()``, it's
effectively run underneath the call to :meth:`start`: if it
raises an exception then that exception is reported by
:meth:`start`, and does *not* propagate out of the nursery. If
:meth:`start` is cancelled, then the child task is also
cancelled.
When the child calls ``task_status.started()``, it's moved from
out from underneath :meth:`start` and into the given nursery.
If the child task passes a value to
``task_status.started(value)``, then :meth:`start` returns this
value. Otherwise it returns ``None``.
"""
if self._closed:
raise RuntimeError("Nursery is closed to new arrivals")
try:
Expand Down

0 comments on commit 2840640

Please sign in to comment.