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.
Motivation
Currently, the blessed data structure for Domainslib is the deque. It's LIFO, and while that's optimal for locality, it's quite easy to shoot oneself in the foot with liveness issues. For example, if a web server is processing a stream of requests and starts some compute in the background, all requests will eventually have to wait for the compute to finish. Even knowing about the issue there's not much that can be improved here (without re-enginering the workload or creating multiple schedulers) because LIFO keeps working on the existing sub-tree of tasks until done by design. FIFO, on the other hand, juggles all subtrees and treats a single task as unit of work. I believe it's a much safer choice for the default scheduling strategy.
Thus, this PR adds a simple single-producer multi-consumer queue inspired by the work-stealing deque and Golang's scheduler. It's useful as a general structure but has been written mostly with Domainslib in mind.
Design
Similar design to the deque. The array is not atomic. Writer first inserts the item and increments the
tail
index. Stealers first read item in the array and try to claim it withcas
on the head. Thus the writer operates on the region of the array between tail (incl.) and head (excl.), while stealers between head (incl.) and tail (excl.). Stealer may do a non-linearizable read of the array but it won't be returned to the caller ascas
fails in such a case.Local deque could be identical to stealing. I've modified to first modify index and then read the array to ensure wait-freedom (or, in particular, to eliminate the risk of local deque competing with steals). I've added further explanations in the code.
The structure is wait-free for the owner of the queue and lock-free for stealers. This design should help it keep stable performance as system becomes loaded and stealing decreases.
Testing
Tests:
We can also add a lock-free steal-half function, which will improve work distribution on skewed workloads, but keeping it simple for now.