@@ -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