From 46ab227f92bfc2dc1b619b5820cac3151fd4e747 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Tue, 15 Oct 2024 16:20:19 +0200 Subject: [PATCH 01/45] Add a ReentrantLock around the busy-wait Add a ReentrantLock around the busy-wait. Given the noted improvements in the axonserver-connector-java concerning the use of the ReentrantLock around instead of using a Thread#yield, we'll try whether it has the same positive effect in the SinksManyWrapper. #enhancement/add-reentrant-lock --- .../queryhandling/SinksManyWrapper.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java index 3d3dc4153e..6f9dd5b56c 100644 --- a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java +++ b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java @@ -19,6 +19,7 @@ import reactor.core.publisher.Sinks; import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; /** @@ -32,6 +33,7 @@ class SinksManyWrapper implements SinkWrapper { private final Sinks.Many fluxSink; + private final ReentrantLock lock; /** * Initializes this wrapper with delegate sink. @@ -40,6 +42,7 @@ class SinksManyWrapper implements SinkWrapper { */ SinksManyWrapper(Sinks.Many fluxSink) { this.fluxSink = fluxSink; + this.lock = new ReentrantLock(); } /** @@ -73,19 +76,24 @@ public void error(Throwable t) { private Sinks.EmitResult performWithBusyWaitSpin(Supplier action) { int i = 0; Sinks.EmitResult result; - while ((result = action.get()) == Sinks.EmitResult.FAIL_NON_SERIALIZED) { - // For 100 iterations, just busy-spin. Will resolve most conditions. - if (i < 100) { - i++; - // Busy spin... - } else if (i < 200) { - // For the next 100 iterations, yield, to force other threads to have a chance. - i++; - Thread.yield(); - } else { - // Then after, park the thread to force other threads to perform their work. - LockSupport.parkNanos(100); + try { + lock.lock(); + while ((result = action.get()) == Sinks.EmitResult.FAIL_NON_SERIALIZED) { + // For 100 iterations, just busy-spin. Will resolve most conditions. + if (i < 100) { + i++; + // Busy spin... + } else if (i < 200) { + // For the next 100 iterations, yield, to force other threads to have a chance. + i++; + Thread.yield(); + } else { + // Then after, park the thread to force other threads to perform their work. + LockSupport.parkNanos(100); + } } + } finally { + lock.unlock(); } return result; } From 468c59aa4806245281b9554d7268c63ec3ceb9bc Mon Sep 17 00:00:00 2001 From: Christian Thiel Date: Thu, 17 Oct 2024 10:24:08 +0200 Subject: [PATCH 02/45] skip axonserver docker tests on ARM64 for older axonserver versions --- .../axonframework/test/server/AxonServerEEContainerTest.java | 3 +++ .../axonframework/test/server/AxonServerSEContainerTest.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/test/src/test/java/org/axonframework/test/server/AxonServerEEContainerTest.java b/test/src/test/java/org/axonframework/test/server/AxonServerEEContainerTest.java index 9aaa7a7a89..7fc69eec9c 100644 --- a/test/src/test/java/org/axonframework/test/server/AxonServerEEContainerTest.java +++ b/test/src/test/java/org/axonframework/test/server/AxonServerEEContainerTest.java @@ -17,6 +17,7 @@ package org.axonframework.test.server; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.condition.DisabledOnOs; import org.testcontainers.utility.DockerImageName; import static org.junit.jupiter.api.Assertions.*; @@ -29,6 +30,7 @@ class AxonServerEEContainerTest { @Test + @DisabledOnOs(architectures = {"aarch64"}) void supportsAxonServer_4_5_X() { try ( final AxonServerEEContainer axonServerEEContainer = @@ -40,6 +42,7 @@ void supportsAxonServer_4_5_X() { } @Test + @DisabledOnOs(architectures = {"aarch64"}) void axonServer_4_5_X_genericTest() { try ( final AxonServerEEContainer axonServerEEContainer = diff --git a/test/src/test/java/org/axonframework/test/server/AxonServerSEContainerTest.java b/test/src/test/java/org/axonframework/test/server/AxonServerSEContainerTest.java index eda3e6eefa..c828c1e6f6 100644 --- a/test/src/test/java/org/axonframework/test/server/AxonServerSEContainerTest.java +++ b/test/src/test/java/org/axonframework/test/server/AxonServerSEContainerTest.java @@ -17,6 +17,7 @@ package org.axonframework.test.server; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.condition.DisabledOnOs; import org.testcontainers.utility.DockerImageName; import static org.junit.jupiter.api.Assertions.*; @@ -29,6 +30,7 @@ class AxonServerSEContainerTest { @Test + @DisabledOnOs(architectures = {"aarch64"}) void supportsAxonServer_4_4_X() { try ( final AxonServerSEContainer axonServerSEContainer = From b5e295485c1ed6d0b2d28565a4cb9c5264f2ca24 Mon Sep 17 00:00:00 2001 From: rsobies Date: Sat, 19 Oct 2024 10:38:47 +0200 Subject: [PATCH 03/45] local query --- .../connector/query/AxonServerQueryBus.java | 31 ++++++++++++++++--- .../queryhandling/SimpleQueryBus.java | 4 +++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 577a95b681..23d4522cb2 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -72,6 +72,7 @@ import org.axonframework.queryhandling.QueryMessage; import org.axonframework.queryhandling.QueryResponseMessage; import org.axonframework.queryhandling.QueryUpdateEmitter; +import org.axonframework.queryhandling.SimpleQueryBus; import org.axonframework.queryhandling.StreamingQueryMessage; import org.axonframework.queryhandling.SubscriptionQueryBackpressure; import org.axonframework.queryhandling.SubscriptionQueryMessage; @@ -249,8 +250,29 @@ public Registration subscribe(@Nonnull String queryName, return new AxonServerRegistration(localRegistration, serverRegistration::cancel); } + private CompletableFuture> tryToRunQueryLocally( + @Nonnull QueryMessage queryMessage) { + + if (localSegment instanceof SimpleQueryBus) { + SimpleQueryBus qb = (SimpleQueryBus) localSegment; + if (qb.hasHandlersForMessage(queryMessage)) { + logger.info("sending query to local segment"); + return localSegment.query(queryMessage); + } + } + + return null; + } + @Override public CompletableFuture> query(@Nonnull QueryMessage queryMessage) { + + CompletableFuture> queryResult = tryToRunQueryLocally(queryMessage); + + if (queryResult != null) { + return queryResult; + } + Span span = spanFactory.createQuerySpan(queryMessage, true).start(); try (SpanScope unused = span.makeCurrent()) { QueryMessage queryWithContext = spanFactory.propagateContext(queryMessage); @@ -285,7 +307,7 @@ public CompletableFuture> query(@Nonnull QueryMes span.recordException(e).end(); } - queryTransaction.whenComplete((r, e) -> { + queryTransaction.whenComplete((r, e) -> { queryInTransit.end(); if (e != null) { span.recordException(e); @@ -400,8 +422,9 @@ public Stream> scatterGather(@Nonnull QueryMessag ShutdownLatch.ActivityHandle queryInTransit = shutdownLatch.registerActivity(); Span span = spanFactory.createScatterGatherSpan(queryMessage, true).start(); - try(SpanScope unused = span.makeCurrent()) { - QueryMessage interceptedQuery = dispatchInterceptors.intercept(spanFactory.propagateContext(queryMessage)); + try (SpanScope unused = span.makeCurrent()) { + QueryMessage interceptedQuery = dispatchInterceptors.intercept(spanFactory.propagateContext( + queryMessage)); long deadline = System.currentTimeMillis() + timeUnit.toMillis(timeout); String targetContext = targetContextResolver.resolveContext(interceptedQuery); QueryRequest queryRequest = @@ -416,7 +439,7 @@ public Stream> scatterGather(@Nonnull QueryMessag AtomicBoolean closed = new AtomicBoolean(false); Runnable closeHandler = () -> { - if(closed.compareAndSet(false, true)) { + if (closed.compareAndSet(false, true)) { queryInTransit.end(); span.end(); } diff --git a/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java b/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java index 1ee3e1cab5..73042bbe16 100644 --- a/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java +++ b/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java @@ -121,6 +121,10 @@ protected SimpleQueryBus(Builder builder) { this.spanFactory = builder.spanFactory; } + public boolean hasHandlersForMessage(QueryMessage query){ + return !getHandlersForMessage(query).isEmpty(); + } + /** * Instantiate a Builder to be able to create a {@link SimpleQueryBus}. *

