Skip to content

Commit e48def2

Browse files
committed
Set readyToWrite flag after cached signals emitted
Issue: SPR-16555
1 parent 6d26e61 commit e48def2

File tree

1 file changed

+64
-32
lines changed

1 file changed

+64
-32
lines changed

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

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -75,6 +75,33 @@ public void subscribe(CoreSubscriber<? super Void> actual) {
7575
}
7676

7777

78+
private enum State {
79+
80+
/** No emissions from the upstream source yet */
81+
NEW,
82+
83+
/**
84+
* At least one signal of any kind has been received; we're ready to
85+
* call the write function and proceed with actual writing.
86+
*/
87+
FIRST_SIGNAL_RECEIVED,
88+
89+
/**
90+
* The write subscriber has subscribed and requested; we're going to
91+
* emit the cached signals.
92+
*/
93+
EMITTING_CACHED_SIGNALS,
94+
95+
/**
96+
* The write subscriber has subscribed, and cached signals have been
97+
* emitted to it; we're ready to switch to a simple pass-through mode
98+
* for all remaining signals.
99+
**/
100+
READY_TO_WRITE
101+
102+
}
103+
104+
78105
/**
79106
* A barrier inserted between the write source and the write subscriber
80107
* (i.e. the HTTP server adapter) that pre-fetches and waits for the first
@@ -99,27 +126,23 @@ private class WriteBarrier implements CoreSubscriber<T>, Subscription, Publisher
99126
@Nullable
100127
private Subscription subscription;
101128

102-
/**
103-
* We've at at least one emission, we've called the write function, the write
104-
* subscriber has subscribed and cached signals have been emitted to it.
105-
* We're now simply passing data through to the write subscriber.
106-
**/
107-
private boolean readyToWrite = false;
108-
109-
/** No emission from upstream yet */
110-
private boolean beforeFirstEmission = true;
111-
112-
/** Cached signal before readyToWrite */
129+
/** Cached data item before readyToWrite */
113130
@Nullable
114131
private T item;
115132

116-
/** Cached 1st/2nd signal before readyToWrite */
133+
/** Cached error signal before readyToWrite */
117134
@Nullable
118135
private Throwable error;
119136

120-
/** Cached 1st/2nd signal before readyToWrite */
137+
/** Cached onComplete signal before readyToWrite */
121138
private boolean completed = false;
122139

140+
/** Recursive demand while emitting cached signals */
141+
private long demandBeforeReadyToWrite;
142+
143+
/** Current state */
144+
private State state = State.NEW;
145+
123146
/** The actual writeSubscriber from the HTTP server adapter */
124147
@Nullable
125148
private Subscriber<? super T> writeSubscriber;
@@ -143,18 +166,18 @@ public final void onSubscribe(Subscription s) {
143166

144167
@Override
145168
public final void onNext(T item) {
146-
if (this.readyToWrite) {
169+
if (this.state == State.READY_TO_WRITE) {
147170
requiredWriteSubscriber().onNext(item);
148171
return;
149172
}
150173
//FIXME revisit in case of reentrant sync deadlock
151174
synchronized (this) {
152-
if (this.readyToWrite) {
175+
if (this.state == State.READY_TO_WRITE) {
153176
requiredWriteSubscriber().onNext(item);
154177
}
155-
else if (this.beforeFirstEmission) {
178+
else if (this.state == State.NEW) {
156179
this.item = item;
157-
this.beforeFirstEmission = false;
180+
this.state = State.FIRST_SIGNAL_RECEIVED;
158181
writeFunction.apply(this).subscribe(this.writeCompletionBarrier);
159182
}
160183
else {
@@ -173,16 +196,16 @@ private Subscriber<? super T> requiredWriteSubscriber() {
173196

174197
@Override
175198
public final void onError(Throwable ex) {
176-
if (this.readyToWrite) {
199+
if (this.state == State.READY_TO_WRITE) {
177200
requiredWriteSubscriber().onError(ex);
178201
return;
179202
}
180203
synchronized (this) {
181-
if (this.readyToWrite) {
204+
if (this.state == State.READY_TO_WRITE) {
182205
requiredWriteSubscriber().onError(ex);
183206
}
184-
else if (this.beforeFirstEmission) {
185-
this.beforeFirstEmission = false;
207+
else if (this.state == State.NEW) {
208+
this.state = State.FIRST_SIGNAL_RECEIVED;
186209
this.writeCompletionBarrier.onError(ex);
187210
}
188211
else {
@@ -193,17 +216,17 @@ else if (this.beforeFirstEmission) {
193216

194217
@Override
195218
public final void onComplete() {
196-
if (this.readyToWrite) {
219+
if (this.state == State.READY_TO_WRITE) {
197220
requiredWriteSubscriber().onComplete();
198221
return;
199222
}
200223
synchronized (this) {
201-
if (this.readyToWrite) {
224+
if (this.state == State.READY_TO_WRITE) {
202225
requiredWriteSubscriber().onComplete();
203226
}
204-
else if (this.beforeFirstEmission) {
227+
else if (this.state == State.NEW) {
205228
this.completed = true;
206-
this.beforeFirstEmission = false;
229+
this.state = State.FIRST_SIGNAL_RECEIVED;
207230
writeFunction.apply(this).subscribe(this.writeCompletionBarrier);
208231
}
209232
else {
@@ -226,19 +249,28 @@ public void request(long n) {
226249
if (s == null) {
227250
return;
228251
}
229-
if (this.readyToWrite) {
252+
if (this.state == State.READY_TO_WRITE) {
230253
s.request(n);
231254
return;
232255
}
233256
synchronized (this) {
234257
if (this.writeSubscriber != null) {
235-
this.readyToWrite = true;
236-
if (emitCachedSignals()) {
258+
if (this.state == State.EMITTING_CACHED_SIGNALS) {
259+
this.demandBeforeReadyToWrite = n;
237260
return;
238261
}
239-
n--;
240-
if (n == 0) {
241-
return;
262+
try {
263+
this.state = State.EMITTING_CACHED_SIGNALS;
264+
if (emitCachedSignals()) {
265+
return;
266+
}
267+
n = n + this.demandBeforeReadyToWrite - 1;
268+
if (n == 0) {
269+
return;
270+
}
271+
}
272+
finally {
273+
this.state = State.READY_TO_WRITE;
242274
}
243275
}
244276
}

0 commit comments

Comments
 (0)