Skip to content

Commit 2eee2a1

Browse files
Merge pull request #964 from ocsigen/dont-catch-ocaml-runtime-exceptions
Dont catch ocaml runtime exceptions
2 parents 792ab06 + 032b120 commit 2eee2a1

19 files changed

+450
-45
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
====== Additions ======
2020

21+
* Lwt.Exception_filter for enabling/disabling system-exception catching (#964)
2122
* Lwt.reraise an exception raising function which preserves backtraces, recommended for use in Lwt.catch (#963)
2223
* Expose Lwt_io.delete_recursively for deleting a directory and its content recursively. (#984, Antonin Décimo)
2324

src/core/lwt.ml

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,20 @@ struct
710710
end
711711
open Basic_helpers
712712

713-
713+
(* Small helpers to avoid catching ocaml-runtime exceptions *)
714+
module Exception_filter = struct
715+
type t = exn -> bool
716+
let handle_all = fun _ -> true
717+
let handle_all_except_runtime = function
718+
| Out_of_memory -> false
719+
| Stack_overflow -> false
720+
| _ -> true
721+
let v =
722+
(* Default value: the legacy behaviour to avoid breaking programs *)
723+
ref handle_all
724+
let set f = v := f
725+
let run e = !v e
726+
end
714727

715728
module Sequence_associated_storage :
716729
sig
@@ -791,7 +804,7 @@ struct
791804
let result = f () in
792805
current_storage := saved_storage;
793806
result
794-
with exn ->
807+
with exn when Exception_filter.run exn ->
795808
current_storage := saved_storage;
796809
raise exn
797810
end
@@ -1129,7 +1142,8 @@ struct
11291142
be reject later, it is not the responsibility of this function to pass
11301143
the exception to [!async_exception_hook]. *)
11311144
try f v
1132-
with exn -> !async_exception_hook exn
1145+
with exn when Exception_filter.run exn ->
1146+
!async_exception_hook exn
11331147

11341148

11351149

@@ -1826,7 +1840,10 @@ struct
18261840
| Fulfilled v ->
18271841
current_storage := saved_storage;
18281842

1829-
let p' = try f v with exn -> fail exn in
1843+
let p' =
1844+
try f v with exn
1845+
when Exception_filter.run exn -> fail exn
1846+
in
18301847
let Internal p' = to_internal_promise p' in
18311848
(* Run the user's function [f]. *)
18321849

@@ -1889,7 +1906,10 @@ struct
18891906
| Fulfilled v ->
18901907
current_storage := saved_storage;
18911908

1892-
let p' = try f v with exn -> fail (add_loc exn) in
1909+
let p' =
1910+
try f v
1911+
with exn when Exception_filter.run exn ->
1912+
fail (add_loc exn) in
18931913
let Internal p' = to_internal_promise p' in
18941914

18951915
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
@@ -1943,7 +1963,10 @@ struct
19431963
| Fulfilled v ->
19441964
current_storage := saved_storage;
19451965

1946-
let p''_result = try Fulfilled (f v) with exn -> Rejected exn in
1966+
let p''_result =
1967+
try Fulfilled (f v) with exn
1968+
when Exception_filter.run exn -> Rejected exn
1969+
in
19471970

19481971
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
19491972
let p'' = underlying p'' in
@@ -1970,7 +1993,9 @@ struct
19701993
~run_immediately_and_ensure_tail_call:true
19711994
~callback:(fun () ->
19721995
to_public_promise
1973-
{state = try Fulfilled (f v) with exn -> Rejected exn})
1996+
{state =
1997+
try Fulfilled (f v)
1998+
with exn when Exception_filter.run exn -> Rejected exn})
19741999
~if_deferred:(fun () ->
19752000
let (p'', callback) =
19762001
create_result_promise_and_callback_if_deferred () in
@@ -1987,7 +2012,10 @@ struct
19872012
external reraise : exn -> 'a = "%reraise"
19882013

19892014
let catch f h =
1990-
let p = try f () with exn -> fail exn in
2015+
let p =
2016+
try f ()
2017+
with exn when Exception_filter.run exn -> fail exn
2018+
in
19912019
let Internal p = to_internal_promise p in
19922020
let p = underlying p in
19932021

@@ -2009,7 +2037,10 @@ struct
20092037
| Rejected exn ->
20102038
current_storage := saved_storage;
20112039

2012-
let p' = try h exn with exn -> fail exn in
2040+
let p' =
2041+
try h exn
2042+
with exn when Exception_filter.run exn -> fail exn
2043+
in
20132044
let Internal p' = to_internal_promise p' in
20142045

20152046
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
@@ -2042,7 +2073,10 @@ struct
20422073
p''
20432074

20442075
let backtrace_catch add_loc f h =
2045-
let p = try f () with exn -> fail exn in
2076+
let p =
2077+
try f ()
2078+
with exn when Exception_filter.run exn -> fail exn
2079+
in
20462080
let Internal p = to_internal_promise p in
20472081
let p = underlying p in
20482082

@@ -2064,7 +2098,11 @@ struct
20642098
| Rejected exn ->
20652099
current_storage := saved_storage;
20662100

2067-
let p' = try h exn with exn -> fail (add_loc exn) in
2101+
let p' =
2102+
try h exn
2103+
with exn when Exception_filter.run exn ->
2104+
fail (add_loc exn)
2105+
in
20682106
let Internal p' = to_internal_promise p' in
20692107

20702108
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
@@ -2097,7 +2135,10 @@ struct
20972135
p''
20982136

20992137
let try_bind f f' h =
2100-
let p = try f () with exn -> fail exn in
2138+
let p =
2139+
try f ()
2140+
with exn when Exception_filter.run exn -> fail exn
2141+
in
21012142
let Internal p = to_internal_promise p in
21022143
let p = underlying p in
21032144

@@ -2111,7 +2152,10 @@ struct
21112152
| Fulfilled v ->
21122153
current_storage := saved_storage;
21132154

2114-
let p' = try f' v with exn -> fail exn in
2155+
let p' =
2156+
try f' v
2157+
with exn when Exception_filter.run exn -> fail exn
2158+
in
21152159
let Internal p' = to_internal_promise p' in
21162160

21172161
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
@@ -2124,7 +2168,10 @@ struct
21242168
| Rejected exn ->
21252169
current_storage := saved_storage;
21262170

2127-
let p' = try h exn with exn -> fail exn in
2171+
let p' =
2172+
try h exn
2173+
with exn when Exception_filter.run exn -> fail exn
2174+
in
21282175
let Internal p' = to_internal_promise p' in
21292176

21302177
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
@@ -2163,7 +2210,10 @@ struct
21632210
p''
21642211

21652212
let backtrace_try_bind add_loc f f' h =
2166-
let p = try f () with exn -> fail exn in
2213+
let p =
2214+
try f ()
2215+
with exn when Exception_filter.run exn -> fail exn
2216+
in
21672217
let Internal p = to_internal_promise p in
21682218
let p = underlying p in
21692219

@@ -2177,7 +2227,11 @@ struct
21772227
| Fulfilled v ->
21782228
current_storage := saved_storage;
21792229

2180-
let p' = try f' v with exn -> fail (add_loc exn) in
2230+
let p' =
2231+
try f' v
2232+
with exn when Exception_filter.run exn ->
2233+
fail (add_loc exn)
2234+
in
21812235
let Internal p' = to_internal_promise p' in
21822236

21832237
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
@@ -2190,7 +2244,11 @@ struct
21902244
| Rejected exn ->
21912245
current_storage := saved_storage;
21922246

2193-
let p' = try h exn with exn -> fail (add_loc exn) in
2247+
let p' =
2248+
try h exn
2249+
with exn when Exception_filter.run exn ->
2250+
fail (add_loc exn)
2251+
in
21942252
let Internal p' = to_internal_promise p' in
21952253

21962254
let State_may_now_be_pending_proxy p'' = may_now_be_proxy p'' in
@@ -2441,7 +2499,10 @@ struct
24412499
external reraise : exn -> 'a = "%reraise"
24422500

24432501
let dont_wait f h =
2444-
let p = try f () with exn -> fail exn in
2502+
let p =
2503+
try f ()
2504+
with exn when Exception_filter.run exn -> fail exn
2505+
in
24452506
let Internal p = to_internal_promise p in
24462507

24472508
match (underlying p).state with
@@ -2461,7 +2522,10 @@ struct
24612522
add_implicitly_removed_callback p_callbacks callback
24622523

24632524
let async f =
2464-
let p = try f () with exn -> fail exn in
2525+
let p =
2526+
try f ()
2527+
with exn when Exception_filter.run exn -> fail exn
2528+
in
24652529
let Internal p = to_internal_promise p in
24662530

24672531
match (underlying p).state with
@@ -3062,37 +3126,40 @@ struct
30623126

30633127

30643128

3065-
let apply f x = try f x with exn -> fail exn
3129+
let apply f x =
3130+
try f x with exn when Exception_filter.run exn -> fail exn
30663131

3067-
let wrap f = try return (f ()) with exn -> fail exn
3132+
let wrap f =
3133+
try return (f ())
3134+
with exn when Exception_filter.run exn -> fail exn
30683135

30693136
let wrap1 f x1 =
30703137
try return (f x1)
3071-
with exn -> fail exn
3138+
with exn when Exception_filter.run exn -> fail exn
30723139

30733140
let wrap2 f x1 x2 =
30743141
try return (f x1 x2)
3075-
with exn -> fail exn
3142+
with exn when Exception_filter.run exn -> fail exn
30763143

30773144
let wrap3 f x1 x2 x3 =
30783145
try return (f x1 x2 x3)
3079-
with exn -> fail exn
3146+
with exn when Exception_filter.run exn -> fail exn
30803147

30813148
let wrap4 f x1 x2 x3 x4 =
30823149
try return (f x1 x2 x3 x4)
3083-
with exn -> fail exn
3150+
with exn when Exception_filter.run exn -> fail exn
30843151

30853152
let wrap5 f x1 x2 x3 x4 x5 =
30863153
try return (f x1 x2 x3 x4 x5)
3087-
with exn -> fail exn
3154+
with exn when Exception_filter.run exn -> fail exn
30883155

30893156
let wrap6 f x1 x2 x3 x4 x5 x6 =
30903157
try return (f x1 x2 x3 x4 x5 x6)
3091-
with exn -> fail exn
3158+
with exn when Exception_filter.run exn -> fail exn
30923159

30933160
let wrap7 f x1 x2 x3 x4 x5 x6 x7 =
30943161
try return (f x1 x2 x3 x4 x5 x6 x7)
3095-
with exn -> fail exn
3162+
with exn when Exception_filter.run exn -> fail exn
30963163

30973164

30983165

src/core/lwt.mli

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,49 @@ val ignore_result : _ t -> unit
19991999
resolved, completing any associated side effects along the way. In fact,
20002000
the function that does {e that} is ordinary {!Lwt.bind}. *)
20012001

2002+
(** {4 Runtime exception filters}
2003+
2004+
Depending on the kind of programs that you write, you may need to treat
2005+
exceptions thrown by the OCaml runtime (namely [Out_of_memory] and
2006+
[Stack_overflow]) differently than all the other exceptions. This is because
2007+
(a) these exceptions are not reproducible (in that they are thrown at
2008+
different points of your program depending on the machine that your program
2009+
runs on) and (b) recovering from these errors may be impossible.
2010+
2011+
The helpers below allow you to change the way that Lwt handles the two OCaml
2012+
runtime exceptions [Out_of_memory] and [Stack_overflow]. *)
2013+
2014+
module Exception_filter: sig
2015+
2016+
(** An [Exception_filter.t] is a value which indicates to Lwt what exceptions to
2017+
catch and what exceptions to let bubble up all the way out of the main loop
2018+
immediately. *)
2019+
type t
2020+
2021+
(** [handle_all] is the default filter. With it the all the exceptions
2022+
(including [Out_of_memory] and [Stack_overflow]) can be handled: caught
2023+
and transformed into rejected promises. *)
2024+
val handle_all : t
2025+
2026+
(** [handle_all_except_runtime] is a filter which lets the OCaml runtime
2027+
exceptions ([Out_of_memory] and [Stack_overflow]) go through all the Lwt
2028+
abstractions and bubble all the way out of the call to [Lwt_main.run].
2029+
2030+
Note that if you set this handler, then the runtime exceptions leave the
2031+
Lwt internal state inconsistent. For this reason, you will not be able to
2032+
call [Lwt_main.run] again after such an exception has escaped
2033+
[Lwt_main.run]. *)
2034+
val handle_all_except_runtime : t
2035+
2036+
(** [set] sets the given exception filter globally. You should call this
2037+
function at most once during the start of your program, before the
2038+
first call to [Lwt_main.run]. *)
2039+
val set : t -> unit
2040+
2041+
(**/**)
2042+
val run : exn -> bool
2043+
2044+
end
20022045

20032046

20042047
(**/**)

src/core/lwt_seq.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ let rec unfold f u () =
270270
match f u with
271271
| None -> return_nil
272272
| Some (x, u') -> Lwt.return (Cons (x, unfold f u'))
273-
| exception exc -> Lwt.fail exc
273+
| exception exc when Lwt.Exception_filter.run exc -> Lwt.fail exc
274274

275275
let rec unfold_lwt f u () =
276276
let* x = f u in
@@ -305,7 +305,7 @@ let rec of_seq seq () =
305305
| Seq.Nil -> return_nil
306306
| Seq.Cons (x, next) ->
307307
Lwt.return (Cons (x, (of_seq next)))
308-
| exception exn -> Lwt.fail exn
308+
| exception exn when Lwt.Exception_filter.run exn -> Lwt.fail exn
309309

310310
let rec of_seq_lwt (seq: 'a Lwt.t Seq.t): 'a t = fun () ->
311311
match seq () with
@@ -321,4 +321,4 @@ let of_seq_lwt (seq: 'a Lwt.t Seq.t): 'a t = fun () ->
321321
let+ x = x in
322322
let next = of_seq_lwt next in
323323
Cons (x, next)
324-
| exception exc -> Lwt.fail exc
324+
| exception exc when Lwt.Exception_filter.run exc -> Lwt.fail exc

src/react/lwt_react.ml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@ module E = struct
9595
let event, push = create () in
9696
let t =
9797
Lwt.pause () >>= fun () ->
98-
Lwt_stream.iter (fun v -> try push v with exn -> !Lwt.async_exception_hook exn) stream in
98+
Lwt_stream.iter
99+
(fun v ->
100+
try push v
101+
with exn when Lwt.Exception_filter.run exn ->
102+
!Lwt.async_exception_hook exn)
103+
stream in
99104
with_finaliser (cancel_thread t) event
100105

101106
let delay thread =

src/unix/lwt_io.ml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,12 @@ let make :
533533
mode = mode;
534534
offset = 0L;
535535
typ =
536-
Type_normal
537-
(perform_io, fun pos cmd -> try seek pos cmd with e -> Lwt.fail e);
536+
Type_normal (
537+
perform_io,
538+
fun pos cmd ->
539+
try seek pos cmd
540+
with e when Lwt.Exception_filter.run e -> Lwt.fail e
541+
);
538542
} and wrapper = {
539543
state = Idle;
540544
channel = ch;

0 commit comments

Comments
 (0)