-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
In-place optimisation in IntoIterator can lead to memory leak #101628
Comments
Nice find! Technically this is a regression. @rustbot label regression-from-stable-to-stable, T-libs. It works properly until 1.47, it double-frees |
I haven’t looked at the code yet myself to figure out what’s going on. In the meantime, I think that bisecting what changes were relevant for the regression 1.47 to 1.48 on one hand, and the point between 1.51 and 1.52 that appeared to have fixed the double-free (but introduced or kept (hard to tell with the abort) the memory leak) could be interesting. @rustbot label E-needs-bisection. ^^ |
Doing the bisection. First one is
second one is
@rustbot label -E-needs-bisection |
I’m not sure if I’m reading the PR description of #83629 correctly as in that this leak might be intentional? (If it is intentional, I haven’t understood the rationale yet though…)
I mean… who knows, maybe this is talking about something entirely different than what this issue is about ^^ |
Looking at the code, this leak seems relatively easy to avoid. Assuming that dropping the Also any idea why and how |
The relevant piece of code seems to be this one: rust/library/alloc/src/vec/in_place_collect.rs Lines 201 to 203 in cedd26b
Where rust/library/alloc/src/vec/into_iter.rs Lines 110 to 124 in cedd26b
So what happens is:
The problem is that a panic can occur while dropping the remaining elements, and in that case we didn't yet create the final The simpliest fix would be to create the
Dropping the old elements is done with |
Yeah, to fix the double-drop in #83618, this was the simplest solution that didn't require any extra parts. And if you're panicing in drop then leaks are allowed behavior. Avoiding them is more a QoI thing. |
A bigger concern imo is... well... panic-safety. If the |
The idea was to create it right before calling |
That could work. At least I'm not seeing anything that would go wrong with that given current implementations. The question is whether it's worth it adding yet another unsafe wrinkle to this goldberg machine. |
This comment was marked as outdated.
This comment was marked as outdated.
I think the issue will be type confusion. Which is what I initially looked for. If dropping those remaining Old elements panics, it would drop Olds as News. I think a simpel drop guard before forget_ should do the trick.
… On 10. Sep 2022, at 11:36, the8472 ***@***.***> wrote:
That could work. At least I'm not seeing anything that would go wrong with that given current implementations. The question is whether it's worth it adding yet another unsafe wrinkle to this goldberg machine.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.
|
I tried this code:
I expected to see this happen:
The drop implementation for
New(11)
andNew(22)
is called.Instead, this happened:
Everything that was mapped and the allocation was leaked.
Meta
rustc --version --verbose
:I discovered this issue when I learned that
IntoIterator
re-uses aVec
allocation if possible. Which prompted me to read the code. Running the example withcargo miri run
also reports the memory leak. And changingNew(i32)
toNew(i64)
no longer displays the memory leak. This is what I think happens:While dropping
[3]
theptr::drop_in_place(remaining)
call inforget_allocation_drop_remaining
panics.Then
[4]
is being dropped, but honestly I don't really understand why. My understanding is thatptr::drop_in_place(remaining)
should unwind. If anyone can explain this, please do so.Then as part of unwinding
collect_in_place
IntoIter
is dropped, but it has its fields set to an empty buffer of len 0, so it does nothing. Note, I suspect more optimal code gen could be achieved by usingmem::forget
to avoid generating code for theIntoIter
drop that will never do anything after we cleared the fields.The two created
New
will never get dropped, thus leading to the memory leak. Plus the mainVec
allocation, previously owned byIntoIterator
as stored indst_buf
will be leaked too. Because it was cleared inforget_allocation_drop_remaining
and assumed to be taken over bylet vec = unsafe { Vec::from_raw_parts(dst_buf, len, cap) };
but we never reached that point.Proposed solution, a drop guard in
collect_in_place
that guards theforget_allocation_drop_remaining
call and would drop the head in this case0..2
and the backing allocation ifforget_allocation_drop_remaining
panics.The text was updated successfully, but these errors were encountered: