@@ -325,7 +325,7 @@ internal class SelectBuilderImpl<in R>(
325
325
while (true ) { // lock-free loop on state
326
326
val state = this .state
327
327
if (state == = this ) {
328
- if (addLastIf(node, { this .state == = this }) )
328
+ if (addLastIf(node) { this .state == = this })
329
329
return
330
330
} else { // already selected
331
331
handle.dispose()
@@ -344,9 +344,21 @@ internal class SelectBuilderImpl<in R>(
344
344
// it is just like start(), but support idempotent start
345
345
override fun trySelect (idempotent : Any? ): Boolean {
346
346
assert { idempotent !is OpDescriptor } // "cannot use OpDescriptor as idempotent marker"
347
- while (true ) { // lock-free loop on state
348
- val state = this .state
347
+ _state .loop { state -> // lock-free loop on state
349
348
when {
349
+ state is AtomicSelectOp <* > && state.impl == = this -> {
350
+ /*
351
+ * We cannot do state.perform(this) here and "help" it since it is the same select and
352
+ * we'll get StackOverflowError. See https://github.com/Kotlin/kotlinx.coroutines/issues/1411
353
+ * "Properly" supporting this case is complicated in the current select architecture, since we
354
+ * have to "pull up" body of two clauses that need to be activated and link them
355
+ * together so that they are sequentially executed, followed by the continuation of
356
+ * the code after the select expression. This requires massive implementation rewrite
357
+ * (instead of immediately resuming clause bodies, they have to be stored somewhere first).
358
+ */
359
+ error(" Cannot use different select clauses on the same object" )
360
+ }
361
+ state is OpDescriptor -> state.perform(this )
350
362
state == = this -> {
351
363
if (_state .compareAndSet(this , idempotent)) {
352
364
doAfterSelect()
@@ -361,10 +373,14 @@ internal class SelectBuilderImpl<in R>(
361
373
}
362
374
}
363
375
364
- override fun performAtomicTrySelect (desc : AtomicDesc ): Any? = AtomicSelectOp (desc, true ).perform(null )
365
- override fun performAtomicIfNotSelected (desc : AtomicDesc ): Any? = AtomicSelectOp (desc, false ).perform(null )
376
+ override fun performAtomicTrySelect (desc : AtomicDesc ): Any? =
377
+ AtomicSelectOp (this , desc, true ).perform(null )
378
+
379
+ override fun performAtomicIfNotSelected (desc : AtomicDesc ): Any? =
380
+ AtomicSelectOp (this , desc, false ).perform(null )
366
381
367
- private inner class AtomicSelectOp (
382
+ private class AtomicSelectOp <R >(
383
+ @JvmField val impl : SelectBuilderImpl <R >,
368
384
@JvmField val desc : AtomicDesc ,
369
385
@JvmField val select : Boolean
370
386
) : AtomicOp<Any?>() {
@@ -375,34 +391,45 @@ internal class SelectBuilderImpl<in R>(
375
391
// we are originator (affected reference is not null if helping)
376
392
prepareIfNotSelected()?.let { return it }
377
393
}
378
- return desc.prepare(this )
394
+ try {
395
+ return desc.prepare(this )
396
+ } catch (e: Throwable ) {
397
+ // undo prepareIfNotSelected on crash (for example if IllegalStateException is thrown)
398
+ if (affected == null ) undoPrepare()
399
+ throw e
400
+ }
379
401
}
380
402
381
403
override fun complete (affected : Any? , failure : Any? ) {
382
404
completeSelect(failure)
383
405
desc.complete(this , failure)
384
406
}
385
407
386
- fun prepareIfNotSelected (): Any? {
387
- _state .loop { state ->
408
+ private fun prepareIfNotSelected (): Any? {
409
+ impl. _state .loop { state ->
388
410
when {
389
- state == = this @AtomicSelectOp -> return null // already in progress
390
- state is OpDescriptor -> state.perform(this @SelectBuilderImpl ) // help
391
- state == = this @SelectBuilderImpl -> {
392
- if (_state .compareAndSet(this @SelectBuilderImpl , this @AtomicSelectOp ))
411
+ state == = this -> return null // already in progress
412
+ state is OpDescriptor -> state.perform(impl ) // help
413
+ state == = impl -> {
414
+ if (impl. _state .compareAndSet(impl , this ))
393
415
return null // success
394
416
}
395
417
else -> return ALREADY_SELECTED
396
418
}
397
419
}
398
420
}
399
421
422
+ // reverts the change done by prepareIfNotSelected
423
+ private fun undoPrepare () {
424
+ impl._state .compareAndSet(this , impl)
425
+ }
426
+
400
427
private fun completeSelect (failure : Any? ) {
401
428
val selectSuccess = select && failure == null
402
- val update = if (selectSuccess) null else this @SelectBuilderImpl
403
- if (_state .compareAndSet(this @AtomicSelectOp , update)) {
429
+ val update = if (selectSuccess) null else impl
430
+ if (impl. _state .compareAndSet(this , update)) {
404
431
if (selectSuccess)
405
- doAfterSelect()
432
+ impl. doAfterSelect()
406
433
}
407
434
}
408
435
}
0 commit comments