Tracking issue for channel selection #27800

Open
alexcrichton opened this Issue Aug 13, 2015 · 28 comments

Projects

None yet
@alexcrichton
Member

This is a tracking issue for the unstable mpsc_select feature in the standard library. Known points:

  • The API is unsafe, and should probably never be stabilized.
  • Servo apparently makes very heavy use of this API, so it can't be removed without replacement currently.
  • The correctness of the implementation is somewhat dubious, it's incredibly complicated.
  • The macro-based approach for selection is dubious.
  • This is not a scalable implementation of selection (e.g. "poll everything each time wait() is called")
  • This drags down some other channel-related code with a few mutexes in otherwise lock-free code.
@jdm
Contributor
jdm commented Aug 13, 2015

cc me

@steveklabnik
Contributor

Is it worth investigating @BurntSushi 's https://github.com/BurntSushi/chan ?

@steveklabnik
Contributor

I also think @carllerche was supporting the idea of removing channels from the stdlib

@alexcrichton
Member

Note that I'm not advocating removing channels from the standard library (plus they're stable). Removing selection is also probably not an option as it's so closely tied to the implementation details of channels.

One of the major points of complexity of the current implementation is that it's all basically 99% lock free (giving us a nice performance boost in theory). I believe @BurntSushi's implementation (please correct me if I'm wrong!) is largely based on mutexes and condition variables.

@BurntSushi
Member

@alexcrichton Correct. I doubt very much that chan will ever be lock free because I'm not convinced the semantics I want can be done with them. It's also much easier to implement. :-)

@nagisa
Contributor
nagisa commented Aug 13, 2015

I personally prefer the epoll-like design as implemented in comm.

@carllerche
Member

Streams in Eventual are lock free :) There is also stream selection, which is lock free (and definitely is a pain to implement).

Re: removing channels from stdlib, as @alexcrichton points out, they are stable. I would not worry about adding select to them. They are good as a starting point. If somebody requires more complex concurrency semantics, they can use a lib. I'm not sure why @alexcrichton thinks that removing select from std is hard though...

@Ms2ger Ms2ger referenced this issue in servo/servo Aug 16, 2015
Open

Review feature gates. #5286

49 of 78 tasks complete
@tripped
tripped commented Aug 20, 2015

Just hopping on to mention #12902 as one of the current weaknesses of the macro approach to select!.

@softprops

Fwiw, channel selection with golang channels is part of what makes golang concurrency story so alluring to newcomers to go. For a newcomer to a safe concurrent language like rust, needing to mull over which 3rd party concurrency crate is popular this week to pick for actually writing concurrent code feels onerous. Having primatives like channels and channel selection in the std lib make rust more attractive for concurrent applications than alternatives like go and make crates a little more interoperable than resolving conflicting implementation issues betwwen 3rd party options.

@BurntSushi
Member

Fwiw, channel selection with golang channels is part of what makes golang concurrency story so alluring to newcomers to go.

@softprops This doesn't address your primary concerns, but FYI, chan_select! in the chan crate was built to specifically match the semantics of Go's select.

@jonhoo
Contributor
jonhoo commented May 1, 2016

Correct me if I'm wrong, but doesn't the macro approach also have the drawback that selecting over a dynamic set of channels isn't easily expressible?

@szagi3891

Best to get rid of the macro select.
I made a working prototype that shows that it is possible:
https://github.com/szagi3891/channels_async-rs/blob/master/examples/select.rs#L76
Without any section unsafe.

@canndrew
Contributor
canndrew commented Jun 2, 2016

Here's a wacky idea: add support for generically sized tuples and anonymous enums. Then it will be possible to select across an arbitrary number of channels of different types without macros:

let ch0: Receiver<i32> = ...;
let ch1: Receiver<String> = ...;
match select((ch0, ch1)) {
    (x|!) => println!("Got an i32: {}", x),
    (!|y) => println!("Got a String: {}", y),
}
@BurntSushi
Member

Is there anyone still using this API? I don't see any obvious path to stabilization. It would be nice to just remove it.

@BurntSushi BurntSushi referenced this issue in BurntSushi/chan Nov 2, 2016
Open

[feature request] re-expose try_recv() #12

@alexcrichton
Member

@jdm is this still being used in Servo?

@SimonSapin
Contributor

grep finds 4 instances of select! in servo master.

@mattgreen
mattgreen commented Nov 4, 2016 edited

I'd like to remove this from watchexec, but I'm unsure what to move to. One of my crates already streams data to me via a channel, so I hopped onto that bandwagon.

I'm usually watching for either control-c or file system inputs at all times, since I have cleanup to do when the user hits control-c. select! might be unstable but I can at least accomplish this.

@softprops

I've had only good experiences with https://github.com/BurntSushi/chan

@szagi3891
szagi3891 commented Nov 4, 2016 edited

@mattgreen :
I think you should be able to replace this selecta! macoro of one channel.

This channel type will be :

enum Message {
    CtrlC,
    NormalMessage(....),
}

The main thread would send in theright time message Message::CtrlC.
The remainder of the program would send messages Message::NormalMessage

It makes sense ?

@antrik
Contributor
antrik commented Nov 12, 2016

