Skip to content

Commit d06eeee

Browse files
committed
Updated postgis_parallel_tests to use the restore-mode workaround across the whole source test DB lifecycle instead of toggling it per clone
1 parent d8e259f commit d06eeee

File tree

1 file changed

+92
-20
lines changed

1 file changed

+92
-20
lines changed
Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import sys
12
from contextlib import contextmanager
23
from time import sleep
34

45
from django.db.backends.postgresql.creation import DatabaseCreation as PostGISDatabaseCreation
56

67

78
class DatabaseCreation(PostGISDatabaseCreation):
9+
_hold_restore_mode_for_parallel = False
10+
_block_source_database_connections = False
11+
812
@contextmanager
913
def _source_database_cursor(self):
1014
self.connection.close()
@@ -14,55 +18,123 @@ def _source_database_cursor(self):
1418
finally:
1519
self.connection.close()
1620

17-
def _enter_timescaledb_restore_mode(self):
21+
def _parallel_test_processes_requested(self):
22+
args = sys.argv[1:]
23+
for index, arg in enumerate(args):
24+
if arg == "--parallel":
25+
if index + 1 >= len(args) or args[index + 1].startswith("-"):
26+
return 2
27+
value = args[index + 1]
28+
elif arg.startswith("--parallel="):
29+
value = arg.split("=", 1)[1]
30+
else:
31+
continue
32+
33+
if value == "auto":
34+
return 2
35+
try:
36+
return int(value)
37+
except ValueError:
38+
return 0
39+
return 0
40+
41+
def _source_database_name(self):
42+
return self.connection.settings_dict["NAME"]
43+
44+
def _timescaledb_extension_installed(self):
1845
with self._source_database_cursor() as cursor:
1946
cursor.execute("SELECT 1 FROM pg_extension WHERE extname = 'timescaledb'")
20-
if cursor.fetchone() is None:
21-
return False
47+
return cursor.fetchone() is not None
2248

49+
def _timescaledb_restore_mode_enabled(self):
50+
with self._source_database_cursor() as cursor:
2351
cursor.execute("SHOW timescaledb.restoring")
24-
if cursor.fetchone()[0].lower() == "on":
25-
return False
52+
return cursor.fetchone()[0].lower() == "on"
2653

54+
def _enter_timescaledb_restore_mode(self):
55+
if not self._timescaledb_extension_installed():
56+
return False
57+
if self._timescaledb_restore_mode_enabled():
58+
return False
59+
60+
with self._source_database_cursor() as cursor:
2761
# Timescale background workers attach to the template database and
2862
# block CREATE DATABASE ... WITH TEMPLATE during parallel test setup.
2963
cursor.execute("SELECT timescaledb_pre_restore()")
30-
return True
64+
return True
3165

3266
def _exit_timescaledb_restore_mode(self):
67+
if not self._timescaledb_extension_installed():
68+
return
69+
if not self._timescaledb_restore_mode_enabled():
70+
return
71+
3372
with self._source_database_cursor() as cursor:
34-
cursor.execute("SHOW timescaledb.restoring")
35-
if cursor.fetchone()[0].lower() == "on":
36-
cursor.execute("SELECT timescaledb_post_restore()")
73+
cursor.execute("SELECT timescaledb_post_restore()")
74+
75+
def _set_source_database_allow_connections(self, allow_connections):
76+
source_database_name = self._source_database_name()
77+
quoted_source_database_name = self._quote_name(source_database_name)
78+
allow_connections_sql = "true" if allow_connections else "false"
79+
80+
with self._nodb_cursor() as cursor:
81+
cursor.execute(f"ALTER DATABASE {quoted_source_database_name} WITH ALLOW_CONNECTIONS {allow_connections_sql}")
3782

3883
def _disconnect_source_database_sessions(self):
39-
with self._source_database_cursor() as cursor:
84+
source_database_name = self._source_database_name()
85+
with self._nodb_cursor() as cursor:
4086
for _ in range(20):
4187
cursor.execute(
4288
"""
4389
SELECT pg_terminate_backend(pid)
4490
FROM pg_stat_activity
45-
WHERE datname = current_database()
91+
WHERE datname = %s
4692
AND pid <> pg_backend_pid()
47-
"""
93+
""",
94+
[source_database_name],
4895
)
4996
cursor.execute(
5097
"""
5198
SELECT COUNT(*)
5299
FROM pg_stat_activity
53-
WHERE datname = current_database()
100+
WHERE datname = %s
54101
AND pid <> pg_backend_pid()
55-
"""
102+
""",
103+
[source_database_name],
56104
)
57105
if cursor.fetchone()[0] == 0:
58106
return
59107
sleep(0.25)
60108

61-
def _clone_test_db(self, suffix, verbosity, keepdb=False):
62-
restore_mode_changed = self._enter_timescaledb_restore_mode()
63-
try:
109+
def create_test_db(self, verbosity=1, autoclobber=False, serialize=True, keepdb=False):
110+
test_database_name = super().create_test_db(
111+
verbosity=verbosity,
112+
autoclobber=autoclobber,
113+
serialize=serialize,
114+
keepdb=keepdb,
115+
)
116+
117+
if self._parallel_test_processes_requested() > 1:
118+
self._hold_restore_mode_for_parallel = self._enter_timescaledb_restore_mode() or self._timescaledb_restore_mode_enabled()
119+
self._set_source_database_allow_connections(False)
120+
self._block_source_database_connections = True
64121
self._disconnect_source_database_sessions()
65-
super()._clone_test_db(suffix, verbosity, keepdb)
66-
finally:
67-
if restore_mode_changed:
122+
123+
return test_database_name
124+
125+
def _clone_test_db(self, suffix, verbosity, keepdb=False):
126+
if not self._hold_restore_mode_for_parallel:
127+
self._enter_timescaledb_restore_mode()
128+
self._disconnect_source_database_sessions()
129+
super()._clone_test_db(suffix, verbosity, keepdb)
130+
131+
def destroy_test_db(self, old_database_name=None, verbosity=1, keepdb=False, suffix=None):
132+
if suffix is None:
133+
if self._block_source_database_connections and keepdb:
134+
self._set_source_database_allow_connections(True)
135+
self._block_source_database_connections = False
136+
137+
if keepdb and self._hold_restore_mode_for_parallel and self._timescaledb_extension_installed():
68138
self._exit_timescaledb_restore_mode()
139+
self._hold_restore_mode_for_parallel = False
140+
super().destroy_test_db(old_database_name, verbosity, keepdb, suffix)

0 commit comments

Comments
 (0)