Skip to content

Commit 5a90a88

Browse files
committed
Fix: flake
1 parent f277536 commit 5a90a88

1 file changed

Lines changed: 102 additions & 59 deletions

File tree

servlet/src/main/java/io/grpc/servlet/AsyncServletOutputStreamWriter.java

Lines changed: 102 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -173,19 +173,30 @@ void complete() {
173173
/** Called from the container thread {@link javax.servlet.WriteListener#onWritePossible()}. */
174174
void onWritePossible() throws IOException {
175175
log.finest("onWritePossible: ENTRY. The servlet output stream becomes ready");
176-
if (writeState.get().readyAndDrained) {
177-
assureReadyAndDrainedTurnsFalse();
176+
if (writeState.get().state == WriteState.WRITING) {
177+
if (!assureWritingStateTurnsStateOtherThanWriting()) {
178+
return;
179+
}
180+
}
181+
if (writeState.get().state == WriteState.READY_AND_DRAINED) {
182+
return;
178183
}
179184
while (isReady.getAsBoolean()) {
180185
WriteState curState = writeState.get();
186+
if (curState.state == WriteState.WRITING) {
187+
if (!assureWritingStateTurnsStateOtherThanWriting()) {
188+
return;
189+
}
190+
continue;
191+
}
181192

182193
ActionItem actionItem = writeChain.poll();
183194
if (actionItem != null) {
184195
actionItem.run();
185196
continue;
186197
}
187198

188-
if (writeState.compareAndSet(curState, curState.withReadyAndDrained(true))) {
199+
if (writeState.compareAndSet(curState, new WriteState(WriteState.READY_AND_DRAINED))) {
189200
// state has not changed since.
190201
log.finest(
191202
"onWritePossible: EXIT. All data available now is sent out and the servlet output"
@@ -198,15 +209,20 @@ void onWritePossible() throws IOException {
198209
log.finest("onWritePossible: EXIT. The servlet output stream becomes not ready");
199210
}
200211

201-
private void assureReadyAndDrainedTurnsFalse() {
202-
// readyAndDrained should have been set to false already.
203-
// Just in case due to a race condition readyAndDrained is still true at this moment and is
204-
// being set to false by runOrBuffer() concurrently.
212+
private boolean assureWritingStateTurnsStateOtherThanWriting() {
205213
parkingThread = Thread.currentThread();
206-
while (writeState.get().readyAndDrained) {
207-
LockSupport.parkNanos(TimeUnit.MINUTES.toNanos(1)); // should return immediately
214+
try {
215+
while (writeState.get().state == WriteState.WRITING) {
216+
LockSupport.parkNanos(TimeUnit.MINUTES.toNanos(1));
217+
if (Thread.interrupted()) {
218+
log.fine("Thread interrupted while parked");
219+
return false;
220+
}
221+
}
222+
return true;
223+
} finally {
224+
parkingThread = null;
208225
}
209-
parkingThread = null;
210226
}
211227

212228
/**
@@ -216,31 +232,76 @@ private void assureReadyAndDrainedTurnsFalse() {
216232
* <p>Called from application thread.
217233
*/
218234
private void runOrBuffer(ActionItem actionItem) throws IOException {
219-
WriteState curState = writeState.get();
220-
if (curState.readyAndDrained) { // write to the outputStream directly
221-
actionItem.run();
222-
if (actionItem == completeAction) {
223-
return;
224-
}
225-
if (!isReady.getAsBoolean()) {
226-
boolean successful =
227-
writeState.compareAndSet(curState, curState.withReadyAndDrained(false));
228-
LockSupport.unpark(parkingThread);
229-
checkState(successful, "Bug: curState is unexpectedly changed by another thread");
230-
log.finest("the servlet output stream becomes not ready");
231-
}
232-
} else { // buffer to the writeChain
233-
writeChain.offer(actionItem);
234-
if (!writeState.compareAndSet(curState, curState.withReadyAndDrained(false))) {
235-
checkState(
236-
writeState.get().readyAndDrained,
237-
"Bug: onWritePossible() should have changed readyAndDrained to true, but not");
238-
ActionItem lastItem = writeChain.poll();
239-
if (lastItem != null) {
240-
checkState(lastItem == actionItem, "Bug: lastItem != actionItem");
241-
runOrBuffer(lastItem);
235+
ActionItem itemToWrite = actionItem;
236+
while (true) {
237+
WriteState curState = writeState.get();
238+
if (curState.state == WriteState.READY_AND_DRAINED) { // write to the outputStream directly
239+
WriteState writingState = new WriteState(WriteState.WRITING);
240+
if (writeState.compareAndSet(curState, writingState)) {
241+
boolean successfulExited = false;
242+
try {
243+
while (true) {
244+
if (itemToWrite != null) {
245+
itemToWrite.run();
246+
if (itemToWrite == completeAction) {
247+
writeState.set(WriteState.DEFAULT);
248+
LockSupport.unpark(parkingThread);
249+
successfulExited = true;
250+
return;
251+
}
252+
}
253+
254+
if (isReady.getAsBoolean()) {
255+
itemToWrite = writeChain.poll();
256+
if (itemToWrite != null) {
257+
continue;
258+
}
259+
WriteState latestState = writeState.get();
260+
if (latestState.state == WriteState.WRITING) {
261+
if (writeState.compareAndSet(latestState, new WriteState(WriteState.READY_AND_DRAINED))) {
262+
LockSupport.unpark(parkingThread);
263+
successfulExited = true;
264+
return;
265+
}
266+
// CAS failed, loop again to poll and write
267+
} else {
268+
successfulExited = true;
269+
return;
270+
}
271+
} else {
272+
WriteState latestState = writeState.get();
273+
if (latestState.state == WriteState.WRITING) {
274+
if (writeState.compareAndSet(latestState, WriteState.DEFAULT)) {
275+
LockSupport.unpark(parkingThread);
276+
log.finest("the servlet output stream becomes not ready");
277+
successfulExited = true;
278+
return;
279+
}
280+
// CAS failed, loop again with itemToWrite = null
281+
itemToWrite = null;
282+
} else {
283+
successfulExited = true;
284+
return;
285+
}
286+
}
287+
}
288+
} finally {
289+
if (!successfulExited) {
290+
writeState.set(WriteState.DEFAULT);
291+
LockSupport.unpark(parkingThread);
292+
}
293+
}
294+
}
295+
} else { // NOT_READY_OR_NOT_DRAINED or WRITING
296+
writeChain.offer(itemToWrite);
297+
if (writeState.compareAndSet(curState, new WriteState(curState.state))) {
298+
return;
242299
}
243-
} // state has not changed since
300+
itemToWrite = writeChain.poll();
301+
if (itemToWrite == null) {
302+
return;
303+
}
304+
}
244305
}
245306
}
246307

@@ -263,34 +324,16 @@ default void finest(String str, Object...params) {}
263324
}
264325

265326
private static final class WriteState {
327+
static final int READY_AND_DRAINED = 0;
328+
static final int NOT_READY_OR_NOT_DRAINED = 1;
329+
static final int WRITING = 2; // Active direct write in progress
266330

267-
static final WriteState DEFAULT = new WriteState(false);
331+
static final WriteState DEFAULT = new WriteState(NOT_READY_OR_NOT_DRAINED);
268332

269-
/**
270-
* The servlet output stream is ready and the writeChain is empty.
271-
*
272-
* <p>readyAndDrained turns from false to true when:
273-
* {@code onWritePossible()} exits while currently there is no more data to write, but the last
274-
* check of {@link javax.servlet.ServletOutputStream#isReady()} is true.
275-
*
276-
* <p>readyAndDrained turns from true to false when:
277-
* {@code runOrBuffer()} exits while either the action item is written directly to the
278-
* servlet output stream and the check of {@link javax.servlet.ServletOutputStream#isReady()}
279-
* right after that returns false, or the action item is buffered into the writeChain.
280-
*/
281-
final boolean readyAndDrained;
282-
283-
WriteState(boolean readyAndDrained) {
284-
this.readyAndDrained = readyAndDrained;
285-
}
333+
final int state;
286334

287-
/**
288-
* Only {@code onWritePossible()} can set readyAndDrained to true, and only {@code
289-
* runOrBuffer()} can set it to false.
290-
*/
291-
@CheckReturnValue
292-
WriteState withReadyAndDrained(boolean readyAndDrained) {
293-
return new WriteState(readyAndDrained);
335+
WriteState(int state) {
336+
this.state = state;
294337
}
295338
}
296339
}

0 commit comments

Comments
 (0)