It is rather easy to expect Lwt.protected to work like choose or pick (which remove listeners) and keep adding listeners accidentally to an existing thread when wrapping it with Lwt.protected. This could happen if the user does for example something resembling match_lwt Lwt.pick [Lwt.protected t; Lwt_unix.sleep t0 >> raise_lwt Timeout] (where the t thread is not canceled on timeout, but the timeout thread is on completion).
Iow., is there a reason for the following not to run in constant mem?
let () =
Lwt_main.run begin
let t, _ = Lwt.wait () in
Lwt.on_cancel t (fun () -> print_endline "CANCEL 1");
let rec loop () =
let t' = Lwt.bind (Lwt.protected t) @@ fun () ->
Lwt.return @@ print_endline "X"
in
Lwt.cancel t';
loop ()
in
loop ()
end