Actually I’ve realized that using Lwt.pick
instead of Lwt.choose
on the main thread does work as expected, the program is terminated immediately, and all the finalizers get executed:
open Lwt.Infix
let () =
let w,u = Lwt.wait () in
Lwt_unix.on_signal
Sys.sigint
(fun _ ->
Lwt.wakeup_exn u (Failure "Caught SIGINT"))
|> ignore;
let t () =
Lwt.finalize
(fun () ->
Lwt_io.eprintl "block 1" >>= fun () ->
Lwt_unix.sleep 2. >>= fun () ->
Lwt.finalize
(fun () ->
Lwt_io.eprintl "block 2" >>= fun () ->
Lwt_unix.sleep 2. >>= fun () ->
raise Pervasives.Exit)
(fun () -> Lwt_io.eprintl "cleanup 2")
>>= fun () ->
Lwt_unix.sleep 2.
)
(fun () -> Lwt_io.eprintl "cleanup 1")
in
let t () = Lwt.pick [t (); w] in
Lwt_main.run (t ())
Output:
$ ./prog
block 1
^Ccleanup 1
Fatal error: exception Failure("Caught SIGINT")
$ ./prog
block 1
block 2
^Ccleanup 2
cleanup 1
Fatal error: exception Failure("Caught SIGINT")
$ ./prog
block 1
block 2
cleanup 2
cleanup 1
Fatal error: exception Pervasives.Exit
$
It also works when I combine threads with Lwt.join
:
open Lwt.Infix
let () =
let w,u = Lwt.wait () in
Lwt_unix.on_signal
Sys.sigint
(fun _ ->
Lwt.wakeup_exn u (Failure "Caught SIGINT"))
|> ignore;
let t () =
Lwt.finalize
(fun () ->
Lwt_io.eprintl "block 1" >>= fun () ->
Lwt_unix.sleep 2. >>= fun () ->
let t2 () =
Lwt.finalize
(fun () ->
Lwt_io.eprintl "block 2" >>= fun () ->
Lwt_unix.sleep 2.)
(fun () -> Lwt_io.eprintl "cleanup 2")
in
let t3 () =
Lwt.finalize
(fun () ->
Lwt_io.eprintl "block 3" >>= fun () ->
Lwt_unix.sleep 2.)
(fun () -> Lwt_io.eprintl "cleanup 3")
in
Lwt.join [t2 (); t3 ()] >>= fun () ->
Lwt_unix.sleep 2.
)
(fun () -> Lwt_io.eprintl "cleanup 1")
in
let t () = Lwt.pick [t (); w] in
Lwt_main.run (t ())
Output:
$ ./prog
block 1
^Ccleanup 1
Fatal error: exception Failure("Caught SIGINT")
$ ./prog
block 1
block 3
block 2
cleanup 3
cleanup 2
^Ccleanup 1
Fatal error: exception Failure("Caught SIGINT")
$ ./prog
block 1
block 3
block 2
^Ccleanup 3
cleanup 2
cleanup 1
Fatal error: exception Failure("Caught SIGINT")
$
I also noticed that when a background thread isn’t connected to the main one (e.g. uses let _ = ...
), its finalizer do not get executed at all when I use this method:
open Lwt.Infix
let () =
let w,u = Lwt.wait () in
Lwt_unix.on_signal
Sys.sigint
(fun _ ->
Lwt.wakeup_exn u (Failure "Caught SIGINT"))
|> ignore;
let t () =
Lwt.finalize
(fun () ->
Lwt_io.eprintl "block 1" >>= fun () ->
Lwt_unix.sleep 2. >>= fun () ->
let _ =
Lwt.finalize
(fun () ->
Lwt_io.eprintl "block 2" >>= fun () ->
Lwt_unix.sleep 2.)
(fun () -> Lwt_io.eprintl "cleanup 2")
in
Lwt_unix.sleep 2.
)
(fun () -> Lwt_io.eprintl "cleanup 1")
in
let t () = Lwt.pick [t (); w] in
Lwt_main.run (t ())
Output:
# all finalizers are executed after normal termination:
$ ./prog
block 1
block 2
cleanup 2
cleanup 1
$ ./prog
block 1
^Ccleanup 1
Fatal error: exception Failure("Caught SIGINT")
$ ./prog
block 1
block 2
^Ccleanup 1
Fatal error: exception Failure("Caught SIGINT")
$
When I tried to use this method for a more complicated program with a recursive Lwt thread, it did not work, not even when I join
ed all the background threads together after the recursive thread , so I still had to go for the ugly workaround that I described above.