Skip to content

Commit 61d34af

Browse files
Don't catch runtime exceptions, even in Lwt_main.run
1 parent 2837e08 commit 61d34af

File tree

11 files changed

+229
-118
lines changed

11 files changed

+229
-118
lines changed

src/unix/lwt_main.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ let run p =
107107
| result ->
108108
finished ();
109109
result
110-
| exception exn ->
110+
| exception exn when Lwt.is_not_ocaml_runtime_exception exn ->
111111
finished ();
112112
raise exn
113113

src/unix/lwt_main.mli

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ let () = Lwt_main.run (main ())
3939
[Lwt_main.run] will raise [Failure]. This should be considered a logic
4040
error (i.e., code making such a call is inherently broken).
4141
42+
In addition, note that Lwt does not attempt to catch exceptions thrown by
43+
the OCaml runtime. Specifically, Lwt lets [Out_of_memory] and
44+
[Stack_overflow] exceptions traverse all of its functions and bubble up to
45+
the caller of [Lwt_main.run]. Moreover because these exceptions are left
46+
to traverse the call stack, they leave the internal data-structures in an
47+
inconsistent state. For this reason, calling [Lwt_main.run] again after
48+
such an exception will raise [Failure].
49+
4250
It is not safe to call [Lwt_main.run] in a function registered with
4351
[Stdlib.at_exit], use {!Lwt_main.at_exit} instead. *)
4452

test/unix/dune

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
(library
22
(name tester)
33
(libraries lwt lwttester)
4-
(modules (:standard \ main dummy) ))
4+
(modules
5+
(:standard
6+
\
7+
main
8+
dummy
9+
ocaml_runtime_exc_1
10+
ocaml_runtime_exc_2
11+
ocaml_runtime_exc_3
12+
ocaml_runtime_exc_4
13+
ocaml_runtime_exc_5
14+
ocaml_runtime_exc_6)))
515

616
(executable
717
(name dummy)
@@ -13,6 +23,36 @@
1323
(libraries lwttester tester)
1424
(modules main))
1525

26+
(executable
27+
(name ocaml_runtime_exc_1)
28+
(libraries lwt lwt.unix)
29+
(modules ocaml_runtime_exc_1))
30+
31+
(executable
32+
(name ocaml_runtime_exc_2)
33+
(libraries lwt lwt.unix)
34+
(modules ocaml_runtime_exc_2))
35+
36+
(executable
37+
(name ocaml_runtime_exc_3)
38+
(libraries lwt lwt.unix)
39+
(modules ocaml_runtime_exc_3))
40+
41+
(executable
42+
(name ocaml_runtime_exc_4)
43+
(libraries lwt lwt.unix)
44+
(modules ocaml_runtime_exc_4))
45+
46+
(executable
47+
(name ocaml_runtime_exc_5)
48+
(libraries lwt lwt.unix)
49+
(modules ocaml_runtime_exc_5))
50+
51+
(executable
52+
(name ocaml_runtime_exc_6)
53+
(libraries lwt lwt.unix)
54+
(modules ocaml_runtime_exc_6))
55+
1656
(alias
1757
(name runtest)
1858
(package lwt)

test/unix/main.ml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,3 @@ let () =
1616
Test_lwt_bytes.suite;
1717
Test_sleep_and_timeout.suite;
1818
]
19-
20-
let () =
21-
(* tests that cannot be run inside of the test framework because they manage
22-
their own Lwt_main.run *)
23-
Test_run_and_runtime_exceptions.test ();
24-
()

test/unix/ocaml_runtime_exc_1.ml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
(* This file is part of Lwt, released under the MIT license. See LICENSE.md for
2+
details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *)
3+
4+
(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a
5+
different way than other exceptions and they leave the Lwt main loop in an
6+
inconsistent state where it cannot be restarted. Indeed, attempting to call
7+
[Lwt_main.run] again after it has crashed with a runtime exception causes a
8+
"Nested calls to Lwt_main.run are not allowed" error.
9+
10+
For this reason, we run this test as its own executable rather than as part
11+
of a larger suite. *)
12+
13+
open Lwt.Syntax
14+
15+
let test () =
16+
try
17+
let () = Lwt_main.run (
18+
let* () = Lwt.pause () in
19+
if true then raise Out_of_memory else Lwt.return_unit
20+
) in
21+
Printf.eprintf "Test run+raise failure\n";
22+
Stdlib.exit 1
23+
with
24+
| Out_of_memory -> ()
25+
26+
let () = test ()

test/unix/ocaml_runtime_exc_2.ml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
(* This file is part of Lwt, released under the MIT license. See LICENSE.md for
2+
details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *)
3+
4+
(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a
5+
different way than other exceptions and they leave the Lwt main loop in an
6+
inconsistent state where it cannot be restarted. Indeed, attempting to call
7+
[Lwt_main.run] again after it has crashed with a runtime exception causes a
8+
"Nested calls to Lwt_main.run are not allowed" error.
9+
10+
For this reason, we run this test as its own executable rather than as part
11+
of a larger suite. *)
12+
13+
open Lwt.Syntax
14+
15+
let test () =
16+
try
17+
let () = Lwt_main.run (
18+
let* () = Lwt_unix.sleep 0.001 in
19+
if true then raise Out_of_memory else Lwt.return_unit
20+
) in
21+
Printf.eprintf "Test run+raise failure\n";
22+
Stdlib.exit 1
23+
with
24+
| Out_of_memory -> ()
25+
26+
let () = test ()
27+

