-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
feat(oneshot channel): ensure msg won't be dropped on sender side when send returns ok #6558
feat(oneshot channel): ensure msg won't be dropped on sender side when send returns ok #6558
Conversation
Can you please add a loom test that catches the issue you have mentioned? It would need to go somewhere in this folder. Preferably the test should be as simple as possible. |
I have added a loom test. The test logic is as simple as followed.
We can assert that msg can only be dropped in thread 2, because in thread 1, |
tokio/src/sync/tests/loom_oneshot.rs
Outdated
// we call `std::mem::forget(msg)`, so that | ||
// `drop` is not expected to be called in the | ||
// tx thread. | ||
assert!(*is_rx.borrow()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the newly added code in oneshot.rs
is removed, we will hit this assertion.
I suspect this could be solved by having |
It's feasible. I have updated the code. Thanks for the suggestion!. Now no dependency will be added in this PR. @Darksonn |
3701d4e
to
76f4b49
Compare
tokio/src/sync/tests/loom_oneshot.rs
Outdated
let rx_thread_join_handle = thread::spawn(move || { | ||
drop(rx); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just call drop(rx)
on the main thread. Spawning threads is very expensive in loom tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a try.
If I simply use drop(rx)
in the main thread, when I revert the changes in oneshot.rs
, the assertion cannot be hit, and if I do it in a spawned thread, the assertion can be hit.
I doubt that only drop(rx)
in a separate thread can cover the case in loom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you hit the assertion with LOOM_MAX_PREEMPTIONS=2
? That's what we run with in CI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, when I set it to 2, the assertion can be hit.
Should the CONTRIBUTING.md
be updated? I ran the loom test following the guide in it.
Line 195 in df77063
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It takes a lot longer to run with the option set to 2, and most bugs can be caught with the value set to 1. It may be better to have your test use the loom test builder to explicitly set the max preemptions to 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Have updated the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
Motivation
Related: risingwavelabs/risingwave#6617.
When we use
oneshot::Sender
, it is intuitive to assume that whentx.send(msg)
returns withOk(())
, themsg
will have been added to the buffer and either will be consumed by therx.await
, or dropped along with droppingrx
, but in either way,msg
should not be dropped on thetx
side.However, in the current implementation, the assumption does not hold. It might happen that, even though
tx.send(msg)
returns withOk(())
, themsg
is still dropped on the sender side.The current struct of
Sender
isIn the current implementation, in
tx.send(msg)
,msg
will be added toinner
. Beforemsg
gets consumed byrx
, the structInner
holds the ownership ofmsg
.Inner
is protected byArc
with reference counting to avoid memory leak. When the reference counting ofArc<Inner>
decreases to 0,msg
gets dropped along withInner
. However, in the following execution flow,msg
will be dropped on sender side whentx.send(msg)
returnsOk(())
.Solution
The problem happens due to the implicit drop of variable
inner
. We are not aware of whether or not droppinginner
will decreases theArc
ref count to 0 and further drop theInner
.Therefore, in this PR, we can leverage the Arc::into_inner to explicitly consume the variable
inner
so that we can atomically decrease the ref count and check whether theInner
will be further dropped. IfInner
is going to be dropped, we can then take themsg
out and return withErr(msg)
if the msg has not been consumed byrx
yet.