forked from goodboy/tractor
1
0
Fork 0
tractor/examples
Tyler Goodlet 408a74784e Catch `.pause_from_sync()` in root bg thread bugs!
Originally discovered as while using `tractor.pause_from_sync()`
from the `i3ipc` client running in a bg-thread that uses `asyncio`
inside `modden`.

Turns out we definitely aren't correctly handling `.pause_from_sync()`
from the root actor when called from a `trio.to_thread.run_sync()`
bg thread:
- root-actor bg threads which can't `Lock._debug_lock.acquire()` since
  they aren't in `trio.Task`s.
- even if scheduled via `.to_thread.run_sync(_debug._pause)` the
  acquirer won't be the task/thread which calls `Lock.release()` from
  `PdbREPL` hooks; this results in a RTE raised by `trio`..
- multiple threads will step on each other's stdio since cpython's GIL
  seems to ctx switch threads on every input from the user to the REPL
  loop..

Reproduce via reworking our example and test so that they catch and fail
for all edge cases:
- rework the `/examples/debugging/sync_bp.py` example to demonstrate the
  above issues, namely the stdio clobbering in the REPL when multiple
  threads and/or a subactor try to debug simultaneously.
  |_ run one thread using a task nursery to ensure it runs conc with the
     nursery's parent task.
  |_ ensure the bg threads run conc a subactor usage of
     `.pause_from_sync()`.
  |_ gravely detail all the special cases inside a TODO comment.
  |_ add some control flags to `sync_pause()` helper and don't use
     `breakpoint()` by default.
- extend and adjust `test_debugger.test_pause_from_sync` to match (and
  thus currently fail) by ensuring exclusive `PdbREPL` attachment when
  the 2 bg root-actor threads are concurrently interacting alongside the
  subactor:
  |_ should only see one of the `_pause_msg` logs at a time for either
     one of the threads or the subactor.
  |_ ensure each attaches (in no particular order) before expecting the
     script to exit.

Impl adjustments to `.devx._debug`:
- drop `Lock.repl`, no longer used.
- add `Lock._owned_by_root: bool` for the `.ctx_in_debug == None`
  root-actor-task active case.
- always `log.exception()` for any `._debug_lock.release()` ownership
  RTE emitted by `trio`, like we used to..
- add special `Lock.release()` log message for the stale lock but
  `._owned_by_root == True` case; oh yeah and actually
  `log.devx(message)`..
- rename `Lock.acquire()` -> `.acquire_for_ctx()` since it's only ever
  used from subactor IPC usage; well that and for local root-task
  usage we should prolly add a `.acquire_from_root_task()`?
- buncha `._pause()` impl improvements:
 |_ type `._pause()`'s `debug_func` as a `partial` as well.
 |_ offer `called_from_sync: bool` and `called_from_bg_thread: bool`
    for the special case handling when called from `.pause_from_sync()`
 |_ only set `DebugStatus.repl/repl_task` when `debug_func != None`
   (OW ensure the `.repl_task` is not the current one).
 |_ handle error logging even when `debug_func is None`..
 |_ lotsa detailed commentary around root-actor-bg-thread special cases.
- when `._set_trace(hide_tb=False)` do `pdbp.set_trace(frame=currentframe())`
  so the `._debug` internal frames are always included.
- by default always hide tracebacks for `.pause[_from_sync]()` internals.
- improve `.pause_from_sync()` to avoid root-bg-thread crashes:
 |_ pass new `called_from_xxx_` flags and ensure `DebugStatus.repl_task`
    is actually set to the `threading.current_thread()` when needed.
 |_ manually call `Lock._debug_lock.acquire_nowait()` for the non-bg
    thread case.
 |_ TODO: still need to implement the bg-thread case using a bg
    `trio.Task`-in-thread with an `trio.Event` set by thread REPL exit.
2024-06-06 16:56:30 -04:00
..
advanced_faults Start a new `._testing.fault_simulation` 2024-04-03 10:19:50 -04:00
debugging Catch `.pause_from_sync()` in root bg thread bugs! 2024-06-06 16:56:30 -04:00
integration Move pydantic-click hang example to new dir, skip in test suite 2022-08-02 12:16:58 -04:00
parallelism Drop now-deprecated deps on modern `trio`/Python 2024-03-13 18:41:24 -04:00
__init__.py Make example a subpkg for `python -m <mod>` testing 2022-07-27 11:40:02 -04:00
__main__.py Use trio.run() in windows tests 2021-05-07 11:21:40 -04:00
a_trynamic_first_scene.py Drop `tractor.run()` from all examples 2021-05-07 11:21:40 -04:00
actor_spawning_and_causality.py Drop `tractor.run()` from all examples 2021-05-07 11:21:40 -04:00
actor_spawning_and_causality_with_daemon.py Drop lingering rpc_module_paths refs 2021-05-07 11:21:40 -04:00
asynchronous_generators.py Terminate async gen example caller to avoid (benign) errors in console output 2021-08-02 21:49:15 -04:00
full_fledged_streaming_service.py Modernize streaming example script 2024-05-09 16:51:51 -04:00
infected_asyncio_echo_server.py Add context test that opens an inter-task-channel that errors 2022-07-14 16:13:12 -04:00
multiple_streams_one_portal.py Update all examples to new streaming API 2021-04-28 12:23:08 -04:00
quick_cluster.py Drop unecessary partial 2021-11-04 10:41:25 -04:00
remote_error_propagation.py Drop lingering rpc_module_paths refs 2021-05-07 11:21:40 -04:00
rpc_bidir_streaming.py Drop now-deprecated deps on modern `trio`/Python 2024-03-13 18:41:24 -04:00
service_discovery.py Drop `tractor.run()` from all examples 2021-05-07 11:21:40 -04:00