test/unix/ocaml_runtime_exc_3.ml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
(* This file is part of Lwt, released under the MIT license. See LICENSE.md for
2+
details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *)
3+
4+
(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a
5+
different way than other exceptions and they leave the Lwt main loop in an
6+
inconsistent state where it cannot be restarted. Indeed, attempting to call
7+
[Lwt_main.run] again after it has crashed with a runtime exception causes a
8+
"Nested calls to Lwt_main.run are not allowed" error.
9+
10+
For this reason, we run this test as its own executable rather than as part
11+
of a larger suite. *)
12+
13+
open Lwt.Syntax
14+
15+
let test () =
16+
try
17+
let () = Lwt_main.run (
18+
let* () = Lwt.pause () in
19+
Lwt.choose [
20+
(let* () = Lwt.pause () in raise Out_of_memory);
21+
Lwt_unix.sleep 2.;
22+
]
23+
) in
24+
Printf.eprintf "Test run+raise failure\n";
25+
Stdlib.exit 1
26+
with
27+
| Out_of_memory -> ()
28+
29+
let () = test ()
30+
31+

test/unix/ocaml_runtime_exc_4.ml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
(* This file is part of Lwt, released under the MIT license. See LICENSE.md for
2+
details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *)
3+
4+
(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a
5+
different way than other exceptions and they leave the Lwt main loop in an
6+
inconsistent state where it cannot be restarted. Indeed, attempting to call
7+
[Lwt_main.run] again after it has crashed with a runtime exception causes a
8+
"Nested calls to Lwt_main.run are not allowed" error.
9+
10+
For this reason, we run this test as its own executable rather than as part
11+
of a larger suite. *)
12+
13+
open Lwt.Syntax
14+
15+
let test () =
16+
try
17+
let () = Lwt_main.run (
18+
let* () = Lwt.pause () in
19+
Lwt.catch
20+
(fun () -> raise Out_of_memory)
21+
(fun _ -> Lwt.return_unit)
22+
) in
23+
Printf.eprintf "Test run+raise failure\n";
24+
Stdlib.exit 1
25+
with
26+
| Out_of_memory -> ()
27+
28+
let () = test ()
29+
30+

test/unix/ocaml_runtime_exc_5.ml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
(* This file is part of Lwt, released under the MIT license. See LICENSE.md for
2+
details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *)
3+
4+
(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a
5+
different way than other exceptions and they leave the Lwt main loop in an
6+
inconsistent state where it cannot be restarted. Indeed, attempting to call
7+
[Lwt_main.run] again after it has crashed with a runtime exception causes a
8+
"Nested calls to Lwt_main.run are not allowed" error.
9+
10+
For this reason, we run this test as its own executable rather than as part
11+
of a larger suite. *)
12+
13+
open Lwt.Syntax
14+
15+
let test () =
16+
try
17+
let () = Lwt_main.run (
18+
let* () = Lwt.pause () in
19+
let _ =
20+
Lwt.async
21+
(fun () -> let* () = Lwt.pause () in raise Out_of_memory)
22+
in
23+
Lwt_unix.sleep 0.5
24+
) in
25+
Printf.eprintf "Test run+raise failure\n";
26+
Stdlib.exit 1
27+
with
28+
| Out_of_memory -> ()
29+
30+
let () = test ()
31+
32+

test/unix/ocaml_runtime_exc_6.ml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
(* This file is part of Lwt, released under the MIT license. See LICENSE.md for
2+
details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *)
3+
4+
(* OCaml runtime exceptions (out-of-memory, stack-overflow) are fatal in a
5+
different way than other exceptions and they leave the Lwt main loop in an
6+
inconsistent state where it cannot be restarted. Indeed, attempting to call
7+
[Lwt_main.run] again after it has crashed with a runtime exception causes a
8+
"Nested calls to Lwt_main.run are not allowed" error.
9+
10+
For this reason, we run this test as its own executable rather than as part
11+
of a larger suite. *)
12+
13+
open Lwt.Syntax
14+
15+
let test () =
16+
try
17+
let () = Lwt_main.run (
18+
let* () = Lwt.pause () in
19+
let _ =
20+
Lwt.dont_wait
21+
(fun () -> let* () = Lwt.pause () in raise Out_of_memory)
22+
(fun _ -> ())
23+
in
24+
Lwt_unix.sleep 0.5
25+
) in
26+
Printf.eprintf "Test run+raise failure\n";
27+
Stdlib.exit 1
28+
with
29+
| Out_of_memory -> ()
30+
31+
let () = test ()
32+
33+

0 commit comments

Comments
 (0)