multiCMD 1.55__tar.gz → 1.56__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {multicmd-1.55 → multicmd-1.56}/PKG-INFO +50 -73
  2. {multicmd-1.55 → multicmd-1.56}/README.md +49 -72
  3. {multicmd-1.55 → multicmd-1.56}/multiCMD.egg-info/PKG-INFO +50 -73
  4. {multicmd-1.55 → multicmd-1.56}/multiCMD.egg-info/SOURCES.txt +1 -1
  5. {multicmd-1.55 → multicmd-1.56}/multiCMD.py +273 -910
  6. {multicmd-1.55 → multicmd-1.56}/tests/test_async_executor.py +15 -267
  7. multicmd-1.56/tests/test_interrupts.py +623 -0
  8. {multicmd-1.55 → multicmd-1.56}/tests/test_pool.py +0 -24
  9. {multicmd-1.55 → multicmd-1.56}/tests/test_process_groups.py +35 -148
  10. {multicmd-1.55 → multicmd-1.56}/tests/test_pty.py +0 -27
  11. {multicmd-1.55 → multicmd-1.56}/tests/test_run_command.py +45 -0
  12. multicmd-1.55/tests/test_settle.py +0 -2412
  13. {multicmd-1.55 → multicmd-1.56}/LICENSE +0 -0
  14. {multicmd-1.55 → multicmd-1.56}/multiCMD.egg-info/dependency_links.txt +0 -0
  15. {multicmd-1.55 → multicmd-1.56}/multiCMD.egg-info/entry_points.txt +0 -0
  16. {multicmd-1.55 → multicmd-1.56}/multiCMD.egg-info/top_level.txt +0 -0
  17. {multicmd-1.55 → multicmd-1.56}/setup.cfg +0 -0
  18. {multicmd-1.55 → multicmd-1.56}/setup.py +0 -0
  19. {multicmd-1.55 → multicmd-1.56}/tests/test_api.py +0 -0
  20. {multicmd-1.55 → multicmd-1.56}/tests/test_cli.py +0 -0
  21. {multicmd-1.55 → multicmd-1.56}/tests/test_cli_input.py +0 -0
  22. {multicmd-1.55 → multicmd-1.56}/tests/test_cli_summary.py +0 -0
  23. {multicmd-1.55 → multicmd-1.56}/tests/test_display.py +0 -0
  24. {multicmd-1.55 → multicmd-1.56}/tests/test_env.py +0 -0
  25. {multicmd-1.55 → multicmd-1.56}/tests/test_expand_ranges.py +0 -0
  26. {multicmd-1.55 → multicmd-1.56}/tests/test_helpers.py +0 -0
  27. {multicmd-1.55 → multicmd-1.56}/tests/test_parsing.py +0 -0
  28. {multicmd-1.55 → multicmd-1.56}/tests/test_ping.py +0 -0
  29. {multicmd-1.55 → multicmd-1.56}/tests/test_reader.py +0 -0
  30. {multicmd-1.55 → multicmd-1.56}/tests/test_regressions.py +0 -0
  31. {multicmd-1.55 → multicmd-1.56}/tests/test_run_commands.py +0 -0
  32. {multicmd-1.55 → multicmd-1.56}/tests/test_sudo.py +0 -0
  33. {multicmd-1.55 → multicmd-1.56}/tests/test_supervision.py +0 -0
  34. {multicmd-1.55 → multicmd-1.56}/tests/test_task.py +0 -0
  35. {multicmd-1.55 → multicmd-1.56}/tests/test_windows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiCMD
3
- Version: 1.55
3
+ Version: 1.56
4
4
  Summary: Run commands simultaneously
5
5
  Home-page: https://github.com/yufei-pan/multiCMD
6
6
  Author: Yufei Pan
@@ -411,67 +411,37 @@ default, in the API and on the command line) disables it.
411
411
 
412
412
  ## Process groups and signals
413
413
 
