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

Ignore SIGINT when in a debugger REPL #165

Merged
merged 100 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
3cac323
Add WIP while-debugger-active SIGINT ignore handler
goodboy Jan 23, 2022
6b7b583
(facepalm) Reraise `BdbQuit` and discard ownerless lock releases
goodboy Jan 23, 2022
4e60c17
Refine the handler for child vs. root cases
goodboy Jan 23, 2022
345573e
Make `mypy` happy
goodboy Jan 23, 2022
42f9d10
Add a pre-started breakpoint example
goodboy Jan 27, 2022
e519526
Handle a context cancel? Might be a noop
goodboy Feb 4, 2022
99c4319
Fix example name typo
goodboy Feb 7, 2022
7964a9f
Try overriding `_GeneratorContextManager.__exit__()`; didn't work..
goodboy Feb 7, 2022
aea8f63
Drop all the `@cm.__exit__()` override attempts..
goodboy Feb 7, 2022
21dccb2
A `.open_context()` example that causes a hang!
goodboy Feb 7, 2022
8f4bbf1
Add and use a pdb instance factory
goodboy Feb 9, 2022
8892204
Add notes around py3.10 stdlib bug from `pdb++`
goodboy Feb 9, 2022
74b819a
Typing fixes, simplify `_set_trace()`
goodboy Feb 9, 2022
bb732ce
Drop high log level in ctx example
goodboy Feb 9, 2022
bf0ac31
Only cancel/get-result from a ctx if transport is up
goodboy Feb 14, 2022
206c7c0
Make `Actor._process_messages()` report disconnects
goodboy Feb 14, 2022
41924c8
Drop uneeded backframe traceback hide annotation
goodboy Feb 14, 2022
f2671ed
Type annot updates
goodboy Feb 14, 2022
2819b6a
Avoid attr error XD
goodboy Feb 16, 2022
89b44f8
Pre-declare disconnected flag
goodboy Feb 16, 2022
dd23e78
Add back in async gen loop
goodboy Feb 16, 2022
fe0fd1a
Add example that triggers bug #302
goodboy Feb 17, 2022
4fd924c
Make example a subpkg for `python -m <mod>` testing
goodboy Feb 18, 2022
7bb5add
Only warn on `trio.BrokenResourceError`s from `_invoke()`
goodboy Feb 18, 2022
4be13b7
Just warn on IPC breaks
goodboy Feb 24, 2022
0062c96
Log cancels with appropriate level
goodboy Mar 4, 2022
d47d0e7
Always call pdb hook even if tty locking fails
goodboy Apr 11, 2022
deaca7d
Always propagate SIGINT when no locking peer found
goodboy May 14, 2022
c7035be
Tolerate double `.remove()`s of stream on portal teardowns
goodboy May 14, 2022
418e74e
Pin to `pdbpp` upstream master, 3.10 problem?
goodboy May 31, 2022
2f5a604
Readme formatting tweaks
goodboy May 31, 2022
f07e9db
Always undo SIGINT overrides, cancel detached children
goodboy Jun 26, 2022
b29def8
Add runtime level msg around channel draining
goodboy Jun 26, 2022
e2453fd
Add spaces before values in log msg
goodboy Jun 26, 2022
2a61aa0
Move pydantic-click hang example to new dir, skip in test suite
goodboy Jun 26, 2022
201c026
Show full KBI trace for help with CI hangs
goodboy Jun 26, 2022
18c525d
Hack around double long list print issue..
goodboy Jun 27, 2022
439d320
Add basic ctl-c testing cases to suite
goodboy Jun 27, 2022
abb0053
Add help msg for non `__main__` modules as well
goodboy Jul 1, 2022
ff3f595
Always enable debug level logging if mode enabled
goodboy Jul 1, 2022
56c1909
Add basic module-not-found when opening a ctx eg.
goodboy Jul 6, 2022
519f4c3
I dunno, seems like `breakpoint()` needs this?
goodboy Jul 7, 2022
4e08605
Only do `pdbpp` from `git` install on 3.10+
goodboy Jul 10, 2022
d0dcd55
Only report disconnected actors if proc is still alive?
goodboy Jul 10, 2022
a90ca4b
Call longlist normally when on py < 3.10
goodboy Jul 11, 2022
9bc38cb
Add slight delay 2nd ctlc round..
goodboy Jul 11, 2022
bd7d507
Guard against `asyncio` cancelled logged to console
goodboy Jul 11, 2022
8b9f342
Port to new `.lowlevel.open_process()` API
goodboy Jul 11, 2022
19fb77f
Pin to `trio >= 0.20`
goodboy Jul 11, 2022
64909e6
Fix loglevel in subactor test; actually pass the level XD
goodboy Jul 11, 2022
4dcc212
Only call `.poll()` if a method on the spawn backend
goodboy Jul 11, 2022
b9eb601
General typing fixes for `mypy`
goodboy Jul 11, 2022
925d5c1
Pin to specific `pdbppp` master commit
goodboy Jul 11, 2022
56b30a9
Add sleep around ctl-c iteration loop
goodboy Jul 11, 2022
70ad0f6
Add longer delays around ctl-c loop, don't expect longlist
goodboy Jul 11, 2022
8358361
Just don't call longlist on 3.10+ for now
goodboy Jul 11, 2022
a101971
Go back to original longlist code
goodboy Jul 11, 2022
ef8dc02
Just drop all longlisting for now and leave comments
goodboy Jul 11, 2022
a723501
Test: drop expect prompt
goodboy Jul 11, 2022
dadd5e6
Add back prompt expect via flag
goodboy Jul 12, 2022
617d57d
Disable ctl-c prompt checks again
goodboy Jul 12, 2022
ba7b355
Add note about default behaviour of `fancycompleter`
goodboy Jul 12, 2022
a2e9019
Add ctl-c case to `subactor_breakpoint` example test
goodboy Jul 12, 2022
adbebd3
Add ctl-c to remaining tests, only expect prompt in non-CI
goodboy Jul 12, 2022
6bdcbdb
Do child decode on `do_ctlc` exit?
goodboy Jul 12, 2022
4779bad
Add before assert helper and print console bytes on fail
goodboy Jul 15, 2022
b21f2e1
Always consider the debugger when exiting contexts
goodboy Jul 23, 2022
808d7ae
Add timeout guard around caller side context open
goodboy Jul 27, 2022
cb0c47c
Try disabling prompt expect in ctrlc cases
goodboy Jul 28, 2022
bd362a0
Run release hook around `next` repl commands as well
goodboy Jul 28, 2022
b01daa5
Factor lock-state release logic into helper
goodboy Jul 28, 2022
a4538a3
Drop ctlc tests on Py3.9...
goodboy Jul 28, 2022
c0cd99e
Timeout on arbiter ping, avoid TCP SYN hangs in CI?
goodboy Jul 28, 2022
1d4d55f
Increase verbosity in ci tests for now
goodboy Jul 28, 2022
20c660f
Add timeout on spawn error msg check
goodboy Jul 29, 2022
a4bac13
Use `pytest-timeout` plug to try and prevent CI hang
goodboy Jul 29, 2022
457499b
Avoid infinite wait for EOF
goodboy Jul 29, 2022
6f01c78
Disable `pygments` highlighting on ctlc tests
goodboy Jul 29, 2022
5e23b3c
Drop pytest full-tracing in CI again
goodboy Jul 29, 2022
08cf03c
Handle missing prompt render case?
goodboy Jul 29, 2022
91f034a
Move all module vars into a `Lock` type
goodboy Jul 29, 2022
937ed99
Factor sigint overriding into lock methods
goodboy Jul 29, 2022
87b2ccb
Try less times for EOF
goodboy Jul 29, 2022
8896ba2
Use `assert_before` more extensively
goodboy Jul 29, 2022
aca9a6b
Try just skipping nested actor tests in CI
goodboy Aug 1, 2022
acfbae4
Drop verbose level, report xfails
goodboy Aug 1, 2022
a9aaee9
Use xfails for nested cases, revert prompt expect
goodboy Aug 1, 2022
e4771ee
Go back to skipping since xfail is wack
goodboy Aug 1, 2022
c5c7a90
Line len lint and drop rpc log msg level again
goodboy Aug 1, 2022
54de72d
Loosen timeout on nested child re-locking
goodboy Aug 1, 2022
fa43888
Add an expect wrapper, use in hanging CI test
goodboy Aug 1, 2022
02c3b9a
Put `pygments` back to default
goodboy Aug 1, 2022
8115759
Mark final nested-actor debugger test
goodboy Aug 1, 2022
2d387f2
Add in issue link for nested cases
goodboy Aug 2, 2022
7f6169a
Drop legacy commented/todo remote debug helper block
goodboy Aug 2, 2022
e4006da
Drop `pdbpp` bug notes, add follow up issue #320 note
goodboy Aug 2, 2022
650313d
Drop legacy handler blocks factored into `_acquire_debug_lock()`
goodboy Aug 2, 2022
65540f3
Add nooz
goodboy Aug 2, 2022
8f1fe23
Simplify all hooks to a common `Lock.release()`
goodboy Aug 2, 2022
cc5f60b
List deps in CI
goodboy Aug 2, 2022
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
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
Loading