Skip to content

Commit a259143

Browse files
author
Jasper Geurtz
committed
Double-buffer SideEffectQueue to avoid blocking enqueue during flush
1 parent c955502 commit a259143

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

‎src/main/java/bwapi/SideEffectQueue.java‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
class SideEffectQueue {
99

1010
private ArrayList<SideEffect> queue = new ArrayList<>();
11+
private ArrayList<SideEffect> flushQueue = new ArrayList<>();
1112

1213
/**
1314
* Includes a side effect to be sent back to BWAPI in the future.
@@ -25,8 +26,19 @@ synchronized void enqueue(SideEffect sideEffect) {
2526
* @param liveGameData
2627
* The live game frame's data, using the BWAPI shared memory.
2728
*/
28-
synchronized void flushTo(ClientData.GameData liveGameData) {
29-
queue.forEach(x -> x.apply(liveGameData));
30-
queue.clear();
29+
void flushTo(ClientData.GameData liveGameData) {
30+
final ArrayList<SideEffect> toFlush;
31+
synchronized (this) {
32+
if (queue.isEmpty()) {
33+
return;
34+
}
35+
toFlush = queue;
36+
queue = flushQueue;
37+
flushQueue = toFlush;
38+
}
39+
for (int i = 0, size = toFlush.size(); i < size; i++) {
40+
toFlush.get(i).apply(liveGameData);
41+
}
42+
toFlush.clear();
3143
}
3244
}

‎src/test/java/bwapi/SynchronizationTest.java‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,4 +388,46 @@ public void async_DisablesLatencyCompensation() {
388388
environmentAsync.onFrame(2, () -> { assertThrows(IllegalStateException.class, () -> environmentAsync.bwClient.getGame().setLatCom(true)); });
389389
environmentAsync.runGame(3);
390390
}
391+
392+
@Test
393+
public void sideEffectQueue_DoesNotBlockEnqueueDuringFlush() throws Exception {
394+
SideEffectQueue queue = new SideEffectQueue();
395+
CountDownLatch inFlush = new CountDownLatch(1);
396+
CountDownLatch enqueueDone = new CountDownLatch(1);
397+
AtomicBoolean secondApplied = new AtomicBoolean(false);
398+
399+
SideEffect first = org.mockito.Mockito.mock(SideEffect.class);
400+
org.mockito.Mockito.doAnswer(inv -> {
401+
inFlush.countDown();
402+
assertTrue("enqueue should not be blocked while flushTo is applying side effects",
403+
enqueueDone.await(5, TimeUnit.SECONDS));
404+
return null;
405+
}).when(first).apply(null);
406+
407+
SideEffect second = org.mockito.Mockito.mock(SideEffect.class);
408+
org.mockito.Mockito.doAnswer(inv -> {
409+
secondApplied.set(true);
410+
return null;
411+
}).when(second).apply(null);
412+
413+
queue.enqueue(first);
414+
415+
Thread enqueuer = new Thread(() -> {
416+
try {
417+
assertTrue(inFlush.await(5, TimeUnit.SECONDS));
418+
queue.enqueue(second);
419+
enqueueDone.countDown();
420+
} catch (InterruptedException e) {
421+
Thread.currentThread().interrupt();
422+
}
423+
});
424+
enqueuer.start();
425+
426+
queue.flushTo(null);
427+
enqueuer.join(5000);
428+
assertFalse("Second side effect should remain queued for the next flush", secondApplied.get());
429+
430+
queue.flushTo(null);
431+
assertTrue("Second side effect should be applied on the next flush", secondApplied.get());
432+
}
391433
}

0 commit comments

Comments
 (0)