414
- Every command runs in a process group of its own, so a timeout, `stop()` or Ctrl-C
415
- reaches the whole process tree, not just the first process. On Python 3.11+ this uses
416
- `Popen(process_group=0)`; older Pythons start the command through a tiny exec wrapper.
417
- Commands that try to read the terminal (a password prompt, for example) fail at once
418
- instead of hanging. The library does this by blocking SIGTTIN/SIGTTOU for the command,
419
- which covers commands started directly or through bash; `mcmd` also ignores them, which
420
- every command inherits through any shell.
421
-
422
- Because the commands are not in your program's process group, signals sent to that
423
- group do not reach them. multiCMD handles this for you: a call waiting on the main
424
- thread that gets Ctrl-C, or the SystemExit of a SIGTERM handler's `sys.exit()`
425
- (`join_threads()` and `AsyncExecutor.wait()` included), stops its commands, the queued ones
426
- skipped, before re-raising, giving them up to 5 s to exit (a second Ctrl-C stops waiting
427
- for them at once, a serial run's command still sent SIGTERM; one that lands while multiCMD stops them or forgets the threads that never ran ends
428
- it too), `mcmd` stops them on
429
- SIGTERM / SIGHUP (unless it was started with them ignored, as under `nohup`), and any
430
- commands still running when the interpreter exits are killed. From then on a command queued
431
- before the exit on one of multiCMD's threads does not start: it fails (return code -1, "not started:
432
- the interpreter is exiting"), as the thread would be frozen with the interpreter and the command
433
- outlive it; a command run from an `atexit` handler, a `weakref.finalize` callback or a `__del__`
434
- after that (on the main thread) runs as usual. A program that handles
435
- SIGTERM itself, or that runs multiCMD from its own threads (where Ctrl-C never
436
- arrives), can call `multiCMD.kill_all()` from its handler. A command still running a second
437
- after SIGKILL (in uninterruptible sleep, or not yours to signal) is no longer tracked, so later
438
- calls and the exit do not wait for it again; multiCMD names its pid on stderr, unless quiet.
439
-
440
- A Ctrl-C (or a signal handler's exception, such as a SIGTERM handler's `sys.exit()`) that lands
441
- while a command starts stops it like any other, from the moment `Popen` knows the child's pid.
442
- One that lands while the command is being forked can leave it running unregistered, as in 1.54:
443
- if Python raises it as the fork returns, before `Popen` has stored the pid, neither the call nor
444
- `kill_all()` nor the exit hook knows the command, which runs on in its own process group. Python
445
- raises those on the main thread only, so this concerns serial runs (`max_threads=1`); and the window
446
- is the fork's own length, some 60-150 µs per command however long it then runs. That is the only place
447
- one Ctrl-C can lose a command. Before Python 3.11 a second one can too: on Python 3.6-3.10 a second
448
- Ctrl-C that lands while the first is still on its way out of the command's start, before multiCMD has
449
- begun to deal with it, is raised before multiCMD has registered the command (one that lands so while a
450
- call waits for a running command leaves it registered but not signalled: `kill_all()` and the exit hook
451
- still reach it), and on 3.6-3.9, where Python raises a Ctrl-C at nearly any instruction, a second one at a
452
- few other moments of that handling can do the same, and even a first one, at a few instructions between the
453
- steps of a run, can leave a command registered but not signalled. From Python 3.11 no other window is known where
454
- a Ctrl-C loses a command. On every Python, two Ctrl-Cs at nearly the same moment, the first cutting short the stop
455
- of a command that timed out or of what a finished command left running with its output open, can leave that
456
- not signalled: `kill_all()` still reaches it, and so does the exit hook, except on Python 3.6-3.10 for what a
457
- finished command left running, which can then outlive the program.
458
- On Python 3.6-3.9 and 3.14 a Ctrl-C that lands just as Python's `threading` takes one of its own
459
- locks with `with` (as `Thread.start()` does, for a serial run's output readers too) can leave that
460
- lock held, and the program then hangs as its threads end. That is CPython's own, and any threaded
461
- program can meet it; Python 3.10-3.13 do not leave the lock held.
414
+ Every command runs in a process group of its own, so a timeout, `stop()` or Ctrl-C reaches the
415
+ whole process tree, not just the first process. On Python 3.11+ this uses `Popen(process_group=0)`;
416
+ older Pythons start the command through a tiny exec wrapper. A command that reads the terminal (a
417
+ password prompt, for example) fails at once instead of hanging: multiCMD blocks SIGTTIN/SIGTTOU for
418
+ it, and `mcmd` also ignores them, which every command inherits through any shell.
419
+
420
+ Signals sent to your program's group do not reach the commands, so multiCMD stops them itself. One
421
+ Ctrl-C (or a SIGTERM handler's `sys.exit()`) in a call waiting on the main thread, `join_threads()`
422
+ and `AsyncExecutor.wait()` included, stops every command, skips the queued ones and
423
+ re-raises, giving them up to 5 s to exit; a second Ctrl-C ends that wait at once. `mcmd` stops its
424
+ commands on SIGTERM / SIGHUP (unless started with them ignored, as under `nohup`). At interpreter exit
425
+ the commands still running are killed, and a command queued on one of multiCMD's threads fails (-1,
426
+ "not started: the interpreter is exiting") instead of outliving the program; one run from an `atexit`
427
+ handler after that, on the main thread, runs as usual. A program that handles SIGTERM itself, or runs
428
+ multiCMD from its own threads (where Ctrl-C never arrives), can call `multiCMD.kill_all()` from its
429
+ handler. A command not seen to exit a second after SIGKILL is no longer tracked; its pid is named on
430
+ stderr, unless quiet.
462
431
 
463
432
  multiCMD never touches SIGINT's handler (`mcmd` sets its own SIGTERM / SIGHUP handlers, nothing
464
- else). A Ctrl-C that lands while multiCMD does its bookkeeping on the rare paths (a thread that
465
- cannot start, stopping commands after a Ctrl-C or a failure, cancelling a batch, recording a command
466
- that cannot be run) makes that bookkeeping run again from the start, and the KeyboardInterrupt (or
467
- whatever your own handler raised) is raised once it is complete, so it neither cuts that short nor gets
468
- lost. Your handler, if you installed one, is called with every Ctrl-C as it lands, and C-level hooks on
469
- SIGINT (`faulthandler.register`, `signal.siginterrupt`) stay as they are. Whatever your handler raises
470
- while a call waits for its commands is handled as a Ctrl-C: the commands are stopped (a serial run's
471
- current one recorded as stopped, the ones after it skipped) and the exception is raised on. multiCMD
472
- tells it from a failure of its own by your handler's frame in its traceback, and takes anything raised
473
- while it only waits for a running command as yours; a handler that uninstalls itself before it
474
- raises is not seen elsewhere, and what it raises then while a command starts counts as a failure.
433
+ else). What your handler raises while a call waits for its commands is handled as a Ctrl-C.
434
+
435
+ A command is registered from the moment `Popen` returns it: a Ctrl-C that Python raises inside
436
+ `Popen` as a serial run starts a command (some 50-70 µs each) can leave it running, unknown to
437
+ `kill_all()`. Two Ctrl-Cs microseconds apart can leave a command running, still registered, until
438
+ `kill_all()` or the exit stops it. On Python 3.6-3.9, which raise a Ctrl-C at nearly any instruction,
439
+ even one at a few points between the steps of a run can leave its command registered but not signalled.
440
+ On Python 3.6-3.9 and 3.14 a Ctrl-C at the wrong instruction of a `with` on a lock can leave it held: one of
441
+ `threading`'s can hang the program as its threads end (CPython's own), and one of multiCMD's (an
442
+ `AsyncExecutor`'s queue, the live output) hangs that executor, or the next call that is not quiet, if the
443
+ program keeps going.
444
+ These and the other known limits are in [docs/limitations.md](docs/limitations.md).
475
445
 