From 056f2f40489c47c72bce9c8037307da847786c14 Mon Sep 17 00:00:00 2001 From: rsobies Date: Sat, 19 Oct 2024 10:47:12 +0200 Subject: [PATCH 04/45] local query --- .../axonserver/connector/query/AxonServerQueryBus.java | 1 - 1 file changed, 1 deletion(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 23d4522cb2..e95fe5e305 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -256,7 +256,6 @@ private CompletableFuture> tryToRunQueryLocally( if (localSegment instanceof SimpleQueryBus) { SimpleQueryBus qb = (SimpleQueryBus) localSegment; if (qb.hasHandlersForMessage(queryMessage)) { - logger.info("sending query to local segment"); return localSegment.query(queryMessage); } } From cafb52f265021e35c72ed7d0e5481f26621169e7 Mon Sep 17 00:00:00 2001 From: Weronika Trzaska Date: Wed, 2 Oct 2024 22:37:36 +0200 Subject: [PATCH 05/45] Fixed style check (cherry picked from commit f933856a584e94192c34f49c9389fd8baad5e057) --- .../AnnotatedMessageHandlingMemberTest.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/messaging/src/test/java/org/axonframework/messaging/annotation/AnnotatedMessageHandlingMemberTest.java b/messaging/src/test/java/org/axonframework/messaging/annotation/AnnotatedMessageHandlingMemberTest.java index 6c537e2758..8e3123af00 100644 --- a/messaging/src/test/java/org/axonframework/messaging/annotation/AnnotatedMessageHandlingMemberTest.java +++ b/messaging/src/test/java/org/axonframework/messaging/annotation/AnnotatedMessageHandlingMemberTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Axon Framework + * Copyright (c) 2010-2024. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,12 +38,16 @@ class AnnotatedMessageHandlingMemberTest { @BeforeEach void setUp() { - testSubject = new AnnotatedMessageHandlingMember<>( - AnnotatedHandler.class.getMethods()[0], - EventMessage.class, - String.class, - ClasspathParameterResolverFactory.forClass(AnnotatedHandler.class) - ); + try { + testSubject = new AnnotatedMessageHandlingMember<>( + AnnotatedHandler.class.getMethod("handlingMethod", String.class), + EventMessage.class, + String.class, + ClasspathParameterResolverFactory.forClass(AnnotatedHandler.class) + ); + } catch (NoSuchMethodException e) { + fail(e.getMessage()); + } } @Test From 3912c1649ead0861e4aa4d6df0fd2539373ccce6 Mon Sep 17 00:00:00 2001 From: Weronika Trzaska Date: Wed, 16 Oct 2024 21:20:15 -0500 Subject: [PATCH 06/45] Fix messageSerialization test in GenericMessageTest (cherry picked from commit 4bfeb5fc8d282c0fca0614244632ca699d533f91) --- .../messaging/GenericMessageTest.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java index dfbdd0930c..92aa0eca94 100644 --- a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java +++ b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java @@ -27,6 +27,9 @@ import org.axonframework.serialization.json.JacksonSerializer; import org.junit.jupiter.api.*; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -68,7 +71,7 @@ void correlationDataAddedToNewMessage() { } @Test - void messageSerialization() { + void messageSerialization() throws IOException{ GenericMessage message = new GenericMessage<>("payload", Collections.singletonMap("key", "value")); Serializer jacksonSerializer = JacksonSerializer.builder().build(); @@ -76,7 +79,14 @@ void messageSerialization() { SerializedObject serializedMetaData = message.serializeMetaData(jacksonSerializer, String.class); assertEquals("\"payload\"", serializedPayload.getData()); - assertEquals("{\"key\":\"value\",\"foo\":\"bar\"}", serializedMetaData.getData()); + + ObjectMapper objectMapper = new ObjectMapper(); + Map expectedMetaData = new HashMap<>(); + expectedMetaData.put("key", "value"); + expectedMetaData.put("foo", "bar"); + Map actualMetaData = objectMapper.readValue(serializedMetaData.getData(), Map.class); + + assertEquals(expectedMetaData, actualMetaData); } @Test From d47d6dbd355392801d06bdfcca9f27a4bac3943b Mon Sep 17 00:00:00 2001 From: Weronika Trzaska Date: Thu, 17 Oct 2024 07:00:54 -0500 Subject: [PATCH 07/45] Fix test messageSerialization by reusing metadata map (cherry picked from commit 207f148652d2e4df19dcfab553d8715793a28ff8) --- .../messaging/GenericMessageTest.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java index 92aa0eca94..87ef799db7 100644 --- a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java +++ b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java @@ -72,21 +72,23 @@ void correlationDataAddedToNewMessage() { @Test void messageSerialization() throws IOException{ - GenericMessage message = new GenericMessage<>("payload", Collections.singletonMap("key", "value")); - Serializer jacksonSerializer = JacksonSerializer.builder().build(); + Map metaDataMap = Collections.singletonMap("key", "value"); + + GenericMessage message = new GenericMessage<>("payload", metaDataMap); + + JacksonSerializer jacksonSerializer = JacksonSerializer.builder().build(); + SerializedObject serializedPayload = message.serializePayload(jacksonSerializer, String.class); SerializedObject serializedMetaData = message.serializeMetaData(jacksonSerializer, String.class); assertEquals("\"payload\"", serializedPayload.getData()); - ObjectMapper objectMapper = new ObjectMapper(); - Map expectedMetaData = new HashMap<>(); - expectedMetaData.put("key", "value"); - expectedMetaData.put("foo", "bar"); + + ObjectMapper objectMapper = jacksonSerializer.getObjectMapper(); Map actualMetaData = objectMapper.readValue(serializedMetaData.getData(), Map.class); - assertEquals(expectedMetaData, actualMetaData); + assertTrue(actualMetaData.entrySet().containsAll(metaDataMap.entrySet())); } @Test From b09ff13d72a2380742f67415d570cbc8fc8ec444 Mon Sep 17 00:00:00 2001 From: Weronika Trzaska Date: Thu, 17 Oct 2024 04:20:15 +0200 Subject: [PATCH 08/45] Fix messageSerialization test in GenericMessageTest (cherry picked from commit 4bfeb5fc8d282c0fca0614244632ca699d533f91) --- .../messaging/GenericMessageTest.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java index 87ef799db7..19b0c3ba9e 100644 --- a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java +++ b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022. Axon Framework + * Copyright (c) 2010-2024. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,23 +72,21 @@ void correlationDataAddedToNewMessage() { @Test void messageSerialization() throws IOException{ - Map metaDataMap = Collections.singletonMap("key", "value"); - - GenericMessage message = new GenericMessage<>("payload", metaDataMap); - - JacksonSerializer jacksonSerializer = JacksonSerializer.builder().build(); - + GenericMessage message = new GenericMessage<>("payload", Collections.singletonMap("key", "value")); + Serializer jacksonSerializer = JacksonSerializer.builder().build(); SerializedObject serializedPayload = message.serializePayload(jacksonSerializer, String.class); SerializedObject serializedMetaData = message.serializeMetaData(jacksonSerializer, String.class); assertEquals("\"payload\"", serializedPayload.getData()); - - ObjectMapper objectMapper = jacksonSerializer.getObjectMapper(); + ObjectMapper objectMapper = new ObjectMapper(); + Map expectedMetaData = new HashMap<>(); + expectedMetaData.put("key", "value"); + expectedMetaData.put("foo", "bar"); Map actualMetaData = objectMapper.readValue(serializedMetaData.getData(), Map.class); - assertTrue(actualMetaData.entrySet().containsAll(metaDataMap.entrySet())); + assertEquals(expectedMetaData, actualMetaData); } @Test From ca75114e84912d96ae8fbb6fd5cfa23a684b7097 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Mon, 21 Oct 2024 13:23:39 +0200 Subject: [PATCH 09/45] Clean up indentation Clean up indentation #3156 --- .../org/axonframework/messaging/GenericMessageTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java index 19b0c3ba9e..1a3487f498 100644 --- a/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java +++ b/messaging/src/test/java/org/axonframework/messaging/GenericMessageTest.java @@ -16,6 +16,7 @@ package org.axonframework.messaging; +import com.fasterxml.jackson.databind.ObjectMapper; import org.axonframework.eventhandling.GenericEventMessage; import org.axonframework.messaging.correlation.ThrowingCorrelationDataProvider; import org.axonframework.messaging.unitofwork.CurrentUnitOfWork; @@ -27,8 +28,6 @@ import org.axonframework.serialization.json.JacksonSerializer; import org.junit.jupiter.api.*; -import com.fasterxml.jackson.databind.ObjectMapper; - import java.io.IOException; import java.util.Collections; import java.util.HashMap; @@ -71,7 +70,7 @@ void correlationDataAddedToNewMessage() { } @Test - void messageSerialization() throws IOException{ + void messageSerialization() throws IOException { GenericMessage message = new GenericMessage<>("payload", Collections.singletonMap("key", "value")); Serializer jacksonSerializer = JacksonSerializer.builder().build(); @@ -109,7 +108,7 @@ void asMessageWrapsProvidedObjectsInMessage() { } @Test - void whenCorrelationDataProviderThrowsException_thenCatchException(){ + void whenCorrelationDataProviderThrowsException_thenCatchException() { unitOfWork = new DefaultUnitOfWork<>(new GenericEventMessage<>("Input 1")); CurrentUnitOfWork.set(unitOfWork); unitOfWork.registerCorrelationDataProvider(new ThrowingCorrelationDataProvider()); From c752ab9ea1d3abd3212d26eb49fe65f0e6219f24 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Mon, 21 Oct 2024 15:49:46 +0200 Subject: [PATCH 10/45] Revert: Add a ReentrantLock around the busy-wait Reverting the addition of the ReentrantLock to be able to make a clean PR out of this #enhancement/add-reentrant-lock --- .../queryhandling/SinksManyWrapper.java | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java index 6f9dd5b56c..3d3dc4153e 100644 --- a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java +++ b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java @@ -19,7 +19,6 @@ import reactor.core.publisher.Sinks; import java.util.concurrent.locks.LockSupport; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; /** @@ -33,7 +32,6 @@ class SinksManyWrapper implements SinkWrapper { private final Sinks.Many fluxSink; - private final ReentrantLock lock; /** * Initializes this wrapper with delegate sink. @@ -42,7 +40,6 @@ class SinksManyWrapper implements SinkWrapper { */ SinksManyWrapper(Sinks.Many fluxSink) { this.fluxSink = fluxSink; - this.lock = new ReentrantLock(); } /** @@ -76,24 +73,19 @@ public void error(Throwable t) { private Sinks.EmitResult performWithBusyWaitSpin(Supplier action) { int i = 0; Sinks.EmitResult result; - try { - lock.lock(); - while ((result = action.get()) == Sinks.EmitResult.FAIL_NON_SERIALIZED) { - // For 100 iterations, just busy-spin. Will resolve most conditions. - if (i < 100) { - i++; - // Busy spin... - } else if (i < 200) { - // For the next 100 iterations, yield, to force other threads to have a chance. - i++; - Thread.yield(); - } else { - // Then after, park the thread to force other threads to perform their work. - LockSupport.parkNanos(100); - } + while ((result = action.get()) == Sinks.EmitResult.FAIL_NON_SERIALIZED) { + // For 100 iterations, just busy-spin. Will resolve most conditions. + if (i < 100) { + i++; + // Busy spin... + } else if (i < 200) { + // For the next 100 iterations, yield, to force other threads to have a chance. + i++; + Thread.yield(); + } else { + // Then after, park the thread to force other threads to perform their work. + LockSupport.parkNanos(100); } - } finally { - lock.unlock(); } return result; } From 12b14acbdb978b63cb2ea1b782793f7212b7b410 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Tue, 15 Oct 2024 16:20:19 +0200 Subject: [PATCH 11/45] Add a ReentrantLock around the busy-wait Add a ReentrantLock around the busy-wait. Given the noted improvements in the axonserver-connector-java concerning the use of the ReentrantLock around instead of using a Thread#yield, we'll try whether it has the same positive effect in the SinksManyWrapper. #enhancement/add-reentrant-lock --- .../queryhandling/SinksManyWrapper.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java index 3d3dc4153e..6f9dd5b56c 100644 --- a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java +++ b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java @@ -19,6 +19,7 @@ import reactor.core.publisher.Sinks; import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; /** @@ -32,6 +33,7 @@ class SinksManyWrapper implements SinkWrapper { private final Sinks.Many fluxSink; + private final ReentrantLock lock; /** * Initializes this wrapper with delegate sink. @@ -40,6 +42,7 @@ class SinksManyWrapper implements SinkWrapper { */ SinksManyWrapper(Sinks.Many fluxSink) { this.fluxSink = fluxSink; + this.lock = new ReentrantLock(); } /** @@ -73,19 +76,24 @@ public void error(Throwable t) { private Sinks.EmitResult performWithBusyWaitSpin(Supplier action) { int i = 0; Sinks.EmitResult result; - while ((result = action.get()) == Sinks.EmitResult.FAIL_NON_SERIALIZED) { - // For 100 iterations, just busy-spin. Will resolve most conditions. - if (i < 100) { - i++; - // Busy spin... - } else if (i < 200) { - // For the next 100 iterations, yield, to force other threads to have a chance. - i++; - Thread.yield(); - } else { - // Then after, park the thread to force other threads to perform their work. - LockSupport.parkNanos(100); + try { + lock.lock(); + while ((result = action.get()) == Sinks.EmitResult.FAIL_NON_SERIALIZED) { + // For 100 iterations, just busy-spin. Will resolve most conditions. + if (i < 100) { + i++; + // Busy spin... + } else if (i < 200) { + // For the next 100 iterations, yield, to force other threads to have a chance. + i++; + Thread.yield(); + } else { + // Then after, park the thread to force other threads to perform their work. + LockSupport.parkNanos(100); + } } + } finally { + lock.unlock(); } return result; } From 787519b521ae14b0d1ab85bb664f20b6b17e5b87 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Mon, 21 Oct 2024 15:51:50 +0200 Subject: [PATCH 12/45] Add descriptive comment for the existence of the Lock Add descriptive comment for the existence of the Lock #enhancement/add-reentrant-lock --- .../java/org/axonframework/queryhandling/SinksManyWrapper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java index 6f9dd5b56c..bb848c611c 100644 --- a/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java +++ b/messaging/src/main/java/org/axonframework/queryhandling/SinksManyWrapper.java @@ -77,6 +77,7 @@ private Sinks.EmitResult performWithBusyWaitSpin(Supplier acti int i = 0; Sinks.EmitResult result; try { + // The lock's in place to have a safe and efficient way to pause a thread before jumping in the while-loop. lock.lock(); while ((result = action.get()) == Sinks.EmitResult.FAIL_NON_SERIALIZED) { // For 100 iterations, just busy-spin. Will resolve most conditions. From 6e624048be0cc70582e9684a0e565d504db88d4d Mon Sep 17 00:00:00 2001 From: MATTHIAS BECHTOLD Date: Tue, 22 Oct 2024 15:41:45 +0200 Subject: [PATCH 13/45] fix: log statement issue with logback classic --- .../deadletter/jdbc/JdbcSequencedDeadLetterQueue.java | 3 ++- .../deadletter/jpa/JpaSequencedDeadLetterQueue.java | 10 +++++++--- .../legacyjpa/JpaSequencedDeadLetterQueue.java | 2 +- .../deadletter/InMemorySequencedDeadLetterQueue.java | 10 +++++++--- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jdbc/JdbcSequencedDeadLetterQueue.java b/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jdbc/JdbcSequencedDeadLetterQueue.java index c9f6eb6221..54958744f4 100644 --- a/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jdbc/JdbcSequencedDeadLetterQueue.java +++ b/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jdbc/JdbcSequencedDeadLetterQueue.java @@ -203,7 +203,8 @@ public void enqueue(@Nonnull Object sequenceIdentifier, Optional optionalCause = letter.cause(); if (optionalCause.isPresent()) { logger.info("Adding dead letter with message id [{}] because [{}].", - letter.message().getIdentifier(), optionalCause.get()); + letter.message().getIdentifier(), + optionalCause.get().type()); } else { logger.info( "Adding dead letter with message id [{}] because the sequence identifier [{}] is already present.", diff --git a/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jpa/JpaSequencedDeadLetterQueue.java b/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jpa/JpaSequencedDeadLetterQueue.java index db75a956d2..b9324cdc6e 100644 --- a/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jpa/JpaSequencedDeadLetterQueue.java +++ b/messaging/src/main/java/org/axonframework/eventhandling/deadletter/jpa/JpaSequencedDeadLetterQueue.java @@ -138,10 +138,14 @@ public void enqueue(@Nonnull Object sequenceIdentifier, @Nonnull DeadLetter optionalCause = letter.cause(); if (optionalCause.isPresent()) { - logger.info("Adding dead letter with message id [{}] because [{}].", letter.message().getIdentifier(), optionalCause.get()); + logger.info("Adding dead letter with message id [{}] because [{}].", + letter.message().getIdentifier(), + optionalCause.get().type()); } else { - logger.info("Adding dead letter with message id [{}] because the sequence identifier [{}] is already present.", - letter.message().getIdentifier(), stringSequenceIdentifier); + logger.info( + "Adding dead letter with message id [{}] because the sequence identifier [{}] is already present.", + letter.message().getIdentifier(), + stringSequenceIdentifier); } DeadLetterEventEntry entry = converters diff --git a/messaging/src/main/java/org/axonframework/eventhandling/deadletter/legacyjpa/JpaSequencedDeadLetterQueue.java b/messaging/src/main/java/org/axonframework/eventhandling/deadletter/legacyjpa/JpaSequencedDeadLetterQueue.java index c5705a2dcd..d18c1f5211 100644 --- a/messaging/src/main/java/org/axonframework/eventhandling/deadletter/legacyjpa/JpaSequencedDeadLetterQueue.java +++ b/messaging/src/main/java/org/axonframework/eventhandling/deadletter/legacyjpa/JpaSequencedDeadLetterQueue.java @@ -147,7 +147,7 @@ public void enqueue(@Nonnull Object sequenceIdentifier, @Nonnull DeadLetter optionalCause = letter.cause(); if (optionalCause.isPresent()) { - logger.debug("Adding dead letter with message id [{}] because [{}].", letter.message().getIdentifier(), optionalCause.get()); + logger.debug("Adding dead letter with message id [{}] because [{}].", + letter.message().getIdentifier(), + optionalCause.get().type()); } else { - logger.debug("Adding dead letter with message id [{}] because the sequence identifier [{}] is already present.", - letter.message().getIdentifier(), sequenceIdentifier); + logger.debug( + "Adding dead letter with message id [{}] because the sequence identifier [{}] is already present.", + letter.message().getIdentifier(), + sequenceIdentifier); } } From 95bf4d9a7dd7bf57204d854c6b603c464d80e834 Mon Sep 17 00:00:00 2001 From: rsobies Date: Wed, 23 Oct 2024 12:53:35 +0200 Subject: [PATCH 14/45] local query --- .../connector/query/AxonServerQueryBus.java | 30 +++++++++++++++---- .../queryhandling/SimpleQueryBus.java | 21 ++++++------- .../AxonServerBusAutoConfiguration.java | 2 +- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index e95fe5e305..7d32aebe9f 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -96,10 +96,12 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Spliterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -152,6 +154,9 @@ public class AxonServerQueryBus implements QueryBus, Distributed, Life private final String context; private final QueryBusSpanFactory spanFactory; + private final Set queryHandlersNames = new CopyOnWriteArraySet<>(); + private final boolean localSegmentShortCut; + /** * Instantiate a {@link AxonServerQueryBus} based on the fields contained in the {@link Builder}. * @@ -175,6 +180,7 @@ public AxonServerQueryBus(Builder builder) { PriorityBlockingQueue queryProcessQueue = new PriorityBlockingQueue<>(QUERY_QUEUE_CAPACITY); queryExecutor = builder.executorServiceBuilder.apply(configuration, queryProcessQueue); localSegmentAdapter = new LocalSegmentAdapter(); + this.localSegmentShortCut = builder.localSegmentShortCut; } @Override @@ -247,19 +253,25 @@ public Registration subscribe(@Nonnull String queryName, .queryChannel() .registerQueryHandler(localSegmentAdapter, queryDefinition); - return new AxonServerRegistration(localRegistration, serverRegistration::cancel); + queryHandlersNames.add(queryName); + return new AxonServerRegistration(() -> unsubscribe(queryName, localRegistration), serverRegistration::cancel); + } + + private boolean unsubscribe(String queryName, Registration localSegmentRegistration) { + boolean result = localSegmentRegistration.cancel(); + if (result) { + queryHandlersNames.remove(queryName); + } + return result; } private CompletableFuture> tryToRunQueryLocally( @Nonnull QueryMessage queryMessage) { - - if (localSegment instanceof SimpleQueryBus) { - SimpleQueryBus qb = (SimpleQueryBus) localSegment; - if (qb.hasHandlersForMessage(queryMessage)) { + if(localSegmentShortCut){ + if(queryHandlersNames.contains(queryMessage.getQueryName())){ return localSegment.query(queryMessage); } } - return null; } @@ -589,6 +601,7 @@ public static class Builder { private QueryBusSpanFactory spanFactory = DefaultQueryBusSpanFactory.builder() .spanFactory(NoOpSpanFactory.INSTANCE) .build(); + private boolean localSegmentShortCut; /** * Sets the {@link AxonServerConnectionManager} used to create connections between this application and an Axon @@ -630,6 +643,11 @@ public Builder localSegment(QueryBus localSegment) { return this; } + public Builder localSegmentShortCut(boolean localSegmentShortCut){ + this.localSegmentShortCut = localSegmentShortCut; + return this; + } + /** * Sets the {@link QueryUpdateEmitter} which can be used to emit updates to queries. Required to honor the * {@link QueryBus#queryUpdateEmitter()} contract. diff --git a/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java b/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java index 73042bbe16..8892b76040 100644 --- a/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java +++ b/messaging/src/main/java/org/axonframework/queryhandling/SimpleQueryBus.java @@ -15,7 +15,6 @@ */ package org.axonframework.queryhandling; -import org.axonframework.commandhandling.CommandBusSpanFactory; import org.axonframework.common.Assert; import org.axonframework.common.AxonConfigurationException; import org.axonframework.common.Registration; @@ -121,10 +120,6 @@ protected SimpleQueryBus(Builder builder) { this.spanFactory = builder.spanFactory; } - public boolean hasHandlersForMessage(QueryMessage query){ - return !getHandlersForMessage(query).isEmpty(); - } - /** * Instantiate a Builder to be able to create a {@link SimpleQueryBus}. *

@@ -478,7 +473,8 @@ public SubscriptionQueryResult, SubscriptionQu Mono> initialResult = Mono.fromFuture(() -> query(query)) .doOnError(error -> logger.error( "An error happened while trying to report an initial result. Query: {}", - query, error)); + query, + error)); UpdateHandlerRegistration updateHandlerRegistration = queryUpdateEmitter.registerUpdateHandler(query, backpressure, updateBufferSize); @@ -499,7 +495,8 @@ public SubscriptionQueryResult, SubscriptionQu Mono> initialResult = Mono.fromFuture(() -> query(query)) .doOnError(error -> logger.error( "An error happened while trying to report an initial result. Query: {}", - query, error)); + query, + error)); UpdateHandlerRegistration updateHandlerRegistration = queryUpdateEmitter.registerUpdateHandler(query, updateBufferSize); @@ -655,8 +652,8 @@ Registration registerDispatchInterceptor( *

* The {@link MessageMonitor} is defaulted to {@link NoOpMessageMonitor}, {@link TransactionManager} to * {@link NoTransactionManager}, {@link QueryInvocationErrorHandler} to {@link LoggingQueryInvocationErrorHandler}, - * the {@link QueryUpdateEmitter} to {@link SimpleQueryUpdateEmitter} and the {@link QueryBusSpanFactory} defaults to a - * {@link DefaultQueryBusSpanFactory} backed by a {@link NoOpSpanFactory}. + * the {@link QueryUpdateEmitter} to {@link SimpleQueryUpdateEmitter} and the {@link QueryBusSpanFactory} defaults + * to a {@link DefaultQueryBusSpanFactory} backed by a {@link NoOpSpanFactory}. */ public static class Builder { @@ -667,7 +664,11 @@ public static class Builder { .build(); private DuplicateQueryHandlerResolver duplicateQueryHandlerResolver = DuplicateQueryHandlerResolution.logAndAccept(); private QueryUpdateEmitter queryUpdateEmitter = SimpleQueryUpdateEmitter.builder() - .spanFactory(DefaultQueryUpdateEmitterSpanFactory.builder().spanFactory(NoOpSpanFactory.INSTANCE).build()) + .spanFactory( + DefaultQueryUpdateEmitterSpanFactory.builder() + .spanFactory( + NoOpSpanFactory.INSTANCE) + .build()) .build(); private QueryBusSpanFactory spanFactory = DefaultQueryBusSpanFactory.builder() .spanFactory(NoOpSpanFactory.INSTANCE) diff --git a/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java index ef57967bb8..c8c3f9b91a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java @@ -43,7 +43,6 @@ import org.axonframework.queryhandling.SimpleQueryBus; import org.axonframework.serialization.Serializer; import org.axonframework.springboot.util.ConditionalOnMissingQualifiedBean; -import org.axonframework.tracing.SpanFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -126,6 +125,7 @@ public AxonServerQueryBus queryBus(AxonServerConnectionManager axonServerConnect .priorityCalculator(priorityCalculator) .targetContextResolver(targetContextResolver) .spanFactory(axonConfiguration.getComponent(QueryBusSpanFactory.class)) + .localSegmentShortCut(true) .build(); } From dbde22d91f8f86c51101437c3e828fb3d096c32d Mon Sep 17 00:00:00 2001 From: rsobies Date: Wed, 23 Oct 2024 12:58:41 +0200 Subject: [PATCH 15/45] local query --- .../connector/query/AxonServerQueryBus.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 7d32aebe9f..5a1386321b 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -72,7 +72,6 @@ import org.axonframework.queryhandling.QueryMessage; import org.axonframework.queryhandling.QueryResponseMessage; import org.axonframework.queryhandling.QueryUpdateEmitter; -import org.axonframework.queryhandling.SimpleQueryBus; import org.axonframework.queryhandling.StreamingQueryMessage; import org.axonframework.queryhandling.SubscriptionQueryBackpressure; import org.axonframework.queryhandling.SubscriptionQueryMessage; @@ -265,12 +264,14 @@ private boolean unsubscribe(String queryName, Registration localSegmentRegistrat return result; } + private boolean shouldRunQueryLocally(QueryMessage queryMessage) { + return localSegmentShortCut && queryHandlersNames.contains(queryMessage.getQueryName()); + } + private CompletableFuture> tryToRunQueryLocally( @Nonnull QueryMessage queryMessage) { - if(localSegmentShortCut){ - if(queryHandlersNames.contains(queryMessage.getQueryName())){ - return localSegment.query(queryMessage); - } + if (shouldRunQueryLocally(queryMessage)) { + return localSegment.query(queryMessage); } return null; } @@ -643,7 +644,7 @@ public Builder localSegment(QueryBus localSegment) { return this; } - public Builder localSegmentShortCut(boolean localSegmentShortCut){ + public Builder localSegmentShortCut(boolean localSegmentShortCut) { this.localSegmentShortCut = localSegmentShortCut; return this; } From 1f922c39adee7602fe107989d7f83c8aeb78318d Mon Sep 17 00:00:00 2001 From: rsobies Date: Wed, 23 Oct 2024 13:06:42 +0200 Subject: [PATCH 16/45] local query --- .../axonserver/connector/query/AxonServerQueryBus.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 5a1386321b..9e4175237b 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -153,7 +153,7 @@ public class AxonServerQueryBus implements QueryBus, Distributed, Life private final String context; private final QueryBusSpanFactory spanFactory; - private final Set queryHandlersNames = new CopyOnWriteArraySet<>(); + private final Set queryHandlerNames = new CopyOnWriteArraySet<>(); private final boolean localSegmentShortCut; /** @@ -252,20 +252,20 @@ public Registration subscribe(@Nonnull String queryName, .queryChannel() .registerQueryHandler(localSegmentAdapter, queryDefinition); - queryHandlersNames.add(queryName); + queryHandlerNames.add(queryName); return new AxonServerRegistration(() -> unsubscribe(queryName, localRegistration), serverRegistration::cancel); } private boolean unsubscribe(String queryName, Registration localSegmentRegistration) { boolean result = localSegmentRegistration.cancel(); if (result) { - queryHandlersNames.remove(queryName); + queryHandlerNames.remove(queryName); } return result; } private boolean shouldRunQueryLocally(QueryMessage queryMessage) { - return localSegmentShortCut && queryHandlersNames.contains(queryMessage.getQueryName()); + return localSegmentShortCut && queryHandlerNames.contains(queryMessage.getQueryName()); } private CompletableFuture> tryToRunQueryLocally( From 3ea584e13d8eef201670733437af79ea047cf249 Mon Sep 17 00:00:00 2001 From: rsobies Date: Sat, 26 Oct 2024 08:29:31 +0200 Subject: [PATCH 17/45] local query --- .../connector/query/AxonServerQueryBus.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 9e4175237b..1676138a46 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -182,8 +182,21 @@ public AxonServerQueryBus(Builder builder) { this.localSegmentShortCut = builder.localSegmentShortCut; } + private Publisher> tryToRunStreamingQueryLocally(StreamingQueryMessage query) { + if (shouldRunQueryLocally(query.getQueryName())) { + return localSegment.streamingQuery(query); + } + return null; + } + @Override public Publisher> streamingQuery(StreamingQueryMessage query) { + + Publisher> queryResult = tryToRunStreamingQueryLocally(query); + if (queryResult != null) { + return queryResult; + } + Span span = spanFactory.createStreamingQuerySpan(query, true).start(); try (SpanScope unused = span.makeCurrent()) { StreamingQueryMessage queryWithContext = spanFactory.propagateContext(query); @@ -264,13 +277,13 @@ private boolean unsubscribe(String queryName, Registration localSegmentRegistrat return result; } - private boolean shouldRunQueryLocally(QueryMessage queryMessage) { - return localSegmentShortCut && queryHandlerNames.contains(queryMessage.getQueryName()); + private boolean shouldRunQueryLocally(String queryName) { + return localSegmentShortCut && queryHandlerNames.contains(queryName); } private CompletableFuture> tryToRunQueryLocally( @Nonnull QueryMessage queryMessage) { - if (shouldRunQueryLocally(queryMessage)) { + if (shouldRunQueryLocally(queryMessage.getQueryName())) { return localSegment.query(queryMessage); } return null; From abbf277e01731a9f292f4621e5b292457dd9f0ce Mon Sep 17 00:00:00 2001 From: rsobies Date: Mon, 28 Oct 2024 12:49:44 +0100 Subject: [PATCH 18/45] local query --- .../connector/AxonServerConfiguration.java | 52 +++++++------ .../connector/query/AxonServerQueryBus.java | 9 ++- .../query/AxonServerQueryBusTest.java | 75 ++++++++++++++++++- .../AxonServerBusAutoConfiguration.java | 28 ++++--- 4 files changed, 123 insertions(+), 41 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java index 150abbfd09..a80f356887 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java @@ -151,6 +151,8 @@ public class AxonServerConfiguration { */ private FlowControlConfiguration commandFlowControl; + private boolean localSegmentShortCut = false; + /** * The number of threads executing commands. Defaults to {@code 10} threads. */ @@ -234,9 +236,9 @@ public class AxonServerConfiguration { /** * Flag that allows block-listing of event types to be enabled. *

- * Disabling this may have serious performance impact, as it requires all - * {@link EventMessage events} from Axon Server to be sent to clients, even if a - * client is unable to process the event. Default is to have block-listing enabled. + * Disabling this may have serious performance impact, as it requires all {@link EventMessage events} from Axon + * Server to be sent to clients, even if a client is unable to process the event. Default is to have block-listing + * enabled. */ private boolean eventBlockListingEnabled = true; @@ -298,8 +300,7 @@ public class AxonServerConfiguration { /** * Defines the number of threads that should be used for connection management activities by the - * {@link AxonServerConnectionFactory} used by the - * {@link AxonServerConnectionManager}. + * {@link AxonServerConnectionFactory} used by the {@link AxonServerConnectionManager}. *

* This includes activities related to connecting to Axon Server, setting up instruction streams, sending and * validating heartbeats, etc. @@ -320,8 +321,7 @@ public class AxonServerConfiguration { private Eventhandling eventhandling = new Eventhandling(); /** - * Properties describing the settings for the - * {@link AxonServerEventStore EventStore}. + * Properties describing the settings for the {@link AxonServerEventStore EventStore}. */ private EventStoreConfiguration eventStoreConfiguration = new EventStoreConfiguration(); @@ -358,6 +358,14 @@ public boolean isEnabled() { return enabled; } + public boolean getLocalSegmentShortCut() { + return localSegmentShortCut; + } + + public void setLocalSegmentShortCut(boolean localSegmentShortCut) { + this.localSegmentShortCut = localSegmentShortCut; + } + /** * Set whether (automatic) configuration of the Axon Server Connector is enabled. When {@code false}, the connector * will not be implicitly be configured. Defaults to {@code true}. @@ -945,9 +953,9 @@ public void setDisableEventBlacklisting(boolean disableEventBlacklisting) { /** * Flag that allows block-listing of event types to be enabled. *

- * Disabling this may have serious performance impact, as it requires all - * {@link EventMessage events} from Axon Server to be sent to clients, even if a - * client is unable to process the event. Default is to have block-listing enabled. + * Disabling this may have serious performance impact, as it requires all {@link EventMessage events} from Axon + * Server to be sent to clients, even if a client is unable to process the event. Default is to have block-listing + * enabled. * * @return Flag that allows block-listing of event types to be enabled. */ @@ -958,9 +966,9 @@ public boolean isEventBlockListingEnabled() { /** * Sets flag that allows block-listing of event types to be enabled. *

- * Disabling this may have serious performance impact, as it requires all - * {@link EventMessage events} from Axon Server to be sent to clients, even if a - * client is unable to process the event. Default is to have block-listing enabled. + * Disabling this may have serious performance impact, as it requires all {@link EventMessage events} from Axon + * Server to be sent to clients, even if a client is unable to process the event. Default is to have block-listing + * enabled. * * @param eventBlockListingEnabled Flag that allows block-listing of event types to be enabled. */ @@ -1128,8 +1136,7 @@ public void setForceReconnectThroughServers(boolean forceReconnectThroughServers /** * The number of threads that should be used for connection management activities by the - * {@link AxonServerConnectionFactory} used by the - * {@link AxonServerConnectionManager}. + * {@link AxonServerConnectionFactory} used by the {@link AxonServerConnectionManager}. *

* This includes activities related to connecting to Axon Server, setting up instruction streams, sending and * validating heartbeats, etc. @@ -1137,8 +1144,7 @@ public void setForceReconnectThroughServers(boolean forceReconnectThroughServers * Defaults to a pool size of {@code 2} threads. * * @return The number of threads that should be used for connection management activities by the - * {@link AxonServerConnectionFactory} used by the - * {@link AxonServerConnectionManager}. + * {@link AxonServerConnectionFactory} used by the {@link AxonServerConnectionManager}. */ public int getConnectionManagementThreadPoolSize() { return connectionManagementThreadPoolSize; @@ -1146,8 +1152,7 @@ public int getConnectionManagementThreadPoolSize() { /** * Define the number of threads that should be used for connection management activities by the - * {@link AxonServerConnectionFactory} used by the - * {@link AxonServerConnectionManager}. + * {@link AxonServerConnectionFactory} used by the {@link AxonServerConnectionManager}. *

* This includes activities related to connecting to Axon Server, setting up instruction streams, sending and * validating heartbeats, etc. @@ -1155,9 +1160,8 @@ public int getConnectionManagementThreadPoolSize() { * Defaults to a pool size of {@code 2} threads. * * @param connectionManagementThreadPoolSize The number of threads that should be used for connection management - * activities by the - * {@link AxonServerConnectionFactory} used - * by the {@link AxonServerConnectionManager}. + * activities by the {@link AxonServerConnectionFactory} used by the + * {@link AxonServerConnectionManager}. */ public void setConnectionManagementThreadPoolSize(int connectionManagementThreadPoolSize) { this.connectionManagementThreadPoolSize = connectionManagementThreadPoolSize; @@ -1490,8 +1494,8 @@ public static class ProcessorSettings { * The load balancing strategy tells Axon Server how to share the event handling load among all available * application instances running this event processor, by moving segments from one instance to another. Note * that load balancing is only supported for - * {@link StreamingEventProcessor StreamingEventProcessors}, as only - * {@code StreamingEventProcessors} are capable of splitting the event handling load in segments. + * {@link StreamingEventProcessor StreamingEventProcessors}, as only {@code StreamingEventProcessors} are + * capable of splitting the event handling load in segments. *

* As the strategies names may change per Axon Server version it is recommended to check the documentation * for the possible strategies. diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 1676138a46..c1b5081c73 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -258,6 +258,7 @@ public void start() { public Registration subscribe(@Nonnull String queryName, @Nonnull Type responseType, @Nonnull MessageHandler> handler) { + logger.info("subscribing new query " + queryName); Registration localRegistration = localSegment.subscribe(queryName, responseType, handler); QueryDefinition queryDefinition = new QueryDefinition(queryName, responseType); io.axoniq.axonserver.connector.Registration serverRegistration = @@ -284,6 +285,7 @@ private boolean shouldRunQueryLocally(String queryName) { private CompletableFuture> tryToRunQueryLocally( @Nonnull QueryMessage queryMessage) { if (shouldRunQueryLocally(queryMessage.getQueryName())) { + logger.info("running query locally"); return localSegment.query(queryMessage); } return null; @@ -291,7 +293,7 @@ private CompletableFuture> tryToRunQueryLocally( @Override public CompletableFuture> query(@Nonnull QueryMessage queryMessage) { - + logger.info("running query " + queryMessage.getQueryName()); CompletableFuture> queryResult = tryToRunQueryLocally(queryMessage); if (queryResult != null) { @@ -505,6 +507,7 @@ public SubscriptionQueryResult, SubscriptionQu @Nonnull SubscriptionQueryMessage query, int updateBufferSize ) { + Assert.isFalse(Publisher.class.isAssignableFrom(query.getResponseType().getExpectedResponseType()), () -> "The subscription Query query does not support Flux as a return type."); Assert.isFalse(Publisher.class.isAssignableFrom(query.getUpdateResponseType().getExpectedResponseType()), @@ -657,8 +660,8 @@ public Builder localSegment(QueryBus localSegment) { return this; } - public Builder localSegmentShortCut(boolean localSegmentShortCut) { - this.localSegmentShortCut = localSegmentShortCut; + public Builder enabledLocalSegmentShortCut() { + this.localSegmentShortCut = true; return this; } diff --git a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java index 872de0097b..0c8eb69c80 100644 --- a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java +++ b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java @@ -196,10 +196,80 @@ void query() throws Exception { assertEquals("test", testSubject.query(testQuery).get().getPayload()); verify(targetContextResolver).resolveContext(testQuery); + verify(localSegment, never()).query(testQuery); spanFactory.verifySpanCompleted("QueryBus.queryDistributed"); spanFactory.verifySpanPropagated("QueryBus.queryDistributed", testQuery); } + @Nested + class LocalSegmentShortCutEnabled { + + private Registration registration; + private final QueryMessage testQuery = new GenericQueryMessage<>("Hello, World", + TEST_QUERY, + instanceOf(String.class)); + + private final StreamingQueryMessage testStreamingQuery = + new GenericStreamingQueryMessage<>("Hello, World", TEST_QUERY, String.class); + + @BeforeEach + void setUp() { + testSubject = AxonServerQueryBus.builder() + .axonServerConnectionManager(axonServerConnectionManager) + .configuration(configuration) + .localSegment(localSegment) + .updateEmitter(SimpleQueryUpdateEmitter.builder().build()) + .messageSerializer(serializer) + .genericSerializer(serializer) + .targetContextResolver(targetContextResolver) + .enabledLocalSegmentShortCut() + .spanFactory( + DefaultQueryBusSpanFactory.builder() + .spanFactory(spanFactory) + .build() + ) + .build(); + + registration = testSubject.subscribe(TEST_QUERY, String.class, q -> "test"); + } + + @Test + void queryWhenLocalHandlerIsPresent() { + when(localSegment.query(testQuery)).thenReturn(CompletableFuture.completedFuture(new GenericQueryResponseMessage<>( + "ok"))); + + testSubject.query(testQuery); + + verify(localSegment).query(testQuery); + verify(mockQueryChannel, never()).query(any()); + } + + @Test + void queryWhenRegistrationIsCancel() { + registration.cancel(); + + testSubject.query(testQuery); + + verify(localSegment, never()).query(testQuery); + } + + @Test + void streamingQueryWhenLocalHandlerIsPresent() { + testSubject.streamingQuery(testStreamingQuery); + + verify(localSegment).streamingQuery(testStreamingQuery); + verify(mockQueryChannel, never()).query(any()); + } + + @Test + void streamingQueryWhenRegistrationIsCancel() { + registration.cancel(); + testSubject.streamingQuery(testStreamingQuery); + + verify(localSegment, never()).streamingQuery(testStreamingQuery); + } + } + @Test void queryReportsDispatchException() throws Exception { //noinspection rawtypes @@ -338,8 +408,8 @@ void scatterGatherCloseStreamDoesNotThrowExceptionOnCloseMethod() { stubResponse("3"))); Stream> stream = testSubject.scatterGather(testQuery, - 12, - TimeUnit.SECONDS); + 12, + TimeUnit.SECONDS); assertEquals(3, stream.count()); stream.close(); } @@ -362,6 +432,7 @@ void streamingFluxQuery() { .verifyComplete(); verify(targetContextResolver).resolveContext(testQuery); + verify(localSegment, never()).streamingQuery(testQuery); //noinspection resource verify(mockQueryChannel).query(argThat( r -> r.getPayload().getData().toStringUtf8().equals("Hello, World") diff --git a/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java index c8c3f9b91a..3d66605c5f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java @@ -115,18 +115,22 @@ public AxonServerQueryBus queryBus(AxonServerConnectionManager axonServerConnect new CorrelationDataInterceptor<>(axonConfiguration.correlationDataProviders()) ); - return AxonServerQueryBus.builder() - .axonServerConnectionManager(axonServerConnectionManager) - .configuration(axonServerConfiguration) - .localSegment(simpleQueryBus) - .updateEmitter(simpleQueryBus.queryUpdateEmitter()) - .messageSerializer(messageSerializer) - .genericSerializer(genericSerializer) - .priorityCalculator(priorityCalculator) - .targetContextResolver(targetContextResolver) - .spanFactory(axonConfiguration.getComponent(QueryBusSpanFactory.class)) - .localSegmentShortCut(true) - .build(); + AxonServerQueryBus.Builder axonQueryBuilder = AxonServerQueryBus.builder() + .axonServerConnectionManager( + axonServerConnectionManager) + .configuration(axonServerConfiguration) + .localSegment(simpleQueryBus) + .updateEmitter(simpleQueryBus.queryUpdateEmitter()) + .messageSerializer(messageSerializer) + .genericSerializer(genericSerializer) + .priorityCalculator(priorityCalculator) + .targetContextResolver(targetContextResolver) + .spanFactory(axonConfiguration.getComponent( + QueryBusSpanFactory.class)); + if (axonServerConfiguration.getLocalSegmentShortCut()) { + axonQueryBuilder.enabledLocalSegmentShortCut(); + } + return axonQueryBuilder.build(); } @Bean From 048047b288736d70a51ab4f59216aebdc30a8f57 Mon Sep 17 00:00:00 2001 From: rsobies Date: Mon, 28 Oct 2024 14:55:48 +0100 Subject: [PATCH 19/45] local query --- .../connector/query/AxonServerQueryBus.java | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index c1b5081c73..ff87ce5cf9 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -182,19 +182,11 @@ public AxonServerQueryBus(Builder builder) { this.localSegmentShortCut = builder.localSegmentShortCut; } - private Publisher> tryToRunStreamingQueryLocally(StreamingQueryMessage query) { - if (shouldRunQueryLocally(query.getQueryName())) { - return localSegment.streamingQuery(query); - } - return null; - } - @Override public Publisher> streamingQuery(StreamingQueryMessage query) { - Publisher> queryResult = tryToRunStreamingQueryLocally(query); - if (queryResult != null) { - return queryResult; + if (shouldRunQueryLocally(query.getQueryName())) { + return localSegment.streamingQuery(query); } Span span = spanFactory.createStreamingQuerySpan(query, true).start(); @@ -258,7 +250,6 @@ public void start() { public Registration subscribe(@Nonnull String queryName, @Nonnull Type responseType, @Nonnull MessageHandler> handler) { - logger.info("subscribing new query " + queryName); Registration localRegistration = localSegment.subscribe(queryName, responseType, handler); QueryDefinition queryDefinition = new QueryDefinition(queryName, responseType); io.axoniq.axonserver.connector.Registration serverRegistration = @@ -282,22 +273,11 @@ private boolean shouldRunQueryLocally(String queryName) { return localSegmentShortCut && queryHandlerNames.contains(queryName); } - private CompletableFuture> tryToRunQueryLocally( - @Nonnull QueryMessage queryMessage) { - if (shouldRunQueryLocally(queryMessage.getQueryName())) { - logger.info("running query locally"); - return localSegment.query(queryMessage); - } - return null; - } - @Override public CompletableFuture> query(@Nonnull QueryMessage queryMessage) { - logger.info("running query " + queryMessage.getQueryName()); - CompletableFuture> queryResult = tryToRunQueryLocally(queryMessage); - if (queryResult != null) { - return queryResult; + if (shouldRunQueryLocally(queryMessage.getQueryName())) { + return localSegment.query(queryMessage); } Span span = spanFactory.createQuerySpan(queryMessage, true).start(); From d92d5a7839d7b648ea66a6510c814a6ae9753edd Mon Sep 17 00:00:00 2001 From: rsobies Date: Tue, 29 Oct 2024 09:30:45 +0100 Subject: [PATCH 20/45] local query --- .../connector/query/AxonServerQueryBus.java | 77 ++++++++++--------- .../query/AxonServerQueryBusTest.java | 8 +- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index ff87ce5cf9..b1202fd713 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -185,26 +185,27 @@ public AxonServerQueryBus(Builder builder) { @Override public Publisher> streamingQuery(StreamingQueryMessage query) { - if (shouldRunQueryLocally(query.getQueryName())) { - return localSegment.streamingQuery(query); - } - Span span = spanFactory.createStreamingQuerySpan(query, true).start(); try (SpanScope unused = span.makeCurrent()) { StreamingQueryMessage queryWithContext = spanFactory.propagateContext(query); int priority = priorityCalculator.determinePriority(queryWithContext); + AtomicReference scheduler = new AtomicReference<>(PriorityTaskSchedulers.forPriority( queryExecutor, priority, TASK_SEQUENCE)); return Mono.fromSupplier(this::registerStreamingQueryActivity).flatMapMany( activity -> Mono.just(dispatchInterceptors.intercept(queryWithContext)) - .flatMapMany(intercepted -> - Mono.just(serializeStreaming(intercepted, priority)) - .flatMapMany(queryRequest -> new ResultStreamPublisher<>( - () -> sendRequest(intercepted, queryRequest))) - .concatMap(queryResponse -> deserialize(intercepted, - queryResponse)) + .flatMapMany(intercepted -> { + if (shouldRunQueryLocally(intercepted.getQueryName())) { + return localSegment.streamingQuery(intercepted); + } + return Mono.just(serializeStreaming(intercepted, priority)) + .flatMapMany(queryRequest -> new ResultStreamPublisher<>( + () -> sendRequest(intercepted, queryRequest))) + .concatMap(queryResponse -> deserialize(intercepted, + queryResponse)); + } ) .publishOn(scheduler.get()) .doOnError(span::recordException) @@ -276,10 +277,6 @@ private boolean shouldRunQueryLocally(String queryName) { @Override public CompletableFuture> query(@Nonnull QueryMessage queryMessage) { - if (shouldRunQueryLocally(queryMessage.getQueryName())) { - return localSegment.query(queryMessage); - } - Span span = spanFactory.createQuerySpan(queryMessage, true).start(); try (SpanScope unused = span.makeCurrent()) { QueryMessage queryWithContext = spanFactory.propagateContext(queryMessage); @@ -288,30 +285,37 @@ public CompletableFuture> query(@Nonnull QueryMes shutdownLatch.ifShuttingDown("Cannot dispatch new queries as this bus is being shut down"); QueryMessage interceptedQuery = dispatchInterceptors.intercept(queryWithContext); + + CompletableFuture> queryTransaction = new CompletableFuture<>(); //noinspection resource ShutdownLatch.ActivityHandle queryInTransit = shutdownLatch.registerActivity(); - CompletableFuture> queryTransaction = new CompletableFuture<>(); - try { - int priority = priorityCalculator.determinePriority(interceptedQuery); - QueryRequest queryRequest = serialize(interceptedQuery, false, priority); - ResultStream result = sendRequest(interceptedQuery, queryRequest); - queryTransaction.whenComplete((r, e) -> result.close()); - Span responseTaskSpan = spanFactory.createResponseProcessingSpan(interceptedQuery); - Runnable responseProcessingTask = new ResponseProcessingTask<>(result, - serializer, - queryTransaction, - queryMessage.getResponseType(), - responseTaskSpan); - - result.onAvailable(() -> queryExecutor.execute(new PriorityRunnable( - responseProcessingTask, - priority, - TASK_SEQUENCE.incrementAndGet()))); - } catch (Exception e) { - logger.debug("There was a problem issuing a query {}.", interceptedQuery, e); - AxonException exception = ErrorCode.QUERY_DISPATCH_ERROR.convert(configuration.getClientId(), e); - queryTransaction.completeExceptionally(exception); - span.recordException(e).end(); + + if (shouldRunQueryLocally(interceptedQuery.getQueryName())) { + queryTransaction = localSegment.query(interceptedQuery); + } else { + + try { + int priority = priorityCalculator.determinePriority(interceptedQuery); + QueryRequest queryRequest = serialize(interceptedQuery, false, priority); + ResultStream result = sendRequest(interceptedQuery, queryRequest); + queryTransaction.whenComplete((r, e) -> result.close()); + Span responseTaskSpan = spanFactory.createResponseProcessingSpan(interceptedQuery); + Runnable responseProcessingTask = new ResponseProcessingTask<>(result, + serializer, + queryTransaction, + queryMessage.getResponseType(), + responseTaskSpan); + + result.onAvailable(() -> queryExecutor.execute(new PriorityRunnable( + responseProcessingTask, + priority, + TASK_SEQUENCE.incrementAndGet()))); + } catch (Exception e) { + logger.debug("There was a problem issuing a query {}.", interceptedQuery, e); + AxonException exception = ErrorCode.QUERY_DISPATCH_ERROR.convert(configuration.getClientId(), e); + queryTransaction.completeExceptionally(exception); + span.recordException(e).end(); + } } queryTransaction.whenComplete((r, e) -> { @@ -978,6 +982,7 @@ public void handle(QueryRequest query, ReplyChannel responseHandl @Override public FlowControl stream(QueryRequest query, ReplyChannel responseHandler) { + logger.info("stream query " + Thread.currentThread()); Runnable onClose = () -> queriesInProgress.remove(query.getMessageIdentifier()); CloseAwareReplyChannel closeAwareReplyChannel = new CloseAwareReplyChannel<>(responseHandler, onClose); diff --git a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java index 0c8eb69c80..2649bb0f73 100644 --- a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java +++ b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java @@ -255,7 +255,13 @@ void queryWhenRegistrationIsCancel() { @Test void streamingQueryWhenLocalHandlerIsPresent() { - testSubject.streamingQuery(testStreamingQuery); + when(localSegment.streamingQuery(testStreamingQuery)).thenReturn(Flux.fromIterable(List.of(new GenericQueryResponseMessage<>( + "ok")))); + + StepVerifier.create(Flux.from(testSubject.streamingQuery(testStreamingQuery)) + .map(Message::getPayload)) + .expectNext("ok") + .verifyComplete(); verify(localSegment).streamingQuery(testStreamingQuery); verify(mockQueryChannel, never()).query(any()); From 39033f51143d7a2866f61080fe3dbef27b3d44cb Mon Sep 17 00:00:00 2001 From: rsobies Date: Tue, 29 Oct 2024 14:44:49 +0100 Subject: [PATCH 21/45] local query --- .../connector/query/AxonServerQueryBus.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index b1202fd713..9698e1b48e 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -184,12 +184,10 @@ public AxonServerQueryBus(Builder builder) { @Override public Publisher> streamingQuery(StreamingQueryMessage query) { - Span span = spanFactory.createStreamingQuerySpan(query, true).start(); try (SpanScope unused = span.makeCurrent()) { StreamingQueryMessage queryWithContext = spanFactory.propagateContext(query); int priority = priorityCalculator.determinePriority(queryWithContext); - AtomicReference scheduler = new AtomicReference<>(PriorityTaskSchedulers.forPriority( queryExecutor, priority, @@ -285,15 +283,12 @@ public CompletableFuture> query(@Nonnull QueryMes shutdownLatch.ifShuttingDown("Cannot dispatch new queries as this bus is being shut down"); QueryMessage interceptedQuery = dispatchInterceptors.intercept(queryWithContext); - - CompletableFuture> queryTransaction = new CompletableFuture<>(); //noinspection resource ShutdownLatch.ActivityHandle queryInTransit = shutdownLatch.registerActivity(); - + CompletableFuture> queryTransaction = new CompletableFuture<>(); if (shouldRunQueryLocally(interceptedQuery.getQueryName())) { queryTransaction = localSegment.query(interceptedQuery); } else { - try { int priority = priorityCalculator.determinePriority(interceptedQuery); QueryRequest queryRequest = serialize(interceptedQuery, false, priority); @@ -491,7 +486,6 @@ public SubscriptionQueryResult, SubscriptionQu @Nonnull SubscriptionQueryMessage query, int updateBufferSize ) { - Assert.isFalse(Publisher.class.isAssignableFrom(query.getResponseType().getExpectedResponseType()), () -> "The subscription Query query does not support Flux as a return type."); Assert.isFalse(Publisher.class.isAssignableFrom(query.getUpdateResponseType().getExpectedResponseType()), @@ -644,6 +638,12 @@ public Builder localSegment(QueryBus localSegment) { return this; } + /** + * Enables shortcut to local {@link QueryBus}. If query handlers are registered in the local environment they + * will be invoked directly instead of sending request to axon server. + * + * @return the current Builder instance, for fluent interfacing + */ public Builder enabledLocalSegmentShortCut() { this.localSegmentShortCut = true; return this; @@ -982,7 +982,6 @@ public void handle(QueryRequest query, ReplyChannel responseHandl @Override public FlowControl stream(QueryRequest query, ReplyChannel responseHandler) { - logger.info("stream query " + Thread.currentThread()); Runnable onClose = () -> queriesInProgress.remove(query.getMessageIdentifier()); CloseAwareReplyChannel closeAwareReplyChannel = new CloseAwareReplyChannel<>(responseHandler, onClose); From 1d310d280578f24ff053ad86657248cca1af4264 Mon Sep 17 00:00:00 2001 From: rsobies Date: Tue, 29 Oct 2024 14:53:31 +0100 Subject: [PATCH 22/45] local query --- .../axonserver/connector/AxonServerConfiguration.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java index a80f356887..240c921dd8 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java @@ -151,6 +151,10 @@ public class AxonServerConfiguration { */ private FlowControlConfiguration commandFlowControl; + /** + * A toggle dictating whether to invoke query handlers directly if they are registered in the local environment. + * Defaults to {@code false} + */ private boolean localSegmentShortCut = false; /** From f4889d0875aeb209a248f282dfcec7d769c99871 Mon Sep 17 00:00:00 2001 From: rsobies Date: Wed, 30 Oct 2024 13:44:50 +0100 Subject: [PATCH 23/45] local query --- .../connector/query/AxonServerQueryBus.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 9698e1b48e..5a2c07306d 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -286,10 +286,10 @@ public CompletableFuture> query(@Nonnull QueryMes //noinspection resource ShutdownLatch.ActivityHandle queryInTransit = shutdownLatch.registerActivity(); CompletableFuture> queryTransaction = new CompletableFuture<>(); - if (shouldRunQueryLocally(interceptedQuery.getQueryName())) { - queryTransaction = localSegment.query(interceptedQuery); - } else { - try { + try { + if (shouldRunQueryLocally(interceptedQuery.getQueryName())) { + queryTransaction = localSegment.query(interceptedQuery); + } else { int priority = priorityCalculator.determinePriority(interceptedQuery); QueryRequest queryRequest = serialize(interceptedQuery, false, priority); ResultStream result = sendRequest(interceptedQuery, queryRequest); @@ -305,12 +305,12 @@ public CompletableFuture> query(@Nonnull QueryMes responseProcessingTask, priority, TASK_SEQUENCE.incrementAndGet()))); - } catch (Exception e) { - logger.debug("There was a problem issuing a query {}.", interceptedQuery, e); - AxonException exception = ErrorCode.QUERY_DISPATCH_ERROR.convert(configuration.getClientId(), e); - queryTransaction.completeExceptionally(exception); - span.recordException(e).end(); } + } catch (Exception e) { + logger.debug("There was a problem issuing a query {}.", interceptedQuery, e); + AxonException exception = ErrorCode.QUERY_DISPATCH_ERROR.convert(configuration.getClientId(), e); + queryTransaction.completeExceptionally(exception); + span.recordException(e).end(); } queryTransaction.whenComplete((r, e) -> { From d4d894cceb9ac37afbcc933476147b71ec1a2727 Mon Sep 17 00:00:00 2001 From: rsobies Date: Fri, 1 Nov 2024 11:50:15 +0100 Subject: [PATCH 24/45] Update axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java Co-authored-by: Steven van Beelen --- .../axonserver/connector/AxonServerConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java index 240c921dd8..4ca020259e 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java @@ -153,7 +153,7 @@ public class AxonServerConfiguration { /** * A toggle dictating whether to invoke query handlers directly if they are registered in the local environment. - * Defaults to {@code false} + * Defaults to {@code false}. */ private boolean localSegmentShortCut = false; From 3c3a0adb0d3a1e741e7be53c935af2f0802d813e Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 1 Nov 2024 13:54:23 +0100 Subject: [PATCH 25/45] Add test case showing problem scenario Add test case showing problem scenario that a polymorphic aggregate with a creation policy on the child fails to be instantiated. #3171 --- .../aggregate/FixtureTest_CreationPolicy.java | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/org/axonframework/test/aggregate/FixtureTest_CreationPolicy.java b/test/src/test/java/org/axonframework/test/aggregate/FixtureTest_CreationPolicy.java index 1d5fa2e022..31d9ac6cf8 100644 --- a/test/src/test/java/org/axonframework/test/aggregate/FixtureTest_CreationPolicy.java +++ b/test/src/test/java/org/axonframework/test/aggregate/FixtureTest_CreationPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022. Axon Framework + * Copyright (c) 2010-2024. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -176,6 +176,26 @@ void whenProtectedConstructorCombinedWithAlwaysPolicyThenAggregateWorksAsExpecte .expectSuccessfulHandlerExecution(); } + @Test + void whenPolymorphicAggregateWithUniquelyNamedCreateIfMissingPolicyOnChildThenWorksAsExpected() { + new AggregateTestFixture<>(TestAggregateParentForPolymorphicCase.class) + .withSubtypes(TestAggregateChildForPolymorphicCase.class) + .givenNoPriorActivity() + .when(new CreateOrUpdateCommand(AGGREGATE_ID, PUBLISH_EVENTS)) + .expectEvents(new CreatedOrUpdatedEvent(AGGREGATE_ID)) + .expectSuccessfulHandlerExecution(); + } + + @Test + void whenPolymorphicAggregateWithUniquelyNamedAlwaysPolicyOnChildThenWorksAsExpected() { + new AggregateTestFixture<>(TestAggregateParentForPolymorphicCase.class) + .withSubtypes(TestAggregateChildForPolymorphicCase.class) + .givenNoPriorActivity() + .when(new AlwaysCreateWithoutResultCommand(AGGREGATE_ID, PUBLISH_EVENTS)) + .expectEvents(new AlwaysCreatedEvent(AGGREGATE_ID)) + .expectSuccessfulHandlerExecution(); + } + private static class CreateCommand { @TargetAggregateIdentifier @@ -552,6 +572,49 @@ public void on(AlwaysCreatedEvent event) { } } + public static abstract class TestAggregateParentForPolymorphicCase { + + @AggregateIdentifier + protected ComplexAggregateId id; + + public TestAggregateParentForPolymorphicCase() { + // Constructor made public on purpose for testing. + } + } + + public static class TestAggregateChildForPolymorphicCase extends TestAggregateParentForPolymorphicCase { + + public TestAggregateChildForPolymorphicCase() { + // Constructor made public on purpose for testing. + } + + @CommandHandler + @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING) + public void handle(CreateOrUpdateCommand command) { + if (command.shouldPublishEvent()) { + apply(new CreatedOrUpdatedEvent(command.getId())); + } + } + + @CommandHandler + @CreationPolicy(AggregateCreationPolicy.ALWAYS) + public void handle(AlwaysCreateWithoutResultCommand command) { + if (command.shouldPublishEvent()) { + apply(new AlwaysCreatedEvent(command.getId())); + } + } + + @EventSourcingHandler + private void on(CreatedOrUpdatedEvent event) { + this.id = event.getId(); + } + + @EventSourcingHandler + public void on(AlwaysCreatedEvent event) { + this.id = event.getId(); + } + } + /** * Test identifier introduced for issue #1356. From dea3d4e27f4b7c0e41a1eb12ad6913afdea9778c Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 1 Nov 2024 14:33:24 +0100 Subject: [PATCH 26/45] Set CreationPolicyAggregateFactory for every type. Set CreationPolicyAggregateFactory for every type. By doing so, the command handler constructed for the creation policy annotated methods can pick the CreationPolicyAggregateFactory instance for the declared class it resides on, ensuring the right object is instantiated. #3171 --- .../AggregateAnnotationCommandHandler.java | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java index e44692a121..4215810105 100644 --- a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java +++ b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java @@ -72,7 +72,7 @@ public class AggregateAnnotationCommandHandler implements CommandMessageHandl private final List>> handlers; private final Set supportedCommandNames; private final Map>>> supportedCommandsByName; - private final CreationPolicyAggregateFactory creationPolicyAggregateFactory; + private final Map, CreationPolicyAggregateFactory> factoryPerType; /** * Instantiate a Builder to be able to create a {@link AggregateAnnotationCommandHandler}. @@ -111,16 +111,30 @@ protected AggregateAnnotationCommandHandler(Builder builder) { this.supportedCommandNames = new HashSet<>(); this.supportedCommandsByName = new HashMap<>(); AggregateModel aggregateModel = builder.buildAggregateModel(); - this.creationPolicyAggregateFactory = - initializeAggregateFactory(aggregateModel.entityClass(), builder.creationPolicyAggregateFactory); + // Suppressing cast to Class as we are definitely dealing with implementations of T. + //noinspection unchecked + this.factoryPerType = initializeAggregateFactories( + aggregateModel.types() + .map(type -> (Class) type) + .collect(Collectors.toList()), + builder.creationPolicyAggregateFactory + ); + this.handlers = initializeHandlers(aggregateModel); } - private CreationPolicyAggregateFactory initializeAggregateFactory(Class aggregateClass, - CreationPolicyAggregateFactory configuredAggregateFactory) { - return configuredAggregateFactory != null - ? configuredAggregateFactory - : new NoArgumentConstructorCreationPolicyAggregateFactory<>(aggregateClass); + private Map, CreationPolicyAggregateFactory> initializeAggregateFactories( + List> aggregateTypes, + CreationPolicyAggregateFactory configuredAggregateFactory + ) { + Map, CreationPolicyAggregateFactory> typeToFactory = new HashMap<>(); + for (Class aggregateType : aggregateTypes) { + typeToFactory.put(aggregateType, configuredAggregateFactory != null + ? configuredAggregateFactory + : new NoArgumentConstructorCreationPolicyAggregateFactory<>(aggregateType) + ); + } + return typeToFactory; } /** @@ -189,12 +203,14 @@ private void initializeHandler(AggregateModel aggregateModel, } else { switch (policy.orElse(NEVER)) { case ALWAYS: - messageHandler = - new AlwaysCreateAggregateCommandHandler(handler, creationPolicyAggregateFactory); + messageHandler = new AlwaysCreateAggregateCommandHandler( + handler, factoryPerType.get(handler.declaringClass()) + ); break; case CREATE_IF_MISSING: - messageHandler = - new AggregateCreateOrUpdateCommandHandler(handler, creationPolicyAggregateFactory); + messageHandler = new AggregateCreateOrUpdateCommandHandler( + handler, factoryPerType.get(handler.declaringClass()) + ); break; case NEVER: messageHandler = new AggregateCommandHandler(handler); From 9e96a91d2d35a2bcbab3e279a796089605e83eed Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 1 Nov 2024 14:33:49 +0100 Subject: [PATCH 27/45] Update documentation to include note about polymorphic aggregates Update documentation to include note about polymorphic aggregates #3171 --- .../axonframework/config/AggregateConfigurer.java | 12 +++++++----- .../command/AggregateAnnotationCommandHandler.java | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/config/src/main/java/org/axonframework/config/AggregateConfigurer.java b/config/src/main/java/org/axonframework/config/AggregateConfigurer.java index 9f1e153305..0e3d6fdc49 100644 --- a/config/src/main/java/org/axonframework/config/AggregateConfigurer.java +++ b/config/src/main/java/org/axonframework/config/AggregateConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Axon Framework + * Copyright (c) 2010-2024. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -312,12 +312,14 @@ public AggregateConfigurer configureAggregateFactory( } /** - * Defines the factory to create new Aggregates instances of the type under configuration when initializing those - * instances from non constructor Command handlers annotated with + * Defines the factory to create new aggregate instances of the type under configuration when initializing those + * instances from non-constructor command handlers annotated with * {@link org.axonframework.modelling.command.CreationPolicy}. * - * @param creationPolicyAggregateFactoryBuilder The builder function for the CreationPolicyAggregateFactory - * @return this configurer instance for chaining + * When {@link #withSubtypes(Class[]) subtypes} are provided, the given {@link CreationPolicyAggregateFactory} is used for every implementation of the aggregate under construction. + * + * @param creationPolicyAggregateFactoryBuilder The builder function for the {@link CreationPolicyAggregateFactory}. + * @return This configurer instance for chaining. */ public AggregateConfigurer configureCreationPolicyAggregateFactory( Function> creationPolicyAggregateFactoryBuilder diff --git a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java index 4215810105..19ff4c2963 100644 --- a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java +++ b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022. Axon Framework + * Copyright (c) 2010-2024. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -367,11 +367,13 @@ public Builder aggregateModel(AggregateModel aggregateModel) { } /** - * Sets the {@link CreationPolicyAggregateFactory} for generic type {@code T}. The aggregate factory must - * produce a new instance of the Aggregate root based on the supplied Identifier. + * Sets the {@link CreationPolicyAggregateFactory} for generic type {@code T}. * - * @param creationPolicyAggregateFactory that returns the aggregate instance based on the identifier - * @return the current Builder instance, for fluent interfacing + * The aggregate factory must + * produce a new instance of the aggregate root based on the supplied identifier. When dealing with a polymorphic aggregate, the given {@code creationPolicyAggregateFactory} will be used for every {@link AggregateModel#types() type}. + * + * @param creationPolicyAggregateFactory The {@link CreationPolicyAggregateFactory} the constructs an aggregate instance based on an identifier. + * @return The current Builder instance, for fluent interfacing. */ public Builder creationPolicyAggregateFactory( CreationPolicyAggregateFactory creationPolicyAggregateFactory From ab7b14f6fbdc65ae64688f5eaa01efc578c60c0f Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 1 Nov 2024 14:44:34 +0100 Subject: [PATCH 28/45] Fix indentation Fix indentation #3171 --- .../config/AggregateConfigurer.java | 30 +++++-------------- .../AggregateAnnotationCommandHandler.java | 27 +++++------------ 2 files changed, 16 insertions(+), 41 deletions(-) diff --git a/config/src/main/java/org/axonframework/config/AggregateConfigurer.java b/config/src/main/java/org/axonframework/config/AggregateConfigurer.java index 0e3d6fdc49..accdbffae4 100644 --- a/config/src/main/java/org/axonframework/config/AggregateConfigurer.java +++ b/config/src/main/java/org/axonframework/config/AggregateConfigurer.java @@ -28,35 +28,19 @@ import org.axonframework.common.lock.PessimisticLockFactory; import org.axonframework.disruptor.commandhandling.DisruptorCommandBus; import org.axonframework.eventhandling.DomainEventMessage; -import org.axonframework.eventsourcing.AggregateFactory; -import org.axonframework.eventsourcing.EventSourcingRepository; -import org.axonframework.eventsourcing.GenericAggregateFactory; -import org.axonframework.eventsourcing.NoSnapshotTriggerDefinition; -import org.axonframework.eventsourcing.SnapshotTriggerDefinition; +import org.axonframework.eventsourcing.*; import org.axonframework.eventsourcing.eventstore.EventStore; import org.axonframework.eventsourcing.snapshotting.RevisionSnapshotFilter; import org.axonframework.eventsourcing.snapshotting.SnapshotFilter; import org.axonframework.lifecycle.Phase; import org.axonframework.messaging.Distributed; -import org.axonframework.modelling.command.AggregateAnnotationCommandHandler; -import org.axonframework.modelling.command.AnnotationCommandTargetResolver; -import org.axonframework.modelling.command.CommandTargetResolver; -import org.axonframework.modelling.command.CreationPolicyAggregateFactory; -import org.axonframework.modelling.command.GenericJpaRepository; -import org.axonframework.modelling.command.NoArgumentConstructorCreationPolicyAggregateFactory; -import org.axonframework.modelling.command.Repository; -import org.axonframework.modelling.command.RepositorySpanFactory; +import org.axonframework.modelling.command.*; import org.axonframework.modelling.command.inspection.AggregateMetaModelFactory; import org.axonframework.modelling.command.inspection.AggregateModel; import org.axonframework.modelling.command.inspection.AnnotatedAggregateMetaModelFactory; import org.axonframework.serialization.Revision; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; @@ -315,10 +299,12 @@ public AggregateConfigurer configureAggregateFactory( * Defines the factory to create new aggregate instances of the type under configuration when initializing those * instances from non-constructor command handlers annotated with * {@link org.axonframework.modelling.command.CreationPolicy}. + *

+ * When {@link #withSubtypes(Class[]) subtypes} are provided, the given {@link CreationPolicyAggregateFactory} is + * used for every implementation of the aggregate under construction. * - * When {@link #withSubtypes(Class[]) subtypes} are provided, the given {@link CreationPolicyAggregateFactory} is used for every implementation of the aggregate under construction. - * - * @param creationPolicyAggregateFactoryBuilder The builder function for the {@link CreationPolicyAggregateFactory}. + * @param creationPolicyAggregateFactoryBuilder The builder function for the + * {@link CreationPolicyAggregateFactory}. * @return This configurer instance for chaining. */ public AggregateConfigurer configureCreationPolicyAggregateFactory( diff --git a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java index 19ff4c2963..6cf584b835 100644 --- a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java +++ b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java @@ -16,13 +16,7 @@ package org.axonframework.modelling.command; -import org.axonframework.commandhandling.AnnotationCommandHandlerAdapter; -import org.axonframework.commandhandling.CommandBus; -import org.axonframework.commandhandling.CommandHandler; -import org.axonframework.commandhandling.CommandMessage; -import org.axonframework.commandhandling.CommandMessageHandler; -import org.axonframework.commandhandling.CommandMessageHandlingMember; -import org.axonframework.commandhandling.NoHandlerForCommandException; +import org.axonframework.commandhandling.*; import org.axonframework.common.AxonConfigurationException; import org.axonframework.common.ReflectionUtils; import org.axonframework.common.Registration; @@ -37,14 +31,7 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -368,11 +355,13 @@ public Builder aggregateModel(AggregateModel aggregateModel) { /** * Sets the {@link CreationPolicyAggregateFactory} for generic type {@code T}. + *

+ * The aggregate factory must produce a new instance of the aggregate root based on the supplied identifier. + * When dealing with a polymorphic aggregate, the given {@code creationPolicyAggregateFactory} will be used for + * every {@link AggregateModel#types() type}. * - * The aggregate factory must - * produce a new instance of the aggregate root based on the supplied identifier. When dealing with a polymorphic aggregate, the given {@code creationPolicyAggregateFactory} will be used for every {@link AggregateModel#types() type}. - * - * @param creationPolicyAggregateFactory The {@link CreationPolicyAggregateFactory} the constructs an aggregate instance based on an identifier. + * @param creationPolicyAggregateFactory The {@link CreationPolicyAggregateFactory} the constructs an aggregate + * instance based on an identifier. * @return The current Builder instance, for fluent interfacing. */ public Builder creationPolicyAggregateFactory( From d410d2ca9430fab4178f8efb5d41ec57b990a07c Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 1 Nov 2024 14:46:17 +0100 Subject: [PATCH 29/45] Revert unintended import changes Revert unintended import changes #3171 --- .../config/AggregateConfigurer.java | 22 ++++++++++++++++--- .../AggregateAnnotationCommandHandler.java | 17 ++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/config/src/main/java/org/axonframework/config/AggregateConfigurer.java b/config/src/main/java/org/axonframework/config/AggregateConfigurer.java index accdbffae4..3552db04e6 100644 --- a/config/src/main/java/org/axonframework/config/AggregateConfigurer.java +++ b/config/src/main/java/org/axonframework/config/AggregateConfigurer.java @@ -28,19 +28,35 @@ import org.axonframework.common.lock.PessimisticLockFactory; import org.axonframework.disruptor.commandhandling.DisruptorCommandBus; import org.axonframework.eventhandling.DomainEventMessage; -import org.axonframework.eventsourcing.*; +import org.axonframework.eventsourcing.AggregateFactory; +import org.axonframework.eventsourcing.EventSourcingRepository; +import org.axonframework.eventsourcing.GenericAggregateFactory; +import org.axonframework.eventsourcing.NoSnapshotTriggerDefinition; +import org.axonframework.eventsourcing.SnapshotTriggerDefinition; import org.axonframework.eventsourcing.eventstore.EventStore; import org.axonframework.eventsourcing.snapshotting.RevisionSnapshotFilter; import org.axonframework.eventsourcing.snapshotting.SnapshotFilter; import org.axonframework.lifecycle.Phase; import org.axonframework.messaging.Distributed; -import org.axonframework.modelling.command.*; +import org.axonframework.modelling.command.AggregateAnnotationCommandHandler; +import org.axonframework.modelling.command.AnnotationCommandTargetResolver; +import org.axonframework.modelling.command.CommandTargetResolver; +import org.axonframework.modelling.command.CreationPolicyAggregateFactory; +import org.axonframework.modelling.command.GenericJpaRepository; +import org.axonframework.modelling.command.NoArgumentConstructorCreationPolicyAggregateFactory; +import org.axonframework.modelling.command.Repository; +import org.axonframework.modelling.command.RepositorySpanFactory; import org.axonframework.modelling.command.inspection.AggregateMetaModelFactory; import org.axonframework.modelling.command.inspection.AggregateModel; import org.axonframework.modelling.command.inspection.AnnotatedAggregateMetaModelFactory; import org.axonframework.serialization.Revision; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; diff --git a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java index 6cf584b835..72d80460ac 100644 --- a/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java +++ b/modelling/src/main/java/org/axonframework/modelling/command/AggregateAnnotationCommandHandler.java @@ -16,7 +16,13 @@ package org.axonframework.modelling.command; -import org.axonframework.commandhandling.*; +import org.axonframework.commandhandling.AnnotationCommandHandlerAdapter; +import org.axonframework.commandhandling.CommandBus; +import org.axonframework.commandhandling.CommandHandler; +import org.axonframework.commandhandling.CommandMessage; +import org.axonframework.commandhandling.CommandMessageHandler; +import org.axonframework.commandhandling.CommandMessageHandlingMember; +import org.axonframework.commandhandling.NoHandlerForCommandException; import org.axonframework.common.AxonConfigurationException; import org.axonframework.common.ReflectionUtils; import org.axonframework.common.Registration; @@ -31,7 +37,14 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; From 30f6ec9c0c6ea45ce344b91594e718d38883f200 Mon Sep 17 00:00:00 2001 From: rsobies Date: Mon, 4 Nov 2024 09:47:31 +0100 Subject: [PATCH 30/45] local query --- .../axonserver/connector/AxonServerConfiguration.java | 2 +- .../springboot/autoconfig/AxonServerBusAutoConfiguration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java index 4ca020259e..ecde19ec8e 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java @@ -362,7 +362,7 @@ public boolean isEnabled() { return enabled; } - public boolean getLocalSegmentShortCut() { + public boolean isShortcutQueriesToLocalHandlers() { return localSegmentShortCut; } diff --git a/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java index 3d66605c5f..581bed1e92 100644 --- a/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/axonframework/springboot/autoconfig/AxonServerBusAutoConfiguration.java @@ -127,7 +127,7 @@ public AxonServerQueryBus queryBus(AxonServerConnectionManager axonServerConnect .targetContextResolver(targetContextResolver) .spanFactory(axonConfiguration.getComponent( QueryBusSpanFactory.class)); - if (axonServerConfiguration.getLocalSegmentShortCut()) { + if (axonServerConfiguration.isShortcutQueriesToLocalHandlers()) { axonQueryBuilder.enabledLocalSegmentShortCut(); } return axonQueryBuilder.build(); From 7dd324d20a01ee51b9a4572e4bab578b50b63e52 Mon Sep 17 00:00:00 2001 From: rsobies Date: Mon, 4 Nov 2024 13:47:52 +0100 Subject: [PATCH 31/45] Update axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java Co-authored-by: Steven van Beelen --- .../axonserver/connector/AxonServerConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java index ecde19ec8e..6e074c2604 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java @@ -366,7 +366,7 @@ public boolean isShortcutQueriesToLocalHandlers() { return localSegmentShortCut; } - public void setLocalSegmentShortCut(boolean localSegmentShortCut) { + public void setShortcutQueriesToLocalHandlers(boolean shortcutQueriesToLocalHandlers) { this.localSegmentShortCut = localSegmentShortCut; } From 74ebae2cdf40833baaeb51fcbbbe577a2181d575 Mon Sep 17 00:00:00 2001 From: rsobies Date: Mon, 4 Nov 2024 14:49:07 +0100 Subject: [PATCH 32/45] local query --- .../axonserver/connector/AxonServerConfiguration.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java index 6e074c2604..83bd218215 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/AxonServerConfiguration.java @@ -155,7 +155,7 @@ public class AxonServerConfiguration { * A toggle dictating whether to invoke query handlers directly if they are registered in the local environment. * Defaults to {@code false}. */ - private boolean localSegmentShortCut = false; + private boolean shortcutQueriesToLocalHandlers = false; /** * The number of threads executing commands. Defaults to {@code 10} threads. @@ -363,11 +363,11 @@ public boolean isEnabled() { } public boolean isShortcutQueriesToLocalHandlers() { - return localSegmentShortCut; + return shortcutQueriesToLocalHandlers; } public void setShortcutQueriesToLocalHandlers(boolean shortcutQueriesToLocalHandlers) { - this.localSegmentShortCut = localSegmentShortCut; + this.shortcutQueriesToLocalHandlers = shortcutQueriesToLocalHandlers; } /** From 938c1579c908163280e3ed36791f3abc654736b9 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Wed, 6 Nov 2024 13:31:31 +0100 Subject: [PATCH 33/45] Add QueryProcessingTask#resultPending Add QueryProcessingTask#resultPending. This method will allow the AxonServerQueryBus to await the tasks for a configurable amount of time before they return. #bug/gracefully-await-queries-in-progress --- .../connector/query/QueryProcessingTask.java | 13 ++++++- .../QueryProcessingTaskIntegrationTest.java | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/QueryProcessingTask.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/QueryProcessingTask.java index b37ae816cb..544d0a0e6a 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/QueryProcessingTask.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/QueryProcessingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Axon Framework + * Copyright (c) 2010-2024. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -184,6 +184,17 @@ public void cancel() { } } + /** + * Returns {@code true} if this task is still waiting for a result, and {@code false} otherwise. + *

+ * Note that this would this return {@code true}, even if the streamable result has not been canceled yet! + * + * @return {@code true} if this task is still waiting for a result, and {@code false} otherwise. + */ + public boolean resultPending() { + return streamableResultRef.get() == null; + } + private void streamingQuery(QueryMessage originalQueryMessage) { // noinspection unchecked StreamingQueryMessage streamingQueryMessage = new GenericStreamingQueryMessage<>( diff --git a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java index 4fa846b64e..9f2b17d20c 100644 --- a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java +++ b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java @@ -691,6 +691,42 @@ void listStreamingQueryWhenRequestingTooMany() { assertTrue(responseHandler.completed()); } + @Test + void responsePendingReturnsTrueForUncompletedTask() { + QueryMessage> testQuery = + new GenericQueryMessage<>(new FluxQuery(1000), ResponseTypes.publisherOf(String.class)); + QueryRequest testRequest = querySerializer.serializeRequest(testQuery, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1); + QueryProcessingTask testSubject = new QueryProcessingTask(localSegment, + testRequest, + responseHandler, + querySerializer, + CLIENT_ID, + queryBusSpanFactory); + + assertTrue(testSubject.resultPending()); + } + + @Test + void responsePendingReturnsFalseForCompletedTask() { + QueryMessage> testQuery = + new GenericQueryMessage<>(new FluxQuery(1), ResponseTypes.publisherOf(String.class)); + QueryRequest testRequest = querySerializer.serializeRequest(testQuery, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1); + QueryProcessingTask testSubject = new QueryProcessingTask(localSegment, + testRequest, + responseHandler, + querySerializer, + CLIENT_ID, + queryBusSpanFactory); + + assertTrue(testSubject.resultPending()); + testSubject.run(); + testSubject.request(1); + assertEquals(1, responseHandler.sent().size()); + assertOrder(responseHandler.sent().get(0)); + assertTrue(responseHandler.completed()); + assertFalse(testSubject.resultPending()); + } + private void assertOrder(List responses) { for (int i = 0; i < responses.size(); i++) { QueryResponseMessage responseMessage = From f4bf61b8b20b2cb270c5b6ac42841539170cd82f Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Wed, 6 Nov 2024 13:32:38 +0100 Subject: [PATCH 34/45] Clean up indentation and resolve warnings Clean up indentation and resolve warnings by: - Suppressing certain warnings - Removing deprecated and unused code - Renaming private methods and classes #bug/gracefully-await-queries-in-progress --- .../QueryProcessingTaskIntegrationTest.java | 71 ++++++++----------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java index 9f2b17d20c..65c68a6778 100644 --- a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java +++ b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/QueryProcessingTaskIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Axon Framework + * Copyright (c) 2010-2024. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,14 +91,16 @@ void setUp() { .build(); querySerializer = new QuerySerializer(serializer, serializer, config); queryHandlingComponent1 = new QueryHandlingComponent1(); + //noinspection resource new AnnotationQueryHandlerAdapter<>(queryHandlingComponent1).subscribe(localSegment); + //noinspection resource new AnnotationQueryHandlerAdapter<>(new QueryHandlingComponent2()).subscribe(localSegment); } @Test void directQueryWhenRequesterDoesntSupportStreaming() { - QueryMessage> queryMessage = new GenericQueryMessage<>(new FluxQuery(1000), - ResponseTypes.publisherOf(String.class)); + QueryMessage> queryMessage = + new GenericQueryMessage<>(new FluxQuery(1000), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1); @@ -118,8 +120,8 @@ void directQueryWhenRequesterDoesntSupportStreaming() { @Test void queryProcessingTaskIsTraced() { - QueryMessage> queryMessage = new GenericQueryMessage<>(new FluxQuery(1000), - ResponseTypes.publisherOf(String.class)); + QueryMessage> queryMessage = + new GenericQueryMessage<>(new FluxQuery(1000), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1); @@ -136,8 +138,8 @@ void queryProcessingTaskIsTraced() { @Test void directQueryWhenRequesterDoesntSupportStreamingAndFlowControlMessagesComesBeforeQueryExecution() { - QueryMessage> queryMessage = new GenericQueryMessage<>(new FluxQuery(1000), - ResponseTypes.publisherOf(String.class)); + QueryMessage> queryMessage = + new GenericQueryMessage<>(new FluxQuery(1000), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1); @@ -157,8 +159,8 @@ void directQueryWhenRequesterDoesntSupportStreamingAndFlowControlMessagesComesBe @Test void directQueryWhenRequesterDoesntSupportStreamingAndCancelMessagesComesBeforeQueryExecution() { - QueryMessage> queryMessage = new GenericQueryMessage<>(new FluxQuery(1000), - ResponseTypes.publisherOf(String.class)); + QueryMessage> queryMessage = + new GenericQueryMessage<>(new FluxQuery(1000), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1); @@ -177,8 +179,8 @@ void directQueryWhenRequesterDoesntSupportStreamingAndCancelMessagesComesBeforeQ @Test void streamingQuery() { - QueryMessage> queryMessage = new GenericQueryMessage<>(new FluxQuery(1000), - ResponseTypes.publisherOf(String.class)); + QueryMessage> queryMessage = + new GenericQueryMessage<>(new FluxQuery(1000), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1, true) @@ -210,8 +212,7 @@ void streamingQuery() { @Test void streamingAList() { QueryMessage> queryMessage = - new GenericQueryMessage<>(new ListQuery(1000), - ResponseTypes.multipleInstancesOf(String.class)); + new GenericQueryMessage<>(new ListQuery(1000), ResponseTypes.multipleInstancesOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1, true) @@ -243,8 +244,7 @@ void streamingAList() { @Test void streamingAListWhenReactorIsNotOnClasspath() { QueryMessage> queryMessage = - new GenericQueryMessage<>(new ListQuery(1000), - ResponseTypes.multipleInstancesOf(String.class)); + new GenericQueryMessage<>(new ListQuery(1000), ResponseTypes.multipleInstancesOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1, true) @@ -274,11 +274,9 @@ void streamingAListWhenReactorIsNotOnClasspath() { } @Test - void streamingAListWhenReactorIsNotOnClasspathWithConcurrentRequests() - throws InterruptedException { + void streamingAListWhenReactorIsNotOnClasspathWithConcurrentRequests() throws InterruptedException { QueryMessage> queryMessage = - new GenericQueryMessage<>(new ListQuery(1000), - ResponseTypes.multipleInstancesOf(String.class)); + new GenericQueryMessage<>(new ListQuery(1000), ResponseTypes.multipleInstancesOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1, true) @@ -327,8 +325,7 @@ void streamingAListWhenReactorIsNotOnClasspathWithConcurrentRequests() @Test void streamingQueryWithConcurrentRequests() throws InterruptedException { QueryMessage> queryMessage = - new GenericQueryMessage<>(new FluxQuery(1000), - ResponseTypes.publisherOf(String.class)); + new GenericQueryMessage<>(new FluxQuery(1000), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1, true) @@ -535,8 +532,8 @@ void streamingListQueryWhenCancelMessageComesFirst() { @Test void fluxEmittingErrorAfterAWhile() { - QueryMessage> queryMessage = - new GenericQueryMessage<>(new ErroringAfterAWhileFluxQuery(), ResponseTypes.publisherOf(String.class)); + QueryMessage> queryMessage = + new GenericQueryMessage<>(new ErrorAfterAWhileFluxQuery(), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1, true) @@ -562,8 +559,8 @@ void fluxEmittingErrorAfterAWhile() { @Test void fluxEmittingErrorRightAway() { - QueryMessage> queryMessage = - new GenericQueryMessage<>(new ErroringFluxQuery(), ResponseTypes.publisherOf(String.class)); + QueryMessage> queryMessage = + new GenericQueryMessage<>(new ErrorFluxQuery(), ResponseTypes.publisherOf(String.class)); QueryRequest request = querySerializer.serializeRequest(queryMessage, DIRECT_QUERY_NUMBER_OF_RESULTS, 1000, 1) @@ -753,6 +750,7 @@ private ProcessingInstruction asSupportsStreaming() { .build(); } + @SuppressWarnings("unused") // Suppressing query handler unused message, as they are used. private static class QueryHandlingComponent1 { private final AtomicBoolean fluxQueryCancelled = new AtomicBoolean(); @@ -788,6 +786,7 @@ public Flux fluxMultiInstance(MultipleInstanceQuery query) { } } + @SuppressWarnings("unused") // Suppressing query handler unused message, as they are used. private static class QueryHandlingComponent2 { @QueryHandler @@ -799,23 +798,23 @@ public List listMultiInstance(MultipleInstanceQuery query) { } @QueryHandler - public Flux erroringFluxQuery(ErroringFluxQuery query) { + public Flux errorFluxQuery(ErrorFluxQuery query) { return Flux.error(new RuntimeException("oops")); } @QueryHandler - public Flux erroringFluxQuery(ErroringAfterAWhileFluxQuery query) { + public Flux errorFluxQuery(ErrorAfterAWhileFluxQuery query) { return Flux.just("0", "1", "2") .concatWith(Flux.error(new RuntimeException("oops"))); } @QueryHandler - public Flux erroringFluxQuery(ThrowingExceptionFluxQuery query) { + public Flux errorFluxQuery(ThrowingExceptionFluxQuery query) { throw new RuntimeException("oops"); } @QueryHandler - public Flux erroringFluxQuery(ThrowingExceptionListQuery query) { + public Flux errorFluxQuery(ThrowingExceptionListQuery query) { throw new RuntimeException("oops"); } } @@ -859,11 +858,11 @@ public int numberOfResults() { } } - private static class ErroringFluxQuery { + private static class ErrorFluxQuery { } - private static class ErroringAfterAWhileFluxQuery { + private static class ErrorAfterAWhileFluxQuery { } @@ -889,16 +888,6 @@ public void send(T outboundMessage) { cache.add(outboundMessage); } - @Override - public void sendAck() { - // noop - } - - @Override - public void sendNack(ErrorMessage errorMessage) { - // noop - } - @Override public void complete() { completed = true; From d85d307860fbb430c5f14defd7e1d19f43699c7f Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Wed, 6 Nov 2024 13:36:07 +0100 Subject: [PATCH 35/45] Cleanly await termination of the LocalSegmentAdapter Cleanly await termination of the LocalSegmentAdapter. The current implementation cancels all QueryProcessingTasks that are in progress right away. This causes a pending result to get lost, even when it was returned by the query handler. Thus, instead, we should use the QueryProcessingTask#resultPending to validate if we are still waiting for results within a configurable duration. This duration should be adjustable on the AxonServerQueryBus.Builder. Only when the await termination duration is reached should we proceed to cancelling the queries in progress #bug/gracefully-await-queries-in-progress --- .../connector/query/AxonServerQueryBus.java | 64 ++++++-- .../query/AxonServerQueryBusTest.java | 139 ++++++++++++++++-- 2 files changed, 179 insertions(+), 24 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 577a95b681..ac57389246 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -91,8 +91,11 @@ import reactor.core.publisher.SignalType; import reactor.core.scheduler.Scheduler; +import javax.annotation.Nonnull; import java.lang.invoke.MethodHandles; import java.lang.reflect.Type; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Spliterator; @@ -106,11 +109,11 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import javax.annotation.Nonnull; import static java.lang.String.format; import static org.axonframework.common.BuilderUtils.assertNonEmpty; @@ -150,6 +153,7 @@ public class AxonServerQueryBus implements QueryBus, Distributed, Life private final LocalSegmentAdapter localSegmentAdapter; private final String context; private final QueryBusSpanFactory spanFactory; + private final Duration queryInProgressAwait; /** * Instantiate a {@link AxonServerQueryBus} based on the fields contained in the {@link Builder}. @@ -168,6 +172,7 @@ public AxonServerQueryBus(Builder builder) { this.context = StringUtils.nonEmptyOrNull(builder.defaultContext) ? builder.defaultContext : configuration.getContext(); this.targetContextResolver = builder.targetContextResolver.orElse(m -> context); this.spanFactory = builder.spanFactory; + this.queryInProgressAwait = builder.queryInProgressAwait; dispatchInterceptors = new DispatchInterceptors<>(); @@ -517,14 +522,18 @@ Registration registerDispatchInterceptor( } /** - * Disconnect the query bus from Axon Server, by unsubscribing all known query handlers. This shutdown operation is - * performed in the {@link Phase#INBOUND_QUERY_CONNECTOR} phase. + * Disconnect the query bus from Axon Server, by unsubscribing all known query handlers and aborting all queries in progress. */ public void disconnect() { if (axonServerConnectionManager.isConnected(context)) { - axonServerConnectionManager.getConnection(context).queryChannel().prepareDisconnect(); + axonServerConnectionManager.getConnection(context) + .queryChannel() + .prepareDisconnect(); + } + if (!localSegmentAdapter.awaitTermination(queryInProgressAwait)) { + logger.info("Awaited termination of queries in progress without success. Going to cancel remaining queries in progress."); + localSegmentAdapter.cancel(); } - localSegmentAdapter.cancel(); } /** @@ -567,6 +576,7 @@ public static class Builder { private QueryBusSpanFactory spanFactory = DefaultQueryBusSpanFactory.builder() .spanFactory(NoOpSpanFactory.INSTANCE) .build(); + private Duration queryInProgressAwait = Duration.ofSeconds(5); /** * Sets the {@link AxonServerConnectionManager} used to create connections between this application and an Axon @@ -771,6 +781,22 @@ public Builder spanFactory(@Nonnull QueryBusSpanFactory spanFactory) { return this; } + /** + * Sets the {@link Duration query in progress await timeout} used to await the successful termination of queries + * in progress. When this timeout is exceeded, the query in progress will be canceled. + *

+ * Defaults to a {@code Duration} of 5 seconds. + * + * @param queryInProgressAwait The {@link Duration query in progress await timeout} used to await the successful + * termination of queries in progress + * @return The current Builder instance, for fluent interfacing. + */ + public Builder queryInProgressAwait(@Nonnull Duration queryInProgressAwait) { + assertNonNull(queryInProgressAwait, "Query in progress await timeout may not be null"); + this.queryInProgressAwait = queryInProgressAwait; + return this; + } + /** * Initializes a {@link AxonServerQueryBus} as specified through this Builder. * @@ -928,12 +954,6 @@ private class LocalSegmentAdapter implements QueryHandler { private final Map queriesInProgress = new ConcurrentHashMap<>(); - public void cancel() { - queriesInProgress.values() - .iterator() - .forEachRemaining(QueryProcessingTask::cancel); - } - @Override public void handle(QueryRequest query, ReplyChannel responseHandler) { stream(query, responseHandler).request(Long.MAX_VALUE); @@ -1000,5 +1020,27 @@ public io.axoniq.axonserver.connector.Registration registerSubscriptionQuery(Sub return CompletableFuture.completedFuture(null); }; } + + private boolean awaitTermination(Duration timeout) { + Instant startAwait = Instant.now(); + Instant endAwait = startAwait.plusSeconds(timeout.getSeconds()); + while (Instant.now().isBefore(endAwait) && !queriesInProgress.isEmpty()) { + queriesInProgress.values() + .stream() + .findFirst() + .ifPresent(queryInProgress -> { + while (Instant.now().isBefore(endAwait) && queryInProgress.resultPending()) { + LockSupport.parkNanos(50); + } + }); + } + return Instant.now().isBefore(endAwait); + } + + private void cancel() { + queriesInProgress.values() + .iterator() + .forEachRemaining(QueryProcessingTask::cancel); + } } } diff --git a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java index 872de0097b..15c0c7774d 100644 --- a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java +++ b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java @@ -42,6 +42,7 @@ import org.axonframework.messaging.Message; import org.axonframework.messaging.MessageHandler; import org.axonframework.messaging.MessageHandlerInterceptor; +import org.axonframework.messaging.MetaData; import org.axonframework.messaging.responsetypes.InstanceResponseType; import org.axonframework.queryhandling.DefaultQueryBusSpanFactory; import org.axonframework.queryhandling.GenericQueryMessage; @@ -619,7 +620,7 @@ void equalPriorityMessagesProcessedInOrder() throws InterruptedException { .putMetaData("index", MetaDataValue.newBuilder().setNumberValue(i).build()) .build(); - queryHandler.handle(queryRequest, new NoOpReplyChannel()); + queryHandler.handle(queryRequest, new StubReplyChannel()); } startProcessingGate.countDown(); //noinspection ResultOfMethodCallIgnored @@ -629,6 +630,117 @@ void equalPriorityMessagesProcessedInOrder() throws InterruptedException { assertEquals(expected, actual); } + @Test + void disconnectCancelsQueriesInProgressIfAwaitDurationIsSurpassed() { + AxonServerQueryBus queryInProgressTestSubject = + AxonServerQueryBus.builder() + .axonServerConnectionManager(axonServerConnectionManager) + .configuration(configuration) + .localSegment(localSegment) + .updateEmitter(SimpleQueryUpdateEmitter.builder().build()) + .messageSerializer(serializer) + .genericSerializer(serializer) + .targetContextResolver(targetContextResolver) + .executorServiceBuilder((c, q) -> new ThreadPoolExecutor( + 1, 1, 5, TimeUnit.SECONDS, q + )) + .queryInProgressAwait(Duration.ofSeconds(1)) + .build(); + CountDownLatch handlerLatch = new CountDownLatch(1); + AtomicReference responseReference = new AtomicReference<>(); + queriesInProgressTestSetup(queryInProgressTestSubject, handlerLatch, responseReference); + + // Start disconnecting right away. As a blocking operation, this ensures we surpass the await duration. + queryInProgressTestSubject.disconnect(); + // Release te latch, to let go of the blocking query handler. + handlerLatch.countDown(); + + await().atMost(Duration.ofSeconds(1)) + .pollDelay(Duration.ofMillis(250)) + .untilAsserted(() -> assertNull(responseReference.get())); + } + + @Test + void disconnectReturnsResponseFromQueriesInProgressIfAwaitDurationIsNotExceeded() throws InterruptedException { + AxonServerQueryBus queryInProgressTestSubject = + AxonServerQueryBus.builder() + .axonServerConnectionManager(axonServerConnectionManager) + .configuration(configuration) + .localSegment(localSegment) + .updateEmitter(SimpleQueryUpdateEmitter.builder().build()) + .messageSerializer(serializer) + .genericSerializer(serializer) + .targetContextResolver(targetContextResolver) + .executorServiceBuilder((c, q) -> new ThreadPoolExecutor( + 1, 1, 5, TimeUnit.SECONDS, q + )) + .queryInProgressAwait(Duration.ofSeconds(1)) + .build(); + CountDownLatch handlerLatch = new CountDownLatch(1); + AtomicReference responseReference = new AtomicReference<>(); + queriesInProgressTestSetup(queryInProgressTestSubject, handlerLatch, responseReference); + + // Start disconnecting in a separate thread to ensure the response latch is released + new Thread(queryInProgressTestSubject::disconnect).start(); + // Sleep a little, to ensure there is some space between disconnecting and releasing the query handler latch + Thread.sleep(250); + // Release te latch, to let go of the blocking query handler. + handlerLatch.countDown(); + + await().atMost(Duration.ofSeconds(1)) + .pollDelay(Duration.ofMillis(250)) + .untilAsserted(() -> assertNotNull(responseReference.get())); + assertEquals("Hello", responseReference.get().getMetaDataOrThrow("response").getTextValue()); + } + + private void queriesInProgressTestSetup(AxonServerQueryBus queryInProgressTestSubject, + CountDownLatch responseLatch, + AtomicReference responseReference) { + AtomicReference handlerReference = new AtomicReference<>(); + doAnswer(i -> { + handlerReference.set(i.getArgument(0)); + return (io.axoniq.axonserver.connector.Registration) () -> CompletableFuture.completedFuture(null); + }).when(mockQueryChannel) + .registerQueryHandler(any(), any()); + + when(localSegment.query(any())).thenAnswer(i -> { + responseLatch.await(); + QueryMessage message = i.getArgument(0); + QueryResponseMessage queryResponse = new GenericQueryResponseMessage<>(message.getPayload()).withMetaData( + MetaData.with("response", message.getPayload())); + return CompletableFuture.completedFuture(queryResponse); + }); + + // We create a subscription to force a registration for this type of query. + // It doesn't get invoked because the localSegment is mocked + //noinspection resource + queryInProgressTestSubject.subscribe("testQuery", + String.class, + (MessageHandler>) message -> "ok"); + await().atMost(Duration.ofSeconds(1)) + .pollDelay(Duration.ofMillis(250)) + .untilAsserted(() -> assertNotNull(handlerReference.get())); + + QueryHandler queryHandler = handlerReference.get(); + QueryRequest queryRequest = + QueryRequest.newBuilder() + .setQuery("testQuery") + .setMessageIdentifier(UUID.randomUUID().toString()) + .setPayload(SerializedObject.newBuilder() + .setType("java.lang.String") + .setData(ByteString.copyFromUtf8("Hello")) + ) + .setResponseType(SerializedObject.newBuilder() + .setData(ByteString.copyFromUtf8( + INSTANCE_RESPONSE_TYPE_XML + )) + .setType(InstanceResponseType.class.getName()) + .build()) + .putMetaData("response", MetaDataValue.newBuilder().setTextValue("Hello").build()) + .build(); + queryHandler.handle(queryRequest, new StubReplyChannel(responseReference)); + } + private QueryResponse stubResponse(String payload) { return QueryResponse.newBuilder() .setRequestIdentifier("request") @@ -771,36 +883,37 @@ public ResultStream updates() { } } - private static class NoOpReplyChannel implements ReplyChannel { + private static class StubReplyChannel implements ReplyChannel { - @Override - public void send(QueryResponse outboundMessage) { - // Do nothing - no-op implementation + private final AtomicReference responseReference; + + // No-arg constructor acts like a Noop version of this ReplyChannel + private StubReplyChannel() { + this(new AtomicReference<>()); } - @Override - public void sendAck() { - // Do nothing - no-op implementation + private StubReplyChannel(AtomicReference responseReference) { + this.responseReference = responseReference; } @Override - public void sendNack(ErrorMessage errorMessage) { - // Do nothing - no-op implementation + public void send(QueryResponse outboundMessage) { + responseReference.set(outboundMessage); } @Override public void complete() { - // Do nothing - no-op implementation + // Do nothing - not required for testing } @Override public void completeWithError(ErrorMessage errorMessage) { - // Do nothing - no-op implementation + // Do nothing - not required for testing } @Override public void completeWithError(ErrorCategory errorCategory, String message) { - // Do nothing - no-op implementation + // Do nothing - not required for testing } } } From 30bec628b5ef44dba08420dbe279698c9796fd7f Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Wed, 6 Nov 2024 15:13:51 +0100 Subject: [PATCH 36/45] Use Flux#just i.o. Flux#fromIterable Use Flux#just i.o. Flux#fromIterable, as we do not have to wrap the single response in a collection at all. Furthermore, the used operation was not JDK8 compliant, and thus unsupported by Axon Framework's internals #3161 --- .../query/AxonServerQueryBusTest.java | 50 +++++-------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java index 2649bb0f73..df6c994286 100644 --- a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java +++ b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java @@ -30,11 +30,7 @@ import io.axoniq.axonserver.grpc.query.QueryRequest; import io.axoniq.axonserver.grpc.query.QueryResponse; import io.axoniq.axonserver.grpc.query.QueryUpdate; -import org.axonframework.axonserver.connector.AxonServerConfiguration; -import org.axonframework.axonserver.connector.AxonServerConnectionManager; -import org.axonframework.axonserver.connector.ErrorCode; -import org.axonframework.axonserver.connector.TargetContextResolver; -import org.axonframework.axonserver.connector.TestTargetContextResolver; +import org.axonframework.axonserver.connector.*; import org.axonframework.axonserver.connector.util.ProcessingInstructionHelper; import org.axonframework.axonserver.connector.utils.TestSerializer; import org.axonframework.common.Registration; @@ -43,41 +39,21 @@ import org.axonframework.messaging.MessageHandler; import org.axonframework.messaging.MessageHandlerInterceptor; import org.axonframework.messaging.responsetypes.InstanceResponseType; -import org.axonframework.queryhandling.DefaultQueryBusSpanFactory; -import org.axonframework.queryhandling.GenericQueryMessage; -import org.axonframework.queryhandling.GenericQueryResponseMessage; -import org.axonframework.queryhandling.GenericStreamingQueryMessage; -import org.axonframework.queryhandling.GenericSubscriptionQueryMessage; -import org.axonframework.queryhandling.QueryBus; -import org.axonframework.queryhandling.QueryExecutionException; -import org.axonframework.queryhandling.QueryMessage; -import org.axonframework.queryhandling.QueryResponseMessage; -import org.axonframework.queryhandling.SimpleQueryUpdateEmitter; -import org.axonframework.queryhandling.StreamingQueryMessage; -import org.axonframework.queryhandling.SubscriptionQueryMessage; -import org.axonframework.queryhandling.SubscriptionQueryResult; -import org.axonframework.queryhandling.SubscriptionQueryUpdateMessage; +import org.axonframework.queryhandling.*; import org.axonframework.serialization.Serializer; import org.axonframework.tracing.TestSpanFactory; -import org.junit.jupiter.api.*; -import org.mockito.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.Duration; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -252,12 +228,12 @@ void queryWhenRegistrationIsCancel() { verify(localSegment, never()).query(testQuery); } - + @Test void streamingQueryWhenLocalHandlerIsPresent() { - when(localSegment.streamingQuery(testStreamingQuery)).thenReturn(Flux.fromIterable(List.of(new GenericQueryResponseMessage<>( - "ok")))); - + when(localSegment.streamingQuery(testStreamingQuery)) + .thenReturn(Flux.just(new GenericQueryResponseMessage<>("ok"))); + StepVerifier.create(Flux.from(testSubject.streamingQuery(testStreamingQuery)) .map(Message::getPayload)) .expectNext("ok") From eaee76e9479909c54bb59d5b702c72987e8ac7b8 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Wed, 6 Nov 2024 15:35:25 +0100 Subject: [PATCH 37/45] Upgrade axon-server-connector-java to 2024.1.1 Upgrade axon-server-connector-java to 2024.1.1 #dependency-upgrade/axon-server-connector-java-2024.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94d426f260..c1ed904eb5 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ ${project.basedir}/../coverage-report/target/site/jacoco-aggregate/jacoco.xml - 2024.1.0 + 2024.1.1 2.10.9.2 3.10.8 From 7c690327146b14766c0bccb53556449f6df2488b Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 11:14:57 +0100 Subject: [PATCH 38/45] Replace parkNanos for parkUntil and set to 10ms Replace parkNanos for parkUntil and set to 10ms. Parking for 50 nanoseconds will change it too much into a busy-wait loop, which is not desired. #3176 --- .../axonserver/connector/query/AxonServerQueryBus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index ac57389246..c4905d22f9 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -1030,7 +1030,7 @@ private boolean awaitTermination(Duration timeout) { .findFirst() .ifPresent(queryInProgress -> { while (Instant.now().isBefore(endAwait) && queryInProgress.resultPending()) { - LockSupport.parkNanos(50); + LockSupport.parkUntil(10); } }); } From 5f5f2161f88c1518091afa66085c68bbb177afc9 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 11:48:44 +0100 Subject: [PATCH 39/45] Revert unintended import changes Revert unintended import changes #3176 --- .../connector/query/AxonServerQueryBus.java | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index dabf94529b..5cbccd0a97 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -24,23 +24,60 @@ import io.axoniq.axonserver.connector.query.QueryDefinition; import io.axoniq.axonserver.connector.query.QueryHandler; import io.axoniq.axonserver.grpc.ErrorMessage; -import io.axoniq.axonserver.grpc.query.*; +import io.axoniq.axonserver.grpc.query.QueryProviderInbound; +import io.axoniq.axonserver.grpc.query.QueryProviderOutbound; +import io.axoniq.axonserver.grpc.query.QueryRequest; +import io.axoniq.axonserver.grpc.query.QueryResponse; +import io.axoniq.axonserver.grpc.query.QueryUpdate; +import io.axoniq.axonserver.grpc.query.SubscriptionQuery; import io.grpc.stub.StreamObserver; -import org.axonframework.axonserver.connector.*; +import org.axonframework.axonserver.connector.AxonServerConfiguration; +import org.axonframework.axonserver.connector.AxonServerConnectionManager; +import org.axonframework.axonserver.connector.DefaultInstructionAckSource; +import org.axonframework.axonserver.connector.DispatchInterceptors; +import org.axonframework.axonserver.connector.ErrorCode; +import org.axonframework.axonserver.connector.InstructionAckSource; +import org.axonframework.axonserver.connector.PriorityRunnable; +import org.axonframework.axonserver.connector.TargetContextResolver; import org.axonframework.axonserver.connector.command.AxonServerRegistration; import org.axonframework.axonserver.connector.query.subscription.AxonServerSubscriptionQueryResult; import org.axonframework.axonserver.connector.query.subscription.SubscriptionMessageSerializer; -import org.axonframework.axonserver.connector.util.*; -import org.axonframework.common.*; +import org.axonframework.axonserver.connector.util.ExceptionSerializer; +import org.axonframework.axonserver.connector.util.ExecutorServiceBuilder; +import org.axonframework.axonserver.connector.util.PriorityTaskSchedulers; +import org.axonframework.axonserver.connector.util.ProcessingInstructionHelper; +import org.axonframework.axonserver.connector.util.UpstreamAwareStreamObserver; +import org.axonframework.common.Assert; +import org.axonframework.common.AxonConfigurationException; +import org.axonframework.common.AxonException; +import org.axonframework.common.AxonThreadFactory; +import org.axonframework.common.Registration; +import org.axonframework.common.StringUtils; import org.axonframework.lifecycle.Lifecycle; import org.axonframework.lifecycle.Phase; import org.axonframework.lifecycle.ShutdownLatch; -import org.axonframework.messaging.*; +import org.axonframework.messaging.Distributed; +import org.axonframework.messaging.GenericMessage; +import org.axonframework.messaging.MessageDispatchInterceptor; +import org.axonframework.messaging.MessageHandler; +import org.axonframework.messaging.MessageHandlerInterceptor; import org.axonframework.messaging.responsetypes.ConvertingResponseMessage; import org.axonframework.messaging.responsetypes.InstanceResponseType; import org.axonframework.messaging.responsetypes.MultipleInstancesResponseType; import org.axonframework.messaging.responsetypes.ResponseType; -import org.axonframework.queryhandling.*; +import org.axonframework.queryhandling.DefaultQueryBusSpanFactory; +import org.axonframework.queryhandling.GenericQueryResponseMessage; +import org.axonframework.queryhandling.QueryBus; +import org.axonframework.queryhandling.QueryBusSpanFactory; +import org.axonframework.queryhandling.QueryMessage; +import org.axonframework.queryhandling.QueryResponseMessage; +import org.axonframework.queryhandling.QueryUpdateEmitter; +import org.axonframework.queryhandling.StreamingQueryMessage; +import org.axonframework.queryhandling.SubscriptionQueryBackpressure; +import org.axonframework.queryhandling.SubscriptionQueryMessage; +import org.axonframework.queryhandling.SubscriptionQueryResult; +import org.axonframework.queryhandling.SubscriptionQueryUpdateMessage; +import org.axonframework.queryhandling.UpdateHandlerRegistration; import org.axonframework.serialization.Serializer; import org.axonframework.tracing.NoOpSpanFactory; import org.axonframework.tracing.Span; @@ -63,7 +100,14 @@ import java.util.Map; import java.util.Set; import java.util.Spliterator; -import java.util.concurrent.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; From 8a430e88a03c0094ab6e9bdb8853f26f1c164ed7 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 13:05:03 +0100 Subject: [PATCH 40/45] Switch back to parkNanos, as parkUntil does not do what I expected... Switch back to parkNanos, as parkUntil does not do what I expected... #3176 --- .../axonserver/connector/query/AxonServerQueryBus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 5cbccd0a97..48f3afcf17 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -1070,7 +1070,7 @@ private boolean awaitTermination(Duration timeout) { .findFirst() .ifPresent(queryInProgress -> { while (Instant.now().isBefore(endAwait) && queryInProgress.resultPending()) { - LockSupport.parkUntil(10); + LockSupport.parkNanos(10_000_000); } }); } From e451b86ee487d2e75270ac2a161318d8ae9d1763 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 13:21:50 +0100 Subject: [PATCH 41/45] Return queriesInProgress#isEmpty i.o. time check Return queriesInProgress#isEmpty i.o. time check. As what matters, is that everything in progress is done at this point in time. #3176 --- .../axonserver/connector/query/AxonServerQueryBus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java index 48f3afcf17..13935f6072 100644 --- a/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java +++ b/axon-server-connector/src/main/java/org/axonframework/axonserver/connector/query/AxonServerQueryBus.java @@ -1074,7 +1074,7 @@ private boolean awaitTermination(Duration timeout) { } }); } - return Instant.now().isBefore(endAwait); + return queriesInProgress.isEmpty(); } private void cancel() { From 8bd5e4669bae165f504c6eaeead8f7c6cf83c21a Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 13:29:59 +0100 Subject: [PATCH 42/45] Minor test cleanup Minor test cleanup #3176 --- .../axonserver/connector/query/AxonServerQueryBusTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java index 4939caca66..c62db51dff 100644 --- a/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java +++ b/axon-server-connector/src/test/java/org/axonframework/axonserver/connector/query/AxonServerQueryBusTest.java @@ -79,7 +79,7 @@ class AxonServerQueryBusTest { private static final String TEST_QUERY = "testQuery"; private static final String CONTEXT = "default-test"; - public static final String INSTANCE_RESPONSE_TYPE_XML = "java.lang.String"; + private static final String INSTANCE_RESPONSE_TYPE_XML = "java.lang.String"; private final QueryBus localSegment = mock(QueryBus.class); private final Serializer serializer = TestSerializer.xStreamSerializer(); @@ -212,8 +212,8 @@ void setUp() { @Test void queryWhenLocalHandlerIsPresent() { - when(localSegment.query(testQuery)).thenReturn(CompletableFuture.completedFuture(new GenericQueryResponseMessage<>( - "ok"))); + when(localSegment.query(testQuery)) + .thenReturn(CompletableFuture.completedFuture(new GenericQueryResponseMessage<>("ok"))); testSubject.query(testQuery); From 87ca29652d229e0e34340d757072f2e2f90a09b9 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 15:32:20 +0100 Subject: [PATCH 43/45] [maven-release-plugin] prepare release axon-4.10.2 --- axon-server-connector/pom.xml | 2 +- config/pom.xml | 2 +- disruptor/pom.xml | 2 +- eventsourcing/pom.xml | 2 +- integrationtests/pom.xml | 2 +- legacy/pom.xml | 2 +- messaging/pom.xml | 2 +- metrics-micrometer/pom.xml | 2 +- metrics/pom.xml | 2 +- migration/pom.xml | 2 +- modelling/pom.xml | 2 +- pom.xml | 4 ++-- reactorless-test/pom.xml | 2 +- spring-boot-autoconfigure/pom.xml | 2 +- spring-boot-starter/pom.xml | 4 ++-- spring-boot3-dummy/pom.xml | 2 +- spring/pom.xml | 2 +- test/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 2 +- 19 files changed, 21 insertions(+), 21 deletions(-) diff --git a/axon-server-connector/pom.xml b/axon-server-connector/pom.xml index 432219ab73..0d8890009c 100644 --- a/axon-server-connector/pom.xml +++ b/axon-server-connector/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-server-connector diff --git a/config/pom.xml b/config/pom.xml index 72cd02d7e5..0b6f43d271 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -20,7 +20,7 @@ axon org.axonframework - 4.10.2-SNAPSHOT + 4.10.2 axon-configuration diff --git a/disruptor/pom.xml b/disruptor/pom.xml index 09955c83a6..867187f682 100644 --- a/disruptor/pom.xml +++ b/disruptor/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-disruptor diff --git a/eventsourcing/pom.xml b/eventsourcing/pom.xml index 647828ec6c..07947fe54f 100644 --- a/eventsourcing/pom.xml +++ b/eventsourcing/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-eventsourcing diff --git a/integrationtests/pom.xml b/integrationtests/pom.xml index 84843193bb..59aa35cbe4 100644 --- a/integrationtests/pom.xml +++ b/integrationtests/pom.xml @@ -19,7 +19,7 @@ axon org.axonframework - 4.10.2-SNAPSHOT + 4.10.2 4.0.0 diff --git a/legacy/pom.xml b/legacy/pom.xml index 306cfa8519..6f5bfd7fdc 100644 --- a/legacy/pom.xml +++ b/legacy/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 4.0.0 diff --git a/messaging/pom.xml b/messaging/pom.xml index 0d015d89de..8fdedd48b0 100644 --- a/messaging/pom.xml +++ b/messaging/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-messaging diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index 115fcb6161..1ca322cf91 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-micrometer diff --git a/metrics/pom.xml b/metrics/pom.xml index 91b8c7dbbd..98be4f14aa 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-metrics diff --git a/migration/pom.xml b/migration/pom.xml index b9dc05c1e2..611b7543eb 100644 --- a/migration/pom.xml +++ b/migration/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-migration diff --git a/modelling/pom.xml b/modelling/pom.xml index 18ea85d661..9889318830 100644 --- a/modelling/pom.xml +++ b/modelling/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-modelling diff --git a/pom.xml b/pom.xml index c1ed904eb5..e0f1942036 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-server-connector disruptor @@ -933,7 +933,7 @@ scm:git:git://github.com/AxonFramework/AxonFramework.git scm:git:git@github.com:AxonFramework/AxonFramework.git https://github.com/AxonFramework/AxonFramework - HEAD + axon-4.10.2 diff --git a/reactorless-test/pom.xml b/reactorless-test/pom.xml index 6d812f4bff..115ec55e03 100644 --- a/reactorless-test/pom.xml +++ b/reactorless-test/pom.xml @@ -19,7 +19,7 @@ axon org.axonframework - 4.10.2-SNAPSHOT + 4.10.2 4.0.0 diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index e7233b67c6..67bfb4d5dc 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 4.0.0 diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml index 21fb1c91a9..15c713c016 100644 --- a/spring-boot-starter/pom.xml +++ b/spring-boot-starter/pom.xml @@ -19,12 +19,12 @@ axon org.axonframework - 4.10.2-SNAPSHOT + 4.10.2 4.0.0 axon-spring-boot-starter - 4.10.2-SNAPSHOT + 4.10.2 Spring Boot Starter module for Axon Framework diff --git a/spring-boot3-dummy/pom.xml b/spring-boot3-dummy/pom.xml index f8683fbec2..b63e119849 100644 --- a/spring-boot3-dummy/pom.xml +++ b/spring-boot3-dummy/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-spring-boot3-dummy diff --git a/spring/pom.xml b/spring/pom.xml index a5f6a582c8..149771bb00 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 4.0.0 diff --git a/test/pom.xml b/test/pom.xml index 51d64d0280..5fa039282f 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-test diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index 798a43aabe..a8e5cbb148 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.2 axon-tracing-opentelemetry From f3dd9801ce4d0745df26cca17144919ad25758a8 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 15:32:22 +0100 Subject: [PATCH 44/45] [maven-release-plugin] prepare for next development iteration --- axon-server-connector/pom.xml | 2 +- config/pom.xml | 2 +- disruptor/pom.xml | 2 +- eventsourcing/pom.xml | 2 +- integrationtests/pom.xml | 2 +- legacy/pom.xml | 2 +- messaging/pom.xml | 2 +- metrics-micrometer/pom.xml | 2 +- metrics/pom.xml | 2 +- migration/pom.xml | 2 +- modelling/pom.xml | 2 +- pom.xml | 4 ++-- reactorless-test/pom.xml | 2 +- spring-boot-autoconfigure/pom.xml | 2 +- spring-boot-starter/pom.xml | 4 ++-- spring-boot3-dummy/pom.xml | 2 +- spring/pom.xml | 2 +- test/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 2 +- 19 files changed, 21 insertions(+), 21 deletions(-) diff --git a/axon-server-connector/pom.xml b/axon-server-connector/pom.xml index 0d8890009c..a27f89379a 100644 --- a/axon-server-connector/pom.xml +++ b/axon-server-connector/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-server-connector diff --git a/config/pom.xml b/config/pom.xml index 0b6f43d271..034fc5b08e 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -20,7 +20,7 @@ axon org.axonframework - 4.10.2 + 4.10.3-SNAPSHOT axon-configuration diff --git a/disruptor/pom.xml b/disruptor/pom.xml index 867187f682..8c5d006368 100644 --- a/disruptor/pom.xml +++ b/disruptor/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-disruptor diff --git a/eventsourcing/pom.xml b/eventsourcing/pom.xml index 07947fe54f..6f4372328b 100644 --- a/eventsourcing/pom.xml +++ b/eventsourcing/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-eventsourcing diff --git a/integrationtests/pom.xml b/integrationtests/pom.xml index 59aa35cbe4..d64487415c 100644 --- a/integrationtests/pom.xml +++ b/integrationtests/pom.xml @@ -19,7 +19,7 @@ axon org.axonframework - 4.10.2 + 4.10.3-SNAPSHOT 4.0.0 diff --git a/legacy/pom.xml b/legacy/pom.xml index 6f5bfd7fdc..243868fdd5 100644 --- a/legacy/pom.xml +++ b/legacy/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT 4.0.0 diff --git a/messaging/pom.xml b/messaging/pom.xml index 8fdedd48b0..0f40e38689 100644 --- a/messaging/pom.xml +++ b/messaging/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-messaging diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index 1ca322cf91..d0acadb0a6 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-micrometer diff --git a/metrics/pom.xml b/metrics/pom.xml index 98be4f14aa..43064cef0c 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-metrics diff --git a/migration/pom.xml b/migration/pom.xml index 611b7543eb..e74846b3e9 100644 --- a/migration/pom.xml +++ b/migration/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-migration diff --git a/modelling/pom.xml b/modelling/pom.xml index 9889318830..540bc816e8 100644 --- a/modelling/pom.xml +++ b/modelling/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-modelling diff --git a/pom.xml b/pom.xml index e0f1942036..d4a8086d37 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-server-connector disruptor @@ -933,7 +933,7 @@ scm:git:git://github.com/AxonFramework/AxonFramework.git scm:git:git@github.com:AxonFramework/AxonFramework.git https://github.com/AxonFramework/AxonFramework - axon-4.10.2 + HEAD diff --git a/reactorless-test/pom.xml b/reactorless-test/pom.xml index 115ec55e03..45fd3e1a94 100644 --- a/reactorless-test/pom.xml +++ b/reactorless-test/pom.xml @@ -19,7 +19,7 @@ axon org.axonframework - 4.10.2 + 4.10.3-SNAPSHOT 4.0.0 diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 67bfb4d5dc..5ea53964de 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT 4.0.0 diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml index 15c713c016..681ce6653e 100644 --- a/spring-boot-starter/pom.xml +++ b/spring-boot-starter/pom.xml @@ -19,12 +19,12 @@ axon org.axonframework - 4.10.2 + 4.10.3-SNAPSHOT 4.0.0 axon-spring-boot-starter - 4.10.2 + 4.10.3-SNAPSHOT Spring Boot Starter module for Axon Framework diff --git a/spring-boot3-dummy/pom.xml b/spring-boot3-dummy/pom.xml index b63e119849..3d6a032e76 100644 --- a/spring-boot3-dummy/pom.xml +++ b/spring-boot3-dummy/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-spring-boot3-dummy diff --git a/spring/pom.xml b/spring/pom.xml index 149771bb00..98d7faabd7 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -19,7 +19,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT 4.0.0 diff --git a/test/pom.xml b/test/pom.xml index 5fa039282f..0da95e42b9 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-test diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index a8e5cbb148..68a953da91 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2 + 4.10.3-SNAPSHOT axon-tracing-opentelemetry From 0ff6fe291609e2e4dc323533e101abcec894ef1d Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Thu, 7 Nov 2024 17:10:05 +0100 Subject: [PATCH 45/45] Update version of non-deployed dependencies Update version of non-deployed dependencies #release/4.10.2 --- coverage-report/pom.xml | 2 +- hibernate-6-integrationtests/pom.xml | 2 +- spring-boot-3-integrationtests/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml index e483f6c493..a2d477a53e 100644 --- a/coverage-report/pom.xml +++ b/coverage-report/pom.xml @@ -20,7 +20,7 @@ org.axonframework axon - 4.10.2-SNAPSHOT + 4.10.3-SNAPSHOT axon-coverage-report diff --git a/hibernate-6-integrationtests/pom.xml b/hibernate-6-integrationtests/pom.xml index 925ef43a34..aea8326e04 100644 --- a/hibernate-6-integrationtests/pom.xml +++ b/hibernate-6-integrationtests/pom.xml @@ -20,7 +20,7 @@ axon org.axonframework - 4.10.2-SNAPSHOT + 4.10.3-SNAPSHOT 4.0.0 diff --git a/spring-boot-3-integrationtests/pom.xml b/spring-boot-3-integrationtests/pom.xml index 4bbf330c97..4cc110c5af 100644 --- a/spring-boot-3-integrationtests/pom.xml +++ b/spring-boot-3-integrationtests/pom.xml @@ -27,7 +27,7 @@ org.axonframework axon-spring-boot-3-integrationtests - 4.10.2-SNAPSHOT + 4.10.3-SNAPSHOT Axon Framework - Spring Boot 3 Integration Tests