This repository has been archived by the owner on Jul 27, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First attempt at implementing select.
This contains a rewrite of the various waiters in q.go to support select operations. It then leverages this new waiter design to implement read and write select for unbounded and bounded channels. Currently the only code that uses Select is test code -- that will change when I am more confident in the API and have written more documentation. Notes: - Both kinds of waiter (waiter and weakwaiter) have been replaced by a condWaiter. condwaiters act like waiters except there can be an arbitrary number of sends, but only one send will succeed. Furthermore, senders are notified if their send was successful. Send operations may also be cancelled by a receiver, in which case the cancelled send will also report failure. Conwaiters were introduced to facilitate select operations. As such not only do they allow for a value to be stored, but they also keep track of which channel has successfully sent on them. condWaiter send/recv methods have "overlapping" variants in which sends will report success if any sender from the same channel succeeded. This is used to accommodate the subtlety of multiple receive threads attempting to wake up a blocking sender (an issue present in the initial design). All standard channel protocols are essentially unchanged, except that if their send operation fails they retry the entire send/receive until they are successful. - Given this new object, a Select operates on a list of SelectRead or SelectWrite requests. The operation creates a new condWaiter and performs the first part of the read and write operations for the channel, these parts are: - For reading: Increment the H index and either return the value in the relevant location or CAS in the waiter object. - For writing: Increment the T index and either perform the send protocol (always true for the unbounded case) or potentially block by placing the condWaiter in its relevant location. If any of these processes succeed (i.e. no blocking on the condWaiter is required) then the condWaiter is cancelled and that value is returned. If blocking is required for all read/write requests, the thread then blocks on a waiter and is awoken by the sender/receiver who will win the select. - One additional subtlety here is that in the bounded case, blocking waiters are still woken up _even if the cell chanSize ahead of them represents a failed select_. While this is counter-intuitive (and requires justification!), it seems to preserve the intended semantics. - All of this required restructuring the enqueue/dequeue code so that it executes an initial non-blocking step and then passes a callback for the remaining portion of the protocol to be executed if the send on the condWaiter succeeds. This has resulted in code that is less readable than it was before -- more cleanup is warranted. - I was recently informed that a select on N operations, k of which do not require blocking, must choose which channel to succeed randomly among those k. I currently do not do this, but implementing it should be as simple as iterating over the request in a random order. Caveats: - This implementation contains a lot of subtle moves, so it probably has bugs. - There are currently some performance regressions (particularly for buffered channels) compared to send/recv performance before select support was implemented. These need to be addressed. I see no reason why this code should be much slower, but I would like to come up with a way that doesn't have a ton of special cases related to detecting if a waiting thread is selecting or not. - These channels still do not support close, so they are not at feature parity with go channels yet. However, I suspect that select is the more difficult of the two to implement.
- Loading branch information