A common problem that I personally had when developing async code in OCaml is that both Lwt
and Async
leads to useless stack traces. When using lwt_ppx
that is handled by the ppx, but the same is not possible with let*
, let.await
or >>=
.
If you want to solve that, you can use ppx_let_locs, it will detect your calls and when there is a replacement function available that would allow stack traces to be enhanced it will enhance that by default for you.
How it works
Everytime an ident is applied it tries to find a backtrace_ident
or ident_backtrace
version of it, if that is present and it has the right signature, the PPX will then apply the backtraced one with the additional argument. This means it also works with operators, like >>=_backtrace
, as they’re just normal function application.
For letop there is a special case, the signature of the letop must be slightly different to ensure that the typing order was not changed, essentially the let*_backtrace
must have the following signature (exn -> exn * 'a) -> 'b -> 'c
.
To achieve that this is actually a typed PPX, which runs a slightly patched version of the OCaml typechecker, then untype it and pass to the OCaml compiler. This implies a couple of things:
- it needs to be executed as
(staged_pps ppx_let_locs)
on dune - versions must be explicitly supported, for now only 4.10 and 4.12 are supported, if you need any other version feel free to request.
- it can be quite slow on some codebases
To solve the above problems, an important property is that, assuming the backtraced version behaves identically to the non backtraced one, it is in theory a noop, as removing or adding the PPX should not change the behavior of the code. So it can be used only for development or for production, as adding or removing the PPX will not break your code.
Async
Currently it seems like Async doesn’t provide anything similar to Lwt.backtrace_bind
and Async.try_with
also looses the stack trace, so I would love help in this area.
Examples
from:
to:
As you can see, on the second stack trace you can find out that it was actually being called on test_interpretation.ml
.