@@ -133,6 +133,16 @@ public interface SelectInstance<in R> {
133
133
public fun disposeOnSelect (handle : DisposableHandle )
134
134
}
135
135
136
+ /* *
137
+ * Descriptor that is a part of the atomic select operator.
138
+ *
139
+ * @suppress **This is unstable API and it is subject to change.**
140
+ */
141
+ @InternalCoroutinesApi
142
+ public interface SelectInstanceDesc {
143
+ val select: SelectInstance <* >
144
+ }
145
+
136
146
/* *
137
147
* Waits for the result of multiple suspending functions simultaneously, which are specified using _clauses_
138
148
* in the [builder] scope of this select invocation. The caller is suspended until one of the clauses
@@ -325,7 +335,7 @@ internal class SelectBuilderImpl<in R>(
325
335
while (true ) { // lock-free loop on state
326
336
val state = this .state
327
337
if (state == = this ) {
328
- if (addLastIf(node, { this .state == = this }) )
338
+ if (addLastIf(node) { this .state == = this })
329
339
return
330
340
} else { // already selected
331
341
handle.dispose()
@@ -344,9 +354,26 @@ internal class SelectBuilderImpl<in R>(
344
354
// it is just like start(), but support idempotent start
345
355
override fun trySelect (idempotent : Any? ): Boolean {
346
356
assert { idempotent !is OpDescriptor } // "cannot use OpDescriptor as idempotent marker"
347
- while (true ) { // lock-free loop on state
348
- val state = this .state
357
+ _state .loop { state -> // lock-free loop on state
349
358
when {
359
+ // Found descriptor from the same select instance while registering another clause on the same instance
360
+ state is AtomicSelectOp <* > && state.impl == = this &&
361
+ idempotent is SelectInstanceDesc && idempotent.select == = this ->
362
+ {
363
+ /*
364
+ * We cannot do state.perform(this) here and "help" it since it is the same select and
365
+ * we'll get StackOverflowError. See https://github.com/Kotlin/kotlinx.coroutines/issues/1411
366
+ * "Properly" supporting this case is complicated in the current select architecture, since we
367
+ * have to "pull up" body of two clauses that need to be activated and link them
368
+ * together so that they are sequentially executed, followed by the continuation of
369
+ * the code after the select expression. This requires massive implementation rewrite
370
+ * (instead of immediately resuming clause bodies, they have to be stored somewhere first).
371
+ */
372
+ error(" Cannot use matching select clauses on the same object" )
373
+ }
374
+ // Found descriptor of any other atomic operation -- help it
375
+ state is OpDescriptor -> state.perform(this )
376
+ // Found initial state (not selected yet)
350
377
state == = this -> {
351
378
if (_state .compareAndSet(this , idempotent)) {
352
379
doAfterSelect()
@@ -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