Skip to content

Commit 5907991

Browse files
Merge pull request #963 from ocsigen/export-reraise-and-update-recommendations
Export reraise, document recommended use
2 parents f7d95ac + d70f6e6 commit 5907991

File tree

4 files changed

+51
-43
lines changed

4 files changed

+51
-43
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
* Lwt_result.catch now takes a function (unit -> 'a t) rather than a promise ('a t) (#965)
66

7+
====== Additions ======
8+
9+
* Lwt.reraise an exception raising function which preserves backtraces, recommended for use in Lwt.catch (#963)
10+
711
====== Build ======
812

913
* Remove unused dependency in dune file. (#969, Kate Deplaix)

src/core/lwt.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,7 @@ sig
16771677
(* Main interface (public) *)
16781678
val bind : 'a t -> ('a -> 'b t) -> 'b t
16791679
val map : ('a -> 'b) -> 'a t -> 'b t
1680+
external reraise : exn -> 'a = "%reraise"
16801681
val catch : (unit -> 'a t) -> (exn -> 'a t) -> 'a t
16811682
val finalize : (unit -> 'a t) -> (unit -> unit t) -> 'a t
16821683
val try_bind : (unit -> 'a t) -> ('a -> 'b t) -> (exn -> 'b t) -> 'b t
@@ -2004,6 +2005,8 @@ struct
20042005
add_implicitly_removed_callback p_callbacks callback;
20052006
p''
20062007

2008+
external reraise : exn -> 'a = "%reraise"
2009+
20072010
let catch f h =
20082011
let p = try f () with exn -> fail exn in
20092012
let Internal p = to_internal_promise p in

src/core/lwt.mli

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -458,10 +458,14 @@ val fail : exn -> _ t
458458
captures a backtrace, while [Lwt.fail] does not. If you call [raise exn] in
459459
a callback that is expected by Lwt to return a promise, Lwt will
460460
automatically wrap [exn] in a rejected promise, but the backtrace will have
461-
been recorded by the OCaml runtime. Use [Lwt.fail] only when you
462-
specifically want to create a rejected promise, to pass to another function,
463-
or store in a data structure. *)
461+
been recorded by the OCaml runtime.
464462
463+
For example, [bind]'s second argument is a callback which returns a promise.
464+
And so it is recommended to use [raise] in the body of that callback. This
465+
applies to the aliases of [bind] as well: [( >>= )] and [( let* )].
466+
467+
Use [Lwt.fail] only when you specifically want to create a rejected promise,
468+
to pass to another function, or store in a data structure. *)
465469

466470

467471
(** {3 Callbacks} *)
@@ -574,6 +578,16 @@ let () =
574578

575579
(** {2:2_Rejection Rejection} *)
576580

581+
external reraise : exn -> 'a = "%reraise"
582+
(** [reraise e] raises the exception [e]. Unlike [raise e], [reraise e]
583+
preserves the existing exception backtrace and even adds a "Re-raised at"
584+
entry with the call location.
585+
586+
This function is intended to be used in the exception handlers of
587+
[Lwt.catch] and [Lwt.try_bind].
588+
589+
It is also used in the code produced by Lwt_ppx. *)
590+
577591
val catch : (unit -> 'a t) -> (exn -> 'a t) -> 'a t
578592
(** [Lwt.catch f h] applies [f ()], which returns a promise, and then makes it
579593
so that [h] (“handler”) will run when that promise is {{!t} {e rejected}}.
@@ -582,10 +596,10 @@ val catch : (unit -> 'a t) -> (exn -> 'a t) -> 'a t
582596
let () =
583597
Lwt_main.run begin
584598
Lwt.catch
585-
(fun () -> Lwt.fail Exit)
599+
(fun () -> raise Exit)
586600
(function
587601
| Exit -> Lwt_io.printl "Got Stdlib.Exit"
588-
| exn -> Lwt.fail exn)
602+
| exn -> Lwt.reraise exn)
589603
end
590604
591605
(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)
@@ -598,15 +612,15 @@ let () =
598612
{[
599613
let () =
600614
Lwt_main.run begin
601-
try%lwt Lwt.fail Exit
615+
try%lwt raise Exit
602616
with Exit -> Lwt_io.printl "Got Stdlb.Exit"
603617
end
604618
605619
(* ocamlfind opt -linkpkg -thread -package lwt_ppx,lwt.unix code.ml && ./a.out *)
606620
]}
607621
608622
A particular advantage of the PPX syntax is that it is not necessary to
609-
artificially insert a catch-all [exn -> Lwt.fail exn] case. Like in the core
623+
artificially insert a catch-all [exn -> reraise exn] case. Like in the core
610624
language's [try] expression, the catch-all case is implied in [try%lwt].
611625
612626
[Lwt.catch] is a counterpart to {!Lwt.bind} – {!Lwt.bind} is for
@@ -640,33 +654,7 @@ let () =
640654
- If [h exn] instead returns the promise [p_2], [p_3] is effectively made
641655
into a reference to [p_2]. This means [p_3] and [p_2] have the same state,
642656
undergo the same state changes, and performing any operation one is
643-
equivalent to performing it on the other.
644-
645-
{b (2)} {b Warning}: it may be tempting to write this code, which differs
646-
from the second example above only in that [try] is used instead of
647-
[try%lwt]:
648-
649-
{[
650-
let () =
651-
Lwt_main.run begin
652-
try Lwt.fail Exit
653-
with Exit -> Lwt_io.printl "Got Stdlib.Exit"
654-
end
655-
656-
(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)
657-
]}
658-
659-
This does {e not} handle the exception and does not print the message.
660-
Instead, it terminates the program with an unhandled [Stdlib.Exit].
661-
662-
This is because the call to {!Lwt.fail} creates a rejected promise. The
663-
promise is still an ordinary OCaml value, though, and not a {e raised}
664-
exception. So, [try] considers that code to have succeeded, and doesn't run
665-
the handler. When that rejected promise reaches {!Lwt_main.run},
666-
it is {!Lwt_main.run} that raises the exception.
667-
668-
Basically, the rule is: if the code inside [try] evaluates to a promise
669-
(has type [_ Lwt.t]), replace [try] by [try%lwt]. *)
657+
equivalent to performing it on the other. *)
670658

