Conversation
|
@armanbilge , @djspiewak , looks like we have the answer to this question!
|
reardonj
left a comment
There was a problem hiding this comment.
This makes sense to me.
| // First try to steal some expired timers. | ||
| val stoleTimers = pool.stealTimers(now, rnd) | ||
|
|
||
| // Stolen timer callbacks resume fibers, and resumed fibers are |
There was a problem hiding this comment.
Do stolen timer callbacks always resume fibers? Wondering if we could just check stoleTimers instead of queue.nonEmpty()
There was a problem hiding this comment.
I don't know if we can have a race condition if we have a cancel that happens in firing moment making it don't resume fiber ( just hypothesis)
I think stoleTimers is a sufficient guard and it is the shape the commit originally had,
both are correct just matter of clarity and taste , I can change it if you want
There was a problem hiding this comment.
The nonEmpty call is likely going to be a bit more expensive since it ends up reading and comparing several AtomicIntegers, instead of the boolean we just computed, so it would be nice to avoid it if possible.
860befe to
d899276
Compare
djspiewak
left a comment
There was a problem hiding this comment.
This is a really good catch, but as written I think it runs the risk of creating permanently asymmetric workloads by always prioritizing timer theft, particularly if an application has a lot of timer expiries that schedule new timers. It may be better to alternate this prioritization, either swapping back and forth (steal fibers first, then timers; steal timers first, then fibers) or by efficiently randomly choosing which one to do first.
Valid point, for having alternating the prio per worker it add state to the worker and more branches, also for app with periodic load having a lot of timers in case of alternation it makes the worker search for work first where there is nothing to steal from other worker queue, the good part is that simple to test, deterministic and costs almost nothing. |
7508c51 to
75c91ee
Compare
|
I checked the git history and my understanding is that enqueueBatch has condition that never changed which is that it's only called when the queue has spare capacity , otherwise it will spin forever. |
`stealFromOtherWorkerThread` falls back to polling the external queue and enqueueing a batch on the local queue of the searching worker thread, assuming that this local queue is empty. However, a searching worker thread steals expired timers before stealing fibers, and the timer callbacks may resume enough fibers onto its local queue to leave no room for a batch. `enqueueBatch` then spins forever and block the worker Add `LocalQueue.hasCapacityForBatch`, and use it in `stealFromOtherWorkerThread` to skip the external queue when a batch does not fit. Timers and fibers are still stolen together, to avoid asymmetry Fixes typelevel#4674
75c91ee to
848d3d9
Compare
Add a check that prevent searcher thread from stealling from other thread or from external queue when it queue is no longer empty. The current behavior cause a forver spin of searcher thread when expiring timer fill all the local queue
issue discussed here #issues/4674