Skip to content

Commit f0851de

Browse files
concurrent-api: make context capture more generic (#3183)
Motivation: Our context capture and restore process is centered upon the ContextMap type. The AsyncContext type is only one specific type of context that applications may want to capture and restore across async boundaries. Other examples include the OTEL context, grpc context, and really any random ThreadLocal you want to preserve across async boundaries. They also don't necessarily fit the same pattern as a AsycContext. Modifications: Further abstract the context type to a CapturedContext. The key method is `Scope attachContext()` which has the job of attaching whatever context it knows about. The key benefit of this abstraction is that it is very composable. We can add extensions that are simple proxy wrappers that capture the context and restore their own, while delegating to an underlying version that is ultimately rooted in what we know as the AsyncContext. Result: More composable context capture and restore.
1 parent b625ab7 commit f0851de

File tree

95 files changed

+1010
-699
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+1010
-699
lines changed

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractAsynchronousCompletableOperator.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package io.servicetalk.concurrent.api;
1717

18-
import io.servicetalk.context.api.ContextMap;
19-
2018
import static java.util.Objects.requireNonNull;
2119

2220
/**
@@ -38,13 +36,13 @@ abstract class AbstractAsynchronousCompletableOperator extends AbstractNoHandleS
3836

3937
@Override
4038
final void handleSubscribe(Subscriber subscriber,
41-
ContextMap contextMap, AsyncContextProvider contextProvider) {
39+
CapturedContext capturedContext, AsyncContextProvider contextProvider) {
4240
// The AsyncContext needs to be preserved when ever we interact with the original Subscriber, so we wrap it here
4341
// with the original contextMap. Otherwise some other context may leak into this subscriber chain from the other
4442
// side of the asynchronous boundary.
4543
final Subscriber operatorSubscriber =
46-
contextProvider.wrapCompletableSubscriberAndCancellable(subscriber, contextMap);
44+
contextProvider.wrapCompletableSubscriberAndCancellable(subscriber, capturedContext);
4745
final Subscriber upstreamSubscriber = apply(operatorSubscriber);
48-
original.delegateSubscribe(upstreamSubscriber, contextMap, contextProvider);
46+
original.delegateSubscribe(upstreamSubscriber, capturedContext, contextProvider);
4947
}
5048
}

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractAsynchronousPublisherOperator.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package io.servicetalk.concurrent.api;
1717

18-
import io.servicetalk.context.api.ContextMap;
19-
2018
import static java.util.Objects.requireNonNull;
2119

2220
/**
@@ -41,13 +39,13 @@ abstract class AbstractAsynchronousPublisherOperator<T, R> extends AbstractNoHan
4139

4240
@Override
4341
final void handleSubscribe(Subscriber<? super R> subscriber,
44-
ContextMap contextMap, AsyncContextProvider contextProvider) {
42+
CapturedContext capturedContext, AsyncContextProvider contextProvider) {
4543
// The AsyncContext needs to be preserved when ever we interact with the original Subscriber, so we wrap it here
4644
// with the original contextMap. Otherwise some other context may leak into this subscriber chain from the other
4745
// side of the asynchronous boundary.
4846
final Subscriber<? super R> operatorSubscriber =
49-
contextProvider.wrapPublisherSubscriberAndSubscription(subscriber, contextMap);
47+
contextProvider.wrapPublisherSubscriberAndSubscription(subscriber, capturedContext);
5048
final Subscriber<? super T> upstreamSubscriber = apply(operatorSubscriber);
51-
original.delegateSubscribe(upstreamSubscriber, contextMap, contextProvider);
49+
original.delegateSubscribe(upstreamSubscriber, capturedContext, contextProvider);
5250
}
5351
}

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractAsynchronousSingleOperator.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package io.servicetalk.concurrent.api;
1717

18-
import io.servicetalk.context.api.ContextMap;
19-
2018
import static java.util.Objects.requireNonNull;
2119

2220
/**
@@ -41,13 +39,13 @@ abstract class AbstractAsynchronousSingleOperator<T, R> extends AbstractNoHandle
4139

4240
@Override
4341
final void handleSubscribe(Subscriber<? super R> subscriber,
44-
ContextMap contextMap, AsyncContextProvider contextProvider) {
42+
CapturedContext capturedContext, AsyncContextProvider contextProvider) {
4543
// The AsyncContext needs to be preserved when ever we interact with the original Subscriber, so we wrap it here
4644
// with the original contextMap. Otherwise some other context may leak into this subscriber chain from the other
4745
// side of the asynchronous boundary.
4846
final Subscriber<? super R> operatorSubscriber =
49-
contextProvider.wrapSingleSubscriberAndCancellable(subscriber, contextMap);
47+
contextProvider.wrapSingleSubscriberAndCancellable(subscriber, capturedContext);
5048
final Subscriber<? super T> upstreamSubscriber = apply(operatorSubscriber);
51-
original.delegateSubscribe(upstreamSubscriber, contextMap, contextProvider);
49+
original.delegateSubscribe(upstreamSubscriber, capturedContext, contextProvider);
5250
}
5351
}

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractCompletableAndSingleConcatenated.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,21 @@
1818
import io.servicetalk.concurrent.Cancellable;
1919
import io.servicetalk.concurrent.CompletableSource;
2020
import io.servicetalk.concurrent.internal.SequentialCancellable;
21-
import io.servicetalk.context.api.ContextMap;
2221

2322
import javax.annotation.Nullable;
2423

2524
abstract class AbstractCompletableAndSingleConcatenated<T> extends AbstractNoHandleSubscribeSingle<T> {
2625

2726
@Override
2827
protected void handleSubscribe(final Subscriber<? super T> subscriber,
29-
final ContextMap contextMap, final AsyncContextProvider contextProvider) {
30-
final Subscriber<? super T> wrappedSubscriber = contextProvider.wrapSingleSubscriber(subscriber, contextMap);
31-
delegateSubscribeToOriginal(wrappedSubscriber, contextMap, contextProvider);
28+
final CapturedContext capturedContext, final AsyncContextProvider contextProvider) {
29+
final Subscriber<? super T> wrappedSubscriber =
30+
contextProvider.wrapSingleSubscriber(subscriber, capturedContext);
31+
delegateSubscribeToOriginal(wrappedSubscriber, capturedContext, contextProvider);
3232
}
3333

3434
abstract void delegateSubscribeToOriginal(Subscriber<? super T> offloadSubscriber,
35-
ContextMap contextMap, AsyncContextProvider contextProvider);
35+
CapturedContext capturedContext, AsyncContextProvider contextProvider);
3636

3737
abstract static class AbstractConcatWithSubscriber<T> implements Subscriber<T>, CompletableSource.Subscriber {
3838

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractMergeCompletableOperator.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package io.servicetalk.concurrent.api;
1717

18-
import io.servicetalk.context.api.ContextMap;
19-
2018
import static java.util.Objects.requireNonNull;
2119

2220
abstract class AbstractMergeCompletableOperator<T extends CompletableMergeSubscriber>
@@ -30,15 +28,15 @@ abstract class AbstractMergeCompletableOperator<T extends CompletableMergeSubscr
3028
}
3129

3230
@Override
33-
final void handleSubscribe(Subscriber subscriber, ContextMap contextMap,
31+
final void handleSubscribe(Subscriber subscriber, CapturedContext capturedContext,
3432
AsyncContextProvider contextProvider) {
3533
// The AsyncContext needs to be preserved when ever we interact with the original Subscriber, so we wrap it here
3634
// with the original contextMap. Otherwise some other context may leak into this subscriber chain from the other
3735
// side of the asynchronous boundary.
3836
final Subscriber operatorSubscriber =
39-
contextProvider.wrapCompletableSubscriberAndCancellable(subscriber, contextMap);
37+
contextProvider.wrapCompletableSubscriberAndCancellable(subscriber, capturedContext);
4038
T mergeSubscriber = apply(operatorSubscriber);
41-
original.delegateSubscribe(mergeSubscriber, contextMap, contextProvider);
39+
original.delegateSubscribe(mergeSubscriber, capturedContext, contextProvider);
4240
doMerge(mergeSubscriber);
4341
}
4442

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractPubToCompletable.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import io.servicetalk.concurrent.PublisherSource;
1919
import io.servicetalk.concurrent.internal.DelayedCancellable;
20-
import io.servicetalk.context.api.ContextMap;
2120

2221
/**
2322
* A {@link Completable} created from a {@link Publisher}.
@@ -40,12 +39,12 @@ abstract class AbstractPubToCompletable<T> extends AbstractNoHandleSubscribeComp
4039

4140
@Override
4241
final void handleSubscribe(final Subscriber subscriber,
43-
final ContextMap contextMap, final AsyncContextProvider contextProvider) {
42+
final CapturedContext capturedContext, final AsyncContextProvider contextProvider) {
4443
// We are now subscribing to the original Publisher chain for the first time, wrap Subscription to preserve the
4544
// context.
4645
PublisherSource.Subscriber<? super T> wrappedSubscriber =
47-
contextProvider.wrapSubscription(newSubscriber(subscriber), contextMap);
48-
source.delegateSubscribe(wrappedSubscriber, contextMap, contextProvider);
46+
contextProvider.wrapSubscription(newSubscriber(subscriber), capturedContext);
47+
source.delegateSubscribe(wrappedSubscriber, capturedContext, contextProvider);
4948
}
5049

5150
abstract static class AbstractPubToCompletableSubscriber<T> extends DelayedCancellable

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractPubToSingle.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import io.servicetalk.concurrent.PublisherSource;
1919
import io.servicetalk.concurrent.PublisherSource.Subscription;
20-
import io.servicetalk.context.api.ContextMap;
2120

2221
import org.slf4j.Logger;
2322
import org.slf4j.LoggerFactory;
@@ -38,12 +37,12 @@ abstract class AbstractPubToSingle<T> extends AbstractNoHandleSubscribeSingle<T>
3837

3938
@Override
4039
final void handleSubscribe(final Subscriber<? super T> subscriber,
41-
final ContextMap contextMap, final AsyncContextProvider contextProvider) {
40+
final CapturedContext capturedContext, final AsyncContextProvider contextProvider) {
4241
// We are now subscribing to the original Publisher chain for the first time, wrap Subscription to preserve the
4342
// context.
4443
PublisherSource.Subscriber<? super T> wrappedSubscription =
45-
contextProvider.wrapSubscription(newSubscriber(subscriber), contextMap);
46-
source.delegateSubscribe(wrappedSubscription, contextMap, contextProvider);
44+
contextProvider.wrapSubscription(newSubscriber(subscriber), capturedContext);
45+
source.delegateSubscribe(wrappedSubscription, capturedContext, contextProvider);
4746
}
4847

4948
abstract PublisherSource.Subscriber<T> newSubscriber(Subscriber<? super T> original);

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractPublisherGroupBy.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import io.servicetalk.concurrent.PublisherSource;
1919
import io.servicetalk.concurrent.internal.ConcurrentSubscription;
2020
import io.servicetalk.concurrent.internal.DuplicateSubscribeException;
21-
import io.servicetalk.context.api.ContextMap;
2221

2322
import java.util.Map;
2423
import java.util.concurrent.ConcurrentHashMap;
@@ -49,21 +48,21 @@ abstract class AbstractPublisherGroupBy<Key, T> extends AbstractNoHandleSubscrib
4948
abstract static class AbstractGroupBySubscriber<Key, T> implements Subscriber<T> {
5049
private boolean rootCancelled;
5150
private final int queueLimit;
52-
private final ContextMap contextMap;
51+
private final CapturedContext capturedContext;
5352
private final AsyncContextProvider contextProvider;
5453
private final Map<Key, GroupMulticastSubscriber<Key, T>> groups;
5554
private final GroupMulticastSubscriber<String, GroupedPublisher<Key, T>> target;
5655
@Nullable
5756
private Subscription subscription;
5857

5958
AbstractGroupBySubscriber(final Subscriber<? super GroupedPublisher<Key, T>> target, final int queueLimit,
60-
final int initialCapacityForGroups, final ContextMap contextMap,
59+
final int initialCapacityForGroups, final CapturedContext capturedContext,
6160
final AsyncContextProvider contextProvider) {
6261
this.queueLimit = queueLimit;
63-
this.contextMap = contextMap;
62+
this.capturedContext = capturedContext;
6463
this.contextProvider = contextProvider;
6564
this.target = new GroupMulticastSubscriber<>(this, "root");
66-
this.target.subscriber(target, false, contextMap, contextProvider);
65+
this.target.subscriber(target, false, capturedContext, contextProvider);
6766
groups = new ConcurrentHashMap<>(initialCapacityForGroups);
6867
}
6968

@@ -98,7 +97,7 @@ final void onNext(Key key, @Nullable T t) {
9897
} else {
9998
groupSub = new GroupMulticastSubscriber<>(this, key);
10099
GroupedPublisher<Key, T> groupedPublisher = new DefaultGroupedPublisher<>(key, groupSub,
101-
contextMap, contextProvider);
100+
capturedContext, contextProvider);
102101
final GroupMulticastSubscriber<Key, T> oldVal = groups.put(key, groupSub);
103102
assert oldVal == null; // concurrent onNext not allowed, collision not expected.
104103
groupSub.onNext(t); // deliver to group first to avoid re-entry creating ordering issues.
@@ -165,16 +164,16 @@ public String toString() {
165164
}
166165

167166
void subscriber(final Subscriber<? super T> subscriber, final boolean triggerOnSubscribe,
168-
final ContextMap contextMap, final AsyncContextProvider contextProvider) {
167+
final CapturedContext capturedContext, final AsyncContextProvider contextProvider) {
169168
// The root Subscriber's downstream subscriber is set internally, so no need for atomic operation to filter
170169
// duplicates.
171170
if (!triggerOnSubscribe) {
172171
assert this.subscriber == null && ctxSubscriber == null;
173172
this.subscriber = subscriber;
174-
ctxSubscriber = contextProvider.wrapPublisherSubscriber(subscriber, contextMap);
173+
ctxSubscriber = contextProvider.wrapPublisherSubscriber(subscriber, capturedContext);
175174
} else if (subscriberStateUpdater.compareAndSet(this, 0, 1)) {
176175
this.subscriber = subscriber;
177-
ctxSubscriber = contextProvider.wrapPublisherSubscriber(subscriber, contextMap);
176+
ctxSubscriber = contextProvider.wrapPublisherSubscriber(subscriber, capturedContext);
178177
triggerOnSubscribe();
179178
} else {
180179
// this.subscriber may be null (we set the subscriber variable after subscriberStateUpdater),
@@ -215,14 +214,14 @@ int outstandingDemandLimit() {
215214
private static final class DefaultGroupedPublisher<Key, T> extends GroupedPublisher<Key, T>
216215
implements PublisherSource<T> {
217216
private final GroupMulticastSubscriber<Key, T> groupSink;
218-
private final ContextMap contextMap;
217+
private final CapturedContext capturedContext;
219218
private final AsyncContextProvider contextProvider;
220219

221220
DefaultGroupedPublisher(final Key key, final GroupMulticastSubscriber<Key, T> groupSink,
222-
final ContextMap contextMap, final AsyncContextProvider contextProvider) {
221+
final CapturedContext capturedContext, final AsyncContextProvider contextProvider) {
223222
super(key);
224223
this.groupSink = groupSink;
225-
this.contextMap = contextMap;
224+
this.capturedContext = capturedContext;
226225
this.contextProvider = contextProvider;
227226
}
228227

@@ -233,7 +232,7 @@ public void subscribe(final Subscriber<? super T> subscriber) {
233232

234233
@Override
235234
protected void handleSubscribe(Subscriber<? super T> sub) {
236-
groupSink.subscriber(sub, true, contextMap, contextProvider);
235+
groupSink.subscriber(sub, true, capturedContext, contextProvider);
237236
}
238237
}
239238
}

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractSynchronousCompletable.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package io.servicetalk.concurrent.api;
1717

18-
import io.servicetalk.context.api.ContextMap;
19-
2018
/**
2119
* Base class for all {@link Completable}s that are created with already realized result and does not generate result
2220
* asynchronously.
@@ -25,10 +23,10 @@ abstract class AbstractSynchronousCompletable extends AbstractNoHandleSubscribeC
2523

2624
@Override
2725
final void handleSubscribe(Subscriber subscriber,
28-
ContextMap contextMap, AsyncContextProvider contextProvider) {
26+
CapturedContext capturedContext, AsyncContextProvider contextProvider) {
2927
// We need to wrap the Subscriber to save/restore the AsyncContext on each operation or else the AsyncContext
3028
// may leak from another thread.
31-
doSubscribe(contextProvider.wrapCompletableSubscriber(subscriber, contextMap));
29+
doSubscribe(contextProvider.wrapCompletableSubscriber(subscriber, capturedContext));
3230
}
3331

3432
/**

servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractSynchronousCompletableOperator.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package io.servicetalk.concurrent.api;
1717

18-
import io.servicetalk.context.api.ContextMap;
19-
2018
import static java.util.Objects.requireNonNull;
2119

2220
/**
@@ -41,7 +39,7 @@ abstract class AbstractSynchronousCompletableOperator extends AbstractNoHandleSu
4139

4240
@Override
4341
final void handleSubscribe(Subscriber subscriber,
44-
ContextMap contextMap, AsyncContextProvider contextProvider) {
45-
original.delegateSubscribe(apply(subscriber), contextMap, contextProvider);
42+
CapturedContext capturedContext, AsyncContextProvider contextProvider) {
43+
original.delegateSubscribe(apply(subscriber), capturedContext, contextProvider);
4644
}
4745
}

0 commit comments

Comments
 (0)