Fluxzero 1.185.0
1.185.0 (2026-05-25)
Features
feat(benchmark): add tracker preload mode (
65a34f0)Replace the old publishMode/concurrentPublish switch with an explicit benchmark mode: PRELOAD, BULK, or CONCURRENT. PRELOAD publishes the event set before registering consumers so profiling can focus on post-publish drain behavior.
Tests: ./mvnw -q -pl sdk -Dtest=TrackerIntegrationBenchmark test-compile
feat(benchmark): allow local tracker client mode (
8f2a86e)Add a clientMode property to TrackerIntegrationBenchmark so it can run either against the default websocket/TestServer path or a shared LocalClient in-memory store.
The local path intentionally reuses one root LocalClient across benchmark Fluxzero instances so default consumers and publishers observe the same message log.
Tests: ./mvnw -q -pl sdk -am test-compile
Tests: java -DclientMode=LOCAL -DclientCount=2 -DconsumerCount=3 -DthreadCount=1 -DmessageCount=50 -DpublisherCount=2 -DpublishBatchSize=5 -DpayloadBytes=0 -DbenchmarkTimeoutMs=10000 -cp ... io.fluxzero.sdk.benchmark.TrackerIntegrationBenchmark
Bug Fixes
fix(handling): guard reusable handler fast path (
0340cca)Do not let generic delegating handlers expose reusable handler methods. Decorators that only override invoker resolution can otherwise be bypassed by the optimized DefaultTracking path.
Add regression coverage for expired request filtering, websocket lifecycle decorators, protected data restoration, lazy payload selection, and error-reporting policy on reusable handler methods.
Tests: ./mvnw -q -pl sdk -am -Dtest=ExpiredRequestDecoratorTest,WebsocketHandlerDecoratorTest,DataProtectionInterceptorTest,ErrorReportingInterceptorTest,PayloadParameterResolverTest -Dsurefire.failIfNoSpecifiedTests=false test
fix(handling): keep payload preparation lazy (
84be102)Avoid reading serialized payloads while preparing payload parameter resolvers. Handler selection can use the serialized payload class, while actual payload deserialization remains deferred until the selected invoker resolves its argument.
If a payload is already in memory or already deserialized, the resolver still reuses it for nullability checks. Chunked typed payloads remain lazy so selection does not wait for the final chunk.
Tests: ./mvnw -q -pl sdk -am -Dtest=PayloadParameterResolverTest,ChunkedMessageTest#typedChunkedHandlerSelectionDoesNotBlockTrackerBeforeFinalChunk,DeserializingMessageTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
fix(benchmark): isolate tracker clients per run (
cd9602a)Give TrackerIntegrationBenchmark clients a run-specific id suffix derived from the namespace so TestServer command-idempotency results from a prior JVM cannot satisfy new append commands.
Also log the namespace to make rerun diagnostics easier.
Tests: ./mvnw -q -pl sdk -am test-compile
Performance Improvements
perf(tracking): trim batch handler overhead (
4bac307)Reduce per-message tracking overhead by using an indexed list path for DeserializingMessage.forEachInBatch and by preparing payload parameters without repeating the payload cache lookup for regular non-chunked messages.
Chunked typed payloads deliberately keep the old lazy resolver path so handler selection does not wait for the final chunk.
Tests: ./mvnw -q -pl sdk -am -Dtest=ChunkedMessageTest#typedChunkedHandlerSelectionDoesNotBlockTrackerBeforeFinalChunk,PayloadParameterResolverTest,DeserializingMessageTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
perf(handling): specialize single dynamic parameters (
aec5079)Avoid allocating a Function[] and using the general IntFunction path for unvalidated handler methods with one dynamic parameter. This keeps the existing resolver matching semantics, but lets the hot payload-handler case call the single-argument member invoker directly.
Tests: ./mvnw -q -pl common -Dtest=HandlerInspectorTest,HandlerInspectorParameterResolverTest test
Tests: ./mvnw -q -pl sdk -am test
perf(serialization): avoid payload memoizer maps (
caae69e)Use a single-value cache for the default Object.class payload lookup in DeserializingObject and keep the typed map only for explicit getPayloadAs lookups. This avoids per-message ConcurrentHashMap allocation in the common payload handler path.
Tests: ./mvnw -q -pl sdk -am -Dtest=DeserializingMessageTest -Dsurefire.failIfNoSpecifiedTests=false test; ./mvnw -q -pl sdk -am test
perf(tracking): reduce batch filtering allocations (
0a91eec)Replace hot stream and Optional-heavy tracking paths with direct loops for position checks, tracker filtering, and batch message handling while preserving the existing stream-based deserialize API.
Tests: ./mvnw -q -pl common -Dtest=PositionTest,DefaultTrackingStrategyTest test; ./mvnw -q -pl sdk -am -Dtest=DeserializingMessageTest,ChunkedMessageTest -Dsurefire.failIfNoSpecifiedTests=false test; ./mvnw -q -pl sdk -am test
perf(reflection): specialize direct member invocation (
77ccca1)Avoid creating per-call parameter-provider lambdas for the common no-arg and single-arg DefaultMemberInvoker paths while keeping the existing general IntFunction route for multi-arg and fallback invocation.
Tests: ./mvnw -q -pl common -Dtest=DefaultMemberInvokerTest test; ./mvnw -q -pl sdk -am -Dtest=HandlerInspectorTest,ErrorReportingInterceptorTest,InvocationTest -Dsurefire.failIfNoSpecifiedTests=false test; ./mvnw -q -pl sdk -am test
perf(handling): reuse bound handler methods (
7b7aed0)Split handler metadata into HandlerDescriptor and add a reusable HandlerMethod fast path for fixed-target single-method handlers. This keeps HandlerInvoker as the existing per-message compatibility adapter while allowing DefaultTracking and SDK-owned decorators to avoid per-message invoker and reporting wrapper allocation on eligible handlers.
Also avoid an unused AtomicReference allocation in Invocation lazy id handling and cover the reusable method path in handler inspector and error reporting tests.
Tests: ./mvnw -q -pl sdk -am test
perf(handling): reuse invokers as parameter providers (
c46d646)Have unvalidated prepared and dynamic handler invokers implement IntFunction directly so invocation no longer creates an extra parameter-provider lambda. This preserves the same per-message invoker lifecycle while trimming one allocation layer from reflective handler dispatch.
Tests: ./mvnw -q -pl sdk -am -Dtest=HandlerInspectorParameterResolverTest,TriggerParameterResolverTest,ThrowingErrorHandlerTest,TrackerIntegrationBenchmark,DefaultHandlerFactoryTest,HandlerMonitorTest -Dsurefire.failIfNoSpecifiedTests=false test; ./mvnw -q -pl sdk -am test
JFR: local preload noop consumption showed the MethodHandlerMatcher lambda allocation disappear; remaining handler allocation is the per-message prepared invoker itself.
perf(handling): avoid handler policy cache hit allocations (
b02c386)Avoid capturable loader lambdas on hot cache-hit paths in trigger filtering and error-reporting policy lookup. Trigger filters now also prepare to allowAll for methods without @Trigger, removing that resolver from ordinary handler dispatch.
Tests: ./mvnw -q -pl sdk -am -Dtest=TriggerParameterResolverTest,ThrowingErrorHandlerTest,TrackerIntegrationBenchmark,DefaultHandlerFactoryTest,HandlerMonitorTest -Dsurefire.failIfNoSpecifiedTests=false test; ./mvnw -q -pl sdk -am test
JFR: local preload noop consumption completed in 1398 ms; TriggerMetadata and ErrorReportingInterceptor loader lambdas dropped out of the hot allocation sites, leaving prepared invoker allocation as the main handler-layer item.
perf(handling): reduce invocation threadlocal churn (
482e5e0)Preserve the existing Invocation.getCurrent() semantics while avoiding ThreadLocal map remove churn for each top-level handler invocation. The new test pins that callbacks still run after the current invocation is cleared.
Tests: ./mvnw -q -pl sdk -am -Dtest=InvocationTest,TrackerIntegrationBenchmark,DefaultHandlerFactoryTest,HandlerMonitorTest,ThrowingErrorHandlerTest -Dsurefire.failIfNoSpecifiedTests=false test; ./mvnw -q -pl sdk -am test
JFR: local preload noop consumption completed in 1933 ms and removed ThreadLocalMap.set from the allocation top sites.
perf(handling): collapse no-op message filters (
bdf06a0)Allow prepared message filters to identify always-true filters and collapse composed filter chains around them. Payload filters without allowed classes and segment filters without routing keys now disappear from the per-message hot path.
JFR: /private/tmp/fluxzero-consumption-prepared-filter-noop.jfr no longer showed PayloadFilter/MessageFilter in the noop hot-method list; the run completed in 1320 ms.
Tests: ./mvnw -q -pl sdk -am -Dtest=TrackerIntegrationBenchmark,DefaultHandlerFactoryTest,HandlerMonitorTest,ThrowingErrorHandlerTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
perf(handling): reduce per-message invocation allocations (
503849e)Skip building handler argument arrays when the method invocation validator is the default no-op validator. The validating path is preserved for web request parameter validation and custom validators.
Reuse a completed report stage for handlers that do not need to send a response, avoiding a completed CompletableFuture allocation per event.
JFR: /private/tmp/fluxzero-consumption-completed-report-noop.jfr no longer showed CompletableFuture.completedFuture in the allocation top; the noop handler run completed in 1594 ms.
Tests: ./mvnw -q -pl sdk -am -Dtest=TrackerIntegrationBenchmark,DefaultHandlerFactoryTest,HandlerMonitorTest,ThrowingErrorHandlerTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
perf(serialization): avoid optional allocation for first messages (
b17c5a5)Add a nullable first-message deserialization path and use it from DefaultTracking and the benchmark raw deserialize pipeline. The existing Optional-returning API remains available and delegates to the nullable method.
JFR: /private/tmp/fluxzero-consumption-nullable-serializer-deserialize.jfr showed Optional allocation pressure dropping in the deserialize-only profile, with wall-clock remaining in the same ~1.3s band.
Tests: ./mvnw -q -pl sdk -am -Dtest=JacksonSerializerTest,TrackerIntegrationBenchmark,DeserializingMessageTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
perf(handling): avoid optional allocation in invoker resolution (
7bdf8aa)Add nullable invoker resolution methods alongside the existing Optional-based handler APIs and use them in the tracking hot path and core handler decorators. This keeps source compatibility while reducing per-message Optional churn through nested handlers and interceptors.
JFR: /private/tmp/fluxzero-consumption-nullable-invoker-noop.jfr showed Optional.of allocation pressure dropping from roughly 6.6% to 3.8% in the noop handler profile; wall-clock remained noisy.
Tests: ./mvnw -q -pl sdk -am -Dtest=DeserializingMessageTest,TrackerIntegrationBenchmark,ErrorReportingInterceptorTest,DefaultHandlerFactoryTest,HandlerMonitorTest,ThrowingErrorHandlerTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
perf(serialization): memoize deserialized payloads lazily (
45943f6)Delay creation of the memoizing payload wrapper until a deserializing object actually materializes or checks its payload. This removes memoizer allocation from tracking paths that only need serialized message metadata.
Keep metadata copies sharing the same memoized payload function and cover that behavior with a focused regression test.
Tests: ./mvnw -q -pl sdk -am -Dtest=DeserializingMessageTest,ObjectUtilsTest,TrackerIntegrationBenchmark -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
perf(common): allocate memoization storage lazily (
4c70f4c)Avoid creating a ConcurrentHashMap for memoized functions that are never evaluated. This reduces allocation pressure in deserializing message paths where payload functions are often carried through without materializing the payload.
The Lombok-generated all-args constructor shape is retained explicitly for compatibility.
Tests: ./mvnw -q -pl sdk -am test
perf(handling): cache static handler resolution (
c3873d5)Prepare static parameter resolvers once per handler method so invocation no longer re-runs resolver matching for message, metadata, user, and timestamp parameters.
Dynamic payload, trigger, and entity resolution remains message-dependent. Also cache error-reporting local-handler policy and skip request-expiry decoration for non-request handlers without changing public handler contracts.
Tests: ./mvnw -B install
perf(handling): reduce tracking invocation overhead (
8f7fd1c)Prepare message filters once per handler method, add first-message serializer and caster fast paths, and remove several per-message wrapper/allocation paths in tracking handlers.
The serializer fast path preserves findAny semantics for split/drop upcasters and the message apply path keeps batch completion behavior covered by focused tests.
Tests: ./mvnw -q -pl sdk -am -Dtest=HandlerInspectorTest,HandlerInspectorParameterResolverTest,HandlerInspectorSpecificityTest,ClientUtilsTest,DeserializingMessageTest,JacksonSerializerTest,UpcasterChainTest,ChunkedMessageTest,DataProtectionInterceptorTest,GivenWhenThenTest,GivenWhenThenRecursivePublicationGuardTest,HandlerInterceptorTest,ExpiredRequestDecoratorTest,StatefulHandlerTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests: ./mvnw -q -pl sdk -am test
Tests: ./mvnw -B install
perf(tracking): reduce local in-memory tracking contention (
efb0210)Reduce contention seen in local tracker profiling by replacing copy-on-write/synchronized hot-path structures, avoiding full-tail copies in the in-memory message store, and caching the local runtime client id.
Keep DefaultTracking's non-chunked batch path eager and one-result-per-input so split upcasters cannot change tracking semantics while still avoiding chunk-specific bookkeeping for ordinary batches.
Tests: ./mvnw -q -pl common -Dtest=InMemoryTaskSchedulerTest,DefaultTrackingStrategyTest test
Tests: ./mvnw -q -pl sdk -Dtest=LocalTrackingClientTest,InMemoryMessageStoreTest,ChunkedMessageTest test
Tests: ./mvnw -q -pl sdk -Dtest=ChunkedMessageTest,JacksonSerializerTest,UpcasterChainTest test
Tests: git diff --check
Tests
test(benchmark): add consumption jfr option (
b9a8fc9)Allow TrackerIntegrationBenchmark to start a programmatic JFR recording around the consumption phase only. This keeps publish/setup noise out of profiling runs while preserving the normal benchmark path when no jfrFile is configured.
Tests: ./mvnw -q -pl sdk -am test
test(benchmark): add tracker pipeline modes (
2b1c21f)Add pipelineMode variants to TrackerIntegrationBenchmark so LocalClient preload runs can isolate raw tracking, deserialization, direct handler invocation, annotated no-op handling, payload handling, and the existing benchmark handler.
This keeps the existing benchmark defaults unchanged while making JFR comparisons between store/client, serializer, and handler pipeline costs repeatable.
Tests: ./mvnw -q -pl sdk -am test