Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immediate remote cancels #245

Merged
merged 25 commits into from
Oct 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7643bbf
Make actor runtime cancellation immediate
goodboy Oct 8, 2021
41f0992
Don't whine about ; it ain't rpc
goodboy Oct 8, 2021
bb9d9c7
Do immediate remote task cancels
goodboy Oct 10, 2021
46ff558
Unwind process opening and shield hard reap
goodboy Oct 13, 2021
2df16c1
Lol, fix sub-actor case
goodboy Oct 12, 2021
77ec290
Simplify to soft and hard reap sequences
goodboy Oct 13, 2021
893bad7
Add a maybe-open-debugger helper
goodboy Oct 8, 2021
6203507
Reduce some loglevels, stick in comment about blocking till next tick
goodboy Oct 13, 2021
f3a6ab6
Use debugger helper in nursery and spawn tasks
goodboy Oct 13, 2021
d30ce96
Breakout `wait_for_parent_stdin_hijack()`, increase root pdb checker …
goodboy Oct 14, 2021
4b2710b
Add tty lock acquire ctx mngr
goodboy Oct 14, 2021
daa28ea
Handle depth > 1 nursery owners which use debug mode
goodboy Oct 14, 2021
b14699d
Adjust debugger tests to expect depth > 1 crashes
goodboy Oct 14, 2021
6f5c35d
Fix missing task status type
goodboy Oct 14, 2021
fa317d1
Change lock helper to take an actor uid tuple
goodboy Oct 14, 2021
9d83ef8
Remove union type for root getter
goodboy Oct 14, 2021
7ee121a
Try to handle variable windows errors
goodboy Oct 14, 2021
51259c4
Pass uid not actor object
goodboy Oct 14, 2021
533457c
Handle nested multierror case on windows
goodboy Oct 15, 2021
a42ec1f
Add nooz
goodboy Oct 15, 2021
e4ed0fd
Right, only worry about pdb lock when in debug mode
goodboy Oct 15, 2021
4f222a5
Use type match of expected error
goodboy Oct 15, 2021
5d827f7
Fix pluggy readme link and typo
goodboy Oct 15, 2021
5cfac58
Don't pop a child entry that was never inserted
goodboy Oct 15, 2021
b3c4851
Grab lock if cancelled during spawn before hard kill
goodboy Oct 15, 2021
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 examples/debugging/multi_subactor_root_errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
'''
Test that a nested nursery will avoid clobbering
the debugger latched by a broken child.

'''
import trio
import tractor

Expand Down Expand Up @@ -35,6 +40,7 @@ async def main():
"""
async with tractor.open_nursery(
debug_mode=True,
# loglevel='cancel',
) as n:

# spawn both actors
Expand Down
13 changes: 13 additions & 0 deletions newsfragments/245.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Change the core message loop to handle task and actor-runtime cancel
requests immediately instead of scheduling them as is done for rpc-task
requests.

In order to obtain more reliable teardown mechanics for (complex) actor
trees it's important that we specially treat cancel requests as having
higher priority. Previously, it was possible that task cancel requests
could actually also themselves be cancelled if a "actor-runtime" cancel
request was received (can happen during messy multi actor crashes that
propagate). Instead cancels now block the msg loop until serviced and
a response is relayed back to the requester. This also allows for
improved debugger support since we have determinism guarantees about
which processes must wait before hard killing their children.
2 changes: 1 addition & 1 deletion newsfragments/HOWTO.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ now and use the default `fragment set`_.


.. _towncrier docs: https://github.com/twisted/towncrier#quick-start
.. _pluggy release readme: https://github.com/twisted/towncrier#quick-start
.. _pluggy release readme: https://github.com/pytest-dev/pluggy/blob/main/changelog/README.rst
.. _fragment set: https://github.com/twisted/towncrier#news-fragments
25 changes: 20 additions & 5 deletions tests/test_cancellation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""
Cancellation and error propagation

"""
import os
import signal
Expand Down Expand Up @@ -365,14 +366,26 @@ async def test_nested_multierrors(loglevel, start_method):
# to happen before an actor is spawned
if isinstance(subexc, trio.Cancelled):
continue
else:

elif isinstance(subexc, tractor.RemoteActorError):
# on windows it seems we can't exactly be sure wtf
# will happen..
assert subexc.type in (
tractor.RemoteActorError,
trio.Cancelled,
trio.MultiError
)

elif isinstance(subexc, trio.MultiError):
for subsub in subexc.exceptions:

if subsub in (tractor.RemoteActorError,):
subsub = subsub.type

assert type(subsub) in (
trio.Cancelled,
trio.MultiError,
)
else:
assert isinstance(subexc, tractor.RemoteActorError)

Expand All @@ -381,13 +394,14 @@ async def test_nested_multierrors(loglevel, start_method):
# on windows sometimes spawning is just too slow and
# we get back the (sent) cancel signal instead
if platform.system() == 'Windows':
assert (subexc.type is trio.MultiError) or (
subexc.type is tractor.RemoteActorError)
if isinstance(subexc, tractor.RemoteActorError):
assert subexc.type in (trio.MultiError, tractor.RemoteActorError)
else:
assert isinstance(subexc, trio.MultiError)
else:
assert subexc.type is trio.MultiError
else:
assert (subexc.type is tractor.RemoteActorError) or (
subexc.type is trio.Cancelled)
assert subexc.type in (tractor.RemoteActorError, trio.Cancelled)


