Skip to content

Commit

Permalink
Make stdio non-optional
Browse files Browse the repository at this point in the history
  • Loading branch information
patricoferris committed Oct 16, 2022
1 parent 88c40c4 commit b083833
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 66 deletions.
8 changes: 4 additions & 4 deletions lib_eio/process.ml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ let stop proc = proc#stop

class virtual mgr = object (_ : #Generic.t)
method probe _ = None
method virtual spawn : sw:Switch.t -> ?cwd:Fs.dir Path.t -> ?stderr:Flow.sink -> ?stdout:Flow.sink -> ?stdin:Flow.source -> string -> string list -> t
method virtual spawn_detached : ?cwd:Fs.dir Path.t -> ?stderr:Flow.sink -> ?stdout:Flow.sink -> ?stdin:Flow.source -> string -> string list -> t
method virtual spawn : sw:Switch.t -> ?cwd:Fs.dir Path.t -> stdin:Flow.source -> stdout:Flow.sink -> stderr:Flow.sink -> string -> string list -> t
method virtual spawn_detached : ?cwd:Fs.dir Path.t -> stdin:Flow.source -> stdout:Flow.sink -> stderr:Flow.sink -> string -> string list -> t
end

let spawn ~sw t ?cwd ?stderr ?stdout ?stdin cmd args = t#spawn ~sw ?cwd ?stderr ?stdout ?stdin cmd args
let spawn_detached t ?cwd ?stderr ?stdout ?stdin cmd args = t#spawn_detached ?cwd ?stderr ?stdout ?stdin cmd args
let spawn ~sw t ?cwd ~stdin ~stdout ~stderr cmd args = t#spawn ~sw ?cwd ~stdin ~stdout ~stderr cmd args
let spawn_detached t ?cwd ~stdin ~stdout ~stderr cmd args = t#spawn_detached ?cwd ~stdin ~stdout ~stderr cmd args
16 changes: 8 additions & 8 deletions lib_eio/process.mli
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,28 @@ class virtual mgr : object
method virtual spawn :
sw:Switch.t ->
?cwd:Fs.dir Path.t ->
?stderr:Flow.sink ->
?stdout:Flow.sink ->
?stdin:Flow.source ->
stdin:Flow.source ->
stdout:Flow.sink ->
stderr:Flow.sink ->
string ->
string list ->
t

method virtual spawn_detached :
?cwd:Fs.dir Path.t ->
?stderr:Flow.sink ->
?stdout:Flow.sink ->
?stdin:Flow.source ->
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 -> ?stderr:Flow.sink -> ?stdout:Flow.sink -> ?stdin:Flow.source -> string -> string list -> t
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 t cmd] creates a new subprocess that is connected to the
switch [sw]. The standard input and outputs redirect to nothing by default. *)

val spawn_detached : #mgr -> ?cwd:Fs.dir Path.t -> ?stderr:Flow.sink -> ?stdout:Flow.sink -> ?stdin:Flow.source -> string -> string list -> t
val spawn_detached : #mgr -> ?cwd:Fs.dir Path.t -> stdin:Flow.source -> stdout:Flow.sink -> stderr:Flow.sink -> string -> string list -> t
(** [spawn_detached t cmd] is like {! spawn} but the new subprocess is not
attached to any switch. *)
30 changes: 8 additions & 22 deletions lib_eio_linux/eio_linux.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1175,34 +1175,20 @@ let pid_to_process close pid = object
Unix.kill pid Sys.sigkill
end

let source_or_std fd = function
| Some flow -> flow
| None -> (source (FD.of_unix_no_hook ~seekable:(FD.is_seekable fd) ~close_unix:true fd) :> Eio.Flow.source)

let sink_or_std fd = function
| Some flow -> flow
| None -> (sink (FD.of_unix_no_hook ~seekable:(FD.is_seekable fd) ~close_unix:true fd) :> Eio.Flow.sink)

