Skip to content

Commit 4ed9fa0

Browse files
committed
Add AsyncCallback.Mutex
Closes #815
1 parent f378921 commit 4ed9fa0

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

core/src/main/scala/japgolly/scalajs/react/AsyncCallback.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,44 @@ object AsyncCallback {
327327
_ <- b.complete.when_(count <= 0)
328328
} yield new CountDownLatch(count, b)
329329

330+
// ===================================================================================================================
331+
332+
final class Mutex private[AsyncCallback]() {
333+
334+
private var mutex: Option[Barrier] =
335+
None
336+
337+
private val release: Callback =
338+
CallbackTo {
339+
val old = mutex
340+
mutex = None
341+
old
342+
}.flatMap(Callback.traverseOption(_)(_.complete))
343+
344+
/** Wrap a [[AsyncCallback]] so that it executes in the mutex.
345+
*
346+
* Note: THIS IS NOT RE-ENTRANT. Calling this from within the mutex will block.
347+
*/
348+
def apply[A](ac: AsyncCallback[A]): AsyncCallback[A] =
349+
byName {
350+
351+
mutex match {
352+
case None =>
353+
// Mutex empty
354+
val b = barrier.runNow()
355+
mutex = Some(b)
356+
ac.finallyRunSync(release)
357+
358+
case Some(b) =>
359+
// Mutex in use
360+
b.await >> apply(ac)
361+
}
362+
}
363+
}
364+
365+
/** Creates a new (non-reentrant) mutex. */
366+
def mutex: CallbackTo[Mutex] =
367+
CallbackTo(new Mutex)
330368
}
331369

332370
// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████

doc/changelog/1.7.7.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ This entire release is focused on `AsyncCallback`.
3636
}
3737
```
3838

39+
* Added `mutex` which returns a `AsyncCallback.Mutex`:
40+
41+
```scala
42+
AsyncCallback.Mutex {
43+
/** Wrap a AsyncCallback so that it executes in the mutex.
44+
*
45+
* Note: THIS IS NOT RE-ENTRANT. Calling this from within the mutex will block.
46+
*/
47+
def apply[A](ac: AsyncCallback[A]): AsyncCallback[A]
48+
}
49+
```
50+
3951
* You can now add a `_` suffix to the following to return an `AsyncCallback[Unit]` and be more efficient under-the-hood:
4052

4153
* `traverse`

test/src/test/scala/japgolly/scalajs/react/core/AsyncCallbackTest.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,5 +236,16 @@ object AsyncCallbackTest extends TestSuite {
236236
}
237237
}
238238

239+
"mutex" - asyncTest {
240+
for {
241+
mutex <- AsyncCallback.mutex.asAsyncCallback
242+
b <- AsyncCallback.barrier.asAsyncCallback
243+
_ <- mutex(b.await).fork_.asAsyncCallback // mutex start
244+
t <- mutex(AsyncCallback.unit).timeoutMs(500).delayMs(1)
245+
_ <- b.complete.asAsyncCallback // mutex end
246+
_ <- mutex(AsyncCallback.unit)
247+
} yield assert(t.isEmpty)
248+
}
249+
239250
}
240251
}

0 commit comments

Comments
 (0)