Skip to content

Commit

Permalink
Merge pull request #165 from goodboy/signint_saviour
Browse files Browse the repository at this point in the history
Ignore SIGINT when in a debugger REPL
  • Loading branch information
goodboy committed Aug 3, 2022
2 parents 4902e18 + cc5f60b commit 641ed7a
Show file tree
Hide file tree
Showing 21 changed files with 1,279 additions and 451 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ jobs:
- name: Install dependencies
run: pip install -U . -r requirements-test.txt -r requirements-docs.txt --upgrade-strategy eager

- name: List dependencies
run: pip freeze

- name: Run tests
run: pytest tests/ --spawn-backend=${{ matrix.spawn_backend }} -rs
run: pytest tests/ --spawn-backend=${{ matrix.spawn_backend }} -rsx

# We skip 3.10 on windows for now due to
# https://github.com/pytest-dev/pytest/issues/8733
Expand Down Expand Up @@ -110,5 +113,8 @@ jobs:
- name: Install dependencies
run: pip install -U . -r requirements-test.txt -r requirements-docs.txt --upgrade-strategy eager

- name: List dependencies
run: pip freeze

- name: Run tests
run: pytest tests/ --spawn-backend=${{ matrix.spawn_backend }} -rs
run: pytest tests/ --spawn-backend=${{ matrix.spawn_backend }} -rs --full-trace
11 changes: 6 additions & 5 deletions docs/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
|gh_actions|
|docs|

``tractor`` is a `structured concurrent`_, multi-processing_ runtime built on trio_.
``tractor`` is a `structured concurrent`_, multi-processing_ runtime
built on trio_.

Fundamentally ``tractor`` gives you parallelism via ``trio``-"*actors*":
our nurseries_ let you spawn new Python processes which each run a ``trio``
scheduled runtime - a call to ``trio.run()``.

We believe the system adhere's to the `3 axioms`_ of an "`actor model`_"
We believe the system adheres to the `3 axioms`_ of an "`actor model`_"
but likely *does not* look like what *you* probably think an "actor
model" looks like, and that's *intentional*.