476
446
  ## Key functions and objects
477
447
 
@@ -521,6 +491,23 @@ pytest -m "not slow" # skip intentional sleep-heavy cases
521
491
 
522
492
  `test_expand_ranges_perf.py` is a manual performance microbenchmark and is not collected by pytest.
523
493
 
494
+ ## Upgrading to 1.56 from 1.55
495
+
496
+ - Fix: a command that times out while it redraws a `\r` progress line on stderr keeps its `Timeout!`
497
+ line; 1.55 could replace it with the progress line, leaving return code 124 without the marker.
498
+ - Simpler interrupt handling, some 700 lines lighter. One Ctrl-C still stops every command.
499
+ What changes is a few rare corners, each in [docs/limitations.md](docs/limitations.md):
500
+ - two Ctrl-Cs microseconds apart can leave a command running, still registered, until `kill_all()`
501
+ or the exit stops it; 1.55 stopped it at once;
502
+ - a Ctrl-C can lose a serial run's starting command anywhere inside `Popen` (about 51 µs per start on
503
+ Python 3.14), not only inside the fork (about 41 µs);
504
+ - a Ctrl-C that leaves `Popen.poll()`'s lock held makes that command's stop about 2 s slower;
505
+ - an ordinary exception your own signal handler raises as a serial run starts a command is recorded as
506
+ a start failure (-1) and not raised, as in 1.54;
507
+ - `join_threads()` on another thread can return before a batch that is still starting.
508
+ - `__running_threads_lock` and `_live_procs_lock`, unused since 1.55 and kept only for code that imported
509
+ them, are gone.
510
+
524
511
  ## Upgrading to 1.55 from 1.54
525
512
 
