Actually reproduce the de-registration problem
This truly reproduces #141. It turns out the problem only occurs when we're cancelled in the middle of consuming "infinite streams". Good news is this tests a lot of edge cases :)ensure_deregister
parent
699bfd1857
commit
a5279f80a7
|
@ -5,6 +5,7 @@ import os
|
||||||
import signal
|
import signal
|
||||||
import platform
|
import platform
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
import itertools
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import tractor
|
import tractor
|
||||||
|
@ -87,10 +88,35 @@ async def test_trynamic_trio(func, start_method):
|
||||||
print("CUTTTT CUUTT CUT!!?! Donny!! You're supposed to say...")
|
print("CUTTTT CUUTT CUT!!?! Donny!! You're supposed to say...")
|
||||||
|
|
||||||
|
|
||||||
|
async def stream_forever():
|
||||||
|
for i in itertools.count():
|
||||||
|
yield i
|
||||||
|
await trio.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
async def cancel(use_signal, delay=0):
|
||||||
|
# hold on there sally
|
||||||
|
await trio.sleep(delay)
|
||||||
|
|
||||||
|
# trigger cancel
|
||||||
|
if use_signal:
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
pytest.skip("SIGINT not supported on windows")
|
||||||
|
os.kill(os.getpid(), signal.SIGINT)
|
||||||
|
else:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
|
||||||
|
async def stream_from(portal):
|
||||||
|
async for value in await portal.result():
|
||||||
|
print(value)
|
||||||
|
|
||||||
|
|
||||||
async def spawn_and_check_registry(
|
async def spawn_and_check_registry(
|
||||||
arb_addr: tuple,
|
arb_addr: tuple,
|
||||||
use_signal: bool,
|
use_signal: bool,
|
||||||
remote_arbiter: bool = False,
|
remote_arbiter: bool = False,
|
||||||
|
with_streaming: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
actor = tractor.current_actor()
|
actor = tractor.current_actor()
|
||||||
|
|
||||||
|
@ -101,9 +127,7 @@ async def spawn_and_check_registry(
|
||||||
if actor.is_arbiter:
|
if actor.is_arbiter:
|
||||||
async def get_reg():
|
async def get_reg():
|
||||||
return actor._registry
|
return actor._registry
|
||||||
|
|
||||||
extra = 1 # arbiter is local root actor
|
extra = 1 # arbiter is local root actor
|
||||||
|
|
||||||
else:
|
else:
|
||||||
get_reg = partial(portal.run, 'self', 'get_registry')
|
get_reg = partial(portal.run, 'self', 'get_registry')
|
||||||
extra = 2 # local root actor + remote arbiter
|
extra = 2 # local root actor + remote arbiter
|
||||||
|
@ -112,13 +136,18 @@ async def spawn_and_check_registry(
|
||||||
registry = await get_reg()
|
registry = await get_reg()
|
||||||
assert actor.uid in registry
|
assert actor.uid in registry
|
||||||
|
|
||||||
|
if with_streaming:
|
||||||
|
to_run = stream_forever
|
||||||
|
else:
|
||||||
|
to_run = trio.sleep_forever
|
||||||
|
|
||||||
|
async with trio.open_nursery() as trion:
|
||||||
try:
|
try:
|
||||||
async with tractor.open_nursery() as n:
|
async with tractor.open_nursery() as n:
|
||||||
portals = {}
|
portals = {}
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
name = f'a{i}'
|
name = f'a{i}'
|
||||||
portals[name] = await n.run_in_actor(
|
portals[name] = await n.run_in_actor(name, to_run)
|
||||||
name, trio.sleep_forever)
|
|
||||||
|
|
||||||
# wait on last actor to come up
|
# wait on last actor to come up
|
||||||
async with tractor.wait_for_actor(name):
|
async with tractor.wait_for_actor(name):
|
||||||
|
@ -128,50 +157,74 @@ async def spawn_and_check_registry(
|
||||||
|
|
||||||
assert len(portals) + extra == len(registry)
|
assert len(portals) + extra == len(registry)
|
||||||
|
|
||||||
# trigger cancel
|
if with_streaming:
|
||||||
if use_signal:
|
await trio.sleep(0.1)
|
||||||
if platform.system() == 'Windows':
|
|
||||||
pytest.skip("SIGINT not supported on windows")
|
pts = list(portals.values())
|
||||||
os.kill(os.getpid(), signal.SIGINT)
|
for p in pts[:-1]:
|
||||||
|
trion.start_soon(stream_from, p)
|
||||||
|
|
||||||
|
# stream for 1 sec
|
||||||
|
trion.start_soon(cancel, use_signal, 1)
|
||||||
|
|
||||||
|
last_p = pts[-1]
|
||||||
|
async for value in await last_p.result():
|
||||||
|
print(value)
|
||||||
else:
|
else:
|
||||||
raise KeyboardInterrupt
|
await cancel(use_signal)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# all subactors should have de-registered
|
with trio.CancelScope(shield=True):
|
||||||
await trio.sleep(0.5)
|
await trio.sleep(0.5)
|
||||||
|
|
||||||
|
# all subactors should have de-registered
|
||||||
registry = await get_reg()
|
registry = await get_reg()
|
||||||
assert len(registry) == extra
|
assert len(registry) == extra
|
||||||
assert actor.uid in registry
|
assert actor.uid in registry
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('use_signal', [False, True])
|
@pytest.mark.parametrize('use_signal', [False, True])
|
||||||
|
@pytest.mark.parametrize('with_streaming', [False, True])
|
||||||
def test_subactors_unregister_on_cancel(
|
def test_subactors_unregister_on_cancel(
|
||||||
start_method,
|
|
||||||
use_signal,
|
|
||||||
arb_addr
|
|
||||||
):
|
|
||||||
"""Verify that cancelling a nursery results in all subactors
|
|
||||||
deregistering themselves with the arbiter.
|
|
||||||
"""
|
|
||||||
with pytest.raises(KeyboardInterrupt):
|
|
||||||
tractor.run(spawn_and_check_registry, arb_addr, use_signal)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('use_signal', [False, True])
|
|
||||||
def test_subactors_unregister_on_cancel_remote_daemon(
|
|
||||||
daemon,
|
|
||||||
start_method,
|
start_method,
|
||||||
use_signal,
|
use_signal,
|
||||||
arb_addr,
|
arb_addr,
|
||||||
|
with_streaming,
|
||||||
):
|
):
|
||||||
"""Verify that cancelling a nursery results in all subactors
|
"""Verify that cancelling a nursery results in all subactors
|
||||||
deregistering themselves with the arbiter.
|
deregistering themselves with the arbiter.
|
||||||
"""
|
"""
|
||||||
|
with pytest.raises(KeyboardInterrupt):
|
||||||
|
tractor.run(
|
||||||
|
spawn_and_check_registry,
|
||||||
|
arb_addr,
|
||||||
|
use_signal,
|
||||||
|
False,
|
||||||
|
with_streaming,
|
||||||
|
arbiter_addr=arb_addr
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('use_signal', [False, True])
|
||||||
|
@pytest.mark.parametrize('with_streaming', [False, True])
|
||||||
|
def test_subactors_unregister_on_cancel_remote_daemon(
|
||||||
|
daemon,
|
||||||
|
start_method,
|
||||||
|
use_signal,
|
||||||
|
arb_addr,
|
||||||
|
with_streaming,
|
||||||
|
):
|
||||||
|
"""Verify that cancelling a nursery results in all subactors
|
||||||
|
deregistering themselves with a **remote** (not in the local process
|
||||||
|
tree) arbiter.
|
||||||
|
"""
|
||||||
with pytest.raises(KeyboardInterrupt):
|
with pytest.raises(KeyboardInterrupt):
|
||||||
tractor.run(
|
tractor.run(
|
||||||
spawn_and_check_registry,
|
spawn_and_check_registry,
|
||||||
arb_addr,
|
arb_addr,
|
||||||
use_signal,
|
use_signal,
|
||||||
True,
|
True,
|
||||||
|
with_streaming,
|
||||||
# XXX: required to use remote daemon!
|
# XXX: required to use remote daemon!
|
||||||
arbiter_addr=arb_addr
|
arbiter_addr=arb_addr
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue