From 6c9ef6282e65a79daba4dc2146e8b825b4ddadb9 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 30 Mar 2026 09:19:20 -0400 Subject: [PATCH 1/3] Refs #36770 -- Guarded against an endless wait in LiveServerThread.terminate(). terminate() shouldn't assume the main server was started. (A deadlock from mishandling of in-memory SQLite databases may have occurred.) --- django/test/testcases.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/django/test/testcases.py b/django/test/testcases.py index de2a3eb61dfe..b31e70d9d18e 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1783,11 +1783,12 @@ def _create_server(self, connections_override=None): ) def terminate(self): - if hasattr(self, "httpd"): - # Stop the WSGI server - self.httpd.shutdown() - self.httpd.server_close() - self.join() + if self.is_ready.is_set(): + if hasattr(self, "httpd"): + # Stop the WSGI server + self.httpd.shutdown() + self.httpd.server_close() + self.join() class LiveServerTestCase(TransactionTestCase): From 9c9a43b45e07d2a2dcd3dd08681f7f5cecd82ab6 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 30 Mar 2026 12:09:10 -0400 Subject: [PATCH 2/3] Refs #36770 -- Preferred addCleanup() in live server tests. --- tests/servers/tests.py | 121 +++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 66 deletions(-) diff --git a/tests/servers/tests.py b/tests/servers/tests.py index 0ddf55df6f66..f69cff020cbf 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -119,18 +119,16 @@ def test_in_memory_database_lock(self): source_connection = conn.connection # Open a connection to the database. conn.connect() + self.addCleanup(source_connection.close) # Create a transaction to lock the database. cursor = conn.cursor() cursor.execute("BEGIN IMMEDIATE TRANSACTION") + self.addCleanup(cursor.execute, "ROLLBACK") try: with self.urlopen("/create_model_instance/") as f: self.assertEqual(f.status, 200) except HTTPError: self.fail("Unexpected error due to a database lock.") - finally: - # Release the transaction. - cursor.execute("ROLLBACK") - source_connection.close() class FailingLiveServerThread(LiveServerThread): @@ -211,26 +209,23 @@ def test_closes_connection_without_content_length(self): LiveServerViews.server_thread.port, timeout=1, ) - try: - conn.request( - "GET", "/streaming_example_view/", headers={"Connection": "keep-alive"} - ) - response = conn.getresponse() - self.assertTrue(response.will_close) - self.assertEqual(response.read(), b"Iamastream") - self.assertEqual(response.status, 200) - self.assertEqual(response.getheader("Connection"), "close") - - conn.request( - "GET", "/streaming_example_view/", headers={"Connection": "close"} - ) - response = conn.getresponse() - self.assertTrue(response.will_close) - self.assertEqual(response.read(), b"Iamastream") - self.assertEqual(response.status, 200) - self.assertEqual(response.getheader("Connection"), "close") - finally: - conn.close() + self.addCleanup(conn.close) + + conn.request( + "GET", "/streaming_example_view/", headers={"Connection": "keep-alive"} + ) + response = conn.getresponse() + self.assertTrue(response.will_close) + self.assertEqual(response.read(), b"Iamastream") + self.assertEqual(response.status, 200) + self.assertEqual(response.getheader("Connection"), "close") + + conn.request("GET", "/streaming_example_view/", headers={"Connection": "close"}) + response = conn.getresponse() + self.assertTrue(response.will_close) + self.assertEqual(response.read(), b"Iamastream") + self.assertEqual(response.status, 200) + self.assertEqual(response.getheader("Connection"), "close") def test_keep_alive_on_connection_with_content_length(self): """ @@ -241,45 +236,41 @@ def test_keep_alive_on_connection_with_content_length(self): conn = HTTPConnection( LiveServerViews.server_thread.host, LiveServerViews.server_thread.port ) - try: - conn.request("GET", "/example_view/", headers={"Connection": "keep-alive"}) - response = conn.getresponse() - self.assertFalse(response.will_close) - self.assertEqual(response.read(), b"example view") - self.assertEqual(response.status, 200) - self.assertIsNone(response.getheader("Connection")) - - conn.request("GET", "/example_view/", headers={"Connection": "close"}) - response = conn.getresponse() - self.assertFalse(response.will_close) - self.assertEqual(response.read(), b"example view") - self.assertEqual(response.status, 200) - self.assertIsNone(response.getheader("Connection")) - finally: - conn.close() + self.addCleanup(conn.close) + + conn.request("GET", "/example_view/", headers={"Connection": "keep-alive"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.read(), b"example view") + self.assertEqual(response.status, 200) + self.assertIsNone(response.getheader("Connection")) + + conn.request("GET", "/example_view/", headers={"Connection": "close"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.read(), b"example view") + self.assertEqual(response.status, 200) + self.assertIsNone(response.getheader("Connection")) def test_keep_alive_connection_clears_previous_request_data(self): conn = HTTPConnection( LiveServerViews.server_thread.host, LiveServerViews.server_thread.port ) - try: - conn.request( - "POST", "/method_view/", b"{}", headers={"Connection": "keep-alive"} - ) - response = conn.getresponse() - self.assertFalse(response.will_close) - self.assertEqual(response.status, 200) - self.assertEqual(response.read(), b"POST") + self.addCleanup(conn.close) - conn.request( - "POST", "/method_view/", b"{}", headers={"Connection": "close"} - ) - response = conn.getresponse() - self.assertFalse(response.will_close) - self.assertEqual(response.status, 200) - self.assertEqual(response.read(), b"POST") - finally: - conn.close() + conn.request( + "POST", "/method_view/", b"{}", headers={"Connection": "keep-alive"} + ) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), b"POST") + + conn.request("POST", "/method_view/", b"{}", headers={"Connection": "close"}) + response = conn.getresponse() + self.assertFalse(response.will_close) + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), b"POST") def test_404(self): with self.assertRaises(HTTPError) as err: @@ -330,15 +321,13 @@ def test_closes_connection_with_content_length(self): SingleThreadLiveServerViews.server_thread.port, timeout=1, ) - try: - conn.request("GET", "/example_view/", headers={"Connection": "keep-alive"}) - response = conn.getresponse() - self.assertTrue(response.will_close) - self.assertEqual(response.read(), b"example view") - self.assertEqual(response.status, 200) - self.assertEqual(response.getheader("Connection"), "close") - finally: - conn.close() + self.addCleanup(conn.close) + conn.request("GET", "/example_view/", headers={"Connection": "keep-alive"}) + response = conn.getresponse() + self.assertTrue(response.will_close) + self.assertEqual(response.read(), b"example view") + self.assertEqual(response.status, 200) + self.assertEqual(response.getheader("Connection"), "close") class LiveServerDatabase(LiveServerBase): From afa026cd80a2388255a137a274568aef09f9fee7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 30 Mar 2026 12:53:26 -0400 Subject: [PATCH 3/3] Refs #36770 -- Skipped test_in_memory_database_lock(). Skip pending some investigation. --- tests/servers/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/servers/tests.py b/tests/servers/tests.py index f69cff020cbf..13fe603d1a36 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -106,6 +106,7 @@ def test_closes_connections(self): self.assertIsNone(conn.connection) +@unittest.skip("Flaky test as described in https://code.djangoproject.com/ticket/36770") @unittest.skipUnless(connection.vendor == "sqlite", "SQLite specific test.") class LiveServerInMemoryDatabaseLockTest(LiveServerBase): def test_in_memory_database_lock(self):