-
Notifications
You must be signed in to change notification settings - Fork 177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Repititve bind to the same thread might cause memory leak or stack overflow. #432
Comments
@jorisgio Can you give a complete, self-contained example? I'm not 100% following the description + example. Where does "protected" fit in? I'm not sure dead immutable waiters are actually accumulating as described. |
Ok, looking at this again, the TL;DR is it can be worked around by wrapping Lwt.pick [
Lwt.protected stop_condition >>= cleanup >>= fun () -> Lwt.fail Timeout;
get next
]
In detail, this is, most generally, a specific instance of this pattern: let () =
let p, _ = Lwt.wait () in
for i = 1 to 1_000_000 do
Lwt.bind p (fun () -> ())
|> ignore
done This loop attaches one callback to We have no general way to solve this. We can't drop callbacks from This is also an instance of the more specific pattern: let () =
let p, _ = Lwt.wait () in
for i = 1 to 1_000_000 do
let p' = Lwt.bind p (fun () -> Lwt.return ()) in
Lwt.cancel p'
done @jorisgio, I think you wanted cancelation of However, in Lwt right now, As a result, in this case, However, if you wrap let () =
let p, _ = Lwt.wait () in
for i = 1 to 1_000_000 do
let p'' = Lwt.protected p in
let p' = Lwt.bind p'' (fun () -> Lwt.return ()) in
Lwt.cancel p'
done ...you will be creating a temporary cancelable promise I think I got confused by your usage of protected, by which you meant non-cancelable, because actually it is |
I think I am seeing a similar issue with streams. Following is what I am looking: let () =
let stream,pusher = Lwt_stream.create () in
let s =
stream
|> Lwt_stream.iter_p
(fun i ->
Printf.printf "Done %d" i
|> Lwt.return)
in
let limit = 120_000 in
for i = 1 to limit do
if i >= limit
then pusher None
else pusher (Some i)
done;
s
|> Lwt_main.run That gives stack overflow on exit. I tried a few things to use the Any comments on what is a workaround ? |
Thanks for reporting, I will take a look in detail tomorrow morning (~15 hours :/). |
I tried looking into the implementation. From what I could tell, it arises from the match node.data with
| Some x ->
consume s node;
let res = f x in
let rest = iter_p_bb_rec node.next f s in
res >>= fun () -> rest It works for me then, although I am not entirely sure about the semantic difference between the join and bind here. I think it has to do with the cancellation of some promises |
Would you be willing to prepare a PR with this change? I think the issue is not with cancelation, but that the execution order in your example creates a "tower" of
I'm still a bit worried that there may be an execution order that is still pathological, or we might introduce a new pathological execution order with this change. But if that happens, we can further change the code to count how many calls to |
Sure, I can definitely create a PR. Anything specific you looking for ? Or I can just change iter_p implementation to the one I put in there |
Yep, if you just PR your implementation, I'll be happy to merge it :) |
Thanks! |
We've been struggling with a nasty Stackoverflow triggered by cleanup function recursively walking a crazy large list of waiters. In theory this should not happen, thanks to the "silent remove" limit, and the removed waiter tree to be cleanup should not be deeper than 42.
It turns out the issue is caused by
bind
.bind
creates immutable waiters and they can accumulate until a stackoverlow is triggered, if you bind a huge number of thread to a single long live thread.Here is a example :
This code looks harmless, but at every iteration it stacks a waiter to stop_condition, than Cancel it. Assuming stop_condition is protected, dead Immutable waiters will accumulate onwait leaking memory and ultimately crash in cleanup.
I believe cleanup can be made tail recursive but that does not solve the leak.
The text was updated successfully, but these errors were encountered: