Skip to content

Commit 3c2d186

Browse files
committed
Light refactoring/polish in reactive read/write bridge
Issue: SPR-16207
1 parent 2a481c5 commit 3c2d186

File tree

5 files changed

+275
-277
lines changed

5 files changed

+275
-277
lines changed

spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java

Lines changed: 77 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
5252

5353
private volatile long demand;
5454

55-
private volatile boolean publisherCompleted;
55+
private volatile boolean completionBeforeDemand;
5656

5757
@Nullable
58-
private volatile Throwable publisherError;
58+
private volatile Throwable errorBeforeDemand;
5959

6060
@SuppressWarnings("rawtypes")
6161
private static final AtomicLongFieldUpdater<AbstractListenerReadPublisher> DEMAND_FIELD_UPDATER =
@@ -76,31 +76,22 @@ public void subscribe(Subscriber<? super T> subscriber) {
7676
}
7777

7878

79-
// Listener delegation methods...
79+
// Methods for sub-classes to delegate to, when async I/O events occur...
8080

81-
/**
82-
* Listeners can call this to notify when reading is possible.
83-
*/
8481
public final void onDataAvailable() {
8582
if (this.logger.isTraceEnabled()) {
8683
this.logger.trace(this.state + " onDataAvailable");
8784
}
8885
this.state.get().onDataAvailable(this);
8986
}
9087

91-
/**
92-
* Listeners can call this to notify when all data has been read.
93-
*/
9488
public void onAllDataRead() {
9589
if (this.logger.isTraceEnabled()) {
9690
this.logger.trace(this.state + " onAllDataRead");
9791
}
9892
this.state.get().onAllDataRead(this);
9993
}
10094

