Best practice on mixing Async and Lwt?

Sorry if this is a FAQ or a point of contention, I couldn’t find an answer typing keywords into various search boxes.

I’m starting a new app in Async but will need to use a library or two written in Lwt, so I’d like to minimize Lwt and wrap it in Async stuff if possible.

I have a pretty good handle on Async’s conceptual model and making it co-exist in non-Async environments but not sure how it would collide with Lwt.

Has anyone been down this road already? Any pointers besides “lol just use Lwt for everything”? :smiley:

(I notice there’s an lwt-async library on github but it’s not released into opam. Wondering if that’s a sign…)

Actually I forgot this was the internet, so I’ll start this by posting the wrong answer.

I hereby decree that the best practice for mixing Async and Lwt is to spawn a child sub-process that reads requests on stdin to run Lwt actions and writes Lwt result responses to stdout. The parent can use Async and communicate with the Lwt sub-process through a pipe (using Async too). Both async libraries are nicely isolated from one another. Sure you have to come up with an ad-hoc pipe protocol but it’ll be easy because it’s OCaml. For extra speed and convenience at the expense of safety you can just use Marshal. Yeah, problem solved.

This introduces an inter-process communication latency but surely it’ll be negligible. UNIX is, like, totally designed for really really fast pipe I/O right?

1 Like

This is the right answer.

1 Like

I’m going to be annoying and ask: what would happen if you stuck with Lwt for the entire stack?

1 Like

It’s actually not clear to me why Async and Lwt can’t coexist in the same process context. As long as both libraries don’t try to claim fds or threads they didn’t create and don’t try to use signals they should be good.

Except for the brain bending required to be inside of an Async deferred waiting on an Lwt deferred. You might still need to use a UNIX pipe to broker between worlds but it should be possible to pass a pointer instead of marshalling data structures through it.

I just know Async already and would rather not trade in the sometimes bitter learned experiences for a fresh set of roughly equivalent unknowns with Lwt.

The safest way to do this is wrap Lwt code within Async, and to invoke the Lwt_main runtime thread via Async_unix.In_thread. But you need to be careful to not introduce deadlocks between Async and Lwt promises, so try to keep the Lwt evaluation fairly well contained.

Which Lwt libraries do you need to use from Async, out of curiosity?

Cool. What does a deadlock here look like? Perhaps I’m misunderstanding Lwt, but if both schedulers live in their own separate threads shouldn’t there be no additional deadlock risk?

Right now it’s the USB library that adds an Lwt runtime to my project but if exploratory stuff goes well there may be others.

It’s more about this warning in Async:

WARNING: Async code MUST NOT be used from within f . By Async code we mean pretty-much all functions of libraries making use of Async. Only a few functions of the Async library can be called inside In_thread.run . These are explicitly marked as such, using the phrase “thread-safe”.

So when you dispatch to Lwt, only do Lwt in that thread. Not many people have tried this before, so let us know how it goes. The lwt-in-async project you found was an experiment that @diml did to turn the actual definition of Lwt.t into an Async.Deferred, but we ended up deciding that the semantics between the two were sufficiently different that it wasn’t practical to upstream such a merger. That might have changed in recent years as @antron has been steadily cleaning up the innards of Lwt…

1 Like

Ah, I thought you might have been driving at that In_thread.run warning but wondered if it was actually about something else because every Async user ought to know that already :x-smug-grin-emoji:

Will happily report back on how it goes!

2 Likes