let+ response = Http_lwt_client.one_request ~meth:`GET (Uri.to_string uri) in
which results in an error
Fatal error: exception File "src/happy_eyeballs.ml", line 185, characters 5-11: Assertion failed
Raised at Happy_eyeballs.timer in file "src/happy_eyeballs.ml", line 185, characters 5-26
Called from Happy_eyeballs_lwt.timer.loop in file "lwt/happy_eyeballs_lwt.ml", line 117, characters 22-56
Called from Lwt.Sequential_composition.bind.create_result_promise_and_callback_if_deferred.callback in file "src/core/lwt.ml", line 1860, characters 23-26
let%lwt doesnāt change the error
Then I tried wrapping the call in an Lwt.catch - I would have expected that Lwt.catch would catch the exception, allowing me to get a more detailed backtrace:
let+ response =
Lwt.catch
(fun () -> Http_lwt_client.one_request ~meth:`GET (Uri.to_string uri))
(fun e -> Lwt.return_error (`Unknown (sexp_of_exn e)))
in
but the catch is ignored - the code never executes and the app crashes
How is Lwtās catch different from JavaScriptās Promise.catch and is there another equivalent - I want to catch exceptions from a Lwt call, so that the whole app doesnāt crash? For example this JavaScript code works as expected - the catch is called
How to debug and get proper traces from Lwt promises? the raised Fatal error is not helpful - it shows that an error occurred in a file that is a dependency of a library that I use; to connect the dots in a bigger context is simply not possible. I want to get the same stack traces that exist in other languages
Can you show the crash? Is there any output from the crash or does the app just silently exit? Can you log a message from the exception handler so you know the control flow reaches it?
As to why it crashes instead of being caught by Lwt.catch: There is an Lwt.async somewhere in your dependencies that creates the promise that causes the failed assertion, so the exception ends up in Lwt.async_exception_hook.
thanks @quernd I am not sure I understand what this means though,
I have Lwt.async code in my code base as well, why is this a cause of the failed assertion?
Lwt.async in itself is not the cause of the failed assertion, but the assert false happens in a promise created through Lwt.async (Iām guessing here).
The danger with Lwt.async is that whenever such a promise is rejected, the exception is not handled locally, but in a global exception handler, whose default behavior is to just print the exception and terminate: Module Lwt
In general, you should avoid async. Itās occasionally useful but it often lead to issues: exceptions are ādetachedā from the normal control-flow of your program, asynced promises can be left still unresolved when your main resolves and your program exits. So the general recommendations would be
Try to program without them. You can often hold onto the promise and join at a reasonable point later. And if you canāt then you may still be able to register the promises in some global ādetached promiseā collection which you can handle from your main.
If you still need detached promises, then instead of Lwt.async (which uses a global mutable exception handler) you should use Lwt.dont_wait (which takes an explicit exception handler at call-site).
thanks Raphael, unfortunately this specific error resulted from a call to a library, so it seems I donāt have direct control in my application code to prevent that from happening since if I understand correctly - there isnāt a catch all function that captures async exceptions and prevents them from bubbling and crashing Lwt.run ?
So in general Iām hoping to understand how to get more detailed errors originating from Lwt, because currently an error that points to a file, that is not even in my direct dependencies is not useful (and was difficult to find where in my whole application this even originates from)
Try to program without them. You can often hold onto the promise and join at a reasonable point later. And if you canāt then you may still be able to register the promises in some global ādetached promiseā collection which you can handle from your main.
my concrete use case is for things like optional notifications, so there isnāt a good place to join promises - I want to trigger an event and return control back to the caller as quickly as possible
If you still need detached promises, then instead of Lwt.async (which uses a global mutable exception handler) you should use Lwt.dont_wait (which takes an explicit exception handler at call-site).
Thanks, thatās a good idea and iāll definitely replace the calls I have control over!
There is such a ācatch-allā exception handler, itās called Lwt.async_exception_hook. Itās a reference that you can set to a different implementation that doesnāt terminate your program: Module Lwt
sorry, I donāt mean a catch all that deals with unhandled exceptions - I donāt mind these crashing the app - I wouldnāt know what to do with them anyways
I mean a catch that captures exceptions originating from a specific function call
For example this JavaScript code calls a function thirdPartyFnCall and catches errors that only originate from it (even though we are not waiting for the promise):
const delay = (timeout: number) => new Promise(r => setTimeout(r, timeout))
const thirdPartyFnCall = async () => {
await delay(100)
throw new Error('a.err')
}
const b = async () => {
thirdPartyFnCall().catch(e => console.log('caught a error', e))
return 99
}
const main = async () => {
const res = await b();
console.log('res', res);
await delay(200);
console.log('END');
}
main();
Such a catch does not exist in Lwt. Although Iām not sure exactly what such a catch function would be because Iām not sure what āoriginating from a function callā means.
In Lwt, the code equivalent to yours is
open Lwt.Syntax
let thirdPartyFnCall () =
let* () = Lwt_unix.sleep 0.1 in
raise Exit
let b () =
Lwt.dont_wait
thirdPartyFnCall
(fun exc -> Printf.printf "caught a error %s\n" (Printexc.to_string exc)) ;
99
let main () =
let res = b () in
Printf.printf "res %d\n" res;
let* () = Lwt_unix.sleep 0.2 in
Printf.printf "END\n";
Lwt.return ()
let () = Lwt_main.run (main ())