commit:     3f32f9683abed9b95febdf37d7c891a624b75e1a
Author:     Michał Górny <mgorny <AT> gentoo <DOT> org>
AuthorDate: Sun Nov  9 18:40:54 2025 +0000
Commit:     Michał Górny <mgorny <AT> gentoo <DOT> org>
CommitDate: Sun Nov  9 18:40:54 2025 +0000
URL:        https://gitweb.gentoo.org/proj/steve.git/commit/?id=3f32f968

Add `--add-jobs` and `--delete-jobs` options

Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>

 README.rst     | 16 +++++++++-------
 pyproject.toml |  1 -
 steve.py       | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 64 insertions(+), 11 deletions(-)

diff --git a/README.rst b/README.rst
index 54f7cce..205fd5f 100644
--- a/README.rst
+++ b/README.rst
@@ -86,13 +86,15 @@ Adjusting the job count at runtime
 Due to the simplicity of the jobserver protocol, it is trivially
 possible to adjust the job count at runtime.
 
-To reduce the number of available jobs, read an appropriate number
-of characters from the pipe.  For example, to lower the job count
-by 3 jobs, call::
+To add more jobs to the jobserver, use ``--add-jobs`` with the same
+jobserver FIFO path::
 
-    read -L 3 < ${path_to_fifo}
+    steve -a ${jobs} ${path_to_fifo}
 
-Similarly, to increase the number jobs, write an appropriate number
-of (any) characters to the pipe::
+To remove them, use ``--remove-jobs``.  This will block until
+all the requested jobs are removed.  If the number is larger than total
+number of jobs, it will block forever.
 
-    printf ... > ${path_to_fifo}
+::
+
+    steve -d ${jobs} ${path_to_fifo}

diff --git a/pyproject.toml b/pyproject.toml
index b272a92..f5efec8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -58,7 +58,6 @@ extend-select = [
     "ARG",
     "ERA",
     "PGH",
-    "PL",
     "PERF",
     "FURB",
     "RUF",

diff --git a/steve.py b/steve.py
index 12466cc..f15dcc4 100755
--- a/steve.py
+++ b/steve.py
@@ -27,6 +27,13 @@ if TYPE_CHECKING:
 __version__ = "0.0.1"
 
 
+def positive_int(value: str) -> int:
+    ret = int(value)
+    if ret <= 0:
+        raise argparse.ArgumentTypeError("must be positive")
+    return ret
+
+
 def exit_sig_handler(signum: int, _: FrameType | None) -> None:
     print(f"Exiting on {signal.Signals(signum).name}")
     raise SystemExit(0)
@@ -53,6 +60,22 @@ def main() -> None:
     signal.signal(signal.SIGUSR1, signal.SIG_IGN)
 
     argp = argparse.ArgumentParser()
+
+    commands = argp.add_mutually_exclusive_group()
+    commands.add_argument(
+        "-a",
+        "--add-jobs",
+        type=positive_int,
+        help="Add the specified number of jobs to the running jobserver",
+    )
+    commands.add_argument(
+        "-d",
+        "--delete-jobs",
+        "--remove-jobs",
+        type=positive_int,
+        help="Remove the specified number of jobs to the running jobserver",
+    )
+
     argp.add_argument(
         "-c",
         "--control-fifo",
@@ -62,7 +85,7 @@ def main() -> None:
     argp.add_argument(
         "-j",
         "--jobs",
-        type=int,
+        type=positive_int,
         help="Number of jobs to allow (default: nproc)",
     )
     argp.add_argument(
@@ -72,13 +95,42 @@ def main() -> None:
     )
     args = argp.parse_args()
 
+    if args.add_jobs is not None:
+        if args.add_jobs <= 0:
+            argp.error("--add-jobs must be larger than 0")
+        with args.path.open("wb") as job_file:
+            job_file.write(b"." * args.add_jobs)
+        print(f"{args.add_jobs} job tokens added to {args.path}")
+        return
+
+    if args.delete_jobs:
+        # first, remove as many jobs as we can without blocking
+        job_fd = os.open(args.path, os.O_RDONLY | os.O_NONBLOCK)
+        try:
+            removed = len(os.read(job_fd, args.delete_jobs))
+        except BlockingIOError:
+            pass
+        else:
+            print(f"{removed} jobs removed from {args.path}")
+            args.delete_jobs -= removed
+        os.close(job_fd)
+
+        if args.delete_jobs == 0:
+            return
+
+        print("Waiting for more jobs (may block forever)")
+        with open(args.path, "rb") as job_file:
+            while args.delete_jobs > 0:
+                job_file.read(1)
+                args.delete_jobs -= 1
+                print(f"Job removed, {args.delete_jobs} left to remove")
+        return
+
     if args.jobs is None:
         try:
             args.jobs = multiprocessing.cpu_count()
         except NotImplementedError:
             argp.error("Cannot determine CPU count, please specify --jobs")
-    if args.jobs <= 0:
-        argp.error("--jobs must be larger than 0")
 
     with contextlib.ExitStack() as context_managers:
         context_managers.enter_context(hold_fifo(args.path))

Reply via email to