2026-04-14 07:59.39: New job: test ocaml-multicore/lwt_eio https://github.com/ocaml-multicore/lwt_eio.git#refs/heads/master (1fb0815d20827bc3e336b5410fbb0d49b36f5334) (windows-x86_64:windows-server-mingw-ltsc2025-5.4_opam-2.5)Base: ocaml/opam:windows-server-mingw-ltsc2025-ocaml-5.4@sha256:0e4fa08d05a319101c6ab90f612f6b79d83aa94d5df9b492774b9176694093e1Opam project buildTo reproduce locally:git clone --recursive "https://github.com/ocaml-multicore/lwt_eio.git" -b "master" && cd "lwt_eio" && git reset --hard 1fb0815dcat > Dockerfile <<'END-OF-DOCKERFILE'FROM ocaml/opam:windows-server-mingw-ltsc2025-ocaml-5.4@sha256:0e4fa08d05a319101c6ab90f612f6b79d83aa94d5df9b492774b9176694093e1# windows-server-mingw-ltsc2025-5.4_opam-2.5USER 1000:1000ENV CLICOLOR_FORCE="1"ENV OPAMCOLOR="always"RUN ln -f /usr/local/bin/opam-2.5 /usr/local/bin/opamRUN opam init --reinit -niRUN uname -rs && opam exec -- ocaml -version && opam --versionRUN cd ~/opam-repository && (git cat-file -e 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 || git fetch origin master) && git reset -q --hard 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 && git log --no-decorate -n1 --oneline && opam update -uCOPY --chown=1000:1000 lwt_eio.opam /cygwin64/home/opam/src/./RUN opam pin add -yn lwt_eio.dev '/cygwin64/home/opam/src/./'RUN echo '(lang dune 3.0)' > '/home/opam/src/./dune-project'ENV DEPS="arch-x86_64.1 astring.0.8.5 base-bigarray.base base-bytes.base base-domains.base base-effects.base base-nnp.base base-threads.base base-unix.base bigstringaf.0.10.0 camlp-streams.5.0.1 cmdliner.2.1.0 conf-mingw-w64-gcc-x86_64.1 cppo.1.8.0 csexp.1.5.2 cstruct.6.2.0 domain-local-await.1.0.1 dune.3.22.2 dune-configurator.3.22.2 eio.1.3 eio_main.1.3 eio_windows.1.3 flexdll.0.44 fmt.0.11.0 hmap.0.8.1 host-arch-x86_64.1 host-system-mingw.1 logs.0.10.0 lwt.6.1.1 lwt-dllist.1.1.0 mdx.2.5.2 mingw-w64-shims.0.2.0 mtime.2.1.0 ocaml.5.4.1 ocaml-base-compiler.5.4.1 ocaml-compiler.5.4.1 ocaml-config.3 ocaml-env-mingw64.1 ocaml-options-vanilla.1 ocaml-version.4.0.4 ocamlbuild.0.16.1 ocamlfind.1.9.8 ocplib-endian.1.2 optint.0.3.0 psq.0.2.1 re.1.14.0 result.1.5 seq.base system-mingw.1 thread-table.1.0.0 topkg.1.1.1"ENV CI="true"ENV OCAMLCI="true"RUN opam update --depexts && opam install --cli=2.5 --depext-only -y lwt_eio.dev $DEPSRUN opam install $DEPSCOPY --chown=1000:1000 . /cygwin64/home/opam/srcRUN cd /home/opam/src && opam exec -- dune build @install @check @runtest && rm -rf _buildEND-OF-DOCKERFILEdocker build .END-REPRO-BLOCK2026-04-14 07:59.39: Using cache hint "ocaml-multicore/lwt_eio-ocaml/opam:windows-server-mingw-ltsc2025-ocaml-5.4@sha256:0e4fa08d05a319101c6ab90f612f6b79d83aa94d5df9b492774b9176694093e1-windows-server-mingw-ltsc2025-5.4_opam-2.5-605682aae595b344511f4ffad5f4a77a"2026-04-14 07:59.39: Using OBuilder spec:((from ocaml/opam:windows-server-mingw-ltsc2025-ocaml-5.4@sha256:0e4fa08d05a319101c6ab90f612f6b79d83aa94d5df9b492774b9176694093e1)(comment windows-server-mingw-ltsc2025-5.4_opam-2.5)(user (uid 1000) (gid 1000))(env CLICOLOR_FORCE 1)(env OPAMCOLOR always)(run (shell "ln -f /usr/local/bin/opam-2.5 /usr/local/bin/opam"))(run (shell "opam init --reinit -ni"))(run (shell "uname -rs && opam exec -- ocaml -version && opam --version"))(run (cache (opam-archives (target "c:\\opam\\.opam\\download-cache")))(network host)(shell "cd ~/opam-repository && (git cat-file -e 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 || git fetch origin master) && git reset -q --hard 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 && git log --no-decorate -n1 --oneline && opam update -u"))(copy (src lwt_eio.opam) (dst /cygwin64/home/opam/src/./))(run (network host)(shell "opam pin add -yn lwt_eio.dev '/cygwin64/home/opam/src/./'"))(run (shell "echo '(lang dune 3.0)' > '/home/opam/src/./dune-project'"))(env DEPS "arch-x86_64.1 astring.0.8.5 base-bigarray.base base-bytes.base base-domains.base base-effects.base base-nnp.base base-threads.base base-unix.base bigstringaf.0.10.0 camlp-streams.5.0.1 cmdliner.2.1.0 conf-mingw-w64-gcc-x86_64.1 cppo.1.8.0 csexp.1.5.2 cstruct.6.2.0 domain-local-await.1.0.1 dune.3.22.2 dune-configurator.3.22.2 eio.1.3 eio_main.1.3 eio_windows.1.3 flexdll.0.44 fmt.0.11.0 hmap.0.8.1 host-arch-x86_64.1 host-system-mingw.1 logs.0.10.0 lwt.6.1.1 lwt-dllist.1.1.0 mdx.2.5.2 mingw-w64-shims.0.2.0 mtime.2.1.0 ocaml.5.4.1 ocaml-base-compiler.5.4.1 ocaml-compiler.5.4.1 ocaml-config.3 ocaml-env-mingw64.1 ocaml-options-vanilla.1 ocaml-version.4.0.4 ocamlbuild.0.16.1 ocamlfind.1.9.8 ocplib-endian.1.2 optint.0.3.0 psq.0.2.1 re.1.14.0 result.1.5 seq.base system-mingw.1 thread-table.1.0.0 topkg.1.1.1")(env CI true)(env OCAMLCI true)(run (cache (opam-archives (target "c:\\opam\\.opam\\download-cache")))(network host)(shell "opam update --depexts && opam install --cli=2.5 --depext-only -y lwt_eio.dev $DEPS"))(run (cache (opam-archives (target "c:\\opam\\.opam\\download-cache")))(network host)(shell "opam install $DEPS"))(copy (src .) (dst /cygwin64/home/opam/src))(run (shell "cd /home/opam/src && opam exec -- dune build @install @check @runtest && rm -rf _build")))2026-04-14 07:59.39: Waiting for resource in pool OCluster2026-04-14 07:59.39: Waiting for worker…2026-04-14 10:55.35: Got resource from pool OClusterBuilding on ltsc2025-1Initialized empty Git repository in C:/state/git/lwt_eio.git-03549f0b94e06ebe87f28d868bc80903bc20db5a/.git/HEAD is now at 1fb0815 Merge pull request #32 from talex5/release(from ocaml/opam:windows-server-mingw-ltsc2025-ocaml-5.4@sha256:0e4fa08d05a319101c6ab90f612f6b79d83aa94d5df9b492774b9176694093e1)2026-04-14 10:55.56 ---> using "e6e8a2cf62a928430568e9b050d6cc986ddb11ca371f3b24e6b6f1ad80860153" from cacheC:/: (comment windows-server-mingw-ltsc2025-5.4_opam-2.5)C:/: (user (uid 1000) (gid 1000))C:/: (env CLICOLOR_FORCE 1)C:/: (env OPAMCOLOR always)C:/: (run (shell "ln -f /usr/local/bin/opam-2.5 /usr/local/bin/opam"))2026-04-14 10:55.56 ---> using "8371c7bbeda21231937607a48921d4490a1c9c23810f1d7416ef9e1aa874f8e3" from cacheC:/: (run (shell "opam init --reinit -ni"))No configuration file found, using built-in defaults.<><> Unix support infrastructure ><><><><><><><><><><><><><><><><><><><><><><><>opam and the OCaml ecosystem in general require various Unix tools in order to operate correctly. At present, this requires the installation of Cygwin to provide these tools.How should opam obtain Unix tools?> 1. Use tools found in PATH (Cygwin installation at C:\cygwin64)2. Automatically create an internal Cygwin installation that will be managed by opam (recommended)3. Use Cygwin installation found in C:\cygwin644. Use another existing Cygwin/MSYS2 installation5. Abort initialisation[1/2/3/4/5] 1Checking for available remotes: rsync and local, git.- you won't be able to use mercurial repositories unless you install the hg command on your system.- you won't be able to use darcs repositories unless you install the darcs command on your system.<><> Updating repositories ><><><><><><><><><><><><><><><><><><><><><><><><><><>[default] Initialised2026-04-14 10:55.56 ---> using "8bf506c77ce4ea2b6efe0b049434ba77c8ea5f4c0beccb9cedbfead0127ea0b7" from cacheC:/: (run (shell "uname -rs && opam exec -- ocaml -version && opam --version"))CYGWIN_NT-10.0-26100 3.6.7-1.x86_64The OCaml toplevel, version 5.4.12.5.02026-04-14 10:55.56 ---> using "017acac92f6c222a89f675ad77684e3ed5874f73aa36dd7947a6dd1cf4a5b5c7" from cacheC:/: (run (cache (opam-archives (target "c:\\opam\\.opam\\download-cache")))(network host)(shell "cd ~/opam-repository && (git cat-file -e 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 || git fetch origin master) && git reset -q --hard 5f7bb1a6e69f1ea15d8b2b08eeaa70a162c6fd41 && git log --no-decorate -n1 --oneline && opam update -u"))From https://github.com/ocaml/opam-repository* branch master -> FETCH_HEAD65664dc5b2..23068bb422 master -> origin/master5f7bb1a6e6 Merge pull request #29704 from shonfeder/release-dune-3.22.2<><> Updating package repositories ><><><><><><><><><><><><><><><><><><><><><><>[default] synchronised from git+file://C:/cygwin64/home/opam/opam-repositoryEverything as up-to-date as possibleHowever, you may "opam upgrade" these packages explicitly at these versions (e.g. "opam upgrade ocaml.5.5.0"), which will ask permission to downgrade or uninstall the conflicting packages.Nothing to do.# To update the current shell environment, run: eval $(opam env)2026-04-14 10:55.56 ---> using "51cd6e7d9cd63d385c1da1b80ea3b175c1d55dd65f06b3b3be80f9d441a4f7b9" from cacheC:/: (copy (src lwt_eio.opam) (dst /cygwin64/home/opam/src/./))2026-04-14 10:56.55 ---> saved as "bcfb78191f1c6c3281c008e471ac186b214e7317d9f25f889133950f0c24152e"C:/: (run (network host)(shell "opam pin add -yn lwt_eio.dev '/cygwin64/home/opam/src/./'"))[lwt_eio.dev] synchronised (file://C:/cygwin64/home/opam/src)lwt_eio is now pinned to file://C:/cygwin64/home/opam/src (version dev)2026-04-14 10:58.17 ---> saved as "56b102945d5fe42ec04d3cbaa3a350c5d9723d41b14a77e7fdd7c1ee1c493078"C:/: (run (shell "echo '(lang dune 3.0)' > '/home/opam/src/./dune-project'"))2026-04-14 10:59.36 ---> saved as "c1dc91b8e468db1726b7dbfc81270d471ebdc8d5d5c89004c2e4eb811b0a7440"C:/: (env DEPS "arch-x86_64.1 astring.0.8.5 base-bigarray.base base-bytes.base base-domains.base base-effects.base base-nnp.base base-threads.base base-unix.base bigstringaf.0.10.0 camlp-streams.5.0.1 cmdliner.2.1.0 conf-mingw-w64-gcc-x86_64.1 cppo.1.8.0 csexp.1.5.2 cstruct.6.2.0 domain-local-await.1.0.1 dune.3.22.2 dune-configurator.3.22.2 eio.1.3 eio_main.1.3 eio_windows.1.3 flexdll.0.44 fmt.0.11.0 hmap.0.8.1 host-arch-x86_64.1 host-system-mingw.1 logs.0.10.0 lwt.6.1.1 lwt-dllist.1.1.0 mdx.2.5.2 mingw-w64-shims.0.2.0 mtime.2.1.0 ocaml.5.4.1 ocaml-base-compiler.5.4.1 ocaml-compiler.5.4.1 ocaml-config.3 ocaml-env-mingw64.1 ocaml-options-vanilla.1 ocaml-version.4.0.4 ocamlbuild.0.16.1 ocamlfind.1.9.8 ocplib-endian.1.2 optint.0.3.0 psq.0.2.1 re.1.14.0 result.1.5 seq.base system-mingw.1 thread-table.1.0.0 topkg.1.1.1")C:/: (env CI true)C:/: (env OCAMLCI true)C:/: (run (cache (opam-archives (target "c:\\opam\\.opam\\download-cache")))(network host)(shell "opam update --depexts && opam install --cli=2.5 --depext-only -y lwt_eio.dev $DEPS"))<><> Synchronising pinned packages ><><><><><><><><><><><><><><><><><><><><><><>[lwt_eio.dev] synchronised (file://C:/cygwin64/home/opam/src)[NOTE] Package system-mingw is already installed (current version is 1).[NOTE] Package ocaml-options-vanilla is already installed (current version is 1).[NOTE] Package ocaml-env-mingw64 is already installed (current version is 1).[NOTE] Package ocaml-config is already installed (current version is 3).[NOTE] Package ocaml-compiler is already installed (current version is 5.4.1).[NOTE] Package ocaml-base-compiler is already installed (current version is 5.4.1).[NOTE] Package ocaml is already installed (current version is 5.4.1).[NOTE] Package mingw-w64-shims is already installed (current version is 0.2.0).[NOTE] Package host-system-mingw is already installed (current version is 1).[NOTE] Package host-arch-x86_64 is already installed (current version is 1).[NOTE] Package flexdll is already installed (current version is 0.44).[NOTE] Package conf-mingw-w64-gcc-x86_64 is already installed (current version is 1).[NOTE] Package base-unix is already installed (current version is base).[NOTE] Package base-threads is already installed (current version is base).[NOTE] Package base-nnp is already installed (current version is base).[NOTE] Package base-effects is already installed (current version is base).[NOTE] Package base-domains is already installed (current version is base).[NOTE] Package base-bigarray is already installed (current version is base).[NOTE] Package arch-x86_64 is already installed (current version is 1).# To update the current shell environment, run: eval $(opam env)2026-04-14 11:01.25 ---> saved as "6394ec0de3a15456c43bc46ff6754023014f367044e1615b55174b26770574a8"C:/: (run (cache (opam-archives (target "c:\\opam\\.opam\\download-cache")))(network host)(shell "opam install $DEPS"))[NOTE] Package system-mingw is already installed (current version is 1).[NOTE] Package ocaml-options-vanilla is already installed (current version is 1).[NOTE] Package ocaml-env-mingw64 is already installed (current version is 1).[NOTE] Package ocaml-config is already installed (current version is 3).[NOTE] Package ocaml-compiler is already installed (current version is 5.4.1).[NOTE] Package ocaml-base-compiler is already installed (current version is 5.4.1).[NOTE] Package ocaml is already installed (current version is 5.4.1).[NOTE] Package mingw-w64-shims is already installed (current version is 0.2.0).[NOTE] Package host-system-mingw is already installed (current version is 1).[NOTE] Package host-arch-x86_64 is already installed (current version is 1).[NOTE] Package flexdll is already installed (current version is 0.44).[NOTE] Package conf-mingw-w64-gcc-x86_64 is already installed (current version is 1).[NOTE] Package base-unix is already installed (current version is base).[NOTE] Package base-threads is already installed (current version is base).[NOTE] Package base-nnp is already installed (current version is base).[NOTE] Package base-effects is already installed (current version is base).[NOTE] Package base-domains is already installed (current version is base).[NOTE] Package base-bigarray is already installed (current version is base).[NOTE] Package arch-x86_64 is already installed (current version is 1).The following actions will be performed:=== install 32 packages- install astring 0.8.5- install base-bytes base- install bigstringaf 0.10.0- install camlp-streams 5.0.1- install cmdliner 2.1.0- install cppo 1.8.0- install csexp 1.5.2- install cstruct 6.2.0- install domain-local-await 1.0.1- install dune 3.22.2- install dune-configurator 3.22.2- install eio 1.3- install eio_main 1.3- install eio_windows 1.3- install fmt 0.11.0- install hmap 0.8.1- install logs 0.10.0- install lwt 6.1.1- install lwt-dllist 1.1.0- install mdx 2.5.2- install mtime 2.1.0- install ocaml-version 4.0.4- install ocamlbuild 0.16.1- install ocamlfind 1.9.8- install ocplib-endian 1.2- install optint 0.3.0- install psq 0.2.1- install re 1.14.0- install result 1.5- install seq base- install thread-table 1.0.0- install topkg 1.1.1<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>-> retrieved astring.0.8.5 (cached)-> retrieved bigstringaf.0.10.0 (cached)-> retrieved camlp-streams.5.0.1 (cached)-> retrieved cmdliner.2.1.0 (cached)-> retrieved cppo.1.8.0 (cached)-> retrieved csexp.1.5.2 (cached)-> retrieved cstruct.6.2.0 (cached)-> retrieved domain-local-await.1.0.1 (cached)-> retrieved fmt.0.11.0 (cached)-> retrieved eio.1.3, eio_main.1.3, eio_windows.1.3 (cached)-> retrieved hmap.0.8.1 (cached)-> retrieved logs.0.10.0 (cached)-> retrieved lwt-dllist.1.1.0 (cached)-> retrieved lwt.6.1.1 (cached)-> retrieved mdx.2.5.2 (cached)-> retrieved mtime.2.1.0 (cached)-> retrieved ocaml-version.4.0.4 (cached)-> retrieved ocamlbuild.0.16.1 (cached)-> retrieved ocamlfind.1.9.8 (cached)-> retrieved ocplib-endian.1.2 (cached)-> retrieved optint.0.3.0 (cached)-> retrieved psq.0.2.1 (cached)-> retrieved re.1.14.0 (cached)-> retrieved seq.base (cached)-> installed seq.base-> retrieved result.1.5 (cached)-> retrieved thread-table.1.0.0 (cached)-> retrieved dune.3.22.2, dune-configurator.3.22.2 (cached)-> retrieved topkg.1.1.1 (cached)-> installed cmdliner.2.1.0[WARNING] .install file is missing .exe extension for src/findlib/ocamlfind[WARNING] .install file is missing .exe extension for src/findlib/ocamlfind_opt[WARNING] Automatically adding .exe to C:\opam\.opam\5.4\.opam-switch\build\ocamlfind.1.9.8\src\findlib\ocamlfind.exe[WARNING] Automatically adding .exe to C:\opam\.opam\5.4\.opam-switch\build\ocamlfind.1.9.8\src\findlib\ocamlfind_opt.exe[WARNING] C:\opam\.opam\5.4\bin\safe_camlp4 is a script; the command won't be available-> installed ocamlfind.1.9.8-> installed base-bytes.base-> installed ocamlbuild.0.16.1-> installed topkg.1.1.1-> installed dune.3.22.2-> installed hmap.0.8.1-> installed csexp.1.5.2-> installed astring.0.8.5-> installed camlp-streams.5.0.1-> installed cppo.1.8.0-> installed fmt.0.11.0-> installed lwt-dllist.1.1.0-> installed mtime.2.1.0-> installed ocaml-version.4.0.4-> installed optint.0.3.0-> installed psq.0.2.1-> installed re.1.14.0-> installed cstruct.6.2.0-> installed ocplib-endian.1.2-> installed result.1.5-> installed thread-table.1.0.0-> installed dune-configurator.3.22.2-> installed domain-local-await.1.0.1-> installed bigstringaf.0.10.0-> installed eio.1.3-> installed lwt.6.1.1-> installed eio_windows.1.3-> installed eio_main.1.3-> installed logs.0.10.0-> installed mdx.2.5.2Done.# To update the current shell environment, run: eval $(opam env)2026-04-14 11:16.58 ---> saved as "36a42a58c52cf608fa991179d321a859c2c4b695f7f3b7265a61e88dee5dd12e"C:/: (copy (src .) (dst /cygwin64/home/opam/src))2026-04-14 11:21.59 ---> saved as "e9824ec175873dd82cad75693b2296b976310c2ef2536fa427d5b911351927dc"C:/: (run (shell "cd /home/opam/src && opam exec -- dune build @install @check @runtest && rm -rf _build"))File "test/test.md", line 1, characters 0-0:C:\cygwin64\bin\git.exe --no-pager diff --no-index --color=always -u --ignore-cr-at-eol _build/default/test/test.md _build/default/test/.mdx/test.md.correcteddiff --git a/_build/default/test/test.md b/_build/default/test/.mdx/test.md.correctedindex 811afbe..b11e138 100755--- a/_build/default/test/test.md+++ b/_build/default/test/.mdx/test.md.corrected@@ -1,425 +1,426 @@-## Setup--```ocaml-# #require "eio_main";;-# #require "lwt_eio";;-```--```ocaml-open Lwt.Syntax-open Eio.Std--let run fn =- Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->- fn ()-```--## Fairness--Lwt and Eio fibers don't block each other:--```ocaml-# run @@ fun () ->- Fiber.both- (fun () ->- for i = 1 to 8 do- traceln "eio: i = %d" i;- Fiber.yield ()- done- )- (fun () ->- Lwt_eio.Promise.await_lwt begin- let rec aux i =- traceln " lwt: i = %d" i;- let* () = Lwt.pause () in- if i < 8 then aux (i + 1)- else Lwt.return_unit- in- aux 1- end- );;-+eio: i = 1-+ lwt: i = 1-+eio: i = 2-+ lwt: i = 2-+eio: i = 3-+ lwt: i = 3-+eio: i = 4-+ lwt: i = 4-+eio: i = 5-+ lwt: i = 5-+eio: i = 6-+ lwt: i = 6-+eio: i = 7-+ lwt: i = 7-+eio: i = 8-+ lwt: i = 8-- : unit = ()-```--## Forking--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 "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.--```ocaml-# run @@ fun () ->- let p, r = Promise.create () in- Lwt_eio.run_lwt (fun () ->- let handler = Lwt_unix.on_signal Sys.sigusr1 (fun (_ : int) ->- traceln "Signal handler received USR1";- (* It's OK to use Promise.resolve here, as it never blocks *)- Promise.resolve r ()- )- in- Unix.(kill (getpid ())) Sys.sigusr1;- let* () = Lwt_eio.Promise.await_eio p in- traceln "Finished; removing signal handler";- Lwt_unix.disable_signal_handler handler;- Lwt.return_unit- );;-+Signal handler received USR1-+Finished; removing signal handler-- : unit = ()-```--## Exceptions--Lwt failures become Eio exceptions:--```ocaml-# run @@ fun () ->- try Lwt_eio.run_lwt (fun () -> Lwt.fail_with "Simulated error")- with Failure msg -> traceln "Eio caught: %s" msg;;-+Eio caught: Simulated error-- : unit = ()-```--Eio exceptions become Lwt failures:--```ocaml-# run @@ fun () ->- Lwt_eio.run_lwt (fun () ->- Lwt.catch- (fun () -> Lwt_eio.run_eio (fun () -> failwith "Simulated error"))- (fun ex -> traceln "Lwt caught: %a" Fmt.exn ex; Lwt.return_unit)- );;-+Lwt caught: Failure("Simulated error")-- : unit = ()-```--## Cancellation--Cancelling an Eio fiber that is running Lwt code cancels the Lwt promise:--```ocaml-# run @@ fun () ->- let p, r = Lwt.task () in- try- Fiber.both- (fun () -> Lwt_eio.run_lwt (fun () -> p))- (fun () -> failwith "Simulated error");- with Failure _ ->- traceln "Checking status of Lwt promise...";- Lwt_eio.Promise.await_lwt p;;-+Checking status of Lwt promise...-Exception: Lwt.Resolution_loop.Canceled.-```--Cancelling a Lwt thread that is running Eio code cancels the Eio context:--```ocaml-# run @@ fun () ->- Switch.run @@ fun sw ->- Lwt_eio.run_lwt (fun () ->- let p = Lwt_eio.run_eio (fun () ->- try Fiber.await_cancel ()- with Eio.Cancel.Cancelled ex -> traceln "Eio fiber cancelled: %a" Fmt.exn ex- ) in- Lwt.cancel p;- Lwt.return_unit- );;-+Eio fiber cancelled: Lwt.Resolution_loop.Canceled-- : unit = ()-```--Trying to run Lwt code from an already-cancelled context fails immediately:--```ocaml-# run @@ fun () ->- Switch.run @@ fun sw ->- Switch.fail sw (Failure "Simulated error");- Lwt_eio.run_lwt (fun () ->- let* () = Lwt.pause () in- assert false- );;-Exception: Failure "Simulated error".-```--## Cleanup--After finishing with our mainloop, the old Lwt engine is ready for use again:--```ocaml-# run ignore;;-- : unit = ()-# Lwt_main.run (Lwt_unix.sleep 0.01);;-- : unit = ()-```--## Running Lwt code from another domain--A new Eio-only domain runs a job in the original Lwt domain.-The Eio domain is still running while it waits, allowing it to resolve an Lwt promise-and cause the original job to finish and return its result to the Eio domain:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->- let lwt_domain = Domain.self () in- let pp_domain f d =- if d = lwt_domain then Fmt.string f "Lwt domain"- else Fmt.string f "new Eio domain"- in- traceln "Lwt running in %a" pp_domain lwt_domain;- Eio.Domain_manager.run env#domain_mgr (fun () ->- let eio_domain = Domain.self () in- traceln "Eio running in %a" pp_domain eio_domain;- let p, r = Lwt.wait () in- let result =- Fiber.first- (fun () ->- Lwt_eio.run_lwt_in_main (fun () ->- traceln "Lwt callback running %a" pp_domain (Domain.self ());- let+ p = p in- p ^ "L"- )- )- (fun () ->- Lwt_eio.run_lwt_in_main (fun () -> Lwt.wakeup r "E"; Lwt.return_unit);- Fiber.await_cancel ()- )- in- traceln "Result: %S" result- );;-+Lwt running in Lwt domain-+Eio running in new Eio domain-+Lwt callback running Lwt domain-+Result: "EL"-- : unit = ()-```--Cancelling the Eio fiber cancels the Lwt job too:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->- Eio.Domain_manager.run env#domain_mgr (fun () ->- let p, r = Lwt.task () in- Fiber.both- (fun () ->- Lwt_eio.run_lwt_in_main (fun () ->- traceln "Starting Lwt callback...";- Lwt.catch- (fun () -> p)- (fun ex -> traceln "Lwt caught: %a" Fmt.exn ex; raise ex)- )- )- (fun () ->- failwith "Simulated error"- )- );;-+Starting Lwt callback...-+Lwt caught: Lwt.Resolution_loop.Canceled-Exception: Failure "Simulated error".-```--## Debug mode--In debug mode, we detect attempts to use Eio from Lwt context:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Lwt_eio.run_lwt @@ fun () ->- traceln "Traceln is permitted";- Fiber.yield ();- assert false-+Traceln is permitted-WARNING: Attempt to perform effect in Lwt context-Exception: Invalid_argument "Attempt to perform effect in Lwt context".-```--In a new fiber:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Switch.run @@ fun sw ->- Fiber.fork ~sw (fun () ->- Lwt_eio.run_lwt @@ fun () ->- Fiber.yield ();- assert false- );;-WARNING: Attempt to perform effect in Lwt context-Exception: Invalid_argument "Attempt to perform effect in Lwt context".-```--Resumed by the Lwt engine:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Switch.run @@ fun sw ->- let x =- let+ () = Lwt.pause () in- Fiber.yield ()- in- Lwt_eio.Promise.await_lwt x;;-WARNING: Attempt to perform effect in Lwt context-Exception: Invalid_argument "Attempt to perform effect in Lwt context".-```--Resumed by IO:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Switch.run @@ fun sw ->- let r, w = Eio_unix.pipe sw in- Fiber.both- (fun () ->- Lwt_eio.run_lwt @@ fun () ->- let r = Eio_unix.Resource.fd r in- Eio_unix.Fd.use_exn "lwt_read" r @@ fun r ->- let r = Lwt_unix.of_unix_file_descr r in- let buf = Bytes.create 1 in- let* got = Lwt_unix.read r buf 0 1 in- assert (got = 1);- traceln "Got %S" (Bytes.to_string buf);- Fiber.yield ();- assert false- )- (fun () -> Eio.Flow.copy_string "&" w);;-+Got "&"-WARNING: Attempt to perform effect in Lwt context-Exception: Invalid_argument "Attempt to perform effect in Lwt context".-```--Changing to the same mode is an error:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Lwt_eio.run_eio (fun () -> assert false);;-+WARNING: Exception: Failure("Already in Eio context!")-+ No backtrace available.-Exception: Failure "Already in Eio context!".-```--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Lwt_eio.run_lwt @@ fun () ->- Lwt_eio.run_lwt (fun () -> assert false);;-+WARNING: Exception: Failure("Already in Lwt context!")-+ No backtrace available.-Exception: Failure "Already in Lwt context!".-```--`await_eio` resumes in Lwt mode:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Switch.run @@ fun sw ->- let p, r = Promise.create () in- Fiber.both- (fun () ->- Lwt_eio.run_lwt @@ fun () ->- let* () = Lwt_eio.Promise.await_eio p in- Fiber.yield ();- assert false- )- (fun () -> Promise.resolve r ());;-WARNING: Attempt to perform effect in Lwt context-Exception: Invalid_argument "Attempt to perform effect in Lwt context".-```--`await_eio_result` resumes in Lwt mode:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Switch.run @@ fun sw ->- let p = Fiber.fork_promise ~sw (fun () -> Fiber.yield (); traceln "Eio done") in- Lwt_eio.run_lwt @@ fun () ->- let* () = Lwt_eio.Promise.await_eio_result p in- Fiber.yield ();- assert false;;-+Eio done-WARNING: Attempt to perform effect in Lwt context-Exception: Invalid_argument "Attempt to perform effect in Lwt context".-```--`run_eio` resumes in Lwt mode:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Switch.run @@ fun sw ->- Lwt_eio.run_lwt @@ fun () ->- let* () = Lwt_eio.run_eio Fiber.yield in- Fiber.yield ();- assert false;;-WARNING: Attempt to perform effect in Lwt context-Exception: Invalid_argument "Attempt to perform effect in Lwt context".-```--Debug mode is off by default:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->- Lwt_eio.run_lwt @@ fun () ->- Fiber.yield ();- Lwt.return "Didn't notice";;-- : string = "Didn't notice"+## Setup++```ocaml+# #require "eio_main";;+# #require "lwt_eio";;+```++```ocaml+open Lwt.Syntax+open Eio.Std++let run fn =+ Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->+ fn ()+```++## Fairness++Lwt and Eio fibers don't block each other:++```ocaml+# run @@ fun () ->+ Fiber.both+ (fun () ->+ for i = 1 to 8 do+ traceln "eio: i = %d" i;+ Fiber.yield ()+ done+ )+ (fun () ->+ Lwt_eio.Promise.await_lwt begin+ let rec aux i =+ traceln " lwt: i = %d" i;+ let* () = Lwt.pause () in+ if i < 8 then aux (i + 1)+ else Lwt.return_unit+ in+ aux 1+ end+ );;++eio: i = 1++ lwt: i = 1++eio: i = 2++ lwt: i = 2++eio: i = 3++ lwt: i = 3++eio: i = 4++ lwt: i = 4++eio: i = 5++ lwt: i = 5++eio: i = 6++ lwt: i = 6++eio: i = 7++ lwt: i = 7++eio: i = 8++ lwt: i = 8+- : unit = ()+```++## Forking++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 "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.++```ocaml+# run @@ fun () ->+ let p, r = Promise.create () in+ Lwt_eio.run_lwt (fun () ->+ let handler = Lwt_unix.on_signal Sys.sigusr1 (fun (_ : int) ->+ traceln "Signal handler received USR1";+ (* It's OK to use Promise.resolve here, as it never blocks *)+ Promise.resolve r ()+ )+ in+ Unix.(kill (getpid ())) Sys.sigusr1;+ let* () = Lwt_eio.Promise.await_eio p in+ traceln "Finished; removing signal handler";+ Lwt_unix.disable_signal_handler handler;+ Lwt.return_unit+ );;++Signal handler received USR1++Finished; removing signal handler+- : unit = ()+```++## Exceptions++Lwt failures become Eio exceptions:++```ocaml+# run @@ fun () ->+ try Lwt_eio.run_lwt (fun () -> Lwt.fail_with "Simulated error")+ with Failure msg -> traceln "Eio caught: %s" msg;;++Eio caught: Simulated error+- : unit = ()+```++Eio exceptions become Lwt failures:++```ocaml+# run @@ fun () ->+ Lwt_eio.run_lwt (fun () ->+ Lwt.catch+ (fun () -> Lwt_eio.run_eio (fun () -> failwith "Simulated error"))+ (fun ex -> traceln "Lwt caught: %a" Fmt.exn ex; Lwt.return_unit)+ );;++Lwt caught: Failure("Simulated error")+- : unit = ()+```++## Cancellation++Cancelling an Eio fiber that is running Lwt code cancels the Lwt promise:++```ocaml+# run @@ fun () ->+ let p, r = Lwt.task () in+ try+ Fiber.both+ (fun () -> Lwt_eio.run_lwt (fun () -> p))+ (fun () -> failwith "Simulated error");+ with Failure _ ->+ traceln "Checking status of Lwt promise...";+ Lwt_eio.Promise.await_lwt p;;++Checking status of Lwt promise...+Exception: Lwt.Resolution_loop.Canceled.+```++Cancelling a Lwt thread that is running Eio code cancels the Eio context:++```ocaml+# run @@ fun () ->+ Switch.run @@ fun sw ->+ Lwt_eio.run_lwt (fun () ->+ let p = Lwt_eio.run_eio (fun () ->+ try Fiber.await_cancel ()+ with Eio.Cancel.Cancelled ex -> traceln "Eio fiber cancelled: %a" Fmt.exn ex+ ) in+ Lwt.cancel p;+ Lwt.return_unit+ );;++Eio fiber cancelled: Lwt.Resolution_loop.Canceled+- : unit = ()+```++Trying to run Lwt code from an already-cancelled context fails immediately:++```ocaml+# run @@ fun () ->+ Switch.run @@ fun sw ->+ Switch.fail sw (Failure "Simulated error");+ Lwt_eio.run_lwt (fun () ->+ let* () = Lwt.pause () in+ assert false+ );;+Exception: Failure "Simulated error".+```++## Cleanup++After finishing with our mainloop, the old Lwt engine is ready for use again:++```ocaml+# run ignore;;+- : unit = ()+# Lwt_main.run (Lwt_unix.sleep 0.01);;+- : unit = ()+```++## Running Lwt code from another domain++A new Eio-only domain runs a job in the original Lwt domain.+The Eio domain is still running while it waits, allowing it to resolve an Lwt promise+and cause the original job to finish and return its result to the Eio domain:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->+ let lwt_domain = Domain.self () in+ let pp_domain f d =+ if d = lwt_domain then Fmt.string f "Lwt domain"+ else Fmt.string f "new Eio domain"+ in+ traceln "Lwt running in %a" pp_domain lwt_domain;+ Eio.Domain_manager.run env#domain_mgr (fun () ->+ let eio_domain = Domain.self () in+ traceln "Eio running in %a" pp_domain eio_domain;+ let p, r = Lwt.wait () in+ let result =+ Fiber.first+ (fun () ->+ Lwt_eio.run_lwt_in_main (fun () ->+ traceln "Lwt callback running %a" pp_domain (Domain.self ());+ let+ p = p in+ p ^ "L"+ )+ )+ (fun () ->+ Lwt_eio.run_lwt_in_main (fun () -> Lwt.wakeup r "E"; Lwt.return_unit);+ Fiber.await_cancel ()+ )+ in+ traceln "Result: %S" result+ );;++Lwt running in Lwt domain++Eio running in new Eio domain++Lwt callback running Lwt domain++Result: "EL"+- : unit = ()+```++Cancelling the Eio fiber cancels the Lwt job too:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->+ Eio.Domain_manager.run env#domain_mgr (fun () ->+ let p, r = Lwt.task () in+ Fiber.both+ (fun () ->+ Lwt_eio.run_lwt_in_main (fun () ->+ traceln "Starting Lwt callback...";+ Lwt.catch+ (fun () -> p)+ (fun ex -> traceln "Lwt caught: %a" Fmt.exn ex; raise ex)+ )+ )+ (fun () ->+ failwith "Simulated error"+ )+ );;++Starting Lwt callback...++Lwt caught: Lwt.Resolution_loop.Canceled+Exception: Failure "Simulated error".+```++## Debug mode++In debug mode, we detect attempts to use Eio from Lwt context:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Lwt_eio.run_lwt @@ fun () ->+ traceln "Traceln is permitted";+ Fiber.yield ();+ assert false++Traceln is permitted+WARNING: Attempt to perform effect in Lwt context+Exception: Invalid_argument "Attempt to perform effect in Lwt context".+```++In a new fiber:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Switch.run @@ fun sw ->+ Fiber.fork ~sw (fun () ->+ Lwt_eio.run_lwt @@ fun () ->+ Fiber.yield ();+ assert false+ );;+WARNING: Attempt to perform effect in Lwt context+Exception: Invalid_argument "Attempt to perform effect in Lwt context".+```++Resumed by the Lwt engine:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Switch.run @@ fun sw ->+ let x =+ let+ () = Lwt.pause () in+ Fiber.yield ()+ in+ Lwt_eio.Promise.await_lwt x;;+WARNING: Attempt to perform effect in Lwt context+Exception: Invalid_argument "Attempt to perform effect in Lwt context".+```++Resumed by IO:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Switch.run @@ fun sw ->+ let r, w = Eio_unix.pipe sw in+ Fiber.both+ (fun () ->+ Lwt_eio.run_lwt @@ fun () ->+ let r = Eio_unix.Resource.fd r in+ Eio_unix.Fd.use_exn "lwt_read" r @@ fun r ->+ let r = Lwt_unix.of_unix_file_descr r in+ let buf = Bytes.create 1 in+ let* got = Lwt_unix.read r buf 0 1 in+ assert (got = 1);+ traceln "Got %S" (Bytes.to_string buf);+ Fiber.yield ();+ assert false+ )+ (fun () -> Eio.Flow.copy_string "&" w);;++Got "&"+WARNING: Attempt to perform effect in Lwt context+Exception: Invalid_argument "Attempt to perform effect in Lwt context".+```++Changing to the same mode is an error:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Lwt_eio.run_eio (fun () -> assert false);;++WARNING: Exception: Failure("Already in Eio context!")++ No backtrace available.+Exception: Failure "Already in Eio context!".+```++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Lwt_eio.run_lwt @@ fun () ->+ Lwt_eio.run_lwt (fun () -> assert false);;++WARNING: Exception: Failure("Already in Lwt context!")++ No backtrace available.+Exception: Failure "Already in Lwt context!".+```++`await_eio` resumes in Lwt mode:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Switch.run @@ fun sw ->+ let p, r = Promise.create () in+ Fiber.both+ (fun () ->+ Lwt_eio.run_lwt @@ fun () ->+ let* () = Lwt_eio.Promise.await_eio p in+ Fiber.yield ();+ assert false+ )+ (fun () -> Promise.resolve r ());;+WARNING: Attempt to perform effect in Lwt context+Exception: Invalid_argument "Attempt to perform effect in Lwt context".+```++`await_eio_result` resumes in Lwt mode:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Switch.run @@ fun sw ->+ let p = Fiber.fork_promise ~sw (fun () -> Fiber.yield (); traceln "Eio done") in+ Lwt_eio.run_lwt @@ fun () ->+ let* () = Lwt_eio.Promise.await_eio_result p in+ Fiber.yield ();+ assert false;;++Eio done+WARNING: Attempt to perform effect in Lwt context+Exception: Invalid_argument "Attempt to perform effect in Lwt context".+```++`run_eio` resumes in Lwt mode:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Switch.run @@ fun sw ->+ Lwt_eio.run_lwt @@ fun () ->+ let* () = Lwt_eio.run_eio Fiber.yield in+ Fiber.yield ();+ assert false;;+WARNING: Attempt to perform effect in Lwt context+Exception: Invalid_argument "Attempt to perform effect in Lwt context".+```++Debug mode is off by default:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->+ Lwt_eio.run_lwt @@ fun () ->+ Fiber.yield ();+ Lwt.return "Didn't notice";;+- : string = "Didn't notice"+``````File "README.md", line 1, characters 0-0:C:\cygwin64\bin\git.exe --no-pager diff --no-index --color=always -u --ignore-cr-at-eol _build/default/README.md _build/default/.mdx/README.md.correcteddiff --git a/_build/default/README.md b/_build/default/.mdx/README.md.correctedindex cb80495..c89cef0 100755--- a/_build/default/README.md+++ b/_build/default/.mdx/README.md.corrected@@ -1,278 +1,279 @@-# Lwt_eio - run Lwt code from within Eio--Lwt_eio is a Lwt engine that uses [Eio][].-It can be used to run Lwt and Eio code in a single domain.-It allows converting existing code to Eio incrementally.--See `lib/lwt_eio.mli` for the API.--The [examples](./examples/) directory contains some example programs and instructions on using them.--## Porting a Lwt Application to Eio--This guide will show how to migrate an existing Lwt application or library to Eio.-We'll start with this Lwt program, which reads in a list of lines, sorts them,-and writes the result to stdout:--```ocaml-# #require "lwt.unix";;-# open Lwt.Syntax;;--# let process_lines src fn =- let stream = Lwt_io.read_lines src in- let* lines = Lwt_stream.to_list stream in- let* lines = fn lines in- let rec write = function- | [] -> Lwt.return_unit- | x :: xs ->- let* () = Lwt_io.(write_line stdout) x in- write xs- in- let* () = write lines in- Lwt_io.(flush stdout);;-val process_lines :- Lwt_io.input_channel -> (string list -> string list Lwt.t) -> unit Lwt.t =- <fun>--# let sort src =- process_lines src @@ fun lines ->- let* () = Lwt.pause () in (* Simulate async work *)- Lwt.return (List.sort String.compare lines);;-val sort : Lwt_io.input_channel -> unit Lwt.t = <fun>--# Lwt_main.run begin- let input = Lwt_io.(of_bytes ~mode:input)- (Lwt_bytes.of_bytes (Bytes.of_string "b\na\nd\nc\n")) in- sort input- end;;-a-b-c-d-- : unit = ()+# Lwt_eio - run Lwt code from within Eio++Lwt_eio is a Lwt engine that uses [Eio][].+It can be used to run Lwt and Eio code in a single domain.+It allows converting existing code to Eio incrementally.++See `lib/lwt_eio.mli` for the API.++The [examples](./examples/) directory contains some example programs and instructions on using them.++## Porting a Lwt Application to Eio++This guide will show how to migrate an existing Lwt application or library to Eio.+We'll start with this Lwt program, which reads in a list of lines, sorts them,+and writes the result to stdout:++```ocaml+# #require "lwt.unix";;+# open Lwt.Syntax;;++# let process_lines src fn =+ let stream = Lwt_io.read_lines src in+ let* lines = Lwt_stream.to_list stream in+ let* lines = fn lines in+ let rec write = function+ | [] -> Lwt.return_unit+ | x :: xs ->+ let* () = Lwt_io.(write_line stdout) x in+ write xs+ in+ let* () = write lines in+ Lwt_io.(flush stdout);;+val process_lines :+ Lwt_io.input_channel -> (string list -> string list Lwt.t) -> unit Lwt.t =+ <fun>++# let sort src =+ process_lines src @@ fun lines ->+ let* () = Lwt.pause () in (* Simulate async work *)+ Lwt.return (List.sort String.compare lines);;+val sort : Lwt_io.input_channel -> unit Lwt.t = <fun>++# Lwt_main.run begin+ let input = Lwt_io.(of_bytes ~mode:input)+ (Lwt_bytes.of_bytes (Bytes.of_string "b\na\nd\nc\n")) in+ sort input+ end;;+a+b+c+d+- : unit = ()+```++The first step is to replace `Lwt_main.run`, and check that the program still works:++```ocaml+# #require "eio_main";;+# #require "lwt_eio";;+# open Eio.Std;;++# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->+ Lwt_eio.run_lwt @@ fun () ->+ let input = Lwt_io.(of_bytes ~mode:input)+ (Lwt_bytes.of_bytes (Bytes.of_string "b\na\nd\nc\n")) in+ sort input;;+a+b+c+d+- : unit = ()+```++Here, we're using the Eio event loop instead of the normal Lwt one,+but everything else stays the same:++1. `Eio_main.run` starts the Eio event loop, replacing `Lwt_main.run`.+2. `Lwt_eio.with_event_loop` starts the Lwt event loop, using Eio as its backend.+3. `Lwt_eio.run_lwt` switches from Eio context to Lwt context.++Any piece of code is either Lwt code or Eio code.+You use `run_lwt` and `run_eio` to switch back and forth as necessary+(`run_lwt` lets Eio code call Lwt code, while `run_eio` lets Lwt code call Eio).++Note: When I first tried the conversion, it failed with `Fatal error: exception Unhandled`+because I'd forgotten to flush stdout in the Lwt code.+That meant that `sort` returned before Lwt had completely finished and then it+tried to flush lazily after the Eio loop had finished, which is an error.++We can now start converting code to Eio.+There are several places we could start.+Here we'll begin with the `process_lines` function.+We'll take an Eio flow instead of a Lwt_io input:++```ocaml+# let process_lines src fn =+ let* lines =+ Lwt_eio.run_eio @@ fun () ->+ Eio.Buf_read.of_flow src ~max_size:max_int+ |> Eio.Buf_read.lines+ |> List.of_seq+ in+ let* lines = fn lines in+ let rec write = function+ | [] -> Lwt.return_unit+ | x :: xs ->+ let* () = Lwt_io.(write_line stdout) x in+ write xs+ in+ let* () = write lines in+ Lwt_io.(flush stdout);;+val process_lines :+ [> Eio__Flow.source_ty ] r ->+ (string list -> string list Lwt.t) -> unit Lwt.t = <fun>+```++Note that `process_lines` is still a Lwt function,+but it now uses `run_eio` internally to read from the input using Eio.++**Warning:** It's important not to call Eio functions directly from Lwt, but instead wrap such code with `run_eio`.+If you replace the `Lwt_eio.run_eio @@ fun () ->` line with `Lwt.return @@`+then it will appear to work in simple cases, but it will act as a blocking read from Lwt's point of view.+It's similar to trying to turn a blocking call like `Stdlib.input_line` into an asynchronous one+using `Lwt.return`. It doesn't actually make it concurrent.+Using `Lwt_eio.with_event_loop ~debug:true` will detect these problems, by blocking effects when in Lwt mode.++We can now test it using an Eio flow:++```ocaml+# let sort src =+ process_lines src @@ fun lines ->+ let* () = Lwt.pause () in (* Simulate async work *)+ Lwt.return (List.sort String.compare lines);;+val sort : [> Eio__Flow.source_ty ] r -> unit Lwt.t = <fun>++# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ Lwt_eio.run_lwt @@ fun () ->+ sort (Eio.Flow.string_source "b\na\nd\nc\n");;+a+b+c+d+- : unit = ()+```++Let's finish converting `process_lines`:++```ocaml+# let process_lines ~src ~dst fn =+ Eio.Buf_read.of_flow src ~max_size:max_int+ |> Eio.Buf_read.lines+ |> List.of_seq+ |> fn+ |> List.iter (fun line ->+ Eio.Flow.copy_string (line ^ "\n") dst+ );;+val process_lines :+ src:[> Eio__Flow.source_ty ] r ->+ dst:[> Eio.Flow.sink_ty ] r -> (string list -> string list) -> unit = <fun>+```++Now `process_lines` is an Eio function. The `Lwt.t` types have disappeared from its signature.++Note that we now take an extra `dst` argument for the output:+Eio functions should always receive access to external resources explicitly.++To use the new version, we'll have to update `sort` to wrap its Lwt callback:++```ocaml+# let sort ~src ~dst =+ process_lines ~src ~dst @@ fun lines ->+ Lwt_eio.run_lwt @@ fun () ->+ let* () = Lwt.pause () in (* Simulate async work *)+ Lwt.return (List.sort String.compare lines);;+val sort :+ src:[> Eio__Flow.source_ty ] r -> dst:[> Eio.Flow.sink_ty ] r -> unit =+ <fun>+```++`sort` itself now looks like a normal Eio function from its signature.+We can therefore now call it directly from Eio:++```ocaml+# Eio_main.run @@ fun env ->+ Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->+ sort+ ~src:(Eio.Flow.string_source "b\na\nd\nc\n")+ ~dst:env#stdout;;+a+b+c+d+- : unit = ()+```++Finally, we can convert `sort`'s callback to Eio code and drop the use of `Lwt` and `Lwt_eio` completely:++```ocaml+# let sort ~src ~dst =+ process_lines ~src ~dst @@ fun lines ->+ Fiber.yield (); (* Simulate async work *)+ List.sort String.compare lines;;+val sort :+ src:[> Eio__Flow.source_ty ] r -> dst:[> Eio.Flow.sink_ty ] r -> unit =+ <fun>++# Eio_main.run @@ fun env ->+ sort+ ~src:(Eio.Flow.string_source "b\na\nd\nc\n")+ ~dst:env#stdout;;+a+b+c+d+- : unit = ()+```++Key points:++- Start by replacing `Lwt_main.run` while keeping the rest of the code the same.++- Update your program piece by piece, using `Lwt_eio` when moving between Eio and Lwt contexts.++- Never call Eio code directly from Lwt code. Wrap it with `Lwt_eio.run_eio`.+ Simply wrapping the result of an Eio call with `Lwt.return` is NOT safe.++- Almost all uses of Lwt promises (`Lwt.t`) should disappear+ (do not blindly replace Lwt promises with Eio promises).++- You don't have to do the conversion in any particular order.++- You may need to make other changes to your API. In particular:++ - External resources (such as `stdout`, the network and the filesystem) should be passed as inputs to Eio code.++ - Take a `Switch.t` argument if your function creates fibers or file handles that out-live the function.++ - If you are writing a library that requires `Lwt_eio`, consider having its main function (if any)+ take a value of type `Lwt_eio.Token.t`. This will remind users of the library to initialise Lwt_eio first.++For a more in-depth example, see the [ICFP 2023 Lwt-to-Eio porting tutorial][icfp-tutorial].++## Limitations++- Lwt code can only run in a single domain, and using `Lwt_eio` does not change this.+ You can only run Lwt code in the domain that ran `Lwt_eio.with_event_loop`.++- `Lwt_eio` does not make your Lwt programs run faster than before.+ Lwt jobs are still run by Lwt, and do not take advantage of Eio's `io_uring` support, for example.++- `Lwt_unix.fork` internally uses `Unix.fork`, and therefore cannot be used when multiple domains are active.+++## How it works++Integration with Lwt is quite simple, as Lwt already has support for pluggable event loops.+When Lwt wants to wait for a file descriptor to become ready, it calls Lwt_eio,+which forks a new Eio fiber to perform the appropriate operation+(`Eio_unix.await_readable`, etc) and then calls Lwt's callback.++If Lwt wants to run a blocking operation, it will use a thread from its pool of systhreads to do that.+When the operation is complete, the systhread signals the main thread by making a notification file descriptor become ready,+and this is then picked up by the main event loop in the usual way.++Signals registered with `Lwt_unix.on_signal` likewise work by waking the main thread.++What all of this means is that Lwt threads and Eio fibers are scheduled using a single queue and do not starve each other+(any more than cooperative threads would do when not mixing concurrency systems).++If an Eio fiber is cancelled while running `run_lwt`, it cancels the Lwt promise too.+If the Lwt promise returned by `run_eio` is cancelled, the Eio fiber is cancelled too.++See [test/test.md](./test/test.md) for some tests of this.++[Eio]: https://github.com/ocaml-multicore/eio+[icfp-tutorial]: https://github.com/ocaml-multicore/icfp-2023-eio-tutorial```--The first step is to replace `Lwt_main.run`, and check that the program still works:--```ocaml-# #require "eio_main";;-# #require "lwt_eio";;-# open Eio.Std;;--# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ ->- Lwt_eio.run_lwt @@ fun () ->- let input = Lwt_io.(of_bytes ~mode:input)- (Lwt_bytes.of_bytes (Bytes.of_string "b\na\nd\nc\n")) in- sort input;;-a-b-c-d-- : unit = ()-```--Here, we're using the Eio event loop instead of the normal Lwt one,-but everything else stays the same:--1. `Eio_main.run` starts the Eio event loop, replacing `Lwt_main.run`.-2. `Lwt_eio.with_event_loop` starts the Lwt event loop, using Eio as its backend.-3. `Lwt_eio.run_lwt` switches from Eio context to Lwt context.--Any piece of code is either Lwt code or Eio code.-You use `run_lwt` and `run_eio` to switch back and forth as necessary-(`run_lwt` lets Eio code call Lwt code, while `run_eio` lets Lwt code call Eio).--Note: When I first tried the conversion, it failed with `Fatal error: exception Unhandled`-because I'd forgotten to flush stdout in the Lwt code.-That meant that `sort` returned before Lwt had completely finished and then it-tried to flush lazily after the Eio loop had finished, which is an error.--We can now start converting code to Eio.-There are several places we could start.-Here we'll begin with the `process_lines` function.-We'll take an Eio flow instead of a Lwt_io input:--```ocaml-# let process_lines src fn =- let* lines =- Lwt_eio.run_eio @@ fun () ->- Eio.Buf_read.of_flow src ~max_size:max_int- |> Eio.Buf_read.lines- |> List.of_seq- in- let* lines = fn lines in- let rec write = function- | [] -> Lwt.return_unit- | x :: xs ->- let* () = Lwt_io.(write_line stdout) x in- write xs- in- let* () = write lines in- Lwt_io.(flush stdout);;-val process_lines :- [> Eio__Flow.source_ty ] r ->- (string list -> string list Lwt.t) -> unit Lwt.t = <fun>-```--Note that `process_lines` is still a Lwt function,-but it now uses `run_eio` internally to read from the input using Eio.--**Warning:** It's important not to call Eio functions directly from Lwt, but instead wrap such code with `run_eio`.-If you replace the `Lwt_eio.run_eio @@ fun () ->` line with `Lwt.return @@`-then it will appear to work in simple cases, but it will act as a blocking read from Lwt's point of view.-It's similar to trying to turn a blocking call like `Stdlib.input_line` into an asynchronous one-using `Lwt.return`. It doesn't actually make it concurrent.-Using `Lwt_eio.with_event_loop ~debug:true` will detect these problems, by blocking effects when in Lwt mode.--We can now test it using an Eio flow:--```ocaml-# let sort src =- process_lines src @@ fun lines ->- let* () = Lwt.pause () in (* Simulate async work *)- Lwt.return (List.sort String.compare lines);;-val sort : [> Eio__Flow.source_ty ] r -> unit Lwt.t = <fun>--# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- Lwt_eio.run_lwt @@ fun () ->- sort (Eio.Flow.string_source "b\na\nd\nc\n");;-a-b-c-d-- : unit = ()-```--Let's finish converting `process_lines`:--```ocaml-# let process_lines ~src ~dst fn =- Eio.Buf_read.of_flow src ~max_size:max_int- |> Eio.Buf_read.lines- |> List.of_seq- |> fn- |> List.iter (fun line ->- Eio.Flow.copy_string (line ^ "\n") dst- );;-val process_lines :- src:[> Eio__Flow.source_ty ] r ->- dst:[> Eio.Flow.sink_ty ] r -> (string list -> string list) -> unit = <fun>-```--Now `process_lines` is an Eio function. The `Lwt.t` types have disappeared from its signature.--Note that we now take an extra `dst` argument for the output:-Eio functions should always receive access to external resources explicitly.--To use the new version, we'll have to update `sort` to wrap its Lwt callback:--```ocaml-# let sort ~src ~dst =- process_lines ~src ~dst @@ fun lines ->- Lwt_eio.run_lwt @@ fun () ->- let* () = Lwt.pause () in (* Simulate async work *)- Lwt.return (List.sort String.compare lines);;-val sort :- src:[> Eio__Flow.source_ty ] r -> dst:[> Eio.Flow.sink_ty ] r -> unit =- <fun>-```--`sort` itself now looks like a normal Eio function from its signature.-We can therefore now call it directly from Eio:--```ocaml-# Eio_main.run @@ fun env ->- Lwt_eio.with_event_loop ~debug:true ~clock:env#clock @@ fun _ ->- sort- ~src:(Eio.Flow.string_source "b\na\nd\nc\n")- ~dst:env#stdout;;-a-b-c-d-- : unit = ()-```--Finally, we can convert `sort`'s callback to Eio code and drop the use of `Lwt` and `Lwt_eio` completely:--```ocaml-# let sort ~src ~dst =- process_lines ~src ~dst @@ fun lines ->- Fiber.yield (); (* Simulate async work *)- List.sort String.compare lines;;-val sort :- src:[> Eio__Flow.source_ty ] r -> dst:[> Eio.Flow.sink_ty ] r -> unit =- <fun>--# Eio_main.run @@ fun env ->- sort- ~src:(Eio.Flow.string_source "b\na\nd\nc\n")- ~dst:env#stdout;;-a-b-c-d-- : unit = ()-```--Key points:--- Start by replacing `Lwt_main.run` while keeping the rest of the code the same.--- Update your program piece by piece, using `Lwt_eio` when moving between Eio and Lwt contexts.--- Never call Eio code directly from Lwt code. Wrap it with `Lwt_eio.run_eio`.- Simply wrapping the result of an Eio call with `Lwt.return` is NOT safe.--- Almost all uses of Lwt promises (`Lwt.t`) should disappear- (do not blindly replace Lwt promises with Eio promises).--- You don't have to do the conversion in any particular order.--- You may need to make other changes to your API. In particular:-- - External resources (such as `stdout`, the network and the filesystem) should be passed as inputs to Eio code.-- - Take a `Switch.t` argument if your function creates fibers or file handles that out-live the function.-- - If you are writing a library that requires `Lwt_eio`, consider having its main function (if any)- take a value of type `Lwt_eio.Token.t`. This will remind users of the library to initialise Lwt_eio first.--For a more in-depth example, see the [ICFP 2023 Lwt-to-Eio porting tutorial][icfp-tutorial].--## Limitations--- Lwt code can only run in a single domain, and using `Lwt_eio` does not change this.- You can only run Lwt code in the domain that ran `Lwt_eio.with_event_loop`.--- `Lwt_eio` does not make your Lwt programs run faster than before.- Lwt jobs are still run by Lwt, and do not take advantage of Eio's `io_uring` support, for example.--- `Lwt_unix.fork` internally uses `Unix.fork`, and therefore cannot be used when multiple domains are active.---## How it works--Integration with Lwt is quite simple, as Lwt already has support for pluggable event loops.-When Lwt wants to wait for a file descriptor to become ready, it calls Lwt_eio,-which forks a new Eio fiber to perform the appropriate operation-(`Eio_unix.await_readable`, etc) and then calls Lwt's callback.--If Lwt wants to run a blocking operation, it will use a thread from its pool of systhreads to do that.-When the operation is complete, the systhread signals the main thread by making a notification file descriptor become ready,-and this is then picked up by the main event loop in the usual way.--Signals registered with `Lwt_unix.on_signal` likewise work by waking the main thread.--What all of this means is that Lwt threads and Eio fibers are scheduled using a single queue and do not starve each other-(any more than cooperative threads would do when not mixing concurrency systems).--If an Eio fiber is cancelled while running `run_lwt`, it cancels the Lwt promise too.-If the Lwt promise returned by `run_eio` is cancelled, the Eio fiber is cancelled too.--See [test/test.md](./test/test.md) for some tests of this.--[Eio]: https://github.com/ocaml-multicore/eio-[icfp-tutorial]: https://github.com/ocaml-multicore/icfp-2023-eio-tutorial"C:\cygwin64\bin\bash.exe" "-lc" "cd /home/opam/src && opam exec -- dune build @install @check @runtest && rm -rf _build" failed with exit status 12026-04-14 11:23.23: Job failed: Failed: Build failed