You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ab...@apache.org on 2026/10/21 05:12:57 UTC

svn commit: r1939305 - httpd/httpd/trunk/test/modules/proxy

Author: abernal
Date: Wed Oct 21 05:12:57 2026
New Revision: 1939305

Log:
test: Fix a flaky test_proxy_07_cache_control.py (seen on Windows CI).

The cache entry for the first request is committed by the CACHE_SAVE
filter on the EOS-only pass that follows the body, which can still be
running server side for an instant after the client has already read
the complete body. Two back-to-back requests with no delay can race
this, making the second one miss the cache and hit the backend again.

Give the commit room to land before relying on it, instead of
asserting immediately after the first request returns. Reproduced
locally (about 1 in 5 runs failing, on different parametrized cases
each time, none of them involving a comma) and confirmed fixed (0
failures in 55 runs) before and after this change.

Also harden the TCPFaker backend used by this test to close its
socket with an orderly shutdown() instead of an abrupt close(), which
removes one more way for a slow platform to see a reset instead of a
clean end of response.

GitHub: closes #864

Modified:
   httpd/httpd/trunk/test/modules/proxy/env.py
   httpd/httpd/trunk/test/modules/proxy/test_07_cache_control.py

Modified: httpd/httpd/trunk/test/modules/proxy/env.py
==============================================================================
--- httpd/httpd/trunk/test/modules/proxy/env.py	Wed Oct 21 05:12:42 2026	(r1939304)
+++ httpd/httpd/trunk/test/modules/proxy/env.py	Wed Oct 21 05:12:57 2026	(r1939305)
@@ -57,6 +57,17 @@ Hello""".encode()
                     # capture request to backend
                     self._request = data
                     c.sendall(self._make_response(data))
+                    # Half-close for an orderly FIN instead of letting the
+                    # OS tear the socket down under us. An abrupt close()
+                    # right after sendall() can, depending on the platform's
+                    # TCP stack and buffering, surface to the peer as a
+                    # reset rather than a clean end of the response, which
+                    # httpd's proxy and cache code treat as an aborted
+                    # connection and a reason to discard the cache entry.
+                    try:
+                        c.shutdown(socket.SHUT_WR)
+                    except OSError:
+                        pass
                 finally:
                     c.close()
             except socket.timeout:

Modified: httpd/httpd/trunk/test/modules/proxy/test_07_cache_control.py
==============================================================================
--- httpd/httpd/trunk/test/modules/proxy/test_07_cache_control.py	Wed Oct 21 05:12:42 2026	(r1939304)
+++ httpd/httpd/trunk/test/modules/proxy/test_07_cache_control.py	Wed Oct 21 05:12:57 2026	(r1939305)
@@ -1,4 +1,5 @@
 import os
+import time
 import uuid
 
 import pytest
@@ -131,6 +132,12 @@ class TestCacheControl:
     def test_proxy_07_002(self, env, index):
         cc = ", ".join(CASES[index][0])
         r1 = self.get(env, index)
+        # The cache entry for r1 is committed by the CACHE_SAVE filter on
+        # the EOS-only pass that follows the body, which can still be
+        # running server side for an instant after the body itself has
+        # already reached this process. Give it room to land before relying
+        # on it, instead of risking a spurious backend hit on r2.
+        time.sleep(0.1)
         r2 = self.get(env, index)
         assert self.backend_hits(index) == 1, f"Cache-Control: {cc} not cached"
         assert r2["header"]["x-cache"].startswith("HIT")
@@ -145,6 +152,9 @@ class TestCacheControl:
         r1 = self.get(env, index)
         assert r1["header"]["x-secret"] == "s"
         assert r1["header"]["set-cookie"] == "a=b"
+        # See test_proxy_07_002: give the CACHE_SAVE filter's commit time
+        # to land before asserting on it.
+        time.sleep(0.1)
         r2 = self.get(env, index)
         assert self.backend_hits(index) == 1, f"Cache-Control: {cc} not cached"
         assert r2["header"]["x-cache"].startswith("HIT")