671659
val finalize : (unit -> 'a t) -> (unit -> unit t) -> 'a t
672660
(** [Lwt.finalize f c] applies [f ()], which returns a promise, and then makes
@@ -755,6 +743,23 @@ val try_bind : (unit -> 'a t) -> ('a -> 'b t) -> (exn -> 'b t) -> 'b t
755743
fulfilled with, and, respectively, the exception [f ()] was rejected
756744
with.
757745
746+
As with {!Lwt.catch}, it is recommended to use {!reraise} in the catch-all
747+
case of the exception handler:
748+
749+
{[
750+
let () =
751+
Lwt_main.run begin
752+
Lwt.try_bind
753+
(fun () -> raise Exit)
754+
(fun () -> Lwt_io.printl "Got Success")
755+
(function
756+
| Exit -> Lwt_io.printl "Got Stdlib.Exit"
757+
| exn -> Lwt.reraise exn)
758+
end
759+
760+
(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)
761+
]}
762+
758763
The rest is a detailed description of the promises involved.
759764
760765
As with {!Lwt.finalize} and the several preceding functions, three promises
@@ -892,7 +897,7 @@ val async_exception_hook : (exn -> unit) ref
892897
top level of the program:
893898
894899
{[
895-
let () = Lwt.async (fun () -> Lwt.fail Exit)
900+
let () = Lwt.async (fun () -> raise Exit)
896901
897902
(* ocamlfind opt -linkpkg -package lwt code.ml && ./a.out *)
898903
]}

src/ppx/ppx_lwt.ml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,8 @@ let gen_binds e_loc l e =
7777
let new_exp =
7878
let loc = e_loc in
7979
[%expr
80-
let module Reraise = struct external reraise : exn -> 'a = "%reraise" end in
8180
Lwt.backtrace_bind
82-
(fun exn -> try Reraise.reraise exn with exn -> exn)
81+
(fun exn -> try Lwt.reraise exn with exn -> exn)
8382
[%e name]
8483
[%e fun_]
8584
]
@@ -92,9 +91,8 @@ let lwt_sequence mapper ~exp ~lhs ~rhs ~ext_loc =
9291
let lhs, rhs = mapper#expression lhs, mapper#expression rhs in
9392
let loc = exp.pexp_loc in
9493
[%expr
95-
let module Reraise = struct external reraise : exn -> 'a = "%reraise" end in
9694
Lwt.backtrace_bind
97-
(fun exn -> try Reraise.reraise exn with exn -> exn)
95+
(fun exn -> try Lwt.reraise exn with exn -> exn)
9896
[%e lhs]
9997
(fun [%p pat] -> [%e rhs])
10098
]
@@ -222,9 +220,8 @@ let lwt_expression mapper exp attributes ext_loc =
222220
let new_exp =
223221
let loc = !default_loc in
224222
[%expr
225-
let module Reraise = struct external reraise : exn -> 'a = "%reraise" end in
226223
Lwt.backtrace_catch
227-
(fun exn -> try Reraise.reraise exn with exn -> exn)
224+
(fun exn -> try Lwt.reraise exn with exn -> exn)
228225
(fun () -> [%e expr])
229226
[%e pexp_function ~loc cases]
230227
]
@@ -308,9 +305,8 @@ class mapper = object (self)
308305
let new_exp =
309306
let loc = !default_loc in
310307
[%expr
311-
let module Reraise = struct external reraise : exn -> 'a = "%reraise" end in
312308
Lwt.backtrace_finalize
313-
(fun exn -> try Reraise.reraise exn with exn -> exn)
309+
(fun exn -> try Lwt.reraise exn with exn -> exn)
314310
(fun () -> [%e exp])
315311
(fun () -> [%e finally])
316312
]

0 commit comments

Comments
 (0)