From 5bb38a7319c220fb0f821d7cdf00b51f27bc1b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 30 May 2023 17:10:49 +0200 Subject: [PATCH 01/26] Hardening automatic context propagation This change addresses Flux, Mono, and Publisher implementations that are implemented outside of reactor-core for the purposes of automatic restoration of ThreadLocal state from the Context. --- .../src/main/java/reactor/core/Scannable.java | 7 +- .../core/publisher/ConnectableFluxHide.java | 4 +- .../publisher/ConnectableFluxOnAssembly.java | 4 +- .../core/publisher/ConnectableLift.java | 5 +- .../publisher/ConnectableLiftFuseable.java | 5 +- .../java/reactor/core/publisher/Flux.java | 10 + .../reactor/core/publisher/FluxArray.java | 5 +- .../core/publisher/FluxBufferWhen.java | 4 +- .../reactor/core/publisher/FluxCallable.java | 4 +- .../core/publisher/FluxCombineLatest.java | 4 +- .../core/publisher/FluxConcatArray.java | 4 +- .../core/publisher/FluxConcatIterable.java | 4 +- .../reactor/core/publisher/FluxCreate.java | 2 +- .../reactor/core/publisher/FluxDefer.java | 4 +- .../core/publisher/FluxDeferContextual.java | 11 +- .../reactor/core/publisher/FluxEmpty.java | 4 +- .../reactor/core/publisher/FluxError.java | 4 +- .../core/publisher/FluxErrorOnRequest.java | 4 +- .../core/publisher/FluxErrorSupplied.java | 4 +- .../core/publisher/FluxFirstWithSignal.java | 8 +- .../core/publisher/FluxFirstWithValue.java | 4 +- .../reactor/core/publisher/FluxFlatMap.java | 9 +- .../core/publisher/FluxFromMonoOperator.java | 3 +- .../reactor/core/publisher/FluxGenerate.java | 4 +- .../reactor/core/publisher/FluxInterval.java | 5 +- .../reactor/core/publisher/FluxIterable.java | 4 +- .../java/reactor/core/publisher/FluxJust.java | 4 +- .../reactor/core/publisher/FluxMerge.java | 4 +- .../core/publisher/FluxMergeComparing.java | 9 +- .../reactor/core/publisher/FluxNever.java | 4 +- .../reactor/core/publisher/FluxOperator.java | 6 +- .../reactor/core/publisher/FluxPublish.java | 1 + .../reactor/core/publisher/FluxRange.java | 4 +- .../reactor/core/publisher/FluxReplay.java | 3 +- .../reactor/core/publisher/FluxSource.java | 3 +- .../core/publisher/FluxSourceFuseable.java | 4 +- .../reactor/core/publisher/FluxStream.java | 4 +- .../core/publisher/FluxSwitchOnFirst.java | 4 +- .../reactor/core/publisher/FluxUsing.java | 4 +- .../reactor/core/publisher/FluxUsingWhen.java | 4 +- .../java/reactor/core/publisher/FluxZip.java | 4 +- .../reactor/core/publisher/GroupedLift.java | 5 +- .../core/publisher/GroupedLiftFuseable.java | 5 +- .../reactor/core/publisher/InnerProducer.java | 3 +- .../InternalConnectableFluxOperator.java | 3 +- .../core/publisher/InternalFluxOperator.java | 10 +- .../core/publisher/InternalMonoOperator.java | 6 +- .../java/reactor/core/publisher/Mono.java | 6 + .../reactor/core/publisher/MonoCallable.java | 4 +- .../reactor/core/publisher/MonoCreate.java | 4 +- .../reactor/core/publisher/MonoDefer.java | 2 +- .../core/publisher/MonoDeferContextual.java | 6 +- .../reactor/core/publisher/MonoDelay.java | 5 +- .../reactor/core/publisher/MonoEmpty.java | 4 +- .../reactor/core/publisher/MonoError.java | 4 +- .../core/publisher/MonoErrorSupplied.java | 4 +- .../core/publisher/MonoFirstWithSignal.java | 6 +- .../core/publisher/MonoFirstWithValue.java | 4 +- .../core/publisher/MonoFromFluxOperator.java | 3 +- .../core/publisher/MonoFromPublisher.java | 3 + .../core/publisher/MonoIgnorePublisher.java | 5 +- .../core/publisher/MonoIgnoreThen.java | 7 +- .../java/reactor/core/publisher/MonoJust.java | 4 +- .../reactor/core/publisher/MonoNever.java | 4 +- .../reactor/core/publisher/MonoOperator.java | 7 +- .../reactor/core/publisher/MonoRunnable.java | 4 +- .../core/publisher/MonoSequenceEqual.java | 4 +- .../core/publisher/MonoSingleCallable.java | 4 +- .../publisher/MonoSingleOptionalCallable.java | 2 +- .../reactor/core/publisher/MonoSource.java | 2 +- .../reactor/core/publisher/MonoSupplier.java | 4 +- .../reactor/core/publisher/MonoUsing.java | 4 +- .../reactor/core/publisher/MonoUsingWhen.java | 4 +- .../java/reactor/core/publisher/MonoWhen.java | 4 +- .../java/reactor/core/publisher/MonoZip.java | 4 +- .../reactor/core/publisher/Operators.java | 60 +- .../core/publisher/ParallelCollect.java | 3 +- .../core/publisher/ParallelConcatMap.java | 3 +- .../core/publisher/ParallelDoOnEach.java | 3 +- .../core/publisher/ParallelFilter.java | 3 +- .../core/publisher/ParallelFlatMap.java | 3 +- .../core/publisher/ParallelFluxHide.java | 3 +- .../core/publisher/ParallelFluxName.java | 4 +- .../publisher/ParallelFluxOnAssembly.java | 3 +- .../reactor/core/publisher/ParallelLift.java | 3 +- .../core/publisher/ParallelLiftFuseable.java | 3 +- .../reactor/core/publisher/ParallelLog.java | 3 +- .../reactor/core/publisher/ParallelMap.java | 3 +- .../reactor/core/publisher/ParallelPeek.java | 3 +- .../core/publisher/ParallelReduceSeed.java | 3 +- .../reactor/core/publisher/ParallelRunOn.java | 3 +- .../core/publisher/ParallelSource.java | 9 +- .../core/publisher/SourceProducer.java | 7 +- .../AutomaticContextPropagationTest.java | 618 ++++++++++++++++++ .../publisher/ContextPropagationTest.java | 218 ++++++ .../core/publisher/ThreadSwitchingFlux.java | 68 ++ .../core/publisher/ThreadSwitchingMono.java | 68 ++ 97 files changed, 1266 insertions(+), 170 deletions(-) create mode 100644 reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java create mode 100644 reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java diff --git a/reactor-core/src/main/java/reactor/core/Scannable.java b/reactor-core/src/main/java/reactor/core/Scannable.java index 8f5ee597f0..3c03246567 100644 --- a/reactor-core/src/main/java/reactor/core/Scannable.java +++ b/reactor-core/src/main/java/reactor/core/Scannable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,12 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.Spliterators; import java.util.function.Function; import java.util.regex.Pattern; @@ -38,7 +36,6 @@ import reactor.core.scheduler.Scheduler.Worker; import reactor.util.annotation.Nullable; import reactor.util.function.Tuple2; -import reactor.util.function.Tuples; /** * A Scannable component exposes state in a non strictly memory consistent way and @@ -231,6 +228,8 @@ class Attr { */ public static final Attr LIFTER = new Attr<>(null); + public static final Attr INTERNAL_PRODUCER = new Attr<>(false); + /** * An {@link Enum} enumerating the different styles an operator can run : their {@link #ordinal()} reflects the level of confidence * in their running mode diff --git a/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxHide.java b/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxHide.java index e8c49a6565..bcf2779198 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxHide.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxHide.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return super.scanUnsafe(key); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxOnAssembly.java b/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxOnAssembly.java index 993e2fc7f6..35234024b6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxOnAssembly.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ConnectableFluxOnAssembly.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return super.scanUnsafe(key); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/ConnectableLift.java b/reactor-core/src/main/java/reactor/core/publisher/ConnectableLift.java index e4686ff659..35d7e86bc7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ConnectableLift.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ConnectableLift.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,8 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); if (key == Attr.LIFTER) return liftFunction.name; - return null; + + return super.scanUnsafe(key); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/ConnectableLiftFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/ConnectableLiftFuseable.java index 9dd538af45..b73831c3b9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ConnectableLiftFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ConnectableLiftFuseable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +56,8 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); if (key == Attr.LIFTER) return liftFunction.name; - return null; + + return super.scanUnsafe(key); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index 89afdf55b3..e122c03cf1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -16,6 +16,8 @@ package reactor.core.publisher; +import java.io.File; +import java.lang.reflect.ParameterizedType; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; @@ -44,6 +46,7 @@ import java.util.function.Supplier; import java.util.logging.Level; import java.util.stream.Collector; +import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -1060,6 +1063,7 @@ public static Flux firstWithValue(Publisher first, Publisher */ public static Flux from(Publisher source) { //duplicated in wrap, but necessary to detect early and thus avoid applying assembly + // TODO: Consider checking for INTERNAL_PRODUCER here and potentially lift if (source instanceof Flux) { @SuppressWarnings("unchecked") Flux casted = (Flux) source; @@ -8770,6 +8774,12 @@ public final void subscribe(Subscriber actual) { } } + // TODO: Consider: no need to wrap when subscriber is already wrapped and + // actually we should always wrap if we do the right job, as w can't intercept + // subscribe(CoreSubscriber), so this would be the last resort when the user + // directly subscribes. + subscriber = Operators.restoreContextOnSubscriberIfNecessary(publisher, subscriber); + publisher.subscribe(subscriber); } catch (Throwable e) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxArray.java b/reactor-core/src/main/java/reactor/core/publisher/FluxArray.java index 72870d6770..a288edc2bf 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxArray.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,8 +64,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.BUFFERED) return array.length; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - - return null; + return SourceProducer.super.scanUnsafe(key); } static final class ArraySubscription diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxBufferWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxBufferWhen.java index 80347fd5e8..8f92516842 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxBufferWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxBufferWhen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -431,7 +431,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.ERROR) return errors; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return InnerOperator.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCallable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCallable.java index d479901d20..f8bad62c58 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCallable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,6 @@ public T call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java index 5725871e83..b75e43ed5f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -180,7 +180,7 @@ else if (!(actual instanceof QueueSubscription)) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return prefetch; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class CombineLatestCoordinator diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java index f735711b59..701c2bebc7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,7 +83,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.DELAY_ERROR) return delayError; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } /** diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java index a4a1b00349..540f6eceb3 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class ConcatIterableSubscriber diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java index 27564cfc37..2984d9d092 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java @@ -105,7 +105,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } /** diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxDefer.java b/reactor-core/src/main/java/reactor/core/publisher/FluxDefer.java index 17a56d90e8..c8f116ccd9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxDefer.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxDefer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,6 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java b/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java index 128cde8bbf..c84e468987 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,13 +54,18 @@ public void subscribe(CoreSubscriber actual) { return; } - from(p).subscribe(actual); + // TODO: Here assembly hooks were applied, while in Mono's implementation, not + // even wrap (which skips assembly hooks) was called. + // We need to decide whether from/wrap perform the Subscriber wrapping instead + // of the below construct! + Flux flux = from(p); + flux.subscribe(Operators.restoreContextOnSubscriberIfNecessary(flux, actual)); } @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxEmpty.java b/reactor-core/src/main/java/reactor/core/publisher/FluxEmpty.java index 70bf235770..c4da511602 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxEmpty.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxEmpty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } /** diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxError.java b/reactor-core/src/main/java/reactor/core/publisher/FluxError.java index 930a3f62fb..bf853e2848 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxError.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxError.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,6 @@ public Object call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxErrorOnRequest.java b/reactor-core/src/main/java/reactor/core/publisher/FluxErrorOnRequest.java index c68f8bff1d..b819832d9d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxErrorOnRequest.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxErrorOnRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class ErrorSubscription implements InnerProducer { diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxErrorSupplied.java b/reactor-core/src/main/java/reactor/core/publisher/FluxErrorSupplied.java index c1f7cca997..af49df8a8d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxErrorSupplied.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxErrorSupplied.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,6 @@ public Object call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java index 5db4d8ca84..b95c15aa39 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -127,7 +127,7 @@ public void subscribe(CoreSubscriber actual) { new NullPointerException("The single source Publisher is null")); } else { - p.subscribe(actual); + p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, actual)); } return; } @@ -164,7 +164,7 @@ FluxFirstWithSignal orAdditionalSource(Publisher source) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class RaceCoordinator @@ -223,7 +223,7 @@ void subscribe(Publisher[] sources, return; } - p.subscribe(a[i]); + p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, a[i])); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java index bb71c0817c..e20ec188d6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -178,7 +178,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class RaceValuesCoordinator diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java index e14d6eb1fc..95f116b58b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java @@ -196,11 +196,14 @@ static boolean trySubscribeScalarMap(Publisher source, } } else { + // TODO: wrap Subscriber only or wrap the Publisher? What about + // assembly hooks? + CoreSubscriber sub = Operators.restoreContextOnSubscriberIfNecessary(p, s); if (!fuseableExpected || p instanceof Fuseable) { - p.subscribe(s); + p.subscribe(sub); } else { - p.subscribe(new FluxHide.SuppressFuseableSubscriber<>(s)); + p.subscribe(new FluxHide.SuppressFuseableSubscriber<>(sub)); } } @@ -424,7 +427,7 @@ else if (!delayError || !Exceptions.addThrowable(ERROR, this, e_)) { else { FlatMapInner inner = new FlatMapInner<>(this, prefetch); if (add(inner)) { - p.subscribe(inner); + p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, inner)); } else { Operators.onDiscard(t, actual.currentContext()); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java index bcecb5bbf6..ca0d19f318 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,7 @@ protected FluxFromMonoOperator(Mono source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java index 3416db971b..b4da8db435 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,7 +88,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class GenerateSubscription diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxInterval.java b/reactor-core/src/main/java/reactor/core/publisher/FluxInterval.java index 22e40a2922..c3b2a5f416 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxInterval.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxInterval.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,8 +80,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return timedScheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - - return null; + return SourceProducer.super.scanUnsafe(key); } static final class IntervalRunnable implements Runnable, Subscription, diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxIterable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxIterable.java index 30e8e704e0..12bb21a6e9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxIterable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxIterable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,7 +92,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } - return null; + return SourceProducer.super.scanUnsafe(key); } /** diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxJust.java b/reactor-core/src/main/java/reactor/core/publisher/FluxJust.java index 18efb5e48b..53ba8f424e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxJust.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxJust.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ public void subscribe(final CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.BUFFERED) return 1; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java index 3275a6b473..d0fceee9db 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,7 +106,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return prefetch; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } \ No newline at end of file diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java index a61480fa2a..98dc353dfe 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,8 +111,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return prefetch; if (key == Attr.DELAY_ERROR) return delayError; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - - return null; + return SourceProducer.super.scanUnsafe(key); } @Override @@ -396,7 +395,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested - emitted; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return InnerProducer.super.scanUnsafe(key); } } @@ -492,7 +491,7 @@ public Object scanUnsafe(Attr key){ if (key == Attr.BUFFERED) return queue.size(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return InnerOperator.super.scanUnsafe(key); } } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxNever.java b/reactor-core/src/main/java/reactor/core/publisher/FluxNever.java index b63f1ecf7b..b783bd4782 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxNever.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxNever.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } /** diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java index 20a519598b..590aff8bf9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,8 @@ package reactor.core.publisher; import java.util.Objects; -import java.util.function.BiFunction; -import java.util.function.Function; import org.reactivestreams.Publisher; -import reactor.core.CoreSubscriber; import reactor.core.Scannable; import reactor.util.annotation.Nullable; @@ -50,6 +47,7 @@ protected FluxOperator(Flux source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java b/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java index ef0d7587bf..2eaa0176f3 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java @@ -159,6 +159,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRange.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRange.java index 95e004de56..9c0391946a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRange.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class RangeSubscription implements InnerProducer, diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java b/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java index e6c1d2bcdc..a359a714fd 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1214,6 +1214,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java index 92fdfc4cfc..52821796dd 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java @@ -68,6 +68,7 @@ final class FluxSource extends Flux implements SourceProducer, @Override @SuppressWarnings("unchecked") public void subscribe(CoreSubscriber actual) { + // TODO: can we actually call Operators.restoreContextOnSubscriberIfNecessary ? if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { source.subscribe(new FluxSourceRestoringThreadLocalsSubscriber<>(actual)); } else { @@ -96,7 +97,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); - return null; + return SourceProducer.super.scanUnsafe(key); } static final class FluxSourceRestoringThreadLocalsSubscriber diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSourceFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSourceFuseable.java index ffbae0c511..04859c3fc7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSourceFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSourceFuseable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,6 +81,6 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Scannable.Attr.PREFETCH) return getPrefetch(); if (key == Scannable.Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxStream.java b/reactor-core/src/main/java/reactor/core/publisher/FluxStream.java index 270ddd2045..466a0be78d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxStream.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,6 +72,6 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java index 78a0dcd700..44c8d7047a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1013,7 +1013,7 @@ public final Object scanUnsafe(Attr key) { if (key == Attr.CANCELLED) return hasOutboundCancelled(this.parent.state); if (key == Attr.TERMINATED) return hasOutboundTerminated(this.parent.state); - return null; + return InnerOperator.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxUsing.java b/reactor-core/src/main/java/reactor/core/publisher/FluxUsing.java index 2053e68ffd..70f7da430e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxUsing.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxUsing.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -119,7 +119,7 @@ else if (actual instanceof ConditionalSubscriber) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class UsingSubscriber diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java index 774f7415c1..2c77695d0e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -107,7 +107,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } private static Publisher deriveFluxFromResource( diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java b/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java index fc6b2aded3..1f2568bead 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -321,7 +321,7 @@ void handleBoth(CoreSubscriber s, public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return prefetch; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class ZipScalarCoordinator implements InnerProducer, diff --git a/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java b/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java index d87576b941..1201dcf50b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java +++ b/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,6 +62,9 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } + if (key == Attr.INTERNAL_PRODUCER) { + return true; + } return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java index f8acbdef5d..01c04df879 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,9 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } + if (key == Attr.INTERNAL_PRODUCER) { + return true; + } return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java b/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java index 8245345c17..590c455c08 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ default Object scanUnsafe(Attr key){ if (key == Attr.ACTUAL) { return actual(); } + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java index 1af4962249..d0d3926eff 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,6 +90,7 @@ public final CorePublisher source() { public Object scanUnsafe(Scannable.Attr key) { if (key == Scannable.Attr.PREFETCH) return getPrefetch(); if (key == Scannable.Attr.PARENT) return source; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index 06b13d642f..7b3c79b64b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,12 +54,18 @@ public final void subscribe(CoreSubscriber subscriber) { while (true) { subscriber = operator.subscribeOrReturn(subscriber); if (subscriber == null) { + // if internally subscribed, it means the optimized operator is + // already within the internals and subscribing up the chain will + // at some point need to consider the source and wrap it + // null means "I will subscribe myself", returning... return; } OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { - operator.source().subscribe(subscriber); + CorePublisher operatorSource = operator.source(); + subscriber = Operators.restoreContextOnSubscriberIfNecessary(operatorSource, subscriber); + operatorSource.subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java index e21db81e8d..1ad680524c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,9 @@ public final void subscribe(CoreSubscriber subscriber) { } OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { - operator.source().subscribe(subscriber); + CorePublisher operatorSource = operator.source(); + subscriber = Operators.restoreContextOnSubscriberIfNecessary(operatorSource, subscriber); + operatorSource.subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index cc5752426d..26b81c233b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -4492,6 +4492,12 @@ public final void subscribe(Subscriber actual) { } } + // TODO: Consider: no need to wrap when subscriber is already wrapped and + // actually we should always wrap if we do the right job, as w can't intercept + // subscribe(CoreSubscriber), so this would be the last resort when the user + // directly subscribes. + subscriber = Operators.restoreContextOnSubscriberIfNecessary(publisher, subscriber); + publisher.subscribe(subscriber); } catch (Throwable e) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCallable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCallable.java index 313e13713b..a6fb161454 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCallable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,7 +75,7 @@ public T call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static class MonoCallableSubscription diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java index 1116722470..28f42c7f55 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class DefaultMonoSink extends AtomicBoolean diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java index 2ab29550ef..6238895e4f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java @@ -56,6 +56,6 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java index d5b2109f72..e581c12109 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,12 +52,12 @@ public void subscribe(CoreSubscriber actual) { return; } - p.subscribe(actual); + p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, actual)); } @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; //no particular key to be represented, still useful in hooks + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDelay.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDelay.java index 35b40d8ca6..c075ecc612 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDelay.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDelay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,8 +71,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return timedScheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - - return null; + return SourceProducer.super.scanUnsafe(key); } static final class MonoDelayRunnable implements Runnable, InnerProducer { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoEmpty.java b/reactor-core/src/main/java/reactor/core/publisher/MonoEmpty.java index 55e1a010df..5a69c4207a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoEmpty.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoEmpty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,6 +78,6 @@ public Object block() { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoError.java b/reactor-core/src/main/java/reactor/core/publisher/MonoError.java index d74dc02976..c06936018b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoError.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoError.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,6 @@ public Object call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoErrorSupplied.java b/reactor-core/src/main/java/reactor/core/publisher/MonoErrorSupplied.java index 339fc39c00..0c0a16cbb7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoErrorSupplied.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoErrorSupplied.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,6 +68,6 @@ public T call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java index d226e52ef9..161d265d93 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,7 +136,7 @@ public void subscribe(CoreSubscriber actual) { actual.currentContext())); } else { - p.subscribe(actual); + p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, actual)); } return; } @@ -150,7 +150,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } \ No newline at end of file diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java index 983148500a..cc6db08159 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -169,6 +169,6 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java index 61d36edb4f..17768f667d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,6 +62,7 @@ protected MonoFromFluxOperator(Flux source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return Integer.MAX_VALUE; if (key == Attr.PARENT) return source; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java index 9aa1156288..bf0de9c9ff 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java @@ -96,6 +96,9 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Scannable.Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } + if (key == Attr.INTERNAL_PRODUCER) { + return true; + } return null; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java index cfb21ef1e7..3aa48b892d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,6 +86,9 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } + if (key == Attr.INTERNAL_PRODUCER) { + return true; + } return null; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java index 7eef207f20..aceaa4ce65 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,6 +71,7 @@ MonoIgnoreThen shift(Mono newLast) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } @@ -237,7 +238,7 @@ void subscribeNext() { } onComplete(); } else { - m.subscribe(this); + m.subscribe(Operators.restoreContextOnSubscriberIfNecessary(m, this)); } return; } else { @@ -260,7 +261,7 @@ void subscribeNext() { continue; } - m.subscribe((CoreSubscriber) this); + m.subscribe((CoreSubscriber) Operators.restoreContextOnSubscriberIfNecessary(m, this)); return; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoJust.java b/reactor-core/src/main/java/reactor/core/publisher/MonoJust.java index 37368657a7..4c79998098 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoJust.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoJust.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,6 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.BUFFERED) return 1; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoNever.java b/reactor-core/src/main/java/reactor/core/publisher/MonoNever.java index e670ef94f0..6020091848 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoNever.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoNever.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } /** diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java index 5034d3c8c2..fda99e691d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,8 @@ package reactor.core.publisher; import java.util.Objects; -import java.util.function.BiFunction; -import java.util.function.Function; - import org.reactivestreams.Publisher; -import reactor.core.CoreSubscriber; import reactor.core.Scannable; import reactor.util.annotation.Nullable; @@ -51,6 +47,7 @@ protected MonoOperator(Mono source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return Integer.MAX_VALUE; if (key == Attr.PARENT) return source; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoRunnable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoRunnable.java index 1f30b8db31..e84ee52af8 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoRunnable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,7 +77,7 @@ public Void call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class MonoRunnableEagerSubscription extends AtomicBoolean implements Subscription { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java index 960edfebf9..fc728142e4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return prefetch; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class EqualCoordinator implements InnerProducer { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSingleCallable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSingleCallable.java index dcbdbdcdf4..f06c97ab0c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSingleCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSingleCallable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -122,6 +122,6 @@ else if (v == null) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSingleOptionalCallable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSingleOptionalCallable.java index 360e53ce45..f074b4620e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSingleOptionalCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSingleOptionalCallable.java @@ -93,6 +93,6 @@ public Optional call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java index d4bcccf472..58228c85ba 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java @@ -96,7 +96,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) { return Scannable.from(source).scanUnsafe(key); } - return null; + return SourceProducer.super.scanUnsafe(key); } static final class MonoSourceRestoringThreadLocalsSubscriber diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSupplier.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSupplier.java index ac0efa5999..7ed6bfcddd 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSupplier.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ public T call() throws Exception { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static class MonoSupplierSubscription diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java b/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java index 001fe3b7cd..c09a767c52 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -117,7 +117,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class MonoUsingSubscriber diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java index 73bbeee498..481ba9d18a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,7 +101,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } private static Mono deriveMonoFromResource( diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java index 0ea2aaae06..3348b843d7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,7 +104,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.DELAY_ERROR) return delayError; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class WhenCoordinator implements InnerProducer, diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java b/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java index dac6dacf1d..dc3aa0626f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -129,7 +129,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.DELAY_ERROR) return delayError; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + return SourceProducer.super.scanUnsafe(key); } static final class ZipCoordinator implements InnerProducer, diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 65fbf9e282..4b88f3e725 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -991,6 +991,64 @@ public static CorePublisher onLastAssembly(CorePublisher source) { } } + public static CoreSubscriber restoreContextOnSubscriberIfNecessary( + Publisher publisher, CoreSubscriber subscriber) { + + if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals() + // TODO: consider Scannable.from(subscriber).scanOrDefault(Scannable.Attr.TL_RESTORING_SUBSCRIBER, false) instead + || subscriber instanceof MonoSource.MonoSourceRestoringThreadLocalsSubscriber + || subscriber instanceof FluxSource.FluxSourceRestoringThreadLocalsSubscriber) { + return subscriber; + } + + Scannable scannable = Scannable.from(publisher); + // TODO: REPORT AS INTERNAL EVERYWHERE IN reactor-core + boolean internal = scannable.isScanAvailable() + && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); + if (!internal) { + if (publisher instanceof Mono) { + subscriber = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(subscriber); + } else { + subscriber = new FluxSource.FluxSourceRestoringThreadLocalsSubscriber<>(subscriber); + } + } + return subscriber; + } + +// public static CoreSubscriber restoreContextOnSubscriberIfNecessary( +// Mono mono, CoreSubscriber subscriber) { +// +// if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { +// return subscriber; +// } +// +// Scannable scannable = Scannable.from(mono); +// // TODO: REPORT AS INTERNAL EVERYWHERE IN reactor-core +// boolean internal = scannable.isScanAvailable() +// && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); +// if (!internal) { +// subscriber = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(subscriber); +// } +// return subscriber; +// } +// +// public static CoreSubscriber restoreContextOnSubscriberIfNecessary( +// Flux flux, CoreSubscriber subscriber) { +// +// if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { +// return subscriber; +// } +// +// Scannable scannable = Scannable.from(flux); +// // TODO: REPORT AS INTERNAL EVERYWHERE IN reactor-core +// boolean internal = scannable.isScanAvailable() +// && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); +// if (!internal) { +// subscriber = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(subscriber); +// } +// return subscriber; +// } + private static Throwable unwrapOnNextError(Throwable error) { return Exceptions.isBubbling(error) ? error : Exceptions.unwrap(error); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java index ff385e3a18..a045481d1f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java index 58bcff0661..899f344fbc 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.DELAY_ERROR) return errorMode != ErrorMode.IMMEDIATE; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java index 03f1a6e134..db1507fca9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,6 +98,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java index 1b960d0b3b..75008be175 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java index 21e2316d88..51192a9b95 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,6 +69,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.DELAY_ERROR) return delayError; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java index abcd3df8fa..25725eeecf 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java index 8053ddc06f..ef161f2101 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,6 +113,8 @@ public Object scanUnsafe(Attr key) { return SYNC; } + if (key == Attr.INTERNAL_PRODUCER) return true; + return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java index e0672b2507..c524405428 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,6 +97,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java index 2f186c2d29..5ef80ffa9d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,6 +62,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java index 4fd1025a3f..a9e9edf31f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java index 60ae219317..d51cb36c62 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,6 +81,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java index ee0a619914..dc7e1aca4f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java index 4b0bbda432..1bd2f187c1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -153,6 +153,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java index 131f00b263..88bddd42c0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java index c58af4ae37..3907f0604c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java index 8453552a7a..8c0f612678 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,11 @@ final class ParallelSource extends ParallelFlux implements Scannable { if (prefetch <= 0) { throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); } - this.source = source; + // TODO: this can be improved to restore TLs in ParallelSourceMain or by + // modifying the from() method to lift the source to wrap the Subscriber upon + // subscription. + // FIXME: when source is a Flux, but not a INTERNAL_PRODUCER, this won't help + this.source = Flux.from(source); this.parallelism = parallelism; this.prefetch = prefetch; this.queueSupplier = queueSupplier; @@ -76,6 +80,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java b/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java index b00f302522..7a6e6e483d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,8 +36,9 @@ interface SourceProducer extends Scannable, Publisher { @Override @Nullable default Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return Scannable.from(null); - if (key == Attr.ACTUAL) return Scannable.from(null); + if (key == Attr.PARENT) return null; + if (key == Attr.ACTUAL) return null; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 00736448ee..b8a8022d9a 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -16,7 +16,11 @@ package reactor.core.publisher; +import java.io.File; import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -25,7 +29,11 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; import io.micrometer.context.ContextRegistry; import org.junit.jupiter.api.AfterAll; @@ -35,6 +43,10 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; import reactor.core.scheduler.Schedulers; import reactor.test.publisher.TestPublisher; import reactor.test.subscriber.TestSubscriber; @@ -290,6 +302,506 @@ void monoApiUsesContextPropagationConstantFunction() { .isSameAs(ContextPropagation.WITH_GLOBAL_REGISTRY_NO_PREDICATE)); } + @Nested + class NonReactorFluxOrMono { + + private ExecutorService executorService; + + @BeforeEach + void enableAutomaticContextPropagation() { + executorService = Executors.newSingleThreadExecutor(); + } + + @AfterEach + void cleanupThreadLocals() { + executorService.shutdownNow(); + } + + // Scaffold methods + + void assertThreadLocalPresentInOnNext(Mono chain) { + AtomicReference value = new AtomicReference<>(); + + chain.doOnNext(item -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .block(); + + assertThat(value.get()).isEqualTo("present"); + } + + void assertThreadLocalPresentInOnNext(Flux chain) { + AtomicReference value = new AtomicReference<>(); + + chain.doOnNext(item -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + + // Basic tests for Flux + + @Test + void chainedFluxSubscribe() { + ThreadSwitchingFlux chain = new ThreadSwitchingFlux<>("Hello", executorService); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void internalFluxSubscribe() { + ThreadSwitchingFlux inner = new ThreadSwitchingFlux<>("Hello", executorService); + Flux chain = Flux.just("hello").flatMap(item -> inner); + + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void internalFluxSubscribeNoFusion() { + ThreadSwitchingFlux inner = new ThreadSwitchingFlux<>("Hello", executorService); + Flux chain = Flux.just("hello").hide().flatMap(item -> inner); + + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void testFluxSubscriberAsRawSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + + TestSubscriber testSubscriber = + TestSubscriber.builder().contextPut(KEY, "present").build(); + + flux + .doOnNext(i -> value.set(REF.get())) + .subscribe((Subscriber) testSubscriber); + + testSubscriber.block(Duration.ofMillis(10)); + assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void testFluxSubscriberAsCoreSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + + TestSubscriber testSubscriber = + TestSubscriber.builder().contextPut(KEY, "present").build(); + + flux + .doOnNext(i -> value.set(REF.get())) + .subscribe(testSubscriber); + + testSubscriber.block(Duration.ofMillis(10)); + assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); + // Because of onNext in the chain, the internal operator implementation is + // able to wrap the subscriber and restore the ThreadLocal values + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void directFluxSubscribeAsCoreSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext(value, error, latch, complete); + + flux.subscribe(subscriberWithContext); + + latch.await(10, TimeUnit.MILLISECONDS); + + assertThat(error.get()).isNull(); + assertThat(complete.get()).isTrue(); + + // We can't do anything here. subscribe(CoreSubscriber) is abstract in + // CoreSubscriber interface and we have no means to intercept the calls to + // restore ThreadLocals. + assertThat(value.get()).isEqualTo("ref_init"); + } + + @Test + void directFluxSubscribeAsRawSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext(value, error, latch, complete); + + // We force the use of subscribe(Subscriber) override instead of + // subscribe(CoreSubscriber), and we can observe that for such a case we + // are able to wrap the Subscriber and restore ThreadLocal values for the + // signals received downstream. + flux.subscribe((Subscriber) subscriberWithContext); + + latch.await(10, TimeUnit.MILLISECONDS); + + assertThat(error.get()).isNull(); + assertThat(complete.get()).isTrue(); + assertThat(value.get()).isEqualTo("present"); + } + + // Basic tests for Mono + + @Test + void chainedMonoSubscribe() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void internalMonoSubscribe() { + Mono inner = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.just("hello").flatMap(item -> inner); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void testMonoSubscriberAsRawSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + + TestSubscriber testSubscriber = + TestSubscriber.builder().contextPut(KEY, "present").build(); + + mono + .doOnNext(i -> value.set(REF.get())) + .subscribe((Subscriber) testSubscriber); + + testSubscriber.block(Duration.ofMillis(10)); + assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void testMonoSubscriberAsCoreSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + + TestSubscriber testSubscriber = + TestSubscriber.builder().contextPut(KEY, "present").build(); + + mono + .doOnNext(i -> value.set(REF.get())) + .subscribe(testSubscriber); + + testSubscriber.block(Duration.ofMillis(10)); + assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); + // Because of onNext in the chain, the internal operator implementation is + // able to wrap the subscriber and restore the ThreadLocal values + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void directMonoSubscribeAsCoreSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext(value, error, latch, complete); + + mono.subscribe(subscriberWithContext); + + latch.await(10, TimeUnit.MILLISECONDS); + + assertThat(error.get()).isNull(); + assertThat(complete.get()).isTrue(); + + // We can't do anything here. subscribe(CoreSubscriber) is abstract in + // CoreSubscriber interface and we have no means to intercept the calls to + // restore ThreadLocals. + assertThat(value.get()).isEqualTo("ref_init"); + } + + @Test + void directMonoSubscribeAsRawSubscriber() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext(value, error, latch, complete); + + // We force the use of subscribe(Subscriber) override instead of + // subscribe(CoreSubscriber), and we can observe that for such a case we + // are able to wrap the Subscriber and restore ThreadLocal values for the + // signals received downstream. + mono.subscribe((Subscriber) subscriberWithContext); + + latch.await(10, TimeUnit.MILLISECONDS); + + assertThat(error.get()).isNull(); + assertThat(complete.get()).isTrue(); + assertThat(value.get()).isEqualTo("present"); + } + + // Flux tests + + @Test + void fluxIgnoreThenSwitchThread() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Flux.just("Bye").then(mono); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void fluxSwitchThreadThenIgnore() { + Flux flux = new ThreadSwitchingFlux<>("Ignored", executorService); + Mono chain = flux.then(Mono.just("Hello")); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void fluxDeferContextual() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + Flux chain = Flux.deferContextual(ctx -> flux); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void fluxFirstWithSignalArray() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + Flux chain = Flux.firstWithSignal(flux); + assertThreadLocalPresentInOnNext(chain); + + Flux other = new ThreadSwitchingFlux<>("Hello", executorService); + assertThreadLocalPresentInOnNext(chain.or(other)); + } + + @Test + void fluxFirstWithSignalIterable() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + Flux chain = Flux.firstWithSignal(Collections.singletonList(flux)); + assertThreadLocalPresentInOnNext(chain); + + Flux other1 = new ThreadSwitchingFlux<>("Hello", executorService); + Flux other2 = new ThreadSwitchingFlux<>("Hello", executorService); + List> list = Stream.of(other1, other2).collect(Collectors.toList()); + assertThreadLocalPresentInOnNext(Flux.firstWithSignal(list)); + } + + // Mono tests + + @Test + void monoSwitchThreadIgnoreThen() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = mono.then(Mono.just("Bye")); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void monoIgnoreThenSwitchThread() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.just("Bye").then(mono); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void monoDeferContextual() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.deferContextual(ctx -> mono); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void monoFirstWithSignalArray() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.firstWithSignal(mono); + assertThreadLocalPresentInOnNext(chain); + + Mono other = new ThreadSwitchingMono<>("Hello", executorService); + assertThreadLocalPresentInOnNext(chain.or(other)); + } + + @Test + void monoFirstWithSignalIterable() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.firstWithSignal(Collections.singletonList(mono)); + assertThreadLocalPresentInOnNext(chain); + + Mono other1 = new ThreadSwitchingMono<>("Hello", executorService); + Mono other2 = new ThreadSwitchingMono<>("Hello", executorService); + List> list = Stream.of(other1, other2).collect(Collectors.toList()); + assertThreadLocalPresentInOnNext(Mono.firstWithSignal(list)); + } + + // ParallelFlux tests + + @Test + void fuseableParallelFluxToMono() { + Mono flux = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.from(ParallelFlux.from(flux)); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void parallelFlux() { + AtomicReference value = new AtomicReference<>(); + + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + + ParallelFlux.from(mono) + .doOnNext(i -> value.set(REF.get())) + .sequential() + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + + // Sinks tests + + @Test + void sink() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.One sink = Sinks.one(); + + sink.asMono() + .doOnNext(i -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(() -> sink.tryEmitValue(1)); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + + // Other + + List> getAllClassesInClasspathRecursively(File directory) throws Exception { + List> classes = new ArrayList<>(); + + for (File file : directory.listFiles()) { + if (file.isDirectory()) { + classes.addAll(getAllClassesInClasspathRecursively(file)); + } else if (file.getName().endsWith(".class") ) { + String path = file.getPath(); + path = path.replace("./build/classes/java/main/reactor/", ""); + String pkg = path.substring(0, path.lastIndexOf("/") + 1).replace("/", + "."); + String name = path.substring(path.lastIndexOf("/") + 1).replace(".class", ""); + try { + classes.add(Class.forName("reactor." + pkg + name)); + } + catch (ClassNotFoundException ex) { + System.out.println("Ignoring " + pkg + name); + } catch (NoClassDefFoundError err) { + System.out.println("Ignoring " + pkg + name); + } + } + } + + return classes; + } + + @Test + void printInterestingClasses() throws Exception { + List> allClasses = + getAllClassesInClasspathRecursively(new File("./build/classes/java/main/reactor/")); + + System.out.println("Classes that are Publisher, but not SourceProducer, " + + "ConnectableFlux, ParallelFlux, GroupedFlux, MonoFromFluxOperator, " + + "FluxFromMonoOperator:"); + for (Class c : allClasses) { + if (Publisher.class.isAssignableFrom(c) && !SourceProducer.class.isAssignableFrom(c) + && !ConnectableFlux.class.isAssignableFrom(c) + && !ParallelFlux.class.isAssignableFrom(c) + && !GroupedFlux.class.isAssignableFrom(c) + && !MonoFromFluxOperator.class.isAssignableFrom(c) + && !FluxFromMonoOperator.class.isAssignableFrom(c)) { + if (Flux.class.isAssignableFrom(c) && !FluxOperator.class.isAssignableFrom(c)) { + System.out.println(c.getName()); + } + if (Mono.class.isAssignableFrom(c) && !MonoOperator.class.isAssignableFrom(c)) { + System.out.println(c.getName()); + } + } + } + + System.out.println("Classes that are Fuseable and Publisher but not Mono or Flux, ?"); + for (Class c : allClasses) { + if (Fuseable.class.isAssignableFrom(c) && Publisher.class.isAssignableFrom(c) + && !Mono.class.isAssignableFrom(c) + && !Flux.class.isAssignableFrom(c)) { + System.out.println(c.getName()); + } + } + } + + private class CoreSubscriberWithContext implements CoreSubscriber { + + private final AtomicReference value; + private final AtomicReference error; + private final CountDownLatch latch; + private final AtomicBoolean complete; + + public CoreSubscriberWithContext(AtomicReference value, + AtomicReference error, + CountDownLatch latch, + AtomicBoolean complete) { + this.value = value; + this.error = error; + this.latch = latch; + this.complete = complete; + } + + @Override + public Context currentContext() { + return Context.of(KEY, "present"); + } + + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(String s) { + value.set(REF.get()); + } + + @Override + public void onError(Throwable t) { + error.set(t); + latch.countDown(); + } + + @Override + public void onComplete() { + complete.set(true); + latch.countDown(); + } + } + } + @Nested class NonReactorSources { @Test @@ -326,6 +838,41 @@ void fluxFromPublisher() throws InterruptedException, ExecutionException { executorService.shutdownNow(); } + @Test + void fluxFlatMapToPublisher() throws InterruptedException, ExecutionException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + AtomicReference value = new AtomicReference<>(); + + TestPublisher testPublisher = TestPublisher.create(); + Publisher nonReactorPublisher = testPublisher; + + Flux.just("hello") + .flatMap(s -> nonReactorPublisher) + .doOnNext(s -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService + .submit(() -> testPublisher.emit("test").complete()) + .get(); + + testPublisher.assertWasSubscribed(); + testPublisher.assertWasNotCancelled(); + testPublisher.assertWasRequested(); + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF.get())).get(); + + assertThat(value.get()).isEqualTo("ref_init"); + + // validate the current Thread does not have the value set either + assertThat(REF.get()).isEqualTo("ref_init"); + + executorService.shutdownNow(); + } + @Test void monoFromPublisher() throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newSingleThreadExecutor(); @@ -480,6 +1027,77 @@ void monoFromFuture() throws ExecutionException, InterruptedException { executorService.shutdownNow(); } + + @Test + void fluxMerge() throws ExecutionException, InterruptedException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + AtomicReference value = new AtomicReference<>(); + + TestPublisher testPublisher = TestPublisher.create(); + Publisher nonReactorPublisher = testPublisher; + + Flux.merge(Flux.empty(), nonReactorPublisher) + .doOnNext(s -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService + .submit(() -> testPublisher.emit("test").complete()) + .get(); + + testPublisher.assertWasSubscribed(); + testPublisher.assertWasNotCancelled(); + testPublisher.assertWasRequested(); + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF.get())).get(); + + assertThat(value.get()).isEqualTo("ref_init"); + + // validate the current Thread does not have the value set either + assertThat(REF.get()).isEqualTo("ref_init"); + + executorService.shutdownNow(); + } + + @Test + void parallelFlux() throws ExecutionException, InterruptedException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + AtomicReference value = new AtomicReference<>(); + + TestPublisher testPublisher = TestPublisher.create(); + Publisher nonReactorPublisher = testPublisher; + + ParallelFlux.from(nonReactorPublisher) + .doOnNext(i -> value.set(REF.get())) + .sequential() + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService + .submit(() -> testPublisher.emit("test").complete()) + .get(); + + testPublisher.assertWasSubscribed(); + testPublisher.assertWasNotCancelled(); + testPublisher.assertWasRequested(); + + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF.get())).get(); + + assertThat(value.get()).isEqualTo("ref_init"); + + // validate the current Thread does not have the value set either + assertThat(REF.get()).isEqualTo("ref_init"); + + executorService.shutdownNow(); + } } @Nested diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java index be06b14ad9..6d5ae85190 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java @@ -143,6 +143,224 @@ void contextCaptureFunctionWithoutFiltering() { } } + @Nested + class NonReactorFluxOrMono { + + @Test + void nonReactorFlux() { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Hooks.enableAutomaticContextPropagation(); + + AtomicReference value = new AtomicReference<>(); + + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + flux + .doOnNext(item -> value.set(REF1.get())) + .contextWrite(Context.of(KEY1, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + executorService.shutdownNow(); + } + } + + @Nested + class NonReactorSources { + @Test + void fluxFromPublisher() throws InterruptedException, ExecutionException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Hooks.enableAutomaticContextPropagation(); + AtomicReference value = new AtomicReference<>(); + + TestPublisher testPublisher = TestPublisher.create(); + Publisher nonReactorPublisher = testPublisher; + + Flux.from(nonReactorPublisher) + .doOnNext(s -> value.set(REF1.get())) + .contextWrite(Context.of(KEY1, "present")) + .subscribe(); + + executorService + .submit(() -> testPublisher.emit("test").complete()) + .get(); + + testPublisher.assertWasSubscribed(); + testPublisher.assertWasNotCancelled(); + testPublisher.assertWasRequested(); + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF1.get())).get(); + + assertThat(value.get()).isEqualTo("ref1_init"); + + // validate the current Thread does not have the value set either + assertThat(REF1.get()).isEqualTo("ref1_init"); + + executorService.shutdownNow(); + } + + @Test + void monoFromPublisher() throws InterruptedException, ExecutionException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Hooks.enableAutomaticContextPropagation(); + AtomicReference value = new AtomicReference<>(); + + TestPublisher testPublisher = TestPublisher.create(); + Publisher nonReactorPublisher = testPublisher; + + Mono.from(nonReactorPublisher) + .doOnNext(s -> value.set(REF1.get())) + .contextWrite(Context.of(KEY1, "present")) + .subscribe(); + + executorService + .submit(() -> testPublisher.emit("test").complete()) + .get(); + + testPublisher.assertWasSubscribed(); + testPublisher.assertCancelled(); + testPublisher.assertWasRequested(); + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF1.get())).get(); + + assertThat(value.get()).isEqualTo("ref1_init"); + + // validate the current Thread does not have the value set either + assertThat(REF1.get()).isEqualTo("ref1_init"); + + executorService.shutdownNow(); + } + + @Test + void monoFromPublisherIgnoringContract() + throws InterruptedException, ExecutionException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Hooks.enableAutomaticContextPropagation(); + AtomicReference value = new AtomicReference<>(); + + TestPublisher testPublisher = TestPublisher.create(); + Publisher nonReactorPublisher = testPublisher; + + Mono.fromDirect(nonReactorPublisher) + .doOnNext(s -> value.set(REF1.get())) + .contextWrite(Context.of(KEY1, "present")) + .subscribe(); + + executorService + .submit(() -> testPublisher.emit("test").complete()) + .get(); + + testPublisher.assertWasSubscribed(); + testPublisher.assertWasNotCancelled(); + testPublisher.assertWasRequested(); + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF1.get())).get(); + + assertThat(value.get()).isEqualTo("ref1_init"); + + // validate the current Thread does not have the value set either + assertThat(REF1.get()).isEqualTo("ref1_init"); + + executorService.shutdownNow(); + } + + @Test + void monoFromCompletionStage() throws ExecutionException, InterruptedException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + Hooks.enableAutomaticContextPropagation(); + CountDownLatch latch = new CountDownLatch(1); + AtomicReference value = new AtomicReference<>(); + + // we need to delay delivery to ensure the completion signal is delivered + // on a Thread from executorService + CompletionStage completionStage = CompletableFuture.supplyAsync(() -> { + try { + latch.await(); + } + catch (InterruptedException e) { + // ignore + } + return "test"; + }, executorService); + + TestSubscriber testSubscriber = TestSubscriber.create(); + + Mono.fromCompletionStage(completionStage) + .doOnNext(s -> value.set(REF1.get())) + .contextWrite(Context.of(KEY1, "present")) + .subscribe(testSubscriber); + + latch.countDown(); + testSubscriber.block(); + + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF1.get())).get(); + + assertThat(value.get()).isEqualTo("ref1_init"); + + // validate the current Thread does not have the value set either + assertThat(REF1.get()).isEqualTo("ref1_init"); + + executorService.shutdownNow(); + } + + @Test + void monoFromFuture() throws ExecutionException, InterruptedException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + Hooks.enableAutomaticContextPropagation(); + CountDownLatch latch = new CountDownLatch(1); + AtomicReference value = new AtomicReference<>(); + + // we need to delay delivery to ensure the completion signal is delivered + // on a Thread from executorService + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + latch.await(); + } + catch (InterruptedException e) { + // ignore + } + return "test"; + }, executorService); + + TestSubscriber testSubscriber = TestSubscriber.create(); + + Mono.fromFuture(future) + .doOnNext(s -> value.set(REF1.get())) + .contextWrite(Context.of(KEY1, "present")) + .subscribe(testSubscriber); + + latch.countDown(); + testSubscriber.block(); + + assertThat(value.get()).isEqualTo("present"); + + // validate there are no leftovers for other tasks to be attributed to + // previous values + executorService.submit(() -> value.set(REF1.get())).get(); + + assertThat(value.get()).isEqualTo("ref1_init"); + + // validate the current Thread does not have the value set either + assertThat(REF1.get()).isEqualTo("ref1_init"); + + executorService.shutdownNow(); + } + } + // TAP AND HANDLE OPERATOR TESTS static private enum Cases { diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java new file mode 100644 index 0000000000..5fae7af964 --- /dev/null +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class ThreadSwitchingFlux extends Flux implements Subscription, Runnable { + + private final ExecutorService executorService; + private final T item; + private CoreSubscriber actual; + AtomicBoolean done = new AtomicBoolean(); + + public ThreadSwitchingFlux(T item, ExecutorService executorService) { + this.item = item; + this.executorService = executorService; + } + + @Override + public void subscribe(CoreSubscriber actual) { + this.actual = actual; + this.executorService.submit(this); + } + + @Override + public void run() { + this.actual.onSubscribe(this); + } + + private void deliver() { + if (done.compareAndSet(false, true)) { + this.actual.onNext(this.item); + this.actual.onComplete(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (!done.get()) { + this.executorService.submit(this::deliver); + } + } + } + + @Override + public void cancel() { + done.set(true); + } +} \ No newline at end of file diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java new file mode 100644 index 0000000000..f0e2925213 --- /dev/null +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class ThreadSwitchingMono extends Mono implements Subscription, Runnable { + + private final ExecutorService executorService; + private final T item; + private CoreSubscriber actual; + AtomicBoolean done = new AtomicBoolean(); + + public ThreadSwitchingMono(T item, ExecutorService executorService) { + this.item = item; + this.executorService = executorService; + } + + @Override + public void subscribe(CoreSubscriber actual) { + this.actual = actual; + this.executorService.submit(this); + } + + @Override + public void run() { + this.actual.onSubscribe(this); + } + + private void deliver() { + if (done.compareAndSet(false, true)) { + this.actual.onNext(this.item); + this.actual.onComplete(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (!done.get()) { + this.executorService.submit(this::deliver); + } + } + } + + @Override + public void cancel() { + done.set(true); + } +} \ No newline at end of file From 6c5a5002b11bb04fad820313f3ca2e493e5a566c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Thu, 27 Jul 2023 12:11:02 +0200 Subject: [PATCH 02/26] Cleared tests from previously removed code --- .../publisher/ContextPropagationTest.java | 218 ------------------ 1 file changed, 218 deletions(-) diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java index 6d5ae85190..be06b14ad9 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ContextPropagationTest.java @@ -143,224 +143,6 @@ void contextCaptureFunctionWithoutFiltering() { } } - @Nested - class NonReactorFluxOrMono { - - @Test - void nonReactorFlux() { - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Hooks.enableAutomaticContextPropagation(); - - AtomicReference value = new AtomicReference<>(); - - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - flux - .doOnNext(item -> value.set(REF1.get())) - .contextWrite(Context.of(KEY1, "present")) - .blockLast(); - - assertThat(value.get()).isEqualTo("present"); - executorService.shutdownNow(); - } - } - - @Nested - class NonReactorSources { - @Test - void fluxFromPublisher() throws InterruptedException, ExecutionException { - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Hooks.enableAutomaticContextPropagation(); - AtomicReference value = new AtomicReference<>(); - - TestPublisher testPublisher = TestPublisher.create(); - Publisher nonReactorPublisher = testPublisher; - - Flux.from(nonReactorPublisher) - .doOnNext(s -> value.set(REF1.get())) - .contextWrite(Context.of(KEY1, "present")) - .subscribe(); - - executorService - .submit(() -> testPublisher.emit("test").complete()) - .get(); - - testPublisher.assertWasSubscribed(); - testPublisher.assertWasNotCancelled(); - testPublisher.assertWasRequested(); - assertThat(value.get()).isEqualTo("present"); - - // validate there are no leftovers for other tasks to be attributed to - // previous values - executorService.submit(() -> value.set(REF1.get())).get(); - - assertThat(value.get()).isEqualTo("ref1_init"); - - // validate the current Thread does not have the value set either - assertThat(REF1.get()).isEqualTo("ref1_init"); - - executorService.shutdownNow(); - } - - @Test - void monoFromPublisher() throws InterruptedException, ExecutionException { - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Hooks.enableAutomaticContextPropagation(); - AtomicReference value = new AtomicReference<>(); - - TestPublisher testPublisher = TestPublisher.create(); - Publisher nonReactorPublisher = testPublisher; - - Mono.from(nonReactorPublisher) - .doOnNext(s -> value.set(REF1.get())) - .contextWrite(Context.of(KEY1, "present")) - .subscribe(); - - executorService - .submit(() -> testPublisher.emit("test").complete()) - .get(); - - testPublisher.assertWasSubscribed(); - testPublisher.assertCancelled(); - testPublisher.assertWasRequested(); - assertThat(value.get()).isEqualTo("present"); - - // validate there are no leftovers for other tasks to be attributed to - // previous values - executorService.submit(() -> value.set(REF1.get())).get(); - - assertThat(value.get()).isEqualTo("ref1_init"); - - // validate the current Thread does not have the value set either - assertThat(REF1.get()).isEqualTo("ref1_init"); - - executorService.shutdownNow(); - } - - @Test - void monoFromPublisherIgnoringContract() - throws InterruptedException, ExecutionException { - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Hooks.enableAutomaticContextPropagation(); - AtomicReference value = new AtomicReference<>(); - - TestPublisher testPublisher = TestPublisher.create(); - Publisher nonReactorPublisher = testPublisher; - - Mono.fromDirect(nonReactorPublisher) - .doOnNext(s -> value.set(REF1.get())) - .contextWrite(Context.of(KEY1, "present")) - .subscribe(); - - executorService - .submit(() -> testPublisher.emit("test").complete()) - .get(); - - testPublisher.assertWasSubscribed(); - testPublisher.assertWasNotCancelled(); - testPublisher.assertWasRequested(); - assertThat(value.get()).isEqualTo("present"); - - // validate there are no leftovers for other tasks to be attributed to - // previous values - executorService.submit(() -> value.set(REF1.get())).get(); - - assertThat(value.get()).isEqualTo("ref1_init"); - - // validate the current Thread does not have the value set either - assertThat(REF1.get()).isEqualTo("ref1_init"); - - executorService.shutdownNow(); - } - - @Test - void monoFromCompletionStage() throws ExecutionException, InterruptedException { - ExecutorService executorService = Executors.newSingleThreadExecutor(); - - Hooks.enableAutomaticContextPropagation(); - CountDownLatch latch = new CountDownLatch(1); - AtomicReference value = new AtomicReference<>(); - - // we need to delay delivery to ensure the completion signal is delivered - // on a Thread from executorService - CompletionStage completionStage = CompletableFuture.supplyAsync(() -> { - try { - latch.await(); - } - catch (InterruptedException e) { - // ignore - } - return "test"; - }, executorService); - - TestSubscriber testSubscriber = TestSubscriber.create(); - - Mono.fromCompletionStage(completionStage) - .doOnNext(s -> value.set(REF1.get())) - .contextWrite(Context.of(KEY1, "present")) - .subscribe(testSubscriber); - - latch.countDown(); - testSubscriber.block(); - - assertThat(value.get()).isEqualTo("present"); - - // validate there are no leftovers for other tasks to be attributed to - // previous values - executorService.submit(() -> value.set(REF1.get())).get(); - - assertThat(value.get()).isEqualTo("ref1_init"); - - // validate the current Thread does not have the value set either - assertThat(REF1.get()).isEqualTo("ref1_init"); - - executorService.shutdownNow(); - } - - @Test - void monoFromFuture() throws ExecutionException, InterruptedException { - ExecutorService executorService = Executors.newSingleThreadExecutor(); - - Hooks.enableAutomaticContextPropagation(); - CountDownLatch latch = new CountDownLatch(1); - AtomicReference value = new AtomicReference<>(); - - // we need to delay delivery to ensure the completion signal is delivered - // on a Thread from executorService - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - latch.await(); - } - catch (InterruptedException e) { - // ignore - } - return "test"; - }, executorService); - - TestSubscriber testSubscriber = TestSubscriber.create(); - - Mono.fromFuture(future) - .doOnNext(s -> value.set(REF1.get())) - .contextWrite(Context.of(KEY1, "present")) - .subscribe(testSubscriber); - - latch.countDown(); - testSubscriber.block(); - - assertThat(value.get()).isEqualTo("present"); - - // validate there are no leftovers for other tasks to be attributed to - // previous values - executorService.submit(() -> value.set(REF1.get())).get(); - - assertThat(value.get()).isEqualTo("ref1_init"); - - // validate the current Thread does not have the value set either - assertThat(REF1.get()).isEqualTo("ref1_init"); - - executorService.shutdownNow(); - } - } - // TAP AND HANDLE OPERATOR TESTS static private enum Cases { From 75bd4d0942053d1511b67254b215924ec314b2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Thu, 31 Aug 2023 19:50:43 +0200 Subject: [PATCH 03/26] ParallelFlux impl, wrapping Publishers instead of Subscribers only --- .../java/reactor/core/publisher/Flux.java | 39 ++++-- .../core/publisher/FluxDeferContextual.java | 3 +- .../core/publisher/FluxFirstWithSignal.java | 4 +- .../reactor/core/publisher/FluxFlatMap.java | 9 +- .../publisher/FluxRestoringThreadLocals.java | 46 ++++++ .../reactor/core/publisher/FluxSource.java | 98 +------------ .../core/publisher/InternalFluxOperator.java | 3 +- .../core/publisher/InternalMonoOperator.java | 3 +- .../java/reactor/core/publisher/Mono.java | 69 ++++++--- .../core/publisher/MonoDeferContextual.java | 2 +- .../core/publisher/MonoFirstWithSignal.java | 2 +- .../core/publisher/MonoFromFluxOperator.java | 2 +- .../core/publisher/MonoFromPublisher.java | 4 - .../core/publisher/MonoIgnoreThen.java | 4 +- .../publisher/MonoRestoringThreadLocals.java | 46 ++++++ .../reactor/core/publisher/MonoSource.java | 92 +----------- .../reactor/core/publisher/Operators.java | 108 +++++---------- .../core/publisher/ParallelCollect.java | 2 +- .../core/publisher/ParallelConcatMap.java | 2 +- .../core/publisher/ParallelDoOnEach.java | 2 +- .../core/publisher/ParallelFilter.java | 2 +- .../core/publisher/ParallelFlatMap.java | 2 +- .../reactor/core/publisher/ParallelFlux.java | 8 ++ .../core/publisher/ParallelFluxHide.java | 2 +- .../core/publisher/ParallelFluxName.java | 2 +- .../publisher/ParallelFluxOnAssembly.java | 2 +- .../ParallelFluxRestoringThreadLocals.java | 36 +++++ .../reactor/core/publisher/ParallelGroup.java | 3 +- .../reactor/core/publisher/ParallelLift.java | 5 +- .../core/publisher/ParallelLiftFuseable.java | 7 +- .../reactor/core/publisher/ParallelLog.java | 2 +- .../reactor/core/publisher/ParallelMap.java | 2 +- .../core/publisher/ParallelMergeOrdered.java | 3 +- .../core/publisher/ParallelMergeReduce.java | 3 +- .../publisher/ParallelMergeSequential.java | 3 +- .../core/publisher/ParallelMergeSort.java | 3 +- .../reactor/core/publisher/ParallelPeek.java | 2 +- .../core/publisher/ParallelReduceSeed.java | 2 +- .../reactor/core/publisher/ParallelRunOn.java | 2 +- .../core/publisher/ParallelSource.java | 6 +- .../reactor/core/publisher/ParallelThen.java | 3 +- .../AutomaticContextPropagationTest.java | 131 +++++++++++++++++- .../ThreadSwitchingParallelFlux.java | 62 +++++++++ 43 files changed, 484 insertions(+), 349 deletions(-) create mode 100644 reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java create mode 100644 reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java create mode 100644 reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java create mode 100644 reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index e122c03cf1..5d3ca291ef 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -1063,8 +1063,8 @@ public static Flux firstWithValue(Publisher first, Publisher */ public static Flux from(Publisher source) { //duplicated in wrap, but necessary to detect early and thus avoid applying assembly - // TODO: Consider checking for INTERNAL_PRODUCER here and potentially lift - if (source instanceof Flux) { + if (source instanceof Flux + && Scannable.from(source).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false)) { @SuppressWarnings("unchecked") Flux casted = (Flux) source; return casted; @@ -8779,7 +8779,6 @@ public final void subscribe(Subscriber actual) { // subscribe(CoreSubscriber), so this would be the last resort when the user // directly subscribes. subscriber = Operators.restoreContextOnSubscriberIfNecessary(publisher, subscriber); - publisher.subscribe(subscriber); } catch (Throwable e) { @@ -9898,6 +9897,7 @@ public final Flux transformDeferredContextual(BiFunction, return deferContextual(ctxView -> { if (Hooks.DETECT_CONTEXT_LOSS) { ContextTrackingFunctionWrapper wrapper = new ContextTrackingFunctionWrapper<>( + // TODO: why no assembly hooks are applied? publisher -> transformer.apply(wrap(publisher), ctxView), transformer.toString() ); @@ -11075,8 +11075,14 @@ static BiFunction> tuple2Function() { */ @SuppressWarnings("unchecked") static Flux wrap(Publisher source) { - if (source instanceof Flux) { - return (Flux) source; + boolean isInternal = Scannable.from(source) + .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, + false); + if (source instanceof Flux) { + if (isInternal) { + return (Flux) source; + } + return new FluxContextWriteRestoringThreadLocals<>((Flux) source, Function.identity()); } //for scalars we'll instantiate the operators directly to avoid onAssembly @@ -11094,16 +11100,25 @@ static Flux wrap(Publisher source) { } } - if(source instanceof Mono){ - if(source instanceof Fuseable){ - return new FluxSourceMonoFuseable<>((Mono)source); + Publisher target = source; + if (!isInternal) { + if (target instanceof Mono) { + target = new MonoRestoringThreadLocals<>(source); + } else { + target = new FluxRestoringThreadLocals<>(source); } - return new FluxSourceMono<>((Mono)source); } - if(source instanceof Fuseable){ - return new FluxSourceFuseable<>(source); + + if (source instanceof Mono) { + if (source instanceof Fuseable) { + return new FluxSourceMonoFuseable<>((Mono) target); + } + return new FluxSourceMono<>((Mono) target); + } + if (source instanceof Fuseable) { + return new FluxSourceFuseable<>(target); } - return new FluxSource<>(source); + return new FluxSource<>(target); } @SuppressWarnings("rawtypes") diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java b/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java index c84e468987..a6dd95d6e6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java @@ -58,8 +58,7 @@ public void subscribe(CoreSubscriber actual) { // even wrap (which skips assembly hooks) was called. // We need to decide whether from/wrap perform the Subscriber wrapping instead // of the below construct! - Flux flux = from(p); - flux.subscribe(Operators.restoreContextOnSubscriberIfNecessary(flux, actual)); + Operators.toFluxOrMono(p).subscribe(actual); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java index b95c15aa39..ea9ffff8a6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java @@ -127,7 +127,7 @@ public void subscribe(CoreSubscriber actual) { new NullPointerException("The single source Publisher is null")); } else { - p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, actual)); + Operators.toFluxOrMono(p).subscribe(actual); } return; } @@ -223,7 +223,7 @@ void subscribe(Publisher[] sources, return; } - p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, a[i])); + Operators.toFluxOrMono(p).subscribe(a[i]); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java index 95f116b58b..151420ce2a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java @@ -30,6 +30,7 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; import reactor.core.CoreSubscriber; import reactor.core.Exceptions; import reactor.core.Fuseable; @@ -198,12 +199,12 @@ static boolean trySubscribeScalarMap(Publisher source, else { // TODO: wrap Subscriber only or wrap the Publisher? What about // assembly hooks? - CoreSubscriber sub = Operators.restoreContextOnSubscriberIfNecessary(p, s); + CorePublisher pub = Operators.toFluxOrMono(p); if (!fuseableExpected || p instanceof Fuseable) { - p.subscribe(sub); + pub.subscribe(s); } else { - p.subscribe(new FluxHide.SuppressFuseableSubscriber<>(sub)); + pub.subscribe(new FluxHide.SuppressFuseableSubscriber<>(s)); } } @@ -427,7 +428,7 @@ else if (!delayError || !Exceptions.addThrowable(ERROR, this, e_)) { else { FlatMapInner inner = new FlatMapInner<>(this, prefetch); if (add(inner)) { - p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, inner)); + Operators.toFluxOrMono(p).subscribe(inner); } else { Operators.onDiscard(t, actual.currentContext()); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java new file mode 100644 index 0000000000..a30ce51088 --- /dev/null +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import io.micrometer.context.ContextSnapshot; +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; + +final class FluxRestoringThreadLocals extends Flux implements SourceProducer { + + private final Publisher source; + + FluxRestoringThreadLocals(Publisher source) { + this.source = source; + } + + @SuppressWarnings("try") + @Override + public void subscribe(CoreSubscriber actual) { + Context c = actual.currentContext(); + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { + source.subscribe(new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return SourceProducer.super.scanUnsafe(key); + } +} diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java index 52821796dd..6a0ee648f2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSource.java @@ -68,12 +68,7 @@ final class FluxSource extends Flux implements SourceProducer, @Override @SuppressWarnings("unchecked") public void subscribe(CoreSubscriber actual) { - // TODO: can we actually call Operators.restoreContextOnSubscriberIfNecessary ? - if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - source.subscribe(new FluxSourceRestoringThreadLocalsSubscriber<>(actual)); - } else { - source.subscribe(actual); - } + source.subscribe(actual); } @Override @@ -99,95 +94,4 @@ public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); return SourceProducer.super.scanUnsafe(key); } - - static final class FluxSourceRestoringThreadLocalsSubscriber - implements Fuseable.ConditionalSubscriber, InnerConsumer { - - final CoreSubscriber actual; - final Fuseable.ConditionalSubscriber actualConditional; - - Subscription s; - - @SuppressWarnings("unchecked") - FluxSourceRestoringThreadLocalsSubscriber(CoreSubscriber actual) { - this.actual = actual; - if (actual instanceof Fuseable.ConditionalSubscriber) { - this.actualConditional = (Fuseable.ConditionalSubscriber) actual; - } - else { - this.actualConditional = null; - } - } - - @Override - @Nullable - public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) { - return s; - } - if (key == Attr.RUN_STYLE) { - return Attr.RunStyle.SYNC; - } - if (key == Attr.ACTUAL) { - return actual; - } - return null; - } - - @Override - public Context currentContext() { - return actual.currentContext(); - } - - @SuppressWarnings("try") - @Override - public void onSubscribe(Subscription s) { - // This is needed, as the downstream can then switch threads, - // continue the subscription using different primitives and omit this operator - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onSubscribe(s); - } - } - - @SuppressWarnings("try") - @Override - public void onNext(T t) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onNext(t); - } - } - - @SuppressWarnings("try") - @Override - public boolean tryOnNext(T t) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - if (actualConditional != null) { - return actualConditional.tryOnNext(t); - } - actual.onNext(t); - return true; - } - } - - @SuppressWarnings("try") - @Override - public void onError(Throwable t) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onError(t); - } - } - - @SuppressWarnings("try") - @Override - public void onComplete() { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onComplete(); - } - } - } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index 7b3c79b64b..5bfebbbc73 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -64,8 +64,7 @@ public final void subscribe(CoreSubscriber subscriber) { OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { CorePublisher operatorSource = operator.source(); - subscriber = Operators.restoreContextOnSubscriberIfNecessary(operatorSource, subscriber); - operatorSource.subscribe(subscriber); + Operators.toFluxOrMono(operatorSource).subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java index 1ad680524c..b31f752f8c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java @@ -62,8 +62,7 @@ public final void subscribe(CoreSubscriber subscriber) { OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { CorePublisher operatorSource = operator.source(); - subscriber = Operators.restoreContextOnSubscriberIfNecessary(operatorSource, subscriber); - operatorSource.subscribe(subscriber); + Operators.toFluxOrMono(operatorSource).subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index 26b81c233b..800c64b976 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -480,18 +480,28 @@ public static Mono firstWithValue(Mono first, Mono Mono from(Publisher source) { //some sources can be considered already assembled monos //all conversion methods (from, fromDirect, wrap) must accommodate for this - if (source instanceof Mono) { + boolean isInternal = Scannable.from(source) + .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, + false); + if (isInternal && source instanceof Mono) { @SuppressWarnings("unchecked") Mono casted = (Mono) source; return casted; } + if (source instanceof FluxSourceMono || source instanceof FluxSourceMonoFuseable) { @SuppressWarnings("unchecked") FluxFromMonoOperator wrapper = (FluxFromMonoOperator) source; @SuppressWarnings("unchecked") Mono extracted = (Mono) wrapper.source; - return extracted; + boolean isExtractedInternal = Scannable.from(extracted).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); + if (isExtractedInternal) { + return extracted; + } else { + // Skip assembly hook + return wrap(extracted, false); + } } //we delegate to `wrap` and apply assembly hooks @@ -4497,7 +4507,6 @@ public final void subscribe(Subscriber actual) { // subscribe(CoreSubscriber), so this would be the last resort when the user // directly subscribes. subscriber = Operators.restoreContextOnSubscriberIfNecessary(publisher, subscriber); - publisher.subscribe(subscriber); } catch (Throwable e) { @@ -5338,39 +5347,57 @@ static Mono doOnTerminalSignal(Mono source, static Mono wrap(Publisher source, boolean enforceMonoContract) { //some sources can be considered already assembled monos //all conversion methods (from, fromDirect, wrap) must accommodate for this + boolean isInternal = Scannable.from(source) + .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, + false); if (source instanceof Mono) { - return (Mono) source; + if (isInternal) { + return (Mono) source; + } + return new MonoContextWriteRestoringThreadLocals<>((Mono) source, Function.identity()); } - if (source instanceof FluxSourceMono - || source instanceof FluxSourceMonoFuseable) { - @SuppressWarnings("unchecked") - Mono extracted = (Mono) ((FluxFromMonoOperator) source).source; - return extracted; + + if (source instanceof FluxSourceMono || source instanceof FluxSourceMonoFuseable) { + @SuppressWarnings("unchecked") Mono extracted = + (Mono) ((FluxFromMonoOperator) source).source; + boolean isExtractedInternal = Scannable.from(extracted) + .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, + false); + if (isExtractedInternal) { + return extracted; + } + return new MonoContextWriteRestoringThreadLocals<>(extracted, Function.identity()); + } + + if (source instanceof Flux && source instanceof Callable) { + @SuppressWarnings("unchecked") Callable m = (Callable) source; + return Flux.wrapToMono(m); + } + + Publisher target = source; + if (!isInternal) { + target = new FluxRestoringThreadLocals<>(source); } //equivalent to what from used to be, without assembly hooks if (enforceMonoContract) { - if (source instanceof Flux && source instanceof Callable) { - @SuppressWarnings("unchecked") Callable m = (Callable) source; - return Flux.wrapToMono(m); - } if (source instanceof Flux) { - return new MonoNext<>((Flux) source); + return new MonoNext<>((Flux) target); } - return new MonoFromPublisher<>(source); + return new MonoFromPublisher<>(target); } //equivalent to what fromDirect used to be without onAssembly - if(source instanceof Flux && source instanceof Fuseable) { - return new MonoSourceFluxFuseable<>((Flux) source); + if (source instanceof Flux && source instanceof Fuseable) { + return new MonoSourceFluxFuseable<>((Flux) target); } if (source instanceof Flux) { - return new MonoSourceFlux<>((Flux) source); + return new MonoSourceFlux<>((Flux) target); } - if(source instanceof Fuseable) { - return new MonoSourceFuseable<>(source); + if (source instanceof Fuseable) { + return new MonoSourceFuseable<>(target); } - return new MonoSource<>(source); + return new MonoSource<>(target); } @SuppressWarnings("unchecked") diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java index e581c12109..5fbc53fc4d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDeferContextual.java @@ -52,7 +52,7 @@ public void subscribe(CoreSubscriber actual) { return; } - p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, actual)); + Operators.toFluxOrMono(p).subscribe(actual); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java index 161d265d93..ccd127b05f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java @@ -136,7 +136,7 @@ public void subscribe(CoreSubscriber actual) { actual.currentContext())); } else { - p.subscribe(Operators.restoreContextOnSubscriberIfNecessary(p, actual)); + Operators.toFluxOrMono(p).subscribe(actual); } return; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java index 17768f667d..9a70e602c7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java @@ -79,7 +79,7 @@ public final void subscribe(CoreSubscriber subscriber) { } OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { - operator.source().subscribe(subscriber); + Operators.toFluxOrMono(operator.source()).subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java index bf0de9c9ff..2e00029797 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java @@ -55,10 +55,6 @@ final class MonoFromPublisher extends Mono implements Scannable, @Override @SuppressWarnings("unchecked") public void subscribe(CoreSubscriber actual) { - if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - actual = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(actual); - } - try { CoreSubscriber subscriber = subscribeOrReturn(actual); if (subscriber == null) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java index aceaa4ce65..ac33d5dd59 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java @@ -238,7 +238,7 @@ void subscribeNext() { } onComplete(); } else { - m.subscribe(Operators.restoreContextOnSubscriberIfNecessary(m, this)); + Operators.toFluxOrMono(m).subscribe(this); } return; } else { @@ -261,7 +261,7 @@ void subscribeNext() { continue; } - m.subscribe((CoreSubscriber) Operators.restoreContextOnSubscriberIfNecessary(m, this)); + Operators.toFluxOrMono(m).subscribe((CoreSubscriber) this); return; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java new file mode 100644 index 0000000000..dd8fd1dd8d --- /dev/null +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import io.micrometer.context.ContextSnapshot; +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; + +final class MonoRestoringThreadLocals extends Mono implements SourceProducer { + + private final Publisher source; + + MonoRestoringThreadLocals(Publisher source) { + this.source = source; + } + + @SuppressWarnings("try") + @Override + public void subscribe(CoreSubscriber actual) { + Context c = actual.currentContext(); + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { + source.subscribe(new MonoContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return SourceProducer.super.scanUnsafe(key); + } +} diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java index 58228c85ba..f910f31d79 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSource.java @@ -65,11 +65,7 @@ final class MonoSource extends Mono implements Scannable, SourceProducer actual) { - if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - source.subscribe(new MonoSourceRestoringThreadLocalsSubscriber<>(actual)); - } else { - source.subscribe(actual); - } + source.subscribe(actual); } @Override @@ -98,90 +94,4 @@ public Object scanUnsafe(Attr key) { } return SourceProducer.super.scanUnsafe(key); } - - static final class MonoSourceRestoringThreadLocalsSubscriber - implements InnerConsumer { - - final CoreSubscriber actual; - - Subscription s; - boolean done; - - MonoSourceRestoringThreadLocalsSubscriber(CoreSubscriber actual) { - this.actual = actual; - } - - @Override - @Nullable - public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) { - return s; - } - if (key == Attr.RUN_STYLE) { - return Attr.RunStyle.SYNC; - } - if (key == Attr.ACTUAL) { - return actual; - } - return null; - } - - @Override - public Context currentContext() { - return actual.currentContext(); - } - - @SuppressWarnings("try") - @Override - public void onSubscribe(Subscription s) { - // This is needed, as the downstream can then switch threads, - // continue the subscription using different primitives and omit this operator - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onSubscribe(s); - } - } - - @SuppressWarnings("try") - @Override - public void onNext(T t) { - this.done = true; - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onNext(t); - actual.onComplete(); - } - } - - @SuppressWarnings("try") - @Override - public void onError(Throwable t) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - if (this.done) { - Operators.onErrorDropped(t, actual.currentContext()); - return; - } - - this.done = true; - - actual.onError(t); - } - } - - @SuppressWarnings("try") - @Override - public void onComplete() { - if (this.done) { - return; - } - - this.done = true; - - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onComplete(); - } - } - } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 4b88f3e725..3ff71f2ff4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -991,64 +991,44 @@ public static CorePublisher onLastAssembly(CorePublisher source) { } } + public static CorePublisher toFluxOrMono(Publisher publisher) { + if (publisher instanceof Mono) { + return Mono.from(publisher); + } + return Flux.from(publisher); + } + public static CoreSubscriber restoreContextOnSubscriberIfNecessary( - Publisher publisher, CoreSubscriber subscriber) { + Publisher publisher, CoreSubscriber subscriber) { - if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals() - // TODO: consider Scannable.from(subscriber).scanOrDefault(Scannable.Attr.TL_RESTORING_SUBSCRIBER, false) instead - || subscriber instanceof MonoSource.MonoSourceRestoringThreadLocalsSubscriber - || subscriber instanceof FluxSource.FluxSourceRestoringThreadLocalsSubscriber) { - return subscriber; + Scannable scannable = Scannable.from(publisher); + boolean internal = scannable.isScanAvailable() + && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); + if (!internal) { + subscriber = new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, subscriber.currentContext()); } + return subscriber; + } + public static CoreSubscriber[] restoreContextOnSubscribersIfNecessary( + Publisher publisher, CoreSubscriber[] subscribers) { + CoreSubscriber[] actualSubscribers = subscribers; Scannable scannable = Scannable.from(publisher); - // TODO: REPORT AS INTERNAL EVERYWHERE IN reactor-core boolean internal = scannable.isScanAvailable() && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); if (!internal) { - if (publisher instanceof Mono) { - subscriber = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(subscriber); - } else { - subscriber = new FluxSource.FluxSourceRestoringThreadLocalsSubscriber<>(subscriber); + actualSubscribers = new CoreSubscriber[subscribers.length]; + for (int i = 0; i < subscribers.length; i++) { + CoreSubscriber subscriber = + new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscribers[i], subscribers[i].currentContext()); + actualSubscribers[i] = subscriber; } } - return subscriber; + return actualSubscribers; } -// public static CoreSubscriber restoreContextOnSubscriberIfNecessary( -// Mono mono, CoreSubscriber subscriber) { -// -// if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { -// return subscriber; -// } -// -// Scannable scannable = Scannable.from(mono); -// // TODO: REPORT AS INTERNAL EVERYWHERE IN reactor-core -// boolean internal = scannable.isScanAvailable() -// && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); -// if (!internal) { -// subscriber = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(subscriber); -// } -// return subscriber; -// } -// -// public static CoreSubscriber restoreContextOnSubscriberIfNecessary( -// Flux flux, CoreSubscriber subscriber) { -// -// if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { -// return subscriber; -// } -// -// Scannable scannable = Scannable.from(flux); -// // TODO: REPORT AS INTERNAL EVERYWHERE IN reactor-core -// boolean internal = scannable.isScanAvailable() -// && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); -// if (!internal) { -// subscriber = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(subscriber); -// } -// return subscriber; -// } - private static Throwable unwrapOnNextError(Throwable error) { return Exceptions.isBubbling(error) ? error : Exceptions.unwrap(error); } @@ -1534,24 +1514,14 @@ static int unboundedOrLimit(int prefetch, int lowTide) { Operators() { } - static final class CorePublisherAdapter implements CorePublisher, - OptimizableOperator { + static final class CorePublisherAdapter implements CorePublisher { final Publisher publisher; - @Nullable - final OptimizableOperator optimizableOperator; - CorePublisherAdapter(Publisher publisher) { this.publisher = publisher; - if (publisher instanceof OptimizableOperator) { - @SuppressWarnings("unchecked") - OptimizableOperator optimSource = (OptimizableOperator) publisher; - this.optimizableOperator = optimSource; - } - else { - this.optimizableOperator = null; - } + // note: if publisher is not CorePublisher it can't be an + // OptimizableOperator, which extends CorePublisher } @Override @@ -1563,21 +1533,6 @@ public void subscribe(CoreSubscriber subscriber) { public void subscribe(Subscriber s) { publisher.subscribe(s); } - - @Override - public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { - return actual; - } - - @Override - public final CorePublisher source() { - return this; - } - - @Override - public final OptimizableOperator nextOptimizableSource() { - return optimizableOperator; - } } static final Fuseable.ConditionalSubscriber EMPTY_SUBSCRIBER = new Fuseable.ConditionalSubscriber() { @@ -2714,6 +2669,11 @@ final static class LiftFunction final Predicate filter; final String name; + // TODO: this leaks to the users of LiftFunction, encapsulation is broken + // TODO: consider: liftFunction.lifter.apply could go through encapsulation + // like: liftFunction.applyLifter() where what lifter.apply returns is wrapped + // unconditionally; otherwise -> all lift* operators need to be considered as + // NOT INTERNAL_PRODUCER sources final BiFunction, ? extends CoreSubscriber> lifter; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java index a045481d1f..b5563c68b0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java @@ -43,7 +43,7 @@ final class ParallelCollect extends ParallelFlux implements Scannable, ParallelCollect(ParallelFlux source, Supplier initialCollection, BiConsumer collector) { - this.source = source; + this.source = ParallelFlux.from(source); this.initialCollection = initialCollection; this.collector = collector; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java index 899f344fbc..d17ee3f65f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java @@ -50,7 +50,7 @@ final class ParallelConcatMap extends ParallelFlux implements Scannable Function> mapper, Supplier> queueSupplier, int prefetch, ErrorMode errorMode) { - this.source = source; + this.source = ParallelFlux.from(source); this.mapper = Objects.requireNonNull(mapper, "mapper"); this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); this.prefetch = prefetch; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java index db1507fca9..32c0aa5d64 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java @@ -46,7 +46,7 @@ final class ParallelDoOnEach extends ParallelFlux implements Scannable { @Nullable BiConsumer onError, @Nullable Consumer onComplete ) { - this.source = source; + this.source = ParallelFlux.from(source); this.onNext = onNext; this.onError = onError; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java index 75008be175..de053f3a43 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java @@ -35,7 +35,7 @@ final class ParallelFilter extends ParallelFlux implements Scannable{ final Predicate predicate; ParallelFilter(ParallelFlux source, Predicate predicate) { - this.source = source; + this.source = ParallelFlux.from(source); this.predicate = predicate; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java index 51192a9b95..8f94fe89be 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java @@ -53,7 +53,7 @@ final class ParallelFlatMap extends ParallelFlux implements Scannable{ boolean delayError, int maxConcurrency, Supplier> mainQueueSupplier, int prefetch, Supplier> innerQueueSupplier) { - this.source = source; + this.source = ParallelFlux.from(source); this.mapper = mapper; this.delayError = delayError; this.maxConcurrency = maxConcurrency; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java index f225decbc7..92dfab7ba8 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java @@ -76,6 +76,14 @@ */ public abstract class ParallelFlux implements CorePublisher { + public static ParallelFlux from(ParallelFlux source) { + Scannable s = Scannable.from(source); + if (!s.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false)) { + return new ParallelFluxRestoringThreadLocals<>(source); + } + return source; + } + /** * Take a Publisher and prepare to consume it on multiple 'rails' (one per CPU core) * in a round-robin fashion. Equivalent to {@link Flux#parallel}. diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java index 25725eeecf..5a7b50ef27 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java @@ -32,7 +32,7 @@ final class ParallelFluxHide extends ParallelFlux implements Scannable{ final ParallelFlux source; ParallelFluxHide(ParallelFlux source) { - this.source = source; + this.source = ParallelFlux.from(source); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java index ef161f2101..0b12fce5cd 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java @@ -80,7 +80,7 @@ static ParallelFlux createOrAppend(ParallelFlux source, String tagName ParallelFluxName(ParallelFlux source, @Nullable String name, @Nullable List> tags) { - this.source = source; + this.source = ParallelFlux.from(source); this.name = name; this.tagsWithDuplicates = tags; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java index c524405428..1031ac67a2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java @@ -46,7 +46,7 @@ final class ParallelFluxOnAssembly extends ParallelFlux * Create an assembly trace wrapping a {@link ParallelFlux}. */ ParallelFluxOnAssembly(ParallelFlux source, AssemblySnapshot stacktrace) { - this.source = source; + this.source = ParallelFlux.from(source); this.stacktrace = stacktrace; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java new file mode 100644 index 0000000000..0ffee6c2ad --- /dev/null +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java @@ -0,0 +1,36 @@ +package reactor.core.publisher; + +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; + +public class ParallelFluxRestoringThreadLocals extends ParallelFlux implements + Scannable { + + private final ParallelFlux source; + + public ParallelFluxRestoringThreadLocals(ParallelFlux source) { + this.source = source; + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + CoreSubscriber[] actualSubscribers = + Operators.restoreContextOnSubscribersIfNecessary(source, subscribers); + + source.subscribe(actualSubscribers); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; + return null; + } +} diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java index b9f2733b44..766be854cb 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java @@ -40,7 +40,7 @@ final class ParallelGroup extends Flux> implements final ParallelFlux source; ParallelGroup(ParallelFlux source) { - this.source = source; + this.source = ParallelFlux.from(source); } @Override @@ -65,6 +65,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java index 5ef80ffa9d..12b449e813 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java @@ -33,7 +33,7 @@ final class ParallelLift extends ParallelFlux implements Scannable { ParallelLift(ParallelFlux p, Operators.LiftFunction liftFunction) { - this.source = Objects.requireNonNull(p, "source"); + this.source = ParallelFlux.from(Objects.requireNonNull(p, "source")); this.liftFunction = liftFunction; } @@ -62,7 +62,8 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } - if (key == Attr.INTERNAL_PRODUCER) return true; + // We don't control what the lifter does, so we play it safe. + if (key == Attr.INTERNAL_PRODUCER) return false; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java index a9e9edf31f..75885ac004 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java @@ -36,7 +36,7 @@ final class ParallelLiftFuseable extends ParallelFlux ParallelLiftFuseable(ParallelFlux p, Operators.LiftFunction liftFunction) { - this.source = Objects.requireNonNull(p, "source"); + this.source = ParallelFlux.from(Objects.requireNonNull(p, "source")); this.liftFunction = liftFunction; } @@ -65,7 +65,8 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } - if (key == Attr.INTERNAL_PRODUCER) return true; + // We don't control what the lifter does, so we play it safe. + if (key == Attr.INTERNAL_PRODUCER) return false; return null; } @@ -86,6 +87,8 @@ public void subscribe(CoreSubscriber[] s) { int i = 0; while (i < subscribers.length) { CoreSubscriber actual = s[i]; + // As this is not an INTERNAL_PRODUCER, the subscribers should be protected + // in case of automatic context propagation CoreSubscriber converted = Objects.requireNonNull(liftFunction.lifter.apply(source, actual), "Lifted subscriber MUST NOT be null"); diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java index d51cb36c62..ae35c034c2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java @@ -36,7 +36,7 @@ final class ParallelLog extends ParallelFlux implements Scannable { ParallelLog(ParallelFlux source, SignalPeek log ) { - this.source = source; + this.source = ParallelFlux.from(source); this.log = log; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java index dc7e1aca4f..58d8119c9c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java @@ -36,7 +36,7 @@ final class ParallelMap extends ParallelFlux implements Scannable { final Function mapper; ParallelMap(ParallelFlux source, Function mapper) { - this.source = source; + this.source = ParallelFlux.from(source); this.mapper = mapper; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java index ee070166dc..3b8016e47d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java @@ -41,7 +41,7 @@ final class ParallelMergeOrdered extends Flux implements Scannable { if (prefetch <= 0) { throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); } - this.source = source; + this.source = ParallelFlux.from(source); this.prefetch = prefetch; this.valueComparator = valueComparator; } @@ -57,6 +57,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return prefetch; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java index a4ea01aa5d..873492e37d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java @@ -42,7 +42,7 @@ final class ParallelMergeReduce extends Mono implements Scannable, Fuseabl ParallelMergeReduce(ParallelFlux source, BiFunction reducer) { - this.source = source; + this.source = ParallelFlux.from(source); this.reducer = reducer; } @@ -51,6 +51,7 @@ final class ParallelMergeReduce extends Mono implements Scannable, Fuseabl public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java index 2664d05889..dc041375d7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java @@ -46,7 +46,7 @@ final class ParallelMergeSequential extends Flux implements Scannable { if (prefetch <= 0) { throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); } - this.source = source; + this.source = ParallelFlux.from(source); this.prefetch = prefetch; this.queueSupplier = queueSupplier; } @@ -57,6 +57,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java index 1ff75a4cd6..d4c5cde52f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java @@ -47,7 +47,7 @@ final class ParallelMergeSort extends Flux implements Scannable { ParallelMergeSort(ParallelFlux> source, Comparator comparator) { - this.source = source; + this.source = ParallelFlux.from(source); this.comparator = comparator; } @@ -71,6 +71,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java index 1bd2f187c1..2ba33ef1d2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java @@ -52,7 +52,7 @@ final class ParallelPeek extends ParallelFlux implements SignalPeek{ @Nullable LongConsumer onRequest, @Nullable Runnable onCancel ) { - this.source = source; + this.source = ParallelFlux.from(source); this.onNext = onNext; this.onAfterNext = onAfterNext; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java index 88bddd42c0..d9e36c7b55 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java @@ -44,7 +44,7 @@ final class ParallelReduceSeed extends ParallelFlux implements ParallelReduceSeed(ParallelFlux source, Supplier initialSupplier, BiFunction reducer) { - this.source = source; + this.source = ParallelFlux.from(source); this.initialSupplier = initialSupplier; this.reducer = reducer; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java index 3907f0604c..30a27f67a2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java @@ -45,7 +45,7 @@ final class ParallelRunOn extends ParallelFlux implements Scannable{ if (prefetch <= 0) { throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); } - this.source = parent; + this.source = ParallelFlux.from(parent); this.scheduler = scheduler; this.prefetch = prefetch; this.queueSupplier = queueSupplier; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java index 8c0f612678..2755591f95 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java @@ -54,11 +54,7 @@ final class ParallelSource extends ParallelFlux implements Scannable { if (prefetch <= 0) { throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); } - // TODO: this can be improved to restore TLs in ParallelSourceMain or by - // modifying the from() method to lift the source to wrap the Subscriber upon - // subscription. - // FIXME: when source is a Flux, but not a INTERNAL_PRODUCER, this won't help - this.source = Flux.from(source); + this.source = Operators.toFluxOrMono(source); this.parallelism = parallelism; this.prefetch = prefetch; this.queueSupplier = queueSupplier; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java index f8470b3312..dc541bc3ad 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java @@ -34,7 +34,7 @@ final class ParallelThen extends Mono implements Scannable, Fuseable { final ParallelFlux source; ParallelThen(ParallelFlux source) { - this.source = source; + this.source = ParallelFlux.from(source); } @Override @@ -42,6 +42,7 @@ final class ParallelThen extends Mono implements Scannable, Fuseable { public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index b8a8022d9a..b262809cfc 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -339,7 +340,7 @@ void assertThreadLocalPresentInOnNext(Flux chain) { assertThat(value.get()).isEqualTo("present"); } - // Basic tests for Flux + // Fundamental tests for Flux @Test void chainedFluxSubscribe() { @@ -451,7 +452,7 @@ void directFluxSubscribeAsRawSubscriber() throws InterruptedException { assertThat(value.get()).isEqualTo("present"); } - // Basic tests for Mono + // Fundamental tests for Mono @Test void chainedMonoSubscribe() { @@ -644,17 +645,110 @@ void monoFirstWithSignalIterable() { assertThreadLocalPresentInOnNext(Mono.firstWithSignal(list)); } + @Test + void monoFromFluxSingle() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + Mono chain = flux.single(); + assertThreadLocalPresentInOnNext(chain); + } + // ParallelFlux tests @Test - void fuseableParallelFluxToMono() { - Mono flux = new ThreadSwitchingMono<>("Hello", executorService); + void parallelFluxFromMonoToMono() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.from(ParallelFlux.from(mono)); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void parallelFluxFromMonoToFlux() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Flux chain = Flux.from(ParallelFlux.from(mono)); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void parallelFluxFromFluxToMono() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); Mono chain = Mono.from(ParallelFlux.from(flux)); assertThreadLocalPresentInOnNext(chain); } @Test - void parallelFlux() { + void parallelFluxFromFluxToFlux() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + Flux chain = Flux.from(ParallelFlux.from(flux)); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void parallelFluxLift() { + ParallelFlux parallelFlux = + ParallelFlux.from(Flux.just("Hello")); + + Publisher lifted = + Operators.liftPublisher((pub, sub) -> new CoreSubscriber() { + @Override + public void onSubscribe(Subscription s) { + executorService.submit(() -> sub.onSubscribe(s)); + } + + @Override + public void onNext(String s) { + executorService.submit(() -> sub.onNext(s)); + } + + @Override + public void onError(Throwable t) { + executorService.submit(() -> sub.onError(t)); + } + + @Override + public void onComplete() { + executorService.submit(sub::onComplete); + } + }) + .apply(parallelFlux); + + assertThreadLocalPresentInOnNext(((ParallelFlux) lifted).sequential()); + } + + @Test + void parallelFluxLiftFuseable() { + ParallelFlux> parallelFlux = + ParallelFlux.from(Flux.just("Hello")) + .collect(ArrayList::new, ArrayList::add); + + Publisher> lifted = Operators., ArrayList>liftPublisher( + (pub, sub) -> new CoreSubscriber>() { + @Override + public void onSubscribe(Subscription s) { + executorService.submit(() -> sub.onSubscribe(s)); + } + + @Override + public void onNext(ArrayList s) { + executorService.submit(() -> sub.onNext(s)); + } + + @Override + public void onError(Throwable t) { + executorService.submit(() -> sub.onError(t)); + } + + @Override + public void onComplete() { + executorService.submit(sub::onComplete); + } + }) + .apply(parallelFlux); + + assertThreadLocalPresentInOnNext(((ParallelFlux) lifted).sequential()); + } + + @Test + void parallelFluxFromThreadSwitchingMono() { AtomicReference value = new AtomicReference<>(); Mono mono = new ThreadSwitchingMono<>("Hello", executorService); @@ -668,6 +762,33 @@ void parallelFlux() { assertThat(value.get()).isEqualTo("present"); } + @Test + void parallelFluxFromThreadSwitchingFlux() { + AtomicReference value = new AtomicReference<>(); + + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + + ParallelFlux.from(flux) + .doOnNext(i -> value.set(REF.get())) + .sequential() + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void threadSwitchingParallelFlux() { + AtomicReference value = new AtomicReference<>(); + new ThreadSwitchingParallelFlux("Hello", executorService) + .doOnNext(i -> value.set(REF.get())) + .sequential() + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + // Sinks tests @Test diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java new file mode 100644 index 0000000000..309f3070f4 --- /dev/null +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java @@ -0,0 +1,62 @@ +package reactor.core.publisher; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; + +public class ThreadSwitchingParallelFlux extends ParallelFlux implements + Subscription, Runnable { + + private final T item; + private final ExecutorService executorService; + AtomicBoolean done = new AtomicBoolean(); + CoreSubscriber[] actual; + + public ThreadSwitchingParallelFlux(T item, ExecutorService executorService) { + this.item = item; + this.executorService = executorService; + } + + @Override + public int parallelism() { + return 1; + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + this.actual = subscribers; + executorService.submit(this); + } + + @Override + public void run() { + actual[0].onSubscribe(this); + } + + private void deliver() { + if (done.compareAndSet(false, true)) { + this.actual[0].onNext(this.item); + this.actual[0].onComplete(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (!done.get()) { + this.executorService.submit(this::deliver); + } + } + } + + @Override + public void cancel() { + done.set(true); + } +} From 8cbeb285a4047e81a02d10ff4e322beb6cc13ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Thu, 31 Aug 2023 19:58:38 +0200 Subject: [PATCH 04/26] spotless --- .../reactor/core/publisher/ParallelFlux.java | 2 +- .../ParallelFluxRestoringThreadLocals.java | 16 ++++++++++++++++ .../reactor/core/publisher/ParallelGroup.java | 2 +- .../core/publisher/ParallelMergeOrdered.java | 2 +- .../core/publisher/ParallelMergeReduce.java | 2 +- .../core/publisher/ParallelMergeSequential.java | 2 +- .../core/publisher/ParallelMergeSort.java | 2 +- .../reactor/core/publisher/ParallelThen.java | 2 +- .../publisher/ThreadSwitchingParallelFlux.java | 16 ++++++++++++++++ 9 files changed, 39 insertions(+), 7 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java index 92dfab7ba8..691efa80c6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java index 0ffee6c2ad..5ca1c6bca7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package reactor.core.publisher; import reactor.core.CoreSubscriber; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java index 766be854cb..0edc1189ed 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java index 3b8016e47d..2464ab37da 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java index 873492e37d..8064691af9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java index dc041375d7..7dc4a4053d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java index d4c5cde52f..61424fa08b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java index dc541bc3ad..68d39ec7ca 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2019-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java index 309f3070f4..016824448f 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package reactor.core.publisher; import java.util.concurrent.ExecutorService; From f50ff50768747ae3be59cf6bdd6776db0f42081b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Thu, 31 Aug 2023 20:14:26 +0200 Subject: [PATCH 05/26] FluxSubscribeOnValue marked as internal --- .../main/java/reactor/core/publisher/FluxSubscribeOnValue.java | 1 + 1 file changed, 1 insertion(+) diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java index 78cbc0c5c8..6af63a3d79 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java @@ -73,6 +73,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } From 9a9bb89b9f1e9317cd870abe40fb15023cd50ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Wed, 6 Sep 2023 15:54:27 +0200 Subject: [PATCH 06/26] Sinks --- .../java/reactor/core/publisher/Flux.java | 6 +- .../reactor/core/publisher/FluxFlatMap.java | 9 +- .../core/publisher/FluxSubscribeOnValue.java | 2 +- .../core/publisher/InternalFluxOperator.java | 1 + .../java/reactor/core/publisher/Mono.java | 10 +- .../reactor/core/publisher/MonoCreate.java | 8 +- .../reactor/core/publisher/MonoOperator.java | 3 +- .../reactor/core/publisher/Operators.java | 20 +- .../reactor/core/publisher/ParallelLift.java | 5 + .../core/publisher/ParallelLiftFuseable.java | 5 +- .../core/publisher/SinkEmptyMulticast.java | 10 +- .../core/publisher/SinkManyUnicast.java | 3 +- .../core/publisher/SinkOneMulticast.java | 10 +- .../AutomaticContextPropagationTest.java | 193 +++++++++++++++++- 14 files changed, 252 insertions(+), 33 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index 5d3ca291ef..95e2eeac94 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -1064,7 +1064,7 @@ public static Flux firstWithValue(Publisher first, Publisher public static Flux from(Publisher source) { //duplicated in wrap, but necessary to detect early and thus avoid applying assembly if (source instanceof Flux - && Scannable.from(source).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false)) { + && (Scannable.from(source).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false) || !ContextPropagationSupport.shouldPropagateContextToThreadLocals())) { @SuppressWarnings("unchecked") Flux casted = (Flux) source; return casted; @@ -11079,7 +11079,7 @@ static Flux wrap(Publisher source) { .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); if (source instanceof Flux) { - if (isInternal) { + if (isInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { return (Flux) source; } return new FluxContextWriteRestoringThreadLocals<>((Flux) source, Function.identity()); @@ -11101,7 +11101,7 @@ static Flux wrap(Publisher source) { } Publisher target = source; - if (!isInternal) { + if (!isInternal && ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { if (target instanceof Mono) { target = new MonoRestoringThreadLocals<>(source); } else { diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java index 151420ce2a..6a4d05a1d4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java @@ -199,12 +199,12 @@ static boolean trySubscribeScalarMap(Publisher source, else { // TODO: wrap Subscriber only or wrap the Publisher? What about // assembly hooks? - CorePublisher pub = Operators.toFluxOrMono(p); + p = Operators.toFluxOrMono(p); if (!fuseableExpected || p instanceof Fuseable) { - pub.subscribe(s); + p.subscribe(s); } else { - pub.subscribe(new FluxHide.SuppressFuseableSubscriber<>(s)); + p.subscribe(new FluxHide.SuppressFuseableSubscriber<>(s)); } } @@ -428,7 +428,8 @@ else if (!delayError || !Exceptions.addThrowable(ERROR, this, e_)) { else { FlatMapInner inner = new FlatMapInner<>(this, prefetch); if (add(inner)) { - Operators.toFluxOrMono(p).subscribe(inner); + p = Operators.toFluxOrMono(p); + p.subscribe(inner); } else { Operators.onDiscard(t, actual.currentContext()); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java index 6af63a3d79..b6a1fd8a32 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index 5bfebbbc73..7cecbb869a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -94,6 +94,7 @@ public final CorePublisher source() { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index 800c64b976..5692930e91 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -483,7 +483,7 @@ public static Mono from(Publisher source) { boolean isInternal = Scannable.from(source) .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); - if (isInternal && source instanceof Mono) { + if ((isInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) && source instanceof Mono) { @SuppressWarnings("unchecked") Mono casted = (Mono) source; return casted; @@ -496,7 +496,7 @@ public static Mono from(Publisher source) { @SuppressWarnings("unchecked") Mono extracted = (Mono) wrapper.source; boolean isExtractedInternal = Scannable.from(extracted).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); - if (isExtractedInternal) { + if (isExtractedInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { return extracted; } else { // Skip assembly hook @@ -5351,7 +5351,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); if (source instanceof Mono) { - if (isInternal) { + if (isInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { return (Mono) source; } return new MonoContextWriteRestoringThreadLocals<>((Mono) source, Function.identity()); @@ -5363,7 +5363,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { boolean isExtractedInternal = Scannable.from(extracted) .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); - if (isExtractedInternal) { + if (isExtractedInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { return extracted; } return new MonoContextWriteRestoringThreadLocals<>(extracted, Function.identity()); @@ -5375,7 +5375,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { } Publisher target = source; - if (!isInternal) { + if (!isInternal && ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { target = new FluxRestoringThreadLocals<>(source); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java index 28f42c7f55..35a8d78b8e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java @@ -50,15 +50,17 @@ final class MonoCreate extends Mono implements SourceProducer { @Override public void subscribe(CoreSubscriber actual) { - DefaultMonoSink emitter = new DefaultMonoSink<>(actual); + CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(actual); - actual.onSubscribe(emitter); + DefaultMonoSink emitter = new DefaultMonoSink<>(wrapped); + + wrapped.onSubscribe(emitter); try { callback.accept(emitter); } catch (Throwable ex) { - emitter.error(Operators.onOperatorError(ex, actual.currentContext())); + emitter.error(Operators.onOperatorError(ex, wrapped.currentContext())); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java index fda99e691d..7dde724dc6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java @@ -47,7 +47,8 @@ protected MonoOperator(Mono source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return Integer.MAX_VALUE; if (key == Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return true; + // FIXME: not internal! public + if (key == Attr.INTERNAL_PRODUCER) return false; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 3ff71f2ff4..d16bddb5be 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -998,9 +998,11 @@ public static CorePublisher toFluxOrMono(Publisher publisher return Flux.from(publisher); } - public static CoreSubscriber restoreContextOnSubscriberIfNecessary( + static CoreSubscriber restoreContextOnSubscriberIfNecessary( Publisher publisher, CoreSubscriber subscriber) { - + if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return subscriber; + } Scannable scannable = Scannable.from(publisher); boolean internal = scannable.isScanAvailable() && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); @@ -1011,8 +1013,20 @@ public static CoreSubscriber restoreContextOnSubscriberIfNecessar return subscriber; } - public static CoreSubscriber[] restoreContextOnSubscribersIfNecessary( + static CoreSubscriber restoreContextOnSubscriber(CoreSubscriber subscriber) { + if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return subscriber; + } + return new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, subscriber.currentContext()); + } + + static CoreSubscriber[] restoreContextOnSubscribersIfNecessary( Publisher publisher, CoreSubscriber[] subscribers) { + if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return subscribers; + } + CoreSubscriber[] actualSubscribers = subscribers; Scannable scannable = Scannable.from(publisher); boolean internal = scannable.isScanAvailable() diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java index 12b449e813..4a0ba207ff 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java @@ -83,6 +83,11 @@ public void subscribe(CoreSubscriber[] s) { int i = 0; while (i < subscribers.length) { + // As this is not an INTERNAL_PRODUCER, the subscribers should be protected + // in case of automatic context propagation. + // If a user directly subscribes with a set of rails, there is no + // protection against that, so a ThreadLocal restoring subscriber would + // need to be provided. subscribers[i] = Objects.requireNonNull(liftFunction.lifter.apply(source, s[i]), "Lifted subscriber MUST NOT be null"); diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java index 75885ac004..f9d8d8ff7e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java @@ -88,7 +88,10 @@ public void subscribe(CoreSubscriber[] s) { while (i < subscribers.length) { CoreSubscriber actual = s[i]; // As this is not an INTERNAL_PRODUCER, the subscribers should be protected - // in case of automatic context propagation + // in case of automatic context propagation. + // If a user directly subscribes with a set of rails, there is no + // protection against that, so a ThreadLocal restoring subscriber would + // need to be provided. CoreSubscriber converted = Objects.requireNonNull(liftFunction.lifter.apply(source, actual), "Lifted subscriber MUST NOT be null"); diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java index 46e3fa43c3..e3222d3615 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,6 +124,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return isTerminated(subscribers); if (key == Attr.ERROR) return subscribers == TERMINATED_ERROR ? error : null; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } @@ -196,8 +197,9 @@ void remove(Inner ps) { //redefined in SinkOneMulticast @Override public void subscribe(final CoreSubscriber actual) { - Inner as = new VoidInner<>(actual, this); - actual.onSubscribe(as); + CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(actual); + Inner as = new VoidInner<>(wrapped, this); + wrapped.onSubscribe(as); final int addedState = add(as); if (addedState == STATE_ADDED) { if (as.isCancelled()) { @@ -207,7 +209,7 @@ public void subscribe(final CoreSubscriber actual) { else if (addedState == STATE_ERROR) { Throwable ex = error; - actual.onError(ex); + wrapped.onError(ex); } else { as.complete(); diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java index d266bd3cd4..f754c63ca8 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -183,6 +183,7 @@ public Object scanUnsafe(Attr key) { if (Attr.CANCELLED == key) return cancelled; if (Attr.TERMINATED == key) return done; if (Attr.ERROR == key) return error; + if (Attr.INTERNAL_PRODUCER == key) return false; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java index 2dd03f7693..bad605800d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,14 +75,16 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return isTerminated(subscribers); if (key == Attr.ERROR) return subscribers == TERMINATED_ERROR ? error : null; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } @Override public void subscribe(final CoreSubscriber actual) { - NextInner as = new NextInner<>(actual, this); - actual.onSubscribe(as); + CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(actual); + NextInner as = new NextInner<>(wrapped, this); + wrapped.onSubscribe(as); final int addState = add(as); if (addState == STATE_ADDED) { if (as.isCancelled()) { @@ -90,7 +92,7 @@ public void subscribe(final CoreSubscriber actual) { } } else if (addState == STATE_ERROR) { - actual.onError(error); + wrapped.onError(error); } else if (addState == STATE_EMPTY) { as.complete(); diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index b262809cfc..02ed1d5139 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; @@ -41,6 +42,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; @@ -557,6 +559,13 @@ void directMonoSubscribeAsRawSubscriber() throws InterruptedException { // Flux tests + @Test + void fluxMap() { + Flux flux = + new ThreadSwitchingFlux<>("Hello", executorService).map(String::toUpperCase); + assertThreadLocalPresentInOnNext(flux); + } + @Test void fluxIgnoreThenSwitchThread() { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); @@ -754,8 +763,8 @@ void parallelFluxFromThreadSwitchingMono() { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); ParallelFlux.from(mono) - .doOnNext(i -> value.set(REF.get())) .sequential() + .doOnNext(i -> value.set(REF.get())) .contextWrite(Context.of(KEY, "present")) .blockLast(); @@ -769,8 +778,8 @@ void parallelFluxFromThreadSwitchingFlux() { Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); ParallelFlux.from(flux) - .doOnNext(i -> value.set(REF.get())) .sequential() + .doOnNext(i -> value.set(REF.get())) .contextWrite(Context.of(KEY, "present")) .blockLast(); @@ -778,10 +787,62 @@ void parallelFluxFromThreadSwitchingFlux() { } @Test - void threadSwitchingParallelFlux() { + void threadSwitchingParallelFluxSequential() { AtomicReference value = new AtomicReference<>(); new ThreadSwitchingParallelFlux("Hello", executorService) + .sequential() .doOnNext(i -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void threadSwitchingParallelFluxThen() { + AtomicReference value = new AtomicReference<>(); + new ThreadSwitchingParallelFlux("Hello", executorService) + .then() + .doOnSuccess(v -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .block(); + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void threadSwitchingParallelFluxOrdered() { + AtomicReference value = new AtomicReference<>(); + new ThreadSwitchingParallelFlux("Hello", executorService) + .ordered(Comparator.naturalOrder()) + .doOnNext(i -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void threadSwitchingParallelFluxReduce() { + AtomicReference value = new AtomicReference<>(); + new ThreadSwitchingParallelFlux("Hello", executorService) + .reduce((s1, s2) -> s2) + .doOnNext(i -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .block(); + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void threadSwitchingParallelFluxReduceSeed() { + AtomicReference value = new AtomicReference<>(); + new ThreadSwitchingParallelFlux("Hello", executorService) + .reduce(ArrayList::new, (l, s) -> { + value.set(REF.get()); + l.add(s); + return l; + }) .sequential() .contextWrite(Context.of(KEY, "present")) .blockLast(); @@ -789,6 +850,31 @@ void threadSwitchingParallelFlux() { assertThat(value.get()).isEqualTo("present"); } + @Test + void threadSwitchingParallelFluxGroup() { + AtomicReference value = new AtomicReference<>(); + new ThreadSwitchingParallelFlux("Hello", executorService) + .groups() + .doOnNext(i -> value.set(REF.get())) + .flatMap(Flux::last) + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void threadSwitchingParallelFluxSort() { + AtomicReference value = new AtomicReference<>(); + new ThreadSwitchingParallelFlux("Hello", executorService) + .sorted(Comparator.naturalOrder()) + .doOnNext(i -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + // Sinks tests @Test @@ -815,6 +901,107 @@ void sink() throws InterruptedException, TimeoutException { assertThat(value.get()).isEqualTo("present"); } + @Test + void sinkDirect() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.One sink = Sinks.one(); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext(value, error, latch, complete); + + sink.asMono() + .subscribe(subscriberWithContext); + + executorService.submit(() -> sink.tryEmitValue("Hello")); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void sinkEmpty() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.Empty empty = Sinks.empty(); + + empty.asMono() + .doOnTerminate(() -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(empty::tryEmitEmpty); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + @Disabled("Subscribe-time wrapping breaks fusion for now") + //FIXME + void sinkManyUnicast() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.ManySpec spec = Sinks.many(); + + Sinks.Many many = spec.unicast() + .onBackpressureBuffer(); + many.asFlux() + .doOnNext(i -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(() -> many.tryEmitNext("Hello")); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void sinkManyMulticast() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.ManySpec spec = Sinks.many(); + + Sinks.Many many = spec.multicast().directAllOrNothing(); + many.asFlux() + .doOnNext(i -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(() -> many.tryEmitNext("Hello")); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + // Other List> getAllClassesInClasspathRecursively(File directory) throws Exception { From 2f8a5ac66fcc7b35667a04ec1273b95a2c4afdde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Fri, 8 Sep 2023 16:51:33 +0200 Subject: [PATCH 07/26] cleaning up and unifying fuseable vs non-fuseable context restoring instances --- .../core/publisher/ContextPropagation.java | 2 + .../publisher/ContextPropagationSupport.java | 25 +++ .../java/reactor/core/publisher/Flux.java | 36 ++-- ...FluxContextWriteRestoringThreadLocals.java | 2 + ...extWriteRestoringThreadLocalsFuseable.java | 202 ++++++++++++++++++ .../reactor/core/publisher/FluxCreate.java | 1 + .../reactor/core/publisher/FluxOperator.java | 2 +- .../publisher/FluxRestoringThreadLocals.java | 46 ---- .../FluxTapRestoringThreadLocals.java | 4 +- .../core/publisher/InternalFluxOperator.java | 2 +- .../core/publisher/InternalMonoOperator.java | 6 + .../java/reactor/core/publisher/Mono.java | 67 +++--- ...MonoContextWriteRestoringThreadLocals.java | 33 ++- .../reactor/core/publisher/MonoOperator.java | 3 +- .../publisher/MonoRestoringThreadLocals.java | 46 ---- .../MonoTapRestoringThreadLocals.java | 1 + .../reactor/core/publisher/Operators.java | 54 ++--- .../AutomaticContextPropagationTest.java | 42 +++- 18 files changed, 393 insertions(+), 181 deletions(-) create mode 100644 reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java delete mode 100644 reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java delete mode 100644 reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java index f331b6a5c2..af3908133f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java @@ -117,6 +117,8 @@ public static Function scopePassingOnScheduleHook() { }; } + + /** * Create a support function that takes a snapshot of thread locals and merges them with the * provided {@link Context}, resulting in a new {@link Context} which includes entries diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java index c705899a40..e936c2ef34 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java @@ -16,6 +16,12 @@ package reactor.core.publisher; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import reactor.core.CorePublisher; +import reactor.core.Fuseable; +import reactor.core.Scannable; import reactor.util.Logger; import reactor.util.Loggers; @@ -65,7 +71,26 @@ static boolean shouldPropagateContextToThreadLocals() { return isContextPropagationOnClasspath && propagateContextToThreadLocals; } + static boolean shouldWrapPublisher(Publisher publisher) { + return shouldPropagateContextToThreadLocals() && + !Scannable.from(publisher).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); + } + static boolean shouldRestoreThreadLocalsInSomeOperators() { return isContextPropagationOnClasspath && !propagateContextToThreadLocals; } + + static Flux fluxRestoreThreadLocals(Flux flux) { + if (flux instanceof Fuseable) { + return new FluxContextWriteRestoringThreadLocalsFuseable<>(flux, Function.identity()); + } + return new FluxContextWriteRestoringThreadLocals<>(flux, Function.identity()); + } + + static Mono monoRestoreThreadLocals(Mono mono) { + if (mono instanceof Fuseable) { + return new MonoContextWriteRestoringThreadLocals<>(mono, Function.identity()); + } + return new MonoContextWriteRestoringThreadLocals<>(mono, Function.identity()); + } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index 95e2eeac94..d031236508 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -1063,8 +1063,7 @@ public static Flux firstWithValue(Publisher first, Publisher */ public static Flux from(Publisher source) { //duplicated in wrap, but necessary to detect early and thus avoid applying assembly - if (source instanceof Flux - && (Scannable.from(source).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false) || !ContextPropagationSupport.shouldPropagateContextToThreadLocals())) { + if (source instanceof Flux && !ContextPropagationSupport.shouldWrapPublisher(source)) { @SuppressWarnings("unchecked") Flux casted = (Flux) source; return casted; @@ -11075,14 +11074,12 @@ static BiFunction> tuple2Function() { */ @SuppressWarnings("unchecked") static Flux wrap(Publisher source) { - boolean isInternal = Scannable.from(source) - .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, - false); + boolean shouldWrap = ContextPropagationSupport.shouldWrapPublisher(source); if (source instanceof Flux) { - if (isInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + if (!shouldWrap) { return (Flux) source; } - return new FluxContextWriteRestoringThreadLocals<>((Flux) source, Function.identity()); + return ContextPropagationSupport.fluxRestoreThreadLocals((Flux) source); } //for scalars we'll instantiate the operators directly to avoid onAssembly @@ -11100,25 +11097,22 @@ static Flux wrap(Publisher source) { } } - Publisher target = source; - if (!isInternal && ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - if (target instanceof Mono) { - target = new MonoRestoringThreadLocals<>(source); - } else { - target = new FluxRestoringThreadLocals<>(source); - } - } - + Flux target; if (source instanceof Mono) { if (source instanceof Fuseable) { - return new FluxSourceMonoFuseable<>((Mono) target); + target = new FluxSourceMonoFuseable<>((Mono) source); + } else { + target = new FluxSourceMono<>((Mono) source); } - return new FluxSourceMono<>((Mono) target); + } else if (source instanceof Fuseable) { + target = new FluxSourceFuseable<>(source); + } else { + target = new FluxSource<>(source); } - if (source instanceof Fuseable) { - return new FluxSourceFuseable<>(target); + if (shouldWrap) { + return ContextPropagationSupport.fluxRestoreThreadLocals(target); } - return new FluxSource<>(target); + return target; } @SuppressWarnings("rawtypes") diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java index 415c852596..26efce3695 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java @@ -22,6 +22,7 @@ import io.micrometer.context.ContextSnapshot; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; import reactor.core.Fuseable.ConditionalSubscriber; import reactor.util.annotation.Nullable; import reactor.util.context.Context; @@ -49,6 +50,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java new file mode 100644 index 0000000000..7184b48220 --- /dev/null +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Objects; +import java.util.function.Function; + +import io.micrometer.context.ContextSnapshot; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +final class FluxContextWriteRestoringThreadLocalsFuseable extends FluxOperator + implements Fuseable { + + final Function doOnContext; + + FluxContextWriteRestoringThreadLocalsFuseable(Flux source, + Function doOnContext) { + super(source); + this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); + } + + @SuppressWarnings("try") + @Override + public void subscribe(CoreSubscriber actual) { + Context c = doOnContext.apply(actual.currentContext()); + + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { + source.subscribe(new FuseableContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; + return super.scanUnsafe(key); + } + + static final class FuseableContextWriteRestoringThreadLocalsSubscriber + implements ConditionalSubscriber, InnerOperator, + QueueSubscription { + + final CoreSubscriber actual; + final ConditionalSubscriber actualConditional; + final Context context; + + Subscription s; + + @SuppressWarnings("unchecked") + FuseableContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { + this.actual = actual; + this.context = context; + if (actual instanceof ConditionalSubscriber) { + this.actualConditional = (ConditionalSubscriber) actual; + } + else { + this.actualConditional = null; + } + } + + @Override + public T poll() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return this.context; + } + + @SuppressWarnings("try") + @Override + public void onSubscribe(Subscription s) { + // This is needed, as the downstream can then switch threads, + // continue the subscription using different primitives and omit this operator + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + } + + @SuppressWarnings("try") + @Override + public void onNext(T t) { + // We probably ended up here from a request, which set thread locals to + // current context, but we need to clean up and restore thread locals for + // the actual subscriber downstream, as it can expect TLs to match the + // different context. + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onNext(t); + } + } + + @Override + public int size() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Nope"); + } + + @SuppressWarnings("try") + @Override + public boolean tryOnNext(T t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (actualConditional != null) { + return actualConditional.tryOnNext(t); + } + actual.onNext(t); + return true; + } + } + + @SuppressWarnings("try") + @Override + public void onError(Throwable t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(t); + } + } + + @SuppressWarnings("try") + @Override + public void onComplete() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onComplete(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @SuppressWarnings("try") + @Override + public void request(long n) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.request(n); + } + } + + @SuppressWarnings("try") + @Override + public void cancel() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.cancel(); + } + } + } +} diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java index 2984d9d092..cde890c0df 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java @@ -105,6 +105,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.INTERNAL_PRODUCER) return false; return SourceProducer.super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java index 590aff8bf9..e93e3e4f31 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java @@ -47,7 +47,7 @@ protected FluxOperator(Flux source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == Attr.INTERNAL_PRODUCER) return false; // public class! return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java deleted file mode 100644 index a30ce51088..0000000000 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRestoringThreadLocals.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package reactor.core.publisher; - -import io.micrometer.context.ContextSnapshot; -import org.reactivestreams.Publisher; -import reactor.core.CoreSubscriber; -import reactor.util.context.Context; - -final class FluxRestoringThreadLocals extends Flux implements SourceProducer { - - private final Publisher source; - - FluxRestoringThreadLocals(Publisher source) { - this.source = source; - } - - @SuppressWarnings("try") - @Override - public void subscribe(CoreSubscriber actual) { - Context c = actual.currentContext(); - try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { - source.subscribe(new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); - } - } - - @Override - public Object scanUnsafe(Attr key) { - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return SourceProducer.super.scanUnsafe(key); - } -} diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java index 21d90be019..1c2fb9c506 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java @@ -88,7 +88,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - + if (key == Attr.INTERNAL_PRODUCER) return true; return super.scanUnsafe(key); } @@ -130,7 +130,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return s; if (key == Attr.TERMINATED) return done; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - + if (key == Attr.INTERNAL_PRODUCER) return true; return InnerOperator.super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index 7cecbb869a..2caa29b3bf 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -95,7 +95,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; if (key == Attr.INTERNAL_PRODUCER) return true; - return null; + return super.scanUnsafe(key); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java index b31f752f8c..4ac7bd3909 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java @@ -48,6 +48,12 @@ protected InternalMonoOperator(Mono source) { } } + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.INTERNAL_PRODUCER) return true; + return super.scanUnsafe(key); + } + @Override @SuppressWarnings("unchecked") public final void subscribe(CoreSubscriber subscriber) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index 5692930e91..f12352533e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -480,10 +480,8 @@ public static Mono firstWithValue(Mono first, Mono Mono from(Publisher source) { //some sources can be considered already assembled monos //all conversion methods (from, fromDirect, wrap) must accommodate for this - boolean isInternal = Scannable.from(source) - .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, - false); - if ((isInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) && source instanceof Mono) { + boolean shouldWrap = ContextPropagationSupport.shouldWrapPublisher(source); + if (source instanceof Mono && !shouldWrap) { @SuppressWarnings("unchecked") Mono casted = (Mono) source; return casted; @@ -495,8 +493,8 @@ public static Mono from(Publisher source) { FluxFromMonoOperator wrapper = (FluxFromMonoOperator) source; @SuppressWarnings("unchecked") Mono extracted = (Mono) wrapper.source; - boolean isExtractedInternal = Scannable.from(extracted).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); - if (isExtractedInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + boolean shouldWrapExtracted = ContextPropagationSupport.shouldWrapPublisher(extracted); + if (!shouldWrapExtracted) { return extracted; } else { // Skip assembly hook @@ -583,7 +581,8 @@ public static Mono fromCompletionStage(Supplier Mono fromDirect(Publisher source){ //some sources can be considered already assembled monos //all conversion methods (from, fromDirect, wrap) must accommodate for this - if(source instanceof Mono){ + boolean shouldWrap = ContextPropagationSupport.shouldWrapPublisher(source); + if (source instanceof Mono && !shouldWrap) { @SuppressWarnings("unchecked") Mono m = (Mono)source; return m; @@ -594,7 +593,14 @@ public static Mono fromDirect(Publisher source){ FluxFromMonoOperator wrapper = (FluxFromMonoOperator) source; @SuppressWarnings("unchecked") Mono extracted = (Mono) wrapper.source; - return extracted; + boolean shouldWrapExtracted = + ContextPropagationSupport.shouldWrapPublisher(extracted); + if (!shouldWrapExtracted) { + return extracted; + } else { + // Skip assembly hook + return wrap(extracted, false); + } } //we delegate to `wrap` and apply assembly hooks @@ -5347,11 +5353,9 @@ static Mono doOnTerminalSignal(Mono source, static Mono wrap(Publisher source, boolean enforceMonoContract) { //some sources can be considered already assembled monos //all conversion methods (from, fromDirect, wrap) must accommodate for this - boolean isInternal = Scannable.from(source) - .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, - false); + boolean shouldWrap = ContextPropagationSupport.shouldWrapPublisher(source); if (source instanceof Mono) { - if (isInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + if (!shouldWrap) { return (Mono) source; } return new MonoContextWriteRestoringThreadLocals<>((Mono) source, Function.identity()); @@ -5360,10 +5364,9 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { if (source instanceof FluxSourceMono || source instanceof FluxSourceMonoFuseable) { @SuppressWarnings("unchecked") Mono extracted = (Mono) ((FluxFromMonoOperator) source).source; - boolean isExtractedInternal = Scannable.from(extracted) - .scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, - false); - if (isExtractedInternal || !ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + boolean shouldWrapExtracted = + ContextPropagationSupport.shouldWrapPublisher(extracted); + if (!shouldWrapExtracted) { return extracted; } return new MonoContextWriteRestoringThreadLocals<>(extracted, Function.identity()); @@ -5374,30 +5377,30 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { return Flux.wrapToMono(m); } - Publisher target = source; - if (!isInternal && ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - target = new FluxRestoringThreadLocals<>(source); - } + Mono target; //equivalent to what from used to be, without assembly hooks if (enforceMonoContract) { if (source instanceof Flux) { - return new MonoNext<>((Flux) target); + target = new MonoNext<>((Flux) source); + } else { + target = new MonoFromPublisher<>(source); } - return new MonoFromPublisher<>(target); - } - //equivalent to what fromDirect used to be without onAssembly - if (source instanceof Flux && source instanceof Fuseable) { - return new MonoSourceFluxFuseable<>((Flux) target); + } else if (source instanceof Flux && source instanceof Fuseable) { + target = new MonoSourceFluxFuseable<>((Flux) source); + } else if (source instanceof Flux) { + target = new MonoSourceFlux<>((Flux) source); + } else if (source instanceof Fuseable) { + target = new MonoSourceFuseable<>(source); + } else { + target = new MonoSource<>(source); } - if (source instanceof Flux) { - return new MonoSourceFlux<>((Flux) target); - } - if (source instanceof Fuseable) { - return new MonoSourceFuseable<>(target); + + if (shouldWrap) { + return ContextPropagationSupport.monoRestoreThreadLocals(target); } - return new MonoSource<>(target); + return target; } @SuppressWarnings("unchecked") diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java index 69c533c7f3..a44de6257e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java @@ -22,10 +22,13 @@ import io.micrometer.context.ContextSnapshot; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; -final class MonoContextWriteRestoringThreadLocals extends MonoOperator { +// TODO: create fuseable version and handle the same way as Flux +final class MonoContextWriteRestoringThreadLocals extends MonoOperator + implements Fuseable { final Function doOnContext; @@ -48,11 +51,12 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return super.scanUnsafe(key); } static final class ContextWriteRestoringThreadLocalsSubscriber - implements InnerOperator { + implements InnerOperator, QueueSubscription { final CoreSubscriber actual; final Context context; @@ -164,5 +168,30 @@ public void cancel() { s.cancel(); } } + + @Override + public int requestFusion(int requestedMode) { + return NONE; + } + + @Override + public T poll() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public int size() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Nope"); + } } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java index 7dde724dc6..d2bc0aa303 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java @@ -47,8 +47,7 @@ protected MonoOperator(Mono source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return Integer.MAX_VALUE; if (key == Attr.PARENT) return source; - // FIXME: not internal! public - if (key == Attr.INTERNAL_PRODUCER) return false; + if (key == Attr.INTERNAL_PRODUCER) return false; // public class! return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java deleted file mode 100644 index dd8fd1dd8d..0000000000 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoRestoringThreadLocals.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package reactor.core.publisher; - -import io.micrometer.context.ContextSnapshot; -import org.reactivestreams.Publisher; -import reactor.core.CoreSubscriber; -import reactor.util.context.Context; - -final class MonoRestoringThreadLocals extends Mono implements SourceProducer { - - private final Publisher source; - - MonoRestoringThreadLocals(Publisher source) { - this.source = source; - } - - @SuppressWarnings("try") - @Override - public void subscribe(CoreSubscriber actual) { - Context c = actual.currentContext(); - try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { - source.subscribe(new MonoContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); - } - } - - @Override - public Object scanUnsafe(Attr key) { - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return SourceProducer.super.scanUnsafe(key); - } -} diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java index 14b329c99b..e84f17b489 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java @@ -83,6 +83,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return -1; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index d16bddb5be..41f755ff21 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -993,50 +993,54 @@ public static CorePublisher onLastAssembly(CorePublisher source) { public static CorePublisher toFluxOrMono(Publisher publisher) { if (publisher instanceof Mono) { - return Mono.from(publisher); + return Mono.fromDirect(publisher); } return Flux.from(publisher); } static CoreSubscriber restoreContextOnSubscriberIfNecessary( Publisher publisher, CoreSubscriber subscriber) { - if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - return subscriber; - } - Scannable scannable = Scannable.from(publisher); - boolean internal = scannable.isScanAvailable() - && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); - if (!internal) { - subscriber = new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( - subscriber, subscriber.currentContext()); + if (ContextPropagationSupport.shouldWrapPublisher(publisher)) { + if (publisher instanceof Fuseable) { + subscriber = + new FluxContextWriteRestoringThreadLocalsFuseable.FuseableContextWriteRestoringThreadLocalsSubscriber<>(subscriber, + subscriber.currentContext()); + } else { + subscriber = new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, + subscriber.currentContext()); + } } return subscriber; } static CoreSubscriber restoreContextOnSubscriber(CoreSubscriber subscriber) { - if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - return subscriber; + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, subscriber.currentContext()); } - return new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( - subscriber, subscriber.currentContext()); + return subscriber; } static CoreSubscriber[] restoreContextOnSubscribersIfNecessary( Publisher publisher, CoreSubscriber[] subscribers) { - if (!ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - return subscribers; - } - CoreSubscriber[] actualSubscribers = subscribers; - Scannable scannable = Scannable.from(publisher); - boolean internal = scannable.isScanAvailable() - && scannable.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); - if (!internal) { + if (ContextPropagationSupport.shouldWrapPublisher(publisher)) { actualSubscribers = new CoreSubscriber[subscribers.length]; for (int i = 0; i < subscribers.length; i++) { - CoreSubscriber subscriber = - new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( - subscribers[i], subscribers[i].currentContext()); + CoreSubscriber subscriber; + if (publisher instanceof Fuseable) { + subscriber = + new FluxContextWriteRestoringThreadLocalsFuseable.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + subscribers[i], + subscribers[i].currentContext()); + } + else { + subscriber = + new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscribers[i], + subscribers[i].currentContext()); + } actualSubscribers[i] = subscriber; } } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 02ed1d5139..3ed5139c4d 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -25,7 +25,6 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -42,7 +41,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; @@ -559,6 +557,44 @@ void directMonoSubscribeAsRawSubscriber() throws InterruptedException { // Flux tests + @Test + void fluxCreate() { + Flux flux = Flux.create(sink -> { + executorService.submit(() -> { + sink.next("Hello"); + sink.complete(); + }); + }); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxCreateDirect() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext(value, error, latch, complete); + + Publisher flux = Flux.create(sink -> { + executorService.submit(() -> { + sink.next("Hello"); + sink.complete(); + }); + }); + + flux.subscribe(subscriberWithContext); + + latch.await(10, TimeUnit.MILLISECONDS); + + assertThat(error.get()).isNull(); + assertThat(complete.get()).isTrue(); + assertThat(value.get()).isEqualTo("present"); + } + @Test void fluxMap() { Flux flux = @@ -950,7 +986,7 @@ void sinkEmpty() throws InterruptedException, TimeoutException { } @Test - @Disabled("Subscribe-time wrapping breaks fusion for now") +// @Disabled("Subscribe-time wrapping breaks fusion for now") //FIXME void sinkManyUnicast() throws InterruptedException, TimeoutException { AtomicReference value = new AtomicReference<>(); From 6bebd6982f7ba9c2dd4404f2221d1e85bd162e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Mon, 11 Sep 2023 10:15:14 +0200 Subject: [PATCH 08/26] mono fuseable variant --- .../publisher/ContextPropagationSupport.java | 2 +- ...FluxContextWriteRestoringThreadLocals.java | 7 +- .../java/reactor/core/publisher/Mono.java | 4 +- ...MonoContextWriteRestoringThreadLocals.java | 32 +-- ...extWriteRestoringThreadLocalsFuseable.java | 197 ++++++++++++++++++ .../reactor/core/publisher/MonoCreate.java | 3 +- .../reactor/core/publisher/Operators.java | 17 +- .../core/publisher/SinkEmptyMulticast.java | 3 +- .../core/publisher/SinkOneMulticast.java | 3 +- ...ContextWriteRestoringThreadLocalsTest.java | 7 +- 10 files changed, 226 insertions(+), 49 deletions(-) create mode 100644 reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java index e936c2ef34..d6ec49c701 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java @@ -89,7 +89,7 @@ static Flux fluxRestoreThreadLocals(Flux flux) { static Mono monoRestoreThreadLocals(Mono mono) { if (mono instanceof Fuseable) { - return new MonoContextWriteRestoringThreadLocals<>(mono, Function.identity()); + return new MonoContextWriteRestoringThreadLocalsFuseable<>(mono, Function.identity()); } return new MonoContextWriteRestoringThreadLocals<>(mono, Function.identity()); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java index 26efce3695..e2f17bc6af 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java @@ -22,7 +22,6 @@ import io.micrometer.context.ContextSnapshot; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; -import reactor.core.Fuseable; import reactor.core.Fuseable.ConditionalSubscriber; import reactor.util.annotation.Nullable; import reactor.util.context.Context; @@ -43,7 +42,7 @@ public void subscribe(CoreSubscriber actual) { Context c = doOnContext.apply(actual.currentContext()); try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { - source.subscribe(new ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + source.subscribe(new FuseableContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); } } @@ -54,7 +53,7 @@ public Object scanUnsafe(Attr key) { return super.scanUnsafe(key); } - static final class ContextWriteRestoringThreadLocalsSubscriber + static final class FuseableContextWriteRestoringThreadLocalsSubscriber implements ConditionalSubscriber, InnerOperator { final CoreSubscriber actual; @@ -64,7 +63,7 @@ static final class ContextWriteRestoringThreadLocalsSubscriber Subscription s; @SuppressWarnings("unchecked") - ContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { + FuseableContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { this.actual = actual; this.context = context; if (actual instanceof ConditionalSubscriber) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index f12352533e..edb2c1a6ea 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -5358,7 +5358,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { if (!shouldWrap) { return (Mono) source; } - return new MonoContextWriteRestoringThreadLocals<>((Mono) source, Function.identity()); + return ContextPropagationSupport.monoRestoreThreadLocals((Mono) source); } if (source instanceof FluxSourceMono || source instanceof FluxSourceMonoFuseable) { @@ -5369,7 +5369,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { if (!shouldWrapExtracted) { return extracted; } - return new MonoContextWriteRestoringThreadLocals<>(extracted, Function.identity()); + return ContextPropagationSupport.monoRestoreThreadLocals(extracted); } if (source instanceof Flux && source instanceof Callable) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java index a44de6257e..695f411902 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java @@ -22,13 +22,10 @@ import io.micrometer.context.ContextSnapshot; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; -import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; -// TODO: create fuseable version and handle the same way as Flux -final class MonoContextWriteRestoringThreadLocals extends MonoOperator - implements Fuseable { +final class MonoContextWriteRestoringThreadLocals extends MonoOperator { final Function doOnContext; @@ -56,7 +53,7 @@ public Object scanUnsafe(Attr key) { } static final class ContextWriteRestoringThreadLocalsSubscriber - implements InnerOperator, QueueSubscription { + implements InnerOperator { final CoreSubscriber actual; final Context context; @@ -168,30 +165,5 @@ public void cancel() { s.cancel(); } } - - @Override - public int requestFusion(int requestedMode) { - return NONE; - } - - @Override - public T poll() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public int size() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public boolean isEmpty() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public void clear() { - throw new UnsupportedOperationException("Nope"); - } } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java new file mode 100644 index 0000000000..e3dd47c6f2 --- /dev/null +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Objects; +import java.util.function.Function; + +import io.micrometer.context.ContextSnapshot; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +// TODO: create fuseable version and handle the same way as Flux +final class MonoContextWriteRestoringThreadLocalsFuseable extends MonoOperator + implements Fuseable { + + final Function doOnContext; + + MonoContextWriteRestoringThreadLocalsFuseable(Mono source, + Function doOnContext) { + super(source); + this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); + } + + @SuppressWarnings("try") + @Override + public void subscribe(CoreSubscriber actual) { + final Context c = doOnContext.apply(actual.currentContext()); + + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { + source.subscribe(new ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; + return super.scanUnsafe(key); + } + + static final class ContextWriteRestoringThreadLocalsSubscriber + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + final Context context; + + Subscription s; + boolean done; + + ContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { + this.actual = actual; + this.context = context; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return this.context; + } + + @SuppressWarnings("try") + @Override + public void onSubscribe(Subscription s) { + // This is needed, as the downstream can then switch threads, + // continue the subscription using different primitives and omit this operator + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + } + + @SuppressWarnings("try") + @Override + public void onNext(T t) { + this.done = true; + // We probably ended up here from a request, which set thread locals to + // current context, but we need to clean up and restore thread locals for + // the actual subscriber downstream, as it can expect TLs to match the + // different context. + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onNext(t); + actual.onComplete(); + } + } + + @SuppressWarnings("try") + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, context); + return; + } + + this.done = true; + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(t); + } + } + + @SuppressWarnings("try") + @Override + public void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onComplete(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @SuppressWarnings("try") + @Override + public void request(long n) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.request(n); + } + } + + @SuppressWarnings("try") + @Override + public void cancel() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.cancel(); + } + } + + @Override + public int requestFusion(int requestedMode) { + return NONE; + } + + @Override + public T poll() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public int size() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Nope"); + } + } +} diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java index 35a8d78b8e..1523510f4d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java @@ -50,7 +50,8 @@ final class MonoCreate extends Mono implements SourceProducer { @Override public void subscribe(CoreSubscriber actual) { - CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(actual); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); DefaultMonoSink emitter = new DefaultMonoSink<>(wrapped); diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 41f755ff21..19f6560b01 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -1006,7 +1006,7 @@ static CoreSubscriber restoreContextOnSubscriberIfNecessary( new FluxContextWriteRestoringThreadLocalsFuseable.FuseableContextWriteRestoringThreadLocalsSubscriber<>(subscriber, subscriber.currentContext()); } else { - subscriber = new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscriber = new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( subscriber, subscriber.currentContext()); } @@ -1014,10 +1014,17 @@ static CoreSubscriber restoreContextOnSubscriberIfNecessary( return subscriber; } - static CoreSubscriber restoreContextOnSubscriber(CoreSubscriber subscriber) { + static CoreSubscriber restoreContextOnSubscriber( + Publisher publisher, CoreSubscriber subscriber) { if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - return new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( - subscriber, subscriber.currentContext()); + if (publisher instanceof Fuseable) { + return new FluxContextWriteRestoringThreadLocalsFuseable.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, subscriber.currentContext()); + } else { + return new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, + subscriber.currentContext()); + } } return subscriber; } @@ -1037,7 +1044,7 @@ static CoreSubscriber[] restoreContextOnSubscribersIfNecessary( } else { subscriber = - new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( subscribers[i], subscribers[i].currentContext()); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java index e3222d3615..d342b25361 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java @@ -197,7 +197,8 @@ void remove(Inner ps) { //redefined in SinkOneMulticast @Override public void subscribe(final CoreSubscriber actual) { - CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(actual); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); Inner as = new VoidInner<>(wrapped, this); wrapped.onSubscribe(as); final int addedState = add(as); diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java index bad605800d..bc1c48905f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java @@ -82,7 +82,8 @@ public Object scanUnsafe(Attr key) { @Override public void subscribe(final CoreSubscriber actual) { - CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(actual); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); NextInner as = new NextInner<>(wrapped, this); wrapped.onSubscribe(as); final int addState = add(as); diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java index 3d7b2b4ab1..b1d94a235a 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java @@ -39,10 +39,9 @@ public void scanOperator(){ @Test public void scanSubscriber(){ CoreSubscriber actual = new LambdaSubscriber<>(null, e -> {}, null, null); - FluxContextWriteRestoringThreadLocals - .ContextWriteRestoringThreadLocalsSubscriber test = - new FluxContextWriteRestoringThreadLocals - .ContextWriteRestoringThreadLocalsSubscriber<>( + FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber + test = + new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( actual, Context.empty() ); From 1c1f2990877debae24170b1ecebbc0afe6aca494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Mon, 11 Sep 2023 10:39:18 +0200 Subject: [PATCH 09/26] Removed unnecessary fuseable classes --- .../publisher/ContextPropagationSupport.java | 6 - ...FluxContextWriteRestoringThreadLocals.java | 49 ++++- ...extWriteRestoringThreadLocalsFuseable.java | 202 ------------------ ...extWriteRestoringThreadLocalsFuseable.java | 197 ----------------- .../reactor/core/publisher/Operators.java | 12 +- ...ContextWriteRestoringThreadLocalsTest.java | 4 +- 6 files changed, 54 insertions(+), 416 deletions(-) delete mode 100644 reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java delete mode 100644 reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java index d6ec49c701..a3f1f5d9a1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java @@ -81,16 +81,10 @@ static boolean shouldRestoreThreadLocalsInSomeOperators() { } static Flux fluxRestoreThreadLocals(Flux flux) { - if (flux instanceof Fuseable) { - return new FluxContextWriteRestoringThreadLocalsFuseable<>(flux, Function.identity()); - } return new FluxContextWriteRestoringThreadLocals<>(flux, Function.identity()); } static Mono monoRestoreThreadLocals(Mono mono) { - if (mono instanceof Fuseable) { - return new MonoContextWriteRestoringThreadLocalsFuseable<>(mono, Function.identity()); - } return new MonoContextWriteRestoringThreadLocals<>(mono, Function.identity()); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java index e2f17bc6af..f01dc52d3b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java @@ -22,6 +22,7 @@ import io.micrometer.context.ContextSnapshot; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; import reactor.core.Fuseable.ConditionalSubscriber; import reactor.util.annotation.Nullable; import reactor.util.context.Context; @@ -42,7 +43,7 @@ public void subscribe(CoreSubscriber actual) { Context c = doOnContext.apply(actual.currentContext()); try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { - source.subscribe(new FuseableContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + source.subscribe(new ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); } } @@ -53,7 +54,7 @@ public Object scanUnsafe(Attr key) { return super.scanUnsafe(key); } - static final class FuseableContextWriteRestoringThreadLocalsSubscriber + static class ContextWriteRestoringThreadLocalsSubscriber implements ConditionalSubscriber, InnerOperator { final CoreSubscriber actual; @@ -63,7 +64,7 @@ static final class FuseableContextWriteRestoringThreadLocalsSubscriber Subscription s; @SuppressWarnings("unchecked") - FuseableContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { + ContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { this.actual = actual; this.context = context; if (actual instanceof ConditionalSubscriber) { @@ -172,4 +173,46 @@ public void cancel() { } } } + + static final class FuseableContextWriteRestoringThreadLocalsSubscriber + extends ContextWriteRestoringThreadLocalsSubscriber + implements Fuseable.QueueSubscription { + + FuseableContextWriteRestoringThreadLocalsSubscriber( + CoreSubscriber actual, Context context) { + super(actual, context); + } + + // Required for + // FuseableBestPracticesTest.coreFuseableSubscribersShouldNotExtendNonFuseableOnNext + @Override + public void onNext(T t) { + super.onNext(t); + } + + @Override + public T poll() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public int size() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException("Nope"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Nope"); + } + } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java deleted file mode 100644 index 7184b48220..0000000000 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsFuseable.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package reactor.core.publisher; - -import java.util.Objects; -import java.util.function.Function; - -import io.micrometer.context.ContextSnapshot; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.Fuseable; -import reactor.util.annotation.Nullable; -import reactor.util.context.Context; - -final class FluxContextWriteRestoringThreadLocalsFuseable extends FluxOperator - implements Fuseable { - - final Function doOnContext; - - FluxContextWriteRestoringThreadLocalsFuseable(Flux source, - Function doOnContext) { - super(source); - this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); - } - - @SuppressWarnings("try") - @Override - public void subscribe(CoreSubscriber actual) { - Context c = doOnContext.apply(actual.currentContext()); - - try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { - source.subscribe(new FuseableContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); - } - } - - @Override - public Object scanUnsafe(Attr key) { - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; - return super.scanUnsafe(key); - } - - static final class FuseableContextWriteRestoringThreadLocalsSubscriber - implements ConditionalSubscriber, InnerOperator, - QueueSubscription { - - final CoreSubscriber actual; - final ConditionalSubscriber actualConditional; - final Context context; - - Subscription s; - - @SuppressWarnings("unchecked") - FuseableContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { - this.actual = actual; - this.context = context; - if (actual instanceof ConditionalSubscriber) { - this.actualConditional = (ConditionalSubscriber) actual; - } - else { - this.actualConditional = null; - } - } - - @Override - public T poll() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public int requestFusion(int requestedMode) { - return Fuseable.NONE; - } - - @Override - @Nullable - public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) { - return s; - } - if (key == Attr.RUN_STYLE) { - return Attr.RunStyle.SYNC; - } - return InnerOperator.super.scanUnsafe(key); - } - - @Override - public Context currentContext() { - return this.context; - } - - @SuppressWarnings("try") - @Override - public void onSubscribe(Subscription s) { - // This is needed, as the downstream can then switch threads, - // continue the subscription using different primitives and omit this operator - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - if (Operators.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); - } - } - } - - @SuppressWarnings("try") - @Override - public void onNext(T t) { - // We probably ended up here from a request, which set thread locals to - // current context, but we need to clean up and restore thread locals for - // the actual subscriber downstream, as it can expect TLs to match the - // different context. - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onNext(t); - } - } - - @Override - public int size() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public boolean isEmpty() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public void clear() { - throw new UnsupportedOperationException("Nope"); - } - - @SuppressWarnings("try") - @Override - public boolean tryOnNext(T t) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - if (actualConditional != null) { - return actualConditional.tryOnNext(t); - } - actual.onNext(t); - return true; - } - } - - @SuppressWarnings("try") - @Override - public void onError(Throwable t) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onError(t); - } - } - - @SuppressWarnings("try") - @Override - public void onComplete() { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onComplete(); - } - } - - @Override - public CoreSubscriber actual() { - return actual; - } - - @SuppressWarnings("try") - @Override - public void request(long n) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(context)) { - s.request(n); - } - } - - @SuppressWarnings("try") - @Override - public void cancel() { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(context)) { - s.cancel(); - } - } - } -} diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java deleted file mode 100644 index e3dd47c6f2..0000000000 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocalsFuseable.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package reactor.core.publisher; - -import java.util.Objects; -import java.util.function.Function; - -import io.micrometer.context.ContextSnapshot; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.Fuseable; -import reactor.util.annotation.Nullable; -import reactor.util.context.Context; - -// TODO: create fuseable version and handle the same way as Flux -final class MonoContextWriteRestoringThreadLocalsFuseable extends MonoOperator - implements Fuseable { - - final Function doOnContext; - - MonoContextWriteRestoringThreadLocalsFuseable(Mono source, - Function doOnContext) { - super(source); - this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); - } - - @SuppressWarnings("try") - @Override - public void subscribe(CoreSubscriber actual) { - final Context c = doOnContext.apply(actual.currentContext()); - - try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { - source.subscribe(new ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); - } - } - - @Override - public Object scanUnsafe(Attr key) { - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; - return super.scanUnsafe(key); - } - - static final class ContextWriteRestoringThreadLocalsSubscriber - implements InnerOperator, QueueSubscription { - - final CoreSubscriber actual; - final Context context; - - Subscription s; - boolean done; - - ContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { - this.actual = actual; - this.context = context; - } - - @Override - @Nullable - public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) { - return s; - } - if (key == Attr.RUN_STYLE) { - return Attr.RunStyle.SYNC; - } - return InnerOperator.super.scanUnsafe(key); - } - - @Override - public Context currentContext() { - return this.context; - } - - @SuppressWarnings("try") - @Override - public void onSubscribe(Subscription s) { - // This is needed, as the downstream can then switch threads, - // continue the subscription using different primitives and omit this operator - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - if (Operators.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); - } - } - } - - @SuppressWarnings("try") - @Override - public void onNext(T t) { - this.done = true; - // We probably ended up here from a request, which set thread locals to - // current context, but we need to clean up and restore thread locals for - // the actual subscriber downstream, as it can expect TLs to match the - // different context. - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onNext(t); - actual.onComplete(); - } - } - - @SuppressWarnings("try") - @Override - public void onError(Throwable t) { - if (this.done) { - Operators.onErrorDropped(t, context); - return; - } - - this.done = true; - - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onError(t); - } - } - - @SuppressWarnings("try") - @Override - public void onComplete() { - if (this.done) { - return; - } - - this.done = true; - - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - actual.onComplete(); - } - } - - @Override - public CoreSubscriber actual() { - return actual; - } - - @SuppressWarnings("try") - @Override - public void request(long n) { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(context)) { - s.request(n); - } - } - - @SuppressWarnings("try") - @Override - public void cancel() { - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(context)) { - s.cancel(); - } - } - - @Override - public int requestFusion(int requestedMode) { - return NONE; - } - - @Override - public T poll() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public int size() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public boolean isEmpty() { - throw new UnsupportedOperationException("Nope"); - } - - @Override - public void clear() { - throw new UnsupportedOperationException("Nope"); - } - } -} diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 19f6560b01..56e94205b4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -1003,10 +1003,10 @@ static CoreSubscriber restoreContextOnSubscriberIfNecessary( if (ContextPropagationSupport.shouldWrapPublisher(publisher)) { if (publisher instanceof Fuseable) { subscriber = - new FluxContextWriteRestoringThreadLocalsFuseable.FuseableContextWriteRestoringThreadLocalsSubscriber<>(subscriber, + new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>(subscriber, subscriber.currentContext()); } else { - subscriber = new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + subscriber = new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( subscriber, subscriber.currentContext()); } @@ -1018,10 +1018,10 @@ static CoreSubscriber restoreContextOnSubscriber( Publisher publisher, CoreSubscriber subscriber) { if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { if (publisher instanceof Fuseable) { - return new FluxContextWriteRestoringThreadLocalsFuseable.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + return new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( subscriber, subscriber.currentContext()); } else { - return new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + return new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( subscriber, subscriber.currentContext()); } @@ -1038,13 +1038,13 @@ static CoreSubscriber[] restoreContextOnSubscribersIfNecessary( CoreSubscriber subscriber; if (publisher instanceof Fuseable) { subscriber = - new FluxContextWriteRestoringThreadLocalsFuseable.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( subscribers[i], subscribers[i].currentContext()); } else { subscriber = - new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( subscribers[i], subscribers[i].currentContext()); } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java index b1d94a235a..7df3888683 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocalsTest.java @@ -39,9 +39,9 @@ public void scanOperator(){ @Test public void scanSubscriber(){ CoreSubscriber actual = new LambdaSubscriber<>(null, e -> {}, null, null); - FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber + FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber test = - new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( actual, Context.empty() ); From 031f5503752ad31ea13b3652946267200f826eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Mon, 11 Sep 2023 10:42:39 +0200 Subject: [PATCH 10/26] Added Mono.create tests --- .../AutomaticContextPropagationTest.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 3ed5139c4d..74af9a7a92 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -647,6 +647,42 @@ void fluxFirstWithSignalIterable() { // Mono tests + @Test + void monoCreate() { + Mono mono = Mono.create(sink -> { + executorService.submit(() -> { + sink.success("Hello"); + }); + }); + + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void monoCreateDirect() throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext(value, error, latch, complete); + + Publisher mono = Mono.create(sink -> { + executorService.submit(() -> { + sink.success("Hello"); + }); + }); + + mono.subscribe(subscriberWithContext); + + latch.await(10, TimeUnit.MILLISECONDS); + + assertThat(error.get()).isNull(); + assertThat(complete.get()).isTrue(); + assertThat(value.get()).isEqualTo("present"); + } + @Test void monoSwitchThreadIgnoreThen() { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); @@ -986,8 +1022,6 @@ void sinkEmpty() throws InterruptedException, TimeoutException { } @Test -// @Disabled("Subscribe-time wrapping breaks fusion for now") - //FIXME void sinkManyUnicast() throws InterruptedException, TimeoutException { AtomicReference value = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); From 9a17e3a1f839dfe3d53e86f005d042671da7b906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 12 Sep 2023 09:04:43 +0200 Subject: [PATCH 11/26] delayUntil and sinks support --- .../core/publisher/MonoCompletionStage.java | 1 + .../core/publisher/MonoCurrentContext.java | 1 + .../core/publisher/MonoDelayUntil.java | 3 +- .../publisher/MonoSubscribeOnCallable.java | 1 + .../core/publisher/MonoSubscribeOnValue.java | 1 + .../core/publisher/SinkManyBestEffort.java | 14 ++- .../publisher/SinkManyEmitterProcessor.java | 11 +- .../publisher/SinkManyReplayProcessor.java | 9 +- .../core/publisher/SinkManyUnicast.java | 10 +- .../SinkManyUnicastNoBackpressure.java | 13 +- .../AutomaticContextPropagationTest.java | 117 +++++++++++++++++- 11 files changed, 160 insertions(+), 21 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java index 153b0e0b0d..99d0f26856 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java @@ -65,6 +65,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java index c12791b4cc..d091bbcab4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java @@ -39,6 +39,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java index f000c44f7a..e4a260089a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java @@ -123,6 +123,7 @@ public final CorePublisher source() { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; //no particular key to be represented, still useful in hooks } @@ -303,7 +304,7 @@ void subscribeNextTrigger() { this.triggerSubscriber = triggerSubscriber; } - p.subscribe(triggerSubscriber); + Operators.toFluxOrMono(p).subscribe(triggerSubscriber); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java index b4853fb322..dde5ec6734 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java @@ -62,6 +62,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Scannable.Attr key) { if (key == Scannable.Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java index e99aeec10f..4b7532a2a2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java @@ -67,6 +67,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java index 3c63f094b5..cda0e5d724 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,6 +77,7 @@ public Stream inners() { public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return subscribers == TERMINATED; if (key == Attr.ERROR) return error; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } @@ -191,8 +192,11 @@ public Flux asFlux() { public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe(null) is forbidden"); - DirectInner p = new DirectInner<>(actual, this); - actual.onSubscribe(p); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); + + DirectInner p = new DirectInner<>(wrapped, this); + wrapped.onSubscribe(p); if (p.isCancelled()) { return; @@ -206,10 +210,10 @@ public void subscribe(CoreSubscriber actual) { else { Throwable e = error; if (e != null) { - actual.onError(e); + wrapped.onError(e); } else { - actual.onComplete(); + wrapped.onComplete(); } } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java index ad815c50a4..67959a553f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -165,8 +165,12 @@ public Disposable subscribeTo(Publisher upstream) { @Override public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); - EmitterInner inner = new EmitterInner<>(actual, this); - actual.onSubscribe(inner); + + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); + + EmitterInner inner = new EmitterInner<>(wrapped, this); + wrapped.onSubscribe(inner); if (inner.isCancelled()) { return; @@ -373,6 +377,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return isTerminated(); if (key == Attr.ERROR) return getError(); if (key == Attr.CAPACITY) return getPrefetch(); + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java index 9493e79738..29b82a2dab 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -295,8 +295,11 @@ static SinkManyReplayProcessor createSizeAndTimeout(int size, @Override public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); - FluxReplay.ReplaySubscription rs = new ReplayInner<>(actual, this); - actual.onSubscribe(rs); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); + + FluxReplay.ReplaySubscription rs = new ReplayInner<>(wrapped, this); + wrapped.onSubscribe(rs); if (add(rs)) { if (rs.isCancelled()) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java index f754c63ca8..42c6710867 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java @@ -183,7 +183,7 @@ public Object scanUnsafe(Attr key) { if (Attr.CANCELLED == key) return cancelled; if (Attr.TERMINATED == key) return done; if (Attr.ERROR == key) return error; - if (Attr.INTERNAL_PRODUCER == key) return false; + if (Attr.INTERNAL_PRODUCER == key) return true; return null; } @@ -409,18 +409,20 @@ public Context currentContext() { @Override public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { this.hasDownstream = true; - actual.onSubscribe(this); - this.actual = actual; + wrapped.onSubscribe(this); + this.actual = wrapped; if (cancelled) { this.hasDownstream = false; } else { drain(null); } } else { - Operators.error(actual, new IllegalStateException("Sinks.many().unicast() sinks only allow a single Subscriber")); + Operators.error(wrapped, new IllegalStateException("Sinks.many().unicast() sinks only allow a single Subscriber")); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java index 3c9d12f539..fc8e555788 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,13 +76,17 @@ public Flux asFlux() { public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); + if (!STATE.compareAndSet(this, State.INITIAL, State.SUBSCRIBED)) { - Operators.reportThrowInSubscribe(actual, new IllegalStateException("Unicast Sinks.Many allows only a single Subscriber")); + Operators.reportThrowInSubscribe(wrapped, new IllegalStateException( + "Unicast Sinks.Many allows only a single Subscriber")); return; } - this.actual = actual; - actual.onSubscribe(this); + this.actual = wrapped; + wrapped.onSubscribe(this); } @Override @@ -190,6 +194,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.ACTUAL) return actual; if (key == Attr.TERMINATED) return state == State.TERMINATED; if (key == Attr.CANCELLED) return state == State.CANCELLED; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 74af9a7a92..aaf87a2c2c 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -697,6 +697,20 @@ void monoIgnoreThenSwitchThread() { assertThreadLocalPresentInOnNext(chain); } + @Test + void monoSwitchThreadDelayUntil() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = mono.delayUntil(s -> Mono.delay(Duration.ofMillis(1))); + assertThreadLocalPresentInOnNext(chain); + } + + @Test + void monoDelayUntilSwitchingThread() { + Mono mono = Mono.just("Hello"); + Mono chain = mono.delayUntil(s -> new ThreadSwitchingMono<>("Done", executorService)); + assertThreadLocalPresentInOnNext(chain); + } + @Test void monoDeferContextual() { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); @@ -1048,7 +1062,34 @@ void sinkManyUnicast() throws InterruptedException, TimeoutException { } @Test - void sinkManyMulticast() throws InterruptedException, TimeoutException { + void sinkManyUnicastNoBackpressure() throws InterruptedException, + TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.ManySpec spec = Sinks.many(); + + Sinks.Many many = spec.unicast().onBackpressureError(); + many.asFlux() + .doOnNext(i -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(() -> many.tryEmitNext("Hello")); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void sinkManyMulticastAllOrNothing() throws InterruptedException, + TimeoutException { AtomicReference value = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); @@ -1072,6 +1113,80 @@ void sinkManyMulticast() throws InterruptedException, TimeoutException { assertThat(value.get()).isEqualTo("present"); } + @Test + void sinkManyMulticastBuffer() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.ManySpec spec = Sinks.many(); + + Sinks.Many many = spec.multicast().onBackpressureBuffer(); + many.asFlux() + .doOnNext(i -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(() -> many.tryEmitNext("Hello")); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void sinkManyMulticastBestEffort() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.ManySpec spec = Sinks.many(); + + Sinks.Many many = spec.multicast().directBestEffort(); + many.asFlux() + .doOnNext(i -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(() -> many.tryEmitNext("Hello")); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + + @Test + void sinksEmpty() throws InterruptedException, TimeoutException { + AtomicReference value = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + Sinks.Empty spec = Sinks.empty(); + + spec.asMono() + .doOnSuccess(ignored -> { + value.set(REF.get()); + latch.countDown(); + }) + .contextWrite(Context.of(KEY, "present")) + .subscribe(); + + executorService.submit(spec::tryEmitEmpty); + + if (!latch.await(10, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } + + assertThat(value.get()).isEqualTo("present"); + } + // Other List> getAllClassesInClasspathRecursively(File directory) throws Exception { From 772c5fddf661bf93190678f72ee8a28625a3f761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Wed, 20 Sep 2023 16:43:14 +0200 Subject: [PATCH 12/26] Support for repeatWhen, retryWhen, subscribeOnCallable, switchOnFirst, ignorePublisher --- .../core/publisher/FluxRepeatWhen.java | 1 + .../reactor/core/publisher/FluxRetryWhen.java | 10 +- .../publisher/FluxSubscribeOnCallable.java | 1 + .../core/publisher/FluxSwitchOnFirst.java | 8 +- .../core/publisher/InternalFluxOperator.java | 1 + .../core/publisher/MonoIgnorePublisher.java | 2 +- .../reactor/core/publisher/MonoRetryWhen.java | 2 +- .../AutomaticContextPropagationTest.java | 186 ++++++++++++++++-- 8 files changed, 186 insertions(+), 25 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java index 37d892c4a1..a78e3b22ff 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java @@ -225,6 +225,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return main.otherArbiter; if (key == Attr.ACTUAL) return main; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java index b01172497c..46258ccc88 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java @@ -53,12 +53,14 @@ final class FluxRetryWhen extends InternalFluxOperator { static void subscribe(CoreSubscriber s, Retry whenSourceFactory, CorePublisher source) { + CorePublisher wrapped = Operators.toFluxOrMono(source); + RetryWhenOtherSubscriber other = new RetryWhenOtherSubscriber(); CoreSubscriber serial = Operators.serialize(s); RetryWhenMainSubscriber main = - new RetryWhenMainSubscriber<>(serial, other.completionSignal, source, whenSourceFactory.retryContext()); + new RetryWhenMainSubscriber<>(serial, other.completionSignal, wrapped, whenSourceFactory.retryContext()); other.main = main; serial.onSubscribe(main); @@ -71,10 +73,11 @@ static void subscribe(CoreSubscriber s, s.onError(Operators.onOperatorError(e, s.currentContext())); return; } - p.subscribe(other); + + Operators.toFluxOrMono(p).subscribe(other); if (!main.cancelled) { - source.subscribe(main); + wrapped.subscribe(main); } } @@ -255,6 +258,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return main.otherArbiter; if (key == Attr.ACTUAL) return main; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java index 944acba2d9..739964e4e1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java @@ -67,6 +67,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.INTERNAL_PRODUCER) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java index 44c8d7047a..1e3f20c294 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSwitchOnFirst.java @@ -542,7 +542,7 @@ public final void onNext(T t) { return; } - outboundPublisher.subscribe(o); + Operators.toFluxOrMono(outboundPublisher).subscribe(o); return; } @@ -586,7 +586,7 @@ public final void onError(Throwable t) { return; } - result.subscribe(o); + Operators.toFluxOrMono(result).subscribe(o); } } @@ -623,7 +623,7 @@ public final void onComplete() { return; } - result.subscribe(o); + Operators.toFluxOrMono(result).subscribe(o); } } @@ -868,7 +868,7 @@ public boolean tryOnNext(T t) { return true; } - result.subscribe(o); + Operators.toFluxOrMono(result).subscribe(o); return true; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index 2caa29b3bf..25f3327f2e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -65,6 +65,7 @@ public final void subscribe(CoreSubscriber subscriber) { if (newSource == null) { CorePublisher operatorSource = operator.source(); Operators.toFluxOrMono(operatorSource).subscribe(subscriber); +// operatorSource.subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java index 3aa48b892d..444acc051a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java @@ -40,7 +40,7 @@ final class MonoIgnorePublisher extends Mono implements Scannable, final OptimizableOperator optimizableOperator; MonoIgnorePublisher(Publisher source) { - this.source = Objects.requireNonNull(source, "publisher"); + this.source = Operators.toFluxOrMono(Objects.requireNonNull(source, "publisher")); if (source instanceof OptimizableOperator) { @SuppressWarnings("unchecked") OptimizableOperator optimSource = (OptimizableOperator) source; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java index 9ebeec9898..7c412497a6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java @@ -37,7 +37,7 @@ final class MonoRetryWhen extends InternalMonoOperator { final Retry whenSourceFactory; MonoRetryWhen(Mono source, Retry whenSourceFactory) { - super(source); + super(Mono.fromDirect(source)); this.whenSourceFactory = Objects.requireNonNull(whenSourceFactory, "whenSourceFactory"); } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index aaf87a2c2c..9c95e4b80d 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -33,6 +33,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -46,6 +47,7 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.core.scheduler.Schedulers; @@ -53,6 +55,7 @@ import reactor.test.subscriber.TestSubscriber; import reactor.util.concurrent.Queues; import reactor.util.context.Context; +import reactor.util.retry.Retry; import static org.assertj.core.api.Assertions.assertThat; @@ -330,6 +333,45 @@ void assertThreadLocalPresentInOnNext(Mono chain) { assertThat(value.get()).isEqualTo("present"); } + void assertThreadLocalPresentInOnSuccess(Mono chain) { + AtomicReference value = new AtomicReference<>(); + + chain.doOnSuccess(item -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .block(); + + assertThat(value.get()).isEqualTo("present"); + } + + void assertThreadLocalPresentInOnError(Mono chain) { + AtomicReference value = new AtomicReference<>(); + + chain.doOnError(item -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .onErrorComplete() + .block(); + + assertThat(value.get()).isEqualTo("present"); + } + + void assertThatThreadLocalsPresentDirectCoreSubscribe(CorePublisher source) throws InterruptedException { + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); + + source.subscribe(subscriberWithContext); + + latch.await(10, TimeUnit.MILLISECONDS); + + assertThat(error.get()).isNull(); + assertThat(complete.get()).isTrue(); + assertThat(value.get()).isEqualTo("present"); + } + void assertThreadLocalPresentInOnNext(Flux chain) { AtomicReference value = new AtomicReference<>(); @@ -340,6 +382,16 @@ void assertThreadLocalPresentInOnNext(Flux chain) { assertThat(value.get()).isEqualTo("present"); } + void assertThreadLocalPresentInOnComplete(Flux chain) { + AtomicReference value = new AtomicReference<>(); + + chain.doOnComplete(() -> value.set(REF.get())) + .contextWrite(Context.of(KEY, "present")) + .blockLast(); + + assertThat(value.get()).isEqualTo("present"); + } + // Fundamental tests for Flux @Test @@ -411,8 +463,8 @@ void directFluxSubscribeAsCoreSubscriber() throws InterruptedException { Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext(value, error, latch, complete); + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); flux.subscribe(subscriberWithContext); @@ -436,8 +488,8 @@ void directFluxSubscribeAsRawSubscriber() throws InterruptedException { Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext(value, error, latch, complete); + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); // We force the use of subscribe(Subscriber) override instead of // subscribe(CoreSubscriber), and we can observe that for such a case we @@ -514,8 +566,8 @@ void directMonoSubscribeAsCoreSubscriber() throws InterruptedException { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext(value, error, latch, complete); + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); mono.subscribe(subscriberWithContext); @@ -539,8 +591,8 @@ void directMonoSubscribeAsRawSubscriber() throws InterruptedException { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext(value, error, latch, complete); + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); // We force the use of subscribe(Subscriber) override instead of // subscribe(CoreSubscriber), and we can observe that for such a case we @@ -576,8 +628,8 @@ void fluxCreateDirect() throws InterruptedException { AtomicBoolean complete = new AtomicBoolean(); CountDownLatch latch = new CountDownLatch(1); - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext(value, error, latch, complete); + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); Publisher flux = Flux.create(sink -> { executorService.submit(() -> { @@ -645,6 +697,84 @@ void fluxFirstWithSignalIterable() { assertThreadLocalPresentInOnNext(Flux.firstWithSignal(list)); } + @Test + void fluxRetryWhen() { + Flux flux = + new ThreadSwitchingFlux<>("Hello", executorService).retryWhen(Retry.max(1)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxRetryWhenSwitchingThread() { + Flux flux = + Flux.error(new RuntimeException("Oops")) + .retryWhen(Retry.from(f -> new ThreadSwitchingFlux<>( + "Hello", executorService))); + + assertThreadLocalPresentInOnComplete(flux); + } + + @Test + void fluxWindowUntil() { + Flux flux = + new ThreadSwitchingFlux<>("Hello", executorService) + .windowUntil(s -> true) + .flatMap(Function.identity()); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void switchOnFirst() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) + .switchOnFirst((s, f) -> f.map(String::toUpperCase)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void switchOnFirstFuseable() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) + .filter("Hello"::equals) + .switchOnFirst((s, f) -> f.map(String::toUpperCase)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void switchOnFirstSwitchThread() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) + .switchOnFirst((s, f) -> new ThreadSwitchingFlux<>("Goodbye", executorService)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void switchOnFirstFuseableSwitchThread() { + Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) + .filter("Hello"::equals) + .switchOnFirst((s, f) -> new ThreadSwitchingFlux<>("Goodbye", executorService)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxWindowTimeout() { + Flux> flux = new ThreadSwitchingFlux<>("Hello", executorService) + .windowTimeout(1, Duration.ofDays(1), true); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxWindowTimeoutDirect() throws InterruptedException { + Flux> flux = new ThreadSwitchingFlux<>("Hello", executorService) + .windowTimeout(1, Duration.ofDays(1), true); + + assertThatThreadLocalsPresentDirectCoreSubscribe(flux); + } + // Mono tests @Test @@ -665,8 +795,8 @@ void monoCreateDirect() throws InterruptedException { AtomicBoolean complete = new AtomicBoolean(); CountDownLatch latch = new CountDownLatch(1); - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext(value, error, latch, complete); + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); Publisher mono = Mono.create(sink -> { executorService.submit(() -> { @@ -711,6 +841,12 @@ void monoDelayUntilSwitchingThread() { assertThreadLocalPresentInOnNext(chain); } + @Test + void monoIgnoreSwitchingThread() { + Mono mono = Mono.ignoreElements(new ThreadSwitchingMono<>("Hello", executorService)); + assertThreadLocalPresentInOnSuccess(mono); + } + @Test void monoDeferContextual() { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); @@ -747,6 +883,24 @@ void monoFromFluxSingle() { assertThreadLocalPresentInOnNext(chain); } + @Test + void monoRetryWhen() { + Mono mono = + new ThreadSwitchingMono<>("Hello", executorService).retryWhen(Retry.max(1)); + + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void monoRetryWhenSwitchingThread() { + Mono mono = + Mono.error(new RuntimeException("Oops")) + .retryWhen(Retry.from(f -> new ThreadSwitchingMono<>( + "Hello", executorService))); + + assertThreadLocalPresentInOnSuccess(mono); + } + // ParallelFlux tests @Test @@ -996,8 +1150,8 @@ void sinkDirect() throws InterruptedException, TimeoutException { Sinks.One sink = Sinks.one(); - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext(value, error, latch, complete); + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>(value, error, latch, complete); sink.asMono() .subscribe(subscriberWithContext); @@ -1249,7 +1403,7 @@ void printInterestingClasses() throws Exception { } } - private class CoreSubscriberWithContext implements CoreSubscriber { + private class CoreSubscriberWithContext implements CoreSubscriber { private final AtomicReference value; private final AtomicReference error; @@ -1277,7 +1431,7 @@ public void onSubscribe(Subscription s) { } @Override - public void onNext(String s) { + public void onNext(T t) { value.set(REF.get()); } From 9350f8c9bb81e1be3dd160d6eb5a334e89c695c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Wed, 20 Sep 2023 18:34:19 +0200 Subject: [PATCH 13/26] SourceProducer opeartors support (first batch) --- .../core/publisher/FluxConcatArray.java | 6 +- .../core/publisher/FluxDeferContextual.java | 4 - .../core/publisher/FluxFirstWithValue.java | 4 +- .../core/publisher/FluxMergeComparing.java | 1 + .../core/publisher/MonoFirstWithValue.java | 2 +- .../reactor/core/publisher/MonoUsing.java | 4 +- .../core/publisher/ParallelArraySource.java | 2 +- .../AutomaticContextPropagationTest.java | 83 +++++++++++++++++++ 8 files changed, 93 insertions(+), 13 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java index 701c2bebc7..4a775aa3e0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatArray.java @@ -61,7 +61,7 @@ public void subscribe(CoreSubscriber actual) { if (p == null) { Operators.error(actual, new NullPointerException("The single source Publisher is null")); } else { - p.subscribe(actual); + Operators.toFluxOrMono(p).subscribe(actual); } return; } @@ -255,7 +255,7 @@ public void onComplete() { if (this.cancelled) { return; } - p.subscribe(this); + Operators.toFluxOrMono(p).subscribe(this); final Object state = this.get(); if (state != DONE) { @@ -440,7 +440,7 @@ public void onComplete() { return; } - p.subscribe(this); + Operators.toFluxOrMono(p).subscribe(this); final Object state = this.get(); if (state != DONE) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java b/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java index a6dd95d6e6..c06186a2b4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxDeferContextual.java @@ -54,10 +54,6 @@ public void subscribe(CoreSubscriber actual) { return; } - // TODO: Here assembly hooks were applied, while in Mono's implementation, not - // even wrap (which skips assembly hooks) was called. - // We need to decide whether from/wrap perform the Subscriber wrapping instead - // of the below construct! Operators.toFluxOrMono(p).subscribe(actual); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java index e20ec188d6..bd20447ef6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithValue.java @@ -159,7 +159,7 @@ public void subscribe(CoreSubscriber actual) { return; } if (n == 1) { - Publisher p = a[0]; + Publisher p = Flux.from(a[0]); if (p == null) { Operators.error(actual, @@ -237,7 +237,7 @@ void subscribe(Publisher[] sources, return; } - sources[i].subscribe(subscribers[i]); + Operators.toFluxOrMono(sources[i]).subscribe(subscribers[i]); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java index 98dc353dfe..8f6a663eee 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java @@ -65,6 +65,7 @@ final class FluxMergeComparing extends Flux implements SourceProducer { if (source == null) { throw new NullPointerException("sources[" + i + "] is null"); } + sources[i] = Operators.toFluxOrMono(sources[i]); } this.prefetch = prefetch; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java index cc6db08159..cd2c2e0edd 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithValue.java @@ -147,7 +147,7 @@ public void subscribe(CoreSubscriber actual) { return; } if (n == 1) { - Publisher p = a[0]; + Publisher p = Mono.from(a[0]); if (p == null) { Operators.error(actual, diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java b/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java index c09a767c52..a227587cc6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoUsing.java @@ -82,8 +82,8 @@ public void subscribe(CoreSubscriber actual) { Mono p; try { - p = Objects.requireNonNull(sourceFactory.apply(resource), - "The sourceFactory returned a null value"); + p = Mono.fromDirect(Objects.requireNonNull(sourceFactory.apply(resource), + "The sourceFactory returned a null value")); } catch (Throwable e) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java index 3c4b66b513..f56ced780a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java @@ -50,7 +50,7 @@ public void subscribe(CoreSubscriber[] subscribers) { int n = subscribers.length; for (int i = 0; i < n; i++) { - Flux.from(sources[i]).subscribe(subscribers[i]); + Operators.toFluxOrMono(sources[i]).subscribe(subscribers[i]); } } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 9c95e4b80d..473de47541 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -775,6 +775,60 @@ void fluxWindowTimeoutDirect() throws InterruptedException { assertThatThreadLocalsPresentDirectCoreSubscribe(flux); } + @Test + void fluxMergeComparing() { + Flux flux = Flux.mergeComparing(Flux.empty(), + new ThreadSwitchingFlux<>("Hello", executorService)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxMergeComparingDirect() throws InterruptedException { + Flux flux = Flux.mergeComparing(Flux.empty(), + new ThreadSwitchingFlux<>("Hello", executorService)); + + assertThatThreadLocalsPresentDirectCoreSubscribe(flux); + } + + @Test + void fluxFirstWithValueArray() { + Flux flux = Flux.firstWithValue(Flux.empty(), + new ThreadSwitchingFlux<>("Hola", executorService)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxFirstWithValueIterable() { + List> list = Stream.of(Flux.empty(), + new ThreadSwitchingFlux<>("Hola", executorService)) + .collect(Collectors.toList()); + Flux flux = Flux.firstWithValue(list); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxConcatArray() { + Flux flux = Flux.concat(Mono.empty(), + new ThreadSwitchingFlux<>("Hello", executorService)); + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxConcatIterable() { + List> list = Stream.of(Flux.empty(), + new ThreadSwitchingFlux<>("Hello", executorService)) + .collect(Collectors.toList()); + + Flux flux = Flux.concat(list); + + assertThreadLocalPresentInOnNext(flux); + } + + + // Mono tests @Test @@ -901,6 +955,35 @@ void monoRetryWhenSwitchingThread() { assertThreadLocalPresentInOnSuccess(mono); } + @Test + void monoUsing() { + Mono mono = Mono.using( + () -> "Hello", + seed -> new ThreadSwitchingMono<>("Hola", executorService), + seed -> {}, + false); + + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void monoFirstWithValueArray() { + Mono mono = Mono.firstWithValue(Mono.empty(), + new ThreadSwitchingMono<>("Hola", executorService)); + + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void monoFirstWithValueIterable() { + List> list = Stream.of(Mono.empty(), + new ThreadSwitchingMono<>("Hola", executorService)) + .collect(Collectors.toList()); + Mono mono = Mono.firstWithValue(list); + + assertThreadLocalPresentInOnNext(mono); + } + // ParallelFlux tests @Test From 898e0ebd2ed3cca32257b5897d884de39a19f154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Mon, 25 Sep 2023 08:33:27 +0200 Subject: [PATCH 14/26] Remaining source producer wrappings --- .../publisher/ContextPropagationSupport.java | 1 + .../core/publisher/FluxCombineLatest.java | 4 + .../core/publisher/FluxConcatIterable.java | 2 +- .../reactor/core/publisher/FluxCreate.java | 9 +- .../core/publisher/FluxFirstWithSignal.java | 8 +- .../reactor/core/publisher/FluxFlatMap.java | 1 + .../reactor/core/publisher/FluxGenerate.java | 7 +- .../reactor/core/publisher/FluxMerge.java | 3 + .../core/publisher/FluxRepeatWhen.java | 2 +- .../reactor/core/publisher/FluxRetryWhen.java | 2 +- .../publisher/FluxSubscribeOnCallable.java | 2 +- .../reactor/core/publisher/FluxUsingWhen.java | 11 +- .../java/reactor/core/publisher/FluxZip.java | 5 + .../core/publisher/InternalFluxOperator.java | 4 +- .../core/publisher/InternalMonoOperator.java | 4 +- .../core/publisher/MonoCurrentContext.java | 2 +- .../reactor/core/publisher/MonoDefer.java | 2 +- .../core/publisher/MonoDelayUntil.java | 2 +- .../core/publisher/MonoFirstWithSignal.java | 4 + .../reactor/core/publisher/MonoRetryWhen.java | 2 +- .../core/publisher/MonoSequenceEqual.java | 4 +- .../publisher/MonoSubscribeOnCallable.java | 2 +- .../core/publisher/MonoSubscribeOnValue.java | 2 +- .../reactor/core/publisher/MonoUsingWhen.java | 7 +- .../java/reactor/core/publisher/MonoWhen.java | 4 + .../java/reactor/core/publisher/MonoZip.java | 4 + .../reactor/core/publisher/Operators.java | 4 +- .../core/publisher/ParallelArraySource.java | 2 +- .../src/test/java/reactor/HooksTraceTest.java | 4 +- .../reactor/core/publisher/HooksTest.java | 8 +- .../AutomaticContextPropagationTest.java | 119 ++++++++++++++++++ 31 files changed, 198 insertions(+), 39 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java index a3f1f5d9a1..528ebe54cb 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java @@ -19,6 +19,7 @@ import java.util.function.Function; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; import reactor.core.CorePublisher; import reactor.core.Fuseable; import reactor.core.Scannable; diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java index b75e43ed5f..beae28cba7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java @@ -166,6 +166,10 @@ else if (!(actual instanceof QueueSubscription)) { } } + for (int i = 0; i < n; i++) { + a[i] = Operators.toFluxOrMono(a[i]); + } + Queue queue = queueSupplier.get(); CombineLatestCoordinator coordinator = diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java index 540f6eceb3..cd59babd64 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxConcatIterable.java @@ -144,7 +144,7 @@ public void onComplete() { produced(c); } - p.subscribe(this); + Operators.toFluxOrMono(p).subscribe(this); if (isCancelled()) { return; diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java index cde890c0df..1c085e6201 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java @@ -88,9 +88,11 @@ static BaseSink createSink(CoreSubscriber t, @Override public void subscribe(CoreSubscriber actual) { - BaseSink sink = createSink(actual, backpressure); + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); + BaseSink sink = createSink(wrapped, backpressure); - actual.onSubscribe(sink); + wrapped.onSubscribe(sink); try { source.accept( createMode == CreateMode.PUSH_PULL ? new SerializedFluxSink<>(sink) : @@ -98,14 +100,13 @@ public void subscribe(CoreSubscriber actual) { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - sink.error(Operators.onOperatorError(ex, actual.currentContext())); + sink.error(Operators.onOperatorError(ex, wrapped.currentContext())); } } @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - if (key == Attr.INTERNAL_PRODUCER) return false; return SourceProducer.super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java index ea9ffff8a6..09ee8cdc35 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java @@ -132,6 +132,12 @@ public void subscribe(CoreSubscriber actual) { return; } + for (int i = 0; i < n; i++) { + if (a[i] != null) { + a[i] = Operators.toFluxOrMono(a[i]); + } + } + RaceCoordinator coordinator = new RaceCoordinator<>(n); coordinator.subscribe(a, n, actual); @@ -223,7 +229,7 @@ void subscribe(Publisher[] sources, return; } - Operators.toFluxOrMono(p).subscribe(a[i]); + p.subscribe(a[i]); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java index 6a4d05a1d4..8f5a0b5e8a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java @@ -430,6 +430,7 @@ else if (!delayError || !Exceptions.addThrowable(ERROR, this, e_)) { if (add(inner)) { p = Operators.toFluxOrMono(p); p.subscribe(inner); +// p.subscribe((Subscriber) Operators.restoreContextOnSubscriberIfNecessary(p, inner)); } else { Operators.onDiscard(t, actual.currentContext()); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java index b4da8db435..af8bd74b58 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java @@ -74,15 +74,18 @@ final class FluxGenerate @Override public void subscribe(CoreSubscriber actual) { + CoreSubscriber wrapped = + Operators.restoreContextOnSubscriber(this, actual); + S state; try { state = stateSupplier.call(); } catch (Throwable e) { - Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + Operators.error(wrapped, Operators.onOperatorError(e, wrapped.currentContext())); return; } - actual.onSubscribe(new GenerateSubscription<>(actual, state, generator, stateConsumer)); + wrapped.onSubscribe(new GenerateSubscription<>(wrapped, state, generator, stateConsumer)); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java index d0fceee9db..d1dabdc66a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java @@ -54,6 +54,9 @@ final class FluxMerge extends Flux implements SourceProducer { throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); } this.sources = Objects.requireNonNull(sources, "sources"); + for (int i = 0; i < sources.length; i++) { + this.sources[i] = Operators.toFluxOrMono(this.sources[i]); + } this.delayError = delayError; this.maxConcurrency = maxConcurrency; this.prefetch = prefetch; diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java index a78e3b22ff..74e1359f86 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java index 46258ccc88..8fd92d072d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java index 739964e4e1..0f3584e62c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java index 2c77695d0e..ac9906638b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxUsingWhen.java @@ -91,7 +91,7 @@ public void subscribe(CoreSubscriber actual) { asyncCancel, null); - p.subscribe(subscriber); + Operators.toFluxOrMono(p).subscribe(subscriber); } } catch (Throwable e) { @@ -101,7 +101,8 @@ public void subscribe(CoreSubscriber actual) { } //trigger the resource creation and delay the subscription to actual - resourceSupplier.subscribe(new ResourceSubscriber(actual, resourceClosure, asyncComplete, asyncError, asyncCancel, resourceSupplier instanceof Mono)); + Operators.toFluxOrMono(resourceSupplier).subscribe(new ResourceSubscriber(actual, + resourceClosure, asyncComplete, asyncError, asyncCancel, resourceSupplier instanceof Mono)); } @Override @@ -192,7 +193,7 @@ public void onNext(S resource) { final Publisher p = deriveFluxFromResource(resource, resourceClosure); - p.subscribe(FluxUsingWhen.prepareSubscriberForResource(resource, + Operators.toFluxOrMono(p).subscribe(FluxUsingWhen.prepareSubscriberForResource(resource, this.actual, this.asyncComplete, this.asyncError, @@ -361,7 +362,7 @@ public void onError(Throwable t) { return; } - p.subscribe(new RollbackInner(this, t)); + Operators.toFluxOrMono(p).subscribe(new RollbackInner(this, t)); } } @@ -381,7 +382,7 @@ public void onComplete() { return; } - p.subscribe(new CommitInner(this)); + Operators.toFluxOrMono(p).subscribe(new CommitInner(this)); } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java b/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java index 1f2568bead..78443ca1d3 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java @@ -294,6 +294,11 @@ void handleBoth(CoreSubscriber s, @Nullable Object[] scalars, int n, int sc) { + for (int i = 0; i < n; i++) { + if (srcs[i] != null) { + srcs[i] = Operators.toFluxOrMono(srcs[i]); + } + } if (sc != 0 && scalars != null) { if (n != sc) { ZipSingleCoordinator coordinator = diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index 25f3327f2e..a8c50f7b3e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -64,8 +64,8 @@ public final void subscribe(CoreSubscriber subscriber) { OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { CorePublisher operatorSource = operator.source(); - Operators.toFluxOrMono(operatorSource).subscribe(subscriber); -// operatorSource.subscribe(subscriber); +// Operators.toFluxOrMono(operatorSource).subscribe(subscriber); + operatorSource.subscribe(Operators.restoreContextOnSubscriberIfNecessary(operatorSource, subscriber)); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java index 4ac7bd3909..45c194c43b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java @@ -68,7 +68,9 @@ public final void subscribe(CoreSubscriber subscriber) { OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { CorePublisher operatorSource = operator.source(); - Operators.toFluxOrMono(operatorSource).subscribe(subscriber); +// Operators.toFluxOrMono(operatorSource).subscribe(subscriber); + operatorSource.subscribe(Operators.restoreContextOnSubscriberIfNecessary( + operatorSource, subscriber)); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java index d091bbcab4..55c8409d15 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java index 6238895e4f..b7ca5cefa1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDefer.java @@ -50,7 +50,7 @@ public void subscribe(CoreSubscriber actual) { return; } - p.subscribe(actual); + fromDirect(p).subscribe(actual); } @Override diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java index e4a260089a..77489553eb 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java index ccd127b05f..1c4d83ad61 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java @@ -141,6 +141,10 @@ public void subscribe(CoreSubscriber actual) { return; } + for (int i = 0; i < n; i++) { + a[i] = Operators.toFluxOrMono(a[i]); + } + FluxFirstWithSignal.RaceCoordinator coordinator = new FluxFirstWithSignal.RaceCoordinator<>(n); diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java index 7c412497a6..76bd7d7c14 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoRetryWhen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java index fc728142e4..8dd645eca2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSequenceEqual.java @@ -42,8 +42,8 @@ final class MonoSequenceEqual extends Mono implements SourceProducer MonoSequenceEqual(Publisher first, Publisher second, BiPredicate comparer, int prefetch) { - this.first = Objects.requireNonNull(first, "first"); - this.second = Objects.requireNonNull(second, "second"); + this.first = Operators.toFluxOrMono(Objects.requireNonNull(first, "first")); + this.second = Operators.toFluxOrMono(Objects.requireNonNull(second, "second")); this.comparer = Objects.requireNonNull(comparer, "comparer"); if(prefetch < 1){ throw new IllegalArgumentException("Buffer size must be strictly positive: " + diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java index dde5ec6734..d984792bfb 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java index 4b7532a2a2..ab9b9875c2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java index 481ba9d18a..507c78be77 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoUsingWhen.java @@ -84,7 +84,7 @@ public void subscribe(CoreSubscriber actual) { asyncCancel, null); - p.subscribe(subscriber); + fromDirect(p).subscribe(subscriber); } } catch (Throwable e) { @@ -93,7 +93,8 @@ public void subscribe(CoreSubscriber actual) { return; } - resourceSupplier.subscribe(new ResourceSubscriber(actual, resourceClosure, + Operators.toFluxOrMono(resourceSupplier).subscribe(new ResourceSubscriber(actual, + resourceClosure, asyncComplete, asyncError, asyncCancel, resourceSupplier instanceof Mono)); } @@ -180,7 +181,7 @@ public void onNext(S resource) { final Mono p = deriveMonoFromResource(resource, resourceClosure); - p.subscribe(MonoUsingWhen.prepareSubscriberForResource(resource, + fromDirect(p).subscribe(MonoUsingWhen.prepareSubscriberForResource(resource, this.actual, this.asyncComplete, this.asyncError, diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java index 3348b843d7..e9cbd2d7a5 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java @@ -95,6 +95,10 @@ public void subscribe(CoreSubscriber actual) { return; } + for (int i = 0; i < n; i++) { + a[i] = Operators.toFluxOrMono(a[i]); + } + WhenCoordinator parent = new WhenCoordinator(a, actual, n, delayError); actual.onSubscribe(parent); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java b/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java index dc3aa0626f..2f06e02bab 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoZip.java @@ -122,6 +122,10 @@ public void subscribe(CoreSubscriber actual) { return; } + for (int i = 0; i < n; i++) { + a[i] = Mono.fromDirect(a[i]); + } + actual.onSubscribe(new ZipCoordinator<>(a, actual, n, delayError, zipper)); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 56e94205b4..fb789949d8 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -998,8 +998,8 @@ public static CorePublisher toFluxOrMono(Publisher publisher return Flux.from(publisher); } - static CoreSubscriber restoreContextOnSubscriberIfNecessary( - Publisher publisher, CoreSubscriber subscriber) { + static CoreSubscriber restoreContextOnSubscriberIfNecessary( + Publisher publisher, CoreSubscriber subscriber) { if (ContextPropagationSupport.shouldWrapPublisher(publisher)) { if (publisher instanceof Fuseable) { subscriber = diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java index f56ced780a..4be635f2f4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/reactor-core/src/test/java/reactor/HooksTraceTest.java b/reactor-core/src/test/java/reactor/HooksTraceTest.java index b759b909ed..26073c39ee 100644 --- a/reactor-core/src/test/java/reactor/HooksTraceTest.java +++ b/reactor-core/src/test/java/reactor/HooksTraceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -376,7 +376,7 @@ public void onComplete() { StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) .log(null, Level.OFF) .log(null, Level.OFF)) - .expectNext(7, 7) //from now counts as an additional one + .expectNext(6, 6) .verifyComplete(); } diff --git a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java index 84ad1d9b64..e0ec44eee5 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -949,13 +949,13 @@ public void eachOperatorLiftsParallelFlux() { AtomicInteger liftCounter = new AtomicInteger(); Hooks.onEachOperator(Operators.lift((sc, sub) -> liftSubscriber(sc, sub, liftCounter))); - StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) //internally converts each rail to a Flux using Flux.from + StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) .log() .log()) - .expectNext(601, 601) //6x lifts => just,Flux.from,ParallelFlux.from,log,log, back to sequential (shared) + .expectNext(501, 501) //6x lifts => just,Flux.from,ParallelFlux.from,log,log, back to sequential (shared) .verifyComplete(); - assertThat(liftCounter).hasValue(11); //11x total lifts: 2xjust, 2xfrom, 2xparallelFrom,4xlog,sequential + assertThat(liftCounter).hasValue(9); //9x total lifts: 2xjust,2xparallelFrom,4xlog,sequential } @Test diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 473de47541..4617695c2f 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -18,6 +18,7 @@ import java.io.File; import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -55,6 +56,8 @@ import reactor.test.subscriber.TestSubscriber; import reactor.util.concurrent.Queues; import reactor.util.context.Context; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; import reactor.util.retry.Retry; import static org.assertj.core.api.Assertions.assertThat; @@ -621,6 +624,21 @@ void fluxCreate() { assertThreadLocalPresentInOnNext(flux); } + /* + 1. raw RS Publisher: + a) subscribe(Subscriber) -> we need to wrap when we call publisher.subscribe() + 2. Flux/Mono + a) subscribe(CoreSubscriber) -> we need to wrap if publisher is not INTERNAL_PRODUCER + b) subscribe(Subscriber) -> no need to wrap, because of the fallback in Flux#subscribe(Subscriber) + BUT, if implementation overrides subscribe (Subscriber) that fallback is GONE ? NO -> method is final + + Problem with Subscriber wrapping: + When we have raw Publisher ref, we wrap the Subscriber, then call + subscribe(Subscriber), we'd wrap Subscriber again, as the Publisher is still + not INTERNAL_PRODUCER. + We could do instanceof, but would need to go through all instances. + We could have every Subscriber report Scannable RESTORING_THREAD_LOCALS instead. + */ @Test void fluxCreateDirect() throws InterruptedException { AtomicReference value = new AtomicReference<>(); @@ -827,7 +845,56 @@ void fluxConcatIterable() { assertThreadLocalPresentInOnNext(flux); } + @Test + void fluxGenerate() { + Flux flux = Flux.generate(sink -> { + sink.next("Hello"); + // the generator is checked if any signal was delivered by the consumer + // so we check at completion only + executorService.submit(() -> { + sink.complete(); + }); + }); + + assertThreadLocalPresentInOnComplete(flux); + } + + @Test + void fluxCombineLatest() { + Flux flux = Flux.combineLatest(Flux.just(""), + new ThreadSwitchingFlux<>("Hello", executorService), (s1, s2) -> s2); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxUsing() { + Flux flux = Flux.using(() -> 0, i -> new ThreadSwitchingFlux<>( + "Hello", executorService), i -> {}); + + assertThreadLocalPresentInOnNext(flux); + } + @Test + void fluxZip() { + Flux> flux = Flux.zip(Flux.just(""), + new ThreadSwitchingFlux<>("Hello", + executorService)); + + assertThreadLocalPresentInOnNext(flux); + } + + @Test + void fluxZipIterable() { + List> list = Stream.of(Flux.just(""), + new ThreadSwitchingFlux<>("Hello", + executorService)).collect(Collectors.toList()); + + Flux> flux = Flux.zip(list, + obj -> Tuples.of((String) obj[0], (String) obj[1])); + + assertThreadLocalPresentInOnNext(flux); + } // Mono tests @@ -908,6 +975,13 @@ void monoDeferContextual() { assertThreadLocalPresentInOnNext(chain); } + @Test + void monoDefer() { + Mono mono = new ThreadSwitchingMono<>("Hello", executorService); + Mono chain = Mono.defer(() -> mono); + assertThreadLocalPresentInOnNext(chain); + } + @Test void monoFirstWithSignalArray() { Mono mono = new ThreadSwitchingMono<>("Hello", executorService); @@ -984,6 +1058,51 @@ void monoFirstWithValueIterable() { assertThreadLocalPresentInOnNext(mono); } + @Test + void monoZip() { + Mono> mono = Mono.zip(Mono.just(""), + new ThreadSwitchingMono<>("Hello", + executorService)); + + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void monoZipIterable() { + List> list = Stream.of(Mono.just(""), + new ThreadSwitchingMono<>("Hello", + executorService)).collect(Collectors.toList()); + + Mono> mono = Mono.zip(list, + obj -> Tuples.of((String) obj[0], (String) obj[1])); + + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void monoSequenceEqual() { + Mono mono = Mono.sequenceEqual(Mono.just("Hello"), + new ThreadSwitchingMono<>("Hello", executorService)); + + assertThreadLocalPresentInOnNext(mono); + } + + @Test + void monoWhen() { + Mono mono = Mono.when(Mono.empty(), new ThreadSwitchingMono<>("Hello" + , executorService)); + + assertThreadLocalPresentInOnSuccess(mono); + } + + @Test + void monoUsingWhen() { + Mono mono = Mono.usingWhen(Mono.just("Hello"), s -> + new ThreadSwitchingMono<>(s, executorService), s -> Mono.empty()); + + assertThreadLocalPresentInOnNext(mono); + } + // ParallelFlux tests @Test From 1b156114e3001ea1c2a0e84a975ae98798e5f3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Mon, 25 Sep 2023 14:24:35 +0200 Subject: [PATCH 15/26] Cleanup --- .../java/reactor/core/publisher/Flux.java | 6 +- .../core/publisher/FluxCombineLatest.java | 4 +- .../reactor/core/publisher/FluxCreate.java | 2 +- .../core/publisher/FluxFirstWithSignal.java | 6 +- .../reactor/core/publisher/FluxFlatMap.java | 3 - .../core/publisher/FluxFromMonoOperator.java | 1 + .../reactor/core/publisher/FluxGenerate.java | 2 +- .../reactor/core/publisher/FluxMerge.java | 6 +- .../core/publisher/FluxMergeComparing.java | 3 +- .../core/publisher/FluxWindowPredicate.java | 4 +- .../core/publisher/FluxWindowTimeout.java | 4 +- .../java/reactor/core/publisher/FluxZip.java | 7 +-- .../core/publisher/InternalFluxOperator.java | 4 +- .../core/publisher/InternalMonoOperator.java | 5 +- .../java/reactor/core/publisher/Mono.java | 6 +- .../reactor/core/publisher/MonoCreate.java | 2 +- .../core/publisher/MonoFirstWithSignal.java | 4 +- .../core/publisher/MonoFromFluxOperator.java | 3 +- .../core/publisher/MonoFromPublisher.java | 1 + .../java/reactor/core/publisher/MonoWhen.java | 4 +- .../reactor/core/publisher/Operators.java | 59 +++++++++---------- .../core/publisher/ParallelArraySource.java | 1 - .../reactor/core/publisher/ParallelFlux.java | 3 +- .../ParallelFluxRestoringThreadLocals.java | 2 +- .../core/publisher/SinkEmptyMulticast.java | 2 +- .../core/publisher/SinkManyBestEffort.java | 2 +- .../publisher/SinkManyEmitterProcessor.java | 2 +- .../publisher/SinkManyReplayProcessor.java | 2 +- .../core/publisher/SinkManyUnicast.java | 2 +- .../SinkManyUnicastNoBackpressure.java | 2 +- .../core/publisher/SinkOneMulticast.java | 3 +- 31 files changed, 65 insertions(+), 92 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index d031236508..f0bedcfd8f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -8773,11 +8773,7 @@ public final void subscribe(Subscriber actual) { } } - // TODO: Consider: no need to wrap when subscriber is already wrapped and - // actually we should always wrap if we do the right job, as w can't intercept - // subscribe(CoreSubscriber), so this would be the last resort when the user - // directly subscribes. - subscriber = Operators.restoreContextOnSubscriberIfNecessary(publisher, subscriber); + subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(publisher, subscriber); publisher.subscribe(subscriber); } catch (Throwable e) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java index beae28cba7..2e022b7dd3 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCombineLatest.java @@ -166,9 +166,7 @@ else if (!(actual instanceof QueueSubscription)) { } } - for (int i = 0; i < n; i++) { - a[i] = Operators.toFluxOrMono(a[i]); - } + Operators.toFluxOrMono(a); Queue queue = queueSupplier.get(); diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java index 1c085e6201..fcd01bc9d0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxCreate.java @@ -89,7 +89,7 @@ static BaseSink createSink(CoreSubscriber t, @Override public void subscribe(CoreSubscriber actual) { CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); BaseSink sink = createSink(wrapped, backpressure); wrapped.onSubscribe(sink); diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java index 09ee8cdc35..336a8b6182 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFirstWithSignal.java @@ -132,11 +132,7 @@ public void subscribe(CoreSubscriber actual) { return; } - for (int i = 0; i < n; i++) { - if (a[i] != null) { - a[i] = Operators.toFluxOrMono(a[i]); - } - } + Operators.toFluxOrMono(a); RaceCoordinator coordinator = new RaceCoordinator<>(n); diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java index 8f5a0b5e8a..9e384109a7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFlatMap.java @@ -197,8 +197,6 @@ static boolean trySubscribeScalarMap(Publisher source, } } else { - // TODO: wrap Subscriber only or wrap the Publisher? What about - // assembly hooks? p = Operators.toFluxOrMono(p); if (!fuseableExpected || p instanceof Fuseable) { p.subscribe(s); @@ -430,7 +428,6 @@ else if (!delayError || !Exceptions.addThrowable(ERROR, this, e_)) { if (add(inner)) { p = Operators.toFluxOrMono(p); p.subscribe(inner); -// p.subscribe((Subscriber) Operators.restoreContextOnSubscriberIfNecessary(p, inner)); } else { Operators.onDiscard(t, actual.currentContext()); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java index ca0d19f318..37e4e8cb80 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java @@ -81,6 +81,7 @@ public final void subscribe(CoreSubscriber subscriber) { } OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { + subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(operator.source(), subscriber); operator.source().subscribe(subscriber); return; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java index af8bd74b58..fbc2a0f4bb 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxGenerate.java @@ -75,7 +75,7 @@ final class FluxGenerate @Override public void subscribe(CoreSubscriber actual) { CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); S state; diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java index d1dabdc66a..8c97c44cc3 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMerge.java @@ -54,9 +54,9 @@ final class FluxMerge extends Flux implements SourceProducer { throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); } this.sources = Objects.requireNonNull(sources, "sources"); - for (int i = 0; i < sources.length; i++) { - this.sources[i] = Operators.toFluxOrMono(this.sources[i]); - } + + Operators.toFluxOrMono(this.sources); + this.delayError = delayError; this.maxConcurrency = maxConcurrency; this.prefetch = prefetch; diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java index 8f6a663eee..d12f9d54d5 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMergeComparing.java @@ -65,9 +65,10 @@ final class FluxMergeComparing extends Flux implements SourceProducer { if (source == null) { throw new NullPointerException("sources[" + i + "] is null"); } - sources[i] = Operators.toFluxOrMono(sources[i]); } + Operators.toFluxOrMono(this.sources); + this.prefetch = prefetch; this.valueComparator = valueComparator; this.delayError = delayError; diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxWindowPredicate.java b/reactor-core/src/main/java/reactor/core/publisher/FluxWindowPredicate.java index 53d44cdc13..9324ce7d26 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxWindowPredicate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxWindowPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ final class FluxWindowPredicate extends InternalFluxOperator> int prefetch, Predicate predicate, Mode mode) { - super(source); + super(Flux.from(source)); if (prefetch <= 0) { throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxWindowTimeout.java b/reactor-core/src/main/java/reactor/core/publisher/FluxWindowTimeout.java index 3b1c346c66..c916a508b9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxWindowTimeout.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxWindowTimeout.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ final class FluxWindowTimeout extends InternalFluxOperator> { TimeUnit unit, Scheduler timer, boolean fairBackpressure) { - super(source); + super(Flux.from(source)); if (timespan <= 0) { throw new IllegalArgumentException("Timeout period must be strictly positive"); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java b/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java index 78443ca1d3..89f1e1dc38 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxZip.java @@ -294,11 +294,8 @@ void handleBoth(CoreSubscriber s, @Nullable Object[] scalars, int n, int sc) { - for (int i = 0; i < n; i++) { - if (srcs[i] != null) { - srcs[i] = Operators.toFluxOrMono(srcs[i]); - } - } + Operators.toFluxOrMono(srcs); + if (sc != 0 && scalars != null) { if (n != sc) { ZipSingleCoordinator coordinator = diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index a8c50f7b3e..96411e1296 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -64,8 +64,8 @@ public final void subscribe(CoreSubscriber subscriber) { OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { CorePublisher operatorSource = operator.source(); -// Operators.toFluxOrMono(operatorSource).subscribe(subscriber); - operatorSource.subscribe(Operators.restoreContextOnSubscriberIfNecessary(operatorSource, subscriber)); + subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(operatorSource, subscriber); + operatorSource.subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java index 45c194c43b..a563b36426 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java @@ -68,9 +68,8 @@ public final void subscribe(CoreSubscriber subscriber) { OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { CorePublisher operatorSource = operator.source(); -// Operators.toFluxOrMono(operatorSource).subscribe(subscriber); - operatorSource.subscribe(Operators.restoreContextOnSubscriberIfNecessary( - operatorSource, subscriber)); + subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(operatorSource, subscriber); + operatorSource.subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index edb2c1a6ea..7543534928 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -4508,11 +4508,7 @@ public final void subscribe(Subscriber actual) { } } - // TODO: Consider: no need to wrap when subscriber is already wrapped and - // actually we should always wrap if we do the right job, as w can't intercept - // subscribe(CoreSubscriber), so this would be the last resort when the user - // directly subscribes. - subscriber = Operators.restoreContextOnSubscriberIfNecessary(publisher, subscriber); + subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(publisher, subscriber); publisher.subscribe(subscriber); } catch (Throwable e) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java index 1523510f4d..f4f047502f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java @@ -51,7 +51,7 @@ final class MonoCreate extends Mono implements SourceProducer { @Override public void subscribe(CoreSubscriber actual) { CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); DefaultMonoSink emitter = new DefaultMonoSink<>(wrapped); diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java index 1c4d83ad61..29fa86f816 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFirstWithSignal.java @@ -141,9 +141,7 @@ public void subscribe(CoreSubscriber actual) { return; } - for (int i = 0; i < n; i++) { - a[i] = Operators.toFluxOrMono(a[i]); - } + Operators.toFluxOrMono(a); FluxFirstWithSignal.RaceCoordinator coordinator = new FluxFirstWithSignal.RaceCoordinator<>(n); diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java index 9a70e602c7..17ca91f2f9 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java @@ -79,7 +79,8 @@ public final void subscribe(CoreSubscriber subscriber) { } OptimizableOperator newSource = operator.nextOptimizableSource(); if (newSource == null) { - Operators.toFluxOrMono(operator.source()).subscribe(subscriber); + subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(operator.source(), subscriber); + operator.source().subscribe(subscriber); return; } operator = newSource; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java index 2e00029797..3e172d6c53 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java @@ -60,6 +60,7 @@ public void subscribe(CoreSubscriber actual) { if (subscriber == null) { return; } + subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(source, subscriber); source.subscribe(subscriber); } catch (Throwable e) { diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java index e9cbd2d7a5..fa9ab34966 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoWhen.java @@ -95,9 +95,7 @@ public void subscribe(CoreSubscriber actual) { return; } - for (int i = 0; i < n; i++) { - a[i] = Operators.toFluxOrMono(a[i]); - } + Operators.toFluxOrMono(a); WhenCoordinator parent = new WhenCoordinator(a, actual, n, delayError); actual.onSubscribe(parent); diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index fb789949d8..7c450a353f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -991,14 +991,22 @@ public static CorePublisher onLastAssembly(CorePublisher source) { } } - public static CorePublisher toFluxOrMono(Publisher publisher) { + public static CorePublisher toFluxOrMono(Publisher publisher) { if (publisher instanceof Mono) { return Mono.fromDirect(publisher); } return Flux.from(publisher); } - static CoreSubscriber restoreContextOnSubscriberIfNecessary( + public static void toFluxOrMono(Publisher[] sources) { + for (int i = 0; i < sources.length; i++) { + if (sources[i] != null) { + sources[i] = toFluxOrMono(sources[i]); + } + } + } + + static CoreSubscriber restoreContextOnSubscriberIfPublisherNonInternal( Publisher publisher, CoreSubscriber subscriber) { if (ContextPropagationSupport.shouldWrapPublisher(publisher)) { if (publisher instanceof Fuseable) { @@ -1014,42 +1022,31 @@ static CoreSubscriber restoreContextOnSubscriberIfNecessary( return subscriber; } - static CoreSubscriber restoreContextOnSubscriber( + static CoreSubscriber restoreContextOnSubscriberIfAutoCPEnabled( Publisher publisher, CoreSubscriber subscriber) { if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - if (publisher instanceof Fuseable) { - return new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( - subscriber, subscriber.currentContext()); - } else { - return new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( - subscriber, - subscriber.currentContext()); - } + return restoreContextOnSubscriber(publisher, subscriber); } return subscriber; } - static CoreSubscriber[] restoreContextOnSubscribersIfNecessary( + static CoreSubscriber restoreContextOnSubscriber( + Publisher publisher, CoreSubscriber subscriber) { + if (publisher instanceof Fuseable) { + return new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, subscriber.currentContext()); + } else { + return new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( + subscriber, + subscriber.currentContext()); + } + } + + static CoreSubscriber[] restoreContextOnSubscribers( Publisher publisher, CoreSubscriber[] subscribers) { - CoreSubscriber[] actualSubscribers = subscribers; - if (ContextPropagationSupport.shouldWrapPublisher(publisher)) { - actualSubscribers = new CoreSubscriber[subscribers.length]; - for (int i = 0; i < subscribers.length; i++) { - CoreSubscriber subscriber; - if (publisher instanceof Fuseable) { - subscriber = - new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( - subscribers[i], - subscribers[i].currentContext()); - } - else { - subscriber = - new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( - subscribers[i], - subscribers[i].currentContext()); - } - actualSubscribers[i] = subscriber; - } + CoreSubscriber[] actualSubscribers = new CoreSubscriber[subscribers.length]; + for (int i = 0; i < subscribers.length; i++) { + actualSubscribers[i] = restoreContextOnSubscriber(publisher, subscribers[i]); } return actualSubscribers; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java index 4be635f2f4..ecfa0eaa83 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java @@ -48,7 +48,6 @@ public void subscribe(CoreSubscriber[] subscribers) { } int n = subscribers.length; - for (int i = 0; i < n; i++) { Operators.toFluxOrMono(sources[i]).subscribe(subscribers[i]); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java index 691efa80c6..725bb4ddc1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java @@ -77,8 +77,7 @@ public abstract class ParallelFlux implements CorePublisher { public static ParallelFlux from(ParallelFlux source) { - Scannable s = Scannable.from(source); - if (!s.scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false)) { + if (ContextPropagationSupport.shouldWrapPublisher(source)) { return new ParallelFluxRestoringThreadLocals<>(source); } return source; diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java index 5ca1c6bca7..6f0d61ca8f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java @@ -36,7 +36,7 @@ public int parallelism() { @Override public void subscribe(CoreSubscriber[] subscribers) { CoreSubscriber[] actualSubscribers = - Operators.restoreContextOnSubscribersIfNecessary(source, subscribers); + Operators.restoreContextOnSubscribers(source, subscribers); source.subscribe(actualSubscribers); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java index d342b25361..05d29cf51d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java @@ -198,7 +198,7 @@ void remove(Inner ps) { @Override public void subscribe(final CoreSubscriber actual) { CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); Inner as = new VoidInner<>(wrapped, this); wrapped.onSubscribe(as); final int addedState = add(as); diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java index cda0e5d724..a8d0aa2cdc 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java @@ -193,7 +193,7 @@ public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe(null) is forbidden"); CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); DirectInner p = new DirectInner<>(wrapped, this); wrapped.onSubscribe(p); diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java index 67959a553f..dea223d24a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java @@ -167,7 +167,7 @@ public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); EmitterInner inner = new EmitterInner<>(wrapped, this); wrapped.onSubscribe(inner); diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java index 29b82a2dab..6ba04bbf04 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyReplayProcessor.java @@ -296,7 +296,7 @@ static SinkManyReplayProcessor createSizeAndTimeout(int size, public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); FluxReplay.ReplaySubscription rs = new ReplayInner<>(wrapped, this); wrapped.onSubscribe(rs); diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java index 42c6710867..eb1d49950f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java @@ -410,7 +410,7 @@ public Context currentContext() { public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { this.hasDownstream = true; diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java index fc8e555788..d065712f2d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java @@ -77,7 +77,7 @@ public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); if (!STATE.compareAndSet(this, State.INITIAL, State.SUBSCRIBED)) { Operators.reportThrowInSubscribe(wrapped, new IllegalStateException( diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java index bc1c48905f..af9d1594ba 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java @@ -17,7 +17,6 @@ package reactor.core.publisher; import java.time.Duration; -import java.util.Objects; import reactor.core.CoreSubscriber; import reactor.core.publisher.Sinks.EmitResult; @@ -83,7 +82,7 @@ public Object scanUnsafe(Attr key) { @Override public void subscribe(final CoreSubscriber actual) { CoreSubscriber wrapped = - Operators.restoreContextOnSubscriber(this, actual); + Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); NextInner as = new NextInner<>(wrapped, this); wrapped.onSubscribe(as); final int addState = add(as); From c7243fb784d7c0c3c7c2b548a810e77596d68f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Mon, 25 Sep 2023 14:32:53 +0200 Subject: [PATCH 16/26] Replaced unnecessary completion stage handling with Subscriber wrapping --- .../core/publisher/MonoCompletionStage.java | 115 +----------------- 1 file changed, 3 insertions(+), 112 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java index 99d0f26856..e0fccfd688 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java @@ -52,14 +52,9 @@ final class MonoCompletionStage extends Mono @Override public void subscribe(CoreSubscriber actual) { - if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { - actual.onSubscribe( - new MonoCompletionStageRestoringThreadLocalsSubscription<>( - actual, future, suppressCancellation)); - } else { - actual.onSubscribe(new MonoCompletionStageSubscription<>( - actual, future, suppressCancellation)); - } + CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(this, actual); + wrapped.onSubscribe(new MonoCompletionStageSubscription<>( + wrapped, future, suppressCancellation)); } @Override @@ -166,108 +161,4 @@ public void cancel() { } } } - - static class MonoCompletionStageRestoringThreadLocalsSubscription - implements InnerProducer, BiFunction { - - final CoreSubscriber actual; - final CompletionStage future; - final boolean suppressCancellation; - - volatile int requestedOnce; - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater REQUESTED_ONCE = - AtomicIntegerFieldUpdater.newUpdater(MonoCompletionStageRestoringThreadLocalsSubscription.class, "requestedOnce"); - - volatile boolean cancelled; - - MonoCompletionStageRestoringThreadLocalsSubscription( - CoreSubscriber actual, - CompletionStage future, - boolean suppressCancellation) { - this.actual = actual; - this.future = future; - this.suppressCancellation = suppressCancellation; - } - - @Override - public CoreSubscriber actual() { - return this.actual; - } - - @Override - public Void apply(@Nullable T value, @Nullable Throwable e) { - final CoreSubscriber actual = this.actual; - - try (ContextSnapshot.Scope ignored = - ContextPropagation.setThreadLocals(actual.currentContext())) { - if (this.cancelled) { - //nobody is interested in the Mono anymore, don't risk dropping errors - final Context ctx = actual.currentContext(); - if (e == null || e instanceof CancellationException) { - //we discard any potential value and ignore Future cancellations - Operators.onDiscard(value, ctx); - } - else { - //we make sure we keep _some_ track of a Future failure AFTER the Mono cancellation - Operators.onErrorDropped(e, ctx); - //and we discard any potential value just in case both e and v are not null - Operators.onDiscard(value, ctx); - } - - return null; - } - - try { - if (e instanceof CompletionException) { - actual.onError(e.getCause()); - } - else if (e != null) { - actual.onError(e); - } - else if (value != null) { - actual.onNext(value); - actual.onComplete(); - } - else { - actual.onComplete(); - } - } - catch (Throwable e1) { - Operators.onErrorDropped(e1, actual.currentContext()); - throw Exceptions.bubble(e1); - } - return null; - } - } - - @Override - public void request(long n) { - if (this.cancelled) { - return; - } - - if (this.requestedOnce == 1 || !REQUESTED_ONCE.compareAndSet(this, 0 , 1)) { - return; - } - - future.handle(this); - } - - @Override - public void cancel() { - this.cancelled = true; - - final CompletionStage future = this.future; - if (!suppressCancellation && future instanceof Future) { - try { - //noinspection unchecked - ((Future) future).cancel(true); - } - catch (Throwable t) { - Operators.onErrorDropped(t, this.actual.currentContext()); - } - } - } - } } From 479f0c0e5db63c3052210e23835b06a950487ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 26 Sep 2023 09:10:23 +0200 Subject: [PATCH 17/26] Cleanup --- .../reactor/core/publisher/MonoCompletionStage.java | 2 +- .../main/java/reactor/core/publisher/Operators.java | 13 ++----------- .../test/java/reactor/core/publisher/HooksTest.java | 2 +- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java index e0fccfd688..35b69f17f8 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java @@ -52,7 +52,7 @@ final class MonoCompletionStage extends Mono @Override public void subscribe(CoreSubscriber actual) { - CoreSubscriber wrapped = Operators.restoreContextOnSubscriber(this, actual); + CoreSubscriber wrapped = Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual); wrapped.onSubscribe(new MonoCompletionStageSubscription<>( wrapped, future, suppressCancellation)); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 7c450a353f..3c16bb8cd4 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -1009,15 +1009,7 @@ public static void toFluxOrMono(Publisher[] sources) { static CoreSubscriber restoreContextOnSubscriberIfPublisherNonInternal( Publisher publisher, CoreSubscriber subscriber) { if (ContextPropagationSupport.shouldWrapPublisher(publisher)) { - if (publisher instanceof Fuseable) { - subscriber = - new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>(subscriber, - subscriber.currentContext()); - } else { - subscriber = new FluxContextWriteRestoringThreadLocals.ContextWriteRestoringThreadLocalsSubscriber<>( - subscriber, - subscriber.currentContext()); - } + return restoreContextOnSubscriber(publisher, subscriber); } return subscriber; } @@ -1030,8 +1022,7 @@ static CoreSubscriber restoreContextOnSubscriberIfAutoCPEnabled( return subscriber; } - static CoreSubscriber restoreContextOnSubscriber( - Publisher publisher, CoreSubscriber subscriber) { + static CoreSubscriber restoreContextOnSubscriber(Publisher publisher, CoreSubscriber subscriber) { if (publisher instanceof Fuseable) { return new FluxContextWriteRestoringThreadLocals.FuseableContextWriteRestoringThreadLocalsSubscriber<>( subscriber, subscriber.currentContext()); diff --git a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java index e0ec44eee5..1c02879022 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java @@ -952,7 +952,7 @@ public void eachOperatorLiftsParallelFlux() { StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) .log() .log()) - .expectNext(501, 501) //6x lifts => just,Flux.from,ParallelFlux.from,log,log, back to sequential (shared) + .expectNext(501, 501) //5x lifts => just,ParallelFlux.from,log,log,back to sequential (shared) .verifyComplete(); assertThat(liftCounter).hasValue(9); //9x total lifts: 2xjust,2xparallelFrom,4xlog,sequential From 6f61641bf91a9e36399d4f211914b7eed00d7031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 26 Sep 2023 13:41:46 +0200 Subject: [PATCH 18/26] Further cleanup --- .../java/reactor/core/publisher/ContextPropagation.java | 8 ++++++++ .../reactor/core/publisher/ContextPropagationSupport.java | 8 -------- .../src/main/java/reactor/core/publisher/Flux.java | 5 ++--- .../java/reactor/core/publisher/InternalMonoOperator.java | 4 ++++ .../src/main/java/reactor/core/publisher/Mono.java | 6 +++--- .../src/main/java/reactor/core/publisher/Operators.java | 2 +- .../core/publisher/ParallelFluxRestoringThreadLocals.java | 4 ++-- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java index af3908133f..ec630b3ed3 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagation.java @@ -60,6 +60,14 @@ final class ContextPropagation { } } + static Flux fluxRestoreThreadLocals(Flux flux) { + return new FluxContextWriteRestoringThreadLocals<>(flux, Function.identity()); + } + + static Mono monoRestoreThreadLocals(Mono mono) { + return new MonoContextWriteRestoringThreadLocals<>(mono, Function.identity()); + } + static void configureContextSnapshotFactory(boolean clearMissing) { if (ContextPropagationSupport.isContextPropagation103OnClasspath) { globalContextSnapshotFactory = ContextSnapshotFactory.builder() diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java index 528ebe54cb..200c8d95fd 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java @@ -80,12 +80,4 @@ static boolean shouldWrapPublisher(Publisher publisher) { static boolean shouldRestoreThreadLocalsInSomeOperators() { return isContextPropagationOnClasspath && !propagateContextToThreadLocals; } - - static Flux fluxRestoreThreadLocals(Flux flux) { - return new FluxContextWriteRestoringThreadLocals<>(flux, Function.identity()); - } - - static Mono monoRestoreThreadLocals(Mono mono) { - return new MonoContextWriteRestoringThreadLocals<>(mono, Function.identity()); - } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index f0bedcfd8f..b9a3213cb2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -9892,7 +9892,6 @@ public final Flux transformDeferredContextual(BiFunction, return deferContextual(ctxView -> { if (Hooks.DETECT_CONTEXT_LOSS) { ContextTrackingFunctionWrapper wrapper = new ContextTrackingFunctionWrapper<>( - // TODO: why no assembly hooks are applied? publisher -> transformer.apply(wrap(publisher), ctxView), transformer.toString() ); @@ -11075,7 +11074,7 @@ static Flux wrap(Publisher source) { if (!shouldWrap) { return (Flux) source; } - return ContextPropagationSupport.fluxRestoreThreadLocals((Flux) source); + return ContextPropagation.fluxRestoreThreadLocals((Flux) source); } //for scalars we'll instantiate the operators directly to avoid onAssembly @@ -11106,7 +11105,7 @@ static Flux wrap(Publisher source) { target = new FluxSource<>(source); } if (shouldWrap) { - return ContextPropagationSupport.fluxRestoreThreadLocals(target); + return ContextPropagation.fluxRestoreThreadLocals(target); } return target; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java index a563b36426..26d88396be 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java @@ -62,6 +62,10 @@ public final void subscribe(CoreSubscriber subscriber) { while (true) { subscriber = operator.subscribeOrReturn(subscriber); if (subscriber == null) { + // if internally subscribed, it means the optimized operator is + // already within the internals and subscribing up the chain will + // at some point need to consider the source and wrap it + // null means "I will subscribe myself", returning... return; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index 7543534928..8faa8a0c41 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -5354,7 +5354,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { if (!shouldWrap) { return (Mono) source; } - return ContextPropagationSupport.monoRestoreThreadLocals((Mono) source); + return ContextPropagation.monoRestoreThreadLocals((Mono) source); } if (source instanceof FluxSourceMono || source instanceof FluxSourceMonoFuseable) { @@ -5365,7 +5365,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { if (!shouldWrapExtracted) { return extracted; } - return ContextPropagationSupport.monoRestoreThreadLocals(extracted); + return ContextPropagation.monoRestoreThreadLocals(extracted); } if (source instanceof Flux && source instanceof Callable) { @@ -5394,7 +5394,7 @@ static Mono wrap(Publisher source, boolean enforceMonoContract) { } if (shouldWrap) { - return ContextPropagationSupport.monoRestoreThreadLocals(target); + return ContextPropagation.monoRestoreThreadLocals(target); } return target; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Operators.java b/reactor-core/src/main/java/reactor/core/publisher/Operators.java index 3c16bb8cd4..3d60285c48 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Operators.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Operators.java @@ -2683,7 +2683,7 @@ final static class LiftFunction final String name; // TODO: this leaks to the users of LiftFunction, encapsulation is broken - // TODO: consider: liftFunction.lifter.apply could go through encapsulation + // consider: liftFunction.lifter.apply could go through encapsulation // like: liftFunction.applyLifter() where what lifter.apply returns is wrapped // unconditionally; otherwise -> all lift* operators need to be considered as // NOT INTERNAL_PRODUCER sources diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java index 6f0d61ca8f..1c9cb3f6c1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java @@ -19,12 +19,12 @@ import reactor.core.CoreSubscriber; import reactor.core.Scannable; -public class ParallelFluxRestoringThreadLocals extends ParallelFlux implements +class ParallelFluxRestoringThreadLocals extends ParallelFlux implements Scannable { private final ParallelFlux source; - public ParallelFluxRestoringThreadLocals(ParallelFlux source) { + ParallelFluxRestoringThreadLocals(ParallelFlux source) { this.source = source; } From 0ef68d74c5946a368f64082d841dee7ccf345c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 26 Sep 2023 16:16:03 +0200 Subject: [PATCH 19/26] Removed comment --- .../AutomaticContextPropagationTest.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 4617695c2f..04412a659a 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -624,21 +624,6 @@ void fluxCreate() { assertThreadLocalPresentInOnNext(flux); } - /* - 1. raw RS Publisher: - a) subscribe(Subscriber) -> we need to wrap when we call publisher.subscribe() - 2. Flux/Mono - a) subscribe(CoreSubscriber) -> we need to wrap if publisher is not INTERNAL_PRODUCER - b) subscribe(Subscriber) -> no need to wrap, because of the fallback in Flux#subscribe(Subscriber) - BUT, if implementation overrides subscribe (Subscriber) that fallback is GONE ? NO -> method is final - - Problem with Subscriber wrapping: - When we have raw Publisher ref, we wrap the Subscriber, then call - subscribe(Subscriber), we'd wrap Subscriber again, as the Publisher is still - not INTERNAL_PRODUCER. - We could do instanceof, but would need to go through all instances. - We could have every Subscriber report Scannable RESTORING_THREAD_LOCALS instead. - */ @Test void fluxCreateDirect() throws InterruptedException { AtomicReference value = new AtomicReference<>(); From 9d6afb44975627f6232114c6deb9d87f9ef2f6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Thu, 28 Sep 2023 09:29:55 +0200 Subject: [PATCH 20/26] Internal Attr for INTERNAL_PRODUCER --- .../src/main/java/reactor/core/Scannable.java | 2 -- .../publisher/ContextPropagationSupport.java | 2 +- ...FluxContextWriteRestoringThreadLocals.java | 2 +- .../core/publisher/FluxFromMonoOperator.java | 2 +- .../reactor/core/publisher/FluxOperator.java | 2 +- .../reactor/core/publisher/FluxPublish.java | 2 +- .../core/publisher/FluxRepeatWhen.java | 2 +- .../reactor/core/publisher/FluxReplay.java | 2 +- .../reactor/core/publisher/FluxRetryWhen.java | 2 +- .../publisher/FluxSubscribeOnCallable.java | 2 +- .../core/publisher/FluxSubscribeOnValue.java | 2 +- .../FluxTapRestoringThreadLocals.java | 4 +-- .../reactor/core/publisher/GroupedLift.java | 2 +- .../core/publisher/GroupedLiftFuseable.java | 2 +- .../reactor/core/publisher/InnerProducer.java | 2 +- .../InternalConnectableFluxOperator.java | 2 +- .../core/publisher/InternalFluxOperator.java | 2 +- .../core/publisher/InternalMonoOperator.java | 2 +- .../core/publisher/InternalProducerAttr.java | 28 +++++++++++++++++++ .../core/publisher/MonoCompletionStage.java | 2 +- ...MonoContextWriteRestoringThreadLocals.java | 2 +- .../reactor/core/publisher/MonoCreate.java | 1 + .../core/publisher/MonoCurrentContext.java | 2 +- .../core/publisher/MonoDelayUntil.java | 2 +- .../core/publisher/MonoFromFluxOperator.java | 2 +- .../core/publisher/MonoFromPublisher.java | 2 +- .../core/publisher/MonoIgnorePublisher.java | 2 +- .../core/publisher/MonoIgnoreThen.java | 2 +- .../reactor/core/publisher/MonoOperator.java | 2 +- .../publisher/MonoSubscribeOnCallable.java | 2 +- .../core/publisher/MonoSubscribeOnValue.java | 2 +- .../MonoTapRestoringThreadLocals.java | 2 +- .../core/publisher/ParallelCollect.java | 2 +- .../core/publisher/ParallelConcatMap.java | 2 +- .../core/publisher/ParallelDoOnEach.java | 2 +- .../core/publisher/ParallelFilter.java | 2 +- .../core/publisher/ParallelFlatMap.java | 2 +- .../core/publisher/ParallelFluxHide.java | 2 +- .../core/publisher/ParallelFluxName.java | 2 +- .../publisher/ParallelFluxOnAssembly.java | 2 +- .../ParallelFluxRestoringThreadLocals.java | 2 +- .../reactor/core/publisher/ParallelGroup.java | 2 +- .../reactor/core/publisher/ParallelLift.java | 2 +- .../core/publisher/ParallelLiftFuseable.java | 2 +- .../reactor/core/publisher/ParallelLog.java | 2 +- .../reactor/core/publisher/ParallelMap.java | 2 +- .../core/publisher/ParallelMergeOrdered.java | 2 +- .../core/publisher/ParallelMergeReduce.java | 2 +- .../publisher/ParallelMergeSequential.java | 2 +- .../core/publisher/ParallelMergeSort.java | 2 +- .../reactor/core/publisher/ParallelPeek.java | 2 +- .../core/publisher/ParallelReduceSeed.java | 2 +- .../reactor/core/publisher/ParallelRunOn.java | 2 +- .../core/publisher/ParallelSource.java | 2 +- .../reactor/core/publisher/ParallelThen.java | 2 +- .../core/publisher/SinkEmptyMulticast.java | 2 +- .../core/publisher/SinkManyBestEffort.java | 2 +- .../publisher/SinkManyEmitterProcessor.java | 2 +- .../core/publisher/SinkManyUnicast.java | 2 +- .../SinkManyUnicastNoBackpressure.java | 2 +- .../core/publisher/SinkOneMulticast.java | 2 +- .../core/publisher/SourceProducer.java | 2 +- 62 files changed, 89 insertions(+), 62 deletions(-) create mode 100644 reactor-core/src/main/java/reactor/core/publisher/InternalProducerAttr.java diff --git a/reactor-core/src/main/java/reactor/core/Scannable.java b/reactor-core/src/main/java/reactor/core/Scannable.java index 3c03246567..828c0aba03 100644 --- a/reactor-core/src/main/java/reactor/core/Scannable.java +++ b/reactor-core/src/main/java/reactor/core/Scannable.java @@ -228,8 +228,6 @@ class Attr { */ public static final Attr LIFTER = new Attr<>(null); - public static final Attr INTERNAL_PRODUCER = new Attr<>(false); - /** * An {@link Enum} enumerating the different styles an operator can run : their {@link #ordinal()} reflects the level of confidence * in their running mode diff --git a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java index 200c8d95fd..ec9004f77d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ContextPropagationSupport.java @@ -74,7 +74,7 @@ static boolean shouldPropagateContextToThreadLocals() { static boolean shouldWrapPublisher(Publisher publisher) { return shouldPropagateContextToThreadLocals() && - !Scannable.from(publisher).scanOrDefault(Scannable.Attr.INTERNAL_PRODUCER, false); + !Scannable.from(publisher).scanOrDefault(InternalProducerAttr.INSTANCE, false); } static boolean shouldRestoreThreadLocalsInSomeOperators() { diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java index f01dc52d3b..c425db10c8 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java @@ -50,7 +50,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java index 37e4e8cb80..b7c8b1b3f7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxFromMonoOperator.java @@ -63,7 +63,7 @@ protected FluxFromMonoOperator(Mono source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java index e93e3e4f31..b0d416ce9f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxOperator.java @@ -47,7 +47,7 @@ protected FluxOperator(Flux source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return false; // public class! + if (key == InternalProducerAttr.INSTANCE) return false; // public class! return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java b/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java index 2eaa0176f3..68814dc316 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxPublish.java @@ -159,7 +159,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java index 74e1359f86..30726f9ed7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRepeatWhen.java @@ -225,7 +225,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return main.otherArbiter; if (key == Attr.ACTUAL) return main; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java b/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java index a359a714fd..d264e7f1cb 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxReplay.java @@ -1214,7 +1214,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java b/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java index 8fd92d072d..e953e8ad03 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxRetryWhen.java @@ -258,7 +258,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return main.otherArbiter; if (key == Attr.ACTUAL) return main; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java index 0f3584e62c..57fde807fc 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnCallable.java @@ -67,7 +67,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java index b6a1fd8a32..989249d746 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxSubscribeOnValue.java @@ -73,7 +73,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java index 1c2fb9c506..48d2776125 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxTapRestoringThreadLocals.java @@ -88,7 +88,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return super.scanUnsafe(key); } @@ -130,7 +130,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return s; if (key == Attr.TERMINATED) return done; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return InnerOperator.super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java b/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java index 1201dcf50b..a74c73cd50 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java +++ b/reactor-core/src/main/java/reactor/core/publisher/GroupedLift.java @@ -62,7 +62,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } - if (key == Attr.INTERNAL_PRODUCER) { + if (key == InternalProducerAttr.INSTANCE) { return true; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java index 01c04df879..3b1cbbfa0e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/GroupedLiftFuseable.java @@ -64,7 +64,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.LIFTER) { return liftFunction.name; } - if (key == Attr.INTERNAL_PRODUCER) { + if (key == InternalProducerAttr.INSTANCE) { return true; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java b/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java index 590c455c08..1930bcdff7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InnerProducer.java @@ -43,7 +43,7 @@ default Object scanUnsafe(Attr key){ if (key == Attr.ACTUAL) { return actual(); } - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java index d0d3926eff..1e8f412c63 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalConnectableFluxOperator.java @@ -90,7 +90,7 @@ public final CorePublisher source() { public Object scanUnsafe(Scannable.Attr key) { if (key == Scannable.Attr.PREFETCH) return getPrefetch(); if (key == Scannable.Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java index 96411e1296..07f3b151c0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalFluxOperator.java @@ -95,7 +95,7 @@ public final CorePublisher source() { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java index 26d88396be..6c25c2bb3c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalMonoOperator.java @@ -50,7 +50,7 @@ protected InternalMonoOperator(Mono source) { @Override public Object scanUnsafe(Attr key) { - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/InternalProducerAttr.java b/reactor-core/src/main/java/reactor/core/publisher/InternalProducerAttr.java new file mode 100644 index 0000000000..910b944289 --- /dev/null +++ b/reactor-core/src/main/java/reactor/core/publisher/InternalProducerAttr.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import reactor.core.Scannable; + +class InternalProducerAttr extends Scannable.Attr { + + private InternalProducerAttr(Boolean defaultValue) { + super(defaultValue); + } + + static final InternalProducerAttr INSTANCE = new InternalProducerAttr(true); +} diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java index 35b69f17f8..04bcec2f1e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCompletionStage.java @@ -60,7 +60,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java index 695f411902..eaa1b56fcc 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java @@ -48,7 +48,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java index f4f047502f..1f7fbb56c0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCreate.java @@ -68,6 +68,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == InternalProducerAttr.INSTANCE) return true; return SourceProducer.super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java b/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java index 55c8409d15..118e9347fc 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoCurrentContext.java @@ -39,7 +39,7 @@ public void subscribe(CoreSubscriber actual) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java index 77489553eb..26ca6c1aad 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java @@ -123,7 +123,7 @@ public final CorePublisher source() { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; //no particular key to be represented, still useful in hooks } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java index 17ca91f2f9..f5db3c1d65 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromFluxOperator.java @@ -62,7 +62,7 @@ protected MonoFromFluxOperator(Flux source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return Integer.MAX_VALUE; if (key == Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java index 3e172d6c53..61ab8be8d1 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoFromPublisher.java @@ -93,7 +93,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Scannable.Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } - if (key == Attr.INTERNAL_PRODUCER) { + if (key == InternalProducerAttr.INSTANCE) { return true; } return null; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java index 444acc051a..9488a7e0f7 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnorePublisher.java @@ -86,7 +86,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } - if (key == Attr.INTERNAL_PRODUCER) { + if (key == InternalProducerAttr.INSTANCE) { return true; } return null; diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java index ac33d5dd59..4627a406c2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoIgnoreThen.java @@ -71,7 +71,7 @@ MonoIgnoreThen shift(Mono newLast) { @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java index d2bc0aa303..3fc6d7158c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoOperator.java @@ -47,7 +47,7 @@ protected MonoOperator(Mono source) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return Integer.MAX_VALUE; if (key == Attr.PARENT) return source; - if (key == Attr.INTERNAL_PRODUCER) return false; // public class! + if (key == InternalProducerAttr.INSTANCE) return false; // public class! return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java index d984792bfb..ea53f9a505 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnCallable.java @@ -62,7 +62,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Scannable.Attr key) { if (key == Scannable.Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java index ab9b9875c2..c63c18631e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoSubscribeOnValue.java @@ -67,7 +67,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java index e84f17b489..84d8715f97 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoTapRestoringThreadLocals.java @@ -83,7 +83,7 @@ public void subscribe(CoreSubscriber actual) { public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return -1; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return super.scanUnsafe(key); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java index b5563c68b0..a903d52d68 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelCollect.java @@ -54,7 +54,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java index d17ee3f65f..8db76a6d6d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelConcatMap.java @@ -64,7 +64,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.DELAY_ERROR) return errorMode != ErrorMode.IMMEDIATE; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java index 32c0aa5d64..eff7dabc37 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelDoOnEach.java @@ -98,7 +98,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java index de053f3a43..376bb583b0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFilter.java @@ -45,7 +45,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java index 8f94fe89be..314e812d0e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlatMap.java @@ -69,7 +69,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.DELAY_ERROR) return delayError; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java index 5a7b50ef27..6fafe6718f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxHide.java @@ -51,7 +51,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java index 0b12fce5cd..9bbcfd526a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxName.java @@ -113,7 +113,7 @@ public Object scanUnsafe(Attr key) { return SYNC; } - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java index 1031ac67a2..a530efb857 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxOnAssembly.java @@ -97,7 +97,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java index 1c9cb3f6c1..946cd0cae2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFluxRestoringThreadLocals.java @@ -46,7 +46,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java index 0edc1189ed..5cda4f54d5 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelGroup.java @@ -65,7 +65,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java index 4a0ba207ff..83fa0b940b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLift.java @@ -63,7 +63,7 @@ public Object scanUnsafe(Attr key) { return liftFunction.name; } // We don't control what the lifter does, so we play it safe. - if (key == Attr.INTERNAL_PRODUCER) return false; + if (key == InternalProducerAttr.INSTANCE) return false; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java index f9d8d8ff7e..2d3ae6c28c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLiftFuseable.java @@ -66,7 +66,7 @@ public Object scanUnsafe(Attr key) { return liftFunction.name; } // We don't control what the lifter does, so we play it safe. - if (key == Attr.INTERNAL_PRODUCER) return false; + if (key == InternalProducerAttr.INSTANCE) return false; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java index ae35c034c2..bda6d4b54c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelLog.java @@ -81,7 +81,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java index 58d8119c9c..7d978cc824 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMap.java @@ -46,7 +46,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java index 2464ab37da..84a4ed206a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeOrdered.java @@ -57,7 +57,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return prefetch; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java index 8064691af9..0fca4881ed 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeReduce.java @@ -51,7 +51,7 @@ final class ParallelMergeReduce extends Mono implements Scannable, Fuseabl public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java index 7dc4a4053d..9a1b01cf81 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSequential.java @@ -57,7 +57,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java index 61424fa08b..26edb3b366 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelMergeSort.java @@ -71,7 +71,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java index 2ba33ef1d2..65b4ab4899 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelPeek.java @@ -153,7 +153,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java index d9e36c7b55..4bb7c04264 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelReduceSeed.java @@ -55,7 +55,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java index 30a27f67a2..c006024dfe 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelRunOn.java @@ -57,7 +57,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java index 2755591f95..5a5016165f 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelSource.java @@ -76,7 +76,7 @@ public Object scanUnsafe(Scannable.Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java index 68d39ec7ca..dd8fce93bf 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelThen.java @@ -42,7 +42,7 @@ final class ParallelThen extends Mono implements Scannable, Fuseable { public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java index 05d29cf51d..c50da3773c 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkEmptyMulticast.java @@ -124,7 +124,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return isTerminated(subscribers); if (key == Attr.ERROR) return subscribers == TERMINATED_ERROR ? error : null; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java index a8d0aa2cdc..aaf439db5d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyBestEffort.java @@ -77,7 +77,7 @@ public Stream inners() { public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return subscribers == TERMINATED; if (key == Attr.ERROR) return error; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java index dea223d24a..96762282a0 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyEmitterProcessor.java @@ -377,7 +377,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return isTerminated(); if (key == Attr.ERROR) return getError(); if (key == Attr.CAPACITY) return getPrefetch(); - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java index eb1d49950f..9396b1f10b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicast.java @@ -183,7 +183,7 @@ public Object scanUnsafe(Attr key) { if (Attr.CANCELLED == key) return cancelled; if (Attr.TERMINATED == key) return done; if (Attr.ERROR == key) return error; - if (Attr.INTERNAL_PRODUCER == key) return true; + if (InternalProducerAttr.INSTANCE == key) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java index d065712f2d..c1fd5d01d2 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkManyUnicastNoBackpressure.java @@ -194,7 +194,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.ACTUAL) return actual; if (key == Attr.TERMINATED) return state == State.TERMINATED; if (key == Attr.CANCELLED) return state == State.CANCELLED; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java index af9d1594ba..7c5709bf51 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SinkOneMulticast.java @@ -74,7 +74,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return isTerminated(subscribers); if (key == Attr.ERROR) return subscribers == TERMINATED_ERROR ? error : null; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } diff --git a/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java b/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java index 7a6e6e483d..b1f2498092 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java +++ b/reactor-core/src/main/java/reactor/core/publisher/SourceProducer.java @@ -38,7 +38,7 @@ interface SourceProducer extends Scannable, Publisher { default Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return null; if (key == Attr.ACTUAL) return null; - if (key == Attr.INTERNAL_PRODUCER) return true; + if (key == InternalProducerAttr.INSTANCE) return true; return null; } From 95e776d77af0f0bf00d646af942e65f46322bdc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Thu, 28 Sep 2023 14:55:52 +0200 Subject: [PATCH 21/26] Refactoring tests --- .../core/publisher/MonoDelayUntil.java | 2 +- .../AutomaticContextPropagationTest.java | 1081 +++++++---------- 2 files changed, 450 insertions(+), 633 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java index 26ca6c1aad..51eb2610e6 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java +++ b/reactor-core/src/main/java/reactor/core/publisher/MonoDelayUntil.java @@ -94,7 +94,7 @@ MonoDelayUntil copyWithNewTriggerGenerator(boolean delayError, @Override public void subscribe(CoreSubscriber actual) { try { - source.subscribe(subscribeOrReturn(actual)); + Operators.toFluxOrMono(source).subscribe(subscribeOrReturn(actual)); } catch (Throwable e) { Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index 04412a659a..d31aa38756 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -18,7 +18,6 @@ import java.io.File; import java.time.Duration; -import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -35,6 +34,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -43,6 +43,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; @@ -61,6 +62,7 @@ import reactor.util.retry.Retry; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; public class AutomaticContextPropagationTest { @@ -234,7 +236,6 @@ void contextCapturePropagatedAutomaticallyToAllSignals() throws InterruptedExcep // disabling prefetching to observe cancellation .publishOn(Schedulers.parallel(), 1) .doOnNext(i -> { - System.out.println(REF.get()); secondNextTlValue.set(REF.get()); itemDelivered.countDown(); }) @@ -316,7 +317,7 @@ class NonReactorFluxOrMono { @BeforeEach void enableAutomaticContextPropagation() { - executorService = Executors.newSingleThreadExecutor(); + executorService = Executors.newFixedThreadPool(3); } @AfterEach @@ -326,148 +327,224 @@ void cleanupThreadLocals() { // Scaffold methods - void assertThreadLocalPresentInOnNext(Mono chain) { - AtomicReference value = new AtomicReference<>(); + private ThreadSwitchingFlux threadSwitchingFlux() { + return new ThreadSwitchingFlux<>("Hello", executorService); + } - chain.doOnNext(item -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .block(); + private ThreadSwitchingMono threadSwitchingMono() { + return new ThreadSwitchingMono<>("Hello", executorService); + } - assertThat(value.get()).isEqualTo("present"); + void assertThreadLocalsPresentInFlux(Supplier> chainSupplier) { + assertThreadLocalsPresentInFlux(chainSupplier, false); } - void assertThreadLocalPresentInOnSuccess(Mono chain) { - AtomicReference value = new AtomicReference<>(); + void assertThreadLocalsPresentInFlux(Supplier> chainSupplier, + boolean skipCoreSubscriber) { + assertThreadLocalsPresent(chainSupplier.get()); + assertThatNoException().isThrownBy(() -> + assertThatThreadLocalsPresentDirectRawSubscribe(chainSupplier.get())); + if (!skipCoreSubscriber) { + assertThatNoException().isThrownBy(() -> + assertThatThreadLocalsPresentDirectCoreSubscribe(chainSupplier.get())); + } + } + + void assertThreadLocalsPresentInMono(Supplier> chainSupplier) { + assertThreadLocalsPresentInMono(chainSupplier, false); + } - chain.doOnSuccess(item -> value.set(REF.get())) + void assertThreadLocalsPresentInMono(Supplier> chainSupplier, + boolean skipCoreSubscriber) { + assertThreadLocalsPresent(chainSupplier.get()); + assertThatNoException().isThrownBy(() -> + assertThatThreadLocalsPresentDirectRawSubscribe(chainSupplier.get())); + if (!skipCoreSubscriber) { + assertThatNoException().isThrownBy(() -> + assertThatThreadLocalsPresentDirectCoreSubscribe(chainSupplier.get())); + } + } + + void assertThreadLocalsPresent(Flux chain) { + AtomicReference tlInOnNext = new AtomicReference<>(); + AtomicReference tlInOnComplete = new AtomicReference<>(); + AtomicReference tlInOnError = new AtomicReference<>(); + + AtomicBoolean hadNext = new AtomicBoolean(false); + AtomicBoolean hadError = new AtomicBoolean(false); + + chain.doOnEach(signal -> { + if (signal.isOnNext()) { + tlInOnNext.set(REF.get()); + hadNext.set(true); + } else if (signal.isOnError()) { + tlInOnError.set(REF.get()); + hadError.set(true); + } else if (signal.isOnComplete()) { + tlInOnComplete.set(REF.get()); + } + }) .contextWrite(Context.of(KEY, "present")) - .block(); + .blockLast(); - assertThat(value.get()).isEqualTo("present"); + if (hadNext.get()) { + assertThat(tlInOnNext.get()).isEqualTo("present"); + } + if (hadError.get()) { + assertThat(tlInOnError.get()).isEqualTo("present"); + } else { + assertThat(tlInOnComplete.get()).isEqualTo("present"); + } } - void assertThreadLocalPresentInOnError(Mono chain) { - AtomicReference value = new AtomicReference<>(); + void assertThreadLocalsPresent(Mono chain) { + AtomicReference tlInOnNext = new AtomicReference<>(); + AtomicReference tlInOnComplete = new AtomicReference<>(); + AtomicReference tlInOnError = new AtomicReference<>(); - chain.doOnError(item -> value.set(REF.get())) + AtomicBoolean hadNext = new AtomicBoolean(false); + AtomicBoolean hadError = new AtomicBoolean(false); + + chain.doOnEach(signal -> { + if (signal.isOnNext()) { + tlInOnNext.set(REF.get()); + hadNext.set(true); + } else if (signal.isOnError()) { + tlInOnError.set(REF.get()); + hadError.set(true); + } else if (signal.isOnComplete()) { + tlInOnComplete.set(REF.get()); + } + }) .contextWrite(Context.of(KEY, "present")) - .onErrorComplete() .block(); - assertThat(value.get()).isEqualTo("present"); + if (hadNext.get()) { + assertThat(tlInOnNext.get()).isEqualTo("present"); + } + if (hadError.get()) { + assertThat(tlInOnError.get()).isEqualTo("present"); + } else { + assertThat(tlInOnComplete.get()).isEqualTo("present"); + } } - void assertThatThreadLocalsPresentDirectCoreSubscribe(CorePublisher source) throws InterruptedException { - AtomicReference value = new AtomicReference<>(); + void assertThatThreadLocalsPresentDirectCoreSubscribe( + CorePublisher source) throws InterruptedException { + assertThatThreadLocalsPresentDirectCoreSubscribe(source, () -> {}); + } + + void assertThatThreadLocalsPresentDirectCoreSubscribe( + CorePublisher source, Runnable asyncAction) throws InterruptedException { + AtomicReference valueInOnNext = new AtomicReference<>(); + AtomicReference valueInOnComplete = new AtomicReference<>(); + AtomicReference valueInOnError = new AtomicReference<>(); AtomicReference error = new AtomicReference<>(); AtomicBoolean complete = new AtomicBoolean(); + AtomicBoolean hadNext = new AtomicBoolean(); CountDownLatch latch = new CountDownLatch(1); CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); + new CoreSubscriberWithContext<>( + valueInOnNext, valueInOnComplete, valueInOnError, + error, latch, hadNext, complete); source.subscribe(subscriberWithContext); + executorService.submit(asyncAction); + latch.await(10, TimeUnit.MILLISECONDS); - assertThat(error.get()).isNull(); - assertThat(complete.get()).isTrue(); - assertThat(value.get()).isEqualTo("present"); + if (hadNext.get()) { + assertThat(valueInOnNext.get()).isEqualTo("present"); + } + if (error.get() == null) { + assertThat(valueInOnComplete.get()).isEqualTo("present"); + assertThat(complete).isTrue(); + } else { + assertThat(valueInOnError.get()).isEqualTo("present"); + } } - void assertThreadLocalPresentInOnNext(Flux chain) { - AtomicReference value = new AtomicReference<>(); + // We force the use of subscribe(Subscriber) override instead of + // subscribe(CoreSubscriber), and we can observe that for such a case we + // are able to wrap the Subscriber and restore ThreadLocal values for the + // signals received downstream. + void assertThatThreadLocalsPresentDirectRawSubscribe( + Publisher source) throws InterruptedException { + assertThatThreadLocalsPresentDirectRawSubscribe(source, () -> {}); + } - chain.doOnNext(item -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .blockLast(); + void assertThatThreadLocalsPresentDirectRawSubscribe( + Publisher source, Runnable asyncAction) throws InterruptedException { + AtomicReference valueInOnNext = new AtomicReference<>(); + AtomicReference valueInOnComplete = new AtomicReference<>(); + AtomicReference valueInOnError = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean hadNext = new AtomicBoolean(); + AtomicBoolean complete = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); - assertThat(value.get()).isEqualTo("present"); - } + CoreSubscriberWithContext subscriberWithContext = + new CoreSubscriberWithContext<>( + valueInOnNext, valueInOnComplete, valueInOnError, + error, latch, hadNext, complete); - void assertThreadLocalPresentInOnComplete(Flux chain) { - AtomicReference value = new AtomicReference<>(); + source.subscribe(subscriberWithContext); - chain.doOnComplete(() -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .blockLast(); + executorService.submit(asyncAction); - assertThat(value.get()).isEqualTo("present"); + latch.await(10, TimeUnit.MILLISECONDS); + + if (hadNext.get()) { + assertThat(valueInOnNext.get()).isEqualTo("present"); + } + if (error.get() == null) { + assertThat(valueInOnComplete.get()).isEqualTo("present"); + assertThat(complete).isTrue(); + } else { + assertThat(valueInOnError.get()).isEqualTo("present"); + } } // Fundamental tests for Flux @Test - void chainedFluxSubscribe() { - ThreadSwitchingFlux chain = new ThreadSwitchingFlux<>("Hello", executorService); - assertThreadLocalPresentInOnNext(chain); + void fluxSubscribe() { + assertThreadLocalsPresentInFlux(this::threadSwitchingFlux, true); } @Test - void internalFluxSubscribe() { - ThreadSwitchingFlux inner = new ThreadSwitchingFlux<>("Hello", executorService); - Flux chain = Flux.just("hello").flatMap(item -> inner); - - assertThreadLocalPresentInOnNext(chain); + void internalFluxFlatMapSubscribe() { + assertThreadLocalsPresentInFlux(() -> + Flux.just("hello") + .flatMap(item -> threadSwitchingFlux())); } @Test void internalFluxSubscribeNoFusion() { - ThreadSwitchingFlux inner = new ThreadSwitchingFlux<>("Hello", executorService); - Flux chain = Flux.just("hello").hide().flatMap(item -> inner); - - assertThreadLocalPresentInOnNext(chain); - } - - @Test - void testFluxSubscriberAsRawSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - - TestSubscriber testSubscriber = - TestSubscriber.builder().contextPut(KEY, "present").build(); - - flux - .doOnNext(i -> value.set(REF.get())) - .subscribe((Subscriber) testSubscriber); - - testSubscriber.block(Duration.ofMillis(10)); - assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); - assertThat(value.get()).isEqualTo("present"); - } - - @Test - void testFluxSubscriberAsCoreSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - - TestSubscriber testSubscriber = - TestSubscriber.builder().contextPut(KEY, "present").build(); - - flux - .doOnNext(i -> value.set(REF.get())) - .subscribe(testSubscriber); - - testSubscriber.block(Duration.ofMillis(10)); - assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); - // Because of onNext in the chain, the internal operator implementation is - // able to wrap the subscriber and restore the ThreadLocal values - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInFlux(() -> + Flux.just("hello") + .hide() + .flatMap(item -> threadSwitchingFlux())); } @Test void directFluxSubscribeAsCoreSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); + AtomicReference valueInOnNext = new AtomicReference<>(); + AtomicReference valueInOnComplete = new AtomicReference<>(); + AtomicReference valueInOnError = new AtomicReference<>(); AtomicReference error = new AtomicReference<>(); + AtomicBoolean hadNext = new AtomicBoolean(); AtomicBoolean complete = new AtomicBoolean(); CountDownLatch latch = new CountDownLatch(1); - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); + Flux flux = threadSwitchingFlux(); CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); + new CoreSubscriberWithContext<>( + valueInOnNext, valueInOnComplete, valueInOnError, + error, latch, hadNext, complete); flux.subscribe(subscriberWithContext); @@ -479,98 +556,40 @@ void directFluxSubscribeAsCoreSubscriber() throws InterruptedException { // We can't do anything here. subscribe(CoreSubscriber) is abstract in // CoreSubscriber interface and we have no means to intercept the calls to // restore ThreadLocals. - assertThat(value.get()).isEqualTo("ref_init"); - } - - @Test - void directFluxSubscribeAsRawSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - AtomicReference error = new AtomicReference<>(); - AtomicBoolean complete = new AtomicBoolean(); - CountDownLatch latch = new CountDownLatch(1); - - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); - - // We force the use of subscribe(Subscriber) override instead of - // subscribe(CoreSubscriber), and we can observe that for such a case we - // are able to wrap the Subscriber and restore ThreadLocal values for the - // signals received downstream. - flux.subscribe((Subscriber) subscriberWithContext); - - latch.await(10, TimeUnit.MILLISECONDS); - - assertThat(error.get()).isNull(); - assertThat(complete.get()).isTrue(); - assertThat(value.get()).isEqualTo("present"); + assertThat(valueInOnNext.get()).isEqualTo("ref_init"); + assertThat(valueInOnComplete.get()).isEqualTo("ref_init"); } // Fundamental tests for Mono @Test - void chainedMonoSubscribe() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - assertThreadLocalPresentInOnNext(mono); - } - - @Test - void internalMonoSubscribe() { - Mono inner = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Mono.just("hello").flatMap(item -> inner); - assertThreadLocalPresentInOnNext(chain); + void monoSubscribe() { + assertThreadLocalsPresentInMono(this::threadSwitchingMono, true); } @Test - void testMonoSubscriberAsRawSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - - TestSubscriber testSubscriber = - TestSubscriber.builder().contextPut(KEY, "present").build(); - - mono - .doOnNext(i -> value.set(REF.get())) - .subscribe((Subscriber) testSubscriber); - - testSubscriber.block(Duration.ofMillis(10)); - assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); - assertThat(value.get()).isEqualTo("present"); - } - - @Test - void testMonoSubscriberAsCoreSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - - TestSubscriber testSubscriber = - TestSubscriber.builder().contextPut(KEY, "present").build(); - - mono - .doOnNext(i -> value.set(REF.get())) - .subscribe(testSubscriber); - - testSubscriber.block(Duration.ofMillis(10)); - assertThat(testSubscriber.expectTerminalSignal().isOnComplete()).isTrue(); - // Because of onNext in the chain, the internal operator implementation is - // able to wrap the subscriber and restore the ThreadLocal values - assertThat(value.get()).isEqualTo("present"); + void internalMonoFlatMapSubscribe() { + assertThreadLocalsPresentInMono(() -> + Mono.just("hello") + .flatMap(item -> threadSwitchingMono())); } @Test void directMonoSubscribeAsCoreSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); + AtomicReference valueInOnNext = new AtomicReference<>(); + AtomicReference valueInOnComplete = new AtomicReference<>(); + AtomicReference valueInOnError = new AtomicReference<>(); AtomicReference error = new AtomicReference<>(); AtomicBoolean complete = new AtomicBoolean(); + AtomicBoolean hadNext = new AtomicBoolean(); CountDownLatch latch = new CountDownLatch(1); Mono mono = new ThreadSwitchingMono<>("Hello", executorService); CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); + new CoreSubscriberWithContext<>( + valueInOnNext, valueInOnComplete, valueInOnError, + error, latch, hadNext, complete); mono.subscribe(subscriberWithContext); @@ -582,635 +601,443 @@ void directMonoSubscribeAsCoreSubscriber() throws InterruptedException { // We can't do anything here. subscribe(CoreSubscriber) is abstract in // CoreSubscriber interface and we have no means to intercept the calls to // restore ThreadLocals. - assertThat(value.get()).isEqualTo("ref_init"); - } - - @Test - void directMonoSubscribeAsRawSubscriber() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - AtomicReference error = new AtomicReference<>(); - AtomicBoolean complete = new AtomicBoolean(); - CountDownLatch latch = new CountDownLatch(1); - - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); - - // We force the use of subscribe(Subscriber) override instead of - // subscribe(CoreSubscriber), and we can observe that for such a case we - // are able to wrap the Subscriber and restore ThreadLocal values for the - // signals received downstream. - mono.subscribe((Subscriber) subscriberWithContext); - - latch.await(10, TimeUnit.MILLISECONDS); - - assertThat(error.get()).isNull(); - assertThat(complete.get()).isTrue(); - assertThat(value.get()).isEqualTo("present"); + assertThat(valueInOnNext.get()).isEqualTo("ref_init"); + assertThat(valueInOnComplete.get()).isEqualTo("ref_init"); } // Flux tests @Test void fluxCreate() { - Flux flux = Flux.create(sink -> { - executorService.submit(() -> { - sink.next("Hello"); - sink.complete(); - }); - }); - - assertThreadLocalPresentInOnNext(flux); - } - - @Test - void fluxCreateDirect() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - AtomicReference error = new AtomicReference<>(); - AtomicBoolean complete = new AtomicBoolean(); - CountDownLatch latch = new CountDownLatch(1); - - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); - - Publisher flux = Flux.create(sink -> { - executorService.submit(() -> { - sink.next("Hello"); - sink.complete(); - }); - }); - - flux.subscribe(subscriberWithContext); - - latch.await(10, TimeUnit.MILLISECONDS); + Supplier> fluxSupplier = + () -> Flux.create(sink -> executorService.submit(() -> { + sink.next("Hello"); + sink.complete(); + })); - assertThat(error.get()).isNull(); - assertThat(complete.get()).isTrue(); - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInFlux(fluxSupplier); } @Test void fluxMap() { - Flux flux = - new ThreadSwitchingFlux<>("Hello", executorService).map(String::toUpperCase); - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> threadSwitchingFlux().map(String::toUpperCase)); } @Test void fluxIgnoreThenSwitchThread() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Flux.just("Bye").then(mono); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> Flux.just("Bye").then(threadSwitchingMono())); } @Test void fluxSwitchThreadThenIgnore() { - Flux flux = new ThreadSwitchingFlux<>("Ignored", executorService); - Mono chain = flux.then(Mono.just("Hello")); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> threadSwitchingFlux().then(Mono.just("Hi"))); } @Test void fluxDeferContextual() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - Flux chain = Flux.deferContextual(ctx -> flux); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInFlux(() -> + Flux.deferContextual(ctx -> threadSwitchingFlux())); } @Test void fluxFirstWithSignalArray() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - Flux chain = Flux.firstWithSignal(flux); - assertThreadLocalPresentInOnNext(chain); - - Flux other = new ThreadSwitchingFlux<>("Hello", executorService); - assertThreadLocalPresentInOnNext(chain.or(other)); + assertThreadLocalsPresentInFlux(() -> + Flux.firstWithSignal(threadSwitchingFlux())); + assertThreadLocalsPresentInFlux(() -> + Flux.firstWithSignal(threadSwitchingFlux()).or(threadSwitchingFlux())); } @Test void fluxFirstWithSignalIterable() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - Flux chain = Flux.firstWithSignal(Collections.singletonList(flux)); - assertThreadLocalPresentInOnNext(chain); - - Flux other1 = new ThreadSwitchingFlux<>("Hello", executorService); - Flux other2 = new ThreadSwitchingFlux<>("Hello", executorService); - List> list = Stream.of(other1, other2).collect(Collectors.toList()); - assertThreadLocalPresentInOnNext(Flux.firstWithSignal(list)); + assertThreadLocalsPresentInFlux(() -> + Flux.firstWithSignal(Collections.singletonList(threadSwitchingFlux()))); + assertThreadLocalsPresentInFlux(() -> + Flux.firstWithSignal(Stream.of(threadSwitchingFlux(), threadSwitchingFlux()).collect(Collectors.toList()))); } @Test void fluxRetryWhen() { - Flux flux = - new ThreadSwitchingFlux<>("Hello", executorService).retryWhen(Retry.max(1)); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + threadSwitchingFlux().retryWhen(Retry.max(1))); } @Test void fluxRetryWhenSwitchingThread() { - Flux flux = + assertThreadLocalsPresentInFlux(() -> Flux.error(new RuntimeException("Oops")) - .retryWhen(Retry.from(f -> new ThreadSwitchingFlux<>( - "Hello", executorService))); - - assertThreadLocalPresentInOnComplete(flux); + .retryWhen(Retry.from(f -> threadSwitchingFlux()))); } @Test void fluxWindowUntil() { - Flux flux = - new ThreadSwitchingFlux<>("Hello", executorService) - .windowUntil(s -> true) - .flatMap(Function.identity()); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + threadSwitchingFlux().windowUntil(s -> true) + .flatMap(Function.identity())); } @Test void switchOnFirst() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) - .switchOnFirst((s, f) -> f.map(String::toUpperCase)); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + threadSwitchingFlux() + .switchOnFirst((s, f) -> f.map(String::toUpperCase))); } @Test void switchOnFirstFuseable() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) - .filter("Hello"::equals) - .switchOnFirst((s, f) -> f.map(String::toUpperCase)); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + threadSwitchingFlux() + .filter("Hello"::equals) + .switchOnFirst((s, f) -> f.map(String::toUpperCase))); } @Test void switchOnFirstSwitchThread() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) - .switchOnFirst((s, f) -> new ThreadSwitchingFlux<>("Goodbye", executorService)); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + threadSwitchingFlux() + .switchOnFirst((s, f) -> threadSwitchingFlux())); } @Test void switchOnFirstFuseableSwitchThread() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService) - .filter("Hello"::equals) - .switchOnFirst((s, f) -> new ThreadSwitchingFlux<>("Goodbye", executorService)); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + threadSwitchingFlux() + .filter("Hello"::equals) + .switchOnFirst((s, f) -> threadSwitchingFlux())); } @Test void fluxWindowTimeout() { - Flux> flux = new ThreadSwitchingFlux<>("Hello", executorService) - .windowTimeout(1, Duration.ofDays(1), true); - - assertThreadLocalPresentInOnNext(flux); - } - - @Test - void fluxWindowTimeoutDirect() throws InterruptedException { - Flux> flux = new ThreadSwitchingFlux<>("Hello", executorService) - .windowTimeout(1, Duration.ofDays(1), true); - - assertThatThreadLocalsPresentDirectCoreSubscribe(flux); + assertThreadLocalsPresentInFlux(() -> + threadSwitchingFlux() + .windowTimeout(1, Duration.ofDays(1), true)); } @Test void fluxMergeComparing() { - Flux flux = Flux.mergeComparing(Flux.empty(), - new ThreadSwitchingFlux<>("Hello", executorService)); - - assertThreadLocalPresentInOnNext(flux); - } - - @Test - void fluxMergeComparingDirect() throws InterruptedException { - Flux flux = Flux.mergeComparing(Flux.empty(), - new ThreadSwitchingFlux<>("Hello", executorService)); - - assertThatThreadLocalsPresentDirectCoreSubscribe(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.mergeComparing(Flux.empty(), threadSwitchingFlux())); } @Test void fluxFirstWithValueArray() { - Flux flux = Flux.firstWithValue(Flux.empty(), - new ThreadSwitchingFlux<>("Hola", executorService)); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.firstWithValue(Flux.empty(), threadSwitchingFlux())); } @Test void fluxFirstWithValueIterable() { - List> list = Stream.of(Flux.empty(), - new ThreadSwitchingFlux<>("Hola", executorService)) - .collect(Collectors.toList()); - Flux flux = Flux.firstWithValue(list); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.firstWithValue( + Stream.of(Flux.empty(), threadSwitchingFlux()) + .collect(Collectors.toList()))); } @Test void fluxConcatArray() { - Flux flux = Flux.concat(Mono.empty(), - new ThreadSwitchingFlux<>("Hello", executorService)); - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.concat(Mono.empty(), threadSwitchingFlux())); } @Test void fluxConcatIterable() { - List> list = Stream.of(Flux.empty(), - new ThreadSwitchingFlux<>("Hello", executorService)) - .collect(Collectors.toList()); - - Flux flux = Flux.concat(list); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.concat( + Stream.of(Flux.empty(), threadSwitchingFlux()).collect(Collectors.toList()))); } @Test void fluxGenerate() { - Flux flux = Flux.generate(sink -> { + assertThreadLocalsPresentInFlux(() -> Flux.generate(sink -> { sink.next("Hello"); // the generator is checked if any signal was delivered by the consumer - // so we check at completion only + // so we perform asynchronous completion only executorService.submit(() -> { sink.complete(); }); - }); - - assertThreadLocalPresentInOnComplete(flux); + })); } @Test void fluxCombineLatest() { - Flux flux = Flux.combineLatest(Flux.just(""), - new ThreadSwitchingFlux<>("Hello", executorService), (s1, s2) -> s2); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.combineLatest( + Flux.just(""), threadSwitchingFlux(), (s1, s2) -> s2)); } @Test void fluxUsing() { - Flux flux = Flux.using(() -> 0, i -> new ThreadSwitchingFlux<>( - "Hello", executorService), i -> {}); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.using(() -> 0, i -> threadSwitchingFlux(), i -> {})); } @Test void fluxZip() { - Flux> flux = Flux.zip(Flux.just(""), - new ThreadSwitchingFlux<>("Hello", - executorService)); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.zip(Flux.just(""), threadSwitchingFlux())); } @Test void fluxZipIterable() { - List> list = Stream.of(Flux.just(""), - new ThreadSwitchingFlux<>("Hello", - executorService)).collect(Collectors.toList()); - - Flux> flux = Flux.zip(list, - obj -> Tuples.of((String) obj[0], (String) obj[1])); - - assertThreadLocalPresentInOnNext(flux); + assertThreadLocalsPresentInFlux(() -> + Flux.zip(Stream.of(Flux.just(""), threadSwitchingFlux()).collect(Collectors.toList()), + obj -> Tuples.of((String) obj[0], (String) obj[1]))); } // Mono tests @Test void monoCreate() { - Mono mono = Mono.create(sink -> { - executorService.submit(() -> { - sink.success("Hello"); - }); - }); - - assertThreadLocalPresentInOnNext(mono); - } - - @Test - void monoCreateDirect() throws InterruptedException { - AtomicReference value = new AtomicReference<>(); - AtomicReference error = new AtomicReference<>(); - AtomicBoolean complete = new AtomicBoolean(); - CountDownLatch latch = new CountDownLatch(1); - - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); - - Publisher mono = Mono.create(sink -> { - executorService.submit(() -> { - sink.success("Hello"); - }); - }); - - mono.subscribe(subscriberWithContext); - - latch.await(10, TimeUnit.MILLISECONDS); - - assertThat(error.get()).isNull(); - assertThat(complete.get()).isTrue(); - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInMono(() -> + Mono.create(sink -> { + executorService.submit(() -> { + sink.success("Hello"); + }); + })); } @Test void monoSwitchThreadIgnoreThen() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = mono.then(Mono.just("Bye")); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + threadSwitchingMono().then(Mono.just("Bye"))); } @Test void monoIgnoreThenSwitchThread() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Mono.just("Bye").then(mono); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.just("Bye").then(threadSwitchingMono())); } @Test void monoSwitchThreadDelayUntil() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = mono.delayUntil(s -> Mono.delay(Duration.ofMillis(1))); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + threadSwitchingMono().delayUntil(s -> Mono.delay(Duration.ofMillis(1)))); } @Test void monoDelayUntilSwitchingThread() { - Mono mono = Mono.just("Hello"); - Mono chain = mono.delayUntil(s -> new ThreadSwitchingMono<>("Done", executorService)); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.just("Hello").delayUntil(s -> threadSwitchingMono())); } @Test void monoIgnoreSwitchingThread() { - Mono mono = Mono.ignoreElements(new ThreadSwitchingMono<>("Hello", executorService)); - assertThreadLocalPresentInOnSuccess(mono); + assertThreadLocalsPresentInMono(() -> + Mono.ignoreElements(threadSwitchingMono())); } @Test void monoDeferContextual() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Mono.deferContextual(ctx -> mono); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.deferContextual(ctx -> threadSwitchingMono())); } @Test void monoDefer() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Mono.defer(() -> mono); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.defer(this::threadSwitchingMono)); } @Test void monoFirstWithSignalArray() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Mono.firstWithSignal(mono); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.firstWithSignal(threadSwitchingMono())); - Mono other = new ThreadSwitchingMono<>("Hello", executorService); - assertThreadLocalPresentInOnNext(chain.or(other)); + assertThreadLocalsPresentInMono(() -> + Mono.firstWithSignal(threadSwitchingMono()) + .or(threadSwitchingMono())); } @Test void monoFirstWithSignalIterable() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Mono.firstWithSignal(Collections.singletonList(mono)); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.firstWithSignal(Collections.singletonList(threadSwitchingMono()))); - Mono other1 = new ThreadSwitchingMono<>("Hello", executorService); - Mono other2 = new ThreadSwitchingMono<>("Hello", executorService); - List> list = Stream.of(other1, other2).collect(Collectors.toList()); - assertThreadLocalPresentInOnNext(Mono.firstWithSignal(list)); + assertThreadLocalsPresentInMono(() -> + Mono.firstWithSignal( + Stream.of(threadSwitchingMono(), threadSwitchingMono()) + .collect(Collectors.toList()))); } @Test void monoFromFluxSingle() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - Mono chain = flux.single(); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + threadSwitchingFlux().single()); } @Test void monoRetryWhen() { - Mono mono = - new ThreadSwitchingMono<>("Hello", executorService).retryWhen(Retry.max(1)); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + threadSwitchingMono().retryWhen(Retry.max(1))); } @Test void monoRetryWhenSwitchingThread() { - Mono mono = + assertThreadLocalsPresentInMono(() -> Mono.error(new RuntimeException("Oops")) - .retryWhen(Retry.from(f -> new ThreadSwitchingMono<>( - "Hello", executorService))); - - assertThreadLocalPresentInOnSuccess(mono); + .retryWhen(Retry.from(f -> threadSwitchingMono()))); } @Test void monoUsing() { - Mono mono = Mono.using( - () -> "Hello", - seed -> new ThreadSwitchingMono<>("Hola", executorService), - seed -> {}, - false); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + Mono.using(() -> "Hello", + seed -> threadSwitchingMono(), + seed -> {}, + false)); } @Test void monoFirstWithValueArray() { - Mono mono = Mono.firstWithValue(Mono.empty(), - new ThreadSwitchingMono<>("Hola", executorService)); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + Mono.firstWithValue(Mono.empty(), threadSwitchingMono())); } @Test void monoFirstWithValueIterable() { - List> list = Stream.of(Mono.empty(), - new ThreadSwitchingMono<>("Hola", executorService)) - .collect(Collectors.toList()); - Mono mono = Mono.firstWithValue(list); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + Mono.firstWithValue( + Stream.of(Mono.empty(), threadSwitchingMono()) + .collect(Collectors.toList()))); } @Test void monoZip() { - Mono> mono = Mono.zip(Mono.just(""), - new ThreadSwitchingMono<>("Hello", - executorService)); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + Mono.zip(Mono.just(""), threadSwitchingMono())); } @Test void monoZipIterable() { - List> list = Stream.of(Mono.just(""), - new ThreadSwitchingMono<>("Hello", - executorService)).collect(Collectors.toList()); - - Mono> mono = Mono.zip(list, - obj -> Tuples.of((String) obj[0], (String) obj[1])); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + Mono.zip( + Stream.of(Mono.just(""), threadSwitchingMono()) + .collect(Collectors.toList()), + obj -> Tuples.of((String) obj[0], (String) obj[1]))); } @Test void monoSequenceEqual() { - Mono mono = Mono.sequenceEqual(Mono.just("Hello"), - new ThreadSwitchingMono<>("Hello", executorService)); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + Mono.sequenceEqual(Mono.just("Hello"), threadSwitchingMono())); } @Test void monoWhen() { - Mono mono = Mono.when(Mono.empty(), new ThreadSwitchingMono<>("Hello" - , executorService)); - - assertThreadLocalPresentInOnSuccess(mono); + assertThreadLocalsPresentInMono(() -> + Mono.when(Mono.empty(), threadSwitchingMono())); } @Test void monoUsingWhen() { - Mono mono = Mono.usingWhen(Mono.just("Hello"), s -> - new ThreadSwitchingMono<>(s, executorService), s -> Mono.empty()); - - assertThreadLocalPresentInOnNext(mono); + assertThreadLocalsPresentInMono(() -> + Mono.usingWhen(Mono.just("Hello"), s -> threadSwitchingMono(), + s -> Mono.empty())); } // ParallelFlux tests @Test void parallelFluxFromMonoToMono() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Mono chain = Mono.from(ParallelFlux.from(mono)); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.from(ParallelFlux.from(threadSwitchingMono()))); } @Test void parallelFluxFromMonoToFlux() { - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - Flux chain = Flux.from(ParallelFlux.from(mono)); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInFlux(() -> + Flux.from(ParallelFlux.from(threadSwitchingMono()))); } @Test void parallelFluxFromFluxToMono() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - Mono chain = Mono.from(ParallelFlux.from(flux)); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInMono(() -> + Mono.from(ParallelFlux.from(threadSwitchingFlux()))); } @Test void parallelFluxFromFluxToFlux() { - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - Flux chain = Flux.from(ParallelFlux.from(flux)); - assertThreadLocalPresentInOnNext(chain); + assertThreadLocalsPresentInFlux(() -> + Flux.from(ParallelFlux.from(threadSwitchingFlux()))); } @Test void parallelFluxLift() { - ParallelFlux parallelFlux = - ParallelFlux.from(Flux.just("Hello")); + assertThreadLocalsPresentInFlux(() -> { + ParallelFlux parallelFlux = ParallelFlux.from(Flux.just("Hello")); - Publisher lifted = - Operators.liftPublisher((pub, sub) -> new CoreSubscriber() { - @Override - public void onSubscribe(Subscription s) { - executorService.submit(() -> sub.onSubscribe(s)); - } + Publisher lifted = + Operators.liftPublisher((pub, sub) -> new CoreSubscriber() { + @Override + public void onSubscribe(Subscription s) { + executorService.submit(() -> sub.onSubscribe(s)); + } - @Override - public void onNext(String s) { - executorService.submit(() -> sub.onNext(s)); - } + @Override + public void onNext(String s) { + executorService.submit(() -> sub.onNext(s)); + } - @Override - public void onError(Throwable t) { - executorService.submit(() -> sub.onError(t)); - } + @Override + public void onError(Throwable t) { + executorService.submit(() -> sub.onError(t)); + } - @Override - public void onComplete() { - executorService.submit(sub::onComplete); - } - }) - .apply(parallelFlux); + @Override + public void onComplete() { + executorService.submit(sub::onComplete); + } + }) + .apply(parallelFlux); - assertThreadLocalPresentInOnNext(((ParallelFlux) lifted).sequential()); + return ((ParallelFlux) lifted).sequential(); + }); } @Test void parallelFluxLiftFuseable() { - ParallelFlux> parallelFlux = - ParallelFlux.from(Flux.just("Hello")) - .collect(ArrayList::new, ArrayList::add); - - Publisher> lifted = Operators., ArrayList>liftPublisher( - (pub, sub) -> new CoreSubscriber>() { - @Override - public void onSubscribe(Subscription s) { - executorService.submit(() -> sub.onSubscribe(s)); - } - - @Override - public void onNext(ArrayList s) { - executorService.submit(() -> sub.onNext(s)); - } - - @Override - public void onError(Throwable t) { - executorService.submit(() -> sub.onError(t)); - } - - @Override - public void onComplete() { - executorService.submit(sub::onComplete); - } - }) - .apply(parallelFlux); - - assertThreadLocalPresentInOnNext(((ParallelFlux) lifted).sequential()); + assertThreadLocalsPresentInFlux(() -> { + ParallelFlux> parallelFlux = + ParallelFlux.from(Flux.just("Hello")) + .collect(ArrayList::new, ArrayList::add); + + Publisher> lifted = + Operators., ArrayList>liftPublisher((pub, sub) -> new CoreSubscriber>() { + @Override + public void onSubscribe(Subscription s) { + executorService.submit(() -> sub.onSubscribe(s)); + } + + @Override + public void onNext(ArrayList s) { + executorService.submit(() -> sub.onNext(s)); + } + + @Override + public void onError(Throwable t) { + executorService.submit(() -> sub.onError(t)); + } + + @Override + public void onComplete() { + executorService.submit(sub::onComplete); + } + }) + .apply(parallelFlux); + + return ((ParallelFlux) lifted).sequential(); + }); } @Test void parallelFluxFromThreadSwitchingMono() { - AtomicReference value = new AtomicReference<>(); - - Mono mono = new ThreadSwitchingMono<>("Hello", executorService); - - ParallelFlux.from(mono) - .sequential() - .doOnNext(i -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .blockLast(); - - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInFlux(() -> + ParallelFlux.from(threadSwitchingMono()).sequential()); } @Test void parallelFluxFromThreadSwitchingFlux() { - AtomicReference value = new AtomicReference<>(); - - Flux flux = new ThreadSwitchingFlux<>("Hello", executorService); - - ParallelFlux.from(flux) - .sequential() - .doOnNext(i -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .blockLast(); - - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInFlux(() -> + ParallelFlux.from(threadSwitchingFlux()).sequential()); } @Test @@ -1227,26 +1054,16 @@ void threadSwitchingParallelFluxSequential() { @Test void threadSwitchingParallelFluxThen() { - AtomicReference value = new AtomicReference<>(); - new ThreadSwitchingParallelFlux("Hello", executorService) - .then() - .doOnSuccess(v -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .block(); - - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInMono(() -> + new ThreadSwitchingParallelFlux("Hello", executorService) + .then()); } @Test void threadSwitchingParallelFluxOrdered() { - AtomicReference value = new AtomicReference<>(); - new ThreadSwitchingParallelFlux("Hello", executorService) - .ordered(Comparator.naturalOrder()) - .doOnNext(i -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .blockLast(); - - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInFlux(() -> + new ThreadSwitchingParallelFlux("Hello", executorService) + .ordered(Comparator.naturalOrder())); } @Test @@ -1292,14 +1109,9 @@ void threadSwitchingParallelFluxGroup() { @Test void threadSwitchingParallelFluxSort() { - AtomicReference value = new AtomicReference<>(); - new ThreadSwitchingParallelFlux("Hello", executorService) - .sorted(Comparator.naturalOrder()) - .doOnNext(i -> value.set(REF.get())) - .contextWrite(Context.of(KEY, "present")) - .blockLast(); - - assertThat(value.get()).isEqualTo("present"); + assertThreadLocalsPresentInFlux(() -> + new ThreadSwitchingParallelFlux("Hello", executorService) + .sorted(Comparator.naturalOrder())); } // Sinks tests @@ -1330,44 +1142,31 @@ void sink() throws InterruptedException, TimeoutException { @Test void sinkDirect() throws InterruptedException, TimeoutException { - AtomicReference value = new AtomicReference<>(); - AtomicReference error = new AtomicReference<>(); - AtomicBoolean complete = new AtomicBoolean(); - CountDownLatch latch = new CountDownLatch(1); - - Sinks.One sink = Sinks.one(); - - CoreSubscriberWithContext subscriberWithContext = - new CoreSubscriberWithContext<>(value, error, latch, complete); - - sink.asMono() - .subscribe(subscriberWithContext); - - executorService.submit(() -> sink.tryEmitValue("Hello")); - - if (!latch.await(10, TimeUnit.MILLISECONDS)) { - throw new TimeoutException("timed out"); - } + Sinks.One sink1 = Sinks.one(); + assertThatThreadLocalsPresentDirectCoreSubscribe(sink1.asMono(), + () -> sink1.tryEmitValue("Hello")); - assertThat(value.get()).isEqualTo("present"); + Sinks.One sink2 = Sinks.one(); + assertThatThreadLocalsPresentDirectRawSubscribe(sink2.asMono(), + () -> sink2.tryEmitValue("Hello")); } @Test - void sinkEmpty() throws InterruptedException, TimeoutException { + void sinksEmpty() throws InterruptedException, TimeoutException { AtomicReference value = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); - Sinks.Empty empty = Sinks.empty(); + Sinks.Empty spec = Sinks.empty(); - empty.asMono() - .doOnTerminate(() -> { + spec.asMono() + .doOnSuccess(ignored -> { value.set(REF.get()); latch.countDown(); }) .contextWrite(Context.of(KEY, "present")) .subscribe(); - executorService.submit(empty::tryEmitEmpty); + executorService.submit(spec::tryEmitEmpty); if (!latch.await(10, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); @@ -1376,6 +1175,15 @@ void sinkEmpty() throws InterruptedException, TimeoutException { assertThat(value.get()).isEqualTo("present"); } + @Test + void sinksEmptyDirect() throws InterruptedException { + Sinks.Empty empty1 = Sinks.empty(); + assertThatThreadLocalsPresentDirectCoreSubscribe(empty1.asMono(), empty1::tryEmitEmpty); + + Sinks.Empty empty2 = Sinks.empty(); + assertThatThreadLocalsPresentDirectRawSubscribe(empty2.asMono(), empty2::tryEmitEmpty); + } + @Test void sinkManyUnicast() throws InterruptedException, TimeoutException { AtomicReference value = new AtomicReference<>(); @@ -1402,6 +1210,25 @@ void sinkManyUnicast() throws InterruptedException, TimeoutException { assertThat(value.get()).isEqualTo("present"); } + @Test + void sinkManyUnicastDirect() throws InterruptedException { + Sinks.Many many1 = Sinks.many().unicast() + .onBackpressureBuffer(); + + assertThatThreadLocalsPresentDirectCoreSubscribe(many1.asFlux(), () -> { + many1.tryEmitNext("Hello"); + many1.tryEmitComplete(); + }); + + Sinks.Many many2 = Sinks.many().unicast() + .onBackpressureBuffer(); + + assertThatThreadLocalsPresentDirectRawSubscribe(many2.asFlux(), () -> { + many2.tryEmitNext("Hello"); + many2.tryEmitComplete(); + }); + } + @Test void sinkManyUnicastNoBackpressure() throws InterruptedException, TimeoutException { @@ -1504,30 +1331,6 @@ void sinkManyMulticastBestEffort() throws InterruptedException, TimeoutException assertThat(value.get()).isEqualTo("present"); } - @Test - void sinksEmpty() throws InterruptedException, TimeoutException { - AtomicReference value = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - - Sinks.Empty spec = Sinks.empty(); - - spec.asMono() - .doOnSuccess(ignored -> { - value.set(REF.get()); - latch.countDown(); - }) - .contextWrite(Context.of(KEY, "present")) - .subscribe(); - - executorService.submit(spec::tryEmitEmpty); - - if (!latch.await(10, TimeUnit.MILLISECONDS)) { - throw new TimeoutException("timed out"); - } - - assertThat(value.get()).isEqualTo("present"); - } - // Other List> getAllClassesInClasspathRecursively(File directory) throws Exception { @@ -1557,6 +1360,7 @@ List> getAllClassesInClasspathRecursively(File directory) throws Except } @Test + @Disabled("Used to find Publishers that can switch threads") void printInterestingClasses() throws Exception { List> allClasses = getAllClassesInClasspathRecursively(new File("./build/classes/java/main/reactor/")); @@ -1592,18 +1396,28 @@ void printInterestingClasses() throws Exception { private class CoreSubscriberWithContext implements CoreSubscriber { - private final AtomicReference value; + private final AtomicReference valueInOnNext; + private final AtomicReference valueInOnComplete; + private final AtomicReference valueInOnError; private final AtomicReference error; private final CountDownLatch latch; private final AtomicBoolean complete; + private final AtomicBoolean hadNext; - public CoreSubscriberWithContext(AtomicReference value, + public CoreSubscriberWithContext( + AtomicReference valueInOnNext, + AtomicReference valueInOnComplete, + AtomicReference valueInOnError, AtomicReference error, CountDownLatch latch, + AtomicBoolean hadNext, AtomicBoolean complete) { - this.value = value; + this.valueInOnNext = valueInOnNext; + this.valueInOnComplete = valueInOnComplete; + this.valueInOnError = valueInOnError; this.error = error; this.latch = latch; + this.hadNext = hadNext; this.complete = complete; } @@ -1619,18 +1433,21 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - value.set(REF.get()); + hadNext.set(true); + valueInOnNext.set(REF.get()); } @Override public void onError(Throwable t) { error.set(t); + valueInOnError.set(REF.get()); latch.countDown(); } @Override public void onComplete() { complete.set(true); + valueInOnComplete.set(REF.get()); latch.countDown(); } } From 5b3dfbf4649d8540d03a58a069a0ff1a527b9b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Thu, 28 Sep 2023 16:01:29 +0200 Subject: [PATCH 22/26] Delivering completion signals on another thread --- .../AutomaticContextPropagationTest.java | 74 ++++++++++--------- .../core/publisher/ThreadSwitchingFlux.java | 2 +- .../core/publisher/ThreadSwitchingMono.java | 2 +- .../ThreadSwitchingParallelFlux.java | 2 +- 4 files changed, 43 insertions(+), 37 deletions(-) diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java index d31aa38756..988bb0aacc 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/AutomaticContextPropagationTest.java @@ -47,7 +47,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.core.CorePublisher; import reactor.core.CoreSubscriber; @@ -57,7 +56,6 @@ import reactor.test.subscriber.TestSubscriber; import reactor.util.concurrent.Queues; import reactor.util.context.Context; -import reactor.util.function.Tuple2; import reactor.util.function.Tuples; import reactor.util.retry.Retry; @@ -186,7 +184,7 @@ void threadLocalsPresentInDoOnRequest() { } @Test - void threadLocalsPresentInDoAfterTerminate() throws InterruptedException { + void threadLocalsPresentInDoAfterTerminate() throws InterruptedException, TimeoutException { AtomicReference tlValue = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); @@ -202,7 +200,9 @@ void threadLocalsPresentInDoAfterTerminate() throws InterruptedException { // Need to synchronize, as the doAfterTerminate operator can race with the // assertion. First, blockLast receives the completion signal, and only then, // the callback is triggered. - latch.await(10, TimeUnit.MILLISECONDS); + if (!latch.await(100, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } assertThat(tlValue.get()).isEqualTo("present"); } @@ -430,12 +430,12 @@ void assertThreadLocalsPresent(Mono chain) { } void assertThatThreadLocalsPresentDirectCoreSubscribe( - CorePublisher source) throws InterruptedException { + CorePublisher source) throws InterruptedException, TimeoutException { assertThatThreadLocalsPresentDirectCoreSubscribe(source, () -> {}); } void assertThatThreadLocalsPresentDirectCoreSubscribe( - CorePublisher source, Runnable asyncAction) throws InterruptedException { + CorePublisher source, Runnable asyncAction) throws InterruptedException, TimeoutException { AtomicReference valueInOnNext = new AtomicReference<>(); AtomicReference valueInOnComplete = new AtomicReference<>(); AtomicReference valueInOnError = new AtomicReference<>(); @@ -453,7 +453,9 @@ void assertThatThreadLocalsPresentDirectCoreSubscribe( executorService.submit(asyncAction); - latch.await(10, TimeUnit.MILLISECONDS); + if (!latch.await(100, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } if (hadNext.get()) { assertThat(valueInOnNext.get()).isEqualTo("present"); @@ -471,12 +473,12 @@ void assertThatThreadLocalsPresentDirectCoreSubscribe( // are able to wrap the Subscriber and restore ThreadLocal values for the // signals received downstream. void assertThatThreadLocalsPresentDirectRawSubscribe( - Publisher source) throws InterruptedException { + Publisher source) throws InterruptedException, TimeoutException { assertThatThreadLocalsPresentDirectRawSubscribe(source, () -> {}); } void assertThatThreadLocalsPresentDirectRawSubscribe( - Publisher source, Runnable asyncAction) throws InterruptedException { + Publisher source, Runnable asyncAction) throws InterruptedException, TimeoutException { AtomicReference valueInOnNext = new AtomicReference<>(); AtomicReference valueInOnComplete = new AtomicReference<>(); AtomicReference valueInOnError = new AtomicReference<>(); @@ -494,7 +496,9 @@ void assertThatThreadLocalsPresentDirectRawSubscribe( executorService.submit(asyncAction); - latch.await(10, TimeUnit.MILLISECONDS); + if (!latch.await(100, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } if (hadNext.get()) { assertThat(valueInOnNext.get()).isEqualTo("present"); @@ -530,7 +534,7 @@ void internalFluxSubscribeNoFusion() { } @Test - void directFluxSubscribeAsCoreSubscriber() throws InterruptedException { + void directFluxSubscribeAsCoreSubscriber() throws InterruptedException, TimeoutException { AtomicReference valueInOnNext = new AtomicReference<>(); AtomicReference valueInOnComplete = new AtomicReference<>(); AtomicReference valueInOnError = new AtomicReference<>(); @@ -548,7 +552,9 @@ void directFluxSubscribeAsCoreSubscriber() throws InterruptedException { flux.subscribe(subscriberWithContext); - latch.await(10, TimeUnit.MILLISECONDS); + if (!latch.await(100, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } assertThat(error.get()).isNull(); assertThat(complete.get()).isTrue(); @@ -575,7 +581,7 @@ void internalMonoFlatMapSubscribe() { } @Test - void directMonoSubscribeAsCoreSubscriber() throws InterruptedException { + void directMonoSubscribeAsCoreSubscriber() throws InterruptedException, TimeoutException { AtomicReference valueInOnNext = new AtomicReference<>(); AtomicReference valueInOnComplete = new AtomicReference<>(); AtomicReference valueInOnError = new AtomicReference<>(); @@ -593,7 +599,9 @@ void directMonoSubscribeAsCoreSubscriber() throws InterruptedException { mono.subscribe(subscriberWithContext); - latch.await(10, TimeUnit.MILLISECONDS); + if (!latch.await(100, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("timed out"); + } assertThat(error.get()).isNull(); assertThat(complete.get()).isTrue(); @@ -735,7 +743,7 @@ void fluxFirstWithValueIterable() { @Test void fluxConcatArray() { assertThreadLocalsPresentInFlux(() -> - Flux.concat(Mono.empty(), threadSwitchingFlux())); + Flux.concat(Mono.empty(), threadSwitchingFlux())); } @Test @@ -751,9 +759,7 @@ void fluxGenerate() { sink.next("Hello"); // the generator is checked if any signal was delivered by the consumer // so we perform asynchronous completion only - executorService.submit(() -> { - sink.complete(); - }); + executorService.submit(sink::complete); })); } @@ -1043,7 +1049,7 @@ void parallelFluxFromThreadSwitchingFlux() { @Test void threadSwitchingParallelFluxSequential() { AtomicReference value = new AtomicReference<>(); - new ThreadSwitchingParallelFlux("Hello", executorService) + new ThreadSwitchingParallelFlux<>("Hello", executorService) .sequential() .doOnNext(i -> value.set(REF.get())) .contextWrite(Context.of(KEY, "present")) @@ -1055,21 +1061,21 @@ void threadSwitchingParallelFluxSequential() { @Test void threadSwitchingParallelFluxThen() { assertThreadLocalsPresentInMono(() -> - new ThreadSwitchingParallelFlux("Hello", executorService) + new ThreadSwitchingParallelFlux<>("Hello", executorService) .then()); } @Test void threadSwitchingParallelFluxOrdered() { assertThreadLocalsPresentInFlux(() -> - new ThreadSwitchingParallelFlux("Hello", executorService) + new ThreadSwitchingParallelFlux<>("Hello", executorService) .ordered(Comparator.naturalOrder())); } @Test void threadSwitchingParallelFluxReduce() { AtomicReference value = new AtomicReference<>(); - new ThreadSwitchingParallelFlux("Hello", executorService) + new ThreadSwitchingParallelFlux<>("Hello", executorService) .reduce((s1, s2) -> s2) .doOnNext(i -> value.set(REF.get())) .contextWrite(Context.of(KEY, "present")) @@ -1081,7 +1087,7 @@ void threadSwitchingParallelFluxReduce() { @Test void threadSwitchingParallelFluxReduceSeed() { AtomicReference value = new AtomicReference<>(); - new ThreadSwitchingParallelFlux("Hello", executorService) + new ThreadSwitchingParallelFlux<>("Hello", executorService) .reduce(ArrayList::new, (l, s) -> { value.set(REF.get()); l.add(s); @@ -1097,7 +1103,7 @@ void threadSwitchingParallelFluxReduceSeed() { @Test void threadSwitchingParallelFluxGroup() { AtomicReference value = new AtomicReference<>(); - new ThreadSwitchingParallelFlux("Hello", executorService) + new ThreadSwitchingParallelFlux<>("Hello", executorService) .groups() .doOnNext(i -> value.set(REF.get())) .flatMap(Flux::last) @@ -1110,7 +1116,7 @@ void threadSwitchingParallelFluxGroup() { @Test void threadSwitchingParallelFluxSort() { assertThreadLocalsPresentInFlux(() -> - new ThreadSwitchingParallelFlux("Hello", executorService) + new ThreadSwitchingParallelFlux<>("Hello", executorService) .sorted(Comparator.naturalOrder())); } @@ -1133,7 +1139,7 @@ void sink() throws InterruptedException, TimeoutException { executorService.submit(() -> sink.tryEmitValue(1)); - if (!latch.await(10, TimeUnit.MILLISECONDS)) { + if (!latch.await(100, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); } @@ -1168,7 +1174,7 @@ void sinksEmpty() throws InterruptedException, TimeoutException { executorService.submit(spec::tryEmitEmpty); - if (!latch.await(10, TimeUnit.MILLISECONDS)) { + if (!latch.await(100, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); } @@ -1176,7 +1182,7 @@ void sinksEmpty() throws InterruptedException, TimeoutException { } @Test - void sinksEmptyDirect() throws InterruptedException { + void sinksEmptyDirect() throws InterruptedException, TimeoutException { Sinks.Empty empty1 = Sinks.empty(); assertThatThreadLocalsPresentDirectCoreSubscribe(empty1.asMono(), empty1::tryEmitEmpty); @@ -1203,7 +1209,7 @@ void sinkManyUnicast() throws InterruptedException, TimeoutException { executorService.submit(() -> many.tryEmitNext("Hello")); - if (!latch.await(10, TimeUnit.MILLISECONDS)) { + if (!latch.await(100, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); } @@ -1211,7 +1217,7 @@ void sinkManyUnicast() throws InterruptedException, TimeoutException { } @Test - void sinkManyUnicastDirect() throws InterruptedException { + void sinkManyUnicastDirect() throws InterruptedException, TimeoutException { Sinks.Many many1 = Sinks.many().unicast() .onBackpressureBuffer(); @@ -1248,7 +1254,7 @@ void sinkManyUnicastNoBackpressure() throws InterruptedException, executorService.submit(() -> many.tryEmitNext("Hello")); - if (!latch.await(10, TimeUnit.MILLISECONDS)) { + if (!latch.await(100, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); } @@ -1274,7 +1280,7 @@ void sinkManyMulticastAllOrNothing() throws InterruptedException, executorService.submit(() -> many.tryEmitNext("Hello")); - if (!latch.await(10, TimeUnit.MILLISECONDS)) { + if (!latch.await(100, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); } @@ -1299,7 +1305,7 @@ void sinkManyMulticastBuffer() throws InterruptedException, TimeoutException { executorService.submit(() -> many.tryEmitNext("Hello")); - if (!latch.await(10, TimeUnit.MILLISECONDS)) { + if (!latch.await(100, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); } @@ -1324,7 +1330,7 @@ void sinkManyMulticastBestEffort() throws InterruptedException, TimeoutException executorService.submit(() -> many.tryEmitNext("Hello")); - if (!latch.await(10, TimeUnit.MILLISECONDS)) { + if (!latch.await(100, TimeUnit.MILLISECONDS)) { throw new TimeoutException("timed out"); } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java index 5fae7af964..90bb0ad95a 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingFlux.java @@ -48,7 +48,7 @@ public void run() { private void deliver() { if (done.compareAndSet(false, true)) { this.actual.onNext(this.item); - this.actual.onComplete(); + this.executorService.submit(this.actual::onComplete); } } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java index f0e2925213..9b52dcea52 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingMono.java @@ -48,7 +48,7 @@ public void run() { private void deliver() { if (done.compareAndSet(false, true)) { this.actual.onNext(this.item); - this.actual.onComplete(); + this.executorService.submit(this.actual::onComplete); } } diff --git a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java index 016824448f..d7557d1a8a 100644 --- a/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java +++ b/reactor-core/src/withMicrometerTest/java/reactor/core/publisher/ThreadSwitchingParallelFlux.java @@ -58,7 +58,7 @@ public void run() { private void deliver() { if (done.compareAndSet(false, true)) { this.actual[0].onNext(this.item); - this.actual[0].onComplete(); + this.executorService.submit(this.actual[0]::onComplete); } } From b27c77ba55ec51b2b6b069b6cfe827e5b0a2d6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Mon, 2 Oct 2023 09:51:31 +0200 Subject: [PATCH 23/26] Revert ParallelArraySource Operators.toFluxOrMono --- .../main/java/reactor/core/publisher/ParallelArraySource.java | 2 +- reactor-core/src/test/java/reactor/HooksTraceTest.java | 2 +- .../src/test/java/reactor/core/publisher/HooksTest.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java index ecfa0eaa83..ab40cc971d 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelArraySource.java @@ -49,7 +49,7 @@ public void subscribe(CoreSubscriber[] subscribers) { int n = subscribers.length; for (int i = 0; i < n; i++) { - Operators.toFluxOrMono(sources[i]).subscribe(subscribers[i]); + Flux.from(sources[i]).subscribe(subscribers[i]); } } diff --git a/reactor-core/src/test/java/reactor/HooksTraceTest.java b/reactor-core/src/test/java/reactor/HooksTraceTest.java index 26073c39ee..d3e5ec6e0a 100644 --- a/reactor-core/src/test/java/reactor/HooksTraceTest.java +++ b/reactor-core/src/test/java/reactor/HooksTraceTest.java @@ -376,7 +376,7 @@ public void onComplete() { StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) .log(null, Level.OFF) .log(null, Level.OFF)) - .expectNext(6, 6) + .expectNext(7, 7) .verifyComplete(); } diff --git a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java index 1c02879022..d1bad756bb 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java @@ -952,10 +952,10 @@ public void eachOperatorLiftsParallelFlux() { StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) .log() .log()) - .expectNext(501, 501) //5x lifts => just,ParallelFlux.from,log,log,back to sequential (shared) + .expectNext(601, 601) //5x lifts => just,Flux.from,ParallelFlux.from,log,log, back to sequential (shared) .verifyComplete(); - assertThat(liftCounter).hasValue(9); //9x total lifts: 2xjust,2xparallelFrom,4xlog,sequential + assertThat(liftCounter).hasValue(11); //11x total lifts: 2xjust, 2xfrom, 2xparallelFrom,4xlog,sequential } @Test From 2c452ee3831b4afb94248f04221b0e14c4b0da9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 3 Oct 2023 10:12:22 +0200 Subject: [PATCH 24/26] reverted hooks tests changes --- reactor-core/src/test/java/reactor/HooksTraceTest.java | 4 ++-- .../src/test/java/reactor/core/publisher/HooksTest.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/reactor-core/src/test/java/reactor/HooksTraceTest.java b/reactor-core/src/test/java/reactor/HooksTraceTest.java index d3e5ec6e0a..b759b909ed 100644 --- a/reactor-core/src/test/java/reactor/HooksTraceTest.java +++ b/reactor-core/src/test/java/reactor/HooksTraceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -376,7 +376,7 @@ public void onComplete() { StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) .log(null, Level.OFF) .log(null, Level.OFF)) - .expectNext(7, 7) + .expectNext(7, 7) //from now counts as an additional one .verifyComplete(); } diff --git a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java index d1bad756bb..84ad1d9b64 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/HooksTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -949,10 +949,10 @@ public void eachOperatorLiftsParallelFlux() { AtomicInteger liftCounter = new AtomicInteger(); Hooks.onEachOperator(Operators.lift((sc, sub) -> liftSubscriber(sc, sub, liftCounter))); - StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) + StepVerifier.create(ParallelFlux.from(Mono.just(1), Mono.just(1)) //internally converts each rail to a Flux using Flux.from .log() .log()) - .expectNext(601, 601) //5x lifts => just,Flux.from,ParallelFlux.from,log,log, back to sequential (shared) + .expectNext(601, 601) //6x lifts => just,Flux.from,ParallelFlux.from,log,log, back to sequential (shared) .verifyComplete(); assertThat(liftCounter).hasValue(11); //11x total lifts: 2xjust, 2xfrom, 2xparallelFrom,4xlog,sequential From 39b699adb9756a607a2eaa6ca13418f8bcc93677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 3 Oct 2023 10:27:02 +0200 Subject: [PATCH 25/26] new method excludes for japicmp --- reactor-core/build.gradle | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/reactor-core/build.gradle b/reactor-core/build.gradle index f4562cd122..18bd734c36 100644 --- a/reactor-core/build.gradle +++ b/reactor-core/build.gradle @@ -202,19 +202,23 @@ def japicmpReport = tasks.register('japicmpReport') { } doLast { def reportFile = file("${project.buildDir}/reports/japi.txt") - if (reportFile.exists()) { - println "\n **********************************" - println " * /!\\ API compatibility failures *" - println " **********************************" - println "Japicmp report was filtered and interpreted to find the following incompatibilities:" - reportFile.eachLine { - if (it.contains("*") && (!it.contains("***") || it.contains("****"))) - println "source incompatible change: $it" - else if (it.contains("!")) - println "binary incompatible change: $it" - } + if (reportFile.exists()) { + println "\n **********************************" + println " * /!\\ API compatibility failures *" + println " **********************************" + println "Japicmp report was filtered and interpreted to find the following incompatibilities:" + reportFile.eachLine { + if (it.contains("*") && (!it.contains("***") || it.contains("****"))) { + println "source incompatible change: $it" + } + else if (it.contains("!")) { + println "binary incompatible change: $it" + } } - else println "No incompatible change to report" + } + else { + println "No incompatible change to report" + } } } @@ -252,6 +256,9 @@ task japicmp(type: JapicmpTask) { classExcludes = [ ] methodExcludes = [ + "reactor.core.publisher.Operators#toFluxOrMono(org.reactivestreams.Publisher)", + "reactor.core.publisher.Operators#toFluxOrMono(org.reactivestreams.Publisher[])", + "reactor.core.publisher.ParallelFlux#from(reactor.core.publisher.ParallelFlux)" ] } From a2035985a146e36885e0e401fa85128dca249fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Tue, 3 Oct 2023 13:28:46 +0200 Subject: [PATCH 26/26] from(ParallelFlux) is package private --- .../src/main/java/reactor/core/publisher/ParallelFlux.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java index 725bb4ddc1..e1e63e2fbb 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/ParallelFlux.java @@ -76,7 +76,7 @@ */ public abstract class ParallelFlux implements CorePublisher { - public static ParallelFlux from(ParallelFlux source) { + static ParallelFlux from(ParallelFlux source) { if (ContextPropagationSupport.shouldWrapPublisher(source)) { return new ParallelFluxRestoringThreadLocals<>(source); }