run_process ignores thread context and spawns from threadpool
Hi,
run_process uses trio.to_thread.run_sync to spawn the subprocess; this means that the spawned process will inherit various context from a different thread. In my case this means it spawns in the wrong (network) namespace (see setns(2) - "reassociate thread with a namespace").
This might affect other things as well (pthreads(7) lists (among other things) capabilities and CPU affinity).
- the docs should mention this behavior
- it would be nice to have a flag to "spawn from current thread"
I'm also open to good suggestions how to work around this problem :)
(I might try prefixing the target command with ["/usr/bin/nsenter", "--target", threading.get_native_id(), "-n", "--"], or copy the trio run_process/open_process functions).
cheers, Stefan
Looking at the docs, it sounds like we would want to use clone(2) to get a thread for sync stuff?
Not all of the attributes that can be shared when a new thread is created using
clone(2)can be changed usingsetns().
Probably also the thread pool has some capacity limiter (I forgot!) so any change would have to beware... but maybe we can just call clone(2) if it's available to make a thread? I haven't looked at its manpage.
(Disclaimer: I don't know my way around the syscalls available)
Looking at the docs, it sounds like we would want to use
clone(2)to get a thread for sync stuff?
I strongly recommend against (even temporarily) cloning a new thread for every subprocess, and that would be the only way to make sure the subprocess inherits the current context.
Actually, in my case a worker thread(pool) per "normal thread"/"trio loop" would do, because changing the namespace is the first thing I do, i.e. before calling trio.run; but I have multiple threads doing so with different namespaces. See code example at the bottom.
Not all of the attributes that can be shared when a new thread is created using
clone(2)can be changed usingsetns().
No; I think this just means "not every CLONE_* flag is a namespace flag" - i.e. not every CLONE_* flag can be used with setns() (but I think all CLONE_NEW* flags actually can be used).
Probably also the thread pool has some capacity limiter (I forgot!) so any change would have to beware... but maybe we can just call
clone(2)if it's available to make a thread? I haven't looked at its manpage.
Any high-level capacity limiter of a thread pool is unrelated to clone(2) and potential OS resource limits. But if you hit such an OS limit creating a new process is very likely to fail too.
Attaching possible workaround for using a (thread-)local threadpool where needed (call use_local_thread_pool at least once in such threads):
from __future__ import annotations
from trio._core._thread_cache import ThreadCache, THREAD_CACHE as ORIG_CACHE
import trio._core._thread_cache
import threading
import typing
if typing.TYPE_CHECKING:
import outcome
_THREAD_LOCALS = threading.local()
def use_local_thread_pool() -> None:
# activate workaround for https://github.com/python-trio/trio/issues/3360
if not hasattr(_THREAD_LOCALS, "pool"):
_THREAD_LOCALS.pool = ThreadCache()
class _StubCache:
_idle_workers = ORIG_CACHE._idle_workers
def start_thread_soon(
self,
fn: typing.Callable[[], trio._core._thread_cache.RetT],
deliver: typing.Callable[[outcome.Outcome[trio._core._thread_cache.RetT]], object],
name: str | None = None,
) -> None:
if hasattr(_THREAD_LOCALS, "pool"):
_THREAD_LOCALS.pool.start_thread_soon(fn, deliver, name)
else:
ORIG_CACHE.start_thread_soon(fn, deliver, name)
trio._core._thread_cache.THREAD_CACHE = _StubCache() # type: ignore