@@ -193,11 +193,19 @@ private val UNDECIDED: Any = Symbol("UNDECIDED")
193
193
@SharedImmutable
194
194
private val RESUMED : Any = Symbol (" RESUMED" )
195
195
196
+ /* *
197
+ * Descriptor that is a part of the atomic select operator.
198
+ */
199
+ internal interface SelectInstanceDesc {
200
+ val select: SelectInstance <* >
201
+ }
202
+
196
203
@PublishedApi
197
204
internal class SelectBuilderImpl <in R >(
198
205
private val uCont : Continuation <R > // unintercepted delegate continuation
199
206
) : LockFreeLinkedListHead(), SelectBuilder<R>,
200
- SelectInstance <R >, Continuation <R >, CoroutineStackFrame {
207
+ SelectInstance <R >, Continuation <R >, CoroutineStackFrame
208
+ {
201
209
override val callerFrame: CoroutineStackFrame ?
202
210
get() = uCont as ? CoroutineStackFrame
203
211
@@ -325,7 +333,7 @@ internal class SelectBuilderImpl<in R>(
325
333
while (true ) { // lock-free loop on state
326
334
val state = this .state
327
335
if (state == = this ) {
328
- if (addLastIf(node, { this .state == = this }) )
336
+ if (addLastIf(node) { this .state == = this })
329
337
return
330
338
} else { // already selected
331
339
handle.dispose()
@@ -344,13 +352,32 @@ internal class SelectBuilderImpl<in R>(
344
352
// it is just like start(), but support idempotent start
345
353
override fun trySelect (idempotent : Any? ): Boolean {
346
354
assert { idempotent !is OpDescriptor } // "cannot use OpDescriptor as idempotent marker"
347
- while (true ) { // lock-free loop on state
348
- val state = this .state
355
+ _state .loop { state -> // lock-free loop on state
349
356
when {
350
- state == = this -> {
351
- if (_state .compareAndSet(this , idempotent)) {
352
- doAfterSelect()
353
- return true
357
+ // Found initial state (not selected yet) -- try to make it selected
358
+ state == = this -> if (_state .compareAndSet(this , idempotent)) {
359
+ doAfterSelect()
360
+ return true
361
+ }
362
+ // Found descriptor of another atomic operation
363
+ state is OpDescriptor -> {
364
+ // Did we found descriptor from the same select instance
365
+ // while registering another clause on the same instance?
366
+ if (state is AtomicSelectOp <* > && state.impl == = this &&
367
+ idempotent is SelectInstanceDesc && idempotent.select == = this )
368
+ {
369
+ /*
370
+ * We cannot do state.perform(this) here and "help" it since it is the same select and
371
+ * we'll get StackOverflowError. See https://github.com/Kotlin/kotlinx.coroutines/issues/1411
372
+ * "Properly" supporting this case is complicated in the current select architecture, since we
373
+ * have to "pull up" body of two clauses that need to be activated and link them
374
+ * together so that they are sequentially executed, followed by the continuation of
375
+ * the code after the select expression. This requires massive implementation rewrite
376
+ * (instead of immediately resuming clause bodies, they have to be stored somewhere first).
377
+ */
378
+ error(" Cannot use matching select clauses on the same object" )
379
+ } else {
380
+ state.perform(this ) // help it
354
381
}
355
382
}
356
383
// otherwise -- already selected
@@ -361,10 +388,14 @@ internal class SelectBuilderImpl<in R>(
361
388
}
362
389
}
363
390
364
- override fun performAtomicTrySelect (desc : AtomicDesc ): Any? = AtomicSelectOp (desc, true ).perform(null )
365
- override fun performAtomicIfNotSelected (desc : AtomicDesc ): Any? = AtomicSelectOp (desc, false ).perform(null )
391
+ override fun performAtomicTrySelect (desc : AtomicDesc ): Any? =
392
+ AtomicSelectOp (this , desc, true ).perform(null )
393
+
394
+ override fun performAtomicIfNotSelected (desc : AtomicDesc ): Any? =
395
+ AtomicSelectOp (this , desc, false ).perform(null )
366
396
367
- private inner class AtomicSelectOp (
397
+ private class AtomicSelectOp <R >(
398
+ @JvmField val impl : SelectBuilderImpl <R >,
368
399
@JvmField val desc : AtomicDesc ,
369
400
@JvmField val select : Boolean
370
401
) : AtomicOp<Any?>() {
@@ -375,34 +406,45 @@ internal class SelectBuilderImpl<in R>(
375
406
// we are originator (affected reference is not null if helping)
376
407
prepareIfNotSelected()?.let { return it }
377
408
}
378
- return desc.prepare(this )
409
+ try {
410
+ return desc.prepare(this )
411
+ } catch (e: Throwable ) {
412
+ // undo prepareIfNotSelected on crash (for example if IllegalStateException is thrown)
413
+ if (affected == null ) undoPrepare()
414
+ throw e
415
+ }
379
416
}
380
417
381
418
override fun complete (affected : Any? , failure : Any? ) {
382
419
completeSelect(failure)
383
420
desc.complete(this , failure)
384
421
}
385
422
386
- fun prepareIfNotSelected (): Any? {
387
- _state .loop { state ->
423
+ private fun prepareIfNotSelected (): Any? {
424
+ impl. _state .loop { state ->
388
425
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 ))
426
+ state == = this -> return null // already in progress
427
+ state is OpDescriptor -> state.perform(impl ) // help
428
+ state == = impl -> {
429
+ if (impl. _state .compareAndSet(impl , this ))
393
430
return null // success
394
431
}
395
432
else -> return ALREADY_SELECTED
396
433
}
397
434
}
398
435
}
399
436
437
+ // reverts the change done by prepareIfNotSelected
438
+ private fun undoPrepare () {
439
+ impl._state .compareAndSet(this , impl)
440
+ }
441
+
400
442
private fun completeSelect (failure : Any? ) {
401
443
val selectSuccess = select && failure == null
402
- val update = if (selectSuccess) null else this @SelectBuilderImpl
403
- if (_state .compareAndSet(this @AtomicSelectOp , update)) {
444
+ val update = if (selectSuccess) null else impl
445
+ if (impl. _state .compareAndSet(this , update)) {
404
446
if (selectSuccess)
405
- doAfterSelect()
447
+ impl. doAfterSelect()
406
448
}
407
449
}
408
450
}
0 commit comments