{"id":13625191,"url":"https://github.com/ocaml-multicore/eio","last_synced_at":"2025-05-15T08:04:41.276Z","repository":{"id":36953949,"uuid":"343799490","full_name":"ocaml-multicore/eio","owner":"ocaml-multicore","description":"Effects-based direct-style IO for multicore OCaml","archived":false,"fork":false,"pushed_at":"2025-01-27T12:27:06.000Z","size":3927,"stargazers_count":602,"open_issues_count":65,"forks_count":74,"subscribers_count":19,"default_branch":"main","last_synced_at":"2025-04-14T13:08:00.387Z","etag":null,"topics":["concurrency","effects","io","multicore","ocaml"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ocaml-multicore.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-02T14:20:04.000Z","updated_at":"2025-04-12T22:29:14.000Z","dependencies_parsed_at":"2024-02-29T17:29:24.387Z","dependency_job_id":"bb6c824c-587c-4602-9de8-6b77fba134b4","html_url":"https://github.com/ocaml-multicore/eio","commit_stats":{"total_commits":780,"total_committers":32,"mean_commits":24.375,"dds":0.2205128205128205,"last_synced_commit":"fdd2593e33086b1a37d3afb21e669a6a77f6a37c"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Feio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Feio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Feio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ocaml-multicore%2Feio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ocaml-multicore","download_url":"https://codeload.github.com/ocaml-multicore/eio/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254301422,"owners_count":22047901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","effects","io","multicore","ocaml"],"created_at":"2024-08-01T21:01:51.995Z","updated_at":"2025-05-15T08:04:41.140Z","avatar_url":"https://github.com/ocaml-multicore.png","language":"OCaml","readme":"[API reference][Eio API] | [#eio Matrix chat](https://matrix.to/#/#eio:roscidus.com) | [Dev meetings][]\n\n# Eio \u0026mdash; Effects-Based Parallel IO for OCaml\n\nEio provides an effects-based direct-style IO stack for OCaml 5.\nFor example, you can use Eio to read and write files, make network connections,\nor perform CPU-intensive calculations, running multiple operations at the same time.\nIt aims to be easy to use, secure, well documented, and fast.\nA generic cross-platform API is implemented by optimised backends for different platforms.\nEio replaces existing concurrency libraries such as Lwt\n(Eio and Lwt libraries can also be used together).\n\n## Contents\n\n\u003c!-- vim-markdown-toc GFM --\u003e\n\n* [Motivation](#motivation)\n* [Eio packages](#eio-packages)\n* [Getting OCaml](#getting-ocaml)\n* [Getting Eio](#getting-eio)\n* [Running Eio](#running-eio)\n* [Testing with Mocks](#testing-with-mocks)\n* [Fibers](#fibers)\n* [Tracing](#tracing)\n* [Cancellation](#cancellation)\n* [Racing](#racing)\n* [Switches](#switches)\n* [Networking](#networking)\n* [Design Note: Capabilities](#design-note-capabilities)\n* [Buffered Reading and Parsing](#buffered-reading-and-parsing)\n* [Buffered Writing](#buffered-writing)\n* [Error Handling](#error-handling)\n* [Filesystem Access](#filesystem-access)\n* [Running processes](#running-processes)\n* [Time](#time)\n* [Multicore Support](#multicore-support)\n    * [Domain Manager](#domain-manager)\n    * [Executor Pool](#executor-pool)\n* [Synchronisation Tools](#synchronisation-tools)\n    * [Promises](#promises)\n    * [Example: Concurrent Cache](#example-concurrent-cache)\n    * [Streams](#streams)\n    * [Example: Worker Pool](#example-worker-pool)\n    * [Mutexes and Semaphores](#mutexes-and-semaphores)\n    * [Conditions](#conditions)\n    * [Example: Signal handlers](#example-signal-handlers)\n* [Design Note: Determinism](#design-note-determinism)\n* [Provider Interfaces](#provider-interfaces)\n* [Example Applications](#example-applications)\n* [Integrations](#integrations)\n    * [Async](#async)\n    * [Lwt](#lwt)\n    * [Unix and System Threads](#unix-and-system-threads)\n    * [Domainslib](#domainslib)\n    * [kcas](#kcas)\n* [Best Practices](#best-practices)\n    * [Switches](#switches-1)\n    * [Casting](#casting)\n    * [Passing env](#passing-env)\n* [Further Reading](#further-reading)\n\n\u003c!-- vim-markdown-toc --\u003e\n\n## Motivation\n\nThe `Unix` library provided with OCaml uses blocking IO operations, and is not well suited to concurrent programs such as network services or interactive applications.\nFor many years, the solution was to use libraries such as Lwt and Async, which provide a monadic interface.\nThese libraries allow writing code as if there were multiple threads of execution, each with their own stack, but the stacks are simulated using the heap.\n\nOCaml 5 added support for \"effects\", removing the need for monadic code here.\nUsing effects brings several advantages:\n\n1. It's faster, because no heap allocations are needed to simulate a stack.\n2. Concurrent code can be written in the same style as plain non-concurrent code.\n3. Because a real stack is used, backtraces from exceptions work as expected.\n4. Other features of the language (such as `try ... with ...`) can be used in concurrent code.\n\nAdditionally, modern operating systems provide high-performance alternatives to the old Unix `select` call.\nFor example, Linux's io_uring system has applications write the operations they want to perform to a ring buffer,\nwhich Linux handles asynchronously, and Eio can take advantage of this.\n\nYou can always [fall back to using Lwt libraries](#lwt) to provide missing features if necessary.\nSee [Awesome Multicore OCaml][] for links to other projects using Eio.\n\n## Eio packages\n\n- [Eio][] provides concurrency primitives (promises, etc.) and a high-level, cross-platform OS API.\n- [Eio_posix][] provides a cross-platform backend for these APIs for POSIX-type systems.\n- [Eio_linux][] provides a Linux io_uring backend for these APIs.\n- [Eio_windows][] is for use on Windows (incomplete - [help wanted](https://github.com/ocaml-multicore/eio/issues/125)).\n- [Eio_main][] selects an appropriate backend (e.g. `eio_linux` or `eio_posix`), depending on your platform.\n- [Eio_js][] allows Eio code to run in the browser, using `js_of_ocaml`.\n\n## Getting OCaml\n\nYou'll need OCaml 5.1.0 or later.\nYou can either install it yourself or build the included [Dockerfile](./Dockerfile).\n\nTo install it yourself:\n\n1. Make sure you have opam 2.1 or later (run `opam --version` to check).\n\n2. Use opam to install OCaml:\n\n   ```\n   opam switch create 5.2.0\n   ```\n\n## Getting Eio\n\nInstall `eio_main` (and `utop` if you want to try it interactively):\n\n```\nopam install eio_main utop\n```\n\nIf you want to install the latest unreleased development version of Eio, see [HACKING.md](./HACKING.md).\n\n## Running Eio\n\nTry out the examples interactively by running `utop` in the shell.\n\nFirst `require` the `eio_main` library. It's also convenient to open the [Eio.Std][]\nmodule, as follows. (The leftmost `#` shown below is the Utop prompt, so enter the text after the\nprompt and return after each line.)\n\n```ocaml\n# #require \"eio_main\";;\n# open Eio.Std;;\n```\n\nThis function writes a greeting to `out` using [Eio.Flow][]:\n\n```ocaml\nlet main out =\n  Eio.Flow.copy_string \"Hello, world!\\n\" out\n```\n\nWe use [Eio_main.run][] to run the event loop and call `main` from there:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  main (Eio.Stdenv.stdout env);;\nHello, world!\n- : unit = ()\n```\n\nNote that:\n\n- The `env` argument represents the standard environment of a Unix process, allowing it to interact with the outside world.\n  A program will typically start by extracting from `env` whatever things the program will need and then calling `main` with them.\n\n- The type of the `main` function here tells us that this program only interacts via the `out` flow.\n\n- `Eio_main.run` automatically calls the appropriate run function for your platform.\n  For example, on Linux this will call `Eio_linux.run`. For non-portable code you can use the platform-specific library directly.\n\nThis example can also be built using dune; see [examples/hello](./examples/hello/).\n\n## Testing with Mocks\n\nBecause external resources are provided to `main` as arguments, we can easily replace them with mocks for testing.\nFor example, instead of giving `main` the real standard output, we can have it write to a buffer:\n\n```ocaml\n# Eio_main.run @@ fun _env -\u003e\n  let buffer = Buffer.create 20 in\n  main (Eio.Flow.buffer_sink buffer);\n  traceln \"Main would print %S\" (Buffer.contents buffer);;\n+Main would print \"Hello, world!\\n\"\n- : unit = ()\n```\n\n[Eio.traceln][] provides convenient printf-style debugging, without requiring you to plumb `stderr` through your code.\nIt uses the `Format` module, so you can use the extended formatting directives here too.\n\nThe [Eio_mock][] library provides some convenient pre-built mocks:\n\n```ocaml\n# #require \"eio.mock\";;\n# Eio_main.run @@ fun _env -\u003e\n  main (Eio_mock.Flow.make \"mock-stdout\");;\n+mock-stdout: wrote \"Hello, world!\\n\"\n- : unit = ()\n```\n\n## Fibers\n\nHere's an example running two threads of execution concurrently using [Eio.Fiber][]:\n\n```ocaml\nlet main _env =\n  Fiber.both\n    (fun () -\u003e for x = 1 to 3 do traceln \"x = %d\" x; Fiber.yield () done)\n    (fun () -\u003e for y = 1 to 3 do traceln \"y = %d\" y; Fiber.yield () done);;\n```\n\n```ocaml\n# Eio_main.run main;;\n+x = 1\n+y = 1\n+x = 2\n+y = 2\n+x = 3\n+y = 3\n- : unit = ()\n```\n\nThe two fibers run on a single core, so only one can be running at a time.\nCalling an operation that performs an effect (such as `yield`) can switch to a different thread.\n\n## Tracing\n\nWhen OCaml's tracing is turned on, Eio writes events about many actions,\nsuch as creating fibers or resolving promises.\n\nYou can use [eio-trace][] to capture a trace and display it in a window.\nFor example, this is a trace of the counting example above:\n\n```\ndune build ./examples\neio-trace run -- ./_build/default/examples/both/main.exe\n```\n\n\u003cp align='center'\u003e\n  \u003cimg src=\"./doc/traces/both-posix.svg\"/\u003e\n\u003c/p\u003e\n\nThe upper horizontal bar is the initial fiber, and the brackets show `Fiber.both` creating a second fiber.\nThe green segments show when each fiber is running.\nNote that the output from `traceln` appears in the trace as well as on the console.\nIn the eio-trace window, scrolling with the mouse or touchpad will zoom in or out of the diagram.\n\nThird-party tools, such as [Olly][], can also consume this data.\n[examples/trace](./examples/trace/) shows how to consume the events manually.\n\n## Cancellation\n\nEvery fiber has a [cancellation context][Eio.Cancel].\nIf one of the `Fiber.both` fibers fails, the other is cancelled:\n\n```ocaml\n# Eio_main.run @@ fun _env -\u003e\n  Fiber.both\n    (fun () -\u003e for x = 1 to 3 do traceln \"x = %d\" x; Fiber.yield () done)\n    (fun () -\u003e failwith \"Simulated error\");;\n+x = 1\nException: Failure \"Simulated error\".\n```\n\n\u003cp align='center'\u003e\n  \u003cimg src=\"./doc/traces/cancel-posix.svg\"/\u003e\n\u003c/p\u003e\n\nWhat happened here was:\n\n1. `Fiber.both` created a new cancellation context for the child fibers.\n2. The first fiber (the lower one in the diagram) ran, printed `x = 1` and yielded.\n3. The second fiber raised an exception.\n4. `Fiber.both` caught the exception and cancelled the context.\n5. The first thread's `yield` raised a `Cancelled` exception there.\n6. Once both threads had finished, `Fiber.both` re-raised the original exception.\n\nThere is a tree of cancellation contexts for each domain, and every fiber is in one context.\nWhen an exception is raised, it propagates towards the root until handled, cancelling the other branches as it goes.\nYou should assume that any operation that can switch fibers can also raise a `Cancelled` exception if an uncaught exception\nreaches one of its ancestor cancellation contexts.\n\nIf you want to make an operation non-cancellable, wrap it with `Cancel.protect`\n(this creates a new context that isn't cancelled with its parent).\n\n## Racing\n\n`Fiber.first` returns the result of the first fiber to finish, cancelling the other one:\n\n```ocaml\n# Eio_main.run @@ fun _env -\u003e\n  let x =\n    Fiber.first\n      (fun () -\u003e\n        traceln \"first fiber delayed...\";\n        Fiber.yield ();\n        traceln \"delay over\";\n        \"a\"\n      )\n      (fun () -\u003e \"b\")\n  in\n  traceln \"x = %S\" x;;\n+first fiber delayed...\n+x = \"b\"\n- : unit = ()\n```\n\nNote: using `Fiber.first` to ensure that *exactly one* of two actions is performed is not reliable.\nThere is usually a possibility that both actions succeed at the same time (and one result is thrown away).\nFor example, if you ask Eio read from two sockets with `io_uring`\nthen the kernel may have already performed both reads by the time it tells Eio about the first one.\n\n## Switches\n\nA [switch][Eio.Switch] is used to group fibers together, so they can be waited on together.\nThis is a form of [structured concurrency][].\nFor example:\n\n```ocaml\n# Eio_main.run @@ fun _env -\u003e\n  Switch.run (fun sw -\u003e\n      for i = 1 to 3 do\n        Fiber.fork ~sw (fun () -\u003e\n            traceln \"Job %d starting\" i;\n            Fiber.yield ();\n            traceln \"%d done\" i;\n          );\n      done;\n      traceln \"All child fibers forked\";\n    );\n  traceln \"Switch is finished\";;\n+Job 1 starting\n+Job 2 starting\n+Job 3 starting\n+All child fibers forked\n+1 done\n+2 done\n+3 done\n+Switch is finished\n- : unit = ()\n```\n\n\u003cp align='center'\u003e\n  \u003cimg src=\"./doc/traces/switch-mock.svg\"/\u003e\n\u003c/p\u003e\n\n`Switch.run fn` creates a new switch `sw` and runs `fn sw`.\n`fn` may spawn new fibers and attach them to the switch.\nIt may also attach other resources such as open file handles.\n`Switch.run` waits until `fn` and all other attached fibers have finished, and then\nreleases any attached resources (e.g. closing all attached file handles).\n\nIf you call a function without giving it access to a switch,\nthen when the function returns you can be sure that any fibers it spawned have finished,\nand any files it opened have been closed.\nThis works because Eio does not provide e.g. a way to open a file without attaching it to a switch.\nIf a function doesn't have a switch and wants to open a file, it must use `Switch.run` to create one.\nBut then the function can't return until `Switch.run` does, at which point the file is closed.\n\nSo, a `Switch.run` puts a bound on the lifetime of things created within it,\nleading to clearer code and avoiding resource leaks.\nThe `Fiber.fork` call above creates a new fiber that continues running after `fork` returns,\nso it needs to take a switch argument.\n\nEvery switch also creates a new cancellation context.\nYou can use `Switch.fail` to mark the switch as failed and cancel all fibers within it.\nThe exception (or exceptions) passed to `fail` will be raised by `run` when the fibers have exited.\n\n## Networking\n\nEio provides an API for [networking][Eio.Net].\nHere is a server connection handler that handles an incoming connection by sending the client a message:\n\n```ocaml\nlet handle_client flow _addr =\n  traceln \"Server: got connection from client\";\n  Eio.Flow.copy_string \"Hello from server\" flow\n```\n\nWe can test it using a mock flow:\n\n```ocaml\n# Eio_mock.Backend.run @@ fun () -\u003e\n  let flow = Eio_mock.Flow.make \"flow\" in\n  let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 37568) in\n  handle_client flow addr;;\n+Server: got connection from client\n+flow: wrote \"Hello from server\"\n- : unit = ()\n```\n\nNote: `Eio_mock.Backend.run` can be used instead of `Eio_main.run` for tests that don't access the outside environment at all.\nIt doesn't support multiple domains, but this allows it to detect deadlocks automatically\n(a multi-domain loop has to assume it might get an event from another domain, and so must keep waiting).\n\nHere is a client that connects to address `addr` using network `net` and reads a message:\n\n```ocaml\nlet run_client ~net ~addr =\n  Switch.run ~name:\"client\" @@ fun sw -\u003e\n  traceln \"Client: connecting to server\";\n  let flow = Eio.Net.connect ~sw net addr in\n  (* Read all data until end-of-stream (shutdown): *)\n  traceln \"Client: received %S\" (Eio.Flow.read_all flow)\n```\n\nNote: the `flow` is attached to `sw` and will be closed automatically when it finishes.\nWe also named the switch here; this will appear in the trace output (see below).\n\nThis can also be tested on its own using a mock network:\n\n```ocaml\n# Eio_mock.Backend.run @@ fun () -\u003e\n  let net = Eio_mock.Net.make \"mocknet\" in\n  let flow = Eio_mock.Flow.make \"flow\" in\n  Eio_mock.Net.on_connect net [`Return flow];\n  Eio_mock.Flow.on_read flow [\n    `Return \"(packet 1)\";\n    `Yield_then (`Return \"(packet 2)\");\n    `Raise End_of_file;\n  ];\n  let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 8080) in\n  run_client ~net ~addr;;\n+Client: connecting to server\n+mocknet: connect to tcp:127.0.0.1:8080\n+flow: read \"(packet 1)\"\n+flow: read \"(packet 2)\"\n+Client: received \"(packet 1)(packet 2)\"\n+flow: closed\n- : unit = ()\n```\n\n`Eio.Net.run_server` runs a loop accepting clients and handling them (concurrently):\n\n```ocaml\nlet run_server socket =\n  Eio.Net.run_server socket handle_client\n    ~on_error:(traceln \"Error handling connection: %a\" Fmt.exn)\n```\n\nNote: when `handle_client` finishes, `run_server` closes the flow automatically.\n\nWe can now run the client and server together using the real network (in a single process):\n\n```ocaml\nlet main ~net ~addr =\n  Switch.run ~name:\"main\" @@ fun sw -\u003e\n  let server = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:5 addr in\n  Fiber.fork_daemon ~sw (fun () -\u003e run_server server);\n  run_client ~net ~addr\n```\n\n`Fiber.fork_daemon` creates a new fiber and then cancels it when the switch finishes.\nWe need that here because otherwise the server would keep waiting for new connections and\nthe test would never finish.\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  main\n    ~net:(Eio.Stdenv.net env)\n    ~addr:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 8080));;\n+Client: connecting to server\n+Server: got connection from client\n+Client: received \"Hello from server\"\n- : unit = ()\n```\n\n\u003cp align='center'\u003e\n  \u003cimg src=\"./doc/traces/net-posix.svg\"/\u003e\n\u003c/p\u003e\n\nSee [examples/net](./examples/net/) for a more complete example.\n\n## Design Note: Capabilities\n\nEio follows the principles of capability-based security.\nThe key idea here is that the lambda calculus already contains a perfectly good security system:\na function can only access things that are in its scope.\nIf we can avoid breaking this model (for example, by adding global variables to our language)\nthen we can reason about the security properties of code quite easily.\n\nConsider the network example in the previous section.\nImagine this is a large program and we want to know:\n\n1. Does this program modify the filesystem?\n2. Does this program send telemetry data over the network?\n\nIn a capability-safe language, we don't have to read the entire code-base to find the answers:\n\n- All authority starts at the (privileged) `Eio_main.run` function with the `env` parameter,\n  so we must check this code.\n\n- Only `env`'s network access is used, so we know this program doesn't access the filesystem,\n  answering question 1 immediately.\n\n- To check whether telemetry is sent, we need to follow the `net` authority as it is passed to `main`.\n\n- `main` uses `net` to open a listening socket on the loopback interface, which it passes to `run_server`.\n  `run_server` does not get the full `net` access, so we probably don't need to read that code; however,\n  we might want to check whether we granted other parties access to this port on our loopback network.\n\n- `run_client` does get `net`, so we do need to read that.\n  We could make that code easier to audit by passing it `(fun () -\u003e Eio.Net.connect net addr)` instead of `net` .\n  Then we could see that `run_client` could only connect to our loopback address.\n\nSince OCaml is not a capability language, code can ignore Eio and use the non-capability APIs directly.\nHowever, it still makes non-malicious code easier to understand and test,\nand may allow for an extension to the language in the future.\n\nThe [Lambda Capabilities][] blog post provides a more detailed introduction to capabilities,\nwritten for functional programmers.\n\n## Buffered Reading and Parsing\n\nReading from an Eio flow directly may give you more or less data than you wanted.\nFor example, if you want to read a line of text from a TCP stream,\nthe flow will tend to give you the data in packet-sized chunks, not lines.\nTo solve this, you can wrap the flow with a [buffer][Eio.Buf_read] and read from that.\n\nHere's a simple command-line interface that reads `stdin` one line at a time:\n\n```ocaml\nlet cli ~stdin ~stdout =\n  let buf = Eio.Buf_read.of_flow stdin ~initial_size:100 ~max_size:1_000_000 in\n  while true do\n    let line = Eio.Buf_read.line buf in\n    traceln \"\u003e %s\" line;\n    match line with\n    | \"h\" | \"help\" -\u003e Eio.Flow.copy_string \"It's just an example\\n\" stdout\n    | x -\u003e Eio.Flow.copy_string (Fmt.str \"Unknown command %S\\n\" x) stdout\n  done\n```\n\nLet's try it with some test data (you could use the real stdin if you prefer):\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  cli\n    ~stdin:(Eio.Flow.string_source \"help\\nexit\\nquit\\nbye\\nstop\\n\")\n    ~stdout:(Eio.Stdenv.stdout env);;\n+\u003e help\nIt's just an example\n+\u003e exit\nUnknown command \"exit\"\n+\u003e quit\nUnknown command \"quit\"\n+\u003e bye\nUnknown command \"bye\"\n+\u003e stop\nUnknown command \"stop\"\nException: End_of_file.\n```\n\n`Buf_read.of_flow` allocates an internal buffer (with the given `initial_size`).\nWhen you try to read a line from it, it will take a whole line from the buffer if possible.\nIf not, it will ask the underlying flow for the next chunk of data, until it has enough.\n\nFor high performance applications, you should use a larger initial buffer\nso that fewer reads on the underlying flow are needed.\n\nIf the user enters a line that doesn't fit in the buffer then the buffer will be enlarged as needed.\nHowever, it will raise an exception if the buffer would need to grow above `max_size`.\nThis is useful when handling untrusted input, since otherwise when you try to read one line an\nattacker could just keep sending e.g. 'x' characters until your service ran out of memory and crashed.\n\nAs well as calling individual parsers (like `line`) directly,\nyou can also build larger parsers from smaller ones.\nFor example:\n\n```ocaml\nopen Eio.Buf_read.Syntax\n\ntype message = { src : string; body : string }\n\nlet message =\n  let+ src = Eio.Buf_read.(string \"FROM:\" *\u003e line)\n  and+ body = Eio.Buf_read.take_all in\n  { src; body }\n```\n\n```ocaml\n# Eio_main.run @@ fun _ -\u003e\n  let flow = Eio.Flow.string_source \"FROM:Alice\\nHello!\\n\" in\n  match Eio.Buf_read.parse message flow ~max_size:1024 with\n  | Ok { src; body } -\u003e traceln \"%s sent %S\" src body\n  | Error (`Msg err) -\u003e traceln \"Parse failed: %s\" err;;\n+Alice sent \"Hello!\\n\"\n- : unit = ()\n```\n\n## Buffered Writing\n\nFor performance, it's often useful to batch up writes and send them all in one go.\nFor example, consider sending an HTTP response without buffering:\n\n```ocaml\nlet send_response socket =\n  Eio.Flow.copy_string \"HTTP/1.1 200 OK\\r\\n\" socket;\n  Eio.Flow.copy_string \"\\r\\n\" socket;\n  Fiber.yield ();       (* Simulate delayed generation of body *)\n  Eio.Flow.copy_string \"Body data\" socket\n```\n\n```ocaml\n# Eio_main.run @@ fun _ -\u003e\n  send_response (Eio_mock.Flow.make \"socket\");;\n+socket: wrote \"HTTP/1.1 200 OK\\r\\n\"\n+socket: wrote \"\\r\\n\"\n+socket: wrote \"Body data\"\n- : unit = ()\n```\n\nThe socket received three writes, perhaps sending three separate packets over the network.\nWe can wrap a flow with [Eio.Buf_write][] to avoid this:\n\n```ocaml\nmodule Write = Eio.Buf_write\n\nlet send_response socket =\n  Write.with_flow socket @@ fun w -\u003e\n  Write.string w \"HTTP/1.1 200 OK\\r\\n\";\n  Write.string w \"\\r\\n\";\n  Fiber.yield ();       (* Simulate delayed generation of body *)\n  Write.string w \"Body data\"\n```\n\n```ocaml\n# Eio_main.run @@ fun _ -\u003e\n  send_response (Eio_mock.Flow.make \"socket\");;\n+socket: wrote \"HTTP/1.1 200 OK\\r\\n\"\n+              \"\\r\\n\"\n+socket: wrote \"Body data\"\n- : unit = ()\n```\n\nNow the first two writes were combined and sent together.\n\n## Error Handling\n\nErrors interacting with the outside world are indicated by the `Eio.Io (err, context)` exception.\nThis is roughly equivalent to the `Unix.Unix_error` exception from the OCaml standard library.\n\nThe `err` field describes the error using nested error codes,\nallowing you to match on either specific errors or whole classes of errors at once.\nFor example:\n\n```ocaml\nlet test r =\n  try Eio.Buf_read.line r\n  with\n  | Eio.Io (Eio.Net.E Connection_reset Eio_unix.Unix_error _, _) -\u003e \"Unix connection reset\"\n  | Eio.Io (Eio.Net.E Connection_reset _, _) -\u003e \"Connection reset\"\n  | Eio.Io (Eio.Net.E _, _) -\u003e \"Some network error\"\n  | Eio.Io _ -\u003e \"Some I/O error\"\n```\n\nFor portable code, you will want to avoid matching backend-specific errors, so you would avoid the first case.\nThe `Eio.Io` type is extensible, so libraries can also add additional top-level error types if needed.\n\n`Io` errors also allow adding extra context information to the error.\nFor example, this HTTP GET function adds the URL to any IO error:\n\n```ocaml\nlet get ~net ~host ~path =\n  try\n    Eio.Net.with_tcp_connect net ~host ~service:\"http\" @@ fun _flow -\u003e\n    \"...\"\n  with Eio.Io _ as ex -\u003e\n    let bt = Printexc.get_raw_backtrace () in\n    Eio.Exn.reraise_with_context ex bt \"fetching http://%s/%s\" host path;;\n```\n\nIf we test it using a mock network that returns a timeout,\nwe get a useful error message telling us the IP address and port of the failed attempt,\nextended with the hostname we used to get that,\nand then extended again by our `get` function with the full URL:\n\n```ocaml\n# Eio_mock.Backend.run @@ fun () -\u003e\n  let net = Eio_mock.Net.make \"mocknet\" in\n  Eio_mock.Net.on_getaddrinfo net [`Return [`Tcp (Eio.Net.Ipaddr.V4.loopback, 80)]];\n  Eio_mock.Net.on_connect net [`Raise (Eio.Net.err (Connection_failure Timeout))];\n  get ~net ~host:\"example.com\" ~path:\"index.html\";;\n+mocknet: getaddrinfo ~service:http example.com\n+mocknet: connect to tcp:127.0.0.1:80\nException:\nEio.Io Net Connection_failure Timeout,\n  connecting to tcp:127.0.0.1:80,\n  connecting to \"example.com\":http,\n  fetching http://example.com/index.html\n```\n\nTo get more detailed information, you can enable backtraces by setting `OCAMLRUNPARAM=b`\nor by calling `Printexc.record_backtrace true`, as usual.\n\nWhen writing MDX tests that depend on getting the exact error output,\nit can be annoying to have the full backend-specific error displayed:\n\n\u003c!-- $MDX non-deterministic=command --\u003e\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let net = Eio.Stdenv.net env in\n  Switch.run @@ fun sw -\u003e\n  Eio.Net.connect ~sw net (`Tcp (Eio.Net.Ipaddr.V4.loopback, 1234));;\nException:\nEio.Io Net Connection_failure Refused Unix_error (Connection refused, \"connect\", \"\"),\n  connecting to tcp:127.0.0.1:1234\n```\n\nIf we ran this using another backend, the `Unix_error` part might change.\nTo avoid this problem, you can use `Eio.Exn.Backend.show` to hide the backend-specific part of errors:\n\n```ocaml\n# Eio.Exn.Backend.show := false;;\n- : unit = ()\n\n# Eio_main.run @@ fun env -\u003e\n  let net = Eio.Stdenv.net env in\n  Switch.run @@ fun sw -\u003e\n  Eio.Net.connect ~sw net (`Tcp (Eio.Net.Ipaddr.V4.loopback, 1234));;\nException:\nEio.Io Net Connection_failure Refused _,\n  connecting to tcp:127.0.0.1:1234\n```\n\nWe'll leave it like that for the rest of this file,\nso the examples can be tested automatically by MDX.\n\n## Filesystem Access\n\nAccess to the filesystem is performed using [Eio.Path][].\nAn `'a Path.t` is a pair of a capability to a base directory (of type `'a`) and a string path relative to that.\nTo append to the string part, it's convenient to use the `/` operator:\n\n```ocaml\nlet ( / ) = Eio.Path.( / )\n```\n\n\u003c!--\nCleanup previous runs due to [dune runtest --watch] not doing it\n```ocaml\nEio_main.run @@ fun env -\u003e\nlet cwd = Eio.Stdenv.cwd env in\n[\"link-to-dir1\"; \"link-to-tmp\"; \"test.txt\"; \"dir1\"]\n|\u003e List.iter (fun p -\u003e Eio.Path.rmtree ~missing_ok:true (cwd / p))\n```\n--\u003e\n\n`env` provides two initial paths:\n\n- `cwd` restricts access to files beneath the current working directory.\n- `fs` provides full access (just like OCaml's stdlib).\n\nYou can save a whole file using `Path.save`:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let path = Eio.Stdenv.cwd env / \"test.txt\" in\n  traceln \"Saving to %a\" Eio.Path.pp path;\n  Eio.Path.save ~create:(`Exclusive 0o600) path \"line one\\nline two\\n\";;\n+Saving to \u003ccwd:test.txt\u003e\n- : unit = ()\n```\n\nFor more control, use `Path.open_out` (or `with_open_out`) to get a flow.\n\nTo load a file, you can use `load` to read the whole thing into a string,\n`Path.open_in` (or `with_open_in`) to get a flow, or `Path.with_lines` to stream\nthe lines (a convenience function that uses `Buf_read.lines`):\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let path = Eio.Stdenv.cwd env / \"test.txt\" in\n  Eio.Path.with_lines path (fun lines -\u003e\n     Seq.iter (traceln \"Processing %S\") lines\n  );;\n+Processing \"line one\"\n+Processing \"line two\"\n- : unit = ()\n```\n\nAccess to `cwd` only grants access to that sub-tree:\n\n```ocaml\nlet try_save path data =\n  match Eio.Path.save ~create:(`Exclusive 0o600) path data with\n  | () -\u003e traceln \"save %a : ok\" Eio.Path.pp path\n  | exception ex -\u003e traceln \"%a\" Eio.Exn.pp ex\n\nlet try_mkdir path =\n  match Eio.Path.mkdir path ~perm:0o700 with\n  | () -\u003e traceln \"mkdir %a : ok\" Eio.Path.pp path\n  | exception ex -\u003e traceln \"%a\" Eio.Exn.pp ex\n```\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let cwd = Eio.Stdenv.cwd env in\n  try_mkdir (cwd / \"dir1\");\n  try_mkdir (cwd / \"../dir2\");\n  try_mkdir (cwd / \"/tmp/dir3\");;\n+mkdir \u003ccwd:dir1\u003e : ok\n+Eio.Io Fs Permission_denied _, creating directory \u003ccwd:../dir2\u003e\n+Eio.Io Fs Permission_denied _, creating directory \u003ccwd:/tmp/dir3\u003e\n- : unit = ()\n```\n\nThe checks also apply to following symlinks:\n\n```ocaml\n# Unix.symlink \"dir1\" \"link-to-dir1\";\n  Unix.symlink (Filename.get_temp_dir_name ()) \"link-to-tmp\";;\n- : unit = ()\n\n# Eio_main.run @@ fun env -\u003e\n  let cwd = Eio.Stdenv.cwd env in\n  try_save (cwd / \"dir1/file1\") \"A\";\n  try_save (cwd / \"link-to-dir1/file2\") \"B\";\n  try_save (cwd / \"link-to-tmp/file3\") \"C\";;\n+save \u003ccwd:dir1/file1\u003e : ok\n+save \u003ccwd:link-to-dir1/file2\u003e : ok\n+Eio.Io Fs Permission_denied _, opening \u003ccwd:link-to-tmp/file3\u003e\n- : unit = ()\n```\n\nYou can use `open_dir` (or `with_open_dir`) to create a restricted capability to a subdirectory:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let cwd = Eio.Stdenv.cwd env in\n  Eio.Path.with_open_dir (cwd / \"dir1\") @@ fun dir1 -\u003e\n  try_save (dir1 / \"file4\") \"D\";\n  try_save (dir1 / \"../file5\") \"E\";;\n+save \u003cdir1:file4\u003e : ok\n+Eio.Io Fs Permission_denied _, opening \u003cdir1:../file5\u003e\n- : unit = ()\n```\n\nYou only need to use `open_dir` if you want to create a new sandboxed environment.\nYou can use a single base directory object to access all paths beneath it,\nand this allows following symlinks within that subtree.\n\nA program that operates on the current directory will probably want to use `cwd`,\nwhereas a program that accepts a path from the user will probably want to use `fs`,\nperhaps with `open_dir` to constrain all access to be within that directory.\n\nOn systems that provide the [cap_enter][] system call, you can ask the OS to reject accesses\nthat don't use capabilities.\n[examples/capsicum/](./examples/capsicum/) contains an example that\nrestricts itself to using a directory passed on the command-line, and then\ntries reading `/etc/passwd` via the stdlib.\nRunning on FreeBSD, you should see:\n\n```\nmkdir /tmp/cap\ndune exec -- ./examples/capsicum/main.exe /tmp/cap\n+Opened directory \u003cfs:/tmp/cap\u003e        \n+Capsicum mode enabled\n+Using the file-system via the directory resource works:\n+Writing \u003ccap:capsicum-test.txt\u003e...\n+Read: \"A test file\"\n+Bypassing Eio and accessing other resources should fail in Capsicum mode:\nFatal error: exception Sys_error(\"/etc/passwd: Not permitted in capability mode\")\n```\n\n## Running processes\n\nSpawning a child process can be done using the [Eio.Process][] module:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let proc_mgr = Eio.Stdenv.process_mgr env in\n  Eio.Process.run proc_mgr [\"echo\"; \"hello\"];;\nhello\n- : unit = ()\n```\n\nThere are various optional arguments for setting the process's current directory or connecting up the standard streams.\nFor example, we can use `tr` to convert some text to upper-case:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let proc_mgr = Eio.Stdenv.process_mgr env in\n  Eio.Process.run proc_mgr [\"tr\"; \"a-z\"; \"A-Z\"]\n    ~stdin:(Eio.Flow.string_source \"One two three\\n\");;\nONE TWO THREE\n- : unit = ()\n```\n\nIf you want to capture the output of a process, you can provide a suitable `Eio.Flow.sink` as the `stdout` argument,\nor use the `parse_out` convenience wrapper:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let proc_mgr = Eio.Stdenv.process_mgr env in\n  Eio.Process.parse_out proc_mgr Eio.Buf_read.line [\"echo\"; \"hello\"];;\n- : string = \"hello\"\n```\n\nAll process functions either return the exit status or check that it was zero (success):\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let proc_mgr = Eio.Stdenv.process_mgr env in\n  Eio.Process.parse_out proc_mgr Eio.Buf_read.take_all [\"sh\"; \"-c\"; \"exit 3\"];;\nException:\nEio.Io Process Child_error Exited (code 3),\n  running command: sh -c \"exit 3\"\n```\n\n`Process.spawn` and `Process.await` give more control over the process's lifetime\nand exit status, and `Eio_unix.Process` gives more control over passing file\ndescriptors (on systems that support them).\n\n## Time\n\nThe standard environment provides a [clock][Eio.Time] with the usual POSIX time:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let clock = Eio.Stdenv.clock env in\n  traceln \"The time is now %f\" (Eio.Time.now clock);;\n+The time is now 1623940778.270336\n- : unit = ()\n```\n\nThe mock backend provides a mock clock that advances automatically where there is nothing left to do:\n\n```ocaml\n# Eio_mock.Backend.run_full @@ fun env -\u003e\n  let clock = Eio.Stdenv.clock env in\n  traceln \"Sleeping for five seconds...\";\n  Eio.Time.sleep clock 5.0;\n  traceln \"Resumed\";;\n+Sleeping for five seconds...\n+mock time is now 5\n+Resumed\n- : unit = ()\n```\n\nNote: You could also just use `Eio_unix.sleep 5.0` if you don't want to pass a clock around.\nThis is especially useful if you need to insert a delay for some quick debugging.\n\n## Multicore Support\n\nOCaml allows a program to create multiple *domains* in which to run code, allowing multiple CPUs to be used at once.\nFibers are scheduled cooperatively within a single domain, but fibers in different domains run in parallel.\nThis is useful to perform CPU-intensive operations quickly\n(though extra care needs to be taken when using multiple cores; see the [Multicore Guide](./doc/multicore.md) for details).\n\n### Domain Manager\n\n[Eio.Domain_manager][] provides a basic API for spawning domains.\nFor example, let's say we have a CPU intensive task:\n\n```ocaml\nlet sum_to n =\n  traceln \"Starting CPU-intensive task...\";\n  let total = ref 0 in\n  for i = 1 to n do\n    total := !total + i\n  done;\n  traceln \"Finished\";\n  !total\n```\n\nWe can use the domain manager to run this in a separate domain:\n\n```ocaml\nlet main ~domain_mgr =\n  let test n =\n    traceln \"sum 1..%d = %d\" n\n      (Eio.Domain_manager.run domain_mgr\n        (fun () -\u003e sum_to n))\n  in\n  Fiber.both\n    (fun () -\u003e test 100000)\n    (fun () -\u003e test 50000)\n```\n\n\u003c!-- $MDX non-deterministic=output --\u003e\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  main ~domain_mgr:(Eio.Stdenv.domain_mgr env);;\n+Starting CPU-intensive task...\n+Starting CPU-intensive task...\n+Finished\n+sum 1..50000 = 1250025000\n+Finished\n+sum 1..100000 = 5000050000\n- : unit = ()\n```\n\n\u003cp align='center'\u003e\n  \u003cimg src=\"./doc/traces/multicore-posix.svg\"/\u003e\n\u003c/p\u003e\n\nNotes:\n\n- `traceln` can be used safely from multiple domains.\n  It takes a mutex, so that trace lines are output atomically.\n- The exact `traceln` output of this example is non-deterministic,\n  because the OS is free to schedule domains as it likes.\n- You must ensure that the function passed to `run` doesn't have access to any non-threadsafe values.\n  The type system does not check this.\n- `Domain_manager.run` waits for the domain to finish, but it allows other fibers to run while waiting.\n  This is why we use `Fiber.both` to create multiple fibers.\n\n### Executor Pool\n\nAn [Eio.Executor_pool][] distributes jobs among a pool of domain workers.\nDomains are reused and can execute multiple jobs concurrently.\n\nEach domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.\nThe `~weight` represents the expected proportion of a CPU core that the job will take up.\nJobs are queued up if they cannot be started immediately due to all domain workers being busy (`\u003e= 1.0`).\n\nThis is the recommended way of leveraging OCaml 5's multicore capabilities.\n\nUsually you will only want one pool for an entire application, so the pool is typically created when the application starts:\n\n\u003c!-- $MDX skip --\u003e\n```ocaml\nlet () =\n  Eio_main.run @@ fun env -\u003e\n  Switch.run @@ fun sw -\u003e\n  let dm = Eio.Stdenv.domain_mgr env in\n  main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)\n```\n\nThe pool starts its domain workers immediately upon creation.\n\nThe pool will not block our switch `sw` from completing;\nwhen the switch finishes, all domain workers and running jobs are cancelled.\n\n`~domain_count` is the number of domain workers to create.\nThe total number of domains should not exceed `Domain.recommended_domain_count` or the number of cores on your system.\n\nWe can run the previous example using an Executor Pool like this:\n\n```ocaml\nlet main ~pool =\n  let test n =\n    traceln \"sum 1..%d = %d\" n\n      (Eio.Executor_pool.submit_exn pool ~weight:1.0\n        (fun () -\u003e sum_to n))\n  in\n  Fiber.both\n    (fun () -\u003e test 100000)\n    (fun () -\u003e test 50000)\n```\n\n\u003c!-- $MDX non-deterministic=output --\u003e\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  Switch.run @@ fun sw -\u003e\n  let dm = Eio.Stdenv.domain_mgr env in\n  main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)\n+Starting CPU-intensive task...\n+Starting CPU-intensive task...\n+Finished\n+sum 1..50000 = 1250025000\n+Finished\n+sum 1..100000 = 5000050000\n- : unit = ()\n```\n`~weight` is the anticipated proportion of a CPU core used by the job.\nIn other words, the fraction of time actively spent executing OCaml code, not just waiting for I/O or system calls.\nIn the above code snippet we use `~weight:1.0` because the job is entirely CPU-bound: it never waits for I/O or other syscalls.\n`~weight` must be `\u003e= 0.0` and `\u003c= 1.0`.\nExample: given an IO-bound job that averages 2% of one CPU core, pass `~weight:0.02`.\n\nEach domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.\n \n## Synchronisation Tools\n\nEio provides several sub-modules for communicating between fibers,\nand these work even when the fibers are running in different domains.\n\n### Promises\n\n[Promises][Eio.Promise] are a simple and reliable way to communicate between fibers.\nOne fiber can wait for a promise and another can resolve it:\n\n```ocaml\n# Eio_main.run @@ fun _ -\u003e\n  let promise, resolver = Promise.create () in\n  Fiber.both\n    (fun () -\u003e\n      traceln \"Waiting for promise...\";\n      let x = Promise.await promise in\n      traceln \"x = %d\" x\n    )\n    (fun () -\u003e\n      traceln \"Resolving promise\";\n      Promise.resolve resolver 42\n    );;\n+Waiting for promise...\n+Resolving promise\n+x = 42\n- : unit = ()\n```\n\nA promise is initially \"unresolved\", and can only be resolved once.\nAwaiting a promise that is already resolved immediately returns the resolved value.\n\nPromises are one of the easiest tools to use safely:\nit doesn't matter whether you wait on a promise before or after it is resolved,\nand multiple fibers can wait for the same promise and will get the same result.\nPromises are thread-safe; you can wait for a promise in one domain and resolve it in another.\n\nPromises are also useful for integrating with callback-based libraries. For example:\n\n```ocaml\nlet wrap fn x =\n  let promise, resolver = Promise.create () in\n  fn x\n    ~on_success:(Promise.resolve_ok resolver)\n    ~on_error:(Promise.resolve_error resolver);\n  Promise.await_exn promise\n```\n\n### Example: Concurrent Cache\n\nHere's an example using promises to cache lookups,\nwith the twist that another user might ask the cache for the value while it's still adding it.\nWe don't want to start a second fetch in that case, so instead we just store promises in the cache:\n\n```ocaml\nlet make_cache fn =\n  let tbl = Hashtbl.create 10 in\n  fun key -\u003e\n    match Hashtbl.find_opt tbl key with\n    | Some p -\u003e Promise.await_exn p\n    | None -\u003e\n      let p, r = Promise.create () in\n      Hashtbl.add tbl key p;\n      match fn key with\n      | v -\u003e Promise.resolve_ok r v; v\n      | exception ex -\u003e Promise.resolve_error r ex; raise ex\n```\n\nNotice that we store the new promise in the cache immediately,\nwithout doing anything that might switch to another fiber.\n\nWe can use it like this:\n\n```ocaml\n# let fetch url =\n    traceln \"Fetching %S...\" url;\n    Fiber.yield ();             (* Simulate work... *)\n    traceln \"Got response for %S\" url;\n    if url = \"http://example.com\" then \"\u003ch1\u003eExample.com\u003c/h1\u003e\"\n    else failwith \"404 Not Found\";;\nval fetch : string -\u003e string = \u003cfun\u003e\n\n# Eio_main.run @@ fun _ -\u003e\n  let c = make_cache fetch in\n  let test url =\n    traceln \"Requesting %s...\" url;\n    match c url with\n    | page -\u003e traceln \"%s -\u003e %s\" url page\n    | exception ex -\u003e traceln \"%s -\u003e %a\" url Fmt.exn ex\n  in\n  Fiber.List.iter test [\n    \"http://example.com\";\n    \"http://example.com\";\n    \"http://bad.com\";\n    \"http://bad.com\";\n  ];;\n+Requesting http://example.com...\n+Fetching \"http://example.com\"...\n+Requesting http://example.com...\n+Requesting http://bad.com...\n+Fetching \"http://bad.com\"...\n+Requesting http://bad.com...\n+Got response for \"http://example.com\"\n+http://example.com -\u003e \u003ch1\u003eExample.com\u003c/h1\u003e\n+Got response for \"http://bad.com\"\n+http://bad.com -\u003e Failure(\"404 Not Found\")\n+http://example.com -\u003e \u003ch1\u003eExample.com\u003c/h1\u003e\n+http://bad.com -\u003e Failure(\"404 Not Found\")\n- : unit = ()\n```\n\n`Fiber.List.iter` is like `List.iter` but doesn't wait for each job to finish before starting the next.\nNotice that we made four requests, but only started two download operations.\n\nThis version of the cache remembers failed lookups too.\nYou could modify it to remove the entry on failure,\nso that all clients currently waiting still fail,\nbut any future client asking for the failed resource will trigger a new download.\n\nThis cache is not thread-safe.\nYou will need to add a mutex if you want to share it between domains.\n\n### Streams\n\nA [stream][Eio.Stream] is a bounded queue. Reading from an empty stream waits until an item is available.\nWriting to a full stream waits for space.\n\n```ocaml\n# Eio_main.run @@ fun _ -\u003e\n  let stream = Eio.Stream.create 2 in\n  Fiber.both\n    (fun () -\u003e\n       for i = 1 to 5 do\n         traceln \"Adding %d...\" i;\n         Eio.Stream.add stream i\n       done\n    )\n    (fun () -\u003e\n       for i = 1 to 5 do\n         let x = Eio.Stream.take stream in\n         traceln \"Got %d\" x;\n         Fiber.yield ()\n       done\n    );;\n+Adding 1...\n+Adding 2...\n+Adding 3...\n+Got 1\n+Adding 4...\n+Got 2\n+Adding 5...\n+Got 3\n+Got 4\n+Got 5\n- : unit = ()\n```\n\nHere, we create a stream with a maximum size of 2 items.\nThe first fiber added 1 and 2 to the stream, but had to wait before it could insert 3.\n\nA stream with a capacity of 1 acts like a mailbox.\nA stream with a capacity of 0 will wait until both the sender and receiver are ready.\n\nStreams are thread-safe and can be used to communicate between domains.\n\n### Example: Worker Pool\n\nA useful pattern is a pool of workers reading from a stream of work items.\nClient fibers submit items to a stream and workers process the items:\n\n```ocaml\nlet handle_job request =\n  Fiber.yield ();       (* (simulated work) *)\n  Printf.sprintf \"Processed:%d\" request\n\nlet rec run_worker id stream =\n  let request, reply = Eio.Stream.take stream in\n  traceln \"Worker %s processing request %d\" id request;\n  Promise.resolve reply (handle_job request);\n  run_worker id stream\n\nlet submit stream request =\n  let reply, resolve_reply = Promise.create () in\n  Eio.Stream.add stream (request, resolve_reply);\n  Promise.await reply\n```\n\nEach item in the stream is a request payload and a resolver for the reply promise.\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let domain_mgr = Eio.Stdenv.domain_mgr env in\n  Switch.run @@ fun sw -\u003e\n  let stream = Eio.Stream.create 0 in\n  let spawn_worker name =\n    Fiber.fork_daemon ~sw (fun () -\u003e\n       Eio.Domain_manager.run domain_mgr (fun () -\u003e\n          traceln \"Worker %s ready\" name;\n          run_worker name stream\n       )\n    )\n  in\n  spawn_worker \"A\";\n  spawn_worker \"B\";\n  Switch.run (fun sw -\u003e\n     for i = 1 to 3 do\n       Fiber.fork ~sw (fun () -\u003e\n         traceln \"Client %d submitting job...\" i;\n         traceln \"Client %d got %s\" i (submit stream i)\n       );\n       Fiber.yield ()\n     done\n  );;\n+Worker A ready\n+Worker B ready\n+Client 1 submitting job...\n+Worker A processing request 1\n+Client 2 submitting job...\n+Worker B processing request 2\n+Client 3 submitting job...\n+Client 1 got Processed:1\n+Worker A processing request 3\n+Client 2 got Processed:2\n+Client 3 got Processed:3\n- : unit = ()\n```\n\nWe use a zero-capacity stream here, which means that the `Stream.add` doesn't succeed until a worker accepts the job.\nThis is a good choice for a worker pool because it means that if the client fiber gets cancelled while waiting for a worker\nthen the job will never be run. It's also more efficient, as 0-capacity streams use a lock-free algorithm that is faster\nwhen there are multiple domains.\nNote that, while the stream itself is 0-capacity, clients still queue up waiting to use it.\n\nIn the code above, any exception raised while processing a job will exit the whole program.\nWe might prefer to handle exceptions by sending them back to the client and continuing:\n\n```ocaml\nlet rec run_worker id stream =\n  let request, reply = Eio.Stream.take stream in\n  traceln \"Worker %s processing request %d\" id request;\n  begin match handle_job request with\n    | result -\u003e Promise.resolve_ok reply result\n    | exception ex -\u003e Promise.resolve_error reply ex; Fiber.check ()\n  end;\n  run_worker id stream\n```\n\nThe `Fiber.check ()` checks whether the worker itself has been cancelled, and exits the loop if so.\nIt's not actually necessary in this case,\nbecause if we continue instead then the following `Stream.take` will perform the check anyway.\n\nNote: in a real system, you would probably use [Eio.Executor_pool][] for this rather than making your own pool.\n\n### Mutexes and Semaphores\n\nEio also provides `Mutex` and `Semaphore` sub-modules.\nEach of these corresponds to the module with the same name in the OCaml standard library,\nbut allows other fibers to run while waiting instead of blocking the whole domain.\nThey are all safe to use in parallel from multiple domains.\n\n- [Eio.Mutex][] provides *mutual exclusion*, so that only one fiber can access a resource at a time.\n- [Eio.Semaphore][] generalises this to allow up to *n* fibers to access a resource at once.\n\nFor example, if we allow loading and saving data in a file there could be a problem\nif we try to load the data while a save is in progress.\nProtecting the file with a mutex will prevent that:\n\n```ocaml\nmodule Atomic_file = struct\n  type 'a t = {\n    path : 'a Eio.Path.t;\n    mutex : Eio.Mutex.t;\n  }\n\n  let of_path path =\n    { path; mutex = Eio.Mutex.create () }\n\n  let save t data =\n    Eio.Mutex.use_rw t.mutex ~protect:true (fun () -\u003e\n       Eio.Path.save t.path data ~create:(`Or_truncate 0o644)\n    )\n\n  let load t =\n    Eio.Mutex.use_ro t.mutex (fun () -\u003e\n       Eio.Path.load t.path\n    )\nend\n```\n\nThe `~protect:true` in `save` makes the critical section non-cancellable,\nso that if a cancel happens during a save then we will finish writing the data first.\nIt can be used like this:\n\n```ocaml\n# Eio_main.run @@ fun env -\u003e\n  let dir = Eio.Stdenv.cwd env in\n  let t = Atomic_file.of_path (dir / \"data\") in\n  Fiber.both\n    (fun () -\u003e Atomic_file.save t \"some data\")\n    (fun () -\u003e\n      let data = Atomic_file.load t in\n      traceln \"Loaded: %S\" data\n    );;\n+Loaded: \"some data\"\n- : unit = ()\n```\n\nNote: In practice, a better way to make file writes atomic is\nto write the data to a temporary file and then atomically rename it over the old data.\nThat will work even if the whole computer crashes, and does not delay cancellation.\n\nIf the operation being performed is very fast (such as updating some in-memory counters),\nthen it is fine to use the standard library's `Mutex` instead.\n\nIf the operation does not switch fibers *and* the resource is only accessed from one domain,\nthen no mutex is needed at all. For example:\n\n```ocaml\n(* No mutex needed if only used from a single domain: *)\n\nlet in_use = ref 10\nlet free = ref 0\n\nlet release () =\n  incr free;\n  decr in_use\n```\n\n### Conditions\n\n[Eio.Condition][] allows a fiber to wait until some condition is true.\nFor example:\n\n```ocaml\nmodule X = struct\n  (* Note: this version is not safe to share across domains! *)\n\n  type t = {\n    mutable x : int;\n    changed : Eio.Condition.t;\n  }\n\n  let make x = { x; changed = Eio.Condition.create () }\n\n  let await_zero t =\n    while t.x \u003c\u003e 0 do Eio.Condition.await_no_mutex t.changed done;\n    traceln \"x is now zero\"\n\n  let set t x =\n    t.x \u003c- x;\n    Eio.Condition.broadcast t.changed;\n    traceln \"x set to %d\" x\nend\n```\n\n```ocaml\n# Eio_mock.Backend.run @@ fun () -\u003e\n  let x = X.make 5 in\n  Fiber.both\n    (fun () -\u003e\n       traceln \"Waiting for x to be 0\";\n       X.await_zero x\n    )\n    (fun () -\u003e X.set x 0);;\n+Waiting for x to be 0\n+x set to 0\n+x is now zero\n- : unit = ()\n```\n\nNote that we need a loop in `await_zero`.\nThis is needed because it's possible that another fiber might set it to zero\nand then set it to something else before the waiting fiber resumes.\n\nThe above version is not safe to share across domains, because `await_zero` relies on the value of `x` not changing\nafter `x` is read but before `await_no_mutex` registers itself with the condition.\nHere's a domain-safe version:\n\n```ocaml\nmodule Y = struct\n  (* Safe to share between domains. *)\n\n  type t = {\n    mutable y : int;\n    mutex : Eio.Mutex.t;\n    changed : Eio.Condition.t;\n  }\n\n  let make y = {\n    y;\n    mutex = Eio.Mutex.create ();\n    changed = Eio.Condition.create ();\n  }\n\n  let await_zero t =\n    Eio.Mutex.use_ro t.mutex (fun () -\u003e\n      while t.y \u003c\u003e 0 do Eio.Condition.await t.changed t.mutex done;\n      traceln \"y is now zero (at least until we release the mutex)\"\n    )\n\n  let set t y =\n    Eio.Mutex.use_rw t.mutex ~protect:true (fun () -\u003e\n       t.y \u003c- y;\n       Eio.Condition.broadcast t.changed;\n       traceln \"y set to %d\" y\n    );\nend\n```\n\nHere, `Eio.Condition.await` registers itself with `changed` and only then releases the mutex,\nallowing other threads to change `y`. When it gets woken, it re-acquires the mutex.\n\n```ocaml\n# Eio_mock.Backend.run @@ fun () -\u003e\n  let y = Y.make 5 in\n  Fiber.both\n    (fun () -\u003e\n       traceln \"Waiting for y to be 0\";\n       Y.await_zero y\n    )\n    (fun () -\u003e Y.set y 0);;\n+Waiting for y to be 0\n+y set to 0\n+y is now zero (at least until we release the mutex)\n- : unit = ()\n```\n\nConditions are more difficult to use correctly than e.g. promises or streams.\nIn particular, it is easy to miss a notification due to `broadcast` getting called before `await`.\nHowever, they can be useful if used carefully.\n\n### Example: Signal handlers\n\nOn Unix-type systems, processes can react to *signals*.\nFor example, pressing Ctrl-C will send the `SIGINT` (interrupt) signal.\n\nHere is an example function that allows itself to be interrupted:\n\n```ocaml\nlet run_op ~interrupted =\n  Fiber.first\n    (fun () -\u003e\n       Eio.Condition.await_no_mutex interrupted;\n       traceln \"Cancelled at user's request.\"\n    )\n    (fun () -\u003e\n       traceln \"Running operation (Ctrl-C to cancel)...\";\n       Fiber.await_cancel ()       (* Simulated work *)\n    )\n```\n\nNote that we don't need a mutex here.\nWe're just waiting for the number of interrupts received to change,\nand, since that increases monotonically, once we get woken we always want to continue.\nAlso, we don't care about missing interrupts from before this operation started.\n\nThe code here is quite subtle.\nWe rely on the fact that the first branch of the `Fiber.first` runs first,\nand only starts running the second branch once `await_no_mutex` has finished registering.\nThus, we never display the message telling the user to press Ctrl-C before we're ready\nto receive it.\nThis isn't likely to matter if a human is responding to the message,\nbut if the response is automated then the delay could matter.\n\nTo run this function, we need to install a signal handler.\nThere are very few things that you can do safely in a signal handler.\nFor example, you can't take a mutex in a signal handler\nbecause the signal might have interrupted a fiber that had already locked it.\nHowever, you can safely call `Eio.Condition.broadcast`:\n\n\u003c!-- $MDX non-deterministic=command --\u003e\n```ocaml\n# Eio_main.run @@ fun _env -\u003e\n  let interrupted = Eio.Condition.create () in\n  let handle_signal (_signum : int) =\n    (* Warning: we're in a signal handler now.\n       Most operations are unsafe here, except for Eio.Condition.broadcast! *)\n    Eio.Condition.broadcast interrupted\n  in\n  Sys.set_signal Sys.sigint (Signal_handle handle_signal);\n  run_op ~interrupted;;\n+Running operation (Ctrl-C to cancel)...\n[ user presses Ctrl-C here ]\n+Cancelled at user's request.\n- : unit = ()\n```\n\nAnother common pattern when using signals is using `SIGHUP`\nto tell an application to reload its configuration file:\n\n\u003c!-- $MDX file=examples/signals/main.ml,part=main --\u003e\n```ocaml\nlet main ~config_changed =\n  Eio.Condition.loop_no_mutex config_changed (fun () -\u003e\n      traceln \"Reading configuration ('kill -SIGHUP %d' to reload)...\" (Unix.getpid ());\n      load_config ();\n      traceln \"Finished reading configuration\";\n      None      (* Keep waiting for futher changes *)\n    )\n```\n\nSee the `examples/signals` directory for the full code.\n\n## Design Note: Determinism\n\nWithin a domain, fibers are scheduled deterministically.\nPrograms using only the Eio APIs can only behave non-deterministically if given a capability to do so from somewhere else.\n\nFor example, `Fiber.both f g` always starts running `f` first,\nand only switches to `g` when `f` finishes or performs an effect that can switch fibers.\n\nPerforming IO with external objects (e.g., `stdout`, files, or network sockets) will introduce non-determinism,\nas will using multiple domains.\n\nNote that `traceln` is unusual. Although it writes (by default) to stderr, it will not switch fibers.\nInstead, if the OS is not ready to receive trace output, the whole domain is paused until it is ready.\nThis means that adding `traceln` to deterministic code will not affect its scheduling.\n\nIn particular, if you test your code by providing (deterministic) mocks then the tests will be deterministic.\nAn easy way to write tests is by having the mocks call `traceln` and then comparing the trace output with the expected output.\nSee Eio's own tests for examples, e.g., [tests/switch.md](tests/switch.md).\n\nNote: this only applies to the high-level APIs in the `Eio` module.\nPrograms can behave non-deterministically when using `Eio_unix` or the various `Low_level` APIs provided by the backends.\n\n## Provider Interfaces\n\nEio applications use resources by calling functions (such as `Eio.Flow.write`).\nThese functions are actually wrappers that look up the implementing module and call\nthe appropriate function on that.\nThis allows you to define your own resources.\n\nHere's a flow that produces an endless stream of zeros (like \"/dev/zero\"):\n\n```ocaml\nmodule Zero = struct\n  type t = unit\n\n  let single_read () buf = \n    Cstruct.memset buf 0;\n    Cstruct.length buf\n\n  let read_methods = []         (* Optional optimisations *)\nend\n\nlet ops = Eio.Flow.Pi.source (module Zero)\n\nlet zero = Eio.Resource.T ((), ops)\n```\n\nIt can then be used like any other Eio flow:\n\n```ocaml\n# Eio_main.run @@ fun _ -\u003e\n  let r = Eio.Buf_read.of_flow zero ~max_size:100 in\n  traceln \"Got: %S\" (Eio.Buf_read.take 4 r);;\n+Got: \"\\000\\000\\000\\000\"\n- : unit = ()\n```\n\n## Example Applications\n\n- [gemini-eio][] is a simple Gemini browser. It shows how to integrate Eio with `ocaml-tls` and `notty`.\n- [cohttp-eio/examples](https://github.com/mirage/ocaml-cohttp/tree/master/cohttp-eio/examples) shows how to use Eio with HTTP.\n- [capnp-rpc](https://github.com/mirage/capnp-rpc) shows how to use Eio with Cap'n Proto.\n- [Awesome Multicore OCaml][] lists many other projects.\n\n## Integrations\n\nEio can be used with several other IO libraries.\n\n### Async\n\n[Async_eio][] has experimental support for running Async and Eio code together in a single domain.\n\n### Lwt\n\nYou can use [Lwt_eio][] to run Lwt threads and Eio fibers together in a single domain,\nand to convert between Lwt and Eio promises.\nThis may be useful during the process of porting existing code to Eio.\n\n### Unix and System Threads\n\nThe [Eio_unix][] module provides features for using Eio with OCaml's Unix module.\nIn particular, `Eio_unix.run_in_systhread` can be used to run a blocking operation in a separate systhread,\nallowing it to be used within Eio without blocking the whole domain.\n\n### Domainslib\n\nFor certain compute-intensive tasks it may be useful to send work to a pool of [Domainslib][] worker domains.\nYou can resolve an Eio promise from non-Eio domains (or systhreads), which provides an easy way to retrieve the result.\nFor example:\n\n\u003c!-- $MDX skip --\u003e\n```ocaml\nopen Eio.Std\n\nlet pool = Domainslib.Task.setup_pool ~num_domains:2 ()\n\nlet fib n = ... (* Some Domainslib function *)\n\nlet run_in_pool fn x =\n  let result, set_result = Promise.create () in\n  let _ : unit Domainslib.Task.promise = Domainslib.Task.async pool (fun () -\u003e\n      Promise.resolve set_result @@\n      match fn x with\n      | r -\u003e Ok r\n      | exception ex -\u003e Error ex\n    )\n  in\n  Promise.await_exn result\n\nlet () =\n  Eio_main.run @@ fun _ -\u003e\n  Fiber.both\n    (fun () -\u003e traceln \"fib 30 = %d\" (run_in_pool fib 30))\n    (fun () -\u003e traceln \"fib 10 = %d\" (run_in_pool fib 10))\n```\n\nNote that most Domainslib functions can only be called from code running in the Domainslib pool,\nwhile most Eio functions can only be used from Eio domains.\nThe bridge function `run_in_pool` makes use of the fact that `Domainslib.Task.async` is able to run from\nan Eio domain, and `Eio.Promise.resolve` is able to run from a Domainslib one.\n\n### kcas\n\nEio provides the support [kcas][] requires to implement blocking in the\nlock-free software transactional memory (STM) implementation that it provides.\nThis means that one can use all the composable lock-free data structures and\nprimitives for communication and synchronization implemented using **kcas** to\ncommunicate and synchronize between Eio fibers, raw domains, and any other\nschedulers that provide the domain local await mechanism.\n\nTo demonstrate **kcas**\n\n```ocaml\n# #require \"kcas\"\n# open Kcas\n```\n\nlet's first create a couple of shared memory locations\n\n```ocaml\nlet x = Loc.make 0\nlet y = Loc.make 0\n```\n\nand spawn a domain\n\n```ocaml\n# let foreign_domain = Domain.spawn @@ fun () -\u003e\n    let x = Loc.get_as (fun x -\u003e Retry.unless (x \u003c\u003e 0); x) x in\n    Loc.set y 22;\n    x\nval foreign_domain : int Domain.t = \u003cabstr\u003e\n```\n\nthat first waits for one of the locations to change value and then writes to the\nother location.\n\nThen we run a Eio program\n\n```ocaml\n# let y = Eio_main.run @@ fun _env -\u003e\n    Loc.set x 20;\n    Loc.get_as (fun y -\u003e Retry.unless (y \u003c\u003e 0); y) y\nval y : int = 22\n```\n\nthat first writes to the location the other domain is waiting on and then waits\nfor the other domain to write to the other location.\n\nJoining with the other domain\n\n```ocaml\n# y + Domain.join foreign_domain\n- : int = 42\n```\n\nwe arrive at the answer.\n\n## Best Practices\n\nThis section contains some recommendations for designing library APIs for use with Eio.\n\n### Switches\n\nA function should not take a switch argument if it could create one internally instead.\n\nTaking a switch indicates that a function creates resources that outlive the function call,\nand users seeing a switch argument will naturally wonder what these resources may be\nand what lifetime to give them, which is confusing if this is not needed.\n\nCreating the switch inside your function ensures that all resources are released\npromptly.\n\n```ocaml\n(* BAD - switch should be created internally instead *)\nlet load_config ~sw path =\n  parse_config (Eio.Path.open_in ~sw path)\n\n(* GOOD - less confusing and closes file promptly *)\nlet load_config path =\n  Switch.run @@ fun sw -\u003e\n  parse_config (Eio.Path.open_in ~sw path)\n```\n\nOf course, you could use `with_open_in` in this case to simplify it further.\n\n### Casting\n\nUnlike many languages, OCaml does not automatically cast to super-types as needed.\nRemember to keep the type polymorphic in your interface so users don't need to do this manually.\n\nFor example, if you need an `Eio.Flow.source` then users should be able to use a `Flow.two_way`\nwithout having to cast it first:\n\n\u003c!-- $MDX skip --\u003e\n```ocaml\n(* BAD - user must cast to use function: *)\nmodule Message : sig\n  type t\n  val read : Eio.Flow.source_ty r -\u003e t\nend\n\n(* GOOD - a Flow.two_way can be used without casting: *)\nmodule Message : sig\n  type t\n  val read : _ Eio.Flow.source -\u003e t\nend\n```\n\nIf you want to store the argument, this may require you to cast internally:\n\n```ocaml\nmodule Foo : sig\n  type t\n  val of_source : _ Eio.Flow.source -\u003e t\nend = struct\n  type t = {\n    src : Eio.Flow.source_ty r;\n  }\n\n  let of_source x = {\n    src = (x :\u003e Eio.Flow.source_ty r);\n  }\nend\n```\n\n### Passing env\n\nThe `env` value you get from `Eio_main.run` is a powerful capability,\nand programs are easier to understand when it's not passed around too much.\n\nIn many cases, it's clearer (if a little more verbose) to take the resources you need as separate arguments, e.g.\n\n\u003c!-- $MDX skip --\u003e\n```ocaml\nmodule Status : sig\n  val check :\n    clock:_ Eio.Time.clock -\u003e\n    net:_ Eio.Net.t -\u003e\n    bool\nend\n```\n\nYou can also provide a convenience function that takes an `env` too.\nDoing this is most appropriate if many resources are needed and\nyour library is likely to be initialised right at the start of the user's application.\n\nIn that case, be sure to request only the resources you need, rather than the full set.\nThis makes it clearer what you library does, makes it easier to test,\nand allows it to be used on platforms without the full set of OS resources.\nIf you define the type explicitly, you can describe why you need each resource there:\n\n\u003c!-- $MDX skip --\u003e\n```ocaml\nmodule Status : sig\n  type 'a env = 'a constraint 'a = \u003c\n    net : _ Eio.Net.t;             (** To connect to the servers *)\n    clock : _ Eio.Time.clock;      (** Needed for timeouts *)\n    ..\n  \u003e as 'a\n\n  val check : _ env -\u003e bool\nend\n```\n\n## Further Reading\n\n- [API reference][Eio API]\n- [doc/rationale.md](doc/rationale.md) describes some of Eio's design tradeoffs in more detail.\n- [HACKING.md](./HACKING.md) describes how to work with the Eio source code.\n\nSome background about the effects system can be found in:\n\n- [Experiences with effects (video)](https://watch.ocaml.org/videos/watch/74ece0a8-380f-4e2a-bef5-c6bb9092be89), OCaml Workshop 2021.\n- [\"Retrofitting Concurrency onto OCaml\"](https://kcsrk.info/papers/retro-concurrency_pldi_21.pdf) (to appear, PLDI 2021)\n- https://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/\n- Effects examples: https://github.com/ocaml-multicore/effects-examples/tree/master/aio\n- [Concurrent System Programming with Effect Handlers](https://www.repository.cam.ac.uk/bitstream/handle/1810/283239/paper.pdf?sequence=3\u0026isAllowed=y)\n- [Asynchronous effect based IO using effect handlers](https://github.com/kayceesrk/ocaml-aeio)\n\n[Eio API]: https://ocaml-multicore.github.io/eio/\n[Lwt_eio]: https://github.com/ocaml-multicore/lwt_eio\n[mirage-trace-viewer]: https://github.com/talex5/mirage-trace-viewer\n[structured concurrency]: https://en.wikipedia.org/wiki/Structured_concurrency\n[gemini-eio]: https://gitlab.com/talex5/gemini-eio\n[Awesome Multicore OCaml]: https://github.com/ocaml-multicore/awesome-multicore-ocaml\n[Eio]: https://ocaml-multicore.github.io/eio/eio/Eio/index.html\n[Eio.Std]: https://ocaml-multicore.github.io/eio/eio/Eio/Std/index.html\n[Eio.Fiber]: https://ocaml-multicore.github.io/eio/eio/Eio/Fiber/index.html\n[Eio.Flow]: https://ocaml-multicore.github.io/eio/eio/Eio/Flow/index.html\n[Eio.Cancel]: https://ocaml-multicore.github.io/eio/eio/Eio/Cancel/index.html\n[Eio.Switch]: https://ocaml-multicore.github.io/eio/eio/Eio/Switch/index.html\n[Eio.Net]: https://ocaml-multicore.github.io/eio/eio/Eio/Net/index.html\n[Eio.Buf_read]: https://ocaml-multicore.github.io/eio/eio/Eio/Buf_read/index.html\n[Eio.Buf_write]: https://ocaml-multicore.github.io/eio/eio/Eio/Buf_write/index.html\n[Eio.Path]: https://ocaml-multicore.github.io/eio/eio/Eio/Path/index.html\n[Eio.Time]: https://ocaml-multicore.github.io/eio/eio/Eio/Time/index.html\n[Eio.Domain_manager]: https://ocaml-multicore.github.io/eio/eio/Eio/Domain_manager/index.html\n[Eio.Executor_pool]: https://ocaml-multicore.github.io/eio/eio/Eio/Executor_pool/index.html\n[Eio.Promise]: https://ocaml-multicore.github.io/eio/eio/Eio/Promise/index.html\n[Eio.Stream]: https://ocaml-multicore.github.io/eio/eio/Eio/Stream/index.html\n[Eio_posix]: https://ocaml-multicore.github.io/eio/eio_posix/Eio_posix/index.html\n[Eio_linux]: https://ocaml-multicore.github.io/eio/eio_linux/Eio_linux/index.html\n[Eio_windows]: https://github.com/ocaml-multicore/eio/blob/main/lib_eio_windows/eio_windows.mli\n[Eio_main]: https://ocaml-multicore.github.io/eio/eio_main/Eio_main/index.html\n[Eio.traceln]: https://ocaml-multicore.github.io/eio/eio/Eio/index.html#val-traceln\n[Eio_main.run]: https://ocaml-multicore.github.io/eio/eio_main/Eio_main/index.html#val-run\n[Eio_mock]: https://ocaml-multicore.github.io/eio/eio/Eio_mock/index.html\n[Eio_unix]: https://ocaml-multicore.github.io/eio/eio/Eio_unix/index.html\n[Async_eio]: https://github.com/talex5/async_eio\n[Eio.Mutex]: https://ocaml-multicore.github.io/eio/eio/Eio/Mutex/index.html\n[Eio.Semaphore]: https://ocaml-multicore.github.io/eio/eio/Eio/Semaphore/index.html\n[Eio.Condition]: https://ocaml-multicore.github.io/eio/eio/Eio/Condition/index.html\n[Domainslib]: https://github.com/ocaml-multicore/domainslib\n[kcas]: https://github.com/ocaml-multicore/kcas\n[Lambda Capabilities]: https://roscidus.com/blog/blog/2023/04/26/lambda-capabilities/\n[Eio.Process]: https://ocaml-multicore.github.io/eio/eio/Eio/Process/index.html\n[Dev meetings]: https://docs.google.com/document/d/1ZBfbjAkvEkv9ldumpZV5VXrEc_HpPeYjHPW_TiwJe4Q\n[Olly]: https://github.com/tarides/runtime_events_tools\n[eio-trace]: https://github.com/ocaml-multicore/eio-trace\n[cap_enter]: https://man.freebsd.org/cgi/man.cgi?query=cap_enter\n[eio_js]: https://github.com/ocaml-multicore/eio_js\n","funding_links":[],"categories":["OCaml","Concurrency","\u003ca name=\"OCaml\"\u003e\u003c/a\u003eOCaml"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Focaml-multicore%2Feio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Focaml-multicore%2Feio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Focaml-multicore%2Feio/lists"}