Skip to content

Commit de45321

Browse files
patricoferristalex5
authored andcommitted
Add low-level process support to eio_luv
1 parent f30025c commit de45321

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed

lib_eio_luv/eio_luv.ml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,51 @@ module Low_level = struct
574574
Handle.of_luv ~sw sock
575575
end
576576

577+
module Pipe = struct
578+
type t = [`Stream of [`Pipe]] Handle.t
579+
580+
let init ?for_handle_passing ~sw () =
581+
Luv.Pipe.init ~loop:(get_loop ()) ?for_handle_passing ()
582+
|> or_raise
583+
|> Handle.of_luv ~close_unix:true ~sw
584+
end
585+
586+
module Process = struct
587+
type t = {
588+
proc : Luv.Process.t;
589+
status : (int * int64) Promise.t;
590+
}
591+
592+
let pid t = Luv.Process.pid t.proc
593+
594+
let to_parent_pipe ?readable_in_child ?writable_in_child ?overlapped ~fd ~parent_pipe () =
595+
let parent_pipe = Handle.to_luv parent_pipe in
596+
Luv.Process.to_parent_pipe ?readable_in_child ?writable_in_child ?overlapped ~fd ~parent_pipe ()
597+
598+
let await_exit t = Promise.await t.status
599+
600+
let has_exited t = Promise.is_resolved t.status
601+
602+
let send_signal t i = Luv.Process.kill t.proc i |> or_raise
603+
604+
let spawn ?cwd ?env ?uid ?gid ?(redirect=[]) ~sw cmd args =
605+
let status, set_status = Promise.create () in
606+
let hook = ref None in
607+
let on_exit _ ~exit_status ~term_signal =
608+
Option.iter Switch.remove_hook !hook;
609+
Promise.resolve set_status (term_signal, exit_status)
610+
in
611+
let proc = Luv.Process.spawn ?environment:env ?uid ?gid ~loop:(get_loop ()) ?working_directory:cwd ~redirect ~on_exit cmd args |> or_raise in
612+
if not (Promise.is_resolved status) then (
613+
let h = Switch.on_release_cancellable sw (fun () ->
614+
Luv.Process.kill proc Luv.Signal.sigkill |> or_raise;
615+
ignore (Promise.await status)
616+
) in
617+
hook := Some h
618+
);
619+
{ proc; status }
620+
end
621+
577622
module Poll = Poll
578623

579624
let sleep_ms delay =

