It is a great pleasure to announce the release of the first alpha release of Lwt 6. This major version bump brings two major changes to Lwt:
Using Lwt in direct-style! (Big thanks to @c-cube !!)
Using multiple Lwt schedulers running in separate domains!
Direct-style
This contribution from @c-cube is available in alpha00. It comes in the form of an lwt_direct package which provide an Lwt_direct module which provide two core functions:
val run : (unit -> 'a) -> 'a Lwt.t
val await : 'a Lwt.t -> 'a
and allows you to write code such as
run (fun () ->
let continue = ref true in
while !continue do
match await @@ Lwt_io.read_line ic with
| line -> await @@ Lwt_io.write_line oc line
| exception End_of_file -> continue := false
done)
There are a few more functions. All of which is documented in lwt_direct.mli.
Multi-scheduler
This addition is not available in alpha00 but should be added to alpha01 soon. It allows to call Lwt_main.run in different domains and benefit from actual parallelism. (Sneak peek in this pull request)
Installation
lwt.6.0.0~alpha00 and lwt_direct.6.0.0~alpha00 will soon be released on opam (PR on opam-repo. Iāll publish some more alphas as the work progresses, and announce the releases on this thread.
You can also pin the packages to the lwt-6 branch to get everything a little bit earlier:
No, itās !continue, to read the reference . There are multiple ways to write this loop, but here it emphasizes the (new) possibility of writing direct style code that runs on Lwtās scheduler and can wait on any Lwt.t promise.
Dāoh! The perils of working in different languages all the time. I still canāt read it as anything other than āwhile notā even now - the habit is too deeply ingrained. Itās switching between semicolon-terminated and not that mostly gets me for the first hour or two.
Question: why in a separate Lwt_direct module? Why not in the main Lwt module itself? Is it because of the name run? Could we use Lwt.defer and Lwt.now instead?
In the current state of things, lwt_direct requires OCaml 5 while lwt doesnāt. I think otherwise it could make sense for Lwt_direct to live in the lwt library, just maybe not in the Lwt module itself ā after all itās not about promises as much as the moral equivalent of microtasks.
Itās an add-on to lwt offering a different way of thinking, while keeping full compatibility with the ecosystem. A separate module seems reasonable to me
Iām not sure why you would pick names like defer and now. defer sounds like a resource handling function, and now like a clock function?
now is just a contrast to ādeferā, as in āget the value laterā vs āget the value nowā. āDeferā terminology is also used by Jane Street Asyncātheir promise type is 'a Deferred.t.
Maybe async and await would actually be better choices, but there is already Lwt.async so it might be confusing.
I think itās a good question, since @raphael-proust has in the works a multicore PR (one Lwt event loop per domain). Even then I think a separate module would be good, inside the lwt library.
Thanks for explaining defer and now. I think await is non-negotiable, itās almost universal at this point. run is a bit generic and I could imagine there being better names!
May be worth mentioning, the Lwt module is available and very useful in the js-of-ocaml world where effects-compilation is even younger (even kinda experimental?).
Yes, Lwt_direct.await promise will raise the exception e if promise failed with e. It all works normally, try ⦠with, match ⦠with exception ā¦, awaiting inside List.map, etc. Effects are awesome!
Yes, as mentioned by @c-cube the hope is to keep lwt compatible with ocaml4. Itāll likely require some more work on the multi-domain-multi-scheduler feature but I think it should be doable.
As for names, I think a separate module is nice, I can see Lwt_direct.run being renamed Lwt_direct.direct, or maybe the introduction of Lwt_direct.Syntax module which you are meant to open and which provides direct and await and yield so you write let open Lwt_direct.Syntax in direct (⦠bunch of code with awaits ā¦). Suggestions definitelly welcome.
Is the release of Lwt-6 a suitable opportunity to drop the Lwt_process module, as I donāt think it gives Lwt a good look?
That is because that module can no longer be safely used, as Lwt implicitly starts Thread.t threads when encountering blocking calls, and Lwt_unix.set_default_async_method is no longer able to change this in recent versions of Lwt. The problem is that Lwt_process calls up Unix.fork (via Lwt_unix.fork) and also Unix.exec*, none of which is thread safe.
About Unix.fork the OCaml documentation says ā[Unix.fork] fails if the OCaml process is multi-core (any domain has been spawned). In addition, if any thread from the Thread module has been spawned, then the child process might be in a corrupted state.ā
To build an intuition, I would welcome an explanation what run and await do; also, it seems this canāt be implemented with OCaml 4 - but why is that?
run : (unit -> 'a) -> 'a Lwt.t creates a promise, sets up an effect handler to support await during the execution of its argument, executes its argument, resolves the promise with the result that is returned.
await : 'a Lwt.t -> 'a takes a promise as argument. Because of the way the Lwt promises work, that promiseās evaluation is either a resolved promise or a promise waiting for the scheduler to be awoken (when IO is ready or at the next tick). In the former case await simply returns the value. In the latter case await performs an effect. Upon receiving this effect, the handler installed by run attaches the continue callback to the waiting promise (via a simple Lwt.on_any). More specifically, the handler installs a callback to the promise, the callback puts the continue inside a queue, the queue is iterated upon when the scheduler starts a new tick.
There is a bit more to it, like exceptions. But thatās the gist of it.
This is not possible in OCaml 4 because you canāt punch through the scheduler, do some side-effects, and return to wherever you happened to be at.