Handle spurious wake-ups while waiting for an allocation
diff --git a/java/org/apache/coyote/http2/WindowAllocationManager.java b/java/org/apache/coyote/http2/WindowAllocationManager.java
index 94558b1..e784c40 100644
--- a/java/org/apache/coyote/http2/WindowAllocationManager.java
+++ b/java/org/apache/coyote/http2/WindowAllocationManager.java
@@ -16,6 +16,8 @@
  */
 package org.apache.coyote.http2;
 
+import java.util.concurrent.TimeUnit;
+
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.Response;
 import org.apache.juli.logging.Log;
@@ -133,7 +135,7 @@
     }
 
 
-    private void waitFor(int waitTarget, long timeout) throws InterruptedException {
+    private void waitFor(int waitTarget, final long timeout) throws InterruptedException {
         synchronized (stream) {
             if (waitingFor != NONE) {
                 throw new IllegalStateException(sm.getString("windowAllocationManager.waitFor.ise",
@@ -141,12 +143,30 @@
             }
 
             waitingFor = waitTarget;
+            long startNanos = -1;
 
-            if (timeout < 0) {
-                stream.wait();
-            } else {
-                stream.wait(timeout);
-            }
+            // Loop to handle spurious wake-ups
+            do {
+                if (timeout < 0) {
+                    stream.wait();
+                } else {
+                    long timeoutRemaining;
+                    if (startNanos == -1) {
+                        startNanos = System.nanoTime();
+                        timeoutRemaining = timeout;
+                    } else {
+                        long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
+                        if (elapsedMillis == 0) {
+                            elapsedMillis = 1;
+                        }
+                        timeoutRemaining = timeout - elapsedMillis;
+                        if (timeoutRemaining <= 0) {
+                            return;
+                        }
+                    }
+                    stream.wait(timeoutRemaining);
+                }
+            } while (waitingFor != NONE);
         }
     }
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index a25727b..49da890 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -159,6 +159,10 @@
         code paths that could allow a notification from the Poller to be missed
         resuting in a timeout rather than the expected read or write. (markt)
       </fix>
+      <fix>
+        Refactor waiting for an HTTP/2 stream or connection window update to
+        handle spurious wake-ups during the wait. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="WebSocket">