@no_windows
Expand Down Expand Up @@ -448,6 +462,7 @@ async def main():
with pytest.raises(KeyboardInterrupt):
trio.run(main)


async def spin_for(period=3):
"Sync sleep."
time.sleep(period)
Expand Down
50 changes: 44 additions & 6 deletions tests/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ def test_subactor_breakpoint(spawn):


def test_multi_subactors(spawn):
"""Multiple subactors, both erroring and breakpointing as well as
"""
Multiple subactors, both erroring and breakpointing as well as
a nested subactor erroring.
"""
child = spawn(r'multi_subactors')
Expand All @@ -259,6 +260,7 @@ def test_multi_subactors(spawn):
# first name_error failure
child.expect(r"\(Pdb\+\+\)")
before = str(child.before.decode())
assert "Attaching to pdb in crashed actor: ('name_error'" in before
assert "NameError" in before

# continue again
Expand All @@ -267,6 +269,7 @@ def test_multi_subactors(spawn):
# 2nd name_error failure
child.expect(r"\(Pdb\+\+\)")
before = str(child.before.decode())
assert "Attaching to pdb in crashed actor: ('name_error_1'" in before
assert "NameError" in before

# breakpoint loop should re-engage
Expand All @@ -275,6 +278,19 @@ def test_multi_subactors(spawn):
before = str(child.before.decode())
assert "Attaching pdb to actor: ('breakpoint_forever'" in before

# wait for spawn error to show up
while 'breakpoint_forever' in before:
child.sendline('c')
child.expect(r"\(Pdb\+\+\)")
before = str(child.before.decode())

# 2nd depth nursery should trigger
# child.sendline('c')
# child.expect(r"\(Pdb\+\+\)")
# before = str(child.before.decode())
assert "Attaching to pdb in crashed actor: ('spawn_error'" in before
assert "RemoteActorError: ('name_error_1'" in before

# now run some "continues" to show re-entries
for _ in range(5):
child.sendline('c')
Expand All @@ -284,16 +300,24 @@ def test_multi_subactors(spawn):
child.sendline('q')
child.expect(r"\(Pdb\+\+\)")
before = str(child.before.decode())
# debugger attaches to root
assert "Attaching to pdb in crashed actor: ('root'" in before
# expect a multierror with exceptions for each sub-actor
assert "RemoteActorError: ('breakpoint_forever'" in before
assert "RemoteActorError: ('name_error'" in before
assert "RemoteActorError: ('spawn_error'" in before
assert "RemoteActorError: ('name_error_1'" in before
assert 'bdb.BdbQuit' in before

# process should exit
child.sendline('c')
child.expect(pexpect.EOF)

# repeat of previous multierror for final output
before = str(child.before.decode())
assert "RemoteActorError: ('breakpoint_forever'" in before
assert "RemoteActorError: ('name_error'" in before
assert "RemoteActorError: ('spawn_error'" in before
assert "RemoteActorError: ('name_error_1'" in before
assert 'bdb.BdbQuit' in before


Expand Down Expand Up @@ -387,16 +411,29 @@ def test_multi_subactors_root_errors(spawn):
before = str(child.before.decode())
assert "NameError: name 'doggypants' is not defined" in before

# continue again
# continue again to catch 2nd name error from
# actor 'name_error_1' (which is 2nd depth).
child.sendline('c')
child.expect(r"\(Pdb\+\+\)")
before = str(child.before.decode())
assert "Attaching to pdb in crashed actor: ('name_error_1'" in before
assert "NameError" in before

# should now get attached in root with assert error
child.sendline('c')
child.expect(r"\(Pdb\+\+\)")
before = str(child.before.decode())
assert "Attaching to pdb in crashed actor: ('spawn_error'" in before
# boxed error from previous step
assert "RemoteActorError: ('name_error_1'" in before
assert "NameError" in before

# should have come just after priot prompt
child.sendline('c')
child.expect(r"\(Pdb\+\+\)")
before = str(child.before.decode())
assert "Attaching to pdb in crashed actor: ('root'" in before
assert "AssertionError" in before
# boxed error from first level failure
assert "RemoteActorError: ('name_error'" in before
assert "NameError" in before

# warnings assert we probably don't need
# assert "Cancelling nursery in ('spawn_error'," in before
Expand All @@ -406,6 +443,7 @@ def test_multi_subactors_root_errors(spawn):
child.expect(pexpect.EOF)

before = str(child.before.decode())
# error from root actor and root task that created top level nursery
assert "AssertionError" in before


Expand Down
1 change: 1 addition & 0 deletions tests/test_pubsub.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ async def main():
'streamer',
enable_modules=[__name__],
)
name = 'streamer'

even_portal = await n.run_in_actor(
subs,
Expand Down
Loading