Skip to content

Commit 9c59962

Browse files
ARTEMIS-6146 Divert and Mirror causing issues after dropped messages
Assisted-by Claude on implementing PostOfficeImplTest's mock test
1 parent 234fa7d commit 9c59962

8 files changed

Lines changed: 510 additions & 22 deletions

File tree

artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,15 @@ default void clearInternalProperties() {
215215
default void clearAMQPProperties() {
216216
}
217217

218+
default boolean isDropped() {
219+
return false;
220+
}
221+
222+
// Marking the message as dropped
223+
default Message drop() {
224+
return this;
225+
}
226+
218227
/**
219228
* Search for the existence of the property: an implementor can save the message to be decoded, if possible.
220229
*/

artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
117117

118118
private volatile Object owner;
119119

120+
private boolean dropped;
121+
120122
public CoreMessage(final CoreMessageObjectPools coreMessageObjectPools) {
121123
this.coreMessageObjectPools = coreMessageObjectPools;
122124
}
@@ -125,6 +127,17 @@ public CoreMessage() {
125127
this.coreMessageObjectPools = null;
126128
}
127129

130+
@Override
131+
public CoreMessage drop() {
132+
this.dropped = true;
133+
return this;
134+
}
135+
136+
@Override
137+
public boolean isDropped() {
138+
return dropped;
139+
}
140+
128141
@Override
129142
public void setPaged() {
130143
this.paged = true;

artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessage.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ private static void checkCode(int code) {
211211
protected long scheduledTime = -1;
212212
protected byte priority = DEFAULT_MESSAGE_PRIORITY;
213213

214+
protected boolean dropped;
214215
protected boolean isPaged;
215216
protected volatile boolean routed = false;
216217

@@ -219,6 +220,17 @@ public void routed() {
219220
this.routed = true;
220221
}
221222

223+
@Override
224+
public boolean isDropped() {
225+
return dropped;
226+
}
227+
228+
@Override
229+
public AMQPMessage drop() {
230+
this.dropped = true;
231+
return this;
232+
}
233+
222234
// The Proton based AMQP message section that are retained in memory, these are the
223235
// mutable portions of the Message as the broker sees it, although AMQP defines that
224236
// the Properties and ApplicationProperties are immutable so care should be taken

artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagingStoreImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,8 @@ public int page(Message message,
14381438

14391439
if (diskFullMessagePolicy == DiskFullMessagePolicy.DROP || diskFullMessagePolicy == DiskFullMessagePolicy.FAIL) {
14401440
if (diskFull) {
1441+
message.drop();
1442+
14411443
if (message.isLargeMessage()) {
14421444
((LargeServerMessage) message).deleteFile();
14431445
}
@@ -1460,6 +1462,8 @@ public int page(Message message,
14601462

14611463
if (addressFullMessagePolicy == AddressFullMessagePolicy.DROP || addressFullMessagePolicy == AddressFullMessagePolicy.FAIL) {
14621464
if (full) {
1465+
message.drop();
1466+
14631467
if (message.isLargeMessage()) {
14641468
((LargeServerMessage) message).deleteFile();
14651469
}

artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,30 +1725,31 @@ public void processRoute(final Message message,
17251725
}
17261726
}
17271727

1728-
if (mirrorControllerSource != null && !context.isMirrorDisabled()) {
1729-
// we check for isMirrorDisabled as to avoid recursive loop from there
1730-
mirrorControllerSource.sendMessage(tx, message, context);
1731-
}
1732-
1728+
if (!message.isDropped()) {
1729+
if (mirrorControllerSource != null && !context.isMirrorDisabled()) {
1730+
// we check for isMirrorDisabled as to avoid recursive loop from there
1731+
mirrorControllerSource.sendMessage(tx, message, context);
1732+
}
17331733

1734-
if (tx != null) {
1735-
tx.addOperation(new AddOperation(refs));
1736-
} else if (!containsDurables) {
1737-
processReferences(refs, direct);
1738-
} else {
1739-
// This will use the same thread if there are no pending operations
1740-
// avoiding a context switch on this case
1741-
storageManager.afterCompleteOperations(new IOCallback() {
1742-
@Override
1743-
public void onError(final int errorCode, final String errorMessage) {
1744-
ActiveMQServerLogger.LOGGER.ioErrorAddingReferences(errorCode, errorMessage);
1745-
}
1734+
if (tx != null) {
1735+
tx.addOperation(new AddOperation(refs));
1736+
} else if (!containsDurables) {
1737+
processReferences(refs, direct);
1738+
} else {
1739+
// This will use the same thread if there are no pending operations
1740+
// avoiding a context switch on this case
1741+
storageManager.afterCompleteOperations(new IOCallback() {
1742+
@Override
1743+
public void onError(final int errorCode, final String errorMessage) {
1744+
ActiveMQServerLogger.LOGGER.ioErrorAddingReferences(errorCode, errorMessage);
1745+
}
17461746

1747-
@Override
1748-
public void done() {
1749-
processReferences(refs, direct);
1750-
}
1751-
});
1747+
@Override
1748+
public void done() {
1749+
processReferences(refs, direct);
1750+
}
1751+
});
1752+
}
17521753
}
17531754

17541755
if (startedTX) {

artemis-server/src/test/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImplTest.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,33 @@
1616
*/
1717
package org.apache.activemq.artemis.core.postoffice.impl;
1818

19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
1923
import org.apache.activemq.artemis.api.core.Message;
24+
import org.apache.activemq.artemis.api.core.SimpleString;
25+
import org.apache.activemq.artemis.core.paging.PagingManager;
26+
import org.apache.activemq.artemis.core.paging.PagingStore;
27+
import org.apache.activemq.artemis.core.persistence.StorageManager;
28+
import org.apache.activemq.artemis.core.server.Queue;
29+
import org.apache.activemq.artemis.core.server.RouteContextList;
30+
import org.apache.activemq.artemis.core.server.RoutingContext;
31+
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
2032
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
33+
import org.apache.activemq.artemis.core.transaction.Transaction;
2134
import org.junit.jupiter.api.Test;
2235
import org.mockito.ArgumentCaptor;
2336
import org.mockito.Mockito;
2437

2538
import static org.junit.jupiter.api.Assertions.assertNotNull;
2639
import static org.junit.jupiter.api.Assertions.assertTrue;
40+
import static org.mockito.ArgumentMatchers.any;
41+
import static org.mockito.ArgumentMatchers.eq;
42+
import static org.mockito.Mockito.mock;
43+
import static org.mockito.Mockito.never;
44+
import static org.mockito.Mockito.verify;
45+
import static org.mockito.Mockito.when;
2746

2847
public class PostOfficeImplTest {
2948

@@ -305,4 +324,101 @@ public void testPrecedencExpiryDelayOverMinExpiryDelay() {
305324

306325
assertExpirationSetAsExpected(expectedExpirationLow, expectedExpirationHigh, actualExpirationSet);
307326
}
327+
328+
/**
329+
* Tests that mirrorController.sendMessage is NOT called when a message is dropped
330+
* during storageManager.addToPage() call in processRoute().
331+
* This test verifies the fix for ARTEMIS-6146 where dropped messages should not be mirrored.
332+
*
333+
* WITHOUT the fix: This test will FAIL because mirrorController.sendMessage will be called
334+
* WITH the fix: This test will PASS because mirrorController.sendMessage will NOT be called
335+
*/
336+
@Test
337+
public void testProcessRouteDoesNotCallMirrorControllerWhenMessageDropped() throws Exception {
338+
// Setup mocks
339+
StorageManager storageManager = mock(StorageManager.class);
340+
PagingManager pagingManager = mock(PagingManager.class);
341+
PagingStore pagingStore = mock(PagingStore.class);
342+
MirrorController mirrorController = mock(MirrorController.class);
343+
344+
// Create a spy of PostOfficeImpl to call the real processRoute method
345+
PostOfficeImpl postOffice = Mockito.mock(PostOfficeImpl.class);
346+
347+
// Make processRoute call the real implementation
348+
Mockito.doCallRealMethod().when(postOffice).processRoute(any(Message.class), any(RoutingContext.class), Mockito.anyBoolean());
349+
350+
// Mock the getMirrorControlSource to return our mock controller
351+
when(postOffice.getMirrorControlSource()).thenReturn(mirrorController);
352+
353+
SimpleString address = SimpleString.of("testAddress");
354+
355+
// Create a message with dropped tracking
356+
Message message = mock(Message.class);
357+
final boolean[] dropped = {false};
358+
359+
when(message.getAddressSimpleString()).thenReturn(address);
360+
when(message.hasScheduledDeliveryTime()).thenReturn(false);
361+
when(message.isDurable()).thenReturn(false);
362+
when(message.isDropped()).thenAnswer(invocation -> dropped[0]);
363+
when(message.drop()).thenAnswer(invocation -> {
364+
dropped[0] = true;
365+
return message;
366+
});
367+
368+
// Create route context
369+
RoutingContext context = mock(RoutingContext.class);
370+
RouteContextList routeContextList = mock(RouteContextList.class);
371+
Map<SimpleString, RouteContextList> contextListing = new HashMap<>();
372+
contextListing.put(address, routeContextList);
373+
374+
when(context.getContexListing()).thenReturn(contextListing);
375+
when(context.getTransaction()).thenReturn(null);
376+
when(context.isMirrorDisabled()).thenReturn(false);
377+
378+
// Configure route context list to return empty lists (message was paged)
379+
when(routeContextList.getDurableQueues()).thenReturn(List.of());
380+
when(routeContextList.getNonDurableQueues()).thenReturn(List.of());
381+
382+
// Configure paging store
383+
when(pagingStore.getAddress()).thenReturn(address);
384+
when(pagingManager.getPageStore(address)).thenReturn(pagingStore);
385+
386+
// Critical: When addToPage is called, it will drop the message
387+
when(storageManager.addToPage(eq(pagingStore), eq(message), any(), eq(routeContextList)))
388+
.thenAnswer(invocation -> {
389+
// Simulate message.drop() being called inside addToPage
390+
message.drop();
391+
return true; // Returns true indicating the message was added to page
392+
});
393+
394+
// Mock transaction creation for mirror controller
395+
Transaction mockTx = mock(Transaction.class);
396+
when(mockTx.setAsync(true)).thenReturn(mockTx);
397+
when(storageManager.generateID()).thenReturn(1L);
398+
399+
// We need to inject the dependencies into the PostOfficeImpl mock
400+
// Since we can't easily do that, we'll use reflection to set the fields
401+
java.lang.reflect.Field storageManagerField = PostOfficeImpl.class.getDeclaredField("storageManager");
402+
storageManagerField.setAccessible(true);
403+
storageManagerField.set(postOffice, storageManager);
404+
405+
java.lang.reflect.Field pagingManagerField = PostOfficeImpl.class.getDeclaredField("pagingManager");
406+
pagingManagerField.setAccessible(true);
407+
pagingManagerField.set(postOffice, pagingManager);
408+
409+
java.lang.reflect.Field mirrorControllerField = PostOfficeImpl.class.getDeclaredField("mirrorControllerSource");
410+
mirrorControllerField.setAccessible(true);
411+
mirrorControllerField.set(postOffice, mirrorController);
412+
413+
// NOW call the REAL processRoute method
414+
postOffice.processRoute(message, context, false);
415+
416+
// Verify the message was dropped
417+
assertTrue(message.isDropped(), "Message should be marked as dropped after addToPage");
418+
419+
// This is the KEY assertion:
420+
// WITHOUT the fix (isDropped check missing): mirrorController.sendMessage WILL be called -> TEST FAILS
421+
// WITH the fix (isDropped check present): mirrorController.sendMessage will NOT be called -> TEST PASSES
422+
verify(mirrorController, never()).sendMessage(any(), any(), any());
423+
}
308424
}

0 commit comments

Comments
 (0)