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
6 changes: 6 additions & 0 deletions newsfragments/1738.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
:func:`trio.from_thread.run` no longer crashes the Trio run if it is
executed after the system nursery has been closed but before the run
has finished. Calls made at this time will now raise
`trio.RunFinishedError`. This fixes a regression introduced in
Trio 0.17.0. The window in question is only one scheduler tick long in
most cases, but may be longer if async generators need to be cleaned up.
9 changes: 9 additions & 0 deletions trio/_core/_generated_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ def spawn_system_task(async_fn, *args, name=None):

* System tasks do not inherit context variables from their creator.

Towards the end of a call to :meth:`trio.run`, after the main
task and all system tasks have exited, the system nursery
becomes closed. At this point, new calls to
:func:`spawn_system_task` will raise ``RuntimeError("Nursery
is closed to new arrivals")`` instead of creating a system
task. It's possible to encounter this state either in
a ``finally`` block in an async generator, or in a callback
passed to :meth:`TrioToken.run_sync_soon` at the right moment.

Args:
async_fn: An async callable.
args: Positional arguments for ``async_fn``. If you want to pass
Expand Down
9 changes: 9 additions & 0 deletions trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,15 @@ def spawn_system_task(self, async_fn, *args, name=None):

* System tasks do not inherit context variables from their creator.

Towards the end of a call to :meth:`trio.run`, after the main
task and all system tasks have exited, the system nursery
becomes closed. At this point, new calls to
:func:`spawn_system_task` will raise ``RuntimeError("Nursery
is closed to new arrivals")`` instead of creating a system
task. It's possible to encounter this state either in
a ``finally`` block in an async generator, or in a callback
passed to :meth:`TrioToken.run_sync_soon` at the right moment.

Args:
async_fn: An async callable.
args: Positional arguments for ``async_fn``. If you want to pass
Expand Down
12 changes: 10 additions & 2 deletions trio/_threads.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# coding: utf-8

Comment on lines +1 to +2
Copy link
Copy Markdown
Member

@belm0 belm0 Oct 11, 2020

Choose a reason for hiding this comment

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

these headers should not be needed, right?

in the few files where this appears, git blame is always referencing your PR's. Over-eager editor setting?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@belm0 Not needed indeed, feel free to remove those lines

import threading
import queue as stdlib_queue
from itertools import count
Expand Down Expand Up @@ -248,7 +250,8 @@ def from_thread_run(afn, *args, trio_token=None):

Raises:
RunFinishedError: if the corresponding call to :func:`trio.run` has
already completed.
already completed, or if the run has started its final cleanup phase
and can no longer spawn new system tasks.
Cancelled: if the corresponding call to :func:`trio.run` completes
while ``afn(*args)`` is running, then ``afn`` is likely to raise
:exc:`trio.Cancelled`, and this will propagate out into
Expand Down Expand Up @@ -279,7 +282,12 @@ async def unprotected_afn():
async def await_in_trio_thread_task():
q.put_nowait(await outcome.acapture(unprotected_afn))

trio.lowlevel.spawn_system_task(await_in_trio_thread_task, name=afn)
try:
trio.lowlevel.spawn_system_task(await_in_trio_thread_task, name=afn)
except RuntimeError: # system nursery is closed
q.put_nowait(
outcome.Error(trio.RunFinishedError("system nursery is closed"))
)

return _run_fn_as_system_task(callback, afn, *args, trio_token=trio_token)

Expand Down
22 changes: 22 additions & 0 deletions trio/tests/test_threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .. import _core
from .. import Event, CapacityLimiter, sleep
from ..testing import wait_all_tasks_blocked
from .._core.tests.tutil import buggy_pypy_asyncgens
from .._threads import (
to_thread_run_sync,
current_default_thread_limiter,
Expand Down Expand Up @@ -554,3 +555,24 @@ def not_called(): # pragma: no cover
trio_token = _core.current_trio_token()
with pytest.raises(RuntimeError):
from_thread_run_sync(not_called, trio_token=trio_token)


@pytest.mark.skipif(buggy_pypy_asyncgens, reason="pypy 7.2.0 is buggy")
def test_from_thread_run_during_shutdown():
save = []
record = []

async def agen():
try:
yield
finally:
with pytest.raises(_core.RunFinishedError), _core.CancelScope(shield=True):
await to_thread_run_sync(from_thread_run, sleep, 0)
record.append("ok")

async def main():
save.append(agen())
await save[-1].asend(None)

_core.run(main)
assert record == ["ok"]