Expand Down Expand Up @@ -577,13 +578,13 @@ say hi, please feel free to reach us in our `matrix channel`_. If
matrix seems too hip, we're also mostly all in the the `trio gitter
channel`_!

.. _structured concurrent: https://trio.discourse.group/t/concise-definition-of-structured-concurrency/228
.. _multi-processing: https://en.wikipedia.org/wiki/Multiprocessing
.. _trio: https://github.com/python-trio/trio
.. _nurseries: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#nurseries-a-structured-replacement-for-go-statements
.. _actor model: https://en.wikipedia.org/wiki/Actor_model
.. _trio: https://github.com/python-trio/trio
.. _multi-processing: https://en.wikipedia.org/wiki/Multiprocessing
.. _trionic: https://trio.readthedocs.io/en/latest/design.html#high-level-design-principles
.. _async sandwich: https://trio.readthedocs.io/en/latest/tutorial.html#async-sandwich
.. _structured concurrent: https://trio.discourse.group/t/concise-definition-of-structured-concurrency/228
.. _3 axioms: https://www.youtube.com/watch?v=7erJ1DV_Tlo&t=162s
.. .. _3 axioms: https://en.wikipedia.org/wiki/Actor_model#Fundamental_concepts
.. _adherance to: https://www.youtube.com/watch?v=7erJ1DV_Tlo&t=1821s
Expand Down
Empty file added examples/__init__.py
Empty file.
40 changes: 40 additions & 0 deletions examples/debugging/open_ctx_modnofound.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import trio
import tractor


@tractor.context
async def just_sleep(

ctx: tractor.Context,
**kwargs,

) -> None:
'''
Start and sleep.
'''
await ctx.started()
await trio.sleep_forever()


async def main() -> None:

async with tractor.open_nursery(
debug_mode=True,
) as n:
portal = await n.start_actor(
'ctx_child',

# XXX: we don't enable the current module in order
# to trigger `ModuleNotFound`.
enable_modules=[],
)

async with portal.open_context(
just_sleep, # taken from pytest parameterization
) as (ctx, sent):
raise KeyboardInterrupt


if __name__ == '__main__':
trio.run(main)
50 changes: 50 additions & 0 deletions examples/debugging/subactor_bp_in_ctx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import tractor
import trio


async def gen():
yield 'yo'
await tractor.breakpoint()
yield 'yo'
await tractor.breakpoint()


@tractor.context
async def just_bp(
ctx: tractor.Context,
) -> None:

await ctx.started()
await tractor.breakpoint()

# TODO: bps and errors in this call..
async for val in gen():
print(val)

# await trio.sleep(0.5)

# prematurely destroy the connection
await ctx.chan.aclose()

# THIS CAUSES AN UNRECOVERABLE HANG
# without latest ``pdbpp``:
assert 0



async def main():
async with tractor.open_nursery(
debug_mode=True,
) as n:
p = await n.start_actor(
'bp_boi',
enable_modules=[__name__],
)
async with p.open_context(
just_bp,
) as (ctx, first):
await trio.sleep_forever()


if __name__ == '__main__':
trio.run(main)
49 changes: 49 additions & 0 deletions examples/integration/open_context_and_sleep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import trio
import click
import tractor
import pydantic
# from multiprocessing import shared_memory


@tractor.context
async def just_sleep(

ctx: tractor.Context,
**kwargs,

) -> None:
'''
Test a small ping-pong 2-way streaming server.
'''
await ctx.started()
await trio.sleep_forever()


async def main() -> None:

proc = await trio.open_process( (
'python',
'-c',
'import trio; trio.run(trio.sleep_forever)',
))
await proc.wait()
# await trio.sleep_forever()
# async with tractor.open_nursery() as n:

# portal = await n.start_actor(
# 'rpc_server',
# enable_modules=[__name__],
# )

# async with portal.open_context(
# just_sleep, # taken from pytest parameterization
# ) as (ctx, sent):
# await trio.sleep_forever()



if __name__ == '__main__':
import time
# time.sleep(999)
trio.run(main)
36 changes: 36 additions & 0 deletions nooz/165.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Add SIGINT protection to our `pdbpp` based debugger subystem such that
for (single-depth) actor trees in debug mode we ignore interrupts in any
actor currently holding the TTY lock thus avoiding clobbering IPC
connections and/or task and process state when working in the REPL.

As a big note currently so called "nested" actor trees (trees with
actors having more then one parent/ancestor) are not fully supported
since we don't yet have a mechanism to relay the debug mode knowledge
"up" the actor tree (for eg. when handling a crash in a leaf actor).
As such currently there is a set of tests and known scenarios which will
result in process cloberring by the zombie repaing machinery and these
have been documented in https://github.com/goodboy/tractor/issues/320.

The implementation details include:

- utilizing a custom SIGINT handler which we apply whenever an actor's
runtime enters the debug machinery, which we also make sure the
stdlib's `pdb` configuration doesn't override (which it does by
default without special instance config).
- litter the runtime with `maybe_wait_for_debugger()` mostly in spots
where the root actor should block before doing embedded nursery
teardown ops which both cancel potential-children-in-deubg as well
as eventually trigger zombie reaping machinery.
- hardening of the TTY locking semantics/API both in terms of IPC
terminations and cancellation and lock release determinism from
sync debugger instance methods.
- factoring of locking infrastructure into a new `._debug.Lock` global
which encapsulates all details of the ``trio`` sync primitives and
task/actor uid management and tracking.

We also add `ctrl-c` cases throughout the test suite though these are
disabled for py3.9 (`pdbpp` UX differences that don't seem worth
compensating for, especially since this will be our last 3.9 supported
release) and there are a slew of marked cases that aren't expected to
work in CI more generally (as mentioned in the "nested" tree note
above) despite seemingly working when run manually on linux.
1 change: 1 addition & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pytest
pytest-trio
pytest-timeout
pdbpp
mypy<0.920
trio_typing<0.7.0
Expand Down
17 changes: 14 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
install_requires=[

# trio related
'trio>0.8',
'trio >= 0.20',
'async_generator',
'trio_typing',

Expand All @@ -54,12 +54,23 @@
# tooling
'colorlog',
'wrapt',
'pdbpp',

# pip ref docs on these specs:
# https://pip.pypa.io/en/stable/reference/requirement-specifiers/#examples
# and pep:
# https://peps.python.org/pep-0440/#version-specifiers
'pdbpp <= 0.10.1; python_version < "3.10"',

# windows deps workaround for ``pdbpp``
# https://github.com/pdbpp/pdbpp/issues/498
# https://github.com/pdbpp/fancycompleter/issues/37
'pyreadline3 ; platform_system == "Windows"',

# 3.10 has an outstanding unreleased issue and `pdbpp` itself
# pins to patched forks of its own dependencies as well..and
# we need a specific patch on master atm.
'pdbpp @ git+https://github.com/pdbpp/pdbpp@76c4be5#egg=pdbpp ; python_version > "3.9"', # noqa: E501

# serialization
'msgspec >= "0.4.0"'

Expand All @@ -83,8 +94,8 @@
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Intended Audience :: Science/Research",
"Intended Audience :: Developers",
"Topic :: System :: Distributed Computing",
Expand Down
5 changes: 4 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,14 @@ def spawn_backend(request):
return request.config.option.spawn_backend


_ci_env: bool = os.environ.get('CI', False)


@pytest.fixture(scope='session')
def ci_env() -> bool:
"""Detect CI envoirment.
"""
return os.environ.get('TRAVIS', False) or os.environ.get('CI', False)
return _ci_env


@pytest.fixture(scope='session')
Expand Down
62 changes: 32 additions & 30 deletions tests/test_context_stream_semantics.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,42 +265,44 @@ async def test_callee_closes_ctx_after_stream_open():
enable_modules=[__name__],
)

async with portal.open_context(
close_ctx_immediately,
with trio.fail_after(2):
async with portal.open_context(
close_ctx_immediately,

# flag to avoid waiting the final result
# cancel_on_exit=True,
# flag to avoid waiting the final result
# cancel_on_exit=True,

) as (ctx, sent):
) as (ctx, sent):

assert sent is None
assert sent is None

with trio.fail_after(0.5):
async with ctx.open_stream() as stream:

# should fall through since ``StopAsyncIteration``
# should be raised through translation of
# a ``trio.EndOfChannel`` by
# ``trio.abc.ReceiveChannel.__anext__()``
async for _ in stream:
assert 0
else:

# verify stream is now closed
try:
await stream.receive()
except trio.EndOfChannel:
pass
with trio.fail_after(0.5):
async with ctx.open_stream() as stream:

# TODO: should be just raise the closed resource err
# directly here to enforce not allowing a re-open
# of a stream to the context (at least until a time of
# if/when we decide that's a good idea?)
try:
async with ctx.open_stream() as stream:
# should fall through since ``StopAsyncIteration``
# should be raised through translation of
# a ``trio.EndOfChannel`` by
# ``trio.abc.ReceiveChannel.__anext__()``
async for _ in stream:
assert 0
else:

# verify stream is now closed
try:
await stream.receive()
except trio.EndOfChannel:
pass

# TODO: should be just raise the closed resource err
# directly here to enforce not allowing a re-open
# of a stream to the context (at least until a time of
# if/when we decide that's a good idea?)
try:
with trio.fail_after(0.5):
async with ctx.open_stream() as stream:
pass
except trio.ClosedResourceError:
pass
except trio.ClosedResourceError:
pass

await portal.cancel_actor()

Expand Down

0 comments on commit 641ed7a

Please sign in to comment.