101-
/**
102-
* Listeners can call this to notify when a read error has occurred.
103-
*/
10495
public final void onError(Throwable t) {
10596
if (this.logger.isTraceEnabled()) {
10697
this.logger.trace(this.state + " onError: " + t);
@@ -109,11 +100,17 @@ public final void onError(Throwable t) {
109100
}
110101

111102

103+
// Methods for sub-classes to implement...
104+
105+
/**
106+
* Check if data is available, calling {@link #onDataAvailable()} either
107+
* immediately or later when reading is possible.
108+
*/
112109
protected abstract void checkOnDataAvailable();
113110

114111
/**
115-
* Reads a data from the input, if possible.
116-
* @return the data that was read; or {@code null}
112+
* Read once from the input, if possible.
113+
* @return the item that was read; or {@code null}
117114
*/
118115
@Nullable
119116
protected abstract T read() throws IOException;
@@ -125,14 +122,17 @@ protected void suspendReading() {
125122
}
126123

127124

125+
// Private methods for use in State...
126+
128127
/**
129-
* Read and publish data from the input. Continue till there is no more
130-
* demand or there is no more data to be read.
131-
* @return {@code true} if there is more demand; {@code false} otherwise
128+
* Read and publish data one at a time until there is no more data, no more
129+
* demand, or perhaps we completed in the mean time.
130+
* @return {@code true} if there is more demand; {@code false} if there is
131+
* no more demand or we have completed.
132132
*/
133133
private boolean readAndPublish() throws IOException {
134134
long r;
135-
while ((r = demand) > 0 && !publisherCompleted) {
135+
while ((r = this.demand) > 0 && !this.state.get().equals(State.COMPLETED)) {
136136
T data = read();
137137
if (data != null) {
138138
if (r != Long.MAX_VALUE) {
@@ -152,96 +152,95 @@ private boolean changeState(State oldState, State newState) {
152152
return this.state.compareAndSet(oldState, newState);
153153
}
154154

155+
private Subscription createSubscription() {
156+
return new ReadSubscription();
157+
}
155158

156-
private static final class ReadSubscription implements Subscription {
157159

158-
private final AbstractListenerReadPublisher<?> publisher;
160+
/**
161+
* Subscription that delegates signals to State.
162+
*/
163+
private final class ReadSubscription implements Subscription {
159164

160-
public ReadSubscription(AbstractListenerReadPublisher<?> publisher) {
161-
this.publisher = publisher;
162-
}
163165

164166
@Override
165167
public final void request(long n) {
166-
if (this.publisher.logger.isTraceEnabled()) {
167-
this.publisher.logger.trace(state() + " request: " + n);
168+
if (logger.isTraceEnabled()) {
169+
logger.trace(state + " request: " + n);
168170
}
169-
state().request(this.publisher, n);
171+
state.get().request(AbstractListenerReadPublisher.this, n);
170172
}
171173

172174
@Override
173175
public final void cancel() {
174-
if (this.publisher.logger.isTraceEnabled()) {
175-
this.publisher.logger.trace(state() + " cancel");
176+
if (logger.isTraceEnabled()) {
177+
logger.trace(state + " cancel");
176178
}
177-
state().cancel(this.publisher);
178-
}
179-
180-
private State state() {
181-
return this.publisher.state.get();
179+
state.get().cancel(AbstractListenerReadPublisher.this);
182180
}
183181
}
184182

185183

186184
/**
187-
* Represents a state for the {@link Publisher} to be in. The following figure
188-
* indicate the four different states that exist, and the relationships between them.
189-
*
190-
* <pre>
191-
* UNSUBSCRIBED
192-
* |
193-
* v
194-
* NO_DEMAND -------------------> DEMAND
195-
* | ^ ^ |
196-
* | | | |
197-
* | --------- READING <----- |
198-
* | | |
199-
* | v |
200-
* ------------> COMPLETED <---------
185+
* Represents a state for the {@link Publisher} to be in.
186+
* <p><pre>
187+
* UNSUBSCRIBED
188+
* |
189+
* v
190+
* SUBSCRIBING
191+
* |
192+
* v
193+
* +---- NO_DEMAND ---------------> DEMAND ---+
194+
* | ^ ^ |
195+
* | | | |
196+
* | +------- READING <--------+ |
197+
* | | |
198+
* | v |
199+
* +--------------> COMPLETED <---------------+
201200
* </pre>
202-
* Refer to the individual states for more information.
203201
*/
204202
private enum State {
205203

206-
/**
207-
* The initial unsubscribed state. Will respond to {@link
208-
* #subscribe(AbstractListenerReadPublisher, Subscriber)} by
209-
* changing state to {@link #NO_DEMAND}.
210-
*/
211204
UNSUBSCRIBED {
212205
@Override
213206
<T> void subscribe(AbstractListenerReadPublisher<T> publisher, Subscriber<? super T> subscriber) {
214207
Assert.notNull(publisher, "Publisher must not be null");
215208
Assert.notNull(subscriber, "Subscriber must not be null");
216209
if (publisher.changeState(this, SUBSCRIBING)) {
217-
Subscription subscription = new ReadSubscription(publisher);
210+
Subscription subscription = publisher.createSubscription();
218211
publisher.subscriber = subscriber;
219212
subscriber.onSubscribe(subscription);
220213
publisher.changeState(SUBSCRIBING, NO_DEMAND);
221-
if (publisher.publisherCompleted) {
222-
publisher.onAllDataRead();
214+
// Now safe to check "beforeDemand" flags, they won't change once in NO_DEMAND
215+
if (publisher.completionBeforeDemand) {
216+
publisher.state.get().onAllDataRead(publisher);
223217
}
224-
Throwable publisherError = publisher.publisherError;
225-
if (publisherError != null) {
226-
publisher.onError(publisherError);
218+
Throwable ex = publisher.errorBeforeDemand;
219+
if (ex != null) {
220+
publisher.state.get().onError(publisher, ex);
227221
}
228222
}
229223
else {
230-
throw new IllegalStateException(toString());
224+
throw new IllegalStateException("Failed to transition to SUBSCRIBING, " +
225+
"subscriber: " + subscriber);
231226
}
232227
}
233228

234229
@Override
235230
<T> void onAllDataRead(AbstractListenerReadPublisher<T> publisher) {
236-
publisher.publisherCompleted = true;
231+
publisher.completionBeforeDemand = true;
237232
}
238233

239234
@Override
240-
<T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable t) {
241-
publisher.publisherError = t;
235+
<T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable ex) {
236+
publisher.errorBeforeDemand = ex;
242237
}
243238
},
244239

240+
/**
241+
* Very brief state where we know we have a Subscriber but must not
242+
* send onComplete and onError until we after onSubscribe.
243+
*/
245244
SUBSCRIBING {
246245
<T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
247246
if (Operators.validate(n)) {
@@ -254,21 +253,15 @@ <T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
254253

255254
@Override
256255
<T> void onAllDataRead(AbstractListenerReadPublisher<T> publisher) {
257-
publisher.publisherCompleted = true;
256+
publisher.completionBeforeDemand = true;
258257
}
259258

260259
@Override
261-
<T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable t) {
262-
publisher.publisherError = t;
260+
<T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable ex) {
261+
publisher.errorBeforeDemand = ex;
263262
}
264263
},
265264

266-
/**
267-
* State that gets entered when there is no demand. Responds to {@link
268-
* #request(AbstractListenerReadPublisher, long)} by increasing the demand,
269-
* changing state to {@link #DEMAND} and will check whether there
270-
* is data available for reading.
271-
*/
272265
NO_DEMAND {
273266
@Override
274267
<T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
@@ -277,21 +270,17 @@ <T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
277270
if (publisher.changeState(this, DEMAND)) {
278271
publisher.checkOnDataAvailable();
279272
}
273+
// or else we completed at the same time...
280274
}
281275
}
282276
},
283277

284-
/**
285-
* State that gets entered when there is demand. Responds to
286-
* {@link #onDataAvailable(AbstractListenerReadPublisher)} by
287-
* reading the available data. The state will be changed to
288-
* {@link #NO_DEMAND} if there is no demand.
289-
*/
290278
DEMAND {
291279
@Override
292280
<T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
293281
if (Operators.validate(n)) {
294282
Operators.addCap(DEMAND_FIELD_UPDATER, publisher, n);
283+
// Did a concurrent read transition to NO_DEMAND just before us?
295284
if (publisher.changeState(NO_DEMAND, DEMAND)) {
296285
publisher.checkOnDataAvailable();
297286
}
@@ -304,32 +293,38 @@ <T> void onDataAvailable(AbstractListenerReadPublisher<T> publisher) {
304293
if (!read(publisher)) {
305294
return;
306295
}
296+
// Maybe demand arrived between readAndPublish and READING->NO_DEMAND?
307297
long r = publisher.demand;
308298
if (r == 0 || publisher.changeState(NO_DEMAND, this)) {
309299
break;
310300
}
311301
}
312302
}
313303

304+
/**
305+
* @return whether to exit the read loop; false means stop trying
306+
* to read, true means check demand one more time.
307+
*/
314308
<T> boolean read(AbstractListenerReadPublisher<T> publisher) {
315309
if (publisher.changeState(this, READING)) {
316310
try {
317311
boolean demandAvailable = publisher.readAndPublish();
318312
if (demandAvailable) {
319313
if (publisher.changeState(READING, DEMAND)) {
320314
publisher.checkOnDataAvailable();
321-
return false;
322315
}
323316
}
324317
else if (publisher.changeState(READING, NO_DEMAND)) {
325318
publisher.suspendReading();
319+
return true;
326320
}
327321
}
328322
catch (IOException ex) {
329323
publisher.onError(ex);
330324
}
331-
return true;
332325
}
326+
// Either competing onDataAvailable calls (via request or container callback)
327+
// Or a concurrent completion
333328
return false;
334329
}
335330
},
@@ -339,16 +334,14 @@ else if (publisher.changeState(READING, NO_DEMAND)) {
339334
<T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
340335
if (Operators.validate(n)) {
341336
Operators.addCap(DEMAND_FIELD_UPDATER, publisher, n);
337+
// Did a concurrent read transition to NO_DEMAND just before us?
342338
if (publisher.changeState(NO_DEMAND, DEMAND)) {
343339
publisher.checkOnDataAvailable();
344340
}
345341
}
346342
}
347343
},
348344

349-
/**
350-
* The terminal completed state. Does not respond to any events.
351-
*/
352345
COMPLETED {
353346
@Override
354347
<T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
@@ -377,10 +370,7 @@ <T> void request(AbstractListenerReadPublisher<T> publisher, long n) {
377370
}
378371

379372
<T> void cancel(AbstractListenerReadPublisher<T> publisher) {
380-
if (publisher.changeState(this, COMPLETED)) {
381-
publisher.publisherCompleted = true;
382-
}
383-
else {
373+
if (!publisher.changeState(this, COMPLETED)) {
384374
publisher.state.get().cancel(publisher);
385375
}
386376
}
@@ -391,7 +381,6 @@ <T> void onDataAvailable(AbstractListenerReadPublisher<T> publisher) {
391381

392382
<T> void onAllDataRead(AbstractListenerReadPublisher<T> publisher) {
393383
if (publisher.changeState(this, COMPLETED)) {
394-
publisher.publisherCompleted = true;
395384
if (publisher.subscriber != null) {
396385
publisher.subscriber.onComplete();
397386
}

0 commit comments

Comments
 (0)