Native: fix losing Worker.executeAfter jobs on scheduled time clash

The implementation was keeping delayed jobs in std::set sorted
only by the scheduled execution time (in microseconds since epoch).
So two jobs submitted to a worker and having the same scheduled time
were considered equivalent by the set, and one of them got lost.

Fix this by using std::multiset instead of std::set.
This commit is contained in:
Svyatoslav Scherbina
2021-11-26 11:59:40 +00:00
committed by Space
parent 95648f1a9e
commit 24e7a11abc
3 changed files with 65 additions and 2 deletions
@@ -118,3 +118,60 @@ import kotlin.native.concurrent.*
assertEquals(2, counter.value)
}
}
// This test checks that when multiple `executeAfter` jobs are submitted to `targetWorker` and have the
// same scheduled execution time (in micros since an epoch), nether of them gets lost.
@Test fun testExecuteAfterScheduledTimeClash() = withWorker {
val targetWorker = this
val mainWorker = Worker.current
// Configuration of the test.
val numberOfSubmitters = 2
val numberOfTasks = 100
val delayInMicroseconds = 100L
val submitters = Array(numberOfSubmitters) { Worker.start() }
try {
val readySubmittersCounter = AtomicInt(0)
val executedTasksCounter = AtomicInt(0)
val finishedBatchesCounter = AtomicInt(0)
submitters.forEach {
it.executeAfter(0L, {
readySubmittersCounter.increment()
// Wait for other submitters, to make them all start at the same time:
while (readySubmittersCounter.value != numberOfSubmitters) {}
// Concurrently submit tasks with matching scheduled execution time:
repeat(numberOfTasks) {
targetWorker.executeAfter(delayInMicroseconds, {
executedTasksCounter.increment()
}.freeze())
}
// Use larger delay for the task below, to make sure it gets executed after
// the tasks above submitted by the same worker.
// If the order is wrong, the test will fail as well.
// NOTE: the code below was affected by the same problem with clashing times, so despite all the effort
// the test still might hang without a fix.
targetWorker.executeAfter(delayInMicroseconds + 1, {
mainWorker.executeAfter(0L, {
finishedBatchesCounter.increment()
}.freeze())
}.freeze())
}.freeze())
}
while (finishedBatchesCounter.value != numberOfSubmitters) {
// Wait and allow processing the `finishedBatchesCounter.increment()` tasks above:
Worker.current.park(delayInMicroseconds, process = true)
}
// Note: we could have just waited for the condition above to become true,
// but this would mean that the test would hang in case of failure, which is not quite convenient.
assertEquals(numberOfSubmitters * numberOfTasks, executedTasksCounter.value)
} finally {
submitters.forEach { it.requestTermination().result }
}
}