diff --git a/dune b/dune index ea99e27..b60e747 100644 --- a/dune +++ b/dune @@ -1,3 +1,6 @@ (mdx - (packages lwt_eio) - (files README.md)) + (files README.md) + (deps + (env_var "EIO_BACKEND") + (package lwt_eio) + (package eio_main))) diff --git a/dune-project b/dune-project index 6ffc645..6be9e67 100644 --- a/dune-project +++ b/dune-project @@ -1,4 +1,4 @@ -(lang dune 2.9) +(lang dune 3.8) (name lwt_eio) (formatting disabled) (generate_opam_files true) @@ -12,8 +12,8 @@ (synopsis "Run Lwt code within Eio") (description "An Lwt engine that allows running Lwt within an Eio event loop.") (depends - (eio (>= 0.10)) - lwt + (eio (>= 0.11)) + (lwt (>= 5.7.0)) (mdx (and (>= 1.10.0) :with-test)) (eio_main :with-test))) -(using mdx 0.1) +(using mdx 0.2) diff --git a/lib/lwt_eio.ml b/lib/lwt_eio.ml index 5ab4fee..065cd14 100644 --- a/lib/lwt_eio.ml +++ b/lib/lwt_eio.ml @@ -45,6 +45,33 @@ let fork_with_cancel ~sw fn = Option.get !cancel ) +(* Lwt wants to set SIGCHLD to its own handler, but some Eio backends also install a handler for it. + Both Lwt and Eio need to be notified, as they may each have child processes to monitor. + + There are two cases: + + 1. Eio installs its handler first, then Lwt tries to replace it while Eio is running. + We intercept that attempt and prevent the handler from changing. + + 2. Lwt installs its handler first (e.g. because someone ran a Lwt event loop for a bit before using Eio). + In that case, Eio will already have replaced Lwt's handler by the time we get called. + + Either way, Eio ends up owning the installed handler. We also want things to continue working if the Eio + event loop finishes and then the application runs a plain Lwt loop. That's why we use [register_immediate], + rather than running an Eio fiber. + + We also send an extra notification initially, in case we missed one during the hand-over. *) +let install_sigchld_handler = lazy ( + if not Sys.win32 then ( + Eio_unix.Process.install_sigchld_handler (); + let rec register () = + ignore (Eio.Condition.register_immediate Eio_unix.Process.sigchld register : Eio.Condition.request); + Lwt_unix.handle_signal Sys.sigchld + in + register () + ) +) + let make_engine ~sw ~clock = object inherit Lwt_engine.abstract @@ -78,6 +105,9 @@ let make_engine ~sw ~clock = object Eio.Cancel.protect (fun () -> callback (); notify ()) ) + method! forwards_signal signum = + signum = Sys.sigchld + method iter block = if block then ( let p, r = Promise.create () in @@ -110,6 +140,7 @@ let main ~clock user_promise = Lwt_engine.set old_engine let with_event_loop ~clock fn = + Lazy.force install_sigchld_handler; let p, r = Lwt.wait () in Switch.run @@ fun sw -> Fiber.fork ~sw (fun () -> main ~clock p); diff --git a/lwt_eio.opam b/lwt_eio.opam index bd3a511..c869bc1 100644 --- a/lwt_eio.opam +++ b/lwt_eio.opam @@ -10,9 +10,9 @@ homepage: "https://github.com/ocaml-multicore/lwt_eio" doc: "https://ocaml-multicore.github.io/lwt_eio" bug-reports: "https://github.com/ocaml-multicore/lwt_eio/issues" depends: [ - "dune" {>= "2.9"} - "eio" {>= "0.10"} - "lwt" + "dune" {>= "3.8"} + "eio" {>= "0.11"} + "lwt" {>= "5.7.0"} "mdx" {>= "1.10.0" & with-test} "eio_main" {with-test} "odoc" {with-doc} @@ -26,11 +26,9 @@ build: [ name "-j" jobs - "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] - ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/ocaml-multicore/lwt_eio.git" diff --git a/test/dune b/test/dune index 61d170c..1d12513 100644 --- a/test/dune +++ b/test/dune @@ -1,2 +1,5 @@ (mdx - (packages lwt_eio)) + (deps + (env_var "EIO_BACKEND") + (package lwt_eio) + (package eio_main))) diff --git a/test/test.md b/test/test.md index 9311656..a32202f 100644 --- a/test/test.md +++ b/test/test.md @@ -17,8 +17,7 @@ let run fn = ## Fairness -Lwt and Eio fibers don't block each other, although `Lwt_main.run` does call `Lwt.wakeup_paused` twice -during each iteration of the main loop, so the Lwt thread runs twice as often. +Lwt and Eio fibers don't block each other: ```ocaml # run @@ fun () -> @@ -42,19 +41,19 @@ during each iteration of the main loop, so the Lwt thread runs twice as often. );; +eio: i = 1 + lwt: i = 1 -+eio: i = 2 + lwt: i = 2 ++eio: i = 2 + lwt: i = 3 +eio: i = 3 + lwt: i = 4 -+ lwt: i = 5 +eio: i = 4 -+ lwt: i = 6 -+ lwt: i = 7 ++ lwt: i = 5 +eio: i = 5 -+ lwt: i = 8 ++ lwt: i = 6 +eio: i = 6 ++ lwt: i = 7 +eio: i = 7 ++ lwt: i = 8 +eio: i = 8 - : unit = () ``` @@ -65,12 +64,37 @@ We can fork (as long we we're not using multiple domains): ```ocaml # run @@ fun () -> - let output = Lwt_eio.run_lwt (fun () -> Lwt_process.(pread (shell "echo test"))) in + let output = Lwt_eio.run_lwt (fun () -> Lwt_process.(pread (shell "sleep 0.1; echo test"))) in traceln "Subprocess produced %S" output;; +Subprocess produced "test\n" - : unit = () ``` +Lwt's SIGCHLD handling works: + +```ocaml +# run @@ fun () -> + Lwt_eio.run_lwt (fun () -> + let p = Lwt_process.(open_process_none (shell "sleep 0.1; echo test")) in + let+ status = p#status in + assert (status = Unix.WEXITED 0) + );; +test +- : unit = () +``` + +Eio processes work too: + +```ocaml +# Eio_main.run @@ fun env -> + Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ -> + Lwt_eio.run_lwt Lwt.pause; + let proc_mgr = Eio.Stdenv.process_mgr env in + Eio.Process.run proc_mgr ["echo"; "hello"];; +hello +- : unit = () +``` + ## Signals Installing a signal handler with `Lwt_unix.on_signal` causes `lwt_unix_send_notification` to be called when the signal occurs. This signals the main event thread by writing to a pipe or eventfd, which is processed by Eio in the usual way.