let get_fd_or_err flow =
match get_fd_opt flow with
| Some fd ->
Unix.dup ~cloexec:false (FD.to_unix `Peek fd)
| None -> failwith "Currently only flows backed by file descriptors are supported!"

let open_null flags = lazy (Unix.openfile "/dev/null" flags 0o666)

let dev_null_in = open_null [ Unix.O_RDONLY; Unix.O_CLOEXEC ]

let dev_null_out = open_null [ Unix.O_WRONLY; Unix.O_CLOEXEC ]

let process = object
inherit Eio.Process.mgr

(* TODO: Is Sys.chdir cwd domain-safe, ? *)
method spawn ~sw ?cwd ?stderr ?stdout ?stdin cmd args =
let stdin = source_or_std (Lazy.force dev_null_in) stdin |> get_fd_or_err in
let stdout = sink_or_std (Lazy.force dev_null_out) stdout |> get_fd_or_err in
let stderr = sink_or_std (Lazy.force dev_null_out) stderr |> get_fd_or_err in
method spawn ~sw ?cwd ~stdin ~stdout ~stderr cmd args =
let stdin = get_fd_or_err stdin in
let stdout = get_fd_or_err stdout in
let stderr = get_fd_or_err stderr in
let cwd = Option.map snd cwd in
let pid =
Option.iter Sys.chdir cwd;
Expand All @@ -1226,15 +1212,15 @@ let process = object
process


method spawn_detached ?cwd ?stderr ?stdout ?stdin cmd args =
method spawn_detached ?cwd ~stdin ~stdout ~stderr cmd args =
let cwd = Option.map snd cwd in
let pid =
Option.iter Sys.chdir cwd;
Unix.create_process cmd
(Array.of_list args)
(get_fd_or_err (source_or_std (Lazy.force dev_null_in) stdin))
(get_fd_or_err (sink_or_std (Lazy.force dev_null_out) stdout))
(get_fd_or_err (sink_or_std (Lazy.force dev_null_out) stderr))
(get_fd_or_err stdin)
(get_fd_or_err stdout)
(get_fd_or_err stderr)
in
pid_to_process (fun () -> ()) pid
end
Expand Down
24 changes: 12 additions & 12 deletions lib_eio_luv/eio_luv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -858,33 +858,33 @@ let get_fd_or_err flow =
let process = object
inherit Eio.Process.mgr

method spawn ~sw ?cwd ?stderr:stderr_flow ?stdout:stdout_flow ?stdin:stdin_flow cmd args =
method spawn ~sw ?cwd ~stdin:stdin_flow ~stdout:stdout_flow ~stderr:stderr_flow cmd args =
let promise, resolve = Promise.create () in
let stdout_fd = Option.map get_fd_or_err stdout_flow in
let stdin_fd = Option.map get_fd_or_err stdin_flow in
let stderr_fd = Option.map get_fd_or_err stderr_flow in
let stdout_fd = get_fd_or_err stdout_flow in
let stdin_fd = get_fd_or_err stdin_flow in
let stderr_fd = get_fd_or_err stderr_flow in
let on_exit _ ~exit_status ~term_signal =
let status =
match term_signal, exit_status with
| 0, e -> Eio.Process.Exited (Int64.to_int e)
| i, _ -> Eio.Process.Signaled i
in
Option.iter Unix.close stdout_fd;
Option.iter Unix.close stderr_fd;
Option.iter Unix.close stdin_fd;
Unix.close stdout_fd;
Unix.close stderr_fd;
Unix.close stdin_fd;
Promise.resolve resolve status
in
let redirect = Luv.Process.[
Option.map (fun fd -> inherit_fd ~fd:Luv.Process.stdout ~from_parent_fd:(fd |> Obj.magic) ()) stdout_fd;
Option.map (fun fd -> inherit_fd ~fd:Luv.Process.stderr ~from_parent_fd:(fd |> Obj.magic) ()) stderr_fd;
Option.map (fun fd -> inherit_fd ~fd:Luv.Process.stdin ~from_parent_fd:(fd |> Obj.magic) ()) stdin_fd
] |> List.filter_map Fun.id
inherit_fd ~fd:Luv.Process.stdout ~from_parent_fd:(stdout_fd |> Obj.magic) ();
inherit_fd ~fd:Luv.Process.stderr ~from_parent_fd:(stderr_fd |> Obj.magic) ();
inherit_fd ~fd:Luv.Process.stdin ~from_parent_fd:(stdin_fd |> Obj.magic) ()
]
in
let cwd = Option.map snd cwd in
Switch.on_release sw (fun () -> ignore (Promise.await promise));
Luv.Process.spawn ~loop:(get_loop ()) ?working_directory:cwd ~redirect ~on_exit cmd args |> or_raise |> process_of_handle promise

method spawn_detached ?cwd ?stderr:_ ?stdout:_ ?stdin:_ cmd args =
method spawn_detached ?cwd ~stdin:_ ~stdout:_ ~stderr:_ cmd args =
let promise, resolve = Promise.create () in
let on_exit _ ~exit_status ~term_signal =
let status =
Expand Down
47 changes: 27 additions & 20 deletions tests/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@ Creating some useful helper functions
open Eio
open Eio.Std
let spawn ~env ~sw ?cwd cmd args =
Process.spawn ~sw ?cwd ~stdout:env#stdout ~stdin:env#stdin ~stderr:env#stderr env#process cmd args
let spawn_detached ~env ~sw ?cwd cmd args =
Process.spawn ~sw ?cwd ~stdout:env#stdout ~stdin:env#stdin ~stderr:env#stderr env#process cmd args
let run fn =
Eio_main.run @@ fun env ->
fn (Eio.Stdenv.process env) env
fn (spawn ~env) env
let run_env (fn : Eio.Stdenv.t -> 'a) =
let run_detached fn =
Eio_main.run @@ fun env ->
fn env
fn (spawn_detached ~env) env
```

Running a program as a subprocess

```ocaml
# run @@ fun process env ->
# run @@ fun spawn env ->
Switch.run @@ fun sw ->
let t = Eio.Process.spawn ~sw ~stdout:(env#stdout) process "echo" [ "echo"; "hello world" ] in
let t = spawn ~sw "echo" [ "echo"; "hello world" ] in
Eio.Process.status t;;
hello world
- : Process.status = Eio.Process.Exited 0
Expand All @@ -33,21 +39,21 @@ hello world
Stopping a subprocess works and checking the status waits and reports correctly

```ocaml
# run @@ fun process _env ->
# run @@ fun spawn _env ->
Switch.run @@ fun sw ->
let t = Eio.Process.spawn ~sw process "sleep" [ "sleep"; "10" ] in
let t = spawn ~sw "sleep" [ "sleep"; "10" ] in
Eio.Process.stop t;
Eio.Process.status t;;
- : Process.status = Eio.Process.Signaled 9
- : Process.status = Eio.Process.Signaled (-7)
```

A switch will wait for a subprocess to finished when spawned.
<!-- Need a better test of this... -->

```ocaml
# run @@ fun process env ->
# run_detached @@ fun spawn env ->
Switch.run @@ fun sw ->
let _t = Eio.Process.spawn ~sw ~stdout:(env#stdout) process "echo" [ "echo"; "Waited..." ] in
let _t = spawn ~sw "echo" [ "echo"; "Waited..." ] in
();;
Waited...
- : unit = ()
Expand All @@ -56,15 +62,15 @@ Waited...
Passing in flows allows you to redirect the child process' stdout.

```ocaml
# run_env @@ fun env ->
# run @@ fun _spawn env ->
let process = Eio.Stdenv.process env in
let fs = Eio.Stdenv.fs env in
let filename = "process-test.txt" in
let run () =
Eio.Path.(with_open_out ~create:(`Exclusive 0o600) (fs / filename)) @@ fun stdout ->
let stdout = (stdout :> Eio.Flow.sink) in
Switch.run @@ fun sw ->
let t = Eio.Process.spawn ~sw ~stdout process "echo" [ "echo"; "Hello" ] in
let t = Eio.Process.spawn ~sw ~stdout ~stdin:env#stdin ~stderr:env#stderr process "echo" [ "echo"; "Hello" ] in
Eio.Process.status t
in
match run () with
Expand Down Expand Up @@ -94,33 +100,34 @@ val with_pipe_from_child :
unix_fd : [ `Peek | `Take ] -> Unix.file_descr > ->
'c) ->
'c = <fun>
# let pread ~process () =
# let pread env =
with_pipe_from_child @@ fun ~sw ~r ~w ->
let t =
Eio.Process.spawn ~sw ~stdout:(w :> Flow.sink) process "echo" [ "echo"; "Hello" ]
Eio.Process.spawn ~sw ~stdout:(w :> Flow.sink) ~stdin:env#stdin ~stderr:env#stderr env#process "echo" [ "echo"; "Hello" ]
in
let status = Eio.Process.status t in
Eio.traceln "%a" Eio.Process.pp_status status;
Flow.close w;
let buff = Buffer.create 10 in
Flow.copy r (Flow.buffer_sink buff);
Buffer.contents buff;;
val pread : process:#Process.mgr -> unit -> string = <fun>
# run @@ fun process _env ->
pread ~process ();;
val pread :
< process : #Process.mgr; stderr : Flow.sink; stdin : Flow.source; .. > ->
string = <fun>
# run @@ fun _spawn env ->
pread env;;
+Exited 0
- : string = "Hello\n"
```

Spawning subprocesses in new domains works normally

```ocaml
# run_env @@ fun env ->
let process = Eio.Stdenv.process env in
# run @@ fun spawn env ->
let mgr = Eio.Stdenv.domain_mgr env in
Eio.Domain_manager.run mgr @@ fun () ->
Switch.run @@ fun sw ->
let t = Eio.Process.spawn ~sw ~stdout:(env#stdout) process "echo" [ "echo"; "Hello from another domain" ] in
let t = spawn ~sw "echo" [ "echo"; "Hello from another domain" ] in
Eio.Process.status t;;
Hello from another domain
- : Process.status = Eio.Process.Exited 0
Expand Down

0 comments on commit b083833

Please sign in to comment.