Prevent race condition
Fixes 4493

Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java b/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java
index 7564f0c..6163746 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -53,8 +53,11 @@
     private final BlockingDeque<T> queue = new LinkedBlockingDeque<>();
     private final byte[] chunkDelimiter;
     private final AtomicBoolean resumed = new AtomicBoolean(false);
+    private final Object lock = new Object();
 
+    // the following flushing and touchingEntityStream variables are used in a synchronized block exclusively
     private boolean flushing = false;
+    private boolean touchingEntityStream = false;
 
     private volatile boolean closed = false;
 
@@ -198,7 +201,7 @@
                     boolean shouldClose;
                     T t;
 
-                    synchronized (ChunkedOutput.this) {
+                    synchronized (lock) {
                         if (flushing) {
                             // if another thread is already flushing the queue, we don't have to do anything
                             return null;
@@ -220,6 +223,10 @@
 
                     while (t != null) {
                         try {
+                            synchronized (lock) {
+                                touchingEntityStream = true;
+                            }
+
                             final OutputStream origStream = responseContext.getEntityStream();
                             final OutputStream writtenStream = requestContext.getWorkers().writeTo(
                                     t,
@@ -256,10 +263,15 @@
                                 connectionCallback.onDisconnect(asyncContext);
                             }
                             throw mpe;
+                        } finally {
+                           synchronized (lock) {
+                               touchingEntityStream = false;
+                           }
                         }
+
                         t = queue.poll();
                         if (t == null) {
-                            synchronized (ChunkedOutput.this) {
+                            synchronized (lock) {
                                 // queue seems empty
                                 // check again in the synchronized block before clearing the flushing flag
                                 // first remember the closed flag (this has to be before polling the queue,
@@ -290,7 +302,11 @@
         } finally {
             if (closed) {
                 try {
-                    responseContext.close();
+                    synchronized (lock) {
+                        if (!touchingEntityStream) {
+                            responseContext.close();
+                        } // else the next thread will close responseContext
+                    }
                 } catch (final Exception e) {
                     // if no exception remembered before, remember this one
                     // otherwise the previously remembered exception (from catch clause) takes precedence