···2121 stdout = None;
2222 async_switch;
2323 background_jobs = [];
2424+ last_background_process = "";
2425 }
2526 in
2627 match (file, command) with
+26-16
src/lib/eval.ml
···3737 options : Built_ins.Options.t;
3838 stdout : Eio_unix.sink_ty Eio.Flow.sink option;
3939 background_jobs : J.t list;
4040+ last_background_process : string;
4041 async_switch : Eio.Switch.t;
4142 }
4243···8990 | [] -> []
9091 | Ast.WordVariable v :: rest -> (
9192 match v with
9393+ | Ast.VariableAtom ("!", NoAttribute) ->
9494+ Ast.WordName ctx.last_background_process :: expand rest
9295 | Ast.VariableAtom (s, NoAttribute) -> (
9396 match S.lookup ctx.state ~param:s with
9497 | None -> Ast.WordName "" :: expand rest
···206209 |> List.append extra
207210208211 let rec handle_pipeline ~async initial_ctx pipeline_switch p : ctx Exit.t =
209209- let pipeline_switch =
210210- if async then initial_ctx.async_switch else pipeline_switch
212212+ let mode = if async then Types.Async else Types.Switched pipeline_switch in
213213+ let set_last_background ~async process ctx =
214214+ if async then
215215+ { ctx with last_background_process = string_of_int (E.pid process) }
216216+ else ctx
217217+ in
218218+ let on_process ~async process ctx =
219219+ clear_local_state ctx |> set_last_background ~async process
211220 in
212221 let handle_job ~pgid j p =
213222 match (j, p) with
···254263 in
255264 let ctx, job =
256265 let process =
257257- E.exec ctx.executor ?stdout:some_write ~pgid
258258- ~sw:pipeline_switch ~cwd:(cwd_of_ctx ctx)
266266+ E.exec ctx.executor ?stdout:some_write ~pgid ~mode
267267+ ~cwd:(cwd_of_ctx ctx)
259268 ~env:(get_env ~extra:ctx.local_state ())
260269 [ executable ]
261270 in
262271 let job = handle_job ~pgid job (`Process process) in
263263- (clear_local_state ctx, job)
272272+ (on_process ~async process ctx, job)
264273 in
265274 Option.iter Eio.Flow.close some_write;
266275 loop ctx job (pgid, some_read) rest
···270279 in
271280 let ctx, job =
272281 let process =
273273- E.exec ctx.executor ~stdin:stdout ~pgid
274274- ~sw:pipeline_switch ?stdout:some_write
282282+ E.exec ctx.executor ~stdin:stdout ~pgid ~mode
283283+ ?stdout:some_write
275284 ~env:(get_env ~extra:ctx.local_state ())
276285 ~cwd:(cwd_of_ctx ctx) [ executable ]
277286 in
278287 let job = handle_job ~pgid job (`Process process) in
279279- (clear_local_state ctx, job)
288288+ (on_process ~async process ctx, job)
280289 in
281290 Option.iter Eio.Flow.close some_write;
282291 loop ctx job (pgid, some_read) rest))
···316325 in
317326 let ctx, job =
318327 let process =
319319- E.exec ctx.executor ?stdout:some_write ~pgid
320320- ~sw:pipeline_switch ~fds:redirect ~cwd:(cwd_of_ctx ctx)
328328+ E.exec ctx.executor ?stdout:some_write ~pgid ~mode
329329+ ~fds:redirect ~cwd:(cwd_of_ctx ctx)
321330 ~env:(get_env ~extra:ctx.local_state ())
322331 (executable :: args)
323332 in
324333 let job = handle_job ~pgid job (`Process process) in
325325- (clear_local_state ctx, job)
334334+ (on_process ~async process ctx, job)
326335 in
327336 Option.iter Eio.Flow.close some_write;
328337 loop ctx job (pgid, some_read) rest
···332341 in
333342 let ctx, job =
334343 let process =
335335- E.exec ctx.executor ~stdin:stdout ~pgid
336336- ~sw:pipeline_switch ?stdout:some_write ~fds:redirect
344344+ E.exec ctx.executor ~stdin:stdout ~pgid ~mode
345345+ ?stdout:some_write ~fds:redirect
337346 ~env:(get_env ~extra:ctx.local_state ())
338347 ~cwd:(cwd_of_ctx ctx) (executable :: args)
339348 in
340349 let job = handle_job ~pgid job (`Process process) in
341341- (clear_local_state ctx, job)
350350+ (on_process ~async process ctx, job)
342351 in
343352 Option.iter Eio.Flow.close some_write;
344353 loop ctx job (pgid, some_read) rest))
···360369 let ctx, job =
361370 Eio.Switch.run @@ fun ghost_switch ->
362371 let ghost_process =
363363- E.exec ~sw:ghost_switch ~pgid:0 ~cwd:(cwd_of_ctx initial_ctx)
364364- initial_ctx.executor [ "sleep"; "99999999" ]
372372+ E.exec ~mode:(Types.Switched ghost_switch) ~pgid:0
373373+ ~cwd:(cwd_of_ctx initial_ctx) initial_ctx.executor
374374+ [ "sleep"; "99999999" ]
365375 in
366376 loop initial_ctx None (E.pid ghost_process, None) p
367377 in
+144-15
src/lib/posix/exec.ml
···11+(* Much of this code is from Eio_posix.
22+33+ Copyright (C) 2021 Anil Madhavapeddy Copyright (C) 2022 Thomas Leonard
44+55+ Permission to use, copy, modify, and distribute this software for any purpose
66+ with or without fee is hereby granted, provided that the above copyright notice
77+ and this permission notice appear in all copies.
88+99+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1010+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
1111+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1212+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1313+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1414+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1515+ PERFORMANCE OF THIS SOFTWARE. *)
1616+117open Eio.Std
2181919+module Process = struct
2020+ type t = {
2121+ pid : int;
2222+ exit_status : Unix.process_status Promise.t;
2323+ lock : Mutex.t;
2424+ }
2525+ (* When [lock] is unlocked, [exit_status] is resolved iff the process has been reaped. *)
2626+2727+ let exit_status t = t.exit_status
2828+ let pid t = t.pid
2929+3030+ module Fork_action = Eio_unix.Private.Fork_action
3131+3232+ (* Read a (typically short) error message from a child process. *)
3333+ let rec read_response fd =
3434+ let buf = Bytes.create 256 in
3535+ match Eio_posix.Low_level.read fd buf 0 (Bytes.length buf) with
3636+ | 0 -> ""
3737+ | n -> Bytes.sub_string buf 0 n ^ read_response fd
3838+3939+ let with_pipe fn =
4040+ Switch.run @@ fun sw ->
4141+ let r, w = Eio_posix.Low_level.pipe ~sw in
4242+ fn r w
4343+4444+ let signal t signal =
4545+ (* We need the lock here so that one domain can't signal the process exactly as another is reaping it. *)
4646+ Mutex.lock t.lock;
4747+ Fun.protect ~finally:(fun () -> Mutex.unlock t.lock) @@ fun () ->
4848+ if not (Promise.is_resolved t.exit_status) then Unix.kill t.pid signal
4949+ (* else process has been reaped and t.pid is invalid *)
5050+5151+ external eio_spawn :
5252+ Unix.file_descr -> Eio_unix.Private.Fork_action.c_action list -> int
5353+ = "caml_eio_posix_spawn"
5454+5555+ (* Wait for [pid] to exit and then resolve [exit_status] to its status. *)
5656+ let reap t exit_status =
5757+ Eio.Condition.loop_no_mutex Eio_unix.Process.sigchld (fun () ->
5858+ Mutex.lock t.lock;
5959+ match Unix.waitpid [ WNOHANG ] t.pid with
6060+ | 0, _ ->
6161+ Mutex.unlock t.lock;
6262+ None (* Not ready; wait for next SIGCHLD *)
6363+ | p, status ->
6464+ assert (p = t.pid);
6565+ Promise.resolve exit_status status;
6666+ Mutex.unlock t.lock;
6767+ Some ())
6868+6969+ let iter_switch ~f = function
7070+ | Merry.Types.Async -> ()
7171+ | Merry.Types.Switched sw -> f sw
7272+7373+ let spawn ~mode actions =
7474+ with_pipe @@ fun errors_r errors_w ->
7575+ Eio_unix.Private.Fork_action.with_actions actions @@ fun c_actions ->
7676+ iter_switch ~f:Switch.check mode;
7777+ let exit_status, set_exit_status = Promise.create () in
7878+ let t =
7979+ let pid =
8080+ Eio_unix.Fd.use_exn "errors-w" errors_w @@ fun errors_w ->
8181+ Eio.Private.Trace.with_span "spawn" @@ fun () ->
8282+ eio_spawn errors_w c_actions
8383+ in
8484+ Eio_unix.Fd.close errors_w;
8585+ { pid; exit_status; lock = Mutex.create () }
8686+ in
8787+ let () =
8888+ iter_switch
8989+ ~f:(fun sw ->
9090+ let hook =
9191+ Switch.on_release_cancellable sw (fun () ->
9292+ (* Kill process (if still running) *)
9393+ signal t Sys.sigkill;
9494+ (* The switch is being released, so either the daemon fiber got
9595+ cancelled or it hasn't started yet (and never will start). *)
9696+ if not (Promise.is_resolved t.exit_status) then
9797+ (* Do a (non-cancellable) waitpid here to reap the child. *)
9898+ reap t set_exit_status)
9999+ in
100100+ Fiber.fork_daemon ~sw (fun () ->
101101+ reap t set_exit_status;
102102+ Switch.remove_hook hook;
103103+ `Stop_daemon))
104104+ mode
105105+ in
106106+ (* Check for errors starting the process. *)
107107+ match read_response errors_r with
108108+ | "" -> t (* Success! Execing the child closed [errors_w] and we got EOF. *)
109109+ | err -> failwith err
110110+end
111111+112112+module Process_impl = struct
113113+ type t = Process.t
114114+ type tag = [ `Generic | `Unix ]
115115+116116+ let pid = Process.pid
117117+118118+ let await t =
119119+ match Eio.Promise.await @@ Process.exit_status t with
120120+ | Unix.WEXITED i -> `Exited i
121121+ | Unix.WSIGNALED i -> `Signaled i
122122+ | Unix.WSTOPPED _ -> assert false
123123+124124+ let signal = Process.signal
125125+end
126126+127127+let process =
128128+ let handler = Eio.Process.Pi.process (module Process_impl) in
129129+ fun proc -> Eio.Resource.T (proc, handler)
130130+3131let resolve_program name =
4132 if not (String.contains name '/') then
5133 Sys.getenv_opt "PATH"
···11139 else if Sys.file_exists name then Some name
12140 else None
131411414-let read_of_fd ~sw ~default ~to_close = function
1515- | None -> default
1616- | Some f -> (
142142+let read_of_fd ~mode ~default ~to_close v =
143143+ match (mode, v) with
144144+ | Merry.Types.Async, _ | _, None -> default
145145+ | Merry.Types.Switched sw, Some f -> (
17146 match Eio_unix.Resource.fd_opt f with
18147 | Some fd -> fd
19148 | None ->
···25154 to_close := r :: !to_close;
26155 r)
271562828-let write_of_fd ~sw ~default ~to_close = function
2929- | None -> default
3030- | Some f -> (
157157+let write_of_fd ~mode ~default ~to_close v =
158158+ match (mode, v) with
159159+ | Merry.Types.Async, _ | _, None -> default
160160+ | Merry.Types.Switched sw, Some f -> (
31161 match Eio_unix.Resource.fd_opt f with
32162 | Some fd -> fd
33163 | None ->
···91221 Eio_unix.Private.Fork_action.
92222 { run = (fun k -> k (Obj.repr (action_dups, plan, blocking))) }
932239494-let spawn_unix () ~sw ~fork_actions ?pgid ?uid ?gid ~env ~fds ~executable ~cwd
224224+let spawn_unix () ~mode ~fork_actions ?pgid ?uid ?gid ~env ~fds ~executable ~cwd
95225 args =
96226 let open Eio_posix in
97227 let actions =
···132262 in
133263 fn (Low_level.Process.Fork_action.fchdir cwd :: actions)
134264 in
135135- with_actions cwd @@ fun actions ->
136136- Eio_posix__.Process.process (Low_level.Process.spawn ~sw actions)
265265+ with_actions cwd @@ fun actions -> process (Process.spawn ~mode actions)
137266138267let fd_equal_int fd i =
139268 Eio_unix.Fd.use_exn "fd_equal_int" fd @@ fun ufd ->
···142271143272let pp_redirections ppf (i, fd, _) = Fmt.pf ppf "(%i,%a)" i Eio_unix.Fd.pp fd
144273145145-let run ~sw _ ?stdin ?stdout ?stderr ?(fds = []) ?(fork_actions = []) ~pgid ~cwd
146146- ?env ?executable args =
274274+let run ~mode _ ?stdin ?stdout ?stderr ?(fds = []) ?(fork_actions = []) ~pgid
275275+ ~cwd ?env ?executable args =
147276 with_close_list @@ fun to_close ->
148277 let check_fd n = function
149278 | Merry.Types.Redirect (m, _, _) -> Int.equal n m
···155284 else
156285 [
157286 ( 0,
158158- read_of_fd ~sw stdin ~default:Eio_unix.Fd.stdin ~to_close,
287287+ read_of_fd ~mode stdin ~default:Eio_unix.Fd.stdin ~to_close,
159288 `Blocking );
160289 ])
161290 @ (if fd_exists 1 then []
162291 else
163292 [
164293 ( 1,
165165- write_of_fd ~sw stdout ~default:Eio_unix.Fd.stdout ~to_close,
294294+ write_of_fd ~mode stdout ~default:Eio_unix.Fd.stdout ~to_close,
166295 `Blocking );
167296 ])
168297 @
···170299 else
171300 [
172301 ( 2,
173173- write_of_fd ~sw stderr ~default:Eio_unix.Fd.stderr ~to_close,
302302+ write_of_fd ~mode stderr ~default:Eio_unix.Fd.stderr ~to_close,
174303 `Blocking );
175304 ]
176305 in
···186315 let fds = std_fds @ fds in
187316 let executable = get_executable executable ~args in
188317 let env = get_env env in
189189- spawn_unix ~sw ~fork_actions ~cwd ~pgid ~fds ~env ~executable () args
318318+ spawn_unix ~mode ~fork_actions ~cwd ~pgid ~fds ~env ~executable () args
+2-2
src/lib/posix/merry_posix.ml
···1515 | `Exited n -> Merry.Exit.nonzero () n
1616 | `Signaled n -> Merry.Exit.nonzero () n
17171818- let exec ?(fork_actions = []) ?(fds = []) ?stdin ?stdout ?stderr ?env ~sw
1818+ let exec ?(fork_actions = []) ?(fds = []) ?stdin ?stdout ?stderr ?env ~mode
1919 ~pgid ~cwd t args : process =
2020 let env =
2121 Option.map
2222 (fun lst -> List.map (fun (a, b) -> a ^ "=" ^ b) lst |> Array.of_list)
2323 env
2424 in
2525- Exec.run ~fork_actions ~sw ~fds ~pgid ~cwd ?stdin ?stdout ?stderr ?env t
2525+ Exec.run ~fork_actions ~mode ~fds ~pgid ~cwd ?stdin ?stdout ?stderr ?env t
2626 args
2727end
+11-1
src/lib/types.ml
···3333 | Redirect of int * Eio_unix.Fd.t * Eio_unix.Private.Fork_action.blocking
3434 | Close of Eio_unix.Fd.t
35353636+type exec_mode =
3737+ | Switched of Eio.Switch.t
3838+ | Async
3939+ (** How to execute a process. This mainly controls what happens at the end
4040+ of the running a script or some commands. When a process is
4141+ "switched", we use the same semantics as Eio, we sigkill the process
4242+ and cleanup. If the process is complete Async then we do not wait.
4343+ This allows us to exit before some of our child processes, which is a
4444+ requirement for implementing the semantics of a shell! *)
4545+3646module type Exec = sig
3747 type t
3848 (** An executor for commands *)
···4959 ?stdout:_ Eio.Flow.sink ->
5060 ?stderr:_ Eio.Flow.sink ->
5161 ?env:(string * string) list ->
5252- sw:Eio.Switch.t ->
6262+ mode:exec_mode ->
5363 pgid:int ->
5464 cwd:Eio.Fs.dir_ty Eio.Path.t ->
5565 t ->