526
513
  - New: a per-call environment. `env=` (the whole environment, as `subprocess`'s) and `extra_env=`
@@ -544,20 +531,10 @@ pytest -m "not slow" # skip intentional sleep-heavy cases
544
531
  and the exit hook finish the kill. The limits below are the exceptions.
545
532
  - Once the interpreter exits, a command queued on one of multiCMD's threads that has not started fails
546
533
  with -1 ("not started: the interpreter is exiting") instead of starting and outliving the program.
547
- - A command still running a second after SIGKILL is no longer tracked, so later `kill_all()` calls and the
534
+ - A command not seen to exit a second after SIGKILL is no longer tracked, so later `kill_all()` calls and the
548
535
  exit do not wait for it again; its pid is named on stderr, unless quiet.
549
- - Known limits (see [Process groups and signals](#process-groups-and-signals)): a Ctrl-C raised just as
550
- a command's fork returns, before `Popen` has stored the pid, still loses the command, as in 1.54
551
- (serial runs only, some 60-150 µs per command). On Python 3.6-3.10 a second Ctrl-C right after a
552
- first one can lose a command that is starting, or leave one that a call waits for registered but not
553
- signalled; on 3.6-3.9 a second one at a few other moments can lose it too, and a first one at a few
554
- instructions can leave it registered but not signalled. On every Python, two Ctrl-Cs at nearly the same
555
- moment, the first cutting short the stop of a timed-out command or of a finished command's background
556
- processes, can leave them not signalled: `kill_all()` still reaches them, and so does the exit hook, except
557
- on Python 3.6-3.10 for a finished command's background processes, which can then outlive the program. On
558
- Python 3.6-3.9 and 3.14 a Ctrl-C just as
559
- `threading` takes one of its own locks can leave that lock held and hang the program as its threads end
560
- (CPython's own).
536
+ - Known limits: see [Process groups and signals](#process-groups-and-signals) and
537
+ [docs/limitations.md](docs/limitations.md).
561
538
 
562
539
  ## License
563
540
 
@@ -385,67 +385,37 @@ default, in the API and on the command line) disables it.
385
385
 
386
386
  ## Process groups and signals
387
387
 
388
- Every command runs in a process group of its own, so a timeout, `stop()` or Ctrl-C
389
- reaches the whole process tree, not just the first process. On Python 3.11+ this uses
390
- `Popen(process_group=0)`; older Pythons start the command through a tiny exec wrapper.
391
- Commands that try to read the terminal (a password prompt, for example) fail at once
392
- instead of hanging. The library does this by blocking SIGTTIN/SIGTTOU for the command,
393
- which covers commands started directly or through bash; `mcmd` also ignores them, which
394
- every command inherits through any shell.
395
-
396
- Because the commands are not in your program's process group, signals sent to that
397
- group do not reach them. multiCMD handles this for you: a call waiting on the main
398
- thread that gets Ctrl-C, or the SystemExit of a SIGTERM handler's `sys.exit()`
399
- (`join_threads()` and `AsyncExecutor.wait()` included), stops its commands, the queued ones
400
- skipped, before re-raising, giving them up to 5 s to exit (a second Ctrl-C stops waiting
401
- for them at once, a serial run's command still sent SIGTERM; one that lands while multiCMD stops them or forgets the threads that never ran ends
402
- it too), `mcmd` stops them on
403
- SIGTERM / SIGHUP (unless it was started with them ignored, as under `nohup`), and any
404
- commands still running when the interpreter exits are killed. From then on a command queued
405
- before the exit on one of multiCMD's threads does not start: it fails (return code -1, "not started:
406
- the interpreter is exiting"), as the thread would be frozen with the interpreter and the command
407
- outlive it; a command run from an `atexit` handler, a `weakref.finalize` callback or a `__del__`
408
- after that (on the main thread) runs as usual. A program that handles
409
- SIGTERM itself, or that runs multiCMD from its own threads (where Ctrl-C never
410
- arrives), can call `multiCMD.kill_all()` from its handler. A command still running a second
411
- after SIGKILL (in uninterruptible sleep, or not yours to signal) is no longer tracked, so later
412
- calls and the exit do not wait for it again; multiCMD names its pid on stderr, unless quiet.
413
-
414
- A Ctrl-C (or a signal handler's exception, such as a SIGTERM handler's `sys.exit()`) that lands
415
- while a command starts stops it like any other, from the moment `Popen` knows the child's pid.
416
- One that lands while the command is being forked can leave it running unregistered, as in 1.54:
417
- if Python raises it as the fork returns, before `Popen` has stored the pid, neither the call nor
418
- `kill_all()` nor the exit hook knows the command, which runs on in its own process group. Python
419
- raises those on the main thread only, so this concerns serial runs (`max_threads=1`); and the window
420
- is the fork's own length, some 60-150 µs per command however long it then runs. That is the only place
421
- one Ctrl-C can lose a command. Before Python 3.11 a second one can too: on Python 3.6-3.10 a second
422
- Ctrl-C that lands while the first is still on its way out of the command's start, before multiCMD has
423
- begun to deal with it, is raised before multiCMD has registered the command (one that lands so while a
424
- call waits for a running command leaves it registered but not signalled: `kill_all()` and the exit hook
425
- still reach it), and on 3.6-3.9, where Python raises a Ctrl-C at nearly any instruction, a second one at a
426
- few other moments of that handling can do the same, and even a first one, at a few instructions between the
427
- steps of a run, can leave a command registered but not signalled. From Python 3.11 no other window is known where
428
- a Ctrl-C loses a command. On every Python, two Ctrl-Cs at nearly the same moment, the first cutting short the stop
429
- of a command that timed out or of what a finished command left running with its output open, can leave that
430
- not signalled: `kill_all()` still reaches it, and so does the exit hook, except on Python 3.6-3.10 for what a
431
- finished command left running, which can then outlive the program.
432
- On Python 3.6-3.9 and 3.14 a Ctrl-C that lands just as Python's `threading` takes one of its own
433
- locks with `with` (as `Thread.start()` does, for a serial run's output readers too) can leave that
434
- lock held, and the program then hangs as its threads end. That is CPython's own, and any threaded
435
- program can meet it; Python 3.10-3.13 do not leave the lock held.
388
+ Every command runs in a process group of its own, so a timeout, `stop()` or Ctrl-C reaches the
389
+ whole process tree, not just the first process. On Python 3.11+ this uses `Popen(process_group=0)`;
390
+ older Pythons start the command through a tiny exec wrapper. A command that reads the terminal (a
391
+ password prompt, for example) fails at once instead of hanging: multiCMD blocks SIGTTIN/SIGTTOU for
392
+ it, and `mcmd` also ignores them, which every command inherits through any shell.
393
+
394
+ Signals sent to your program's group do not reach the commands, so multiCMD stops them itself. One
395
+ Ctrl-C (or a SIGTERM handler's `sys.exit()`) in a call waiting on the main thread, `join_threads()`
396
+ and `AsyncExecutor.wait()` included, stops every command, skips the queued ones and
397
+ re-raises, giving them up to 5 s to exit; a second Ctrl-C ends that wait at once. `mcmd` stops its
398
+ commands on SIGTERM / SIGHUP (unless started with them ignored, as under `nohup`). At interpreter exit
399
+ the commands still running are killed, and a command queued on one of multiCMD's threads fails (-1,
400
+ "not started: the interpreter is exiting") instead of outliving the program; one run from an `atexit`
401
+ handler after that, on the main thread, runs as usual. A program that handles SIGTERM itself, or runs
402
+ multiCMD from its own threads (where Ctrl-C never arrives), can call `multiCMD.kill_all()` from its
403
+ handler. A command not seen to exit a second after SIGKILL is no longer tracked; its pid is named on
404
+ stderr, unless quiet.
436
405
 
437
406
  multiCMD never touches SIGINT's handler (`mcmd` sets its own SIGTERM / SIGHUP handlers, nothing
438
- else). A Ctrl-C that lands while multiCMD does its bookkeeping on the rare paths (a thread that
439
- cannot start, stopping commands after a Ctrl-C or a failure, cancelling a batch, recording a command
440
- that cannot be run) makes that bookkeeping run again from the start, and the KeyboardInterrupt (or
441
- whatever your own handler raised) is raised once it is complete, so it neither cuts that short nor gets
442
- lost. Your handler, if you installed one, is called with every Ctrl-C as it lands, and C-level hooks on
443
- SIGINT (`faulthandler.register`, `signal.siginterrupt`) stay as they are. Whatever your handler raises
444
- while a call waits for its commands is handled as a Ctrl-C: the commands are stopped (a serial run's
445
- current one recorded as stopped, the ones after it skipped) and the exception is raised on. multiCMD
446
- tells it from a failure of its own by your handler's frame in its traceback, and takes anything raised
447
- while it only waits for a running command as yours; a handler that uninstalls itself before it
448
- raises is not seen elsewhere, and what it raises then while a command starts counts as a failure.
407
+ else). What your handler raises while a call waits for its commands is handled as a Ctrl-C.
408
+
409
+ A command is registered from the moment `Popen` returns it: a Ctrl-C that Python raises inside
410
+ `Popen` as a serial run starts a command (some 50-70 µs each) can leave it running, unknown to
411
+ `kill_all()`. Two Ctrl-Cs microseconds apart can leave a command running, still registered, until
412
+ `kill_all()` or the exit stops it. On Python 3.6-3.9, which raise a Ctrl-C at nearly any instruction,
413
+ even one at a few points between the steps of a run can leave its command registered but not signalled.
414
+ On Python 3.6-3.9 and 3.14 a Ctrl-C at the wrong instruction of a `with` on a lock can leave it held: one of
415
+ `threading`'s can hang the program as its threads end (CPython's own), and one of multiCMD's (an
416
+ `AsyncExecutor`'s queue, the live output) hangs that executor, or the next call that is not quiet, if the
417
+ program keeps going.
418
+ These and the other known limits are in [docs/limitations.md](docs/limitations.md).
449
419
 
450
420
  ## Key functions and objects
451
421
 
@@ -495,6 +465,23 @@ pytest -m "not slow" # skip intentional sleep-heavy cases
495
465
 
496
466
  `test_expand_ranges_perf.py` is a manual performance microbenchmark and is not collected by pytest.
497
467
 
468
+ ## Upgrading to 1.56 from 1.55
469
+
470
+ - Fix: a command that times out while it redraws a `\r` progress line on stderr keeps its `Timeout!`
471
+ line; 1.55 could replace it with the progress line, leaving return code 124 without the marker.
472
+ - Simpler interrupt handling, some 700 lines lighter. One Ctrl-C still stops every command.
473
+ What changes is a few rare corners, each in [docs/limitations.md](docs/limitations.md):
474
+ - two Ctrl-Cs microseconds apart can leave a command running, still registered, until `kill_all()`
475
+ or the exit stops it; 1.55 stopped it at once;
476
+ - a Ctrl-C can lose a serial run's starting command anywhere inside `Popen` (about 51 µs per start on
477
+ Python 3.14), not only inside the fork (about 41 µs);
478
+ - a Ctrl-C that leaves `Popen.poll()`'s lock held makes that command's stop about 2 s slower;
479
+ - an ordinary exception your own signal handler raises as a serial run starts a command is recorded as
480
+ a start failure (-1) and not raised, as in 1.54;
481
+ - `join_threads()` on another thread can return before a batch that is still starting.
482
+ - `__running_threads_lock` and `_live_procs_lock`, unused since 1.55 and kept only for code that imported
483
+ them, are gone.
484
+
498
485
  ## Upgrading to 1.55 from 1.54
499
486
 
500
487
  - New: a per-call environment. `env=` (the whole environment, as `subprocess`'s) and `extra_env=`
@@ -518,20 +505,10 @@ pytest -m "not slow" # skip intentional sleep-heavy cases
518
505
  and the exit hook finish the kill. The limits below are the exceptions.
519
506
  - Once the interpreter exits, a command queued on one of multiCMD's threads that has not started fails
520
507
  with -1 ("not started: the interpreter is exiting") instead of starting and outliving the program.
521
- - A command still running a second after SIGKILL is no longer tracked, so later `kill_all()` calls and the
508
+ - A command not seen to exit a second after SIGKILL is no longer tracked, so later `kill_all()` calls and the
522
509
  exit do not wait for it again; its pid is named on stderr, unless quiet.
523
- - Known limits (see [Process groups and signals](#process-groups-and-signals)): a Ctrl-C raised just as
524
- a command's fork returns, before `Popen` has stored the pid, still loses the command, as in 1.54
525
- (serial runs only, some 60-150 µs per command). On Python 3.6-3.10 a second Ctrl-C right after a
526
- first one can lose a command that is starting, or leave one that a call waits for registered but not
527
- signalled; on 3.6-3.9 a second one at a few other moments can lose it too, and a first one at a few
528
- instructions can leave it registered but not signalled. On every Python, two Ctrl-Cs at nearly the same
529
- moment, the first cutting short the stop of a timed-out command or of a finished command's background
530
- processes, can leave them not signalled: `kill_all()` still reaches them, and so does the exit hook, except
531
- on Python 3.6-3.10 for a finished command's background processes, which can then outlive the program. On
532
- Python 3.6-3.9 and 3.14 a Ctrl-C just as
533
- `threading` takes one of its own locks can leave that lock held and hang the program as its threads end
534
- (CPython's own).
510
+ - Known limits: see [Process groups and signals](#process-groups-and-signals) and
511
+ [docs/limitations.md](docs/limitations.md).
535
512
 
536
513
  ## License
537
514
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiCMD
3
- Version: 1.55
3
+ Version: 1.56
4
4
  Summary: Run commands simultaneously
5
5
  Home-page: https://github.com/yufei-pan/multiCMD
6
6
  Author: Yufei Pan
@@ -411,67 +411,37 @@ default, in the API and on the command line) disables it.
411
411
 
412
412
  ## Process groups and signals
413
413
 
414
- Every command runs in a process group of its own, so a timeout, `stop()` or Ctrl-C
415
- reaches the whole process tree, not just the first process. On Python 3.11+ this uses
416
- `Popen(process_group=0)`; older Pythons start the command through a tiny exec wrapper.
417
- Commands that try to read the terminal (a password prompt, for example) fail at once
418
- instead of hanging. The library does this by blocking SIGTTIN/SIGTTOU for the command,
419
- which covers commands started directly or through bash; `mcmd` also ignores them, which
420
- every command inherits through any shell.
421
-
422
- Because the commands are not in your program's process group, signals sent to that
423
- group do not reach them. multiCMD handles this for you: a call waiting on the main
424
- thread that gets Ctrl-C, or the SystemExit of a SIGTERM handler's `sys.exit()`
425
- (`join_threads()` and `AsyncExecutor.wait()` included), stops its commands, the queued ones
426
- skipped, before re-raising, giving them up to 5 s to exit (a second Ctrl-C stops waiting
427
- for them at once, a serial run's command still sent SIGTERM; one that lands while multiCMD stops them or forgets the threads that never ran ends
428
- it too), `mcmd` stops them on
429
- SIGTERM / SIGHUP (unless it was started with them ignored, as under `nohup`), and any
430
- commands still running when the interpreter exits are killed. From then on a command queued
431
- before the exit on one of multiCMD's threads does not start: it fails (return code -1, "not started:
432
- the interpreter is exiting"), as the thread would be frozen with the interpreter and the command
433
- outlive it; a command run from an `atexit` handler, a `weakref.finalize` callback or a `__del__`
434
- after that (on the main thread) runs as usual. A program that handles
435
- SIGTERM itself, or that runs multiCMD from its own threads (where Ctrl-C never
436
- arrives), can call `multiCMD.kill_all()` from its handler. A command still running a second
437
- after SIGKILL (in uninterruptible sleep, or not yours to signal) is no longer tracked, so later
438
- calls and the exit do not wait for it again; multiCMD names its pid on stderr, unless quiet.
439
-
440
- A Ctrl-C (or a signal handler's exception, such as a SIGTERM handler's `sys.exit()`) that lands
441
- while a command starts stops it like any other, from the moment `Popen` knows the child's pid.
442
- One that lands while the command is being forked can leave it running unregistered, as in 1.54:
443
- if Python raises it as the fork returns, before `Popen` has stored the pid, neither the call nor
444
- `kill_all()` nor the exit hook knows the command, which runs on in its own process group. Python
445
- raises those on the main thread only, so this concerns serial runs (`max_threads=1`); and the window
446
- is the fork's own length, some 60-150 µs per command however long it then runs. That is the only place
447
- one Ctrl-C can lose a command. Before Python 3.11 a second one can too: on Python 3.6-3.10 a second
448
- Ctrl-C that lands while the first is still on its way out of the command's start, before multiCMD has
449
- begun to deal with it, is raised before multiCMD has registered the command (one that lands so while a
450
- call waits for a running command leaves it registered but not signalled: `kill_all()` and the exit hook
451
- still reach it), and on 3.6-3.9, where Python raises a Ctrl-C at nearly any instruction, a second one at a
452
- few other moments of that handling can do the same, and even a first one, at a few instructions between the
453
- steps of a run, can leave a command registered but not signalled. From Python 3.11 no other window is known where
454
- a Ctrl-C loses a command. On every Python, two Ctrl-Cs at nearly the same moment, the first cutting short the stop
455
- of a command that timed out or of what a finished command left running with its output open, can leave that
456
- not signalled: `kill_all()` still reaches it, and so does the exit hook, except on Python 3.6-3.10 for what a
457
- finished command left running, which can then outlive the program.
458
- On Python 3.6-3.9 and 3.14 a Ctrl-C that lands just as Python's `threading` takes one of its own
459
- locks with `with` (as `Thread.start()` does, for a serial run's output readers too) can leave that
460
- lock held, and the program then hangs as its threads end. That is CPython's own, and any threaded
461
- program can meet it; Python 3.10-3.13 do not leave the lock held.
414
+ Every command runs in a process group of its own, so a timeout, `stop()` or Ctrl-C reaches the
415
+ whole process tree, not just the first process. On Python 3.11+ this uses `Popen(process_group=0)`;
416
+ older Pythons start the command through a tiny exec wrapper. A command that reads the terminal (a
417
+ password prompt, for example) fails at once instead of hanging: multiCMD blocks SIGTTIN/SIGTTOU for
418
+ it, and `mcmd` also ignores them, which every command inherits through any shell.
419
+
420
+ Signals sent to your program's group do not reach the commands, so multiCMD stops them itself. One
421
+ Ctrl-C (or a SIGTERM handler's `sys.exit()`) in a call waiting on the main thread, `join_threads()`
422
+ and `AsyncExecutor.wait()` included, stops every command, skips the queued ones and
423
+ re-raises, giving them up to 5 s to exit; a second Ctrl-C ends that wait at once. `mcmd` stops its
424
+ commands on SIGTERM / SIGHUP (unless started with them ignored, as under `nohup`). At interpreter exit
425
+ the commands still running are killed, and a command queued on one of multiCMD's threads fails (-1,
426
+ "not started: the interpreter is exiting") instead of outliving the program; one run from an `atexit`
427
+ handler after that, on the main thread, runs as usual. A program that handles SIGTERM itself, or runs
428
+ multiCMD from its own threads (where Ctrl-C never arrives), can call `multiCMD.kill_all()` from its
429
+ handler. A command not seen to exit a second after SIGKILL is no longer tracked; its pid is named on
430
+ stderr, unless quiet.
462
431
 
463
432
  multiCMD never touches SIGINT's handler (`mcmd` sets its own SIGTERM / SIGHUP handlers, nothing
464
- else). A Ctrl-C that lands while multiCMD does its bookkeeping on the rare paths (a thread that
465
- cannot start, stopping commands after a Ctrl-C or a failure, cancelling a batch, recording a command
466
- that cannot be run) makes that bookkeeping run again from the start, and the KeyboardInterrupt (or
467
- whatever your own handler raised) is raised once it is complete, so it neither cuts that short nor gets
468
- lost. Your handler, if you installed one, is called with every Ctrl-C as it lands, and C-level hooks on
469
- SIGINT (`faulthandler.register`, `signal.siginterrupt`) stay as they are. Whatever your handler raises
470
- while a call waits for its commands is handled as a Ctrl-C: the commands are stopped (a serial run's
471
- current one recorded as stopped, the ones after it skipped) and the exception is raised on. multiCMD
472
- tells it from a failure of its own by your handler's frame in its traceback, and takes anything raised
473
- while it only waits for a running command as yours; a handler that uninstalls itself before it
474
- raises is not seen elsewhere, and what it raises then while a command starts counts as a failure.
433
+ else). What your handler raises while a call waits for its commands is handled as a Ctrl-C.
434
+
435
+ A command is registered from the moment `Popen` returns it: a Ctrl-C that Python raises inside
436
+ `Popen` as a serial run starts a command (some 50-70 µs each) can leave it running, unknown to
437
+ `kill_all()`. Two Ctrl-Cs microseconds apart can leave a command running, still registered, until
438
+ `kill_all()` or the exit stops it. On Python 3.6-3.9, which raise a Ctrl-C at nearly any instruction,
439
+ even one at a few points between the steps of a run can leave its command registered but not signalled.
440
+ On Python 3.6-3.9 and 3.14 a Ctrl-C at the wrong instruction of a `with` on a lock can leave it held: one of
441
+ `threading`'s can hang the program as its threads end (CPython's own), and one of multiCMD's (an
442
+ `AsyncExecutor`'s queue, the live output) hangs that executor, or the next call that is not quiet, if the
443
+ program keeps going.
444
+ These and the other known limits are in [docs/limitations.md](docs/limitations.md).
475
445
 
476
446
  ## Key functions and objects
477
447
 
@@ -521,6 +491,23 @@ pytest -m "not slow" # skip intentional sleep-heavy cases
521
491
 
522
492
  `test_expand_ranges_perf.py` is a manual performance microbenchmark and is not collected by pytest.
523
493
 
494
+ ## Upgrading to 1.56 from 1.55
495
+
496
+ - Fix: a command that times out while it redraws a `\r` progress line on stderr keeps its `Timeout!`
497
+ line; 1.55 could replace it with the progress line, leaving return code 124 without the marker.
498
+ - Simpler interrupt handling, some 700 lines lighter. One Ctrl-C still stops every command.
499
+ What changes is a few rare corners, each in [docs/limitations.md](docs/limitations.md):
500
+ - two Ctrl-Cs microseconds apart can leave a command running, still registered, until `kill_all()`
501
+ or the exit stops it; 1.55 stopped it at once;
502
+ - a Ctrl-C can lose a serial run's starting command anywhere inside `Popen` (about 51 µs per start on
503
+ Python 3.14), not only inside the fork (about 41 µs);
504
+ - a Ctrl-C that leaves `Popen.poll()`'s lock held makes that command's stop about 2 s slower;
505
+ - an ordinary exception your own signal handler raises as a serial run starts a command is recorded as
506
+ a start failure (-1) and not raised, as in 1.54;
507
+ - `join_threads()` on another thread can return before a batch that is still starting.
508
+ - `__running_threads_lock` and `_live_procs_lock`, unused since 1.55 and kept only for code that imported
509
+ them, are gone.
510
+
524
511
  ## Upgrading to 1.55 from 1.54
525
512
 
526
513
  - New: a per-call environment. `env=` (the whole environment, as `subprocess`'s) and `extra_env=`
@@ -544,20 +531,10 @@ pytest -m "not slow" # skip intentional sleep-heavy cases
544
531
  and the exit hook finish the kill. The limits below are the exceptions.
545
532
  - Once the interpreter exits, a command queued on one of multiCMD's threads that has not started fails
546
533
  with -1 ("not started: the interpreter is exiting") instead of starting and outliving the program.
547
- - A command still running a second after SIGKILL is no longer tracked, so later `kill_all()` calls and the
534
+ - A command not seen to exit a second after SIGKILL is no longer tracked, so later `kill_all()` calls and the
548
535
  exit do not wait for it again; its pid is named on stderr, unless quiet.
549
- - Known limits (see [Process groups and signals](#process-groups-and-signals)): a Ctrl-C raised just as
550
- a command's fork returns, before `Popen` has stored the pid, still loses the command, as in 1.54
551
- (serial runs only, some 60-150 µs per command). On Python 3.6-3.10 a second Ctrl-C right after a
552
- first one can lose a command that is starting, or leave one that a call waits for registered but not
553
- signalled; on 3.6-3.9 a second one at a few other moments can lose it too, and a first one at a few
554
- instructions can leave it registered but not signalled. On every Python, two Ctrl-Cs at nearly the same
555
- moment, the first cutting short the stop of a timed-out command or of a finished command's background
556
- processes, can leave them not signalled: `kill_all()` still reaches them, and so does the exit hook, except
557
- on Python 3.6-3.10 for a finished command's background processes, which can then outlive the program. On
558
- Python 3.6-3.9 and 3.14 a Ctrl-C just as
559
- `threading` takes one of its own locks can leave that lock held and hang the program as its threads end
560
- (CPython's own).
536
+ - Known limits: see [Process groups and signals](#process-groups-and-signals) and
537
+ [docs/limitations.md](docs/limitations.md).
561
538
 
562
539
  ## License
563
540
 
@@ -16,6 +16,7 @@ tests/test_display.py
16
16
  tests/test_env.py
17
17
  tests/test_expand_ranges.py
18
18
  tests/test_helpers.py
19
+ tests/test_interrupts.py
19
20
  tests/test_parsing.py
20
21
  tests/test_ping.py
21
22
  tests/test_pool.py
@@ -25,7 +26,6 @@ tests/test_reader.py
25
26
  tests/test_regressions.py
26
27
  tests/test_run_command.py
27
28
  tests/test_run_commands.py
28
- tests/test_settle.py
29
29
  tests/test_sudo.py
30
30
  tests/test_supervision.py
31
31
  tests/test_task.py