diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f0b0d90a..e050ea5b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,11 +17,6 @@ permissions: pages: write id-token: write -# never run >1 pages deploy at once -concurrency: - group: 'pages' - cancel-in-progress: true - jobs: build: name: 'sphinx build' @@ -52,6 +47,12 @@ jobs: name: 'deploy to gh-pages' if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} needs: build + # serialize deploys but NEVER cancel an in-flight one + # mid-upload; queue the next instead (scoped to this job so + # PR builds can't cancel a production deploy). + concurrency: + group: 'pages' + cancel-in-progress: false runs-on: ubuntu-latest environment: name: github-pages diff --git a/docs/explain/architecture.rst b/docs/explain/architecture.rst index 37a7f147..97d9fcdc 100644 --- a/docs/explain/architecture.rst +++ b/docs/explain/architecture.rst @@ -192,13 +192,14 @@ spawn backend, selected with the ``start_method`` kwarg to names (forkserver is posix-only). Mostly interesting for ecosystem compat and start-up-latency tuning. -``'subint'`` (experimental, py3.14+) - Each actor runs as a `PEP 734`_ sub-interpreter - (``concurrent.interpreters``) driven on its own OS thread - *inside the parent process*: interpreter-level - shared-nothing isolation with much faster start-up. Yes, - this bends the one-actor-one-process rule; the rest of the - model is unchanged. +``'subint'`` (in development, py3.14+) + On the roadmap (not yet selectable via ``start_method`` on + this release): run each actor as a `PEP 734`_ + sub-interpreter (``concurrent.interpreters``) driven on its + own OS thread *inside the parent process* — interpreter-level + shared-nothing isolation with much faster start-up. Yes, this + bends the one-actor-one-process rule; the rest of the model + is unchanged. The ``TRACTOR_SPAWN_METHOD`` env-var beats any caller-passed ``start_method``, so you can swap backends under an unmodified diff --git a/docs/guide/context.rst b/docs/guide/context.rst index 9ecab554..15ffc00d 100644 --- a/docs/guide/context.rst +++ b/docs/guide/context.rst @@ -302,9 +302,9 @@ Overruns and backpressure Stream msgs land in a bounded per-context buffer on the receiver side. A sender that outpaces a non-consuming receiver *overruns* -it and the runtime raises :exc:`tractor.StreamOverrun` (also a -:exc:`trio.TooSlowError`) instead of buffering without bound — SC -discipline applies to memory too. +it and the runtime raises ``StreamOverrun`` (from +``tractor._exceptions``; also a :exc:`trio.TooSlowError`) instead +of buffering without bound — SC discipline applies to memory too. Your knobs: diff --git a/docs/start/quickstart.rst b/docs/start/quickstart.rst index 75bbafb2..db50eb7e 100644 --- a/docs/start/quickstart.rst +++ b/docs/start/quickstart.rst @@ -141,7 +141,7 @@ breakfast - run this while monitoring your process tree:: Every subactor (best-effort, via the optional ``setproctitle`` dep) re-titles its OS process like - ``_subactor[worker_0@]``, so ``pstree``/``htop``/ + ``_subactor[worker_0@]``, so ``pstree``/``htop``/ ``pgrep -f`` can tell your actors apart at a glance. You'll see something like:: diff --git a/examples/debugging/shielded_pause.py b/examples/debugging/shielded_pause.py index 3e34d8fc..e6df907c 100644 --- a/examples/debugging/shielded_pause.py +++ b/examples/debugging/shielded_pause.py @@ -78,7 +78,7 @@ async def main(): portal: tractor.Portal = await n.run_in_actor( cancelled_before_pause, ) - await portal.result() + await portal.wait_for_result() # ensure the same works in the root actor! await pm_on_cancelled() diff --git a/examples/debugging/subactor_breakpoint.py b/examples/debugging/subactor_breakpoint.py index 4fdff484..67a5b7e0 100644 --- a/examples/debugging/subactor_breakpoint.py +++ b/examples/debugging/subactor_breakpoint.py @@ -22,7 +22,7 @@ async def main(): portal = await n.run_in_actor( breakpoint_forever, ) - await portal.result() + await portal.wait_for_result() if __name__ == '__main__': diff --git a/examples/debugging/subactor_error.py b/examples/debugging/subactor_error.py index 4bd809f9..fabdcedb 100644 --- a/examples/debugging/subactor_error.py +++ b/examples/debugging/subactor_error.py @@ -18,10 +18,10 @@ async def main(): p: tractor.Portal = await an.run_in_actor(name_error) # with this style, should raise on this line - await p.result() + await p.wait_for_result() # with this alt style should raise at `open_nusery()` - # return await p.result() + # return await p.wait_for_result() if __name__ == '__main__': diff --git a/examples/uds_transport_actor_tree.py b/examples/uds_transport_actor_tree.py index 956dd89e..62ee55f4 100644 --- a/examples/uds_transport_actor_tree.py +++ b/examples/uds_transport_actor_tree.py @@ -4,9 +4,9 @@ Demonstrate an actor tree which talks over unix-domain-socket `enable_transports=['uds']` when opening the root and every subactor inherits the preference. -The child's channel address is a filesystem socket path (no -TCP port in sight!) and, as a kernel-provided bonus, the -peer's pid is exchanged for free via `SO_PEERCRED`. +Every channel address is a filesystem socket path (no TCP port +in sight!) and, as a kernel-provided bonus, the peer's pid is +exchanged for free via `SO_PEERCRED`. ''' import os @@ -38,12 +38,17 @@ async def main() -> None: # filesystem socket path, NOT a (host, port) pair! raddr = portal.chan.raddr assert raddr.proto_key == 'uds' + # NOTE, `.sockpath` is the *shared listener* socket file + # (named for the root registrar) this channel rode in + # on, NOT a per-child path; the child-specific identity + # we get for free is the kernel-reported peer pid (via + # `SO_PEERCRED`). print( f'portal chan tpt proto: {raddr.proto_key!r}\n' - f'portal chan sock file: {raddr.sockpath}\n' + f'listener sock file: {raddr.sockpath}\n' f'kernel-reported peer pid: {raddr.maybe_pid}\n' ) - # ask the child for its own bind addr: also a + # ask the child for its OWN distinct bind addr: another # socket-file path under the runtime dir. print(f'child says: {await portal.run(report_addr)}') await portal.cancel_actor()