|
|
Please consider the following example which is hopefully self-explanatory and introduces the functions `launch-program`, `process-alive-p`, and `terminate-process` as well as `wait-process` and the fact that programs should always be waited for at least once (to make sure their resources are released) and have `close-streams` called on them:
|
|
|
```common-lisp
|
|
|
(require :asdf)
|
|
|
|
|
|
;; these two functions always need to be called before a process goes
|
|
|
;; out of scope and before it can be garbage-collected. otherwise,
|
|
|
;; certain resources may not be returned to the system (causing the
|
|
|
;; system to slow down or eventually become unable to spawn additional
|
|
|
;; external processes)
|
|
|
(defun wait-and-close-streams (process)
|
|
|
(uiop:wait-process process)
|
|
|
(uiop:close-streams process))
|
|
|
|
|
|
(defun bool->string (x)
|
|
|
(if x "T" "F"))
|
|
|
|
|
|
(let ((p9 (uiop:launch-program '("/bin/sleep" "9")))
|
|
|
(p7 (uiop:launch-program '("/bin/sleep" "7")))
|
|
|
(p5 (uiop:launch-program '("/bin/sleep" "5")))
|
|
|
(p3 (uiop:launch-program '("/bin/sleep" "3")))
|
|
|
(p1 (uiop:launch-program '("/bin/sleep" "1"))))
|
|
|
(format t "Waiting for 1s-sleeper to finish sleeping!~%")
|
|
|
(wait-and-close-streams p1)
|
|
|
(format t "1s-sleeper has finished sleeping!~%")
|
|
|
(format t "# Checking: Are the others still sleeping?~%")
|
|
|
(format t "3s-sleeper still sleeping? ~a (expected: T)~%"
|
|
|
(bool->string (uiop:process-alive-p p3)))
|
|
|
(format t "5s-sleeper still sleeping? ~a (expected: T)~%"
|
|
|
(bool->string (uiop:process-alive-p p5)))
|
|
|
(format t "7s-sleeper still sleeping? ~a (expected: T)~%"
|
|
|
(bool->string (uiop:process-alive-p p7)))
|
|
|
(format t "9s-sleeper still sleeping? ~a (expected: T)~%~%"
|
|
|
(bool->string (uiop:process-alive-p p9)))
|
|
|
|
|
|
(format t "Waiting for 5s-sleeper to finish sleeping!~%")
|
|
|
(wait-and-close-streams p5)
|
|
|
(format t "5s-sleeper has finished sleeping!~%")
|
|
|
(format t "# Checking: Are the others still sleeping?~%")
|
|
|
(format t "3s-sleeper still sleeping? ~a (expected: F)~%"
|
|
|
(bool->string (uiop:process-alive-p p3)))
|
|
|
(format t "7s-sleeper still sleeping? ~a (expected: T)~%"
|
|
|
(bool->string (uiop:process-alive-p p7)))
|
|
|
(format t "9s-sleeper still sleeping? ~a (expected: T)~%~%"
|
|
|
(bool->string (uiop:process-alive-p p9)))
|
|
|
(wait-and-close-streams p3)
|
|
|
|
|
|
(format t "Terminating the 9s-sleeper now, then waiting for it!~%")
|
|
|
(uiop:terminate-process p9)
|
|
|
(wait-and-close-streams p9)
|
|
|
(format t "# Checking: Are the others still sleeping?~%")
|
|
|
(format t "7s-sleeper still sleeping? ~a (expected: T)~%~%"
|
|
|
(bool->string (uiop:process-alive-p p7)))
|
|
|
|
|
|
(format t "Finally, waiting for the last remaining process and exiting~%")
|
|
|
(wait-and-close-streams p7))
|
|
|
Please consider the following example whose output consists of blocks of this form:
|
|
|
```
|
|
|
### abcl-1.5.0-fasl43-linux-x64
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143
|
|
|
```
|
|
|
|
|
|
What happens here is that we span five sleeper processes (each expected to sleep and then exit after 1,3,5,7,or 9 seconds, respectively). We influence the natural order of terminate to some extent, though, by
|
|
|
- first waiting for the 1s-sleeper to finish (this should happen right away anyway)
|
|
|
- then waiting for the 3s-sleeper to finish (we can expected the 3s-sleeper to have finished by then, too)
|
|
|
- Terminating the 9s-sleeper earlier and waiting for the 7s-sleeper to finish.
|
|
|
|
|
|
After each such step we check each sleeper whether it is alive and what its return code was.
|
|
|
|
|
|
(A look at the code will also reveal that we make sure to call `uiop:wait-process` on each process at sometime -- if we did not, resources might go unfreed).
|
|
|
|
|
|
We will take a look at the implementation-dependent output to see where there is a bit of unreliability, and look at the code that generated the output, and a look at the underlying code in which we actually see functions such as `uiop:process-alive-p`, `uiop:terminate-process` and `uiop:wait-process` in use.
|
|
|
|
|
|
Here now, is the complete code:
|
|
|
|
|
|
- The output from running the test on a few implementations
|
|
|
|
|
|
```
|
|
|
### abcl-1.5.0-fasl43-linux-x64
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143
|
|
|
### acl-10.1-linux-x86
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### ccl-1.11-f96-linux-x86
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### cmu-20c_release-20c__20c_unicode_-linux-x86
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### cmu-21b__21b_unicode_-linux-x86
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### cmu-snapshot-2017-04-42-g7f3040a7e__21b_unicode_-linux-x86
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### ecl-16.1.3-linux-x64
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### ecl-16.1.3-f4feda8a-linux-x64
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### mkcl-1.1.10.19-2dbfa99-linux-x64
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### sbcl-1.3.21-linux-x64
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
### sbcl-1.3.21.149-9e3453954-linux-x64
|
|
|
Waiting for the 1s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | T
|
|
|
5 | T
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Waiting for the 5s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | T
|
|
|
9 | T
|
|
|
Terminating the 9s-sleeper now
|
|
|
And waiting for the 7s-sleeper to finish
|
|
|
1 | NIL, 0
|
|
|
3 | NIL, 0
|
|
|
5 | NIL, 0
|
|
|
7 | NIL, 0
|
|
|
9 | NIL, 143, 15
|
|
|
```
|
|
|
It should generate the following output:
|
|
|
|
|
|
- The runner (play-with-timers.sh) that runs the underlying tests on a couple of implementations
|
|
|
|
|
|
```shell
|
|
|
a=$HOME/asdf/build/asdf.lisp
|
|
|
f=$HOME/lisp-scripts/play-with-timers.lisp
|
|
|
b=$HOME/lisp-installs
|
|
|
|
|
|
### Loading asdf and requiring mk-unix: the fact that "system mk-unix is out of date" is treated
|
|
|
### as a fatal error prior to ab65958cc97addf88b756dcf135b9ace76a13f02.
|
|
|
# (
|
|
|
# l=$b/mkcl-1.1.10/bin/mkcl;
|
|
|
# $l -eval '(setf *load-verbose* nil)' -eval "(load \"$a\")" -eval "(load \"$f\")" -eval '(quit)'
|
|
|
# )
|
|
|
|
|
|
(
|
|
|
l=$b/abcl-bin-1.5.0/abcl.jar
|
|
|
java -jar $l --noinform --batch --load $a --load $f
|
|
|
)
|
|
|
(
|
|
|
l=$b/acl10.1express-linux-x86/alisp
|
|
|
$l --batch -e '(setf *load-verbose* nil)' -L $a -L $f --kill
|
|
|
)
|
|
|
(
|
|
|
l=$b/ccl-1.11-linux/lx86cl
|
|
|
$l --load $a --load $f --eval '(quit)'
|
|
|
)
|
|
|
(
|
|
|
l=$b/cmucl-20c-x86-linux/bin/lisp
|
|
|
$l -quiet -batch -eval "(handler-bind ((warning #'muffle-warning)) (load \"$a\"))" -load $f -eval '(quit)'
|
|
|
)
|
|
|
(
|
|
|
l=$b/cmucl-21b-x86-linux/bin/lisp
|
|
|
$l -quiet -batch -eval "(handler-bind ((warning #'muffle-warning)) (load \"$a\"))" -load $f -eval '(quit)'
|
|
|
)
|
|
|
(
|
|
|
l=$b/cmucl-2017-04-42-g7f3040a7e-x86-linux/bin/lisp
|
|
|
$l -quiet -batch -eval "(handler-bind ((warning #'muffle-warning)) (load \"$a\"))" -load $f -eval '(quit)'
|
|
|
)
|
|
|
(
|
|
|
l=$b/ecl-16.1.3/bin/ecl
|
|
|
$l --eval '(setf *load-verbose* nil)' --eval "(load \"$a\")" --eval "(load \"$f\")" --eval '(quit)'
|
|
|
)
|
|
|
(
|
|
|
l=$b/ecl-git/bin/ecl
|
|
|
$l --eval '(setf *load-verbose* nil)' --eval "(load \"$a\")" --eval "(load \"$f\")" --eval '(quit)'
|
|
|
)
|
|
|
(
|
|
|
l=$b/mkcl-git/bin/mkcl;
|
|
|
$l -eval '(setf *load-verbose* nil)' -eval "(load \"$a\")" -eval "(load \"$f\")" -eval '(quit)'
|
|
|
)
|
|
|
(
|
|
|
l=$b/sbcl-1.3.21/bin/sbcl
|
|
|
$l --noinform --non-interactive --load $a --load $f
|
|
|
)
|
|
|
(
|
|
|
l=$b/sbcl-git/bin/sbcl
|
|
|
$l --noinform --non-interactive --load $a --load $f
|
|
|
)
|
|
|
```
|
|
|
Waiting for 1s-sleeper to finish sleeping!
|
|
|
1s-sleeper has finished sleeping!
|
|
|
# Checking: Are the others still sleeping?
|
|
|
3s-sleeper still sleeping? T (expected: T)
|
|
|
5s-sleeper still sleeping? T (expected: T)
|
|
|
7s-sleeper still sleeping? T (expected: T)
|
|
|
9s-sleeper still sleeping? T (expected: T)
|
|
|
|
|
|
Waiting for 5s-sleeper to finish sleeping!
|
|
|
5s-sleeper has finished sleeping!
|
|
|
# Checking: Are the others still sleeping?
|
|
|
3s-sleeper still sleeping? F (expected: F)
|
|
|
7s-sleeper still sleeping? T (expected: T)
|
|
|
9s-sleeper still sleeping? T (expected: T)
|
|
|
|
|
|
Terminating the 9s-sleeper now, then waiting for it!
|
|
|
# Checking: Are the others still sleeping?
|
|
|
7s-sleeper still sleeping? T (expected: T)
|
|
|
|
|
|
Finally, waiting for the last remaining process and exiting
|
|
|
|
|
|
- The final script (play-with-timers.lisp) with all the tests:
|
|
|
|
|
|
```common-lisp
|
|
|
(defun analyse (process intended-runtime)
|
|
|
(format t "~a | ~13@a~%"
|
|
|
intended-runtime
|
|
|
(format nil "~{~a~^, ~}"
|
|
|
(let ((alive (uiop:process-alive-p process)))
|
|
|
(if alive
|
|
|
(list T)
|
|
|
`(,alive ,@(multiple-value-list (uiop:wait-process process))))))))
|
|
|
|
|
|
(progn
|
|
|
(format t "### ~a~%" (asdf:implementation-identifier))
|
|
|
|
|
|
(let ((p9 (uiop:launch-program '("/bin/sleep" "9")))
|
|
|
(p7 (uiop:launch-program '("/bin/sleep" "7")))
|
|
|
(p5 (uiop:launch-program '("/bin/sleep" "5")))
|
|
|
(p3 (uiop:launch-program '("/bin/sleep" "3")))
|
|
|
(p1 (uiop:launch-program '("/bin/sleep" "1"))))
|
|
|
(format t "Waiting for the 1s-sleeper to finish~%")
|
|
|
(uiop:wait-process p1)
|
|
|
|
|
|
(analyse p1 1)
|
|
|
(analyse p3 3)
|
|
|
(analyse p5 5)
|
|
|
(analyse p7 7)
|
|
|
(analyse p9 9)
|
|
|
|
|
|
(format t "Waiting for the 5s-sleeper to finish~%")
|
|
|
(uiop:wait-process p5)
|
|
|
|
|
|
(analyse p1 1)
|
|
|
(analyse p3 3)
|
|
|
(analyse p5 5)
|
|
|
(analyse p7 7)
|
|
|
(analyse p9 9)
|
|
|
|
|
|
(format t "Terminating the 9s-sleeper now~%")
|
|
|
(uiop:terminate-process p9)
|
|
|
(format t "And waiting for the 7s-sleeper to finish~%")
|
|
|
(uiop:wait-process p7)
|
|
|
|
|
|
(analyse p1 1)
|
|
|
(analyse p3 3)
|
|
|
(analyse p5 5)
|
|
|
(analyse p7 7)
|
|
|
(analyse p9 9)
|
|
|
|
|
|
;; make sure each process was waited for at some point
|
|
|
(uiop:wait-process p3)
|
|
|
(uiop:wait-process p9))
|
|
|
|
|
|
(uiop:quit 0))
|
|
|
```
|
|
|
and return a code of 0.
|
|
|
|
|
|
The 7s-sleeper and the 9s-sleeper went from alive to
|
|
|
not-alive in different ways here: the former exited while
|
|
|
the latter was terminated, e.g. by having a signal sent to
|
|
|
it. This distinction can be relevant. That's why `wait-process`
|
|
|
exposes it whenever possible: If a process is terminated by signal
|
|
|
15 (SIGTERM), its return code will be 128+15=143. To distinguish that
|
|
|
from a regular exit with return code 143,
|
|
|
`wait-process` will return the two values 143 and 15 whenever possible.
|
|
|
A clean exit with return code 0 can still always simply be checked for
|
|
|
via `(= 0 (wait-process))`. |
|
|
\ No newline at end of file |
|
|
|
|
|
Note: Output of this code was captured on a machine where !83 had already been merged into master.
|
|
|
|
|
|
Note: The difference between the `uiop:process-wait` output `143` and `(values 143 15)` is explained [here](https://gitlab.common-lisp.net/asdf/asdf/wikis/UIOP-Relatively-Well-Supported#uiopprocess-alive-p-and-uiopwait-process) |