Skip to content

Commit 0d53798

Browse files
committed
reworked logic in PostgresNode.__exit__() according to review by @gsmol
1 parent 5878552 commit 0d53798

File tree

3 files changed

+71
-44
lines changed

3 files changed

+71
-44
lines changed

testgres/config.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,28 @@ class TestgresConfig:
77
88
Attributes:
99
cache_initdb: shall we use cached initdb instance?
10-
cache_pg_config: shall we cache pg_config results?
1110
cached_initdb_dir: shall we create a temp dir for cached initdb?
12-
node_cleanup_full: shall we remove EVERYTHING (including logs)?
11+
12+
cache_pg_config: shall we cache pg_config results?
13+
1314
error_log_lines: N of log lines to be included into exception (0=inf).
15+
16+
node_cleanup_full: shall we remove EVERYTHING (including logs)?
17+
node_cleanup_on_good_exit: remove base_dir on nominal __exit__().
18+
node_cleanup_on_bad_exit: remove base_dir on __exit__() via exception.
1419
"""
1520

1621
cache_initdb = True
17-
cache_pg_config = True
1822
cached_initdb_dir = None
19-
node_cleanup_full = True
23+
24+
cache_pg_config = True
25+
2026
error_log_lines = 20
2127

28+
node_cleanup_full = True
29+
node_cleanup_on_good_exit = True
30+
node_cleanup_on_bad_exit = False
31+
2232

2333
def configure_testgres(**options):
2434
"""

testgres/node.py

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from .logger import TestgresLogger
4141

