mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-02 10:41:30 +08:00
feat(companion): Apply cwd and env in spawned companion child
Child runs _apply_environment before the target: os.chdir(cwd) then os.environ.update(env). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5639d467f3
commit
ea2748a209
@ -672,7 +672,7 @@ No per-companion logic in Arbiter.
|
|||||||
- [x] Add status description helpers.
|
- [x] Add status description helpers.
|
||||||
- [x] Add `CompanionManager` skeleton.
|
- [x] Add `CompanionManager` skeleton.
|
||||||
- [x] Spawn one companion process from the manager.
|
- [x] Spawn one companion process from the manager.
|
||||||
- [ ] Apply `cwd` and `env` before target.
|
- [x] Apply `cwd` and `env` before target.
|
||||||
- [ ] Redirect `stdout` and `stderr`.
|
- [ ] Redirect `stdout` and `stderr`.
|
||||||
- [ ] Reap exited companion processes.
|
- [ ] Reap exited companion processes.
|
||||||
- [ ] Implement `STARTING -> RUNNING` using `startsecs`.
|
- [ ] Implement `STARTING -> RUNNING` using `startsecs`.
|
||||||
|
|||||||
@ -3,12 +3,18 @@
|
|||||||
# See the NOTICE for more information.
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Callable, Iterable, Union
|
||||||
|
|
||||||
from gunicorn.companion.process import CompanionProcess, State
|
from gunicorn.companion.process import CompanionProcess, State
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from gunicorn.companion.process import CompanionConfig
|
||||||
|
|
||||||
|
|
||||||
class CompanionManager:
|
class CompanionManager:
|
||||||
"""Forks and supervises companion processes.
|
"""Forks and supervises companion processes.
|
||||||
@ -19,12 +25,12 @@ class CompanionManager:
|
|||||||
socket, and the run loop arrive in later tasks.
|
socket, and the run loop arrive in later tasks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, configs, log):
|
def __init__(self, configs: Iterable[CompanionConfig], log):
|
||||||
self.log = log
|
self.log = log
|
||||||
self.pid = os.getpid()
|
self.pid = os.getpid()
|
||||||
self.processes = {c.name: CompanionProcess(c) for c in configs}
|
self.processes = {c.name: CompanionProcess(c) for c in configs}
|
||||||
|
|
||||||
def spawn_process(self, proc):
|
def spawn_process(self, proc: CompanionProcess) -> int:
|
||||||
"""Fork one companion.
|
"""Fork one companion.
|
||||||
|
|
||||||
Parent records the pid and moves the companion to STARTING. Child
|
Parent records the pid and moves the companion to STARTING. Child
|
||||||
@ -41,6 +47,7 @@ class CompanionManager:
|
|||||||
return pid
|
return pid
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
self._apply_environment(proc.config)
|
||||||
target = self._resolve_target(proc.config.target)
|
target = self._resolve_target(proc.config.target)
|
||||||
target()
|
target()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
@ -51,7 +58,20 @@ class CompanionManager:
|
|||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resolve_target(target):
|
def _apply_environment(config: CompanionConfig) -> None:
|
||||||
|
"""Apply ``cwd`` and ``env`` in the child before running the target.
|
||||||
|
|
||||||
|
cwd is changed first so a relative path in env (or the target itself)
|
||||||
|
resolves against it. env is merged onto the inherited environment, not
|
||||||
|
replaced, so the companion keeps the manager's variables.
|
||||||
|
"""
|
||||||
|
if config.cwd:
|
||||||
|
os.chdir(config.cwd)
|
||||||
|
if config.env:
|
||||||
|
os.environ.update(config.env)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_target(target: Union[Callable, str]) -> Callable:
|
||||||
"""Return the zero-arg callable for a companion target.
|
"""Return the zero-arg callable for a companion target.
|
||||||
|
|
||||||
Accepts an already-callable target or a ``"module:attr"`` import
|
Accepts an already-callable target or a ``"module:attr"`` import
|
||||||
|
|||||||
@ -36,6 +36,24 @@ def test_resolve_target_rejects_bad_string():
|
|||||||
CompanionManager._resolve_target("no_colon")
|
CompanionManager._resolve_target("no_colon")
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_environment_sets_cwd_and_env():
|
||||||
|
config = CompanionConfig(name="rq", target=lambda: None,
|
||||||
|
cwd="/tmp", env={"COMPANION_X": "1"})
|
||||||
|
with mock.patch("os.chdir") as chdir, \
|
||||||
|
mock.patch.dict("os.environ", {}, clear=False):
|
||||||
|
CompanionManager._apply_environment(config)
|
||||||
|
chdir.assert_called_once_with("/tmp")
|
||||||
|
import os
|
||||||
|
assert os.environ["COMPANION_X"] == "1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_environment_noop_without_cwd_env():
|
||||||
|
config = CompanionConfig(name="rq", target=lambda: None)
|
||||||
|
with mock.patch("os.chdir") as chdir:
|
||||||
|
CompanionManager._apply_environment(config)
|
||||||
|
chdir.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_spawn_parent_records_pid_and_starting():
|
def test_spawn_parent_records_pid_and_starting():
|
||||||
mgr = make_manager("rq")
|
mgr = make_manager("rq")
|
||||||
proc = mgr.processes["rq"]
|
proc = mgr.processes["rq"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user