···11+This branch exists to figure out an OCaml specification for the FastCGI interface.
22+33+The spec for the FastCGI protocol is in spec/FastCGI_Specification.html.
44+The Go lang implementation of FastCGI is in spec/fcgi.go.
55+66+Both of these are intended to act as a reference implementation, for us to figure out what the ideal OCaml interface should look like for FastCGI.
77+88+Our target language is OCaml, using the Eio library. The README for Eio is in OCaml-EIO-README.md to give you a reference.
···11+[API reference][Eio API] | [#eio Matrix chat](https://matrix.to/#/#eio:roscidus.com) | [Dev meetings][]
22+33+# Eio — Effects-Based Parallel IO for OCaml
44+55+Eio provides an effects-based direct-style IO stack for OCaml 5.
66+For example, you can use Eio to read and write files, make network connections,
77+or perform CPU-intensive calculations, running multiple operations at the same time.
88+It aims to be easy to use, secure, well documented, and fast.
99+A generic cross-platform API is implemented by optimised backends for different platforms.
1010+Eio replaces existing concurrency libraries such as Lwt
1111+(Eio and Lwt libraries can also be used together).
1212+1313+## Contents
1414+1515+<!-- vim-markdown-toc GFM -->
1616+1717+* [Motivation](#motivation)
1818+* [Eio packages](#eio-packages)
1919+* [Getting OCaml](#getting-ocaml)
2020+* [Getting Eio](#getting-eio)
2121+* [Running Eio](#running-eio)
2222+* [Testing with Mocks](#testing-with-mocks)
2323+* [Fibers](#fibers)
2424+* [Tracing](#tracing)
2525+* [Cancellation](#cancellation)
2626+* [Racing](#racing)
2727+* [Switches](#switches)
2828+* [Networking](#networking)
2929+* [Design Note: Capabilities](#design-note-capabilities)
3030+* [Buffered Reading and Parsing](#buffered-reading-and-parsing)
3131+* [Buffered Writing](#buffered-writing)
3232+* [Error Handling](#error-handling)
3333+* [Filesystem Access](#filesystem-access)
3434+* [Running processes](#running-processes)
3535+* [Time](#time)
3636+* [Multicore Support](#multicore-support)
3737+ * [Domain Manager](#domain-manager)
3838+ * [Executor Pool](#executor-pool)
3939+* [Synchronisation Tools](#synchronisation-tools)
4040+ * [Promises](#promises)
4141+ * [Example: Concurrent Cache](#example-concurrent-cache)
4242+ * [Streams](#streams)
4343+ * [Example: Worker Pool](#example-worker-pool)
4444+ * [Mutexes and Semaphores](#mutexes-and-semaphores)
4545+ * [Conditions](#conditions)
4646+ * [Example: Signal handlers](#example-signal-handlers)
4747+* [Design Note: Determinism](#design-note-determinism)
4848+* [Provider Interfaces](#provider-interfaces)
4949+* [Example Applications](#example-applications)
5050+* [Integrations](#integrations)
5151+ * [Async](#async)
5252+ * [Lwt](#lwt)
5353+ * [Unix and System Threads](#unix-and-system-threads)
5454+ * [Domainslib](#domainslib)
5555+ * [kcas](#kcas)
5656+* [Best Practices](#best-practices)
5757+ * [Switches](#switches-1)
5858+ * [Casting](#casting)
5959+ * [Passing env](#passing-env)
6060+* [Further Reading](#further-reading)
6161+6262+<!-- vim-markdown-toc -->
6363+6464+## Motivation
6565+6666+The `Unix` library provided with OCaml uses blocking IO operations, and is not well suited to concurrent programs such as network services or interactive applications.
6767+For many years, the solution was to use libraries such as Lwt and Async, which provide a monadic interface.
6868+These 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.
6969+7070+OCaml 5 added support for "effects", removing the need for monadic code here.
7171+Using effects brings several advantages:
7272+7373+1. It's faster, because no heap allocations are needed to simulate a stack.
7474+2. Concurrent code can be written in the same style as plain non-concurrent code.
7575+3. Because a real stack is used, backtraces from exceptions work as expected.
7676+4. Other features of the language (such as `try ... with ...`) can be used in concurrent code.
7777+7878+Additionally, modern operating systems provide high-performance alternatives to the old Unix `select` call.
7979+For example, Linux's io_uring system has applications write the operations they want to perform to a ring buffer,
8080+which Linux handles asynchronously, and Eio can take advantage of this.
8181+8282+You can always [fall back to using Lwt libraries](#lwt) to provide missing features if necessary.
8383+See [Awesome Multicore OCaml][] for links to other projects using Eio.
8484+8585+## Eio packages
8686+8787+- [Eio][] provides concurrency primitives (promises, etc.) and a high-level, cross-platform OS API.
8888+- [Eio_posix][] provides a cross-platform backend for these APIs for POSIX-type systems.
8989+- [Eio_linux][] provides a Linux io_uring backend for these APIs.
9090+- [Eio_windows][] is for use on Windows (incomplete - [help wanted](https://github.com/ocaml-multicore/eio/issues/125)).
9191+- [Eio_main][] selects an appropriate backend (e.g. `eio_linux` or `eio_posix`), depending on your platform.
9292+- [Eio_js][] allows Eio code to run in the browser, using `js_of_ocaml`.
9393+9494+## Getting OCaml
9595+9696+You'll need OCaml 5.1.0 or later.
9797+You can either install it yourself or build the included [Dockerfile](./Dockerfile).
9898+9999+To install it yourself:
100100+101101+1. Make sure you have opam 2.1 or later (run `opam --version` to check).
102102+103103+2. Use opam to install OCaml:
104104+105105+ ```
106106+ opam switch create 5.2.0
107107+ ```
108108+109109+## Getting Eio
110110+111111+Install `eio_main` (and `utop` if you want to try it interactively):
112112+113113+```
114114+opam install eio_main utop
115115+```
116116+117117+If you want to install the latest unreleased development version of Eio, see [HACKING.md](./HACKING.md).
118118+119119+## Running Eio
120120+121121+Try out the examples interactively by running `utop` in the shell.
122122+123123+First `require` the `eio_main` library. It's also convenient to open the [Eio.Std][]
124124+module, as follows. (The leftmost `#` shown below is the Utop prompt, so enter the text after the
125125+prompt and return after each line.)
126126+127127+```ocaml
128128+# #require "eio_main";;
129129+# open Eio.Std;;
130130+```
131131+132132+This function writes a greeting to `out` using [Eio.Flow][]:
133133+134134+```ocaml
135135+let main out =
136136+ Eio.Flow.copy_string "Hello, world!\n" out
137137+```
138138+139139+We use [Eio_main.run][] to run the event loop and call `main` from there:
140140+141141+```ocaml
142142+# Eio_main.run @@ fun env ->
143143+ main (Eio.Stdenv.stdout env);;
144144+Hello, world!
145145+- : unit = ()
146146+```
147147+148148+Note that:
149149+150150+- The `env` argument represents the standard environment of a Unix process, allowing it to interact with the outside world.
151151+ A program will typically start by extracting from `env` whatever things the program will need and then calling `main` with them.
152152+153153+- The type of the `main` function here tells us that this program only interacts via the `out` flow.
154154+155155+- `Eio_main.run` automatically calls the appropriate run function for your platform.
156156+ For example, on Linux this will call `Eio_linux.run`. For non-portable code you can use the platform-specific library directly.
157157+158158+This example can also be built using dune; see [examples/hello](./examples/hello/).
159159+160160+## Testing with Mocks
161161+162162+Because external resources are provided to `main` as arguments, we can easily replace them with mocks for testing.
163163+For example, instead of giving `main` the real standard output, we can have it write to a buffer:
164164+165165+```ocaml
166166+# Eio_main.run @@ fun _env ->
167167+ let buffer = Buffer.create 20 in
168168+ main (Eio.Flow.buffer_sink buffer);
169169+ traceln "Main would print %S" (Buffer.contents buffer);;
170170++Main would print "Hello, world!\n"
171171+- : unit = ()
172172+```
173173+174174+[Eio.traceln][] provides convenient printf-style debugging, without requiring you to plumb `stderr` through your code.
175175+It uses the `Format` module, so you can use the extended formatting directives here too.
176176+177177+The [Eio_mock][] library provides some convenient pre-built mocks:
178178+179179+```ocaml
180180+# #require "eio.mock";;
181181+# Eio_main.run @@ fun _env ->
182182+ main (Eio_mock.Flow.make "mock-stdout");;
183183++mock-stdout: wrote "Hello, world!\n"
184184+- : unit = ()
185185+```
186186+187187+## Fibers
188188+189189+Here's an example running two threads of execution concurrently using [Eio.Fiber][]:
190190+191191+```ocaml
192192+let main _env =
193193+ Fiber.both
194194+ (fun () -> for x = 1 to 3 do traceln "x = %d" x; Fiber.yield () done)
195195+ (fun () -> for y = 1 to 3 do traceln "y = %d" y; Fiber.yield () done);;
196196+```
197197+198198+```ocaml
199199+# Eio_main.run main;;
200200++x = 1
201201++y = 1
202202++x = 2
203203++y = 2
204204++x = 3
205205++y = 3
206206+- : unit = ()
207207+```
208208+209209+The two fibers run on a single core, so only one can be running at a time.
210210+Calling an operation that performs an effect (such as `yield`) can switch to a different thread.
211211+212212+## Tracing
213213+214214+When OCaml's tracing is turned on, Eio writes events about many actions,
215215+such as creating fibers or resolving promises.
216216+217217+You can use [eio-trace][] to capture a trace and display it in a window.
218218+For example, this is a trace of the counting example above:
219219+220220+```
221221+dune build ./examples
222222+eio-trace run -- ./_build/default/examples/both/main.exe
223223+```
224224+225225+<p align='center'>
226226+ <img src="./doc/traces/both-posix.svg"/>
227227+</p>
228228+229229+The upper horizontal bar is the initial fiber, and the brackets show `Fiber.both` creating a second fiber.
230230+The green segments show when each fiber is running.
231231+Note that the output from `traceln` appears in the trace as well as on the console.
232232+In the eio-trace window, scrolling with the mouse or touchpad will zoom in or out of the diagram.
233233+234234+Third-party tools, such as [Olly][], can also consume this data.
235235+[examples/trace](./examples/trace/) shows how to consume the events manually.
236236+237237+## Cancellation
238238+239239+Every fiber has a [cancellation context][Eio.Cancel].
240240+If one of the `Fiber.both` fibers fails, the other is cancelled:
241241+242242+```ocaml
243243+# Eio_main.run @@ fun _env ->
244244+ Fiber.both
245245+ (fun () -> for x = 1 to 3 do traceln "x = %d" x; Fiber.yield () done)
246246+ (fun () -> failwith "Simulated error");;
247247++x = 1
248248+Exception: Failure "Simulated error".
249249+```
250250+251251+<p align='center'>
252252+ <img src="./doc/traces/cancel-posix.svg"/>
253253+</p>
254254+255255+What happened here was:
256256+257257+1. `Fiber.both` created a new cancellation context for the child fibers.
258258+2. The first fiber (the lower one in the diagram) ran, printed `x = 1` and yielded.
259259+3. The second fiber raised an exception.
260260+4. `Fiber.both` caught the exception and cancelled the context.
261261+5. The first thread's `yield` raised a `Cancelled` exception there.
262262+6. Once both threads had finished, `Fiber.both` re-raised the original exception.
263263+264264+There is a tree of cancellation contexts for each domain, and every fiber is in one context.
265265+When an exception is raised, it propagates towards the root until handled, cancelling the other branches as it goes.
266266+You should assume that any operation that can switch fibers can also raise a `Cancelled` exception if an uncaught exception
267267+reaches one of its ancestor cancellation contexts.
268268+269269+If you want to make an operation non-cancellable, wrap it with `Cancel.protect`
270270+(this creates a new context that isn't cancelled with its parent).
271271+272272+## Racing
273273+274274+`Fiber.first` returns the result of the first fiber to finish, cancelling the other one:
275275+276276+```ocaml
277277+# Eio_main.run @@ fun _env ->
278278+ let x =
279279+ Fiber.first
280280+ (fun () ->
281281+ traceln "first fiber delayed...";
282282+ Fiber.yield ();
283283+ traceln "delay over";
284284+ "a"
285285+ )
286286+ (fun () -> "b")
287287+ in
288288+ traceln "x = %S" x;;
289289++first fiber delayed...
290290++x = "b"
291291+- : unit = ()
292292+```
293293+294294+Note: using `Fiber.first` to ensure that *exactly one* of two actions is performed is not reliable.
295295+There is usually a possibility that both actions succeed at the same time (and one result is thrown away).
296296+For example, if you ask Eio read from two sockets with `io_uring`
297297+then the kernel may have already performed both reads by the time it tells Eio about the first one.
298298+299299+## Switches
300300+301301+A [switch][Eio.Switch] is used to group fibers together, so they can be waited on together.
302302+This is a form of [structured concurrency][].
303303+For example:
304304+305305+```ocaml
306306+# Eio_main.run @@ fun _env ->
307307+ Switch.run (fun sw ->
308308+ for i = 1 to 3 do
309309+ Fiber.fork ~sw (fun () ->
310310+ traceln "Job %d starting" i;
311311+ Fiber.yield ();
312312+ traceln "%d done" i;
313313+ );
314314+ done;
315315+ traceln "All child fibers forked";
316316+ );
317317+ traceln "Switch is finished";;
318318++Job 1 starting
319319++Job 2 starting
320320++Job 3 starting
321321++All child fibers forked
322322++1 done
323323++2 done
324324++3 done
325325++Switch is finished
326326+- : unit = ()
327327+```
328328+329329+<p align='center'>
330330+ <img src="./doc/traces/switch-mock.svg"/>
331331+</p>
332332+333333+`Switch.run fn` creates a new switch `sw` and runs `fn sw`.
334334+`fn` may spawn new fibers and attach them to the switch.
335335+It may also attach other resources such as open file handles.
336336+`Switch.run` waits until `fn` and all other attached fibers have finished, and then
337337+releases any attached resources (e.g. closing all attached file handles).
338338+339339+If you call a function without giving it access to a switch,
340340+then when the function returns you can be sure that any fibers it spawned have finished,
341341+and any files it opened have been closed.
342342+This works because Eio does not provide e.g. a way to open a file without attaching it to a switch.
343343+If a function doesn't have a switch and wants to open a file, it must use `Switch.run` to create one.
344344+But then the function can't return until `Switch.run` does, at which point the file is closed.
345345+346346+So, a `Switch.run` puts a bound on the lifetime of things created within it,
347347+leading to clearer code and avoiding resource leaks.
348348+The `Fiber.fork` call above creates a new fiber that continues running after `fork` returns,
349349+so it needs to take a switch argument.
350350+351351+Every switch also creates a new cancellation context.
352352+You can use `Switch.fail` to mark the switch as failed and cancel all fibers within it.
353353+The exception (or exceptions) passed to `fail` will be raised by `run` when the fibers have exited.
354354+355355+## Networking
356356+357357+Eio provides an API for [networking][Eio.Net].
358358+Here is a server connection handler that handles an incoming connection by sending the client a message:
359359+360360+```ocaml
361361+let handle_client flow _addr =
362362+ traceln "Server: got connection from client";
363363+ Eio.Flow.copy_string "Hello from server" flow
364364+```
365365+366366+We can test it using a mock flow:
367367+368368+```ocaml
369369+# Eio_mock.Backend.run @@ fun () ->
370370+ let flow = Eio_mock.Flow.make "flow" in
371371+ let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 37568) in
372372+ handle_client flow addr;;
373373++Server: got connection from client
374374++flow: wrote "Hello from server"
375375+- : unit = ()
376376+```
377377+378378+Note: `Eio_mock.Backend.run` can be used instead of `Eio_main.run` for tests that don't access the outside environment at all.
379379+It doesn't support multiple domains, but this allows it to detect deadlocks automatically
380380+(a multi-domain loop has to assume it might get an event from another domain, and so must keep waiting).
381381+382382+Here is a client that connects to address `addr` using network `net` and reads a message:
383383+384384+```ocaml
385385+let run_client ~net ~addr =
386386+ Switch.run ~name:"client" @@ fun sw ->
387387+ traceln "Client: connecting to server";
388388+ let flow = Eio.Net.connect ~sw net addr in
389389+ (* Read all data until end-of-stream (shutdown): *)
390390+ traceln "Client: received %S" (Eio.Flow.read_all flow)
391391+```
392392+393393+Note: the `flow` is attached to `sw` and will be closed automatically when it finishes.
394394+We also named the switch here; this will appear in the trace output (see below).
395395+396396+This can also be tested on its own using a mock network:
397397+398398+```ocaml
399399+# Eio_mock.Backend.run @@ fun () ->
400400+ let net = Eio_mock.Net.make "mocknet" in
401401+ let flow = Eio_mock.Flow.make "flow" in
402402+ Eio_mock.Net.on_connect net [`Return flow];
403403+ Eio_mock.Flow.on_read flow [
404404+ `Return "(packet 1)";
405405+ `Yield_then (`Return "(packet 2)");
406406+ `Raise End_of_file;
407407+ ];
408408+ let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 8080) in
409409+ run_client ~net ~addr;;
410410++Client: connecting to server
411411++mocknet: connect to tcp:127.0.0.1:8080
412412++flow: read "(packet 1)"
413413++flow: read "(packet 2)"
414414++Client: received "(packet 1)(packet 2)"
415415++flow: closed
416416+- : unit = ()
417417+```
418418+419419+`Eio.Net.run_server` runs a loop accepting clients and handling them (concurrently):
420420+421421+```ocaml
422422+let run_server socket =
423423+ Eio.Net.run_server socket handle_client
424424+ ~on_error:(traceln "Error handling connection: %a" Fmt.exn)
425425+```
426426+427427+Note: when `handle_client` finishes, `run_server` closes the flow automatically.
428428+429429+We can now run the client and server together using the real network (in a single process):
430430+431431+```ocaml
432432+let main ~net ~addr =
433433+ Switch.run ~name:"main" @@ fun sw ->
434434+ let server = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:5 addr in
435435+ Fiber.fork_daemon ~sw (fun () -> run_server server);
436436+ run_client ~net ~addr
437437+```
438438+439439+`Fiber.fork_daemon` creates a new fiber and then cancels it when the switch finishes.
440440+We need that here because otherwise the server would keep waiting for new connections and
441441+the test would never finish.
442442+443443+```ocaml
444444+# Eio_main.run @@ fun env ->
445445+ main
446446+ ~net:(Eio.Stdenv.net env)
447447+ ~addr:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 8080));;
448448++Client: connecting to server
449449++Server: got connection from client
450450++Client: received "Hello from server"
451451+- : unit = ()
452452+```
453453+454454+<p align='center'>
455455+ <img src="./doc/traces/net-posix.svg"/>
456456+</p>
457457+458458+See [examples/net](./examples/net/) for a more complete example.
459459+460460+## Design Note: Capabilities
461461+462462+Eio follows the principles of capability-based security.
463463+The key idea here is that the lambda calculus already contains a perfectly good security system:
464464+a function can only access things that are in its scope.
465465+If we can avoid breaking this model (for example, by adding global variables to our language)
466466+then we can reason about the security properties of code quite easily.
467467+468468+Consider the network example in the previous section.
469469+Imagine this is a large program and we want to know:
470470+471471+1. Does this program modify the filesystem?
472472+2. Does this program send telemetry data over the network?
473473+474474+In a capability-safe language, we don't have to read the entire code-base to find the answers:
475475+476476+- All authority starts at the (privileged) `Eio_main.run` function with the `env` parameter,
477477+ so we must check this code.
478478+479479+- Only `env`'s network access is used, so we know this program doesn't access the filesystem,
480480+ answering question 1 immediately.
481481+482482+- To check whether telemetry is sent, we need to follow the `net` authority as it is passed to `main`.
483483+484484+- `main` uses `net` to open a listening socket on the loopback interface, which it passes to `run_server`.
485485+ `run_server` does not get the full `net` access, so we probably don't need to read that code; however,
486486+ we might want to check whether we granted other parties access to this port on our loopback network.
487487+488488+- `run_client` does get `net`, so we do need to read that.
489489+ We could make that code easier to audit by passing it `(fun () -> Eio.Net.connect net addr)` instead of `net` .
490490+ Then we could see that `run_client` could only connect to our loopback address.
491491+492492+Since OCaml is not a capability language, code can ignore Eio and use the non-capability APIs directly.
493493+However, it still makes non-malicious code easier to understand and test,
494494+and may allow for an extension to the language in the future.
495495+496496+The [Lambda Capabilities][] blog post provides a more detailed introduction to capabilities,
497497+written for functional programmers.
498498+499499+## Buffered Reading and Parsing
500500+501501+Reading from an Eio flow directly may give you more or less data than you wanted.
502502+For example, if you want to read a line of text from a TCP stream,
503503+the flow will tend to give you the data in packet-sized chunks, not lines.
504504+To solve this, you can wrap the flow with a [buffer][Eio.Buf_read] and read from that.
505505+506506+Here's a simple command-line interface that reads `stdin` one line at a time:
507507+508508+```ocaml
509509+let cli ~stdin ~stdout =
510510+ let buf = Eio.Buf_read.of_flow stdin ~initial_size:100 ~max_size:1_000_000 in
511511+ while true do
512512+ let line = Eio.Buf_read.line buf in
513513+ traceln "> %s" line;
514514+ match line with
515515+ | "h" | "help" -> Eio.Flow.copy_string "It's just an example\n" stdout
516516+ | x -> Eio.Flow.copy_string (Fmt.str "Unknown command %S\n" x) stdout
517517+ done
518518+```
519519+520520+Let's try it with some test data (you could use the real stdin if you prefer):
521521+522522+```ocaml
523523+# Eio_main.run @@ fun env ->
524524+ cli
525525+ ~stdin:(Eio.Flow.string_source "help\nexit\nquit\nbye\nstop\n")
526526+ ~stdout:(Eio.Stdenv.stdout env);;
527527++> help
528528+It's just an example
529529++> exit
530530+Unknown command "exit"
531531++> quit
532532+Unknown command "quit"
533533++> bye
534534+Unknown command "bye"
535535++> stop
536536+Unknown command "stop"
537537+Exception: End_of_file.
538538+```
539539+540540+`Buf_read.of_flow` allocates an internal buffer (with the given `initial_size`).
541541+When you try to read a line from it, it will take a whole line from the buffer if possible.
542542+If not, it will ask the underlying flow for the next chunk of data, until it has enough.
543543+544544+For high performance applications, you should use a larger initial buffer
545545+so that fewer reads on the underlying flow are needed.
546546+547547+If the user enters a line that doesn't fit in the buffer then the buffer will be enlarged as needed.
548548+However, it will raise an exception if the buffer would need to grow above `max_size`.
549549+This is useful when handling untrusted input, since otherwise when you try to read one line an
550550+attacker could just keep sending e.g. 'x' characters until your service ran out of memory and crashed.
551551+552552+As well as calling individual parsers (like `line`) directly,
553553+you can also build larger parsers from smaller ones.
554554+For example:
555555+556556+```ocaml
557557+open Eio.Buf_read.Syntax
558558+559559+type message = { src : string; body : string }
560560+561561+let message =
562562+ let+ src = Eio.Buf_read.(string "FROM:" *> line)
563563+ and+ body = Eio.Buf_read.take_all in
564564+ { src; body }
565565+```
566566+567567+```ocaml
568568+# Eio_main.run @@ fun _ ->
569569+ let flow = Eio.Flow.string_source "FROM:Alice\nHello!\n" in
570570+ match Eio.Buf_read.parse message flow ~max_size:1024 with
571571+ | Ok { src; body } -> traceln "%s sent %S" src body
572572+ | Error (`Msg err) -> traceln "Parse failed: %s" err;;
573573++Alice sent "Hello!\n"
574574+- : unit = ()
575575+```
576576+577577+## Buffered Writing
578578+579579+For performance, it's often useful to batch up writes and send them all in one go.
580580+For example, consider sending an HTTP response without buffering:
581581+582582+```ocaml
583583+let send_response socket =
584584+ Eio.Flow.copy_string "HTTP/1.1 200 OK\r\n" socket;
585585+ Eio.Flow.copy_string "\r\n" socket;
586586+ Fiber.yield (); (* Simulate delayed generation of body *)
587587+ Eio.Flow.copy_string "Body data" socket
588588+```
589589+590590+```ocaml
591591+# Eio_main.run @@ fun _ ->
592592+ send_response (Eio_mock.Flow.make "socket");;
593593++socket: wrote "HTTP/1.1 200 OK\r\n"
594594++socket: wrote "\r\n"
595595++socket: wrote "Body data"
596596+- : unit = ()
597597+```
598598+599599+The socket received three writes, perhaps sending three separate packets over the network.
600600+We can wrap a flow with [Eio.Buf_write][] to avoid this:
601601+602602+```ocaml
603603+module Write = Eio.Buf_write
604604+605605+let send_response socket =
606606+ Write.with_flow socket @@ fun w ->
607607+ Write.string w "HTTP/1.1 200 OK\r\n";
608608+ Write.string w "\r\n";
609609+ Fiber.yield (); (* Simulate delayed generation of body *)
610610+ Write.string w "Body data"
611611+```
612612+613613+```ocaml
614614+# Eio_main.run @@ fun _ ->
615615+ send_response (Eio_mock.Flow.make "socket");;
616616++socket: wrote "HTTP/1.1 200 OK\r\n"
617617++ "\r\n"
618618++socket: wrote "Body data"
619619+- : unit = ()
620620+```
621621+622622+Now the first two writes were combined and sent together.
623623+624624+## Error Handling
625625+626626+Errors interacting with the outside world are indicated by the `Eio.Io (err, context)` exception.
627627+This is roughly equivalent to the `Unix.Unix_error` exception from the OCaml standard library.
628628+629629+The `err` field describes the error using nested error codes,
630630+allowing you to match on either specific errors or whole classes of errors at once.
631631+For example:
632632+633633+```ocaml
634634+let test r =
635635+ try Eio.Buf_read.line r
636636+ with
637637+ | Eio.Io (Eio.Net.E Connection_reset Eio_unix.Unix_error _, _) -> "Unix connection reset"
638638+ | Eio.Io (Eio.Net.E Connection_reset _, _) -> "Connection reset"
639639+ | Eio.Io (Eio.Net.E _, _) -> "Some network error"
640640+ | Eio.Io _ -> "Some I/O error"
641641+```
642642+643643+For portable code, you will want to avoid matching backend-specific errors, so you would avoid the first case.
644644+The `Eio.Io` type is extensible, so libraries can also add additional top-level error types if needed.
645645+646646+`Io` errors also allow adding extra context information to the error.
647647+For example, this HTTP GET function adds the URL to any IO error:
648648+649649+```ocaml
650650+let get ~net ~host ~path =
651651+ try
652652+ Eio.Net.with_tcp_connect net ~host ~service:"http" @@ fun _flow ->
653653+ "..."
654654+ with Eio.Io _ as ex ->
655655+ let bt = Printexc.get_raw_backtrace () in
656656+ Eio.Exn.reraise_with_context ex bt "fetching http://%s/%s" host path;;
657657+```
658658+659659+If we test it using a mock network that returns a timeout,
660660+we get a useful error message telling us the IP address and port of the failed attempt,
661661+extended with the hostname we used to get that,
662662+and then extended again by our `get` function with the full URL:
663663+664664+```ocaml
665665+# Eio_mock.Backend.run @@ fun () ->
666666+ let net = Eio_mock.Net.make "mocknet" in
667667+ Eio_mock.Net.on_getaddrinfo net [`Return [`Tcp (Eio.Net.Ipaddr.V4.loopback, 80)]];
668668+ Eio_mock.Net.on_connect net [`Raise (Eio.Net.err (Connection_failure Timeout))];
669669+ get ~net ~host:"example.com" ~path:"index.html";;
670670++mocknet: getaddrinfo ~service:http example.com
671671++mocknet: connect to tcp:127.0.0.1:80
672672+Exception:
673673+Eio.Io Net Connection_failure Timeout,
674674+ connecting to tcp:127.0.0.1:80,
675675+ connecting to "example.com":http,
676676+ fetching http://example.com/index.html
677677+```
678678+679679+To get more detailed information, you can enable backtraces by setting `OCAMLRUNPARAM=b`
680680+or by calling `Printexc.record_backtrace true`, as usual.
681681+682682+When writing MDX tests that depend on getting the exact error output,
683683+it can be annoying to have the full backend-specific error displayed:
684684+685685+<!-- $MDX non-deterministic=command -->
686686+```ocaml
687687+# Eio_main.run @@ fun env ->
688688+ let net = Eio.Stdenv.net env in
689689+ Switch.run @@ fun sw ->
690690+ Eio.Net.connect ~sw net (`Tcp (Eio.Net.Ipaddr.V4.loopback, 1234));;
691691+Exception:
692692+Eio.Io Net Connection_failure Refused Unix_error (Connection refused, "connect", ""),
693693+ connecting to tcp:127.0.0.1:1234
694694+```
695695+696696+If we ran this using another backend, the `Unix_error` part might change.
697697+To avoid this problem, you can use `Eio.Exn.Backend.show` to hide the backend-specific part of errors:
698698+699699+```ocaml
700700+# Eio.Exn.Backend.show := false;;
701701+- : unit = ()
702702+703703+# Eio_main.run @@ fun env ->
704704+ let net = Eio.Stdenv.net env in
705705+ Switch.run @@ fun sw ->
706706+ Eio.Net.connect ~sw net (`Tcp (Eio.Net.Ipaddr.V4.loopback, 1234));;
707707+Exception:
708708+Eio.Io Net Connection_failure Refused _,
709709+ connecting to tcp:127.0.0.1:1234
710710+```
711711+712712+We'll leave it like that for the rest of this file,
713713+so the examples can be tested automatically by MDX.
714714+715715+## Filesystem Access
716716+717717+Access to the filesystem is performed using [Eio.Path][].
718718+An `'a Path.t` is a pair of a capability to a base directory (of type `'a`) and a string path relative to that.
719719+To append to the string part, it's convenient to use the `/` operator:
720720+721721+```ocaml
722722+let ( / ) = Eio.Path.( / )
723723+```
724724+725725+<!--
726726+Cleanup previous runs due to [dune runtest --watch] not doing it
727727+```ocaml
728728+Eio_main.run @@ fun env ->
729729+let cwd = Eio.Stdenv.cwd env in
730730+["link-to-dir1"; "link-to-tmp"; "test.txt"; "dir1"]
731731+|> List.iter (fun p -> Eio.Path.rmtree ~missing_ok:true (cwd / p))
732732+```
733733+-->
734734+735735+`env` provides two initial paths:
736736+737737+- `cwd` restricts access to files beneath the current working directory.
738738+- `fs` provides full access (just like OCaml's stdlib).
739739+740740+You can save a whole file using `Path.save`:
741741+742742+```ocaml
743743+# Eio_main.run @@ fun env ->
744744+ let path = Eio.Stdenv.cwd env / "test.txt" in
745745+ traceln "Saving to %a" Eio.Path.pp path;
746746+ Eio.Path.save ~create:(`Exclusive 0o600) path "line one\nline two\n";;
747747++Saving to <cwd:test.txt>
748748+- : unit = ()
749749+```
750750+751751+For more control, use `Path.open_out` (or `with_open_out`) to get a flow.
752752+753753+To load a file, you can use `load` to read the whole thing into a string,
754754+`Path.open_in` (or `with_open_in`) to get a flow, or `Path.with_lines` to stream
755755+the lines (a convenience function that uses `Buf_read.lines`):
756756+757757+```ocaml
758758+# Eio_main.run @@ fun env ->
759759+ let path = Eio.Stdenv.cwd env / "test.txt" in
760760+ Eio.Path.with_lines path (fun lines ->
761761+ Seq.iter (traceln "Processing %S") lines
762762+ );;
763763++Processing "line one"
764764++Processing "line two"
765765+- : unit = ()
766766+```
767767+768768+Access to `cwd` only grants access to that sub-tree:
769769+770770+```ocaml
771771+let try_save path data =
772772+ match Eio.Path.save ~create:(`Exclusive 0o600) path data with
773773+ | () -> traceln "save %a : ok" Eio.Path.pp path
774774+ | exception ex -> traceln "%a" Eio.Exn.pp ex
775775+776776+let try_mkdir path =
777777+ match Eio.Path.mkdir path ~perm:0o700 with
778778+ | () -> traceln "mkdir %a : ok" Eio.Path.pp path
779779+ | exception ex -> traceln "%a" Eio.Exn.pp ex
780780+```
781781+782782+```ocaml
783783+# Eio_main.run @@ fun env ->
784784+ let cwd = Eio.Stdenv.cwd env in
785785+ try_mkdir (cwd / "dir1");
786786+ try_mkdir (cwd / "../dir2");
787787+ try_mkdir (cwd / "/tmp/dir3");;
788788++mkdir <cwd:dir1> : ok
789789++Eio.Io Fs Permission_denied _, creating directory <cwd:../dir2>
790790++Eio.Io Fs Permission_denied _, creating directory <cwd:/tmp/dir3>
791791+- : unit = ()
792792+```
793793+794794+The checks also apply to following symlinks:
795795+796796+```ocaml
797797+# Unix.symlink "dir1" "link-to-dir1";
798798+ Unix.symlink (Filename.get_temp_dir_name ()) "link-to-tmp";;
799799+- : unit = ()
800800+801801+# Eio_main.run @@ fun env ->
802802+ let cwd = Eio.Stdenv.cwd env in
803803+ try_save (cwd / "dir1/file1") "A";
804804+ try_save (cwd / "link-to-dir1/file2") "B";
805805+ try_save (cwd / "link-to-tmp/file3") "C";;
806806++save <cwd:dir1/file1> : ok
807807++save <cwd:link-to-dir1/file2> : ok
808808++Eio.Io Fs Permission_denied _, opening <cwd:link-to-tmp/file3>
809809+- : unit = ()
810810+```
811811+812812+You can use `open_dir` (or `with_open_dir`) to create a restricted capability to a subdirectory:
813813+814814+```ocaml
815815+# Eio_main.run @@ fun env ->
816816+ let cwd = Eio.Stdenv.cwd env in
817817+ Eio.Path.with_open_dir (cwd / "dir1") @@ fun dir1 ->
818818+ try_save (dir1 / "file4") "D";
819819+ try_save (dir1 / "../file5") "E";;
820820++save <dir1:file4> : ok
821821++Eio.Io Fs Permission_denied _, opening <dir1:../file5>
822822+- : unit = ()
823823+```
824824+825825+You only need to use `open_dir` if you want to create a new sandboxed environment.
826826+You can use a single base directory object to access all paths beneath it,
827827+and this allows following symlinks within that subtree.
828828+829829+A program that operates on the current directory will probably want to use `cwd`,
830830+whereas a program that accepts a path from the user will probably want to use `fs`,
831831+perhaps with `open_dir` to constrain all access to be within that directory.
832832+833833+On systems that provide the [cap_enter][] system call, you can ask the OS to reject accesses
834834+that don't use capabilities.
835835+[examples/capsicum/](./examples/capsicum/) contains an example that
836836+restricts itself to using a directory passed on the command-line, and then
837837+tries reading `/etc/passwd` via the stdlib.
838838+Running on FreeBSD, you should see:
839839+840840+```
841841+mkdir /tmp/cap
842842+dune exec -- ./examples/capsicum/main.exe /tmp/cap
843843++Opened directory <fs:/tmp/cap>
844844++Capsicum mode enabled
845845++Using the file-system via the directory resource works:
846846++Writing <cap:capsicum-test.txt>...
847847++Read: "A test file"
848848++Bypassing Eio and accessing other resources should fail in Capsicum mode:
849849+Fatal error: exception Sys_error("/etc/passwd: Not permitted in capability mode")
850850+```
851851+852852+## Running processes
853853+854854+Spawning a child process can be done using the [Eio.Process][] module:
855855+856856+```ocaml
857857+# Eio_main.run @@ fun env ->
858858+ let proc_mgr = Eio.Stdenv.process_mgr env in
859859+ Eio.Process.run proc_mgr ["echo"; "hello"];;
860860+hello
861861+- : unit = ()
862862+```
863863+864864+There are various optional arguments for setting the process's current directory or connecting up the standard streams.
865865+For example, we can use `tr` to convert some text to upper-case:
866866+867867+```ocaml
868868+# Eio_main.run @@ fun env ->
869869+ let proc_mgr = Eio.Stdenv.process_mgr env in
870870+ Eio.Process.run proc_mgr ["tr"; "a-z"; "A-Z"]
871871+ ~stdin:(Eio.Flow.string_source "One two three\n");;
872872+ONE TWO THREE
873873+- : unit = ()
874874+```
875875+876876+If you want to capture the output of a process, you can provide a suitable `Eio.Flow.sink` as the `stdout` argument,
877877+or use the `parse_out` convenience wrapper:
878878+879879+```ocaml
880880+# Eio_main.run @@ fun env ->
881881+ let proc_mgr = Eio.Stdenv.process_mgr env in
882882+ Eio.Process.parse_out proc_mgr Eio.Buf_read.line ["echo"; "hello"];;
883883+- : string = "hello"
884884+```
885885+886886+All process functions either return the exit status or check that it was zero (success):
887887+888888+```ocaml
889889+# Eio_main.run @@ fun env ->
890890+ let proc_mgr = Eio.Stdenv.process_mgr env in
891891+ Eio.Process.parse_out proc_mgr Eio.Buf_read.take_all ["sh"; "-c"; "exit 3"];;
892892+Exception:
893893+Eio.Io Process Child_error Exited (code 3),
894894+ running command: sh -c "exit 3"
895895+```
896896+897897+`Process.spawn` and `Process.await` give more control over the process's lifetime
898898+and exit status, and `Eio_unix.Process` gives more control over passing file
899899+descriptors (on systems that support them).
900900+901901+## Time
902902+903903+The standard environment provides a [clock][Eio.Time] with the usual POSIX time:
904904+905905+```ocaml
906906+# Eio_main.run @@ fun env ->
907907+ let clock = Eio.Stdenv.clock env in
908908+ traceln "The time is now %f" (Eio.Time.now clock);;
909909++The time is now 1623940778.270336
910910+- : unit = ()
911911+```
912912+913913+The mock backend provides a mock clock that advances automatically where there is nothing left to do:
914914+915915+```ocaml
916916+# Eio_mock.Backend.run_full @@ fun env ->
917917+ let clock = Eio.Stdenv.clock env in
918918+ traceln "Sleeping for five seconds...";
919919+ Eio.Time.sleep clock 5.0;
920920+ traceln "Resumed";;
921921++Sleeping for five seconds...
922922++mock time is now 5
923923++Resumed
924924+- : unit = ()
925925+```
926926+927927+Note: You could also just use `Eio_unix.sleep 5.0` if you don't want to pass a clock around.
928928+This is especially useful if you need to insert a delay for some quick debugging.
929929+930930+## Multicore Support
931931+932932+OCaml allows a program to create multiple *domains* in which to run code, allowing multiple CPUs to be used at once.
933933+Fibers are scheduled cooperatively within a single domain, but fibers in different domains run in parallel.
934934+This is useful to perform CPU-intensive operations quickly
935935+(though extra care needs to be taken when using multiple cores; see the [Multicore Guide](./doc/multicore.md) for details).
936936+937937+### Domain Manager
938938+939939+[Eio.Domain_manager][] provides a basic API for spawning domains.
940940+For example, let's say we have a CPU intensive task:
941941+942942+```ocaml
943943+let sum_to n =
944944+ traceln "Starting CPU-intensive task...";
945945+ let total = ref 0 in
946946+ for i = 1 to n do
947947+ total := !total + i
948948+ done;
949949+ traceln "Finished";
950950+ !total
951951+```
952952+953953+We can use the domain manager to run this in a separate domain:
954954+955955+```ocaml
956956+let main ~domain_mgr =
957957+ let test n =
958958+ traceln "sum 1..%d = %d" n
959959+ (Eio.Domain_manager.run domain_mgr
960960+ (fun () -> sum_to n))
961961+ in
962962+ Fiber.both
963963+ (fun () -> test 100000)
964964+ (fun () -> test 50000)
965965+```
966966+967967+<!-- $MDX non-deterministic=output -->
968968+```ocaml
969969+# Eio_main.run @@ fun env ->
970970+ main ~domain_mgr:(Eio.Stdenv.domain_mgr env);;
971971++Starting CPU-intensive task...
972972++Starting CPU-intensive task...
973973++Finished
974974++sum 1..50000 = 1250025000
975975++Finished
976976++sum 1..100000 = 5000050000
977977+- : unit = ()
978978+```
979979+980980+<p align='center'>
981981+ <img src="./doc/traces/multicore-posix.svg"/>
982982+</p>
983983+984984+Notes:
985985+986986+- `traceln` can be used safely from multiple domains.
987987+ It takes a mutex, so that trace lines are output atomically.
988988+- The exact `traceln` output of this example is non-deterministic,
989989+ because the OS is free to schedule domains as it likes.
990990+- You must ensure that the function passed to `run` doesn't have access to any non-threadsafe values.
991991+ The type system does not check this.
992992+- `Domain_manager.run` waits for the domain to finish, but it allows other fibers to run while waiting.
993993+ This is why we use `Fiber.both` to create multiple fibers.
994994+995995+### Executor Pool
996996+997997+An [Eio.Executor_pool][] distributes jobs among a pool of domain workers.
998998+Domains are reused and can execute multiple jobs concurrently.
999999+10001000+Each domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.
10011001+The `~weight` represents the expected proportion of a CPU core that the job will take up.
10021002+Jobs are queued up if they cannot be started immediately due to all domain workers being busy (`>= 1.0`).
10031003+10041004+This is the recommended way of leveraging OCaml 5's multicore capabilities.
10051005+10061006+Usually you will only want one pool for an entire application, so the pool is typically created when the application starts:
10071007+10081008+<!-- $MDX skip -->
10091009+```ocaml
10101010+let () =
10111011+ Eio_main.run @@ fun env ->
10121012+ Switch.run @@ fun sw ->
10131013+ let dm = Eio.Stdenv.domain_mgr env in
10141014+ main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)
10151015+```
10161016+10171017+The pool starts its domain workers immediately upon creation.
10181018+10191019+The pool will not block our switch `sw` from completing;
10201020+when the switch finishes, all domain workers and running jobs are cancelled.
10211021+10221022+`~domain_count` is the number of domain workers to create.
10231023+The total number of domains should not exceed `Domain.recommended_domain_count` or the number of cores on your system.
10241024+10251025+We can run the previous example using an Executor Pool like this:
10261026+10271027+```ocaml
10281028+let main ~pool =
10291029+ let test n =
10301030+ traceln "sum 1..%d = %d" n
10311031+ (Eio.Executor_pool.submit_exn pool ~weight:1.0
10321032+ (fun () -> sum_to n))
10331033+ in
10341034+ Fiber.both
10351035+ (fun () -> test 100000)
10361036+ (fun () -> test 50000)
10371037+```
10381038+10391039+<!-- $MDX non-deterministic=output -->
10401040+```ocaml
10411041+# Eio_main.run @@ fun env ->
10421042+ Switch.run @@ fun sw ->
10431043+ let dm = Eio.Stdenv.domain_mgr env in
10441044+ main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)
10451045++Starting CPU-intensive task...
10461046++Starting CPU-intensive task...
10471047++Finished
10481048++sum 1..50000 = 1250025000
10491049++Finished
10501050++sum 1..100000 = 5000050000
10511051+- : unit = ()
10521052+```
10531053+`~weight` is the anticipated proportion of a CPU core used by the job.
10541054+In other words, the fraction of time actively spent executing OCaml code, not just waiting for I/O or system calls.
10551055+In 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.
10561056+`~weight` must be `>= 0.0` and `<= 1.0`.
10571057+Example: given an IO-bound job that averages 2% of one CPU core, pass `~weight:0.02`.
10581058+10591059+Each domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.
10601060+10611061+## Synchronisation Tools
10621062+10631063+Eio provides several sub-modules for communicating between fibers,
10641064+and these work even when the fibers are running in different domains.
10651065+10661066+### Promises
10671067+10681068+[Promises][Eio.Promise] are a simple and reliable way to communicate between fibers.
10691069+One fiber can wait for a promise and another can resolve it:
10701070+10711071+```ocaml
10721072+# Eio_main.run @@ fun _ ->
10731073+ let promise, resolver = Promise.create () in
10741074+ Fiber.both
10751075+ (fun () ->
10761076+ traceln "Waiting for promise...";
10771077+ let x = Promise.await promise in
10781078+ traceln "x = %d" x
10791079+ )
10801080+ (fun () ->
10811081+ traceln "Resolving promise";
10821082+ Promise.resolve resolver 42
10831083+ );;
10841084++Waiting for promise...
10851085++Resolving promise
10861086++x = 42
10871087+- : unit = ()
10881088+```
10891089+10901090+A promise is initially "unresolved", and can only be resolved once.
10911091+Awaiting a promise that is already resolved immediately returns the resolved value.
10921092+10931093+Promises are one of the easiest tools to use safely:
10941094+it doesn't matter whether you wait on a promise before or after it is resolved,
10951095+and multiple fibers can wait for the same promise and will get the same result.
10961096+Promises are thread-safe; you can wait for a promise in one domain and resolve it in another.
10971097+10981098+Promises are also useful for integrating with callback-based libraries. For example:
10991099+11001100+```ocaml
11011101+let wrap fn x =
11021102+ let promise, resolver = Promise.create () in
11031103+ fn x
11041104+ ~on_success:(Promise.resolve_ok resolver)
11051105+ ~on_error:(Promise.resolve_error resolver);
11061106+ Promise.await_exn promise
11071107+```
11081108+11091109+### Example: Concurrent Cache
11101110+11111111+Here's an example using promises to cache lookups,
11121112+with the twist that another user might ask the cache for the value while it's still adding it.
11131113+We don't want to start a second fetch in that case, so instead we just store promises in the cache:
11141114+11151115+```ocaml
11161116+let make_cache fn =
11171117+ let tbl = Hashtbl.create 10 in
11181118+ fun key ->
11191119+ match Hashtbl.find_opt tbl key with
11201120+ | Some p -> Promise.await_exn p
11211121+ | None ->
11221122+ let p, r = Promise.create () in
11231123+ Hashtbl.add tbl key p;
11241124+ match fn key with
11251125+ | v -> Promise.resolve_ok r v; v
11261126+ | exception ex -> Promise.resolve_error r ex; raise ex
11271127+```
11281128+11291129+Notice that we store the new promise in the cache immediately,
11301130+without doing anything that might switch to another fiber.
11311131+11321132+We can use it like this:
11331133+11341134+```ocaml
11351135+# let fetch url =
11361136+ traceln "Fetching %S..." url;
11371137+ Fiber.yield (); (* Simulate work... *)
11381138+ traceln "Got response for %S" url;
11391139+ if url = "http://example.com" then "<h1>Example.com</h1>"
11401140+ else failwith "404 Not Found";;
11411141+val fetch : string -> string = <fun>
11421142+11431143+# Eio_main.run @@ fun _ ->
11441144+ let c = make_cache fetch in
11451145+ let test url =
11461146+ traceln "Requesting %s..." url;
11471147+ match c url with
11481148+ | page -> traceln "%s -> %s" url page
11491149+ | exception ex -> traceln "%s -> %a" url Fmt.exn ex
11501150+ in
11511151+ Fiber.List.iter test [
11521152+ "http://example.com";
11531153+ "http://example.com";
11541154+ "http://bad.com";
11551155+ "http://bad.com";
11561156+ ];;
11571157++Requesting http://example.com...
11581158++Fetching "http://example.com"...
11591159++Requesting http://example.com...
11601160++Requesting http://bad.com...
11611161++Fetching "http://bad.com"...
11621162++Requesting http://bad.com...
11631163++Got response for "http://example.com"
11641164++http://example.com -> <h1>Example.com</h1>
11651165++Got response for "http://bad.com"
11661166++http://bad.com -> Failure("404 Not Found")
11671167++http://example.com -> <h1>Example.com</h1>
11681168++http://bad.com -> Failure("404 Not Found")
11691169+- : unit = ()
11701170+```
11711171+11721172+`Fiber.List.iter` is like `List.iter` but doesn't wait for each job to finish before starting the next.
11731173+Notice that we made four requests, but only started two download operations.
11741174+11751175+This version of the cache remembers failed lookups too.
11761176+You could modify it to remove the entry on failure,
11771177+so that all clients currently waiting still fail,
11781178+but any future client asking for the failed resource will trigger a new download.
11791179+11801180+This cache is not thread-safe.
11811181+You will need to add a mutex if you want to share it between domains.
11821182+11831183+### Streams
11841184+11851185+A [stream][Eio.Stream] is a bounded queue. Reading from an empty stream waits until an item is available.
11861186+Writing to a full stream waits for space.
11871187+11881188+```ocaml
11891189+# Eio_main.run @@ fun _ ->
11901190+ let stream = Eio.Stream.create 2 in
11911191+ Fiber.both
11921192+ (fun () ->
11931193+ for i = 1 to 5 do
11941194+ traceln "Adding %d..." i;
11951195+ Eio.Stream.add stream i
11961196+ done
11971197+ )
11981198+ (fun () ->
11991199+ for i = 1 to 5 do
12001200+ let x = Eio.Stream.take stream in
12011201+ traceln "Got %d" x;
12021202+ Fiber.yield ()
12031203+ done
12041204+ );;
12051205++Adding 1...
12061206++Adding 2...
12071207++Adding 3...
12081208++Got 1
12091209++Adding 4...
12101210++Got 2
12111211++Adding 5...
12121212++Got 3
12131213++Got 4
12141214++Got 5
12151215+- : unit = ()
12161216+```
12171217+12181218+Here, we create a stream with a maximum size of 2 items.
12191219+The first fiber added 1 and 2 to the stream, but had to wait before it could insert 3.
12201220+12211221+A stream with a capacity of 1 acts like a mailbox.
12221222+A stream with a capacity of 0 will wait until both the sender and receiver are ready.
12231223+12241224+Streams are thread-safe and can be used to communicate between domains.
12251225+12261226+### Example: Worker Pool
12271227+12281228+A useful pattern is a pool of workers reading from a stream of work items.
12291229+Client fibers submit items to a stream and workers process the items:
12301230+12311231+```ocaml
12321232+let handle_job request =
12331233+ Fiber.yield (); (* (simulated work) *)
12341234+ Printf.sprintf "Processed:%d" request
12351235+12361236+let rec run_worker id stream =
12371237+ let request, reply = Eio.Stream.take stream in
12381238+ traceln "Worker %s processing request %d" id request;
12391239+ Promise.resolve reply (handle_job request);
12401240+ run_worker id stream
12411241+12421242+let submit stream request =
12431243+ let reply, resolve_reply = Promise.create () in
12441244+ Eio.Stream.add stream (request, resolve_reply);
12451245+ Promise.await reply
12461246+```
12471247+12481248+Each item in the stream is a request payload and a resolver for the reply promise.
12491249+12501250+```ocaml
12511251+# Eio_main.run @@ fun env ->
12521252+ let domain_mgr = Eio.Stdenv.domain_mgr env in
12531253+ Switch.run @@ fun sw ->
12541254+ let stream = Eio.Stream.create 0 in
12551255+ let spawn_worker name =
12561256+ Fiber.fork_daemon ~sw (fun () ->
12571257+ Eio.Domain_manager.run domain_mgr (fun () ->
12581258+ traceln "Worker %s ready" name;
12591259+ run_worker name stream
12601260+ )
12611261+ )
12621262+ in
12631263+ spawn_worker "A";
12641264+ spawn_worker "B";
12651265+ Switch.run (fun sw ->
12661266+ for i = 1 to 3 do
12671267+ Fiber.fork ~sw (fun () ->
12681268+ traceln "Client %d submitting job..." i;
12691269+ traceln "Client %d got %s" i (submit stream i)
12701270+ );
12711271+ Fiber.yield ()
12721272+ done
12731273+ );;
12741274++Worker A ready
12751275++Worker B ready
12761276++Client 1 submitting job...
12771277++Worker A processing request 1
12781278++Client 2 submitting job...
12791279++Worker B processing request 2
12801280++Client 3 submitting job...
12811281++Client 1 got Processed:1
12821282++Worker A processing request 3
12831283++Client 2 got Processed:2
12841284++Client 3 got Processed:3
12851285+- : unit = ()
12861286+```
12871287+12881288+We use a zero-capacity stream here, which means that the `Stream.add` doesn't succeed until a worker accepts the job.
12891289+This is a good choice for a worker pool because it means that if the client fiber gets cancelled while waiting for a worker
12901290+then the job will never be run. It's also more efficient, as 0-capacity streams use a lock-free algorithm that is faster
12911291+when there are multiple domains.
12921292+Note that, while the stream itself is 0-capacity, clients still queue up waiting to use it.
12931293+12941294+In the code above, any exception raised while processing a job will exit the whole program.
12951295+We might prefer to handle exceptions by sending them back to the client and continuing:
12961296+12971297+```ocaml
12981298+let rec run_worker id stream =
12991299+ let request, reply = Eio.Stream.take stream in
13001300+ traceln "Worker %s processing request %d" id request;
13011301+ begin match handle_job request with
13021302+ | result -> Promise.resolve_ok reply result
13031303+ | exception ex -> Promise.resolve_error reply ex; Fiber.check ()
13041304+ end;
13051305+ run_worker id stream
13061306+```
13071307+13081308+The `Fiber.check ()` checks whether the worker itself has been cancelled, and exits the loop if so.
13091309+It's not actually necessary in this case,
13101310+because if we continue instead then the following `Stream.take` will perform the check anyway.
13111311+13121312+Note: in a real system, you would probably use [Eio.Executor_pool][] for this rather than making your own pool.
13131313+13141314+### Mutexes and Semaphores
13151315+13161316+Eio also provides `Mutex` and `Semaphore` sub-modules.
13171317+Each of these corresponds to the module with the same name in the OCaml standard library,
13181318+but allows other fibers to run while waiting instead of blocking the whole domain.
13191319+They are all safe to use in parallel from multiple domains.
13201320+13211321+- [Eio.Mutex][] provides *mutual exclusion*, so that only one fiber can access a resource at a time.
13221322+- [Eio.Semaphore][] generalises this to allow up to *n* fibers to access a resource at once.
13231323+13241324+For example, if we allow loading and saving data in a file there could be a problem
13251325+if we try to load the data while a save is in progress.
13261326+Protecting the file with a mutex will prevent that:
13271327+13281328+```ocaml
13291329+module Atomic_file = struct
13301330+ type 'a t = {
13311331+ path : 'a Eio.Path.t;
13321332+ mutex : Eio.Mutex.t;
13331333+ }
13341334+13351335+ let of_path path =
13361336+ { path; mutex = Eio.Mutex.create () }
13371337+13381338+ let save t data =
13391339+ Eio.Mutex.use_rw t.mutex ~protect:true (fun () ->
13401340+ Eio.Path.save t.path data ~create:(`Or_truncate 0o644)
13411341+ )
13421342+13431343+ let load t =
13441344+ Eio.Mutex.use_ro t.mutex (fun () ->
13451345+ Eio.Path.load t.path
13461346+ )
13471347+end
13481348+```
13491349+13501350+The `~protect:true` in `save` makes the critical section non-cancellable,
13511351+so that if a cancel happens during a save then we will finish writing the data first.
13521352+It can be used like this:
13531353+13541354+```ocaml
13551355+# Eio_main.run @@ fun env ->
13561356+ let dir = Eio.Stdenv.cwd env in
13571357+ let t = Atomic_file.of_path (dir / "data") in
13581358+ Fiber.both
13591359+ (fun () -> Atomic_file.save t "some data")
13601360+ (fun () ->
13611361+ let data = Atomic_file.load t in
13621362+ traceln "Loaded: %S" data
13631363+ );;
13641364++Loaded: "some data"
13651365+- : unit = ()
13661366+```
13671367+13681368+Note: In practice, a better way to make file writes atomic is
13691369+to write the data to a temporary file and then atomically rename it over the old data.
13701370+That will work even if the whole computer crashes, and does not delay cancellation.
13711371+13721372+If the operation being performed is very fast (such as updating some in-memory counters),
13731373+then it is fine to use the standard library's `Mutex` instead.
13741374+13751375+If the operation does not switch fibers *and* the resource is only accessed from one domain,
13761376+then no mutex is needed at all. For example:
13771377+13781378+```ocaml
13791379+(* No mutex needed if only used from a single domain: *)
13801380+13811381+let in_use = ref 10
13821382+let free = ref 0
13831383+13841384+let release () =
13851385+ incr free;
13861386+ decr in_use
13871387+```
13881388+13891389+### Conditions
13901390+13911391+[Eio.Condition][] allows a fiber to wait until some condition is true.
13921392+For example:
13931393+13941394+```ocaml
13951395+module X = struct
13961396+ (* Note: this version is not safe to share across domains! *)
13971397+13981398+ type t = {
13991399+ mutable x : int;
14001400+ changed : Eio.Condition.t;
14011401+ }
14021402+14031403+ let make x = { x; changed = Eio.Condition.create () }
14041404+14051405+ let await_zero t =
14061406+ while t.x <> 0 do Eio.Condition.await_no_mutex t.changed done;
14071407+ traceln "x is now zero"
14081408+14091409+ let set t x =
14101410+ t.x <- x;
14111411+ Eio.Condition.broadcast t.changed;
14121412+ traceln "x set to %d" x
14131413+end
14141414+```
14151415+14161416+```ocaml
14171417+# Eio_mock.Backend.run @@ fun () ->
14181418+ let x = X.make 5 in
14191419+ Fiber.both
14201420+ (fun () ->
14211421+ traceln "Waiting for x to be 0";
14221422+ X.await_zero x
14231423+ )
14241424+ (fun () -> X.set x 0);;
14251425++Waiting for x to be 0
14261426++x set to 0
14271427++x is now zero
14281428+- : unit = ()
14291429+```
14301430+14311431+Note that we need a loop in `await_zero`.
14321432+This is needed because it's possible that another fiber might set it to zero
14331433+and then set it to something else before the waiting fiber resumes.
14341434+14351435+The above version is not safe to share across domains, because `await_zero` relies on the value of `x` not changing
14361436+after `x` is read but before `await_no_mutex` registers itself with the condition.
14371437+Here's a domain-safe version:
14381438+14391439+```ocaml
14401440+module Y = struct
14411441+ (* Safe to share between domains. *)
14421442+14431443+ type t = {
14441444+ mutable y : int;
14451445+ mutex : Eio.Mutex.t;
14461446+ changed : Eio.Condition.t;
14471447+ }
14481448+14491449+ let make y = {
14501450+ y;
14511451+ mutex = Eio.Mutex.create ();
14521452+ changed = Eio.Condition.create ();
14531453+ }
14541454+14551455+ let await_zero t =
14561456+ Eio.Mutex.use_ro t.mutex (fun () ->
14571457+ while t.y <> 0 do Eio.Condition.await t.changed t.mutex done;
14581458+ traceln "y is now zero (at least until we release the mutex)"
14591459+ )
14601460+14611461+ let set t y =
14621462+ Eio.Mutex.use_rw t.mutex ~protect:true (fun () ->
14631463+ t.y <- y;
14641464+ Eio.Condition.broadcast t.changed;
14651465+ traceln "y set to %d" y
14661466+ );
14671467+end
14681468+```
14691469+14701470+Here, `Eio.Condition.await` registers itself with `changed` and only then releases the mutex,
14711471+allowing other threads to change `y`. When it gets woken, it re-acquires the mutex.
14721472+14731473+```ocaml
14741474+# Eio_mock.Backend.run @@ fun () ->
14751475+ let y = Y.make 5 in
14761476+ Fiber.both
14771477+ (fun () ->
14781478+ traceln "Waiting for y to be 0";
14791479+ Y.await_zero y
14801480+ )
14811481+ (fun () -> Y.set y 0);;
14821482++Waiting for y to be 0
14831483++y set to 0
14841484++y is now zero (at least until we release the mutex)
14851485+- : unit = ()
14861486+```
14871487+14881488+Conditions are more difficult to use correctly than e.g. promises or streams.
14891489+In particular, it is easy to miss a notification due to `broadcast` getting called before `await`.
14901490+However, they can be useful if used carefully.
14911491+14921492+### Example: Signal handlers
14931493+14941494+On Unix-type systems, processes can react to *signals*.
14951495+For example, pressing Ctrl-C will send the `SIGINT` (interrupt) signal.
14961496+14971497+Here is an example function that allows itself to be interrupted:
14981498+14991499+```ocaml
15001500+let run_op ~interrupted =
15011501+ Fiber.first
15021502+ (fun () ->
15031503+ Eio.Condition.await_no_mutex interrupted;
15041504+ traceln "Cancelled at user's request."
15051505+ )
15061506+ (fun () ->
15071507+ traceln "Running operation (Ctrl-C to cancel)...";
15081508+ Fiber.await_cancel () (* Simulated work *)
15091509+ )
15101510+```
15111511+15121512+Note that we don't need a mutex here.
15131513+We're just waiting for the number of interrupts received to change,
15141514+and, since that increases monotonically, once we get woken we always want to continue.
15151515+Also, we don't care about missing interrupts from before this operation started.
15161516+15171517+The code here is quite subtle.
15181518+We rely on the fact that the first branch of the `Fiber.first` runs first,
15191519+and only starts running the second branch once `await_no_mutex` has finished registering.
15201520+Thus, we never display the message telling the user to press Ctrl-C before we're ready
15211521+to receive it.
15221522+This isn't likely to matter if a human is responding to the message,
15231523+but if the response is automated then the delay could matter.
15241524+15251525+To run this function, we need to install a signal handler.
15261526+There are very few things that you can do safely in a signal handler.
15271527+For example, you can't take a mutex in a signal handler
15281528+because the signal might have interrupted a fiber that had already locked it.
15291529+However, you can safely call `Eio.Condition.broadcast`:
15301530+15311531+<!-- $MDX non-deterministic=command -->
15321532+```ocaml
15331533+# Eio_main.run @@ fun _env ->
15341534+ let interrupted = Eio.Condition.create () in
15351535+ let handle_signal (_signum : int) =
15361536+ (* Warning: we're in a signal handler now.
15371537+ Most operations are unsafe here, except for Eio.Condition.broadcast! *)
15381538+ Eio.Condition.broadcast interrupted
15391539+ in
15401540+ Sys.set_signal Sys.sigint (Signal_handle handle_signal);
15411541+ run_op ~interrupted;;
15421542++Running operation (Ctrl-C to cancel)...
15431543+[ user presses Ctrl-C here ]
15441544++Cancelled at user's request.
15451545+- : unit = ()
15461546+```
15471547+15481548+Another common pattern when using signals is using `SIGHUP`
15491549+to tell an application to reload its configuration file:
15501550+15511551+<!-- $MDX file=examples/signals/main.ml,part=main -->
15521552+```ocaml
15531553+let main ~config_changed =
15541554+ Eio.Condition.loop_no_mutex config_changed (fun () ->
15551555+ traceln "Reading configuration ('kill -SIGHUP %d' to reload)..." (Unix.getpid ());
15561556+ load_config ();
15571557+ traceln "Finished reading configuration";
15581558+ None (* Keep waiting for futher changes *)
15591559+ )
15601560+```
15611561+15621562+See the `examples/signals` directory for the full code.
15631563+15641564+## Design Note: Determinism
15651565+15661566+Within a domain, fibers are scheduled deterministically.
15671567+Programs using only the Eio APIs can only behave non-deterministically if given a capability to do so from somewhere else.
15681568+15691569+For example, `Fiber.both f g` always starts running `f` first,
15701570+and only switches to `g` when `f` finishes or performs an effect that can switch fibers.
15711571+15721572+Performing IO with external objects (e.g., `stdout`, files, or network sockets) will introduce non-determinism,
15731573+as will using multiple domains.
15741574+15751575+Note that `traceln` is unusual. Although it writes (by default) to stderr, it will not switch fibers.
15761576+Instead, if the OS is not ready to receive trace output, the whole domain is paused until it is ready.
15771577+This means that adding `traceln` to deterministic code will not affect its scheduling.
15781578+15791579+In particular, if you test your code by providing (deterministic) mocks then the tests will be deterministic.
15801580+An easy way to write tests is by having the mocks call `traceln` and then comparing the trace output with the expected output.
15811581+See Eio's own tests for examples, e.g., [tests/switch.md](tests/switch.md).
15821582+15831583+Note: this only applies to the high-level APIs in the `Eio` module.
15841584+Programs can behave non-deterministically when using `Eio_unix` or the various `Low_level` APIs provided by the backends.
15851585+15861586+## Provider Interfaces
15871587+15881588+Eio applications use resources by calling functions (such as `Eio.Flow.write`).
15891589+These functions are actually wrappers that look up the implementing module and call
15901590+the appropriate function on that.
15911591+This allows you to define your own resources.
15921592+15931593+Here's a flow that produces an endless stream of zeros (like "/dev/zero"):
15941594+15951595+```ocaml
15961596+module Zero = struct
15971597+ type t = unit
15981598+15991599+ let single_read () buf =
16001600+ Cstruct.memset buf 0;
16011601+ Cstruct.length buf
16021602+16031603+ let read_methods = [] (* Optional optimisations *)
16041604+end
16051605+16061606+let ops = Eio.Flow.Pi.source (module Zero)
16071607+16081608+let zero = Eio.Resource.T ((), ops)
16091609+```
16101610+16111611+It can then be used like any other Eio flow:
16121612+16131613+```ocaml
16141614+# Eio_main.run @@ fun _ ->
16151615+ let r = Eio.Buf_read.of_flow zero ~max_size:100 in
16161616+ traceln "Got: %S" (Eio.Buf_read.take 4 r);;
16171617++Got: "\000\000\000\000"
16181618+- : unit = ()
16191619+```
16201620+16211621+## Example Applications
16221622+16231623+- [gemini-eio][] is a simple Gemini browser. It shows how to integrate Eio with `ocaml-tls` and `notty`.
16241624+- [cohttp-eio/examples](https://github.com/mirage/ocaml-cohttp/tree/master/cohttp-eio/examples) shows how to use Eio with HTTP.
16251625+- [capnp-rpc](https://github.com/mirage/capnp-rpc) shows how to use Eio with Cap'n Proto.
16261626+- [Awesome Multicore OCaml][] lists many other projects.
16271627+16281628+## Integrations
16291629+16301630+Eio can be used with several other IO libraries.
16311631+16321632+### Async
16331633+16341634+[Async_eio][] has experimental support for running Async and Eio code together in a single domain.
16351635+16361636+### Lwt
16371637+16381638+You can use [Lwt_eio][] to run Lwt threads and Eio fibers together in a single domain,
16391639+and to convert between Lwt and Eio promises.
16401640+This may be useful during the process of porting existing code to Eio.
16411641+16421642+### Unix and System Threads
16431643+16441644+The [Eio_unix][] module provides features for using Eio with OCaml's Unix module.
16451645+In particular, `Eio_unix.run_in_systhread` can be used to run a blocking operation in a separate systhread,
16461646+allowing it to be used within Eio without blocking the whole domain.
16471647+16481648+### Domainslib
16491649+16501650+For certain compute-intensive tasks it may be useful to send work to a pool of [Domainslib][] worker domains.
16511651+You can resolve an Eio promise from non-Eio domains (or systhreads), which provides an easy way to retrieve the result.
16521652+For example:
16531653+16541654+<!-- $MDX skip -->
16551655+```ocaml
16561656+open Eio.Std
16571657+16581658+let pool = Domainslib.Task.setup_pool ~num_domains:2 ()
16591659+16601660+let fib n = ... (* Some Domainslib function *)
16611661+16621662+let run_in_pool fn x =
16631663+ let result, set_result = Promise.create () in
16641664+ let _ : unit Domainslib.Task.promise = Domainslib.Task.async pool (fun () ->
16651665+ Promise.resolve set_result @@
16661666+ match fn x with
16671667+ | r -> Ok r
16681668+ | exception ex -> Error ex
16691669+ )
16701670+ in
16711671+ Promise.await_exn result
16721672+16731673+let () =
16741674+ Eio_main.run @@ fun _ ->
16751675+ Fiber.both
16761676+ (fun () -> traceln "fib 30 = %d" (run_in_pool fib 30))
16771677+ (fun () -> traceln "fib 10 = %d" (run_in_pool fib 10))
16781678+```
16791679+16801680+Note that most Domainslib functions can only be called from code running in the Domainslib pool,
16811681+while most Eio functions can only be used from Eio domains.
16821682+The bridge function `run_in_pool` makes use of the fact that `Domainslib.Task.async` is able to run from
16831683+an Eio domain, and `Eio.Promise.resolve` is able to run from a Domainslib one.
16841684+16851685+### kcas
16861686+16871687+Eio provides the support [kcas][] requires to implement blocking in the
16881688+lock-free software transactional memory (STM) implementation that it provides.
16891689+This means that one can use all the composable lock-free data structures and
16901690+primitives for communication and synchronization implemented using **kcas** to
16911691+communicate and synchronize between Eio fibers, raw domains, and any other
16921692+schedulers that provide the domain local await mechanism.
16931693+16941694+To demonstrate **kcas**
16951695+16961696+```ocaml
16971697+# #require "kcas"
16981698+# open Kcas
16991699+```
17001700+17011701+let's first create a couple of shared memory locations
17021702+17031703+```ocaml
17041704+let x = Loc.make 0
17051705+let y = Loc.make 0
17061706+```
17071707+17081708+and spawn a domain
17091709+17101710+```ocaml
17111711+# let foreign_domain = Domain.spawn @@ fun () ->
17121712+ let x = Loc.get_as (fun x -> Retry.unless (x <> 0); x) x in
17131713+ Loc.set y 22;
17141714+ x
17151715+val foreign_domain : int Domain.t = <abstr>
17161716+```
17171717+17181718+that first waits for one of the locations to change value and then writes to the
17191719+other location.
17201720+17211721+Then we run a Eio program
17221722+17231723+```ocaml
17241724+# let y = Eio_main.run @@ fun _env ->
17251725+ Loc.set x 20;
17261726+ Loc.get_as (fun y -> Retry.unless (y <> 0); y) y
17271727+val y : int = 22
17281728+```
17291729+17301730+that first writes to the location the other domain is waiting on and then waits
17311731+for the other domain to write to the other location.
17321732+17331733+Joining with the other domain
17341734+17351735+```ocaml
17361736+# y + Domain.join foreign_domain
17371737+- : int = 42
17381738+```
17391739+17401740+we arrive at the answer.
17411741+17421742+## Best Practices
17431743+17441744+This section contains some recommendations for designing library APIs for use with Eio.
17451745+17461746+### Switches
17471747+17481748+A function should not take a switch argument if it could create one internally instead.
17491749+17501750+Taking a switch indicates that a function creates resources that outlive the function call,
17511751+and users seeing a switch argument will naturally wonder what these resources may be
17521752+and what lifetime to give them, which is confusing if this is not needed.
17531753+17541754+Creating the switch inside your function ensures that all resources are released
17551755+promptly.
17561756+17571757+```ocaml
17581758+(* BAD - switch should be created internally instead *)
17591759+let load_config ~sw path =
17601760+ parse_config (Eio.Path.open_in ~sw path)
17611761+17621762+(* GOOD - less confusing and closes file promptly *)
17631763+let load_config path =
17641764+ Switch.run @@ fun sw ->
17651765+ parse_config (Eio.Path.open_in ~sw path)
17661766+```
17671767+17681768+Of course, you could use `with_open_in` in this case to simplify it further.
17691769+17701770+### Casting
17711771+17721772+Unlike many languages, OCaml does not automatically cast to super-types as needed.
17731773+Remember to keep the type polymorphic in your interface so users don't need to do this manually.
17741774+17751775+For example, if you need an `Eio.Flow.source` then users should be able to use a `Flow.two_way`
17761776+without having to cast it first:
17771777+17781778+<!-- $MDX skip -->
17791779+```ocaml
17801780+(* BAD - user must cast to use function: *)
17811781+module Message : sig
17821782+ type t
17831783+ val read : Eio.Flow.source_ty r -> t
17841784+end
17851785+17861786+(* GOOD - a Flow.two_way can be used without casting: *)
17871787+module Message : sig
17881788+ type t
17891789+ val read : _ Eio.Flow.source -> t
17901790+end
17911791+```
17921792+17931793+If you want to store the argument, this may require you to cast internally:
17941794+17951795+```ocaml
17961796+module Foo : sig
17971797+ type t
17981798+ val of_source : _ Eio.Flow.source -> t
17991799+end = struct
18001800+ type t = {
18011801+ src : Eio.Flow.source_ty r;
18021802+ }
18031803+18041804+ let of_source x = {
18051805+ src = (x :> Eio.Flow.source_ty r);
18061806+ }
18071807+end
18081808+```
18091809+18101810+### Passing env
18111811+18121812+The `env` value you get from `Eio_main.run` is a powerful capability,
18131813+and programs are easier to understand when it's not passed around too much.
18141814+18151815+In many cases, it's clearer (if a little more verbose) to take the resources you need as separate arguments, e.g.
18161816+18171817+<!-- $MDX skip -->
18181818+```ocaml
18191819+module Status : sig
18201820+ val check :
18211821+ clock:_ Eio.Time.clock ->
18221822+ net:_ Eio.Net.t ->
18231823+ bool
18241824+end
18251825+```
18261826+18271827+You can also provide a convenience function that takes an `env` too.
18281828+Doing this is most appropriate if many resources are needed and
18291829+your library is likely to be initialised right at the start of the user's application.
18301830+18311831+In that case, be sure to request only the resources you need, rather than the full set.
18321832+This makes it clearer what you library does, makes it easier to test,
18331833+and allows it to be used on platforms without the full set of OS resources.
18341834+If you define the type explicitly, you can describe why you need each resource there:
18351835+18361836+<!-- $MDX skip -->
18371837+```ocaml
18381838+module Status : sig
18391839+ type 'a env = 'a constraint 'a = <
18401840+ net : _ Eio.Net.t; (** To connect to the servers *)
18411841+ clock : _ Eio.Time.clock; (** Needed for timeouts *)
18421842+ ..
18431843+ > as 'a
18441844+18451845+ val check : _ env -> bool
18461846+end
18471847+```
18481848+18491849+## Further Reading
18501850+18511851+- [API reference][Eio API]
18521852+- [doc/rationale.md](doc/rationale.md) describes some of Eio's design tradeoffs in more detail.
18531853+- [HACKING.md](./HACKING.md) describes how to work with the Eio source code.
18541854+18551855+Some background about the effects system can be found in:
18561856+18571857+- [Experiences with effects (video)](https://watch.ocaml.org/videos/watch/74ece0a8-380f-4e2a-bef5-c6bb9092be89), OCaml Workshop 2021.
18581858+- ["Retrofitting Concurrency onto OCaml"](https://kcsrk.info/papers/retro-concurrency_pldi_21.pdf) (to appear, PLDI 2021)
18591859+- https://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/
18601860+- Effects examples: https://github.com/ocaml-multicore/effects-examples/tree/master/aio
18611861+- [Concurrent System Programming with Effect Handlers](https://www.repository.cam.ac.uk/bitstream/handle/1810/283239/paper.pdf?sequence=3&isAllowed=y)
18621862+- [Asynchronous effect based IO using effect handlers](https://github.com/kayceesrk/ocaml-aeio)
18631863+18641864+[Eio API]: https://ocaml-multicore.github.io/eio/
18651865+[Lwt_eio]: https://github.com/ocaml-multicore/lwt_eio
18661866+[mirage-trace-viewer]: https://github.com/talex5/mirage-trace-viewer
18671867+[structured concurrency]: https://en.wikipedia.org/wiki/Structured_concurrency
18681868+[gemini-eio]: https://gitlab.com/talex5/gemini-eio
18691869+[Awesome Multicore OCaml]: https://github.com/ocaml-multicore/awesome-multicore-ocaml
18701870+[Eio]: https://ocaml-multicore.github.io/eio/eio/Eio/index.html
18711871+[Eio.Std]: https://ocaml-multicore.github.io/eio/eio/Eio/Std/index.html
18721872+[Eio.Fiber]: https://ocaml-multicore.github.io/eio/eio/Eio/Fiber/index.html
18731873+[Eio.Flow]: https://ocaml-multicore.github.io/eio/eio/Eio/Flow/index.html
18741874+[Eio.Cancel]: https://ocaml-multicore.github.io/eio/eio/Eio/Cancel/index.html
18751875+[Eio.Switch]: https://ocaml-multicore.github.io/eio/eio/Eio/Switch/index.html
18761876+[Eio.Net]: https://ocaml-multicore.github.io/eio/eio/Eio/Net/index.html
18771877+[Eio.Buf_read]: https://ocaml-multicore.github.io/eio/eio/Eio/Buf_read/index.html
18781878+[Eio.Buf_write]: https://ocaml-multicore.github.io/eio/eio/Eio/Buf_write/index.html
18791879+[Eio.Path]: https://ocaml-multicore.github.io/eio/eio/Eio/Path/index.html
18801880+[Eio.Time]: https://ocaml-multicore.github.io/eio/eio/Eio/Time/index.html
18811881+[Eio.Domain_manager]: https://ocaml-multicore.github.io/eio/eio/Eio/Domain_manager/index.html
18821882+[Eio.Executor_pool]: https://ocaml-multicore.github.io/eio/eio/Eio/Executor_pool/index.html
18831883+[Eio.Promise]: https://ocaml-multicore.github.io/eio/eio/Eio/Promise/index.html
18841884+[Eio.Stream]: https://ocaml-multicore.github.io/eio/eio/Eio/Stream/index.html
18851885+[Eio_posix]: https://ocaml-multicore.github.io/eio/eio_posix/Eio_posix/index.html
18861886+[Eio_linux]: https://ocaml-multicore.github.io/eio/eio_linux/Eio_linux/index.html
18871887+[Eio_windows]: https://github.com/ocaml-multicore/eio/blob/main/lib_eio_windows/eio_windows.mli
18881888+[Eio_main]: https://ocaml-multicore.github.io/eio/eio_main/Eio_main/index.html
18891889+[Eio.traceln]: https://ocaml-multicore.github.io/eio/eio/Eio/index.html#val-traceln
18901890+[Eio_main.run]: https://ocaml-multicore.github.io/eio/eio_main/Eio_main/index.html#val-run
18911891+[Eio_mock]: https://ocaml-multicore.github.io/eio/eio/Eio_mock/index.html
18921892+[Eio_unix]: https://ocaml-multicore.github.io/eio/eio/Eio_unix/index.html
18931893+[Async_eio]: https://github.com/talex5/async_eio
18941894+[Eio.Mutex]: https://ocaml-multicore.github.io/eio/eio/Eio/Mutex/index.html
18951895+[Eio.Semaphore]: https://ocaml-multicore.github.io/eio/eio/Eio/Semaphore/index.html
18961896+[Eio.Condition]: https://ocaml-multicore.github.io/eio/eio/Eio/Condition/index.html
18971897+[Domainslib]: https://github.com/ocaml-multicore/domainslib
18981898+[kcas]: https://github.com/ocaml-multicore/kcas
18991899+[Lambda Capabilities]: https://roscidus.com/blog/blog/2023/04/26/lambda-capabilities/
19001900+[Eio.Process]: https://ocaml-multicore.github.io/eio/eio/Eio/Process/index.html
19011901+[Dev meetings]: https://docs.google.com/document/d/1ZBfbjAkvEkv9ldumpZV5VXrEc_HpPeYjHPW_TiwJe4Q
19021902+[Olly]: https://github.com/tarides/runtime_events_tools
19031903+[eio-trace]: https://github.com/ocaml-multicore/eio-trace
19041904+[cap_enter]: https://man.freebsd.org/cgi/man.cgi?query=cap_enter
19051905+[eio_js]: https://github.com/ocaml-multicore/eio_js
+277
spec/fcgi.go
···11+// Copyright 2011 The Go Authors. All rights reserved.
22+// Use of this source code is governed by a BSD-style
33+// license that can be found in the LICENSE file.
44+55+// Package fcgi implements the FastCGI protocol.
66+//
77+// See https://fast-cgi.github.io/ for an unofficial mirror of the
88+// original documentation.
99+//
1010+// Currently only the responder role is supported.
1111+package fcgi
1212+1313+// This file defines the raw protocol and some utilities used by the child and
1414+// the host.
1515+1616+import (
1717+ "bufio"
1818+ "bytes"
1919+ "encoding/binary"
2020+ "errors"
2121+ "io"
2222+ "sync"
2323+)
2424+2525+// recType is a record type, as defined by
2626+// https://web.archive.org/web/20150420080736/http://www.fastcgi.com/drupal/node/6?q=node/22#S8
2727+type recType uint8
2828+2929+const (
3030+ typeBeginRequest recType = 1
3131+ typeAbortRequest recType = 2
3232+ typeEndRequest recType = 3
3333+ typeParams recType = 4
3434+ typeStdin recType = 5
3535+ typeStdout recType = 6
3636+ typeStderr recType = 7
3737+ typeData recType = 8
3838+ typeGetValues recType = 9
3939+ typeGetValuesResult recType = 10
4040+ typeUnknownType recType = 11
4141+)
4242+4343+// keep the connection between web-server and responder open after request
4444+const flagKeepConn = 1
4545+4646+const (
4747+ maxWrite = 65535 // maximum record body
4848+ maxPad = 255
4949+)
5050+5151+const (
5252+ roleResponder = iota + 1 // only Responders are implemented.
5353+ roleAuthorizer
5454+ roleFilter
5555+)
5656+5757+const (
5858+ statusRequestComplete = iota
5959+ statusCantMultiplex
6060+ statusOverloaded
6161+ statusUnknownRole
6262+)
6363+6464+type header struct {
6565+ Version uint8
6666+ Type recType
6767+ Id uint16
6868+ ContentLength uint16
6969+ PaddingLength uint8
7070+ Reserved uint8
7171+}
7272+7373+type beginRequest struct {
7474+ role uint16
7575+ flags uint8
7676+ reserved [5]uint8
7777+}
7878+7979+func (br *beginRequest) read(content []byte) error {
8080+ if len(content) != 8 {
8181+ return errors.New("fcgi: invalid begin request record")
8282+ }
8383+ br.role = binary.BigEndian.Uint16(content)
8484+ br.flags = content[2]
8585+ return nil
8686+}
8787+8888+// for padding so we don't have to allocate all the time
8989+// not synchronized because we don't care what the contents are
9090+var pad [maxPad]byte
9191+9292+func (h *header) init(recType recType, reqId uint16, contentLength int) {
9393+ h.Version = 1
9494+ h.Type = recType
9595+ h.Id = reqId
9696+ h.ContentLength = uint16(contentLength)
9797+ h.PaddingLength = uint8(-contentLength & 7)
9898+}
9999+100100+// conn sends records over rwc
101101+type conn struct {
102102+ mutex sync.Mutex
103103+ rwc io.ReadWriteCloser
104104+ closeErr error
105105+ closed bool
106106+107107+ // to avoid allocations
108108+ buf bytes.Buffer
109109+ h header
110110+}
111111+112112+func newConn(rwc io.ReadWriteCloser) *conn {
113113+ return &conn{rwc: rwc}
114114+}
115115+116116+// Close closes the conn if it is not already closed.
117117+func (c *conn) Close() error {
118118+ c.mutex.Lock()
119119+ defer c.mutex.Unlock()
120120+ if !c.closed {
121121+ c.closeErr = c.rwc.Close()
122122+ c.closed = true
123123+ }
124124+ return c.closeErr
125125+}
126126+127127+type record struct {
128128+ h header
129129+ buf [maxWrite + maxPad]byte
130130+}
131131+132132+func (rec *record) read(r io.Reader) (err error) {
133133+ if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
134134+ return err
135135+ }
136136+ if rec.h.Version != 1 {
137137+ return errors.New("fcgi: invalid header version")
138138+ }
139139+ n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
140140+ if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
141141+ return err
142142+ }
143143+ return nil
144144+}
145145+146146+func (r *record) content() []byte {
147147+ return r.buf[:r.h.ContentLength]
148148+}
149149+150150+// writeRecord writes and sends a single record.
151151+func (c *conn) writeRecord(recType recType, reqId uint16, b []byte) error {
152152+ c.mutex.Lock()
153153+ defer c.mutex.Unlock()
154154+ c.buf.Reset()
155155+ c.h.init(recType, reqId, len(b))
156156+ if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
157157+ return err
158158+ }
159159+ if _, err := c.buf.Write(b); err != nil {
160160+ return err
161161+ }
162162+ if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
163163+ return err
164164+ }
165165+ _, err := c.rwc.Write(c.buf.Bytes())
166166+ return err
167167+}
168168+169169+func (c *conn) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
170170+ b := make([]byte, 8)
171171+ binary.BigEndian.PutUint32(b, uint32(appStatus))
172172+ b[4] = protocolStatus
173173+ return c.writeRecord(typeEndRequest, reqId, b)
174174+}
175175+176176+func (c *conn) writePairs(recType recType, reqId uint16, pairs map[string]string) error {
177177+ w := newWriter(c, recType, reqId)
178178+ b := make([]byte, 8)
179179+ for k, v := range pairs {
180180+ n := encodeSize(b, uint32(len(k)))
181181+ n += encodeSize(b[n:], uint32(len(v)))
182182+ if _, err := w.Write(b[:n]); err != nil {
183183+ return err
184184+ }
185185+ if _, err := w.WriteString(k); err != nil {
186186+ return err
187187+ }
188188+ if _, err := w.WriteString(v); err != nil {
189189+ return err
190190+ }
191191+ }
192192+ w.Close()
193193+ return nil
194194+}
195195+196196+func readSize(s []byte) (uint32, int) {
197197+ if len(s) == 0 {
198198+ return 0, 0
199199+ }
200200+ size, n := uint32(s[0]), 1
201201+ if size&(1<<7) != 0 {
202202+ if len(s) < 4 {
203203+ return 0, 0
204204+ }
205205+ n = 4
206206+ size = binary.BigEndian.Uint32(s)
207207+ size &^= 1 << 31
208208+ }
209209+ return size, n
210210+}
211211+212212+func readString(s []byte, size uint32) string {
213213+ if size > uint32(len(s)) {
214214+ return ""
215215+ }
216216+ return string(s[:size])
217217+}
218218+219219+func encodeSize(b []byte, size uint32) int {
220220+ if size > 127 {
221221+ size |= 1 << 31
222222+ binary.BigEndian.PutUint32(b, size)
223223+ return 4
224224+ }
225225+ b[0] = byte(size)
226226+ return 1
227227+}
228228+229229+// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
230230+// Closed.
231231+type bufWriter struct {
232232+ closer io.Closer
233233+ *bufio.Writer
234234+}
235235+236236+func (w *bufWriter) Close() error {
237237+ if err := w.Writer.Flush(); err != nil {
238238+ w.closer.Close()
239239+ return err
240240+ }
241241+ return w.closer.Close()
242242+}
243243+244244+func newWriter(c *conn, recType recType, reqId uint16) *bufWriter {
245245+ s := &streamWriter{c: c, recType: recType, reqId: reqId}
246246+ w := bufio.NewWriterSize(s, maxWrite)
247247+ return &bufWriter{s, w}
248248+}
249249+250250+// streamWriter abstracts out the separation of a stream into discrete records.
251251+// It only writes maxWrite bytes at a time.
252252+type streamWriter struct {
253253+ c *conn
254254+ recType recType
255255+ reqId uint16
256256+}
257257+258258+func (w *streamWriter) Write(p []byte) (int, error) {
259259+ nn := 0
260260+ for len(p) > 0 {
261261+ n := len(p)
262262+ if n > maxWrite {
263263+ n = maxWrite
264264+ }
265265+ if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
266266+ return nn, err
267267+ }
268268+ nn += n
269269+ p = p[n:]
270270+ }
271271+ return nn, nil
272272+}
273273+274274+func (w *streamWriter) Close() error {
275275+ // send empty record to close the stream
276276+ return w.c.writeRecord(w.recType, w.reqId, nil)
277277+}