I’m pleased to announce the experimental release of Miou (0.0.1~beta1): a round-robin scheduler for OCaml 5. Miou is a small library that focuses on implementing system & network applications.
You can now find the project on GitHub, Robur’s repository, as well as its documentation and a few tutorials. You can install it via opam: opam install miou. It requires at least OCaml 5.0.0. This release was marked by the implementation of a happy-eyeballs/dns1 client combining concurrency and parallelism. The aim is to be able to continue implementing services using this library and to guide the user/developer towards good system practices.
The project is still in an iteration phase with the implementation of services such as dns and email in order to validate our design. This release invites you to iterate with us to benefit from everyone’s experience.
We will complete this thread with articles specifying the implementation of Miou. The documentation gives a good overview of our objectives and the Miou framework. If you are interested, we invite you to read it.
We would like to thank everyone who has been involved in any way in the project for their experience and their contributions. And we hope to consider other experiences and feedback in order to develop Miou in the same way as Robur has already done for the other projects.
I’m thinking of detailing the reasons why we set out to make Miou knowing about Eio’s existence. But I’ll give a quick description of the differences.
Miou has had several iterations internally and one of them was very close to what Eio can offer. But an informed choice was made to implement a round-robin scheduler to better match our objectives in order to implement services (dns, email, http, etc.).1 In this respect, and as far as I know, Eio has never described a scheduling policy so formally.
Miou drew a lot of inspiration from affect when it came to composability. This is a question that Eio tried to answer with objects (but it seems, for obscure reasons, that this is no longer the case). As far as we’re concerned, affect and lwt (with lwt.unix) seem to be the right compromise for us, developing unikernels and unix applications. We do not wish to incorporate the idea of capabilities.2 We just propose miou (the heart of the library) as well as miou.unix for the development of unix applications.
Finally, on a slightly more social subject, where there is perhaps no difference but which we would like to make clearer, we would like to keep development minimalist but commit ourselves to the long-term maintenance of Miou, while leaving room for other people to complete what may be missing from the Miou framework. In other words, predating on community spaces in order to create a monopolising piece of software is certainly not our goal.
Once again, there are some fundamental technical differences (such as the use of Effect.Shallow instead of Effect.Deep). But I’m going to write a few articles to clarify these differences and, more importantly, their implications. What is certain is that Miou is no better than Eio (or worse, depending on your point of view), it completes the multiplicity of solutions in a fundamentally erratic world .
1: There isn’t just one type of scheduler for any application. In addition, we would certainly advise you to use moonpool for certain applications. The fact that we have formally assumed the implementation of a round-robin scheduler allows us to identify and describe the drawbacks of this type of scheduler in detail (because there are some in all the proposed solutions).
2: We weren’t convinced by the choice of capabilities or the new methods for injecting a backend. We’ve experimented with them at several levels (passing through values, functors, variants, etc.) with unikernels and they can be very powerful for certain uses but too complex for others. In a use that would inevitably become systematic (since we’re talking here about the scheduler - a central element in your application), the old design of lwt (and lwt.unix) seems to us to be the simplest and sufficiently expressive for the development of unikernels (on which the POSIX interface is unavailable): c’est dans les vieux pots qu’on fait les meilleurs soupes.
But an informed choice was made to implement a round-robin scheduler […] In this respect, and as far as I know, Eio has never described a scheduling policy so formally.
Eio keeps a queue of runnable fibers per domain, so for pure computation it is always round-robin. For IO, it is up to the backend how to schedule IO, and backends could allow some sources to be prioritised. However, all current backends just do RR too. For example, here’s a test case reading from /dev/zero (which should never need to block):
let ( / ) = Eio.Path.( / )
let () =
Eio_main.run @@ fun env ->
Eio.Path.with_open_in (env#fs / "/dev/zero") @@ fun zero ->
let test l =
let buf = Cstruct.create 10 in
for _ = 1 to 1000 do
let got = Eio.Flow.single_read zero buf in
traceln "%s: read %d bytes" l got
(fun () -> test "A")
(fun () -> test "B")
I get this output for both the eio_posix and eio_linux (uring) backends. If you don’t see that, let me know; it’s likely a bug.
Once again, there are some fundamental technical differences (such as the use of Effect.Shallow instead of Effect.Deep).
Note that the choice of how to handle effects is up to the backend in Eio. All current backends do use Deep handlers, indeed (I have no strong opinions about this and am curious to know if shallow handlers would offer some benefit).
If we’re talking about a round-robin scheduler, you need to specify a quanta. Suspending a task doesn’t just depend on whether you want to suspend it. You must have a limit that suspends the task independently of the state of the task - which is, in general, time.
The availability of tasks to receive system events depends on this limit - you can’t assume that a task waiting (for a connection, for example) is more likely to be executed than another task waiting (for a packet). The important thing is that A or B have the same chance of receiving events (whether they arrive immediately or in an hour’s time).
Cool library! I’m very happy to see different schedulers because I think the space is still so new it’s good to explore it and find the good bits and the limitations of different designs and ultimately everyone is free to use the scheduler they wish.
A few questions after taking it for a spin. First, and most selfishly, do you think you would consider supporting Meio (not to be confused with Miou :)). Contrary to Meio’s name, I came to think of it as potentially a generic task/async viewing library. I’m not familiar enough with all the internals of Miou yet but I tried shoehorning things in and got a very minimal product working. Some changes here to generalise Meio and hasty changes to Miou. Meio is still along way away, it requires at least the custom runtime events coming in OCaml 5.1.0, but thought I would ask because I think it would be good for Meio to be a little bit more agnostic. The image is from Meio monitoring Miou.
I’m looking forward to this. One choice is making tasks return promises which you explicitly await as compared to Eio’s default structured concurrency approach? I suppose with the promises you could implement a form of structured concurrency should you wish?
Reading the library source code, the quanta in Miou is number of times you perform an effect? So if I understand correctly once a fiber has performed quanta effects its time being the fiber currently running is over? If that’s the case I assume in Eio it is the same except quanta is not configurable and is implicitly 1… perhaps I’ve completely missed something though. Does Miou also assume to be the outermost effect handler as it seems to capture effects it doesn’t understand and raise Invalid_effect ?
Thank you for your reply, it’s always nice to see that people accept the multiplicity of solutions .
I really like meio! At the moment, Miou uses a log system to show what’s going on (see MIOU_DEBUG=1 ...), but I’m keeping something to do with meio in mind. If I understand correctly, it’s important for Miou to emit events to OCaml. In that case, it should be easy to find a bridge between Miou and meio I think. Miou’s code base is really simple. So we can probably find some time together to think about that and implement something in the end.
Your change seems more or less right to me, so I think we can start discussing what you’re proposing in more detail. But first of all, thank you for your work!
Yes, it’s an approach that we’ve more or less already tried out with awa-ssh for example. It’s also a response to a number of memory leak problems that we’ve already seen (especially on mirage-tcpip) which can be explained by the fact that Lwt.async allows you to forget a task.
This is a design choice that we support, even if it requires us to rethink certain implementations. I mentioned happy-eyeballs in the introduction because it was one of the projects where we did a whole bunch of contortions in relation to lwt.
That’s exactly it, and if you know the fundamental difference between Effect.Deep and Effect.Shallow, you should begin to understand why we used the latter .
Again, as far as the round-robin scheduler is concerned, quanta is perhaps the most important thing to explain to the user - it’s obviously not just a matter of putting a task at the end of the toto-list after it’s made a system call. Quanta has a real influence on the performance of your application. In addition, we’ve applied ourselves to writing a tutorial on this subject. More generally, and as I replied to @zapashcanon, formally establishing a scheduler’s task management policy is really what needs to be explained to the user.
If we consider the round-robin scheduler and its quanta, we have to write our application in a certain way. It would be better if the user had the means to think about this certain way .
At the moment, it raises an exception if it doesn’t know the effect, so I admit I haven’t really asked the question yet.
As far as DLA is concerned, I still have a lot of worries about understanding what I could do considering our domain pool implementation. I sincerely hope I’ll have time to talk to @polytypic and find a solution that satisfies us both (I’ve already started talking to him about this). But yes, it’s on my TODO list!
Then with those difference how to render them nicely – for example whilst hacking together the Miou x Meio example I had to change rendering based on tasks vs. cancellation contexts. The latter being more Eio specific in the sense of there always being one or more.
But as I said we’re a while away from that, but it would be good to have multiple inputs on the correct API to see if it can be as useful as possible without becoming overly complex to accommodate the various schedulers.