I roughly translated Real World OCaml's Async concurrency chapter to eio

,

Repo at rwo-eio/lib/rwo_eio.ml at main · dangdennis/rwo-eio · GitHub

I was inspired by Tarides’s Make an Eio version of the Async examples in Real World OCaml to translate the Async examples to eio to test out eio’s concurrency story. Warning, it’s a rough translation. I hardly know OCaml and eio as well as I know my day-job languages :smile: .

There are still a few examples I haven’t figured out.

  1. I don’t know how to implement copy_blocks. In this section, the example uses an intermediate buffer of some sorts to then copy from reader to writer. For now, I’ve left that intermediate buffer out.
  2. I can’t find an interrupt option in cohttp-eio as well as choice and choose. The book explains that cohttp-async can cancel http requests via an interrupt (see section).
  3. For log_delays, I have yet to solve how to await my own every ticker such that I can await its completion and then log the timer at the end.
8 Likes

Thanks for doing this! A few thoughts:

Paths

Eio uses the Eio.Path.t type for paths, while Async using strings. Rather than continuing to use strings everywhere and passing cwd, it’s simpler and more flexible to pass Eio paths. And since paths have their own type and can’t be confused with strings, you don’t need a labelled argument here either.

e.g. instead of:

save ~cwd ~path:"test.txt" ~content:"This is only a test."

I would do:

save (cwd / "test.txt") "This is only a test."

Also, for plain text files, you don’t want the execute bit set, so 0o666 would be a better mode.

Delayer

It’s not clear to me from the book what exactly this is supposed to do. If I’m reading the Async code correctly, it wants to run the callbacks in order, but it seems happy for them to overlap, which is odd.

Eio will already schedule things in order this way, so simply forking a fiber that sleeps and then performs the action should work.

If you wanted to do it the book’s way, I’d suggest moving the switch and clock to create (it doesn’t make sense to use the same delay with a different clock). This reminds me that we should add an Eio.Time.Timeout.sleep function. Then the delay could be a Timeout.t.

You could also remove the promises from schedule’s signature so that it becomes:

  val schedule : t -> (unit -> 'b) -> 'b

It’s not clear from the book whether it returning a promise is desired, or just a side-effect of how Async works. Assuming it’s unnecessary, the whole Delayer could be replaced by a sleep followed by the code you want to run.

Echo server

Using Flow.copy seems fine. Typically it will fall back to using a loop like Async does. It does have push-back, assuming the target flow isn’t buffered (which it won’t be unless wrapped with an Eio.Buf_write).

Note: you can use Fiber.fork_daemon to run the server without it preventing the switch from finishing (so that when the client work is done and the switch ends, the server will be stopped automatically).

Cancellation

I can’t find an interrupt option in cohttp-eio

You can interrupt any Eio operation by cancelling its fiber. Eio.Time.Timeout.run is an easy way to do that.

Logging delays

I have yet to solve how to await my own every ticker

You can just run a loop that waits a bit and prints the result. Run it in a switch with Fiber.fork_daemon so that it stops automatically when the main function is done.

Hope that helps!

3 Likes

Thank you @talex5! Insightful. I’ll update accordingly with your suggestions.

@talex5 I can’t figure out how to write out the type for Eio.Time.clock. May I get your advice?

In my module signature for Delayer_intf, I can type clock as “_ Eio.Time.clock”, and that’s what I see your examples too.

But if I need to define a concrete clock type in my actual module for type t, I can’t do something like “float Eio.Time.clock”. What’s the solution here?

It seems like type 'a t = { ...; 'a Eio.Time.clock } should work?

Thanks for chiming in @yawaramin.

For context, I’m roughly following the chapter’s example. The book defines the module signature with a type t.

Applying your suggestion, I still get an error. Here’s my current broken snippet.

module type Delayer_intf = sig
  type 'a t

  val create : 'clock -> t
end

module Delayer : Delayer_intf = struct
  type 'a t = { clock: 'a Eio.Time.clock; }

  let create clock : t = { clock }
end

The type error itself.

Signature mismatch:
...
Type declarations do not match:
  type 'a t = { clock : 'a Eio_unix.source; }
    constraint 'a = [> float Eio.Time.clock_ty ]
is not included in
  type 'a t
Their parameters differ
The type [> float Eio.Time.clock_ty ] is not equal to the type 'a

Shouldn’t that be val create : 'clock -> 'clock t?

Yeah, it’s a bit overly polymorphic in places! Eio.Time.Mono.ty r is the type for a basic monotonic clock (which is best for delays). See also: Casting.