process-queue
(require process-queue) | package: process-queue |
This library implements a queue to manage the execution of many OS level processes in parallel.
a function to control the process as it runs, in the style of process,
a process will, and
data to associate with the process
The process will describes what to do with a process when it terminates. For instance, the will might collect the results of the process, perform cleanup, and even enqueue a new process to "follow up" on the one that just terminated. In detail, the will is a function accepting the current state of the process queue and the process’s process-info and producing an updated process queue.
The process queue keeps track of the processes actively running and those waiting to run. The operations on the process queue add processes to the waiting list, manage process termination and will execution, and wait for all processes to terminate.
For convenience, the process queue also has a field for storing client data. This is useful for storing information related to processes on the side, since the queue is accessible to process wills.
This library provides several implementations of this basic concept which all conform to the same Process queue interface. First, Functional process queues implements the basic process queue in a functional style (such that the process queue is immutable). Second, Functional process priority queue implements a priority-queue version in a functional style. Third, Imperative process queues implements an imperative version of the basic process queue. Finally, Imperative process priority queue implements a priority-queue version in an imperative style.
1 Example usage
This is an illustrative example of the usage of the Imperative process queues implementation.
It creates a queue that runs up to two processes simultaneously, and then enqueues three processes to be run (1, 2, and 4).
The first two are launched immediately. The second process terminates quickly, and its process will enqueues a followup process (3).
Process 4 is then launched, and after it terminates process 3 is launched and terminates.
Finally process 1, which had been running since the start, terminates.
> (require process-queue)
> (define (simple-process cmd will) (match-define (list stdout stdin pid stderr ctl) (process cmd)) (close-output-port stdin) (close-input-port stderr) (process-info stdout ctl will))
> (define (will:show-result/close-ports q info) (define stdout (process-info-data info)) (displayln (port->string stdout)) (close-input-port stdout) q) > (define q (make-process-queue 2))
> (begin (process-queue-enqueue q (λ () (displayln "launch 1") (simple-process "sleep 5; echo done 1" will:show-result/close-ports))) (process-queue-enqueue q (λ () (displayln "launch 2") (simple-process "sleep 1; echo done 2" (λ (q info) (will:show-result/close-ports q info) (process-queue-enqueue q (λ () (displayln "launch 3") (simple-process "echo done 3" will:show-result/close-ports))))))) (process-queue-enqueue q (λ () (displayln "launch 4") (simple-process "echo done 4" will:show-result/close-ports))) (void (process-queue-wait q)))
launch 1
launch 2
done 2
launch 4
done 4
launch 3
done 3
done 1
2 Process queue interface
All of the implementations below provide the following operations on process queues.
procedure
(process-queue? q) → boolean?
q : any/c
procedure
(process-queue-empty? q) → boolean?
q : process-queue?
procedure
(process-queue-enqueue q launch [extra-data]) → process-queue?
q : process-queue? launch : (-> process-info/c) extra-data : any/c = #f
extra-data provides optional extra information that may or may not be used depending on the implementation (for example, a priority value).
Returns the updated process queue.
procedure
→ (and/c process-queue? process-queue-empty?) q : process-queue?
procedure
q : process-queue?
procedure
q : process-queue?
procedure
(process-queue-set-data q data) → process-queue?
q : process-queue? data : any/c
procedure
(process-queue-get-data q) → any/c
q : process-queue?
struct
(struct process-info (data ctl will))
data : any/c ctl : ((or/c 'status 'wait 'interrupt 'kill) . -> . any) will : process-will/c
contract
=
(struct/c process-info any/c ((or/c 'status 'wait 'interrupt 'kill) . -> . any) process-will/c)
contract
= (process-queue? process-info? . -> . process-queue?)
3 Imperative process queues
These imperative process queue implementations mutate a single process queue in-place. Hence, all of the Process queue interface operations that return a new process queue simply return the input queue after mutating it. (The interface returns the queue to support the Functional process queues.)
3.1 Plain imperative process queue
procedure
(make-process-queue active-limit [ data #:kill-older-than process-timeout-seconds]) → (and/c process-queue? process-queue-empty?) active-limit : positive-integer? data : any/c = #f process-timeout-seconds : (or/c positive-real? #f) = #f
active-limit is the maximum number of processes that can be active at once.
data initializes the data field of the queue which can be accessed with process-queue-get-data and process-queue-set-data.
process-timeout-seconds, if non-false, specifies a "best effort" limit on the real running time of each process in seconds.
Best effort here means that the timeout is not strictly enforced in terms of timing —
3.2 Imperative process priority queue
(require process-queue/imperative-priority) | |
package: process-queue |
Imperative process priority queues prioritize processes to run first using a sorting function, provided when creating the queue. These queues use the third argument of process-queue-enqueue as the priority, which defaults to 0 if not provided.
procedure
(make-process-queue active-limit [ data priority> #:kill-older-than process-timeout-seconds]) → (and/c process-queue? process-queue-empty?) active-limit : positive-integer? data : any/c = #f priority> : (any/c any/c . -> . boolean?) = > process-timeout-seconds : (or/c positive-real? #f) = #f
See Plain imperative process queue for more details on the remaining arguments.
4 Functional process queues
These implementations mirror the imperative versions, but all queue operations functionally transform the queue instead of mutating it in-place.
That means that you (the user of this library) must thread the queue around your program and take care never to use a stale queue: only the latest queue is valid, because queue values reflect the state of external and stateful processes. Hence, the functional implementations are a little strange, and you’re probably better off using the Imperative process queues. That said, sometimes a functional interface fits better with the rest of one’s program structure, caveats and all.
4.1 Plain functional process queue
(require process-queue/functional) | package: process-queue |
procedure
(make-process-queue active-limit [ data #:kill-older-than process-timeout-seconds]) → (and/c process-queue? process-queue-empty?) active-limit : positive-integer? data : any/c = #f process-timeout-seconds : (or/c positive-real? #f) = #f
See Plain imperative process queue for more details on the remaining arguments.
4.2 Functional process priority queue
(require process-queue/priority) | package: process-queue |
Functional process priority queues prioritize processes to run first using a sorting function, provided when creating the queue. These queues use the third argument of process-queue-enqueue as the priority, which defaults to 0 if not provided.
procedure
(make-process-queue active-limit [ data priority> #:kill-older-than process-timeout-seconds]) → (and/c process-queue? process-queue-empty?) active-limit : positive-integer? data : any/c = #f priority> : (any/c any/c . -> . boolean?) = > process-timeout-seconds : (or/c positive-real? #f) = #f
See Plain imperative process queue for more details on the remaining arguments.