Skip to content

Commit c4fa823

Browse files
authored
Publisher#flatMapSingle make subscription non-volatile (#1025)
Motivation: Publisher#flatMapSingle has the Subscription from onSubscribe saved to a volatile variable. This was due to the Subscription being in the context of the mapped Single's Subscriber termination callbacks and ambiguity in the Reactive Streams specification about visibility related to Publisher#subscribe(..) and Subscriber state. However recent [discussions](reactive-streams/reactive-streams-jvm#486) have provided more insight that the Publisher must provide visibility, and therefore the volatile state is not necessary. Modifications: - Publisher#flatMapSingle subscription member variable can be non-volatile Result: Less volatile state and more clear expecations.
1 parent 29c4f7d commit c4fa823

File tree

1 file changed

+8
-12
lines changed

1 file changed

+8
-12
lines changed

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

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,14 @@ private static final class FlatMapSubscriber<T, R> implements Subscriber<T>, Sub
110110
private volatile int active; // Number of currently active Singles.
111111
@SuppressWarnings("unused")
112112
@Nullable
113-
private volatile Subscription subscription;
114-
@SuppressWarnings("unused")
115-
@Nullable
116113
private volatile TerminalNotification terminalNotification;
117114
/**
118115
* This variable is only accessed within the "emitting lock" so we rely upon this to provide visibility to
119116
* other threads.
120117
*/
121118
private boolean targetTerminated;
119+
@Nullable
120+
private Subscription subscription;
122121

123122
private final Queue<Object> pending;
124123
private final DynamicCompositeCancellable cancellable = new MapDynamicCompositeCancellable();
@@ -139,18 +138,17 @@ private static final class FlatMapSubscriber<T, R> implements Subscriber<T>, Sub
139138

140139
@Override
141140
public void request(long n) {
142-
final Subscription s = subscription;
143-
assert s != null;
141+
assert subscription != null;
144142
if (!isRequestNValid(n)) {
145-
s.request(n);
143+
subscription.request(n);
146144
return;
147145
}
148146

149147
requestedUpdater.accumulateAndGet(this, n, FlowControlUtils::addWithOverflowProtection);
150148
int actualSourceRequestN = calculateSourceRequested(requestedUpdater, sourceRequestedUpdater,
151149
sourceEmittedUpdater, source.maxConcurrency, this);
152150
if (actualSourceRequestN != 0) {
153-
s.request(actualSourceRequestN);
151+
subscription.request(actualSourceRequestN);
154152
}
155153
}
156154

@@ -218,9 +216,6 @@ private boolean onError0(Throwable throwable, boolean overrideComplete,
218216
}
219217

220218
private void enqueueAndDrain(Object item) {
221-
Subscription s = subscription;
222-
assert s != null;
223-
224219
if (!pending.offer(item)) {
225220
QueueFullException exception = new QueueFullException("pending");
226221
if (item instanceof TerminalNotification) {
@@ -230,10 +225,11 @@ private void enqueueAndDrain(Object item) {
230225
onError0(exception, true, true);
231226
}
232227
}
233-
drainPending(s);
228+
drainPending();
234229
}
235230

236-
private void drainPending(Subscription subscription) {
231+
private void drainPending() {
232+
assert subscription != null;
237233
long drainedCount = drainSingleConsumerQueue(pending, this::sendToTarget, emittingUpdater, this);
238234
if (drainedCount != 0) {
239235
// We ignore overflow here because once we get to this extreme, we won't be able to account for more

0 commit comments

Comments
 (0)