Enter inherited netns during child bootstrap
Consume an optional child-owned `(fd, inode)` capability before multiprocessing or Trio child bootstrap begins runtime setup. Deats, - enter the netns before Trio patching and actor construction - close the inherited FD before actor runtime startup - preserve entry errors when descriptor cleanup also fails - reject malformed FD values without closing unrelated descriptors - cover multiprocessing and Trio ordering with real stand-in FDs Prompt-IO: ai/prompt-io/opencode/20260828T012205Z_9ae7cd86_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
parent
6e338a1173
commit
7dfefddae3
|
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
session: wg-netns-bootstrap-entry
|
||||||
|
timestamp: 2026-08-28T01:22:05Z
|
||||||
|
git_ref: 9ae7cd86
|
||||||
|
scope: code
|
||||||
|
substantive: true
|
||||||
|
raw_file: 20260828T012205Z_9ae7cd86_prompt_io.raw.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
|
||||||
|
After completing and auditing the stacked rebase, the human approved
|
||||||
|
the next WireGuard Layer C slice: integrate the verified network
|
||||||
|
namespace entry primitive into child spawn bootstrap.
|
||||||
|
|
||||||
|
## Response summary
|
||||||
|
|
||||||
|
Wire an optional child-owned namespace FD and expected inode into both
|
||||||
|
child entrypoints. Keep namespace entry and FD closure synchronous and
|
||||||
|
ordered before actor runtime initialization, while deliberately
|
||||||
|
deferring backend FD transport and public API plumbing.
|
||||||
|
|
||||||
|
## Files changed
|
||||||
|
|
||||||
|
- `tractor/spawn/_entry.py` - enter and release an optional inherited
|
||||||
|
namespace capability before multiprocessing child runtime startup.
|
||||||
|
- `tractor/_child.py` - consume the capability at the start of the
|
||||||
|
Trio child bootloader, before patching or actor construction.
|
||||||
|
- `tests/test_netns_spawn.py` - prove entry ordering, failure isolation,
|
||||||
|
and exact FD ownership for both child entrypoints.
|
||||||
|
|
||||||
|
## Human edits
|
||||||
|
|
||||||
|
The human selected spawn-time namespace integration as the next work
|
||||||
|
slice and authorized implementation. No direct manual source edits
|
||||||
|
were observed during generation.
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
timestamp: 2026-08-28T01:22:05Z
|
||||||
|
git_ref: 9ae7cd86
|
||||||
|
diff_cmd: git diff HEAD~1..HEAD
|
||||||
|
---
|
||||||
|
|
||||||
|
# Raw output - enter inherited netns during child bootstrap
|
||||||
|
|
||||||
|
The human asked to continue the WireGuard Layer C implementation after
|
||||||
|
the rebased branch landed the verified `spawn._netns.enter_netns()`
|
||||||
|
primitive.
|
||||||
|
|
||||||
|
## Generated code
|
||||||
|
|
||||||
|
> `git diff HEAD~1..HEAD -- tractor/_child.py tractor/spawn/_entry.py tests/test_netns_spawn.py`
|
||||||
|
|
||||||
|
Add one optional atomic `(namespace_fd, expected_inode)` bootstrap
|
||||||
|
capability to the multiprocessing entrypoint and Trio child bootloader.
|
||||||
|
Enter and verify the namespace before Trio patching, actor construction,
|
||||||
|
runtime state, logging, Trio startup, parent connection, sockets, or
|
||||||
|
worker threads, then close the child-owned inherited FD before
|
||||||
|
continuing.
|
||||||
|
|
||||||
|
Add privilege-free direct-entrypoint tests using real stand-in FDs and
|
||||||
|
a fake namespace syscall boundary. Cover helper-level no-op behavior,
|
||||||
|
successful entry ordering, exact FD closure, malformed-FD isolation,
|
||||||
|
primary-error preservation, and failure cleanup for both child
|
||||||
|
bootstrap paths.
|
||||||
|
|
||||||
|
## Scope boundary
|
||||||
|
|
||||||
|
This increment does not transfer FDs through spawn backends or expose a
|
||||||
|
public actor API. Backend-specific FD duplication, bootstrap failure
|
||||||
|
reporting, and root-process namespace entry remain follow-up work.
|
||||||
|
|
@ -4,13 +4,37 @@ Pre-runtime Linux network-namespace entry validation.
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import errno
|
||||||
|
from functools import partial
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from typing import BinaryIO
|
from typing import (
|
||||||
|
Any,
|
||||||
|
BinaryIO,
|
||||||
|
)
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tractor.spawn import _netns
|
from tractor import _child
|
||||||
|
from tractor.devx import _proctitle
|
||||||
|
from tractor.spawn import (
|
||||||
|
_entry,
|
||||||
|
_netns,
|
||||||
|
_spawn,
|
||||||
|
)
|
||||||
|
from tractor.trionics import patches
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_fd_closed(namespace_fd: int) -> None:
|
||||||
|
'''
|
||||||
|
Assert that bootstrap consumed its child-owned descriptor.
|
||||||
|
|
||||||
|
'''
|
||||||
|
with pytest.raises(OSError) as exc_info:
|
||||||
|
os.fstat(namespace_fd)
|
||||||
|
|
||||||
|
assert exc_info.value.errno == errno.EBADF
|
||||||
|
|
||||||
|
|
||||||
def test_enter_netns_rejects_mismatched_inherited_fd(
|
def test_enter_netns_rejects_mismatched_inherited_fd(
|
||||||
|
|
@ -140,3 +164,416 @@ def test_enter_netns_rejects_wrong_post_entry_namespace(
|
||||||
# Deliberately differ from `fake_stat()`'s inode + 1.
|
# Deliberately differ from `fake_stat()`'s inode + 1.
|
||||||
inode,
|
inode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_netns_bootstrap_is_a_noop(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Ordinary child startup must not attempt namespace entry.
|
||||||
|
|
||||||
|
Leave the optional capability unset and arm `enter_netns()` as a
|
||||||
|
failure sentinel. The bootstrap boundary must return without any
|
||||||
|
syscall or descriptor ownership work for existing spawn callers.
|
||||||
|
|
||||||
|
'''
|
||||||
|
def fail_enter_netns(namespace_fd: int, inode: int) -> int:
|
||||||
|
'''
|
||||||
|
Reject namespace entry without an explicit capability.
|
||||||
|
|
||||||
|
'''
|
||||||
|
raise AssertionError('empty bootstrap attempted netns entry')
|
||||||
|
|
||||||
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
||||||
|
|
||||||
|
assert _entry._consume_netns_bootstrap(None) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace_fd', (-1, True, '1'))
|
||||||
|
def test_invalid_netns_fd_is_never_closed(
|
||||||
|
namespace_fd: object,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Invalid descriptor values must not reach the OS close boundary.
|
||||||
|
|
||||||
|
Feed negative, boolean, and non-integer values through the atomic
|
||||||
|
capability. Preserve the namespace primitive's validation error
|
||||||
|
without letting `bool` alias stdout or allowing cleanup to mask the
|
||||||
|
primary failure.
|
||||||
|
|
||||||
|
'''
|
||||||
|
entry_error = ValueError('invalid netns capability')
|
||||||
|
|
||||||
|
def fail_enter_netns(namespace_fd: int, inode: int) -> int:
|
||||||
|
'''
|
||||||
|
Raise the primary namespace bootstrap error.
|
||||||
|
|
||||||
|
'''
|
||||||
|
raise entry_error
|
||||||
|
|
||||||
|
def fail_close(inherited_fd: int) -> None:
|
||||||
|
'''
|
||||||
|
Reject cleanup for a value that cannot be an owned FD.
|
||||||
|
|
||||||
|
'''
|
||||||
|
raise AssertionError('invalid namespace FD reached close')
|
||||||
|
|
||||||
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_entry,
|
||||||
|
'os',
|
||||||
|
SimpleNamespace(close=fail_close),
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as exc_info:
|
||||||
|
_entry._consume_netns_bootstrap(
|
||||||
|
(namespace_fd, 1), # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value is entry_error
|
||||||
|
|
||||||
|
|
||||||
|
def test_netns_entry_error_survives_close_failure(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Descriptor cleanup must not mask the primary bootstrap failure.
|
||||||
|
|
||||||
|
Raise a unique entry error for an oversized positive integer whose
|
||||||
|
cleanup also raises `OverflowError`. The entry error must escape
|
||||||
|
with cleanup context attached instead of being replaced by the
|
||||||
|
close failure.
|
||||||
|
|
||||||
|
'''
|
||||||
|
namespace_fd: int = 1 << 100
|
||||||
|
entry_error = ValueError('invalid netns capability')
|
||||||
|
|
||||||
|
def fail_enter_netns(inherited_fd: int, inode: int) -> int:
|
||||||
|
'''
|
||||||
|
Raise the primary namespace bootstrap error.
|
||||||
|
|
||||||
|
'''
|
||||||
|
assert inherited_fd == namespace_fd
|
||||||
|
raise entry_error
|
||||||
|
|
||||||
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as exc_info:
|
||||||
|
_entry._consume_netns_bootstrap((namespace_fd, 1))
|
||||||
|
|
||||||
|
assert exc_info.value is entry_error
|
||||||
|
assert entry_error.__notes__
|
||||||
|
assert 'close inherited namespace FD' in entry_error.__notes__[0]
|
||||||
|
assert 'OverflowError' in entry_error.__notes__[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('backend', ('mp', 'trio'))
|
||||||
|
def test_child_entry_consumes_netns_before_runtime(
|
||||||
|
backend: str,
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Child bootstrap must enter its netns before runtime side effects.
|
||||||
|
|
||||||
|
Give each child entrypoint an exclusively owned stand-in FD. Fake
|
||||||
|
only namespace entry and every later bootstrap boundary, requiring
|
||||||
|
the FD to remain open during entry but be closed before actor state,
|
||||||
|
logging, multiprocessing setup, frame hiding, or `trio.run()`.
|
||||||
|
This proves verified entry and capability release are one
|
||||||
|
synchronous prefix of both child startup paths.
|
||||||
|
|
||||||
|
'''
|
||||||
|
token_path: Path = tmp_path / f'{backend}-netns'
|
||||||
|
token_path.touch()
|
||||||
|
namespace_fd: int = os.open(token_path, os.O_RDONLY)
|
||||||
|
expected_inode: int = os.fstat(namespace_fd).st_ino
|
||||||
|
events: list[str] = []
|
||||||
|
|
||||||
|
def fake_enter_netns(
|
||||||
|
inherited_fd: int,
|
||||||
|
inode: int,
|
||||||
|
) -> int:
|
||||||
|
'''
|
||||||
|
Record verified entry while the capability remains open.
|
||||||
|
|
||||||
|
'''
|
||||||
|
assert os.fstat(inherited_fd).st_ino == expected_inode
|
||||||
|
assert inherited_fd == namespace_fd
|
||||||
|
assert inode == expected_inode
|
||||||
|
events.append('enter-netns')
|
||||||
|
return inode
|
||||||
|
|
||||||
|
def record(
|
||||||
|
event: str,
|
||||||
|
*args: object,
|
||||||
|
**kwargs: object,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Record one post-entry operation after proving FD release.
|
||||||
|
|
||||||
|
'''
|
||||||
|
_assert_fd_closed(namespace_fd)
|
||||||
|
events.append(event)
|
||||||
|
|
||||||
|
class ActorSpy:
|
||||||
|
'''
|
||||||
|
Record multiprocessing actor-state initialization.
|
||||||
|
|
||||||
|
'''
|
||||||
|
loglevel = None
|
||||||
|
uid = ('netns-child', 'test')
|
||||||
|
_infected_aio = False
|
||||||
|
|
||||||
|
def __setattr__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
value: object,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Observe the first multiprocessing entrypoint mutation.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if name == '_forkserver_info':
|
||||||
|
record('forkserver-info')
|
||||||
|
object.__setattr__(self, name, value)
|
||||||
|
|
||||||
|
class StateSpy:
|
||||||
|
'''
|
||||||
|
Record actor publication into runtime-global state.
|
||||||
|
|
||||||
|
'''
|
||||||
|
def __setattr__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
value: object,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Observe `_state._current_actor` publication.
|
||||||
|
|
||||||
|
'''
|
||||||
|
record('runtime-state')
|
||||||
|
object.__setattr__(self, name, value)
|
||||||
|
|
||||||
|
def fake_current_process() -> str:
|
||||||
|
'''
|
||||||
|
Return one display value for multiprocessing startup logging.
|
||||||
|
|
||||||
|
'''
|
||||||
|
return 'fake-child-process'
|
||||||
|
|
||||||
|
def fake_start_method(start_method: str) -> SimpleNamespace:
|
||||||
|
'''
|
||||||
|
Record multiprocessing setup after namespace entry.
|
||||||
|
|
||||||
|
'''
|
||||||
|
record('start-method')
|
||||||
|
return SimpleNamespace(
|
||||||
|
current_process=fake_current_process,
|
||||||
|
)
|
||||||
|
|
||||||
|
def fake_actor(**kwargs: object) -> ActorSpy:
|
||||||
|
'''
|
||||||
|
Record Trio child actor construction after namespace entry.
|
||||||
|
|
||||||
|
'''
|
||||||
|
record('actor-construction')
|
||||||
|
return ActorSpy()
|
||||||
|
|
||||||
|
monkeypatch.setattr(_entry, 'enter_netns', fake_enter_netns)
|
||||||
|
monkeypatch.setattr(_entry, '_state', StateSpy())
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_entry._frame_stack,
|
||||||
|
'hide_runtime_frames',
|
||||||
|
partial(record, 'hide-frames'),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_entry.trio,
|
||||||
|
'run',
|
||||||
|
partial(record, 'trio-run'),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_entry,
|
||||||
|
'log',
|
||||||
|
SimpleNamespace(
|
||||||
|
info=partial(record, 'log'),
|
||||||
|
cancel=partial(record, 'log'),
|
||||||
|
error=partial(record, 'log'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_spawn,
|
||||||
|
'try_set_start_method',
|
||||||
|
fake_start_method,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
patches,
|
||||||
|
'apply_all',
|
||||||
|
partial(record, 'trio-patches'),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(_child, 'Actor', fake_actor)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_proctitle,
|
||||||
|
'set_actor_proctitle',
|
||||||
|
partial(record, 'proctitle'),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_child,
|
||||||
|
'_trio_main',
|
||||||
|
partial(record, 'trio-main'),
|
||||||
|
)
|
||||||
|
|
||||||
|
actor: Any = ActorSpy()
|
||||||
|
bootstrap: tuple[int, int] = (
|
||||||
|
namespace_fd,
|
||||||
|
expected_inode,
|
||||||
|
)
|
||||||
|
if backend == 'mp':
|
||||||
|
_entry._mp_main(
|
||||||
|
actor,
|
||||||
|
[],
|
||||||
|
(None, None, None, None, None),
|
||||||
|
'mp_spawn',
|
||||||
|
netns_bootstrap=bootstrap,
|
||||||
|
)
|
||||||
|
first_runtime_event: str = 'forkserver-info'
|
||||||
|
terminal_event: str = 'trio-run'
|
||||||
|
else:
|
||||||
|
_child._actor_child_main(
|
||||||
|
uid=actor.uid,
|
||||||
|
loglevel=actor.loglevel,
|
||||||
|
parent_addr=None,
|
||||||
|
infect_asyncio=False,
|
||||||
|
netns_bootstrap=bootstrap,
|
||||||
|
)
|
||||||
|
first_runtime_event = 'trio-patches'
|
||||||
|
terminal_event = 'trio-main'
|
||||||
|
|
||||||
|
assert events[:2] == [
|
||||||
|
'enter-netns',
|
||||||
|
first_runtime_event,
|
||||||
|
]
|
||||||
|
assert events.count(terminal_event) == 1
|
||||||
|
_assert_fd_closed(namespace_fd)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('backend', ('mp', 'trio'))
|
||||||
|
def test_child_entry_failure_closes_netns_fd_before_runtime(
|
||||||
|
backend: str,
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Failed namespace entry must close its FD and abort child startup.
|
||||||
|
|
||||||
|
Raise a unique error from the namespace boundary while a real
|
||||||
|
stand-in FD is open. Arm each entrypoint's first later operation as
|
||||||
|
a failure sentinel, then prove the original error escapes, the
|
||||||
|
descriptor is closed, and no actor, multiprocessing, frame, or
|
||||||
|
Trio runtime initialization begins.
|
||||||
|
|
||||||
|
'''
|
||||||
|
token_path: Path = tmp_path / f'{backend}-failed-netns'
|
||||||
|
token_path.touch()
|
||||||
|
namespace_fd: int = os.open(token_path, os.O_RDONLY)
|
||||||
|
expected_inode: int = os.fstat(namespace_fd).st_ino
|
||||||
|
entry_error = RuntimeError('netns entry failed')
|
||||||
|
events: list[str] = []
|
||||||
|
|
||||||
|
def fail_enter_netns(
|
||||||
|
inherited_fd: int,
|
||||||
|
inode: int,
|
||||||
|
) -> int:
|
||||||
|
'''
|
||||||
|
Fail entry while proving the child-owned FD is still open.
|
||||||
|
|
||||||
|
'''
|
||||||
|
assert os.fstat(inherited_fd).st_ino == expected_inode
|
||||||
|
assert inherited_fd == namespace_fd
|
||||||
|
assert inode == expected_inode
|
||||||
|
events.append('enter-netns')
|
||||||
|
raise entry_error
|
||||||
|
|
||||||
|
def fail_after_entry(*args: object, **kwargs: object) -> None:
|
||||||
|
'''
|
||||||
|
Reject any runtime operation after failed namespace entry.
|
||||||
|
|
||||||
|
'''
|
||||||
|
raise AssertionError('child runtime started after netns failure')
|
||||||
|
|
||||||
|
class ActorSpy:
|
||||||
|
'''
|
||||||
|
Reject multiprocessing actor-state initialization.
|
||||||
|
|
||||||
|
'''
|
||||||
|
loglevel = None
|
||||||
|
uid = ('failed-netns-child', 'test')
|
||||||
|
|
||||||
|
def __setattr__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
value: object,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Reject the first multiprocessing entrypoint mutation.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if name == '_forkserver_info':
|
||||||
|
fail_after_entry()
|
||||||
|
object.__setattr__(self, name, value)
|
||||||
|
|
||||||
|
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_entry._frame_stack,
|
||||||
|
'hide_runtime_frames',
|
||||||
|
fail_after_entry,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_spawn,
|
||||||
|
'try_set_start_method',
|
||||||
|
fail_after_entry,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
patches,
|
||||||
|
'apply_all',
|
||||||
|
fail_after_entry,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(_child, 'Actor', fail_after_entry)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_proctitle,
|
||||||
|
'set_actor_proctitle',
|
||||||
|
fail_after_entry,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_child,
|
||||||
|
'_trio_main',
|
||||||
|
fail_after_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
actor: Any = ActorSpy()
|
||||||
|
bootstrap: tuple[int, int] = (
|
||||||
|
namespace_fd,
|
||||||
|
expected_inode,
|
||||||
|
)
|
||||||
|
with pytest.raises(RuntimeError) as exc_info:
|
||||||
|
if backend == 'mp':
|
||||||
|
_entry._mp_main(
|
||||||
|
actor,
|
||||||
|
[],
|
||||||
|
(None, None, None, None, None),
|
||||||
|
'mp_spawn',
|
||||||
|
netns_bootstrap=bootstrap,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_child._actor_child_main(
|
||||||
|
uid=actor.uid,
|
||||||
|
loglevel=actor.loglevel,
|
||||||
|
parent_addr=None,
|
||||||
|
infect_asyncio=False,
|
||||||
|
netns_bootstrap=bootstrap,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value is entry_error
|
||||||
|
assert events == ['enter-netns']
|
||||||
|
_assert_fd_closed(namespace_fd)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,10 @@ from ast import literal_eval
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .runtime._runtime import Actor
|
from .runtime._runtime import Actor
|
||||||
from .spawn._entry import _trio_main
|
from .spawn._entry import (
|
||||||
|
_consume_netns_bootstrap,
|
||||||
|
_trio_main,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .discovery._addr import UnwrappedAddress
|
from .discovery._addr import UnwrappedAddress
|
||||||
|
|
@ -52,6 +55,7 @@ def _actor_child_main(
|
||||||
parent_addr: UnwrappedAddress | None,
|
parent_addr: UnwrappedAddress | None,
|
||||||
infect_asyncio: bool,
|
infect_asyncio: bool,
|
||||||
spawn_method: SpawnMethodKey = 'trio',
|
spawn_method: SpawnMethodKey = 'trio',
|
||||||
|
netns_bootstrap: tuple[int, int]|None = None,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
|
|
@ -62,7 +66,13 @@ def _actor_child_main(
|
||||||
invokes this from inside a fresh `concurrent.interpreters`
|
invokes this from inside a fresh `concurrent.interpreters`
|
||||||
sub-interpreter via `Interpreter.call()`.
|
sub-interpreter via `Interpreter.call()`.
|
||||||
|
|
||||||
|
Consume `netns_bootstrap` before Trio patching, actor construction,
|
||||||
|
process-title setup, or actor-runtime entry. The spawn backend must
|
||||||
|
supply an exclusively child-owned FD duplicate.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
_consume_netns_bootstrap(netns_bootstrap)
|
||||||
|
|
||||||
# Apply defensive monkey-patches for upstream `trio`
|
# Apply defensive monkey-patches for upstream `trio`
|
||||||
# bugs we've encountered while running tractor — see
|
# bugs we've encountered while running tractor — see
|
||||||
# `tractor.trionics.patches` for the catalog +
|
# `tractor.trionics.patches` for the catalog +
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ Sub-process entry points.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
|
import os
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
|
|
@ -48,6 +49,7 @@ from ..runtime._runtime import (
|
||||||
async_main,
|
async_main,
|
||||||
Actor,
|
Actor,
|
||||||
)
|
)
|
||||||
|
from ._netns import enter_netns
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ._spawn import SpawnMethodKey
|
from ._spawn import SpawnMethodKey
|
||||||
|
|
@ -56,6 +58,54 @@ if TYPE_CHECKING:
|
||||||
log = get_logger()
|
log = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def _consume_netns_bootstrap(
|
||||||
|
netns_bootstrap: tuple[int, int]|None,
|
||||||
|
) -> int|None:
|
||||||
|
'''
|
||||||
|
Enter and release one child-owned network namespace capability.
|
||||||
|
|
||||||
|
The FD must be an exclusively child-owned backend duplicate. It is
|
||||||
|
closed whether namespace entry succeeds or fails, before any actor
|
||||||
|
runtime setup can continue.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if netns_bootstrap is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
namespace_fd: int
|
||||||
|
expected_inode: int
|
||||||
|
namespace_fd, expected_inode = netns_bootstrap
|
||||||
|
if (
|
||||||
|
type(namespace_fd) is not int
|
||||||
|
or
|
||||||
|
namespace_fd < 0
|
||||||
|
):
|
||||||
|
# Let `enter_netns()` report its precise validation error, but
|
||||||
|
# never pass bool/non-int/negative values to `os.close()`.
|
||||||
|
return enter_netns(
|
||||||
|
namespace_fd,
|
||||||
|
expected_inode,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
entered_inode: int = enter_netns(
|
||||||
|
namespace_fd,
|
||||||
|
expected_inode,
|
||||||
|
)
|
||||||
|
except BaseException as entry_error:
|
||||||
|
try:
|
||||||
|
os.close(namespace_fd)
|
||||||
|
except Exception as close_error:
|
||||||
|
entry_error.add_note(
|
||||||
|
f'Also failed to close inherited namespace FD '
|
||||||
|
f'{namespace_fd}: {close_error!r}'
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
os.close(namespace_fd)
|
||||||
|
return entered_inode
|
||||||
|
|
||||||
|
|
||||||
def _mp_main(
|
def _mp_main(
|
||||||
|
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
|
|
@ -64,12 +114,18 @@ def _mp_main(
|
||||||
start_method: SpawnMethodKey,
|
start_method: SpawnMethodKey,
|
||||||
parent_addr: UnwrappedAddress | None = None,
|
parent_addr: UnwrappedAddress | None = None,
|
||||||
infect_asyncio: bool = False,
|
infect_asyncio: bool = False,
|
||||||
|
netns_bootstrap: tuple[int, int]|None = None,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
The routine called *after fork* which invokes a fresh `trio.run()`
|
The routine called *after fork* which invokes a fresh `trio.run()`
|
||||||
|
|
||||||
|
Consume `netns_bootstrap` before multiprocessing or actor-runtime
|
||||||
|
setup. The spawn backend must supply a child-owned FD duplicate.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
_consume_netns_bootstrap(netns_bootstrap)
|
||||||
|
|
||||||
actor._forkserver_info = forkserver_info
|
actor._forkserver_info = forkserver_info
|
||||||
from ._spawn import try_set_start_method
|
from ._spawn import try_set_start_method
|
||||||
spawn_ctx: mp.context.BaseContext = try_set_start_method(start_method)
|
spawn_ctx: mp.context.BaseContext = try_set_start_method(start_method)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue