A pattern for overcoming non-determinism of Golang select statement
In this article I will introduce a pattern that simulates behavior of select statement in go but without the non-determinism.
There are cases when we have a goroutine which takes care of multiple similar tasks. For example in case of websocket we might have a hub goroutine that exposes a connect
channel and a disconnect
channel, both of type chan *Connection
. Upon receiving a message from connect
channel it keeps a reference to the connection somewhere and upon receiving from disconnect
channel, it removes reference to the connection. In this case even though we are exposing two channels and handling two different types of events, we need to process events in order of their occurrence. It means that the event which has occurred sooner, should be taken care of sooner as well. Otherwise we may run into cases when for a specific connection, we handle disconnect event sooner than its corresponding connect event.
First thing that comes to mind for handling such cases is using a select statement inside a for loop. However there is an issue with that approach: Select statement provides no guarantee of order of execution. What it means is when multiple channels have some message in them, select statement does not necessarily pick the message that has arrived earlier, first:
> If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
It is ok for many usecases, however there are times when we need to make sure the message that has come earliest, will be read first. In those cases we can not use select
statements anymore. Recently I have found a workaround for this overcoming this problem.
Here is the code before applying the pattern. Note that on each iteration of for loop if connectChan
and disconnectChan
both have messages, select
statement will just read one of them randomly. When deciding which channel to read from it does not care message of which channel has arrived earlier. Therefore we might run into the problem described earlier.
However there is a neat way to have that guarantee of order of execution with a small change. Instead of listening to two different channels, we can expose one command channel, along with a Command
interface and get rid of the select statement. After applying the pattern, our code will look like:
Now all events will go through a single channel, therefore they will be processed in the same order as they have occurred. Beauty of this pattern in my opinion is that there is no switch-case anywhere in it. Therefore it is quite easy to add new types of commands. Conforming with open-closed principle.