lib_eio_luv/eio_luv.mli

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,58 @@ module Low_level : sig
143143
val write : [ `Stream of [< `Pipe | `TCP | `TTY ] ] Handle.t -> Luv.Buffer.t list -> unit
144144
(** [write handle bufs] writes the contents of [bufs] to [handle]. *)
145145
end
146+
147+
module Pipe : sig
148+
type t = [`Pipe] Stream.t
149+
(** A pipe *)
150+
151+
val init : ?for_handle_passing:bool -> sw:Switch.t -> unit -> t
152+
(** Wraps {!Luv.Pipe.init}*)
153+
end
154+
155+
module Process : sig
156+
type t
157+
(** A process *)
158+
159+
val pid : t -> int
160+
(** [pid t] returns the process id of [t]. *)
161+
162+
val to_parent_pipe :
163+
?readable_in_child:bool ->
164+
?writable_in_child:bool ->
165+
?overlapped:bool ->
166+
fd:int ->
167+
parent_pipe:Pipe.t ->
168+
unit ->
169+
Luv.Process.redirection
170+
(** Wraps {!Luv.Process.to_parent_pipe}*)
171+
172+
val await_exit : t -> int * int64
173+
(** [await_exit t] waits for the process [t] to finish.
174+
175+
It returns [(exit_status, term_signal)], see {!Luv.Process.spawn} for
176+
more details on these values. *)
177+
178+
val has_exited : t -> bool
179+
(** [has_exited t] checks if the process [t] has exited or not. *)
180+
181+
val send_signal : t -> int -> unit
182+
(** A wrapper for {!Luv.Process.kill}. *)
183+
184+
val spawn :
185+
?cwd:string ->
186+
?env:(string * string) list ->
187+
?uid:int ->
188+
?gid:int ->
189+
?redirect:Luv.Process.redirection list ->
190+
sw:Switch.t ->
191+
string ->
192+
string list -> t
193+
(** Wraps {!Luv.Process.spawn}.
194+
195+
The process will be stopped when the switch is released if
196+
it has not already exited.*)
197+
end
146198
end
147199

148200
(** {1 Eio API} *)

lib_eio_luv/tests/process.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Set up the test environment
2+
3+
```ocaml
4+
# #require "eio_luv";;
5+
# open Eio.Std;;
6+
# open Eio;;
7+
# module Process = Eio_luv.Low_level.Process;;
8+
module Process = Eio_luv.Low_level.Process
9+
```
10+
11+
A helper function for reading all of the bytes from a handle.
12+
13+
```ocaml
14+
let read_all handle buf =
15+
let rec read acc =
16+
match Eio_luv.Low_level.Stream.read_into handle buf with
17+
| i -> read (acc + i)
18+
| exception End_of_file -> acc
19+
in read 0
20+
```
21+
22+
A simple `echo hello` process redirects to stdout.
23+
24+
```ocaml
25+
# Eio_luv.run @@ fun _env ->
26+
Switch.run @@ fun sw ->
27+
let redirect = Luv.Process.[
28+
inherit_fd ~fd:stdout ~from_parent_fd:stdout ()
29+
] in
30+
let t = Process.spawn ~sw ~redirect "echo" [ "echo"; "hello" ] in
31+
Process.await_exit t;;
32+
hello
33+
- : int * int64 = (0, 0L)
34+
```
35+
36+
Using a pipe to redirect output to a buffer.
37+
38+
```ocaml
39+
# Eio_luv.run @@ fun _env ->
40+
Switch.run @@ fun sw ->
41+
let parent_pipe = Eio_luv.Low_level.Pipe.init ~sw () in
42+
let buf = Luv.Buffer.create 32 in
43+
let redirect = Eio_luv.Low_level.Process.[
44+
to_parent_pipe ~fd:Luv.Process.stdout ~parent_pipe ()
45+
] in
46+
let t = Process.spawn ~sw ~redirect "echo" [ "echo"; "Hello,"; "World!" ] in
47+
let read = read_all parent_pipe buf in
48+
let _ = Process.await_exit t in
49+
Luv.Buffer.to_string (Luv.Buffer.sub buf ~offset:0 ~length:read);;
50+
- : string = "Hello, World!\n"
51+
```
52+
53+
Writing to stdin of a process works.
54+
55+
```ocaml
56+
# Eio_luv.run @@ fun _env ->
57+
Switch.run @@ fun sw ->
58+
let parent_pipe = Eio_luv.Low_level.Pipe.init ~sw () in
59+
let bufs = [ Luv.Buffer.from_string "Hello!" ] in
60+
let redirect = Luv.Process.[
61+
inherit_fd ~fd:stdout ~from_parent_fd:stdout ();
62+
Process.to_parent_pipe ~fd:stdin ~parent_pipe ()
63+
] in
64+
let t = Process.spawn ~sw ~redirect "head" [ "head" ] in
65+
Eio_luv.Low_level.Stream.write parent_pipe bufs;
66+
Eio_luv.Low_level.Handle.close parent_pipe;
67+
Process.await_exit t;;
68+
Hello!
69+
- : int * int64 = (0, 0L)
70+
```
71+
72+
Stopping a process works.
73+
74+
```ocaml
75+
# Eio_luv.run @@ fun _env ->
76+
Switch.run @@ fun sw ->
77+
let redirect = Luv.Process.[
78+
inherit_fd ~fd:stdout ~from_parent_fd:stdout ()
79+
] in
80+
let t = Process.spawn ~sw ~redirect "sleep" [ "sleep"; "10" ] in
81+
Process.send_signal t Luv.Signal.sigkill;
82+
Process.await_exit t;;
83+
- : int * int64 = (9, 0L)
84+
```
85+
86+
Forgetting to wait for a process to finish stops the process.
87+
88+
```ocaml
89+
# Eio_luv.run @@ fun _env ->
90+
let proc =
91+
Switch.run @@ fun sw ->
92+
let redirect = Luv.Process.[
93+
inherit_fd ~fd:stdout ~from_parent_fd:stdout ()
94+
] in
95+
Process.spawn ~sw ~redirect "sleep" [ "sleep"; "10" ]
96+
in
97+
Process.await_exit proc;;
98+
- : int * int64 = (9, 0L)
99+
```
100+
101+
Stopping a process interacts nicely with switches.
102+
103+
```ocaml
104+
# Eio_luv.run @@ fun _env ->
105+
let proc =
106+
Switch.run @@ fun sw ->
107+
let redirect = Luv.Process.[
108+
inherit_fd ~fd:stdout ~from_parent_fd:stdout ()
109+
] in
110+
let t = Process.spawn ~sw ~redirect "sleep" [ "sleep"; "10" ] in
111+
Process.send_signal t Luv.Signal.sigkill;
112+
t
113+
in
114+
Process.await_exit proc;;
115+
- : int * int64 = (9, 0L)
116+
```

0 commit comments

Comments
 (0)