4242
from .utils import \
43+
eprint, \
4344
get_bin_path, \
4445
file_tail, \
4546
pg_version_ge, \
@@ -80,14 +81,18 @@ def __init__(self, name=None, port=None, base_dir=None, use_logging=False):
8081
use_logging: enable python logging.
8182
"""
8283

83-
# public
84+
# basic
8485
self.host = '127.0.0.1'
8586
self.name = name or generate_app_name()
8687
self.port = port or reserve_port()
8788
self.base_dir = base_dir
8889

90+
# defaults for __exit__()
91+
self.cleanup_on_good_exit = TestgresConfig.node_cleanup_on_good_exit
92+
self.cleanup_on_bad_exit = TestgresConfig.node_cleanup_on_bad_exit
93+
self.shutdown_max_attempts = 3
94+
8995
# private
90-
self._should_rm_dirs = base_dir is None
9196
self._should_free_port = port is None
9297
self._use_logging = use_logging
9398
self._logger = None
@@ -100,12 +105,23 @@ def __enter__(self):
100105
return self
101106

102107
def __exit__(self, type, value, traceback):
103-
# stop node if necessary
104-
self.cleanup()
105-
106-
# free port if necessary
107108
self.free_port()
108109

110+
got_exception = value is not None
111+
c1 = self.cleanup_on_good_exit and not got_exception
112+
c2 = self.cleanup_on_bad_exit and got_exception
113+
114+
attempts = self.shutdown_max_attempts
115+
116+
if c1 or c2:
117+
self.cleanup(attempts)
118+
else:
119+
self._try_shutdown(attempts)
120+
121+
@property
122+
def pid(self):
123+
return self.get_pid()
124+
109125
@property
110126
def master(self):
111127
return self._master
@@ -126,6 +142,22 @@ def utils_log_name(self):
126142
def pg_log_name(self):
127143
return os.path.join(self.logs_dir, PG_LOG_FILE)
128144

145+
def _try_shutdown(self, max_attempts):
146+
attempts = 0
147+
148+
# try stopping server N times
149+
while attempts < max_attempts:
150+
try:
151+
self.stop()
152+
break # OK
153+
except ExecUtilException:
154+
pass # one more time
155+
except Exception:
156+
# TODO: probably kill stray instance
157+
eprint('cannot stop node {}'.format(self.name))
158+
159+
attempts += 1
160+
129161
def _assign_master(self, master):
130162
"""NOTE: this is a private method!"""
131163

@@ -163,8 +195,6 @@ def _create_recovery_conf(self, username):
163195
self.append_conf(RECOVERY_CONF_FILE, line)
164196

165197
def _prepare_dirs(self):
166-
"""NOTE: this is a private method!"""
167-
168198
if not self.base_dir:
169199
self.base_dir = tempfile.mkdtemp()
170200

@@ -175,23 +205,17 @@ def _prepare_dirs(self):
175205
os.makedirs(self.logs_dir)
176206

177207
def _maybe_start_logger(self):
178-
"""NOTE: this is a private method!"""
179-
180208
if self._use_logging:
181-
# spawn new logger if it doesn't exist or stopped
209+
# spawn new logger if it doesn't exist or is stopped
182210
if not self._logger or not self._logger.is_alive():
183211
self._logger = TestgresLogger(self.name, self.pg_log_name)
184212
self._logger.start()
185213

186214
def _maybe_stop_logger(self):
187-
"""NOTE: this is a private method!"""
188-
189215
if self._logger:
190216
self._logger.stop()
191217

192218
def _format_verbose_error(self, message=None):
193-
"""NOTE: this is a private method!"""
194-
195219
# list of important files + N of last lines
196220
files = [
197221
(os.path.join(self.data_dir, PG_CONF_FILE), 0),
@@ -560,14 +584,16 @@ def pg_ctl(self, params):
560584
def free_port(self):
561585
"""
562586
Reclaim port owned by this node.
587+
NOTE: does not free auto selected ports.
563588
"""
564589

565590
if self._should_free_port:
566591
release_port(self.port)
567592

568593
def cleanup(self, max_attempts=3):
569594
"""
570-
Stop node if needed and remove its data directory.
595+
Stop node if needed and remove its data/logs directory.
596+
NOTE: take a look at TestgresConfig.node_cleanup_full.
571597
572598
Args:
573599
max_attempts: how many times should we try to stop()?
@@ -576,30 +602,15 @@ def cleanup(self, max_attempts=3):
576602
This instance of PostgresNode.
577603
"""
578604

579-
attempts = 0
605+
self._try_shutdown(max_attempts)
580606

581-
# try stopping server
582-
while attempts < max_attempts:
583-
try:
584-
self.stop()
585-
break # OK
586-
except ExecUtilException:
587-
pass # one more time
588-
except Exception:
589-
print('cannot stop node {}'.format(self.name))
590-
591-
attempts += 1
592-
593-
# remove directory tree if necessary
594-
if self._should_rm_dirs:
595-
596-
# choose directory to be removed
597-
if TestgresConfig.node_cleanup_full:
598-
rm_dir = self.base_dir # everything
599-
else:
600-
rm_dir = self.data_dir # just data, save logs
607+
# choose directory to be removed
608+
if TestgresConfig.node_cleanup_full:
609+
rm_dir = self.base_dir # everything
610+
else:
611+
rm_dir = self.data_dir # just data, save logs
601612

602-
shutil.rmtree(rm_dir, ignore_errors=True)
613+
shutil.rmtree(rm_dir, ignore_errors=True)
603614

604615
return self
605616

@@ -750,8 +761,8 @@ def poll_query_until(self,
750761
raise_programming_error=True,
751762
raise_internal_error=True):
752763
"""
753-
Run a query once a second until it returs 'expected'.
754-
Query should return single column.
764+
Run a query once per second until it returns 'expected'.
765+
Query should return a single value (1 row, 1 column).
755766
756767
Args:
757768
query: query to be executed.

testgres/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# coding: utf-8
22

33
from __future__ import division
4+
from __future__ import print_function
45

56
import functools
67
import io
78
import os
89
import port_for
910
import six
1011
import subprocess
12+
import sys
1113

1214
from distutils.version import LooseVersion
1315

@@ -311,3 +313,7 @@ def bound_func(*args2, **kwargs2):
311313
_dec.__name__ = 'method_decorator({})'.format(decorator.__name__)
312314

313315
return _dec
316+
317+
318+
def eprint(*args, **kwargs):
319+
print(*args, file=sys.stderr, **kwargs)

0 commit comments

Comments
 (0)