Skip to content
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

Introduce disconnection state for channels #1114

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ryoqun
Copy link
Contributor

@ryoqun ryoqun commented May 22, 2024

(hopefully) much improved pr than #959 ....

Currently, crossbeam-channel completely overlaps the channel connected status with rust sender/receiver instance aliveness. While this is uncontroversial and covers most use cases, it prevents advanced channel management.

Namely:

While these seem to be unrelated to each other, the underlying problem is the lack of explicit channel disconnection state as described above.

Thereby, this adds the following new methods:

impl Sender {
    fn disconnect(&self) -> bool;
    fn connect(&self) -> bool;
}
impl Receiver {
    fn disconnect(&self) -> bool;
    fn connect(&self) -> bool;
}

Fortunately, the implementation is quite simple by decoupling the already-existing disconnected bit (MARK_BIT in tail) into the two: one for ::drop() as was before, one for new ::disconnect(), although this pr touches the heart of the impl unlike #959 (i.e. more intrusive....). Also, the almost all of existing code is already robust enough to introduce the 2 kinds of disconnected status. Come to think of it, {receivers, senders} should be able to cope with disconnected {senders, receivers} at any given moment of time due to drops.

As for performance hit, it should be negligible because there's no additional operation at all. just << 2 instead of << 1 in the performance sensitive code-path. Also, no space increse. There were design considerations.

pub fn disconnect(&self) -> bool {
match &self.flavor {
SenderFlavor::List(chan) => chan.disconnect(),
_ => todo!(),
Copy link
Contributor Author

@ryoqun ryoqun May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

obviously, i need to add support other flavors. this is true for other 3 todo!()s.

I'll do this once i get some confirmation of the intent of merging this pr without much change.

@ryoqun ryoqun marked this pull request as ready for review May 22, 2024 07:55

if tail & MARK_BIT == 0 {
if tail & TAIL_DISCONNECT_ANY == 0 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the use of TAIL_DISCONNECT_ANY, instead of TAIL_DISCONNECT_EXPLICIT is intentional and needs a comment...

@ryoqun
Copy link
Contributor Author

ryoqun commented May 22, 2024

@taiki-e learning from #1040 , this pr has finished impl for the list flavor, added tests, also documented fully. CI should pass. Could you review this in your free time? Thanks a lot.

Comment on lines -37 to -41
const SHIFT: usize = 1;
// Has two different purposes:
// * If set in head, indicates that the block is not the last one.
// * If set in tail, indicates that the channel is disconnected.
const MARK_BIT: usize = 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, let me know if this flag dedup should be done as a preparatory pr.

Comment on lines +1200 to +1201
/// `false` as well, if this method is called on channels other than bounded-capacity,
/// unbounded-capacity or zero-capacity channels.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not coded yet. but i hope this isn't controversial behavior... ;)

/// connected. Otherwise, this method does nothing and returns `false`. Also, this method does
/// nothing and returns `false` as well, if this method is called on channels other than
/// bounded-capacity, unbounded-capacity or zero-capacity channels.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, remove this empty line later....

@@ -628,13 +662,13 @@ impl<T> Channel<T> {
drop(Box::from_raw(block));
}
}
head &= !MARK_BIT;
head &= !HEAD_NOT_LAST;
self.head.index.store(head, Ordering::Release);
}

/// Returns `true` if the channel is disconnected.
pub(crate) fn is_disconnected(&self) -> bool {
Copy link
Contributor Author

@ryoqun ryoqun May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's a prime time to expose this to public api? (i know there were controversial in the past..)

///
/// Disconnected channels can be restored connected by calling [`connect`], as long as there
/// are at least a sender and a receiver for the channel.
///
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess i need to add some motivating example here for the justification of disconnect()...

/// Connected channels can be disconnected again by calling [`disconnect`].
///
/// [`disconnect`]: Self::disconnect
pub fn connect(&self) -> bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: rename to reconnect()?

@@ -580,3 +580,84 @@ fn channel_through_channel() {
})
.unwrap();
}

#[test]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better off writing a test for possible race condition, where a sending thread disconnect/reconnect/send(T) successively , and the receiver thread may/may not see disconnected, or actual message.

Comment on lines +606 to +607
// connect should fail after all receivers has gone
assert!(!s.connect());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also disconnect should fail after all receivers has gone even if the channel isn't disconnected yet.

///
/// Disconnected channels can be restored connected by calling [`connect`], as long as there
/// are at least a sender and a receiver for the channel.
///
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe mention about race condition among other disconnect()s and drop of the last instance of sender/receiver...

// * If set in head, indicates that the block is not the last one.
// * If set in tail, indicates that the channel is disconnected.
const MARK_BIT: usize = 1;
const SHIFT: usize = 2;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note to self): HEAD_SHIFT = 1 and TAIL_SHIFT = 2?

@ryoqun
Copy link
Contributor Author

ryoqun commented May 30, 2024

@taiki-e friendly ping. although i self-commented some after opening this pr, needing further work, I still think this pr is in some good standing for initial round of code-review. please let me know if i can do anything to moving forward this pr.

@ryoqun
Copy link
Contributor Author

ryoqun commented Jun 15, 2024

@taiki-e hey, another ping from me. just want to let you know I'm very eager to work on this pr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant