@@ -246,7 +246,7 @@ internal class CoroutineScheduler(
246
246
private val random = Random ()
247
247
248
248
// This is used a "stop signal" for debugging/tests only
249
- private val isTerminated = atomic(0 ) // workaround for atomicfu bug
249
+ private val isTerminated = atomic(false )
250
250
251
251
companion object {
252
252
private const val MAX_SPINS = 1000
@@ -294,29 +294,21 @@ internal class CoroutineScheduler(
294
294
295
295
/*
296
296
* Shuts down current scheduler and waits until all threads are stopped.
297
- * This method uses unsafe API (unconditional unparks, ignoring interruptions etc. )
297
+ * This method uses unsafe API (does unconditional unparks)
298
298
* and intended to be used only for testing. Invocation has no additional effect if already closed.
299
299
*/
300
300
fun shutdown (timeout : Long ) {
301
- if (! isTerminated.compareAndSet(0 , 1 )) return
302
- // Race with recently created threads which may park indefinitely
303
- var finishedThreads = 0
304
- while (finishedThreads < createdWorkers) {
305
- var finished = 0
306
- for (i in 1 .. createdWorkers) {
307
- synchronized(workers) {
308
- // Get the worker from array and also clear reference in array under synchronization
309
- workers[i].also { workers[i] = null }
310
- } ?.also {
311
- if (it.isAlive) {
312
- // Unparking alive thread is unsafe in general, but acceptable for testing purposes
313
- LockSupport .unpark(it)
314
- it.join(timeout)
315
- }
316
- ++ finished
317
- }
301
+ // atomically set termination flag which is checked when workers are added or removed
302
+ if (! isTerminated.compareAndSet(false , true )) return
303
+ // Capture # of created workers that cannot change anymore (mind the synchronized block!)
304
+ val created = synchronized(workers) { createdWorkers }
305
+ for (i in 1 .. created) {
306
+ val worker = workers[i]!!
307
+ if (worker.isAlive) {
308
+ // Unparking alive thread is unsafe in general, but acceptable for testing purposes
309
+ LockSupport .unpark(worker)
310
+ worker.join(timeout)
318
311
}
319
- finishedThreads = finished
320
312
}
321
313
// cleanup state to make sure that tryUnpark tries to create new threads and crashes because it isTerminated
322
314
assert (cpuPermits.availablePermits() == corePoolSize)
@@ -438,8 +430,8 @@ internal class CoroutineScheduler(
438
430
*/
439
431
private fun createNewWorker (): Int {
440
432
synchronized(workers) {
441
- // for test purposes make sure we're not trying to resurrect terminated scheduler
442
- require (isTerminated.value == 0 ) { " This scheduler was terminated " }
433
+ // Make sure we're not trying to resurrect terminated scheduler
434
+ if (isTerminated.value) throw ShutdownException ()
443
435
val state = controlState.value
444
436
val created = createdWorkers(state)
445
437
val blocking = blockingWorkers(state)
@@ -456,6 +448,9 @@ internal class CoroutineScheduler(
456
448
}
457
449
}
458
450
451
+ // Is thrown when attempting to create new worker, but this scheduler isTerminated
452
+ private class ShutdownException : RuntimeException ()
453
+
459
454
/* *
460
455
* Returns [ADDED], or [NOT_ADDED], or [ADDED_REQUIRES_HELP].
461
456
*/
@@ -682,28 +677,33 @@ internal class CoroutineScheduler(
682
677
private var lastStealIndex = 0 // try in order repeated, reset when unparked
683
678
684
679
override fun run () {
685
- var wasIdle = false // local variable to avoid extra idleReset invocations when tasks repeatedly arrive
686
- while (isTerminated.value == 0 && state != WorkerState .TERMINATED ) {
687
- val task = findTask()
688
- if (task == null ) {
689
- // Wait for a job with potential park
690
- if (state == WorkerState .CPU_ACQUIRED ) {
691
- cpuWorkerIdle()
680
+ try {
681
+ var wasIdle = false // local variable to avoid extra idleReset invocations when tasks repeatedly arrive
682
+ while (! isTerminated.value && state != WorkerState .TERMINATED ) {
683
+ val task = findTask()
684
+ if (task == null ) {
685
+ // Wait for a job with potential park
686
+ if (state == WorkerState .CPU_ACQUIRED ) {
687
+ cpuWorkerIdle()
688
+ } else {
689
+ blockingWorkerIdle()
690
+ }
691
+ wasIdle = true
692
692
} else {
693
- blockingWorkerIdle()
694
- }
695
- wasIdle = true
696
- } else {
697
- if (wasIdle) {
698
- idleReset (task.mode )
699
- wasIdle = false
693
+ if (wasIdle) {
694
+ idleReset(task.mode)
695
+ wasIdle = false
696
+ }
697
+ beforeTask(task)
698
+ runSafely (task)
699
+ afterTask(task)
700
700
}
701
- beforeTask(task)
702
- runSafely(task)
703
- afterTask(task)
704
701
}
702
+ } catch (e: ShutdownException ) {
703
+ // race with shutdown -- ignore exception and don't print it on the console
704
+ } finally {
705
+ tryReleaseCpu(WorkerState .TERMINATED )
705
706
}
706
- tryReleaseCpu(WorkerState .TERMINATED )
707
707
}
708
708
709
709
private fun runSafely (task : Task ) {
@@ -814,6 +814,8 @@ internal class CoroutineScheduler(
814
814
*/
815
815
private fun tryTerminateWorker () {
816
816
synchronized(workers) {
817
+ // Make sure we're not trying race with termination of scheduler
818
+ if (isTerminated.value) throw ShutdownException ()
817
819
// Someone else terminated, bail out
818
820
if (createdWorkers <= corePoolSize) return
819
821
// Try to find blocking task before termination
0 commit comments