We developed tools to help us migrate Ocsigen libraries and applications to direct-style concurrency and we are happy to share them.
There are 4 independent tools for different purposes:
-
Replacing every uses of
lwt_ppxwithLwtfunction calls (e.g. replacinglet%lwt ... inwithlet* ... in).
This is purely syntactic and allows you to easily remove a PPX. It handles insertingopen Lwt.Syntaxat the top of the file. -
Warning about occurrences of
let _ = ...andignore ..., which make the next tools less reliable.
These are called “implicit fork” because code run concurrently if the ignored value is an Lwt thread. This requires an explicit call toFiber.forkwith Eio. -
Migrating uses of
Lwt_logtoLogs.
It was used on ocsigenserver for example.
At this point we generate code working as before and did not introduce a dependency on Eio. -
Migrating uses of
LwttoEio. It rewrites code written in monadic style into direct-style, for example this code:let _ = let* x = f 1 in let+ y = f 2 in Lwt.bind (f 3) (fun z -> Lwt.return (x + y + z))is rewritten to:
let _ = let x = f 1 in let y = f 2 in let z = f 3 in x + y + zIt also rewrite
Lwtfunction calls to their equivalent inEioand handles many IO operations.Unfortunately, this tool doesn’t generate fully equivalent code and requires manual modifications. Comments are inserted in many places where intervention is needed.
An example of generated incorrect code is:
let _ = let a = operation_1 () in let* b = operation_2 () in let* a = a in Lwt.return (a + b)which is rewritten into:
let _ = let a = operation_1 () in let b = operation_2 () in let a = a in a + bThis is incorrect because
operation_1andoperation_2are now sequential but were concurrent before. This is a case of an implicit fork that cannot easily be detected. The correct transformation would be:let _ = let a, b = Eio.Fiber.pair operation_1 operation_2 in a + bWe could achieve the migration on eliom and ocsigenserver
All the tools use OCamlformat under the hood to generate the code. This has the inconvenient of enforcing the use of OCamlformat before using them as they will reformat the entire codebase.
The last two tools use Merlin under the hood to locate uses of Lwt functions in the source files. This works by reading Merlin’s ocaml-index files built by Dune to extract the location in the source files that we need.
Currently, it only supports Eio as a backend but can easily be adapted to other direct-style concurrency libraries.
This work was made possible thanks to the support of the NGI Zero Core fund through the Nlnet foundation, and is perfomed by Tarides.