Add initial repl_fixture support, enter/exit hooks around the debugger sys! #28

Closed
goodboy wants to merge 120 commits from repl_fixture into pytest_pluginize

It turns out to be fairly useful to allow hooking into a given actor’s entry-and-exit around .devx._debug._pause/._post_mortem() calls which engage the pdbp.Pdb REPL (really our ._debug.PdbREPL but yeah).

Some very handy use cases include,

  • swapping out-of-band (config) state that may otherwise halt the user’s app since the actor normally handles kb&mouse input, in thread, which means that the handler will be blocked while the REPL is in use.

  • (remotely) reporting actor-runtime state for monitoring purposes around crashes or pauses in normal operation.

  • allowing for crash-handling to be hard-disabled via ._state._runtime_vars say for when you never want a debugger to be entered in a production instance where you’re not-sure-if/don’t-want per-actor debug_mode: bool settings to always be unset, say bc you’re still debugging some edge cases that ow you’d normally want to REPL up.


Impl deats for repl_fxture,

  • add a new optional ._state._runtime_vars['repl_fixture'] field which for now can be manually set; i saw no reason for a formal API yet since we want to convert the dict to a struct anyway (first).

  • augment both .devx._debug._pause()/._post_mortem() with a new optional repl_fixture: AbstractContextManager[bool] kwarg which when provided is opened (effectively ) as with repl_fixture() around the low-level-sync PdbREPL interaction calls; if the enter-result, an expected bool, is False then the interaction is hard-bypassed, the repl_fixture.__exit__() is later invoked by DebugStatus.release() as per the impl deats:

    • a new @cm helper to contain the activation logic, .devx.debug._post_mortem._maybe_open_repl_fixture() which: - when the new runtime-var repl_fixture is overridden, (only possible manually rn) use it but only whenever the repl_fixture`-kwarg is left null.

    • for the ._pause() case the @cm is opened around the entire body of the embedded _enter_repl_sync() closure (for now though, since ideally longer term this entire routine is factored to be a lot less “nested”)

    • for _post_mortem() the entire fn body is embedded and now also excepts an optional boxed_maybe_exc: BoxedMaybeException; it is only passed in the open_crash_handler() caller case.

    • all of the ^^ above was replaced by adding a new DebugStatus.maybe_enter_repl_fixture() which internally uses a ._fiture_stack: ExitStack that is closed from .release() allowing for extended support from the .pause_from_sync() API since the user’s repl_fixture.__exit__() can be invoked an arbitrary time later once PdbREPL interaction is complete!

  • add a BoxedMaybeException.pformat() = __repr__() which when a .value: Exception is set renders a more “objecty” repr of the exc.

Reorg of .devx._debug to new .devx.debug sub-pkg,

  • split up the current .devx._debug components into various .debug.* submods, like:
    • [maybe_]open_crash_handler() and friends into a _post_mortem.
    • all the pdbp specific customizations into a ._repl.
    • all the root actor TTY locking into its own ._tty_lock.
    • all pausing/pdb.set_trace()-like apis into a ._trace
    • our shielding SIGINT handler into ._sigint
    • various (unrefined and higher level) syncing apis which parts of the runtime use mostly to avoid REPL/TTY clobbering in a ._sync
  • move .devx.debug.hide_runtime_frames() -> .devx._frame_stack

Tests

  • as repl_fixture input to sync code’s use of [maybe_]_open_crash_handler() (which is currently implemented the same as the async APIs since async def post_mortem() just calls def _post_mortem())
    • the same but using the global rtvars override
      • which we should add an pub api for.. question is now or later?
  • tests for all async uses which are already implemented via the augmentation of _pause()’s internal _enter_repl_sync() closure.
    • from root actor.
    • from any (nested) sub.
    • from an infected aio task?
  • support for pause_from_sync()/breakpoint() ??
    • we need to break up the provided repl_fixture().__enter__() and .__exit__() calls with the latter invoked as part of the PdbREPL per method calls to DebugaStatus.release() to ensure the fixture’s teardown isn’t invoked until after the user releases their repl interaction.
      • likely impl will be to add an DebugStatus._fixture_stack: ExitStack and have every _pause() call .enter_context() prior to interaction and then have DebugStatus/PdbREPL call .fixture_stack.close() from .set_continue/quit()?
  • support for an @acm impls of replx_fixture?
    • obvi this can only be supported from already async APIs or we have to somehow schedule the async code in the trio/runtime thread, so hardest to do for sync-code crashes/pauses.

Surrounding changes,

  • fix the stackscope-using shield-pause test by adding typing_extenions sub-dep it needs.

  • maybe we can also include some (stringent) tests around similar usage of the undocumented Actor.lifetime_stack: ExitStack ??

It turns out to be fairly useful to allow hooking into a given actor's entry-and-exit around `.devx._debug._pause/._post_mortem()` calls which engage the `pdbp.Pdb` REPL (really our `._debug.PdbREPL` but yeah). Some very handy use cases include, - swapping out-of-band (config) state that may otherwise halt the user's app since the actor normally handles kb&mouse input, in thread, which means that the handler will be blocked while the REPL is in use. - (remotely) reporting actor-runtime state for monitoring purposes around crashes or pauses in normal operation. - allowing for crash-handling to be hard-disabled via `._state._runtime_vars` say for when you never want a debugger to be entered in a production instance where you're not-sure-if/don't-want per-actor `debug_mode: bool` settings to always be unset, say bc you're still debugging some edge cases that ow you'd normally want to REPL up. --- #### Impl deats for `repl_fxture`, - add a new optional `._state._runtime_vars['repl_fixture']` field which for now can be manually set; i saw no reason for a formal API yet since we want to convert the `dict` to a struct anyway (first). - [x] augment both `.devx._debug._pause()/._post_mortem()` with a new optional `repl_fixture: AbstractContextManager[bool]` kwarg which when provided is opened (effectively ) as `with repl_fixture()` around the low-level-sync `PdbREPL` interaction calls; if the enter-result, an expected `bool`, is `False` then the interaction is hard-bypassed, the `repl_fixture.__exit__()` is later invoked by `DebugStatus.release()` as per the impl deats: * ~~a new `@cm` helper to contain the activation logic,~~ ~~`.devx.debug._post_mortem._maybe_open_repl_fixture()` which:~~ ~~- when the new runtime-var `repl_fixture` is overridden, (only~~ ~~possible manually rn) use it but only whenever the~~ ~~repl_fixture`-kwarg is left null.~~ * ~~for the `._pause()` case the `@cm` is opened around the entire body~~ ~~of the embedded `_enter_repl_sync()` closure (for now though,~~ ~~since ideally longer term this entire routine is factored to~~ ~~be a lot less "nested")~~ * ~~for `_post_mortem()` the entire fn body is embedded and now also~~ ~~excepts an optional `boxed_maybe_exc: BoxedMaybeException`; it is~~ ~~only passed in the `open_crash_handler()` caller case.~~ * **all of the ^^ above was replaced by** adding a new `DebugStatus.maybe_enter_repl_fixture()` which internally uses a `._fiture_stack: ExitStack` that is closed from `.release()` allowing for extended support from the `.pause_from_sync()` API since the user's `repl_fixture.__exit__()` can be invoked an arbitrary time later once `PdbREPL` interaction is complete! #### Other related refinements, - add a `BoxedMaybeException.pformat() = __repr__()` which when a `.value: Exception` is set renders a more "objecty" repr of the exc. --- #### Reorg of `.devx._debug` to new `.devx.debug` sub-pkg, - [x] split up the current `.devx._debug` components into various `.debug.*` submods, like: - [x] `[maybe_]open_crash_handler()` and friends into a `_post_mortem`. - [x] all the `pdbp` specific customizations into a `._repl`. - [x] all the root actor TTY locking into its own `._tty_lock`. - [x] all pausing/`pdb.set_trace()`-like apis into a `._trace` - [x] our shielding SIGINT handler into `._sigint` - [x] various (unrefined and higher level) syncing apis which parts of the runtime use mostly to avoid REPL/TTY clobbering in a `._sync` - [x] move `.devx.debug.hide_runtime_frames()` -> `.devx._frame_stack` --- #### Tests - [ ] as `repl_fixture` input to sync code's use of `[maybe_]_open_crash_handler()` (which is currently implemented the same as the async APIs since `async def post_mortem()` just calls `def _post_mortem()`) - [ ] the same but using the global rtvars override - [ ] which we should add an pub api for.. question is now or later? - [ ] tests for all async uses which are already implemented via the augmentation of `_pause()`'s internal `_enter_repl_sync()` closure. - [ ] from root actor. - [ ] from any (nested) sub. - [ ] from an infected aio task? - support for `pause_from_sync()`/`breakpoint()` ?? - [ ] we need to break up the provided `repl_fixture().__enter__()` and `.__exit__()` calls with the latter invoked as part of the `PdbREPL` per method calls to `DebugaStatus.release()` to ensure the fixture's teardown isn't invoked until **after** the user releases their repl interaction. - [ ] likely impl will be to add an `DebugStatus._fixture_stack: ExitStack` and have every `_pause()` call `.enter_context()` prior to interaction and then have `DebugStatus`/`PdbREPL` call `.fixture_stack.close()` from `.set_continue/quit()`? - [ ] support for an `@acm` impls of `replx_fixture`? - obvi this can *only* be supported from already async APIs or we have to somehow schedule the async code in the trio/runtime thread, so hardest to do for sync-code crashes/pauses. Surrounding changes, - [x] fix the `stackscope`-using shield-pause test by adding `typing_extenions` sub-dep it needs. - [ ] maybe we can also include some (stringent) tests around similar usage of the undocumented `Actor.lifetime_stack: ExitStack` ??
goodboy force-pushed repl_fixture from 4fca7317ea to d716c57234 2025-05-16 04:25:36 +00:00 Compare
goodboy force-pushed repl_fixture from 24d21e7463 to 7b05547fcc 2025-06-12 02:09:21 +00:00 Compare
goodboy force-pushed repl_fixture from 7b05547fcc to 490a4cf8c8 2025-07-13 20:53:33 +00:00 Compare
Poster
Owner

This was already landed on main branch (already synced here as well) on GH,

https://github.com/goodboy/tractor/pull/385

Follow testing todo in,

https://github.com/goodboy/tractor/issues/386

This was already landed on `main` branch (already synced here as well) on GH, https://github.com/goodboy/tractor/pull/385 Follow testing todo in, https://github.com/goodboy/tractor/issues/386
goodboy closed this pull request 2025-07-15 20:35:15 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: goodboy/tractor#28
There is no content yet.