@@ -136,11 +136,19 @@ internal class MutexImpl(locked: Boolean) : Mutex {
136
136
@Volatile
137
137
private var _state : Any? = if (locked) EmptyLocked else EmptyUnlocked // shared objects while we have no waiters
138
138
139
+ // resumeNext is: RESUME_QUIESCENT | RESUME_ACTIVE | ResumeReq
140
+ @Volatile
141
+ private var resumeNext: Any = RESUME_QUIESCENT
142
+
139
143
private companion object {
140
144
@JvmField
141
145
val STATE : AtomicReferenceFieldUpdater <MutexImpl , Any ?> =
142
146
AtomicReferenceFieldUpdater .newUpdater(MutexImpl ::class .java, Any ::class .java, " _state" )
143
147
148
+ @JvmField
149
+ val RESUME_NEXT : AtomicReferenceFieldUpdater <MutexImpl , Any > =
150
+ AtomicReferenceFieldUpdater .newUpdater(MutexImpl ::class .java, Any ::class .java, " resumeNext" )
151
+
144
152
@JvmField
145
153
val LOCK_FAIL = Symbol (" LOCK_FAIL" )
146
154
@@ -165,6 +173,11 @@ internal class MutexImpl(locked: Boolean) : Mutex {
165
173
@JvmField
166
174
val EmptyUnlocked = Empty (UNLOCKED )
167
175
176
+ @JvmField
177
+ val RESUME_QUIESCENT = Symbol (" RESUME_QUIESCENT" )
178
+
179
+ @JvmField
180
+ val RESUME_ACTIVE = Symbol (" RESUME_ACTIVE" )
168
181
}
169
182
170
183
public override val isLocked: Boolean get() {
@@ -347,9 +360,17 @@ internal class MutexImpl(locked: Boolean) : Mutex {
347
360
if (STATE .compareAndSet(this , state, op) && op.perform(this ) == null ) return
348
361
} else {
349
362
val token = (waiter as LockWaiter ).tryResumeLockWaiter()
350
- if (token != null ) { // successfully resumed waiter that now is holding the lock
363
+ if (token != null ) {
364
+ // successfully resumed waiter that now is holding the lock
365
+ // we must immediately transfer ownership to the next waiter, because this coroutine
366
+ // might try to lock it again after unlock returns do to StackOverflow avoidance code
367
+ // and its attempts to take a lock must be queued.
351
368
state.owner = waiter.owner ? : LOCKED
352
- waiter.completeResumeLockWaiter(token)
369
+ // StackOverflow avoidance code
370
+ if (startResumeNext(waiter, token)) {
371
+ waiter.completeResumeLockWaiter(token)
372
+ finishResumeNext()
373
+ }
353
374
return
354
375
}
355
376
}
@@ -359,6 +380,44 @@ internal class MutexImpl(locked: Boolean) : Mutex {
359
380
}
360
381
}
361
382
383
+ private class ResumeReq (
384
+ @JvmField val waiter : LockWaiter ,
385
+ @JvmField val token : Any
386
+ )
387
+
388
+ private fun startResumeNext (waiter : LockWaiter , token : Any ): Boolean {
389
+ while (true ) { // lock-free loop on resumeNext
390
+ val resumeNext = this .resumeNext
391
+ when {
392
+ resumeNext == = RESUME_QUIESCENT -> {
393
+ // this is never concurrent, because only one thread is holding mutex and trying to resume
394
+ // next waiter, so no need to CAS here
395
+ this .resumeNext = RESUME_ACTIVE
396
+ return true
397
+ }
398
+ resumeNext == = RESUME_ACTIVE ->
399
+ if (RESUME_NEXT .compareAndSet(this , resumeNext, ResumeReq (waiter, token))) return false
400
+ else -> error(" Cannot happen" )
401
+ }
402
+ }
403
+ }
404
+
405
+ private fun finishResumeNext () {
406
+ while (true ) { // lock-free loop on resumeNext, also a resumption loop to fulfill requests of inner resume invokes
407
+ val resumeNext = this .resumeNext
408
+ when {
409
+ resumeNext == = RESUME_ACTIVE ->
410
+ if (RESUME_NEXT .compareAndSet(this , resumeNext, RESUME_QUIESCENT )) return
411
+ resumeNext is ResumeReq -> {
412
+ // this is never concurrently, only one thread is finishing, so no need to CAS here
413
+ this .resumeNext = RESUME_ACTIVE
414
+ resumeNext.waiter.completeResumeLockWaiter(resumeNext.token)
415
+ }
416
+ else -> error(" Cannot happen" )
417
+ }
418
+ }
419
+ }
420
+
362
421
override fun toString (): String {
363
422
while (true ) {
364
423
val state = this ._state
0 commit comments