Servo's ipc-channel library ( https://github.com/servo/ipc-channel ) also uses mpsc_select (for its inprocess back-end) -- which is a bit of a pain, since platforms relying on this back-end can't use ipc-channel with stable Rust. (See servo/ipc-channel#118 )

It should be noted though that this back-end is not a real implementation, but rather just a partial workaround for platforms that don't have a "real" inter-process implementation yet. While it was originally created for Windows, most likely Windows will be getting a proper implementation soonish. ( servo/ipc-channel#108 )

Right now the inprocess back-end is also being used for Android -- though I'm not sure why really...

So while this back-end might continue to be useful during bring-up of new platforms (depending on whether Servo keeps the option of running in a single process), it's not exactly a first-class citizen I'd say.

@antrik
Contributor
antrik commented Nov 12, 2016

I'd like to point out that https://github.com/BurntSushi/chan -- which is being touted as a replacement for mpsc here -- doesn't really seem like a valid alternative to me: unlike mpsc, it has clonable and sharable receivers.

This might seem convenient for some use cases; but I'm not convinced it's a real win over just wrapping the receiver in a Mutex<> or Arc<Mutex<>> yourself when you know it's save and necessary for your application. (As mutexes are safe and easy to use in Rust -- unlike in Go -- I don't see any motivation for channels to double as a locking substitute...)

More importantly though, for the vast majority of use cases, sharing the receiver is actually not desirable: rather, it often indicates bugs -- and thus not rejecting this by default is indeed more error prone!

(Not even considering implementation overhead for something that's not needed at all in most cases...)

@BurntSushi
Member

@antrik Please see the README for chan. Notably:

The purpose of this crate is to provide a safe concurrent abstraction for communicating sequential processes. Performance takes a second seat to semantics and ergonomics. If you're looking for high performance synchronization, chan is probably not the right fit.

There are a number of reasons why it doesn't necessarily replace std::sync::mpsc. Nevertheless, if you want to write in a CSP style in Rust, then I do think it's your only option on stable Rust because it gives you chan_select!. I suspect that's why people are bringing it up (which seems reasonable to me).

@antrik
Contributor
antrik commented Nov 12, 2016

@BurntSushi as long as there is a general understanding that chan is a special-purpose library rather than a general replacement, that's perfectly fine -- I'm just concerned that there seems to be an air here of "we don't need to fix mpsc since there is chan", which I find rather disconcerting...

@antrik
Contributor
antrik commented Nov 12, 2016

To be perfectly clear, my point is that I believe there is a genuine need for mpsc to have a stable select; or failing that, at the very least for some other library that can indeed serve as a real drop-in replacement. (And is prominently featured as such -- I for one still think that the prevalent "go look around for some random crate that seems like it might fit this very basic need" mindset is seriously hurting the Rust ecosystem...)

@BurntSushi
Member
BurntSushi commented Nov 12, 2016 edited

I don't think anyone's really contesting that though? I think these are the facts on the ground as I know them:

  • mpsc_select hasn't seen any attention towards stabilizing it. I don't think there's even a viable proposal in existence at all.
  • It seems the only thing keeping mpsc_select around is Servo.
  • Some have found success by switching to chan. Heck, the fact that mpsc_select was unstable was one of the reasons why I wrote chan in the first place!
@p-kraszewski

As a Rust newcomer from Erlang/Elixir world - I can live with MPSC.

My question is - do you have any comments on @szagi3891 approach for a static list of channels? Instead of selecting on let's say 2 channels, just .receive on one, carrying enum that is matched and dispatched later? Any do-s, don't-s, don't care-s?

@barr1969
barr1969 commented Jan 5, 2017 edited

Select on Dynamic Lists of Channels

As someone whose been using CSP for decades (though I've not yet used Rust), I feel compelled to submit a plea for the ability to select across dynamic lists of channels. This is such a useful thing to be able to do. It's particularly useful in large systems that have to deal with failure. That sounds like I'm effectively supporting @canndrew's suggestion. Lists that are fixed at compile time diminsh what can be achieved.

Locks, Why They're Not a Big Problem, and Program Architecture

On the topic of striving for a lock free implementation, I'm not convinced there's much merit in that if it results in too much ugliness in the implementation, pain, grief, anguish, etc.

If a receiver is selecting across one or more channels then it is implicitly content to wait for messages to arrive. So there's no real penality there from the receiver's point of view.

However, if the receiver never ends up waiting because the flow of messages exceeds its ability to process them, and this is seen as a performance problem (where a lock-free implementation might be seen as a way of improving performance), then my argument is that one's program's architecture is inadequate. The implication is that the receiver is a performance bottle neck.

The real solution is that there needs to be additional receivers, a separate channel to each one, and a means (e.g. either round robin, push pull, etc - the parallels with some of ZeroMQ's patterns and load balancing are not coincidental) for the sender to know to which one the next message should be sent.

The ideal architecture is one where there's enough parallel receivers to juuuuust ensure that the sender never has to block. The sender should always be finding that a receiver is available to receive a message.

The Actor model is often favoured because of its simplicity, but the runtime problems that might occur (deadlock, livelock, etc), are unpleasant. By using a strict CSP model, and getting the architecture just right, nothing ever blocks on send (just like Actor) but still achieves rendevous but with the added advantage of fixed latencies in data flow through your program. That's generally nice for real time things like streaming video, meeting performance targets, etc.

Now, if locks are used as part of the channel implementation, then the sender's end of a channel should then always be finding that the locks are available (because there's enough receivers to keep up with the required data flow rate). And if the locks are always available, they're probably quick to acquire.

So I'm basically suggesting that we probably shouldn't worry about an implementation using locks; if the locks are thought to be a problem, one's application architecture is likely to be more of an issue than the overhead of the locks.

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