Skip to content

Commit 0baeb14

Browse files
committed
[fix][broker] Fix multiple race conditions in InMemoryDelayedDeliveryTracker
1 parent 30a15a0 commit 0baeb14

2 files changed

Lines changed: 108 additions & 6 deletions

File tree

pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/InMemoryDelayedDeliveryTracker.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private static long trimLowerBit(long timestamp, int bits) {
122122
}
123123

124124
@Override
125-
public boolean addMessage(long ledgerId, long entryId, long deliverAt) {
125+
public synchronized boolean addMessage(long ledgerId, long entryId, long deliverAt) {
126126
if (deliverAt < 0 || deliverAt <= getCutoffTime()) {
127127
messagesHaveFixedDelay = false;
128128
return false;
@@ -161,7 +161,7 @@ private void checkAndUpdateHighest(long deliverAt) {
161161
* Return true if there's at least a message that is scheduled to be delivered already.
162162
*/
163163
@Override
164-
public boolean hasMessageAvailable() {
164+
public synchronized boolean hasMessageAvailable() {
165165
boolean hasMessageAvailable = !delayedMessageMap.isEmpty()
166166
&& delayedMessageMap.firstKey() <= getCutoffTime();
167167
if (!hasMessageAvailable) {
@@ -174,7 +174,7 @@ public boolean hasMessageAvailable() {
174174
* Get a set of position of messages that have already reached.
175175
*/
176176
@Override
177-
public NavigableSet<Position> getScheduledMessages(int maxMessages) {
177+
public synchronized NavigableSet<Position> getScheduledMessages(int maxMessages) {
178178
int n = maxMessages;
179179
NavigableSet<Position> positions = new TreeSet<>();
180180
long cutoffTime = getCutoffTime();
@@ -237,7 +237,7 @@ public NavigableSet<Position> getScheduledMessages(int maxMessages) {
237237
}
238238

239239
@Override
240-
public CompletableFuture<Void> clear() {
240+
public synchronized CompletableFuture<Void> clear() {
241241
this.delayedMessageMap.clear();
242242
this.delayedMessagesCount.set(0);
243243
return CompletableFuture.completedFuture(null);
@@ -262,7 +262,7 @@ public long getBufferMemoryUsage() {
262262
}
263263

264264
@Override
265-
public void close() {
265+
public synchronized void close() {
266266
super.close();
267267
}
268268

@@ -275,7 +275,7 @@ && getNumberOfDelayedMessages() >= fixedDelayDetectionLookahead
275275
&& !hasMessageAvailable();
276276
}
277277

278-
protected long nextDeliveryTime() {
278+
protected synchronized long nextDeliveryTime() {
279279
return delayedMessageMap.firstKey();
280280
}
281281
}

pulsar-broker/src/test/java/org/apache/pulsar/broker/delayed/InMemoryDeliveryTrackerTest.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
import io.netty.util.concurrent.DefaultThreadFactory;
3434
import java.lang.reflect.Method;
3535
import java.time.Clock;
36+
import java.util.concurrent.CountDownLatch;
37+
import java.util.concurrent.ExecutorService;
38+
import java.util.concurrent.Executors;
39+
import java.util.concurrent.TimeUnit;
40+
import java.util.concurrent.atomic.AtomicInteger;
41+
import java.util.concurrent.atomic.AtomicReference;
3642
import java.util.NavigableMap;
3743
import java.util.Set;
3844
import java.util.TreeMap;
@@ -274,4 +280,100 @@ public void testDelaySequence(InMemoryDelayedDeliveryTracker tracker) throws Exc
274280
tracker.close();
275281
}
276282

283+
@Test(dataProvider = "delayedTracker")
284+
public void testRaceConditionInUpdateTimer(InMemoryDelayedDeliveryTracker tracker) throws Exception {
285+
final int numThreads = 15;
286+
final int operationsPerThread = 2000;
287+
final CountDownLatch startLatch = new CountDownLatch(1);
288+
final CountDownLatch doneLatch = new CountDownLatch(numThreads);
289+
final AtomicInteger errors = new AtomicInteger(0);
290+
final AtomicReference<Exception> firstException = new AtomicReference<>();
291+
292+
@Cleanup("shutdown")
293+
ExecutorService executorService = Executors.newFixedThreadPool(32);
294+
295+
for (int i = 0; i < numThreads / 4; i++) {
296+
executorService.submit(() -> {
297+
try {
298+
startLatch.await();
299+
for (int j = 0; j < operationsPerThread; j++) {
300+
tracker.getNumberOfDelayedMessages();
301+
Thread.sleep(1);
302+
}
303+
} catch (Exception e) {
304+
errors.incrementAndGet();
305+
firstException.compareAndSet(null, e);
306+
e.printStackTrace();
307+
} finally {
308+
doneLatch.countDown();
309+
}
310+
});
311+
}
312+
313+
for (int i = numThreads / 4; i < numThreads; i++) {
314+
executorService.submit(() -> {
315+
try {
316+
startLatch.await();
317+
for (int j = 0; j < operationsPerThread; j++) {
318+
tracker.addMessage(1, 1, 10);
319+
Thread.sleep(1);
320+
}
321+
} catch (Exception e) {
322+
errors.incrementAndGet();
323+
firstException.compareAndSet(null, e);
324+
e.printStackTrace();
325+
} finally {
326+
doneLatch.countDown();
327+
}
328+
});
329+
}
330+
331+
for (int i = numThreads / 4; i < numThreads; i++) {
332+
executorService.submit(() -> {
333+
try {
334+
startLatch.await();
335+
for (int j = 0; j < operationsPerThread; j++) {
336+
tracker.clear();
337+
Thread.sleep(1);
338+
}
339+
} catch (Exception e) {
340+
errors.incrementAndGet();
341+
firstException.compareAndSet(null, e);
342+
e.printStackTrace();
343+
} finally {
344+
doneLatch.countDown();
345+
}
346+
});
347+
}
348+
349+
for (int i = numThreads / 4; i < numThreads; i++) {
350+
executorService.submit(() -> {
351+
try {
352+
startLatch.await();
353+
for (int j = 0; j < operationsPerThread; j++) {
354+
tracker.getScheduledMessages(1);
355+
Thread.sleep(1);
356+
}
357+
} catch (Exception e) {
358+
errors.incrementAndGet();
359+
firstException.compareAndSet(null, e);
360+
e.printStackTrace();
361+
} finally {
362+
doneLatch.countDown();
363+
}
364+
});
365+
}
366+
367+
startLatch.countDown();
368+
assertTrue(doneLatch.await(30, TimeUnit.SECONDS), "Test should complete within 30 seconds");
369+
370+
if (errors.get() > 0) {
371+
Exception exception = firstException.get();
372+
if (exception != null) {
373+
System.err.println("First exception caught: " + exception.getMessage());
374+
exception.printStackTrace();
375+
}
376+
}
377+
assertEquals(errors.get(), 0, "No exceptions should occur during concurrent operations");
378+
}
277379
}

0 commit comments

Comments
 (0)