From a14cbff4beaa6f8073d8e3adc4d8165146af35b7 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Wed, 22 Mar 2023 12:20:43 +0000 Subject: [PATCH 01/11] Move spawn to Eio_unix Using `clone3` directly on Linux is difficult as there is no glibc wrapper for it. --- lib_eio/unix/fork_action.c | 16 ++++++++++++++++ lib_eio/unix/fork_action.ml | 6 ++++++ lib_eio/unix/fork_action.mli | 6 ++++++ lib_eio_posix/eio_posix_stubs.c | 14 -------------- lib_eio_posix/low_level.ml | 7 +------ 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib_eio/unix/fork_action.c b/lib_eio/unix/fork_action.c index 2c429862d..1aa96cbc8 100644 --- a/lib_eio/unix/fork_action.c +++ b/lib_eio/unix/fork_action.c @@ -4,7 +4,9 @@ #include #include +#include #include +#include #include "fork_action.h" @@ -39,6 +41,20 @@ void eio_unix_fork_error(int fd, char *fn, char *buf) { try_write_all(fd, buf); } +CAMLprim value eio_unix_spawn(value v_errors, value v_actions) { + CAMLparam1(v_actions); + pid_t child_pid; + + child_pid = fork(); + if (child_pid == 0) { + eio_unix_run_fork_actions(Int_val(v_errors), v_actions); + } else if (child_pid < 0) { + uerror("fork", Nothing); + } + + CAMLreturn(Val_long(child_pid)); +} + static char **make_string_array(int errors, value v_array) { int n = Wosize_val(v_array); char **c = calloc(sizeof(char *), (n + 1)); diff --git a/lib_eio/unix/fork_action.ml b/lib_eio/unix/fork_action.ml index 659410bad..1b87b78ba 100644 --- a/lib_eio/unix/fork_action.ml +++ b/lib_eio/unix/fork_action.ml @@ -20,6 +20,12 @@ let rec with_actions actions fn = let err_closed op () = Fmt.failwith "%s: FD is closed!" op +external eio_spawn : Unix.file_descr -> c_action list -> int = "eio_unix_spawn" + +let spawn errors actions = + Rcfd.use ~if_closed:(err_closed "spawn") errors @@ fun errors -> + eio_spawn errors actions + external action_execve : unit -> fork_fn = "eio_unix_fork_execve" let action_execve = action_execve () let execve path ~argv ~env = { run = fun k -> k (Obj.repr (action_execve, path, argv, env)) } diff --git a/lib_eio/unix/fork_action.mli b/lib_eio/unix/fork_action.mli index 7699b5b45..29022939b 100644 --- a/lib_eio/unix/fork_action.mli +++ b/lib_eio/unix/fork_action.mli @@ -28,6 +28,12 @@ type t = { run : 'a. ((c_action -> 'a) -> 'a) } [@@unboxed] val with_actions : t list -> (c_action list -> 'a) -> 'a +val spawn : Rcfd.t -> c_action list -> int +(** [spawn errors actions] forks a child process and executes [actions] in it. + + If an error occurs, a message is written to [errors]. + Returns the PID of the child process. *) + (** {2 Actions} *) val execve : string -> argv:string array -> env:string array -> t diff --git a/lib_eio_posix/eio_posix_stubs.c b/lib_eio_posix/eio_posix_stubs.c index 93a0f10c4..0d82cba8b 100644 --- a/lib_eio_posix/eio_posix_stubs.c +++ b/lib_eio_posix/eio_posix_stubs.c @@ -187,17 +187,3 @@ CAMLprim value caml_eio_posix_renameat(value v_old_fd, value v_old_path, value v if (ret == -1) uerror("renameat", v_old_path); CAMLreturn(Val_unit); } - -CAMLprim value caml_eio_posix_spawn(value v_errors, value v_actions) { - CAMLparam1(v_actions); - pid_t child_pid; - - child_pid = fork(); - if (child_pid == 0) { - eio_unix_run_fork_actions(Int_val(v_errors), v_actions); - } else if (child_pid < 0) { - uerror("fork", Nothing); - } - - CAMLreturn(Val_long(child_pid)); -} diff --git a/lib_eio_posix/low_level.ml b/lib_eio_posix/low_level.ml index e61a9a2a0..ad1eaf0d4 100644 --- a/lib_eio_posix/low_level.ml +++ b/lib_eio_posix/low_level.ml @@ -261,8 +261,6 @@ module Process = struct Unix.kill t.pid signal ) - external eio_spawn : Unix.file_descr -> Eio_unix.Private.Fork_action.c_action list -> int = "caml_eio_posix_spawn" - let spawn ~sw actions = with_pipe @@ fun errors_r errors_w -> Eio_unix.Private.Fork_action.with_actions actions @@ fun c_actions -> @@ -271,10 +269,7 @@ module Process = struct (* We take the lock to ensure that the signal handler won't reap the process before we've registered it. *) Children.with_lock (fun () -> - let pid = - Fd.use_exn "errors-w" errors_w @@ fun errors_w -> - eio_spawn errors_w c_actions - in + let pid = Eio_unix.Private.Fork_action.spawn (Fd.to_rcfd errors_w) c_actions in Fd.close errors_w; { pid; exit_status = Children.register pid } ) From 12dcd755bce2f9f542ccb686e5a38a540bb84d4c Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 25 Mar 2023 21:28:14 +0000 Subject: [PATCH 02/11] Initial cross-platform subprocess support --- lib_eio/eio.ml | 3 + lib_eio/eio.mli | 12 +++ lib_eio/process.ml | 22 ++++++ lib_eio/process.mli | 39 ++++++++++ lib_eio_linux/eio_linux.ml | 40 ++++++++++ lib_eio_linux/eio_linux.mli | 1 + lib_eio_luv/tests/dune | 6 +- lib_eio_posix/eio_posix.ml | 2 + lib_eio_posix/eio_posix.mli | 1 + lib_eio_posix/process.ml | 38 ++++++++++ lib_main/dune | 6 +- lib_main/eio_main.ml | 6 +- tests/process.md | 143 ++++++++++++++++++++++++++++++++++++ 13 files changed, 310 insertions(+), 9 deletions(-) create mode 100644 lib_eio/process.ml create mode 100644 lib_eio/process.mli create mode 100644 lib_eio_posix/process.ml create mode 100644 tests/process.md diff --git a/lib_eio/eio.ml b/lib_eio/eio.ml index 829506c13..5bb3c9dfa 100644 --- a/lib_eio/eio.ml +++ b/lib_eio/eio.ml @@ -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; @@ -48,6 +50,7 @@ module Stdenv = struct let stdout (t : ) = t#stdout let stderr (t : ) = t#stderr let net (t : ) = t#net + let process_mgr (t : ) = t#process_mgr let domain_mgr (t : ) = t#domain_mgr let clock (t : ) = t#clock let mono_clock (t : ) = t#mono_clock diff --git a/lib_eio/eio.mli b/lib_eio/eio.mli index 6557e2956..c832ae0a3 100644 --- a/lib_eio/eio.mli +++ b/lib_eio/eio.mli @@ -84,6 +84,9 @@ module Buf_write = Buf_write (** Networking. *) module Net = Net +(** Subprocesses. *) +module Process = Process + (** Parallel computation across multiple CPU cores. *) module Domain_manager : sig class virtual t : object @@ -180,6 +183,7 @@ module Stdenv : sig 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; @@ -222,6 +226,14 @@ module Stdenv : sig val net : -> 'a (** [net t] gives access to the process's network namespace. *) + (** {1 Processes } + + To use this, see {!Process}. + *) + + val process_mgr : -> 'a + (** [process_mgr t] allows you to run subprocesses. *) + (** {1 Domains (using multiple CPU cores)} To use this, see {!Domain_manager}. diff --git a/lib_eio/process.ml b/lib_eio/process.ml new file mode 100644 index 000000000..b25220835 --- /dev/null +++ b/lib_eio/process.ml @@ -0,0 +1,22 @@ +type status = Exited of int | Signaled of int | Stopped of int + +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 + +class virtual t = object + method virtual pid : int + method virtual status : status + method virtual signal : int -> unit +end + +let pid proc = proc#pid +let status proc = proc#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 ?cwd ~stdin ~stdout ~stderr cmd args = t#spawn ~sw ?cwd ~stdin ~stdout ~stderr cmd args \ No newline at end of file diff --git a/lib_eio/process.mli b/lib_eio/process.mli new file mode 100644 index 000000000..44694918f --- /dev/null +++ b/lib_eio/process.mli @@ -0,0 +1,39 @@ +type status = Exited of int | Signaled of int | Stopped of int + +val pp_status : Format.formatter -> status -> unit + +class virtual t : object + method virtual pid : int + method virtual status : status + method virtual signal : int -> unit +end + +val pid : #t -> int +(** The process ID *) + +val status : #t -> status +(** Checks the status of the subprocess, this will block waiting for the subprocess + to terminate or be stopped by a signal. *) + +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 stopped when the switch is released. + + You must provide a standard input and outputs that are backed by file descriptors and + [cwd] will optionally change the current working directory of the process.*) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index 4414d1bf3..a6a6ebb56 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -286,11 +286,50 @@ let net = object method getnameinfo = Eio_unix.getnameinfo end +module Process = Low_level.Process + +let unix_status_to_stats = function + | Unix.WEXITED i -> Eio.Process.Exited i + | Unix.WSIGNALED i -> Eio.Process.Signaled i + | Unix.WSTOPPED i -> Eio.Process.Stopped i + +let process proc : Eio.Process.t = object + method pid = Process.pid proc + method status = + let status = Eio.Promise.await @@ Process.exit_status proc in + unix_status_to_stats status + method signal i = Process.signal proc i +end + +let pipe_or_fd flow = + match Eio.Generic.probe flow FD with + | None -> assert false + | Some fd -> FD.to_rcfd fd + +let process_mgr = object + method spawn ~sw ?cwd ~stdin ~stdout ~stderr prog args = + let chdir = Option.to_list cwd |> List.map (fun (_, s) -> Process.Fork_action.chdir s) in + let stdin = pipe_or_fd stdin in + let stdout = pipe_or_fd stdout in + let stderr = pipe_or_fd stderr in + let actions = Process.Fork_action.[ + Eio_unix.Private.Fork_action.inherit_fds [ + 0, stdin, `Blocking; + 1, stdout, `Blocking; + 2, stderr, `Blocking; + ]; + execve prog ~argv:(Array.of_list args) ~env:[||] + ] in + let actions = chdir @ actions in + process (Process.spawn ~sw actions) +end + type stdenv = < stdin : source; stdout : sink; stderr : sink; net : Eio.Net.t; + process_mgr : Eio.Process.mgr; domain_mgr : Eio.Domain_manager.t; clock : Eio.Time.clock; mono_clock : Eio.Time.Mono.t; @@ -427,6 +466,7 @@ let stdenv ~run_event_loop = method stdout = Lazy.force stdout method stderr = Lazy.force stderr method net = net + method process_mgr = process_mgr method domain_mgr = domain_mgr ~run_event_loop method clock = clock method mono_clock = mono_clock diff --git a/lib_eio_linux/eio_linux.mli b/lib_eio_linux/eio_linux.mli index dcfbd95cd..a6829e0c9 100644 --- a/lib_eio_linux/eio_linux.mli +++ b/lib_eio_linux/eio_linux.mli @@ -63,6 +63,7 @@ type stdenv = < stdout : sink; stderr : sink; net : Eio.Net.t; + process_mgr : Eio.Process.mgr; domain_mgr : Eio.Domain_manager.t; clock : Eio.Time.clock; mono_clock : Eio.Time.Mono.t; diff --git a/lib_eio_luv/tests/dune b/lib_eio_luv/tests/dune index 0e1ed9770..1168d0303 100644 --- a/lib_eio_luv/tests/dune +++ b/lib_eio_luv/tests/dune @@ -1,3 +1,3 @@ -(mdx - (package eio_luv) - (deps (package eio_luv))) +; (mdx +; (package eio_luv) +; (deps (package eio_luv))) diff --git a/lib_eio_posix/eio_posix.ml b/lib_eio_posix/eio_posix.ml index 7015f76a3..c0016649b 100644 --- a/lib_eio_posix/eio_posix.ml +++ b/lib_eio_posix/eio_posix.ml @@ -21,6 +21,7 @@ type stdenv = < stdout : ; stderr : ; net : Eio.Net.t; + process_mgr : Eio.Process.mgr; domain_mgr : Eio.Domain_manager.t; clock : Eio.Time.clock; mono_clock : Eio.Time.Mono.t; @@ -45,6 +46,7 @@ let run main = method clock = Time.clock method mono_clock = Time.mono_clock method net = Net.v + method process_mgr = Process.v method domain_mgr = Domain_mgr.v method cwd = ((Fs.cwd, "") :> Eio.Fs.dir Eio.Path.t) method fs = ((Fs.fs, "") :> Eio.Fs.dir Eio.Path.t) diff --git a/lib_eio_posix/eio_posix.mli b/lib_eio_posix/eio_posix.mli index 25e912ca0..fee07d4e0 100644 --- a/lib_eio_posix/eio_posix.mli +++ b/lib_eio_posix/eio_posix.mli @@ -5,6 +5,7 @@ type stdenv = < stdout : ; stderr : ; net : Eio.Net.t; + process_mgr : Eio.Process.mgr; domain_mgr : Eio.Domain_manager.t; clock : Eio.Time.clock; mono_clock : Eio.Time.Mono.t; diff --git a/lib_eio_posix/process.ml b/lib_eio_posix/process.ml new file mode 100644 index 000000000..075d68031 --- /dev/null +++ b/lib_eio_posix/process.ml @@ -0,0 +1,38 @@ +open Low_level +module Rfcd = Eio_unix.Private.Rcfd + +let unix_status_to_stats = function + | Unix.WEXITED i -> Eio.Process.Exited i + | Unix.WSIGNALED i -> Eio.Process.Signaled i + | Unix.WSTOPPED i -> Eio.Process.Stopped i + +let process proc : Eio.Process.t = object + method pid = Process.pid proc + method status = + let status = Eio.Promise.await @@ Process.exit_status proc in + unix_status_to_stats status + method signal i = Process.signal proc i +end + +let pipe_or_fd flow = + match Fd.get_fd_opt flow with + | None -> assert false + | Some fd -> Fd.to_rcfd fd + +let v = object + method spawn ~sw ?cwd ~stdin ~stdout ~stderr prog args = + let chdir = Option.to_list cwd |> List.map (fun (_, s) -> Process.Fork_action.chdir s) in + let stdin = pipe_or_fd stdin in + let stdout = pipe_or_fd stdout in + let stderr = pipe_or_fd stderr in + let actions = Process.Fork_action.[ + Eio_unix.Private.Fork_action.inherit_fds [ + 0, stdin, `Blocking; + 1, stdout, `Blocking; + 2, stderr, `Blocking; + ]; + execve prog ~argv:(Array.of_list args) ~env:[||] + ] in + let actions = chdir @ actions in + process (Process.spawn ~sw actions) +end \ No newline at end of file diff --git a/lib_main/dune b/lib_main/dune index a2e412ef0..0a1242fda 100644 --- a/lib_main/dune +++ b/lib_main/dune @@ -8,7 +8,7 @@ (select posix_backend.ml from (eio_posix -> posix_backend.enabled.ml) ( -> posix_backend.disabled.ml)) - (select luv_backend.ml from - (eio_luv -> luv_backend.enabled.ml) - ( -> luv_backend.disabled.ml)) +; (select luv_backend.ml from +; (eio_luv -> luv_backend.enabled.ml) +; ( -> luv_backend.disabled.ml)) )) diff --git a/lib_main/eio_main.ml b/lib_main/eio_main.ml index 89a1ff09c..c0393dd0a 100644 --- a/lib_main/eio_main.ml +++ b/lib_main/eio_main.ml @@ -4,12 +4,12 @@ let force run fn = let run fn = match Sys.getenv_opt "EIO_BACKEND" with | Some ("io-uring" | "linux") -> force Linux_backend.run fn - | Some "posix" -> force Posix_backend.run fn - | Some "luv" -> force Luv_backend.run fn + | None | Some "posix" -> force Posix_backend.run fn + (* | Some "luv" -> force Luv_backend.run fn | None | Some "" -> Linux_backend.run fn ~fallback:(fun _ -> Posix_backend.run fn ~fallback:(fun _ -> force Luv_backend.run fn ) - ) + ) *) | Some x -> Fmt.failwith "Unknown Eio backend %S (from $EIO_BACKEND)" x diff --git a/tests/process.md b/tests/process.md new file mode 100644 index 000000000..775f19df4 --- /dev/null +++ b/tests/process.md @@ -0,0 +1,143 @@ +# Setting up the environment + +```ocaml +# #require "eio_main";; +``` + +Creating some useful helper functions + +```ocaml +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_mgr cmd args + +let run fn = + Eio_main.run @@ fun env -> + fn (spawn ~env) env +``` + +Running a program as a subprocess + +```ocaml +# run @@ fun spawn env -> + Switch.run @@ fun sw -> + let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in + Process.status t;; +hello world +- : Process.status = Eio.Process.Exited 0 +``` + +Stopping a subprocess works and checking the status waits and reports correctly + +```ocaml +# run @@ fun spawn _env -> + Switch.run @@ fun sw -> + let t = spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ] in + Process.signal t Sys.sigkill; + Process.status t;; +- : Process.status = Eio.Process.Signaled (-7) +``` + +A switch will stop a process when it is released. + + +```ocaml +# run @@ fun spawn env -> + let proc = ref None in + let run () = + Switch.run @@ fun sw -> + proc := Some (spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ]) + in + run (); + Process.status (Option.get !proc);; +- : Process.status = Eio.Process.Signaled (-7) +``` + +Passing in flows allows you to redirect the child process' stdout. + +```ocaml +# run @@ fun _spawn env -> + let process = Eio.Stdenv.process_mgr 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 ~stdin:env#stdin ~stderr:env#stderr process "/usr/bin/echo" [ "echo"; "Hello" ] in + Process.status t + in + match run () with + | Exited 0 -> + Eio.Path.(with_open_in (fs / filename)) @@ fun flow -> + let buff = Buffer.create 128 in + Eio.Flow.copy flow (Eio.Flow.buffer_sink buff); + Buffer.contents buff + | _ -> failwith "Subprocess didn't exit cleanly!";; +- : string = "Hello\n" +``` + +Pipes + +```ocaml +# let with_pipe_from_child fn = + Switch.run @@ fun sw -> + let r, w = Eio_unix.pipe sw in + fn ~sw ~r ~w;; +val with_pipe_from_child : + (sw:Switch.t -> + r:< close : unit; probe : 'a. 'a Generic.ty -> 'a option; + read_into : Cstruct.t -> int; read_methods : Flow.read_method list; + unix_fd : [ `Peek | `Take ] -> Unix.file_descr > -> + w:< close : unit; copy : 'b. (#Flow.source as 'b) -> unit; + probe : 'a. 'a Generic.ty -> 'a option; + unix_fd : [ `Peek | `Take ] -> Unix.file_descr; + write : Cstruct.t list -> unit > -> + 'c) -> + 'c = +# let pread env = + with_pipe_from_child @@ fun ~sw ~r ~w -> + let t = + Eio.Process.spawn ~sw ~stdout:(w :> Flow.sink) ~stdin:env#stdin ~stderr:env#stderr env#process_mgr "/usr/bin/echo" [ "echo"; "Hello" ] + in + let status = 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_mgr : #Process.mgr; stderr : Flow.sink; stdin : Flow.source; .. > -> + string = +# run @@ fun _spawn env -> + pread env;; ++Exited 0 +- : string = "Hello\n" +``` + +Spawning subprocesses in new domains works normally + +```ocaml +# run @@ fun spawn env -> + let mgr = Eio.Stdenv.domain_mgr env in + Eio.Domain_manager.run mgr @@ fun () -> + Switch.run @@ fun sw -> + let t = spawn ~sw "/usr/bin/echo" [ "echo"; "Hello from another domain" ] in + Process.status t;; +Hello from another domain +- : Process.status = Eio.Process.Exited 0 +``` + +Calling `await_exit` multiple times on the same spawn just returns the status. + +```ocaml +# run @@ fun spawn env -> + Switch.run @@ fun sw -> + let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in + (Process.status t, Process.status t, Process.status t);; +hello world +- : Process.status * Process.status * Process.status = +(Eio.Process.Exited 0, Eio.Process.Exited 0, Eio.Process.Exited 0) +``` From d553598aeebf293c7ba8f884a17dc32eaeaafe2e Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 25 Mar 2023 22:38:33 +0000 Subject: [PATCH 03/11] Support all sources and sinks --- lib_eio/process.ml | 13 +++++++++-- lib_eio/process.mli | 12 +++++----- lib_eio_linux/eio_linux.ml | 47 ++++++++++++++++++++++++++++---------- lib_eio_posix/process.ml | 45 +++++++++++++++++++++++++++--------- tests/process.md | 19 ++++++++++++++- 5 files changed, 104 insertions(+), 32 deletions(-) diff --git a/lib_eio/process.ml b/lib_eio/process.ml index b25220835..ea21e536f 100644 --- a/lib_eio/process.ml +++ b/lib_eio/process.ml @@ -16,7 +16,16 @@ let status proc = proc#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 + method virtual spawn : 'a 'b 'c. + sw:Switch.t -> + ?cwd:Fs.dir Path.t -> + stdin:(#Flow.source as 'a) -> + stdout:(#Flow.sink as 'b) -> + stderr:(#Flow.sink as 'c) -> + string -> + string list -> + t end -let spawn ~sw t ?cwd ~stdin ~stdout ~stderr cmd args = t#spawn ~sw ?cwd ~stdin ~stdout ~stderr cmd args \ No newline at end of file +let spawn ~sw (t:#mgr) ?cwd ~(stdin:#Flow.source) ~(stdout:#Flow.sink) ~(stderr:#Flow.sink) cmd args = + t#spawn ~sw ?cwd ~stdin ~stdout ~stderr cmd args \ No newline at end of file diff --git a/lib_eio/process.mli b/lib_eio/process.mli index 44694918f..8113cc82b 100644 --- a/lib_eio/process.mli +++ b/lib_eio/process.mli @@ -19,19 +19,19 @@ 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 -> + method virtual spawn : 'a 'b 'c. + sw:Switch.t -> ?cwd:Fs.dir Path.t -> - stdin:Flow.source -> - stdout:Flow.sink -> - stderr:Flow.sink -> + stdin:(#Flow.source as 'a) -> + stdout:(#Flow.sink as 'b) -> + stderr:(#Flow.sink as 'c) -> 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 +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 stopped when the switch is released. diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index a6a6ebb56..ff79f7224 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -301,27 +301,50 @@ let process proc : Eio.Process.t = object method signal i = Process.signal proc i end -let pipe_or_fd flow = - match Eio.Generic.probe flow FD with - | None -> assert false - | Some fd -> FD.to_rcfd fd +let read_of_fd ~sw t = + match get_fd_opt t with + | None -> + let r, w = Low_level.pipe ~sw in + Some (flow w), r + | Some fd -> None, fd + +let write_of_fd ~sw t = + match get_fd_opt t with + | None -> + let r, w = Low_level.pipe ~sw in + Some (flow r), w + | Some fd -> None, fd let process_mgr = object - method spawn ~sw ?cwd ~stdin ~stdout ~stderr prog args = + inherit Eio.Process.mgr + + method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = let chdir = Option.to_list cwd |> List.map (fun (_, s) -> Process.Fork_action.chdir s) in - let stdin = pipe_or_fd stdin in - let stdout = pipe_or_fd stdout in - let stderr = pipe_or_fd stderr in + let stdin_w, stdin_fd = read_of_fd ~sw stdin in + let stdout_r, stdout_fd = write_of_fd ~sw stdout in + let stderr_r, stderr_fd = write_of_fd ~sw stderr in let actions = Process.Fork_action.[ Eio_unix.Private.Fork_action.inherit_fds [ - 0, stdin, `Blocking; - 1, stdout, `Blocking; - 2, stderr, `Blocking; + 0, Fd.to_rcfd stdin_fd, `Blocking; + 1, Fd.to_rcfd stdout_fd, `Blocking; + 2, Fd.to_rcfd stderr_fd, `Blocking; ]; execve prog ~argv:(Array.of_list args) ~env:[||] ] in let actions = chdir @ actions in - process (Process.spawn ~sw actions) + let proc = process (Process.spawn ~sw actions) in + Option.iter (fun stdin_w -> + Eio.Fiber.fork ~sw (fun () -> + Eio.Flow.copy stdin stdin_w; + Eio.Flow.close stdin_w + )) stdin_w; + Option.iter (fun stdout_r -> + Fd.close stdout_fd; + Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stdout_r stdout)) stdout_r; + Option.iter (fun stderr_r -> + Fd.close stderr_fd; + Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stderr_r stdout)) stderr_r; + proc end type stdenv = < diff --git a/lib_eio_posix/process.ml b/lib_eio_posix/process.ml index 075d68031..409eb09d9 100644 --- a/lib_eio_posix/process.ml +++ b/lib_eio_posix/process.ml @@ -14,25 +14,48 @@ let process proc : Eio.Process.t = object method signal i = Process.signal proc i end -let pipe_or_fd flow = +let read_of_fd ~sw flow = match Fd.get_fd_opt flow with - | None -> assert false - | Some fd -> Fd.to_rcfd fd + | None -> + let r, w = pipe ~sw in + Some (Flow.of_fd w), r + | Some fd -> None, fd + +let write_of_fd ~sw t = + match Fd.get_fd_opt t with + | None -> + let r, w = pipe ~sw in + Some (Flow.of_fd r), w + | Some fd -> None, fd let v = object - method spawn ~sw ?cwd ~stdin ~stdout ~stderr prog args = + inherit Eio.Process.mgr + + method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = let chdir = Option.to_list cwd |> List.map (fun (_, s) -> Process.Fork_action.chdir s) in - let stdin = pipe_or_fd stdin in - let stdout = pipe_or_fd stdout in - let stderr = pipe_or_fd stderr in + let stdin_w, stdin_fd = read_of_fd ~sw stdin in + let stdout_r, stdout_fd = write_of_fd ~sw stdout in + let stderr_r, stderr_fd = write_of_fd ~sw stderr in let actions = Process.Fork_action.[ Eio_unix.Private.Fork_action.inherit_fds [ - 0, stdin, `Blocking; - 1, stdout, `Blocking; - 2, stderr, `Blocking; + 0, Fd.to_rcfd stdin_fd, `Blocking; + 1, Fd.to_rcfd stdout_fd, `Blocking; + 2, Fd.to_rcfd stderr_fd, `Blocking; ]; execve prog ~argv:(Array.of_list args) ~env:[||] ] in let actions = chdir @ actions in - process (Process.spawn ~sw actions) + let proc = process (Process.spawn ~sw actions) in + Option.iter (fun stdin_w -> + Eio.Fiber.fork ~sw (fun () -> + Eio.Flow.copy stdin stdin_w; + Eio.Flow.close stdin_w + )) stdin_w; + Option.iter (fun stdout_r -> + Fd.close stdout_fd; + Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stdout_r stdout)) stdout_r; + Option.iter (fun stderr_r -> + Fd.close stderr_fd; + Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stderr_r stdout)) stderr_r; + proc end \ No newline at end of file diff --git a/tests/process.md b/tests/process.md index 775f19df4..548b81bd8 100644 --- a/tests/process.md +++ b/tests/process.md @@ -109,7 +109,8 @@ val with_pipe_from_child : Flow.copy r (Flow.buffer_sink buff); Buffer.contents buff;; val pread : - < process_mgr : #Process.mgr; stderr : Flow.sink; stdin : Flow.source; .. > -> + < process_mgr : #Process.mgr; stderr : #Flow.sink; stdin : #Flow.source; + .. > -> string = # run @@ fun _spawn env -> pread env;; @@ -141,3 +142,19 @@ hello world - : Process.status * Process.status * Process.status = (Eio.Process.Exited 0, Eio.Process.Exited 0, Eio.Process.Exited 0) ``` + +Using sources and sinks that are not backed by file descriptors. + +```ocaml +# run @@ fun _spawn env -> + let proc = env#process_mgr in + let buf = Buffer.create 16 in + let dst = Flow.buffer_sink buf in + Eio.Switch.run @@ fun sw -> + let p = + Eio.Process.spawn proc ~sw ~stdin:env#stdin ~stdout:dst ~stderr:env#stderr "/usr/bin/echo" [ "echo"; "Hello, world" ] + in + let _ : Process.status = Process.status p in + Buffer.contents buf +- : string = "Hello, world\n" +``` From bf2d18335c12652f2262e7e32c4c99b90c827498 Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 1 Apr 2023 13:26:48 +0100 Subject: [PATCH 04/11] Rename Process.status to Process.exit_status --- lib_eio/process.ml | 4 ++-- lib_eio/process.mli | 10 +++++----- lib_eio_linux/eio_linux.ml | 2 +- lib_eio_posix/process.ml | 2 +- tests/process.md | 16 ++++++++-------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib_eio/process.ml b/lib_eio/process.ml index ea21e536f..2c31a8cf9 100644 --- a/lib_eio/process.ml +++ b/lib_eio/process.ml @@ -7,12 +7,12 @@ let pp_status ppf = function class virtual t = object method virtual pid : int - method virtual status : status + method virtual exit_status : status method virtual signal : int -> unit end let pid proc = proc#pid -let status proc = proc#status +let exit_status proc = proc#exit_status let signal proc = proc#signal class virtual mgr = object diff --git a/lib_eio/process.mli b/lib_eio/process.mli index 8113cc82b..31e79be01 100644 --- a/lib_eio/process.mli +++ b/lib_eio/process.mli @@ -4,16 +4,16 @@ val pp_status : Format.formatter -> status -> unit class virtual t : object method virtual pid : int - method virtual status : status + method virtual exit_status : status method virtual signal : int -> unit end val pid : #t -> int (** The process ID *) -val status : #t -> status -(** Checks the status of the subprocess, this will block waiting for the subprocess - to terminate or be stopped by a signal. *) +val exit_status : #t -> status +(** Reports the exit status of the subprocess. This will block waiting for the subprocess + to exit. *) val signal : #t -> int -> unit (** [signal t i] sends the signal [i] to process [t]. *) @@ -33,7 +33,7 @@ end 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 stopped when the switch is released. + 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 [cwd] will optionally change the current working directory of the process.*) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index ff79f7224..3348f840a 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -295,7 +295,7 @@ let unix_status_to_stats = function let process proc : Eio.Process.t = object method pid = Process.pid proc - method status = + method exit_status = let status = Eio.Promise.await @@ Process.exit_status proc in unix_status_to_stats status method signal i = Process.signal proc i diff --git a/lib_eio_posix/process.ml b/lib_eio_posix/process.ml index 409eb09d9..bd974e447 100644 --- a/lib_eio_posix/process.ml +++ b/lib_eio_posix/process.ml @@ -8,7 +8,7 @@ let unix_status_to_stats = function let process proc : Eio.Process.t = object method pid = Process.pid proc - method status = + method exit_status = let status = Eio.Promise.await @@ Process.exit_status proc in unix_status_to_stats status method signal i = Process.signal proc i diff --git a/tests/process.md b/tests/process.md index 548b81bd8..083ee1599 100644 --- a/tests/process.md +++ b/tests/process.md @@ -24,7 +24,7 @@ Running a program as a subprocess # run @@ fun spawn env -> Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in - Process.status t;; + Process.exit_status t;; hello world - : Process.status = Eio.Process.Exited 0 ``` @@ -36,7 +36,7 @@ Stopping a subprocess works and checking the status waits and reports correctly Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ] in Process.signal t Sys.sigkill; - Process.status t;; + Process.exit_status t;; - : Process.status = Eio.Process.Signaled (-7) ``` @@ -51,7 +51,7 @@ A switch will stop a process when it is released. proc := Some (spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ]) in run (); - Process.status (Option.get !proc);; + Process.exit_status (Option.get !proc);; - : Process.status = Eio.Process.Signaled (-7) ``` @@ -67,7 +67,7 @@ Passing in flows allows you to redirect the child process' stdout. let stdout = (stdout :> Eio.Flow.sink) in Switch.run @@ fun sw -> let t = Eio.Process.spawn ~sw ~stdout ~stdin:env#stdin ~stderr:env#stderr process "/usr/bin/echo" [ "echo"; "Hello" ] in - Process.status t + Process.exit_status t in match run () with | Exited 0 -> @@ -102,7 +102,7 @@ val with_pipe_from_child : let t = Eio.Process.spawn ~sw ~stdout:(w :> Flow.sink) ~stdin:env#stdin ~stderr:env#stderr env#process_mgr "/usr/bin/echo" [ "echo"; "Hello" ] in - let status = Process.status t in + let status = Process.exit_status t in Eio.traceln "%a" Eio.Process.pp_status status; Flow.close w; let buff = Buffer.create 10 in @@ -126,7 +126,7 @@ Spawning subprocesses in new domains works normally Eio.Domain_manager.run mgr @@ fun () -> Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/echo" [ "echo"; "Hello from another domain" ] in - Process.status t;; + Process.exit_status t;; Hello from another domain - : Process.status = Eio.Process.Exited 0 ``` @@ -137,7 +137,7 @@ Calling `await_exit` multiple times on the same spawn just returns the status. # run @@ fun spawn env -> Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in - (Process.status t, Process.status t, Process.status t);; + (Process.exit_status t, Process.exit_status t, Process.exit_status t);; hello world - : Process.status * Process.status * Process.status = (Eio.Process.Exited 0, Eio.Process.Exited 0, Eio.Process.Exited 0) @@ -154,7 +154,7 @@ Using sources and sinks that are not backed by file descriptors. let p = Eio.Process.spawn proc ~sw ~stdin:env#stdin ~stdout:dst ~stderr:env#stderr "/usr/bin/echo" [ "echo"; "Hello, world" ] in - let _ : Process.status = Process.status p in + let _ : Process.status = Process.exit_status p in Buffer.contents buf - : string = "Hello, world\n" ``` From 37cc0226a69d2f3406c520d49a2dbeb07a2863a8 Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 1 Apr 2023 13:29:49 +0100 Subject: [PATCH 05/11] Simplify process test --- tests/process.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/process.md b/tests/process.md index 083ee1599..b6f33e2d0 100644 --- a/tests/process.md +++ b/tests/process.md @@ -70,11 +70,7 @@ Passing in flows allows you to redirect the child process' stdout. Process.exit_status t in match run () with - | Exited 0 -> - Eio.Path.(with_open_in (fs / filename)) @@ fun flow -> - let buff = Buffer.create 128 in - Eio.Flow.copy flow (Eio.Flow.buffer_sink buff); - Buffer.contents buff + | Exited 0 -> Eio.Path.(load (fs / filename)) | _ -> failwith "Subprocess didn't exit cleanly!";; - : string = "Hello\n" ``` From 9f2a5ed9e89739a243e0518817b16e560278d486 Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 1 Apr 2023 14:41:22 +0100 Subject: [PATCH 06/11] Use fchdir --- lib_eio_linux/eio_linux.ml | 20 ++++++++++++++++++-- lib_eio_posix/process.ml | 14 ++++++++++++-- tests/process.md | 12 ++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index 3348f840a..3ca6e06c7 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -319,7 +319,6 @@ let process_mgr = object inherit Eio.Process.mgr method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = - let chdir = Option.to_list cwd |> List.map (fun (_, s) -> Process.Fork_action.chdir s) in let stdin_w, stdin_fd = read_of_fd ~sw stdin in let stdout_r, stdout_fd = write_of_fd ~sw stdout in let stderr_r, stderr_fd = write_of_fd ~sw stderr in @@ -331,7 +330,24 @@ let process_mgr = object ]; execve prog ~argv:(Array.of_list args) ~env:[||] ] in - let actions = chdir @ actions in + let with_actions cwd fn = match cwd with + | Some (fd, s) -> ( + match get_dir_fd_opt fd with + | None -> fn actions + | Some dir_fd -> + let root = + Low_level.openat ~sw + ~seekable:false + ~access:`R + ~perm:0 + ~flags:Uring.Open_flags.(cloexec + path + directory) + dir_fd s + in + fn (Process.Fork_action.fchdir root :: actions) + ) + | None -> fn actions + in + with_actions cwd @@ fun actions -> let proc = process (Process.spawn ~sw actions) in Option.iter (fun stdin_w -> Eio.Fiber.fork ~sw (fun () -> diff --git a/lib_eio_posix/process.ml b/lib_eio_posix/process.ml index bd974e447..4ad952244 100644 --- a/lib_eio_posix/process.ml +++ b/lib_eio_posix/process.ml @@ -32,7 +32,6 @@ let v = object inherit Eio.Process.mgr method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = - let chdir = Option.to_list cwd |> List.map (fun (_, s) -> Process.Fork_action.chdir s) in let stdin_w, stdin_fd = read_of_fd ~sw stdin in let stdout_r, stdout_fd = write_of_fd ~sw stdout in let stderr_r, stderr_fd = write_of_fd ~sw stderr in @@ -44,7 +43,18 @@ let v = object ]; execve prog ~argv:(Array.of_list args) ~env:[||] ] in - let actions = chdir @ actions in + let with_actions cwd fn = match cwd with + | Some ((dir, path) : Eio.Fs.dir Eio.Path.t) -> ( + match Eio.Generic.probe dir Fs.Posix_dir with + | None -> fn actions + | Some posix -> + posix#with_parent_dir path @@ fun dirfd s -> + let cwd = Low_level.openat ?dirfd ~sw ~mode:0 s Low_level.Open_flags.(rdonly + directory) in + fn (Process.Fork_action.fchdir cwd :: actions) + ) + | None -> fn actions + in + with_actions cwd @@ fun actions -> let proc = process (Process.spawn ~sw actions) in Option.iter (fun stdin_w -> Eio.Fiber.fork ~sw (fun () -> diff --git a/tests/process.md b/tests/process.md index b6f33e2d0..53c5a4f43 100644 --- a/tests/process.md +++ b/tests/process.md @@ -154,3 +154,15 @@ Using sources and sinks that are not backed by file descriptors. Buffer.contents buf - : string = "Hello, world\n" ``` + +Changing directory + +```ocaml +# run @@ fun spawn env -> + Switch.run @@ fun sw -> + let root = Eio.Path.(env#fs / "/") in + let child = spawn ~cwd:root ~sw "/usr/bin/env" [ "env"; "pwd" ] in + Process.exit_status child +/ +- : Process.status = Eio.Process.Exited 0 +``` From 419c5fb67237d8a5fe21b9c73e9420b2c0095b9e Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 1 Apr 2023 14:45:42 +0100 Subject: [PATCH 07/11] Tidy process mgr class --- lib_eio/process.ml | 15 +++++++++------ lib_eio/process.mli | 8 ++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib_eio/process.ml b/lib_eio/process.ml index 2c31a8cf9..e1139d5ae 100644 --- a/lib_eio/process.ml +++ b/lib_eio/process.ml @@ -16,16 +16,19 @@ let exit_status proc = proc#exit_status let signal proc = proc#signal class virtual mgr = object - method virtual spawn : 'a 'b 'c. + method virtual spawn : sw:Switch.t -> ?cwd:Fs.dir Path.t -> - stdin:(#Flow.source as 'a) -> - stdout:(#Flow.sink as 'b) -> - stderr:(#Flow.sink as 'c) -> + stdin:Flow.source -> + stdout:Flow.sink -> + stderr:Flow.sink -> string -> string list -> t end -let spawn ~sw (t:#mgr) ?cwd ~(stdin:#Flow.source) ~(stdout:#Flow.sink) ~(stderr:#Flow.sink) cmd args = - t#spawn ~sw ?cwd ~stdin ~stdout ~stderr cmd args \ No newline at end of file +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) \ No newline at end of file diff --git a/lib_eio/process.mli b/lib_eio/process.mli index 31e79be01..7236d952b 100644 --- a/lib_eio/process.mli +++ b/lib_eio/process.mli @@ -19,12 +19,12 @@ val signal : #t -> int -> unit (** [signal t i] sends the signal [i] to process [t]. *) class virtual mgr : object - method virtual spawn : 'a 'b 'c. + method virtual spawn : sw:Switch.t -> ?cwd:Fs.dir Path.t -> - stdin:(#Flow.source as 'a) -> - stdout:(#Flow.sink as 'b) -> - stderr:(#Flow.sink as 'c) -> + stdin:Flow.source -> + stdout:Flow.sink -> + stderr:Flow.sink -> string -> string list -> t From f4024231a56ce5f2b38e0166498d1aff63e61acc Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 1 Apr 2023 15:01:39 +0100 Subject: [PATCH 08/11] Stub out processes on luv backend --- lib_eio_luv/eio_luv.ml | 2 ++ lib_eio_luv/eio_luv.mli | 1 + lib_main/dune | 6 +++--- lib_main/eio_main.ml | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib_eio_luv/eio_luv.ml b/lib_eio_luv/eio_luv.ml index 61de3dd68..a6ab3f71d 100644 --- a/lib_eio_luv/eio_luv.ml +++ b/lib_eio_luv/eio_luv.ml @@ -959,6 +959,7 @@ type stdenv = < stdout : sink; stderr : sink; net : Eio.Net.t; + process_mgr : Eio.Process.mgr; domain_mgr : Eio.Domain_manager.t; clock : Eio.Time.clock; mono_clock : Eio.Time.Mono.t; @@ -1160,6 +1161,7 @@ let stdenv ~run_event_loop = method stdout = Lazy.force stdout method stderr = Lazy.force stderr method net = net + method process_mgr = failwith "Processes are not supported using libuv" method domain_mgr = domain_mgr ~run_event_loop method clock = clock method mono_clock = mono_clock diff --git a/lib_eio_luv/eio_luv.mli b/lib_eio_luv/eio_luv.mli index 9de90c30a..6cee71a99 100644 --- a/lib_eio_luv/eio_luv.mli +++ b/lib_eio_luv/eio_luv.mli @@ -210,6 +210,7 @@ type stdenv = < stdout : sink; stderr : sink; net : Eio.Net.t; + process_mgr : Eio.Process.mgr; domain_mgr : Eio.Domain_manager.t; clock : Eio.Time.clock; mono_clock : Eio.Time.Mono.t; diff --git a/lib_main/dune b/lib_main/dune index 0a1242fda..a2e412ef0 100644 --- a/lib_main/dune +++ b/lib_main/dune @@ -8,7 +8,7 @@ (select posix_backend.ml from (eio_posix -> posix_backend.enabled.ml) ( -> posix_backend.disabled.ml)) -; (select luv_backend.ml from -; (eio_luv -> luv_backend.enabled.ml) -; ( -> luv_backend.disabled.ml)) + (select luv_backend.ml from + (eio_luv -> luv_backend.enabled.ml) + ( -> luv_backend.disabled.ml)) )) diff --git a/lib_main/eio_main.ml b/lib_main/eio_main.ml index c0393dd0a..89a1ff09c 100644 --- a/lib_main/eio_main.ml +++ b/lib_main/eio_main.ml @@ -4,12 +4,12 @@ let force run fn = let run fn = match Sys.getenv_opt "EIO_BACKEND" with | Some ("io-uring" | "linux") -> force Linux_backend.run fn - | None | Some "posix" -> force Posix_backend.run fn - (* | Some "luv" -> force Luv_backend.run fn + | Some "posix" -> force Posix_backend.run fn + | Some "luv" -> force Luv_backend.run fn | None | Some "" -> Linux_backend.run fn ~fallback:(fun _ -> Posix_backend.run fn ~fallback:(fun _ -> force Luv_backend.run fn ) - ) *) + ) | Some x -> Fmt.failwith "Unknown Eio backend %S (from $EIO_BACKEND)" x From 5afdbd4ac8c41972204fbc77b98a784787d9a3dc Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sat, 1 Apr 2023 21:54:44 +0100 Subject: [PATCH 09/11] Rename exit_status to await and await_exn and add docs --- README.md | 32 ++++++++++++++++++++++++++++++++ doc/prelude.ml | 14 ++++++++------ lib_eio/eio.ml | 1 + lib_eio/eio.mli | 3 +++ lib_eio/process.ml | 23 +++++++++++++++++++++-- lib_eio/process.mli | 15 +++++++++++---- lib_eio_linux/eio_linux.ml | 2 +- lib_eio_posix/process.ml | 2 +- tests/process.md | 18 +++++++++--------- 9 files changed, 87 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 065ff04bc..613aa81c5 100644 --- a/README.md +++ b/README.md @@ -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,36 @@ 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. + +```ocaml +# Eio_main.run @@ fun env -> + let proc_mgr = Eio.Stdenv.process_mgr env in + let stdin, stdout, stderr = Eio.Stdenv.stdio env in + Eio.Switch.run @@ fun sw -> + let child = Eio.Process.spawn proc_mgr ~sw ~stdin ~stdout ~stderr "/usr/bin/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 + Eio.Switch.run @@ fun sw -> + let child = Eio.Process.spawn proc_mgr ~sw ~stdin ~stdout ~stderr "/usr/bin/echo" [ "echo"; "hello" ] in + Eio.Process.await_exn child; + Buffer.contents buffer;; +- : string = "hello\n" +``` + ## Time The standard environment provides a [clock][Eio.Time] with the usual POSIX time: @@ -1743,3 +1774,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 diff --git a/doc/prelude.ml b/doc/prelude.ml index 602d196de..7f39e3d38 100644 --- a/doc/prelude.ml +++ b/doc/prelude.ml @@ -32,12 +32,14 @@ module Eio_main = struct let run fn = Eio_main.run @@ fun env -> fn @@ object - method net = env#net - method stdin = env#stdin - method stdout = env#stdout - method cwd = env#cwd - method domain_mgr = fake_domain_mgr - method clock = fake_clock env#clock + method net = env#net + method stdin = env#stdin + method stdout = env#stdout + method stderr = env#stderr + method cwd = env#cwd + method process_mgr = env#process_mgr + method domain_mgr = fake_domain_mgr + method clock = fake_clock env#clock end end diff --git a/lib_eio/eio.ml b/lib_eio/eio.ml index 5bb3c9dfa..122c8a394 100644 --- a/lib_eio/eio.ml +++ b/lib_eio/eio.ml @@ -49,6 +49,7 @@ module Stdenv = struct let stdin (t : ) = t#stdin let stdout (t : ) = t#stdout let stderr (t : ) = t#stderr + let stdio (t : ) = t#stdin, t#stdout, t#stderr let net (t : ) = t#net let process_mgr (t : ) = t#process_mgr let domain_mgr (t : ) = t#domain_mgr diff --git a/lib_eio/eio.mli b/lib_eio/eio.mli index c832ae0a3..7ad7b7d84 100644 --- a/lib_eio/eio.mli +++ b/lib_eio/eio.mli @@ -201,6 +201,9 @@ module Stdenv : sig val stdout : -> 'a val stderr : -> 'a + val stdio : -> 'a * 'b * 'c + (** [stdio t] returns [stdin, stdout, stderr]. *) + (** {1 File-system access} To use these, see {!Path}. *) diff --git a/lib_eio/process.ml b/lib_eio/process.ml index e1139d5ae..e794f39c3 100644 --- a/lib_eio/process.ml +++ b/lib_eio/process.ml @@ -5,14 +5,33 @@ let pp_status ppf = function | Signaled i -> Format.fprintf ppf "Signalled %i" i | Stopped i -> Format.fprintf ppf "Stopped %i" i +type Exn.err += E of status + +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 exit_status : status + method virtual await : status method virtual signal : int -> unit end let pid proc = proc#pid -let exit_status proc = proc#exit_status +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 diff --git a/lib_eio/process.mli b/lib_eio/process.mli index 7236d952b..6440ce1dd 100644 --- a/lib_eio/process.mli +++ b/lib_eio/process.mli @@ -2,18 +2,25 @@ 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 exit_status : status + method virtual await : status method virtual signal : int -> unit end val pid : #t -> int (** The process ID *) -val exit_status : #t -> status -(** Reports the exit status of the subprocess. This will block waiting for the subprocess - to exit. *) +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]. *) val signal : #t -> int -> unit (** [signal t i] sends the signal [i] to process [t]. *) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index 3ca6e06c7..af272a6c4 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -295,7 +295,7 @@ let unix_status_to_stats = function let process proc : Eio.Process.t = object method pid = Process.pid proc - method exit_status = + method await = let status = Eio.Promise.await @@ Process.exit_status proc in unix_status_to_stats status method signal i = Process.signal proc i diff --git a/lib_eio_posix/process.ml b/lib_eio_posix/process.ml index 4ad952244..af06403eb 100644 --- a/lib_eio_posix/process.ml +++ b/lib_eio_posix/process.ml @@ -8,7 +8,7 @@ let unix_status_to_stats = function let process proc : Eio.Process.t = object method pid = Process.pid proc - method exit_status = + method await = let status = Eio.Promise.await @@ Process.exit_status proc in unix_status_to_stats status method signal i = Process.signal proc i diff --git a/tests/process.md b/tests/process.md index 53c5a4f43..924366554 100644 --- a/tests/process.md +++ b/tests/process.md @@ -24,7 +24,7 @@ Running a program as a subprocess # run @@ fun spawn env -> Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in - Process.exit_status t;; + Process.await t;; hello world - : Process.status = Eio.Process.Exited 0 ``` @@ -36,7 +36,7 @@ Stopping a subprocess works and checking the status waits and reports correctly Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ] in Process.signal t Sys.sigkill; - Process.exit_status t;; + Process.await t;; - : Process.status = Eio.Process.Signaled (-7) ``` @@ -51,7 +51,7 @@ A switch will stop a process when it is released. proc := Some (spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ]) in run (); - Process.exit_status (Option.get !proc);; + Process.await (Option.get !proc);; - : Process.status = Eio.Process.Signaled (-7) ``` @@ -67,7 +67,7 @@ Passing in flows allows you to redirect the child process' stdout. let stdout = (stdout :> Eio.Flow.sink) in Switch.run @@ fun sw -> let t = Eio.Process.spawn ~sw ~stdout ~stdin:env#stdin ~stderr:env#stderr process "/usr/bin/echo" [ "echo"; "Hello" ] in - Process.exit_status t + Process.await t in match run () with | Exited 0 -> Eio.Path.(load (fs / filename)) @@ -98,7 +98,7 @@ val with_pipe_from_child : let t = Eio.Process.spawn ~sw ~stdout:(w :> Flow.sink) ~stdin:env#stdin ~stderr:env#stderr env#process_mgr "/usr/bin/echo" [ "echo"; "Hello" ] in - let status = Process.exit_status t in + let status = Process.await t in Eio.traceln "%a" Eio.Process.pp_status status; Flow.close w; let buff = Buffer.create 10 in @@ -122,7 +122,7 @@ Spawning subprocesses in new domains works normally Eio.Domain_manager.run mgr @@ fun () -> Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/echo" [ "echo"; "Hello from another domain" ] in - Process.exit_status t;; + Process.await t;; Hello from another domain - : Process.status = Eio.Process.Exited 0 ``` @@ -133,7 +133,7 @@ Calling `await_exit` multiple times on the same spawn just returns the status. # run @@ fun spawn env -> Switch.run @@ fun sw -> let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in - (Process.exit_status t, Process.exit_status t, Process.exit_status t);; + (Process.await t, Process.await t, Process.await t);; hello world - : Process.status * Process.status * Process.status = (Eio.Process.Exited 0, Eio.Process.Exited 0, Eio.Process.Exited 0) @@ -150,7 +150,7 @@ Using sources and sinks that are not backed by file descriptors. let p = Eio.Process.spawn proc ~sw ~stdin:env#stdin ~stdout:dst ~stderr:env#stderr "/usr/bin/echo" [ "echo"; "Hello, world" ] in - let _ : Process.status = Process.exit_status p in + let _ : Process.status = Process.await p in Buffer.contents buf - : string = "Hello, world\n" ``` @@ -162,7 +162,7 @@ Changing directory Switch.run @@ fun sw -> let root = Eio.Path.(env#fs / "/") in let child = spawn ~cwd:root ~sw "/usr/bin/env" [ "env"; "pwd" ] in - Process.exit_status child + Process.await child / - : Process.status = Eio.Process.Exited 0 ``` From dc12c8e46e7541290bdb884fedf60483769f7a15 Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sun, 2 Apr 2023 11:43:06 +0100 Subject: [PATCH 10/11] Add Eio_unix.resolve_program --- README.md | 14 +++++++++++--- lib_eio/unix/eio_unix.ml | 15 ++++++++++++++- lib_eio/unix/eio_unix.mli | 8 ++++++-- tests/process.md | 35 ++++++++++++++++++++++++----------- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 613aa81c5..c49ff8ce5 100644 --- a/README.md +++ b/README.md @@ -894,14 +894,19 @@ so be careful if symlinks out of the subtree may be created while the program is ## Subprocesses -Spawning subprocesses is provided by the [Eio.Process][] module. +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 "/usr/bin/echo" [ "echo"; "hello" ] in + 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 @@ -915,8 +920,11 @@ If you want to capture the output of a process, you can provide a suitable `Eio. 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 "/usr/bin/echo" [ "echo"; "hello" ] in + let child = Eio.Process.spawn proc_mgr ~sw ~stdin ~stdout ~stderr echo [ "echo"; "hello" ] in Eio.Process.await_exn child; Buffer.contents buffer;; - : string = "hello\n" diff --git a/lib_eio/unix/eio_unix.ml b/lib_eio/unix/eio_unix.ml index a078505e3..fb7e45159 100644 --- a/lib_eio/unix/eio_unix.ml +++ b/lib_eio/unix/eio_unix.ml @@ -18,7 +18,7 @@ type socket = < module Private = struct type _ Eio.Generic.ty += Unix_file_descr : [`Peek | `Take] -> Unix.file_descr Eio.Generic.ty - type _ Effect.t += + type _ Effect.t += | Await_readable : Unix.file_descr -> unit Effect.t | Await_writable : Unix.file_descr -> unit Effect.t | Get_monotonic_clock : Eio.Time.Mono.t Effect.t @@ -79,3 +79,16 @@ let getnameinfo (sockaddr : Eio.Net.Sockaddr.t) = run_in_systhread (fun () -> let Unix.{ni_hostname; ni_service} = Unix.getnameinfo sockaddr options in (ni_hostname, ni_service)) + +let resolve_program ~paths prog = + if not (Filename.is_relative prog) then begin + if Sys.file_exists prog then Some prog else None + end + else + let rec loop = function + | path :: rest -> + let p = Filename.concat path prog in + if Sys.file_exists p then Some p else loop rest + | [] -> None + in + loop paths \ No newline at end of file diff --git a/lib_eio/unix/eio_unix.mli b/lib_eio/unix/eio_unix.mli index 491d33549..97c2e149b 100644 --- a/lib_eio/unix/eio_unix.mli +++ b/lib_eio/unix/eio_unix.mli @@ -90,7 +90,7 @@ module Private : sig type _ Eio.Generic.ty += Unix_file_descr : [`Peek | `Take] -> Unix.file_descr Eio.Generic.ty (** See {!FD}. *) - type _ Effect.t += + type _ Effect.t += | Await_readable : Unix.file_descr -> unit Effect.t (** See {!await_readable} *) | Await_writable : Unix.file_descr -> unit Effect.t (** See {!await_writable} *) | Get_monotonic_clock : Eio.Time.Mono.t Effect.t @@ -98,7 +98,7 @@ module Private : sig socket Effect.t (** See {!FD.as_socket} *) | Socketpair : Eio.Switch.t * Unix.socket_domain * Unix.socket_type * int -> (socket * socket) Effect.t (** See {!socketpair} *) - | Pipe : Eio.Switch.t -> + | Pipe : Eio.Switch.t -> ( * ) Effect.t (** See {!pipe} *) module Rcfd = Rcfd @@ -110,3 +110,7 @@ module Ctf = Ctf_unix val getnameinfo : Eio.Net.Sockaddr.t -> (string * string) (** [getnameinfo sockaddr] returns domain name and service for [sockaddr]. *) + +val resolve_program : paths:string list -> string -> string option +(** [resolve_program ~paths prog] tries to resolve the absolute path for [prog] + by looking in each of [paths]. *) \ No newline at end of file diff --git a/tests/process.md b/tests/process.md index 924366554..b7245aa2c 100644 --- a/tests/process.md +++ b/tests/process.md @@ -10,7 +10,15 @@ Creating some useful helper functions open Eio open Eio.Std +let paths = [ "/usr/bin"; "/bin"; "/usr/local/bin" ] + +let resolve_program cmd = + match Eio_unix.resolve_program ~paths cmd with + | Some cmd -> cmd + | None -> failwith "Failed to resolve program" + let spawn ~env ~sw ?cwd cmd args = + let cmd = resolve_program cmd in Process.spawn ~sw ?cwd ~stdout:env#stdout ~stdin:env#stdin ~stderr:env#stderr env#process_mgr cmd args let run fn = @@ -23,7 +31,7 @@ Running a program as a subprocess ```ocaml # run @@ fun spawn env -> Switch.run @@ fun sw -> - let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in + let t = spawn ~sw "echo" [ "echo"; "hello world" ] in Process.await t;; hello world - : Process.status = Eio.Process.Exited 0 @@ -34,7 +42,7 @@ Stopping a subprocess works and checking the status waits and reports correctly ```ocaml # run @@ fun spawn _env -> Switch.run @@ fun sw -> - let t = spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ] in + let t = spawn ~sw "sleep" [ "sleep"; "10" ] in Process.signal t Sys.sigkill; Process.await t;; - : Process.status = Eio.Process.Signaled (-7) @@ -45,10 +53,10 @@ A switch will stop a process when it is released. ```ocaml # run @@ fun spawn env -> - let proc = ref None in + let proc = ref None in let run () = Switch.run @@ fun sw -> - proc := Some (spawn ~sw "/usr/bin/sleep" [ "sleep"; "10" ]) + proc := Some (spawn ~sw "sleep" [ "sleep"; "10" ]) in run (); Process.await (Option.get !proc);; @@ -66,7 +74,8 @@ Passing in flows allows you to redirect the child process' stdout. 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 ~stdin:env#stdin ~stderr:env#stderr process "/usr/bin/echo" [ "echo"; "Hello" ] in + let echo = resolve_program "echo" in + let t = Eio.Process.spawn ~sw ~stdout ~stdin:env#stdin ~stderr:env#stderr process echo [ "echo"; "Hello" ] in Process.await t in match run () with @@ -95,8 +104,9 @@ val with_pipe_from_child : 'c = # let pread env = with_pipe_from_child @@ fun ~sw ~r ~w -> + let echo = resolve_program "echo" in let t = - Eio.Process.spawn ~sw ~stdout:(w :> Flow.sink) ~stdin:env#stdin ~stderr:env#stderr env#process_mgr "/usr/bin/echo" [ "echo"; "Hello" ] + Eio.Process.spawn ~sw ~stdout:(w :> Flow.sink) ~stdin:env#stdin ~stderr:env#stderr env#process_mgr echo [ "echo"; "Hello" ] in let status = Process.await t in Eio.traceln "%a" Eio.Process.pp_status status; @@ -121,7 +131,8 @@ Spawning subprocesses in new domains works normally let mgr = Eio.Stdenv.domain_mgr env in Eio.Domain_manager.run mgr @@ fun () -> Switch.run @@ fun sw -> - let t = spawn ~sw "/usr/bin/echo" [ "echo"; "Hello from another domain" ] in + let echo = resolve_program "echo" in + let t = spawn ~sw echo [ "echo"; "Hello from another domain" ] in Process.await t;; Hello from another domain - : Process.status = Eio.Process.Exited 0 @@ -132,7 +143,8 @@ Calling `await_exit` multiple times on the same spawn just returns the status. ```ocaml # run @@ fun spawn env -> Switch.run @@ fun sw -> - let t = spawn ~sw "/usr/bin/echo" [ "echo"; "hello world" ] in + let echo = resolve_program "echo" in + let t = spawn ~sw echo [ "echo"; "hello world" ] in (Process.await t, Process.await t, Process.await t);; hello world - : Process.status * Process.status * Process.status = @@ -147,8 +159,9 @@ Using sources and sinks that are not backed by file descriptors. let buf = Buffer.create 16 in let dst = Flow.buffer_sink buf in Eio.Switch.run @@ fun sw -> - let p = - Eio.Process.spawn proc ~sw ~stdin:env#stdin ~stdout:dst ~stderr:env#stderr "/usr/bin/echo" [ "echo"; "Hello, world" ] + let echo = resolve_program "echo" in + let p = + Eio.Process.spawn proc ~sw ~stdin:env#stdin ~stdout:dst ~stderr:env#stderr echo [ "echo"; "Hello, world" ] in let _ : Process.status = Process.await p in Buffer.contents buf @@ -161,7 +174,7 @@ Changing directory # run @@ fun spawn env -> Switch.run @@ fun sw -> let root = Eio.Path.(env#fs / "/") in - let child = spawn ~cwd:root ~sw "/usr/bin/env" [ "env"; "pwd" ] in + let child = spawn ~cwd:root ~sw "env" [ "env"; "pwd" ] in Process.await child / - : Process.status = Eio.Process.Exited 0 From d6eec9f235431fb9c8dcd2207c0b56d95b85392f Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Sun, 2 Apr 2023 12:36:44 +0100 Subject: [PATCH 11/11] Don't fork for copying --- lib_eio_linux/eio_linux.ml | 19 +++++++++---------- lib_eio_posix/process.ml | 21 +++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index af272a6c4..61159897b 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -295,7 +295,7 @@ let unix_status_to_stats = function let process proc : Eio.Process.t = object method pid = Process.pid proc - method await = + method await = let status = Eio.Promise.await @@ Process.exit_status proc in unix_status_to_stats status method signal i = Process.signal proc i @@ -317,8 +317,8 @@ let write_of_fd ~sw t = let process_mgr = object inherit Eio.Process.mgr - - method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = + + method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = let stdin_w, stdin_fd = read_of_fd ~sw stdin in let stdout_r, stdout_fd = write_of_fd ~sw stdout in let stderr_r, stderr_fd = write_of_fd ~sw stderr in @@ -347,19 +347,18 @@ let process_mgr = object ) | None -> fn actions in - with_actions cwd @@ fun actions -> + with_actions cwd @@ fun actions -> let proc = process (Process.spawn ~sw actions) in Option.iter (fun stdin_w -> - Eio.Fiber.fork ~sw (fun () -> - Eio.Flow.copy stdin stdin_w; - Eio.Flow.close stdin_w - )) stdin_w; + Eio.Flow.copy stdin stdin_w; + Eio.Flow.close stdin_w + ) stdin_w; Option.iter (fun stdout_r -> Fd.close stdout_fd; - Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stdout_r stdout)) stdout_r; + Eio.Flow.copy stdout_r stdout) stdout_r; Option.iter (fun stderr_r -> Fd.close stderr_fd; - Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stderr_r stdout)) stderr_r; + Eio.Flow.copy stderr_r stdout) stderr_r; proc end diff --git a/lib_eio_posix/process.ml b/lib_eio_posix/process.ml index af06403eb..d42b97935 100644 --- a/lib_eio_posix/process.ml +++ b/lib_eio_posix/process.ml @@ -8,7 +8,7 @@ let unix_status_to_stats = function let process proc : Eio.Process.t = object method pid = Process.pid proc - method await = + method await = let status = Eio.Promise.await @@ Process.exit_status proc in unix_status_to_stats status method signal i = Process.signal proc i @@ -31,7 +31,7 @@ let write_of_fd ~sw t = let v = object inherit Eio.Process.mgr - method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = + method spawn ~sw ?cwd ~(stdin : #Eio.Flow.source) ~(stdout : #Eio.Flow.sink) ~(stderr : #Eio.Flow.sink) prog args = let stdin_w, stdin_fd = read_of_fd ~sw stdin in let stdout_r, stdout_fd = write_of_fd ~sw stdout in let stderr_r, stderr_fd = write_of_fd ~sw stderr in @@ -54,18 +54,19 @@ let v = object ) | None -> fn actions in - with_actions cwd @@ fun actions -> - let proc = process (Process.spawn ~sw actions) in + let proc = + with_actions cwd @@ fun actions -> + process (Process.spawn ~sw actions) + in Option.iter (fun stdin_w -> - Eio.Fiber.fork ~sw (fun () -> - Eio.Flow.copy stdin stdin_w; - Eio.Flow.close stdin_w - )) stdin_w; + Eio.Flow.copy stdin stdin_w; + Eio.Flow.close stdin_w + ) stdin_w; Option.iter (fun stdout_r -> Fd.close stdout_fd; - Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stdout_r stdout)) stdout_r; + Eio.Flow.copy stdout_r stdout) stdout_r; Option.iter (fun stderr_r -> Fd.close stderr_fd; - Eio.Fiber.fork ~sw (fun () -> Eio.Flow.copy stderr_r stdout)) stderr_r; + Eio.Flow.copy stderr_r stdout) stderr_r; proc end \ No newline at end of file