-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Process API Redux #473
Process API Redux #473
Changes from all commits
a14cbff
12dcd75
d553598
bf2d183
37cc022
9f2a5ed
419c5fb
f402423
5afdbd4
dc12c8e
d6eec9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -33,6 +33,7 @@ Eio replaces existing concurrency libraries such as Lwt | |||||||||||||||||||||||
* [Buffered Writing](#buffered-writing) | ||||||||||||||||||||||||
* [Error Handling](#error-handling) | ||||||||||||||||||||||||
* [Filesystem Access](#filesystem-access) | ||||||||||||||||||||||||
* [Subprocesses](#subprocesses) | ||||||||||||||||||||||||
* [Time](#time) | ||||||||||||||||||||||||
* [Multicore Support](#multicore-support) | ||||||||||||||||||||||||
* [Synchronisation Tools](#synchronisation-tools) | ||||||||||||||||||||||||
|
@@ -891,6 +892,44 @@ Note: the `eio_luv` backend doesn't have the `openat`, `mkdirat`, etc., | |||||||||||||||||||||||
calls that are necessary to implement these checks without races, | ||||||||||||||||||||||||
so be careful if symlinks out of the subtree may be created while the program is running. | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
## Subprocesses | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
Spawning subprocesses is provided by the [Eio.Process][] module. [Eio_unix][] contains a helper function | ||||||||||||||||||||||||
for finding the absolute path to programs. | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
```ocaml | ||||||||||||||||||||||||
# Eio_main.run @@ fun env -> | ||||||||||||||||||||||||
let proc_mgr = Eio.Stdenv.process_mgr env in | ||||||||||||||||||||||||
let stdin, stdout, stderr = Eio.Stdenv.stdio env in | ||||||||||||||||||||||||
let echo = | ||||||||||||||||||||||||
Option.get @@ Eio_unix.resolve_program ~paths:[ "/usr/bin"; "/bin" ] "echo" | ||||||||||||||||||||||||
in | ||||||||||||||||||||||||
Eio.Switch.run @@ fun sw -> | ||||||||||||||||||||||||
let child = Eio.Process.spawn proc_mgr ~sw ~stdin ~stdout ~stderr echo [ "echo"; "hello" ] in | ||||||||||||||||||||||||
Eio.Process.await child;; | ||||||||||||||||||||||||
hello | ||||||||||||||||||||||||
- : Eio.Process.status = Eio.Process.Exited 0 | ||||||||||||||||||||||||
``` | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
If you want to capture the output of a process, you can provide a suitable `Eio.Flow.sink` as the `stdout` argument. | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
```ocaml | ||||||||||||||||||||||||
# Eio_main.run @@ fun env -> | ||||||||||||||||||||||||
let proc_mgr = Eio.Stdenv.process_mgr env in | ||||||||||||||||||||||||
let buffer = Buffer.create 4 in | ||||||||||||||||||||||||
let stdin, _, stderr = Eio.Stdenv.stdio env in | ||||||||||||||||||||||||
let stdout = Eio.Flow.buffer_sink buffer in | ||||||||||||||||||||||||
let echo = | ||||||||||||||||||||||||
Option.get @@ Eio_unix.resolve_program ~paths:[ "/usr/bin"; "/bin" ] "echo" | ||||||||||||||||||||||||
in | ||||||||||||||||||||||||
Eio.Switch.run @@ fun sw -> | ||||||||||||||||||||||||
let child = Eio.Process.spawn proc_mgr ~sw ~stdin ~stdout ~stderr echo [ "echo"; "hello" ] in | ||||||||||||||||||||||||
Eio.Process.await_exn child; | ||||||||||||||||||||||||
Buffer.contents buffer;; | ||||||||||||||||||||||||
Comment on lines
+920
to
+929
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At some point we should add some other convenience functions like
Suggested change
|
||||||||||||||||||||||||
- : string = "hello\n" | ||||||||||||||||||||||||
``` | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
## Time | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
The standard environment provides a [clock][Eio.Time] with the usual POSIX time: | ||||||||||||||||||||||||
|
@@ -1743,3 +1782,4 @@ Some background about the effects system can be found in: | |||||||||||||||||||||||
[Eio.Mutex]: https://ocaml-multicore.github.io/eio/eio/Eio/Mutex/index.html | ||||||||||||||||||||||||
[Eio.Semaphore]: https://ocaml-multicore.github.io/eio/eio/Eio/Semaphore/index.html | ||||||||||||||||||||||||
[Eio.Condition]: https://ocaml-multicore.github.io/eio/eio/Eio/Condition/index.html | ||||||||||||||||||||||||
[Eio.Process]: https://ocaml-multicore.github.io/eio/eio/Eio/Process/index.html | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: this will be a broken link until the next release. Might be OK, or we could link to the source code until then. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ module Flow = Flow | |
module Buf_read = Buf_read | ||
module Buf_write = Buf_write | ||
module Net = Net | ||
module Process = Process | ||
module Domain_manager = Domain_manager | ||
module Time = Time | ||
module File = File | ||
|
@@ -35,6 +36,7 @@ module Stdenv = struct | |
stdout : Flow.sink; | ||
stderr : Flow.sink; | ||
net : Net.t; | ||
process_mgr : Process.mgr; | ||
domain_mgr : Domain_manager.t; | ||
clock : Time.clock; | ||
mono_clock : Time.Mono.t; | ||
|
@@ -47,7 +49,9 @@ module Stdenv = struct | |
let stdin (t : <stdin : #Flow.source; ..>) = t#stdin | ||
let stdout (t : <stdout : #Flow.sink; ..>) = t#stdout | ||
let stderr (t : <stderr : #Flow.sink; ..>) = t#stderr | ||
let stdio (t : <stdin : #Flow.source; stdout: #Flow.sink; stderr : #Flow.sink; ..>) = t#stdin, t#stdout, t#stderr | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not very convinced about this. I suspect that providing defaults for the standard streams would make this unnecessary. |
||
let net (t : <net : #Net.t; ..>) = t#net | ||
let process_mgr (t : <process_mgr : #Process.mgr; ..>) = t#process_mgr | ||
let domain_mgr (t : <domain_mgr : #Domain_manager.t; ..>) = t#domain_mgr | ||
let clock (t : <clock : #Time.clock; ..>) = t#clock | ||
let mono_clock (t : <mono_clock : #Time.Mono.t; ..>) = t#mono_clock | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,53 @@ | ||||||||||||
type status = Exited of int | Signaled of int | Stopped of int | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a polymorphic variant (often the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would something like type exit_status = [ `Exited of int | `Signaled of int ]
type status = [ exit_status | `Stopped of int ] be useful? If the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think it can never happen and we should |
||||||||||||
|
||||||||||||
let pp_status ppf = function | ||||||||||||
| Exited i -> Format.fprintf ppf "Exited %i" i | ||||||||||||
| Signaled i -> Format.fprintf ppf "Signalled %i" i | ||||||||||||
| Stopped i -> Format.fprintf ppf "Stopped %i" i | ||||||||||||
|
||||||||||||
type Exn.err += E of status | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should allow for the possibility of other process errors:
Suggested change
|
||||||||||||
|
||||||||||||
let err e = Exn.create (E e) | ||||||||||||
|
||||||||||||
let () = | ||||||||||||
Exn.register_pp (fun f -> function | ||||||||||||
| E e -> | ||||||||||||
Fmt.string f "Process "; | ||||||||||||
pp_status f e; | ||||||||||||
true | ||||||||||||
| _ -> false | ||||||||||||
) | ||||||||||||
|
||||||||||||
class virtual t = object | ||||||||||||
method virtual pid : int | ||||||||||||
method virtual await : status | ||||||||||||
method virtual signal : int -> unit | ||||||||||||
end | ||||||||||||
|
||||||||||||
let pid proc = proc#pid | ||||||||||||
let await proc = proc#await | ||||||||||||
|
||||||||||||
let await_exn proc = | ||||||||||||
match proc#await with | ||||||||||||
| Exited 0 -> () | ||||||||||||
| status -> raise (err status) | ||||||||||||
|
||||||||||||
let signal proc = proc#signal | ||||||||||||
|
||||||||||||
class virtual mgr = object | ||||||||||||
method virtual spawn : | ||||||||||||
sw:Switch.t -> | ||||||||||||
?cwd:Fs.dir Path.t -> | ||||||||||||
stdin:Flow.source -> | ||||||||||||
stdout:Flow.sink -> | ||||||||||||
stderr:Flow.sink -> | ||||||||||||
string -> | ||||||||||||
string list -> | ||||||||||||
t | ||||||||||||
end | ||||||||||||
|
||||||||||||
let spawn ~sw (t:#mgr) ?cwd ~stdin ~stdout ~stderr cmd args = | ||||||||||||
t#spawn ~sw ?cwd cmd args | ||||||||||||
~stdin:(stdin :> Flow.source) | ||||||||||||
~stdout:(stdout :> Flow.sink) | ||||||||||||
~stderr:(stderr :> Flow.sink) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,46 @@ | ||||||
type status = Exited of int | Signaled of int | Stopped of int | ||||||
|
||||||
val pp_status : Format.formatter -> status -> unit | ||||||
|
||||||
type Exn.err += E of status | ||||||
|
||||||
val err : status -> exn | ||||||
(** [err e] is [Eio.Exn.create (E e)] *) | ||||||
|
||||||
class virtual t : object | ||||||
method virtual pid : int | ||||||
method virtual await : status | ||||||
method virtual signal : int -> unit | ||||||
end | ||||||
|
||||||
val pid : #t -> int | ||||||
(** The process ID *) | ||||||
|
||||||
val await : #t -> status | ||||||
(** This functions waits for the subprocess to exit and then reports the status. *) | ||||||
|
||||||
val await_exn : #t -> unit | ||||||
(** Like {! await} except an exception is raised if the status is not [Exited 0]. *) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a shame we won't have the failing command in the error message. We could have a |
||||||
|
||||||
val signal : #t -> int -> unit | ||||||
(** [signal t i] sends the signal [i] to process [t]. *) | ||||||
|
||||||
class virtual mgr : object | ||||||
method virtual spawn : | ||||||
sw:Switch.t -> | ||||||
?cwd:Fs.dir Path.t -> | ||||||
stdin:Flow.source -> | ||||||
stdout:Flow.sink -> | ||||||
stderr:Flow.sink -> | ||||||
string -> | ||||||
string list -> | ||||||
t | ||||||
end | ||||||
(** A process manager capable of spawning new processes. *) | ||||||
|
||||||
val spawn : sw:Switch.t -> #mgr -> ?cwd:Fs.dir Path.t -> stdin:#Flow.source -> stdout:#Flow.sink -> stderr:#Flow.sink -> string -> string list -> t | ||||||
(** [spawn ~sw mgr ?cwd ~stdin ~stdout ~stderr cmd args] creates a new subprocess that is connected to the | ||||||
switch [sw]. A process will be sent {! Sys.sigkill} when the switch is released. | ||||||
|
||||||
You must provide a standard input and outputs that are backed by file descriptors and | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, hopefully with the pipe changes I added in d553598 this can be completely removed as we check if there's an FD and if not we pipe into the sources and sinks (that code could do with a review I think) |
||||||
[cwd] will optionally change the current working directory of the process.*) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the low-level API should mimic the platform's native API, this high-level API is where we can add convenience features to make things easier to use.
For example, I think it should handle looking up the path for you. It could have an optional
?executable
argument to take the path explicitly, but default to doing the sensible thing withargv[0]
(i.e. look up implicit paths in$PATH
). Note that this example wouldn't work on e.g. NixOS, whereecho
is at/run/current-system/sw/bin/echo
.stdin
can default to/dev/null
I think (we don't want to encourage passing it if it's not needed), and thinking about it further I'm OK withstderr
being passed through by default. We already let all Eio code output traceln messages to stderr.Not sure what's best for
stdout
. Making it optional and passing it though seems OK, as does making it a required argument. I think defaulting to /dev/null would be bad though as you wouldn't get any error indicating why it didn't work (unlike redirecting stdin, where you get end-of-file).