perf: replace busy-wait channel receives with receiveTimeout #448
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Every thread in sig that receives on a channel utilizes a CPU core at 100% from busy-waiting in the receive loop. This PR eliminates the busy-waiting by switching from
tryReceive
toreceiveTimeout
in those places. After this change, I see a dramatic decrease in CPU usage while running sig.I did not remove
tryReceive
from defer statements or unit tests. It does not seem appropriate to use blocking calls in those contexts if non-blocking calls are already known to be sufficient.Handling
error.Closed
Unlike
tryReceive
,receiveTimeout
can return an error to indicate that the channel is closed. I needed to make a decision about how to handle this error. To replicate the same behavior astryReceive
, the error should simply be ignored withcatch null
in every scenario. But I didn't take this approach in most cases.error.Closed
provides new information that we didn't have before, and we can use that information to our advantage on a case-by-case basis. If that information can lead us to a definitive conclusion that it would be pointless to remain in a particular context, then I've changed the code to exit from that context. I've done this conservatively: the control flow will remain in a context if there is any possibility of doing more useful work in that context.I used the following where appropriate:
catch return
: There is no more meaningful work that can be done in this function if the channel is closed.catch break
: The current scope has nothing else to do, but there is some additional work that can be done in the function after exiting the current scope.catch null
: There is meaningful work that can be done within the current scope even if the channel is closed. Often this is because we collect a batch of items from the channel before processing any of them.