From 70d7049aa3e7281886aae7cec85e583603b98836 Mon Sep 17 00:00:00 2001 From: Alvaro Leiva Date: Mon, 5 Jan 2026 14:10:36 -0800 Subject: [PATCH] Document cwd and wait_for_activation parameters in pystemd.run Added missing documentation for the cwd and wait_for_activation parameters. The wait_for_activation parameter addresses a race condition between pystemd.run returning the unit and the unit actually being activated. While most of the time waiting for full completion is fine, sometimes we only need to wait for activation to avoid race conditions without blocking until the service completes. --- _docs/pystemd.run.md | 7 ++++++- pyproject.toml | 2 +- pystemd/__version__.py | 2 +- pystemd/futures.py | 1 + pystemd/run.py | 29 +++++++++++++++++++++-------- setup.py | 2 +- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/_docs/pystemd.run.md b/_docs/pystemd.run.md index b97ee35..bba720b 100644 --- a/_docs/pystemd.run.md +++ b/_docs/pystemd.run.md @@ -33,10 +33,15 @@ but we will assume that this just another building block for your program. * env: A dict with environment variables. * extra: If you know what you are doing, you can pass extra configuration settings to the start_transient_unit method. -machine: Machine name to execute the command, by default we connect to +* cwd: Working directory for the command. If not specified, systemd's + default working directory will be used. +* machine: Machine name to execute the command, by default we connect to the host's dbus. * wait: Wait for command completion before returning control, defaults to False. +* wait_for_activation: If True, wait only for the service to reach the + 'running' state, then return immediately without waiting for completion. + Defaults to False. * remain_after_exit: If True, the transient unit will remain after cmd has finished, also if true, this methods will return pystemd.systemd1.Unit object. defaults to False and this method diff --git a/pyproject.toml b/pyproject.toml index 58a1ffb..a45ae76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pystemd" -version = "0.15.0" +version = "0.15.1" readme = "README.md" description="A systemd binding for python" requires-python=">=3.11" diff --git a/pystemd/__version__.py b/pystemd/__version__.py index e777d09..b8f628d 100644 --- a/pystemd/__version__.py +++ b/pystemd/__version__.py @@ -10,6 +10,6 @@ # during development this version is always at least "one up" the # latest release. -__version__ = "0.15.0" +__version__ = "0.15.1" sys.modules[__name__] = __version__ # type: ignore diff --git a/pystemd/futures.py b/pystemd/futures.py index 7278396..f5f0488 100644 --- a/pystemd/futures.py +++ b/pystemd/futures.py @@ -46,6 +46,7 @@ def start_unit(self) -> pystemd.systemd1.Unit: self.main_process_cmd, name=self.unit_name, user_mode=self.user_mode, + wait_for_activation=True, extra={ **self.properties, "Delegate": True, diff --git a/pystemd/run.py b/pystemd/run.py index 0393016..28bf50d 100644 --- a/pystemd/run.py +++ b/pystemd/run.py @@ -57,6 +57,7 @@ def run( cwd=None, machine=None, wait=False, + wait_for_activation=False, remain_after_exit=False, collect=False, raise_on_fail=False, @@ -96,10 +97,15 @@ def run( env: A dict with environment variables. extra: If you know what you are doing, you can pass extra configuration settings to the start_transient_unit method. + cwd: Working directory for the command. If not specified, systemd's + default working directory will be used. machine: Machine name to execute the command, by default we connect to the host's dbus. wait: Wait for command completion before returning control, defaults to False. + wait_for_activation: If True, wait only for the service to reach the + 'running' state, then return immediately without waiting for completion. + Defaults to False. remain_after_exit: If True, the transient unit will remain after cmd has finished, also if true, this methods will return pystemd.systemd1.Unit object. defaults to False and this method @@ -259,7 +265,7 @@ def bus_factory(): unit_properties = {k: v for k, v in unit_properties.items() if v is not None} unit = Unit(name, bus=bus, _autoload=True) - if wait: + if wait or wait_for_activation: mstr = ( ( "type='signal'," @@ -289,7 +295,7 @@ def bus_factory(): name, b"fail", unit_properties ) - while wait: + while wait or wait_for_activation: events = sel.select(timeout=_wait_polling) _in = [key.fileobj for key, _ in events] @@ -323,13 +329,20 @@ def bus_factory(): m.get_path() == unit.path and m.body[0] == b"org.freedesktop.systemd1.Unit" ): - _, message_job_path = m.body[1].get(b"Job", (0, b"/")) - if ( - message_job_path != unit_start_job - and m.body[1].get(b"SubState") in EXIT_SUBSTATES - ): - break + _, message_job_path = m.body[1].get(b"Job", (0, b"/")) + if wait: + if ( + message_job_path != unit_start_job + and m.body[1].get(b"SubState") in EXIT_SUBSTATES + ): + break + elif wait_for_activation: + if ( + message_job_path == unit_start_job + and m.body[1].get(b"SubState") in (b"running",) + ): + break if _wait_polling and not _in and unit.Service.MainPID == 0: # on usermode the subscribe to events does not work that well diff --git a/setup.py b/setup.py index 355ece0..904f83a 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ setup( name="pystemd", - version="0.15.0", + version="0.15.1", author="Alvaro Leiva Geisse", author_email="aleivag@gmail.com", packages=["pystemd", "pystemd.systemd1", "pystemd.machine1", "pystemd.DBus"],