Compare commits
10 Commits
705538398f
...
e5ee2e3de8
Author | SHA1 | Date |
---|---|---|
goodboy | e5ee2e3de8 | |
Tyler Goodlet | 41aa91c8eb | |
Tyler Goodlet | 6758e4487c | |
Tyler Goodlet | 1c3893a383 | |
Tyler Goodlet | 73befac9bc | |
Tyler Goodlet | 79622bbeea | |
Tyler Goodlet | 95535b2226 | |
Tyler Goodlet | 87c6e09d6b | |
Tyler Goodlet | 9ccd3a74b6 | |
Tyler Goodlet | ae4ff5dc8d |
|
@ -6,8 +6,14 @@
|
||||||
``tractor`` is a `structured concurrent`_, multi-processing_ runtime
|
``tractor`` is a `structured concurrent`_, multi-processing_ runtime
|
||||||
built on trio_.
|
built on trio_.
|
||||||
|
|
||||||
Fundamentally ``tractor`` gives you parallelism via ``trio``-"*actors*":
|
Fundamentally, ``tractor`` gives you parallelism via
|
||||||
our nurseries_ let you spawn new Python processes which each run a ``trio``
|
``trio``-"*actors*": independent Python processes (aka
|
||||||
|
non-shared-memory threads) which maintain structured
|
||||||
|
concurrency (SC) *end-to-end* inside a *supervision tree*.
|
||||||
|
|
||||||
|
Cross-process (and thus cross-host) SC is accomplished through the
|
||||||
|
combined use of our "actor nurseries_" and an "SC-transitive IPC
|
||||||
|
protocol" constructed on top of multiple Pythons each running a ``trio``
|
||||||
scheduled runtime - a call to ``trio.run()``.
|
scheduled runtime - a call to ``trio.run()``.
|
||||||
|
|
||||||
We believe the system adheres to the `3 axioms`_ of an "`actor model`_"
|
We believe the system adheres to the `3 axioms`_ of an "`actor model`_"
|
||||||
|
@ -23,7 +29,8 @@ Features
|
||||||
- **It's just** a ``trio`` API
|
- **It's just** a ``trio`` API
|
||||||
- *Infinitely nesteable* process trees
|
- *Infinitely nesteable* process trees
|
||||||
- Builtin IPC streaming APIs with task fan-out broadcasting
|
- Builtin IPC streaming APIs with task fan-out broadcasting
|
||||||
- A (first ever?) "native" multi-core debugger UX for Python using `pdb++`_
|
- A "native" multi-core debugger REPL using `pdbp`_ (a fork & fix of
|
||||||
|
`pdb++`_ thanks to @mdmintz!)
|
||||||
- Support for a swappable, OS specific, process spawning layer
|
- Support for a swappable, OS specific, process spawning layer
|
||||||
- A modular transport stack, allowing for custom serialization (eg. with
|
- A modular transport stack, allowing for custom serialization (eg. with
|
||||||
`msgspec`_), communications protocols, and environment specific IPC
|
`msgspec`_), communications protocols, and environment specific IPC
|
||||||
|
@ -149,7 +156,7 @@ it **is a bug**.
|
||||||
|
|
||||||
"Native" multi-process debugging
|
"Native" multi-process debugging
|
||||||
--------------------------------
|
--------------------------------
|
||||||
Using the magic of `pdb++`_ and our internal IPC, we've
|
Using the magic of `pdbp`_ and our internal IPC, we've
|
||||||
been able to create a native feeling debugging experience for
|
been able to create a native feeling debugging experience for
|
||||||
any (sub-)process in your ``tractor`` tree.
|
any (sub-)process in your ``tractor`` tree.
|
||||||
|
|
||||||
|
@ -597,6 +604,7 @@ channel`_!
|
||||||
.. _adherance to: https://www.youtube.com/watch?v=7erJ1DV_Tlo&t=1821s
|
.. _adherance to: https://www.youtube.com/watch?v=7erJ1DV_Tlo&t=1821s
|
||||||
.. _trio gitter channel: https://gitter.im/python-trio/general
|
.. _trio gitter channel: https://gitter.im/python-trio/general
|
||||||
.. _matrix channel: https://matrix.to/#/!tractor:matrix.org
|
.. _matrix channel: https://matrix.to/#/!tractor:matrix.org
|
||||||
|
.. _pdbp: https://github.com/mdmintz/pdbp
|
||||||
.. _pdb++: https://github.com/pdbpp/pdbpp
|
.. _pdb++: https://github.com/pdbpp/pdbpp
|
||||||
.. _guest mode: https://trio.readthedocs.io/en/stable/reference-lowlevel.html?highlight=guest%20mode#using-guest-mode-to-run-trio-on-top-of-other-event-loops
|
.. _guest mode: https://trio.readthedocs.io/en/stable/reference-lowlevel.html?highlight=guest%20mode#using-guest-mode-to-run-trio-on-top-of-other-event-loops
|
||||||
.. _messages: https://en.wikipedia.org/wiki/Message_passing
|
.. _messages: https://en.wikipedia.org/wiki/Message_passing
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import trio
|
||||||
|
import tractor
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
async with tractor.open_nursery(debug_mode=True) as an:
|
||||||
|
|
||||||
|
assert os.environ['PYTHONBREAKPOINT'] == 'tractor._debug._set_trace'
|
||||||
|
|
||||||
|
# TODO: an assert that verifies the hook has indeed been, hooked
|
||||||
|
# XD
|
||||||
|
assert sys.breakpointhook is not tractor._debug._set_trace
|
||||||
|
|
||||||
|
breakpoint()
|
||||||
|
|
||||||
|
# TODO: an assert that verifies the hook is unhooked..
|
||||||
|
assert sys.breakpointhook
|
||||||
|
breakpoint()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
trio.run(main)
|
|
@ -0,0 +1,15 @@
|
||||||
|
Switch to using the fork & fix of `pdb++`, `pdbp`:
|
||||||
|
https://github.com/mdmintz/pdbp
|
||||||
|
|
||||||
|
Allows us to sidestep a variety of issues that aren't being maintained
|
||||||
|
in the upstream project thanks to the hard work of @mdmintz!
|
||||||
|
|
||||||
|
We also include some default settings adjustments as per recent
|
||||||
|
development on the fork:
|
||||||
|
|
||||||
|
- sticky mode is still turned on by default but now activates when
|
||||||
|
a using the `ll` repl command.
|
||||||
|
- turn off line truncation by default to avoid inter-line gaps when
|
||||||
|
resizing the terimnal during use.
|
||||||
|
- when using the backtrace cmd either by `w` or `bt`, the config
|
||||||
|
automatically switches to non-sticky mode.
|
|
@ -1,7 +1,7 @@
|
||||||
pytest
|
pytest
|
||||||
pytest-trio
|
pytest-trio
|
||||||
pytest-timeout
|
pytest-timeout
|
||||||
pdbpp
|
pdbp
|
||||||
mypy
|
mypy
|
||||||
trio_typing
|
trio_typing
|
||||||
pexpect
|
pexpect
|
||||||
|
|
|
@ -151,19 +151,6 @@ def ctlc(
|
||||||
|
|
||||||
use_ctlc = request.param
|
use_ctlc = request.param
|
||||||
|
|
||||||
# TODO: we can remove this bc pdbp right?
|
|
||||||
if (
|
|
||||||
sys.version_info <= (3, 10)
|
|
||||||
and use_ctlc
|
|
||||||
):
|
|
||||||
# on 3.9 it seems the REPL UX
|
|
||||||
# is highly unreliable and frankly annoying
|
|
||||||
# to test for. It does work from manual testing
|
|
||||||
# but i just don't think it's wroth it to try
|
|
||||||
# and get this working especially since we want to
|
|
||||||
# be 3.10+ mega-asap.
|
|
||||||
pytest.skip('Py3.9 and `pdbpp` son no bueno..')
|
|
||||||
|
|
||||||
node = request.node
|
node = request.node
|
||||||
markers = node.own_markers
|
markers = node.own_markers
|
||||||
for mark in markers:
|
for mark in markers:
|
||||||
|
@ -194,13 +181,15 @@ def ctlc(
|
||||||
ids=lambda item: f'{item[0]} -> {item[1]}',
|
ids=lambda item: f'{item[0]} -> {item[1]}',
|
||||||
)
|
)
|
||||||
def test_root_actor_error(spawn, user_in_out):
|
def test_root_actor_error(spawn, user_in_out):
|
||||||
"""Demonstrate crash handler entering pdbpp from basic error in root actor.
|
'''
|
||||||
"""
|
Demonstrate crash handler entering pdb from basic error in root actor.
|
||||||
|
|
||||||
|
'''
|
||||||
user_input, expect_err_str = user_in_out
|
user_input, expect_err_str = user_in_out
|
||||||
|
|
||||||
child = spawn('root_actor_error')
|
child = spawn('root_actor_error')
|
||||||
|
|
||||||
# scan for the pdbpp prompt
|
# scan for the prompt
|
||||||
expect(child, PROMPT)
|
expect(child, PROMPT)
|
||||||
|
|
||||||
before = str(child.before.decode())
|
before = str(child.before.decode())
|
||||||
|
@ -231,7 +220,7 @@ def test_root_actor_bp(spawn, user_in_out):
|
||||||
user_input, expect_err_str = user_in_out
|
user_input, expect_err_str = user_in_out
|
||||||
child = spawn('root_actor_breakpoint')
|
child = spawn('root_actor_breakpoint')
|
||||||
|
|
||||||
# scan for the pdbpp prompt
|
# scan for the prompt
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
|
|
||||||
assert 'Error' not in str(child.before)
|
assert 'Error' not in str(child.before)
|
||||||
|
@ -339,7 +328,7 @@ def test_subactor_error(
|
||||||
'''
|
'''
|
||||||
child = spawn('subactor_error')
|
child = spawn('subactor_error')
|
||||||
|
|
||||||
# scan for the pdbpp prompt
|
# scan for the prompt
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
|
|
||||||
before = str(child.before.decode())
|
before = str(child.before.decode())
|
||||||
|
@ -387,7 +376,7 @@ def test_subactor_breakpoint(
|
||||||
|
|
||||||
child = spawn('subactor_breakpoint')
|
child = spawn('subactor_breakpoint')
|
||||||
|
|
||||||
# scan for the pdbpp prompt
|
# scan for the prompt
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
|
|
||||||
before = str(child.before.decode())
|
before = str(child.before.decode())
|
||||||
|
@ -448,7 +437,7 @@ def test_multi_subactors(
|
||||||
'''
|
'''
|
||||||
child = spawn(r'multi_subactors')
|
child = spawn(r'multi_subactors')
|
||||||
|
|
||||||
# scan for the pdbpp prompt
|
# scan for the prompt
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
|
|
||||||
before = str(child.before.decode())
|
before = str(child.before.decode())
|
||||||
|
@ -688,7 +677,7 @@ def test_multi_subactors_root_errors(
|
||||||
'''
|
'''
|
||||||
child = spawn('multi_subactor_root_errors')
|
child = spawn('multi_subactor_root_errors')
|
||||||
|
|
||||||
# scan for the pdbpp prompt
|
# scan for the prompt
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
|
|
||||||
# at most one subactor should attach before the root is cancelled
|
# at most one subactor should attach before the root is cancelled
|
||||||
|
|
|
@ -54,18 +54,6 @@ from ._exceptions import (
|
||||||
)
|
)
|
||||||
from ._ipc import Channel
|
from ._ipc import Channel
|
||||||
|
|
||||||
|
|
||||||
# TODO: we can drop this now yah?
|
|
||||||
# try:
|
|
||||||
# # wtf: only exported when installed in dev mode?
|
|
||||||
# import pdbp
|
|
||||||
# except ImportError:
|
|
||||||
# # pdbpp is installed in regular mode...it monkey patches stuff
|
|
||||||
# import pdb
|
|
||||||
# xpm = getattr(pdb, 'xpm', None)
|
|
||||||
# assert xpm, "pdbpp is not installed?" # type: ignore
|
|
||||||
# pdbpp = pdb
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -161,13 +149,13 @@ class TractorConfig(pdbp.DefaultConfig):
|
||||||
Custom ``pdbp`` goodness :surfer:
|
Custom ``pdbp`` goodness :surfer:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# use_pygments = True
|
use_pygments: bool = True
|
||||||
sticky_by_default = True
|
sticky_by_default: bool = False
|
||||||
enable_hidden_frames = False
|
enable_hidden_frames: bool = False
|
||||||
|
|
||||||
# much thanks @mdmintz for the hot tip!
|
# much thanks @mdmintz for the hot tip!
|
||||||
# fixes line spacing issue when resizing terminal B)
|
# fixes line spacing issue when resizing terminal B)
|
||||||
truncate_long_lines = False
|
truncate_long_lines: bool = False
|
||||||
|
|
||||||
|
|
||||||
class MultiActorPdb(pdbp.Pdb):
|
class MultiActorPdb(pdbp.Pdb):
|
||||||
|
@ -754,8 +742,8 @@ def shield_sigint_handler(
|
||||||
|
|
||||||
|
|
||||||
def _set_trace(
|
def _set_trace(
|
||||||
actor: Optional[tractor.Actor] = None,
|
actor: tractor.Actor | None = None,
|
||||||
pdb: Optional[MultiActorPdb] = None,
|
pdb: MultiActorPdb | None = None,
|
||||||
):
|
):
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
actor = actor or tractor.current_actor()
|
actor = actor or tractor.current_actor()
|
||||||
|
@ -765,7 +753,11 @@ def _set_trace(
|
||||||
if frame:
|
if frame:
|
||||||
frame = frame.f_back # type: ignore
|
frame = frame.f_back # type: ignore
|
||||||
|
|
||||||
if frame and pdb and actor is not None:
|
if (
|
||||||
|
frame
|
||||||
|
and pdb
|
||||||
|
and actor is not None
|
||||||
|
):
|
||||||
log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n")
|
log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n")
|
||||||
# no f!#$&* idea, but when we're in async land
|
# no f!#$&* idea, but when we're in async land
|
||||||
# we need 2x frames up?
|
# we need 2x frames up?
|
||||||
|
@ -774,7 +766,8 @@ def _set_trace(
|
||||||
else:
|
else:
|
||||||
pdb, undo_sigint = mk_mpdb()
|
pdb, undo_sigint = mk_mpdb()
|
||||||
|
|
||||||
# we entered the global ``breakpoint()`` built-in from sync code?
|
# we entered the global ``breakpoint()`` built-in from sync
|
||||||
|
# code?
|
||||||
Lock.local_task_in_debug = 'sync'
|
Lock.local_task_in_debug = 'sync'
|
||||||
|
|
||||||
pdb.set_trace(frame=frame)
|
pdb.set_trace(frame=frame)
|
||||||
|
|
|
@ -22,8 +22,9 @@ from contextlib import asynccontextmanager
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
import typing
|
import typing
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
@ -84,8 +85,10 @@ async def open_root_actor(
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# Override the global debugger hook to make it play nice with
|
# Override the global debugger hook to make it play nice with
|
||||||
# ``trio``, see:
|
# ``trio``, see much discussion in:
|
||||||
# https://github.com/python-trio/trio/issues/1155#issuecomment-742964018
|
# https://github.com/python-trio/trio/issues/1155#issuecomment-742964018
|
||||||
|
builtin_bp_handler = sys.breakpointhook
|
||||||
|
orig_bp_path: str | None = os.environ.get('PYTHONBREAKPOINT', None)
|
||||||
os.environ['PYTHONBREAKPOINT'] = 'tractor._debug._set_trace'
|
os.environ['PYTHONBREAKPOINT'] = 'tractor._debug._set_trace'
|
||||||
|
|
||||||
# attempt to retreive ``trio``'s sigint handler and stash it
|
# attempt to retreive ``trio``'s sigint handler and stash it
|
||||||
|
@ -254,6 +257,15 @@ async def open_root_actor(
|
||||||
await actor.cancel()
|
await actor.cancel()
|
||||||
finally:
|
finally:
|
||||||
_state._current_actor = None
|
_state._current_actor = None
|
||||||
|
|
||||||
|
# restore breakpoint hook state
|
||||||
|
sys.breakpointhook = builtin_bp_handler
|
||||||
|
if orig_bp_path is not None:
|
||||||
|
os.environ['PYTHONBREAKPOINT'] = orig_bp_path
|
||||||
|
else:
|
||||||
|
# clear env back to having no entry
|
||||||
|
os.environ.pop('PYTHONBREAKPOINT')
|
||||||
|
|
||||||
logger.runtime("Root actor terminated")
|
logger.runtime("Root actor terminated")
|
||||||
|
|
||||||
|
|
||||||
|
@ -289,7 +301,7 @@ def run_daemon(
|
||||||
async def _main():
|
async def _main():
|
||||||
|
|
||||||
async with open_root_actor(
|
async with open_root_actor(
|
||||||
arbiter_addr=registry_addr,
|
registry_addr=registry_addr,
|
||||||
name=name,
|
name=name,
|
||||||
start_method=start_method,
|
start_method=start_method,
|
||||||
debug_mode=debug_mode,
|
debug_mode=debug_mode,
|
||||||
|
|
Loading…
Reference in New Issue