diff --git a/patroni/ha.py b/patroni/ha.py index 7ed1e139..fdc2ce07 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -1290,7 +1290,8 @@ class Ha(object): data_sysid = self.state_handler.sysid if not self.sysid_valid(data_sysid): # data directory is not empty, but no valid sysid, cluster must be broken, suggest reinit - return "data dir for the cluster is not empty, but system ID is invalid; consider doing reinitalize" + return ("data dir for the cluster is not empty, but system ID is invalid; consider doing" + "reinitialize") if self.sysid_valid(self.cluster.initialize): if self.cluster.initialize != data_sysid: diff --git a/patroni/postgresql/__init__.py b/patroni/postgresql/__init__.py index 5c0ad8cc..7fba00d3 100644 --- a/patroni/postgresql/__init__.py +++ b/patroni/postgresql/__init__.py @@ -516,7 +516,7 @@ class Postgresql(object): self.set_state('stopping') # Send signal to postmaster to stop - success = postmaster.signal_stop(mode) + success = postmaster.signal_stop(mode, self.pgcommand('pg_ctl')) if success is not None: if success and on_safepoint: on_safepoint() @@ -533,11 +533,10 @@ class Postgresql(object): return True, True - @staticmethod - def terminate_starting_postmaster(postmaster): + def terminate_starting_postmaster(self, postmaster): """Terminates a postmaster that has not yet opened ports or possibly even written a pid file. Blocks until the process goes away.""" - postmaster.signal_stop('immediate') + postmaster.signal_stop('immediate', self.pgcommand('pg_ctl')) postmaster.wait() def _wait_for_connection_close(self, postmaster): diff --git a/patroni/postgresql/postmaster.py b/patroni/postgresql/postmaster.py index f2f23b25..add11488 100644 --- a/patroni/postgresql/postmaster.py +++ b/patroni/postgresql/postmaster.py @@ -22,7 +22,7 @@ logger = logging.getLogger(__name__) STOP_SIGNALS = { 'smart': signal.SIGTERM, 'fast': signal.SIGINT, - 'immediate': signal.SIGQUIT if os.name != 'nt' else signal.SIGABRT, + 'immediate': signal.SIGQUIT, } @@ -105,7 +105,7 @@ class PostmasterProcess(psutil.Process): except psutil.NoSuchProcess: return None - def signal_stop(self, mode): + def signal_stop(self, mode, pg_ctl='pg_ctl'): """Signal postmaster process to stop :returns None if signaled, True if process is already gone, False if error @@ -113,6 +113,8 @@ class PostmasterProcess(psutil.Process): if self.is_single_user: logger.warning("Cannot stop server; single-user server is running (PID: {0})".format(self.pid)) return False + if os.name != 'posix': + return self.pg_ctl_kill(mode, pg_ctl) try: self.send_signal(STOP_SIGNALS[mode]) except psutil.NoSuchProcess: @@ -123,6 +125,17 @@ class PostmasterProcess(psutil.Process): return None + def pg_ctl_kill(self, mode, pg_ctl): + SIGNALNAME = {"smart": "TERM", "fast": "INT", "immediate": "QUIT"}[mode] + try: + status = subprocess.call([pg_ctl, "kill", SIGNALNAME, str(self.pid)]) + except OSError: + return False + if status == 0: + return None + else: + return not self.is_running() + def wait_for_user_backends_to_close(self): # These regexps are cross checked against versions PostgreSQL 9.1 .. 11 aux_proc_re = re.compile("(?:postgres:)( .*:)? (?:(?:archiver|startup|autovacuum launcher|autovacuum worker|" diff --git a/tests/test_postmaster.py b/tests/test_postmaster.py index 83afaeea..a62436e3 100644 --- a/tests/test_postmaster.py +++ b/tests/test_postmaster.py @@ -76,6 +76,21 @@ class TestPostmasterProcess(unittest.TestCase): self.assertEqual(proc.signal_stop('immediate'), True) self.assertEqual(proc.signal_stop('immediate'), False) + @patch('psutil.Process.__init__', Mock()) + @patch('patroni.postgresql.postmaster.os') + @patch('subprocess.call', Mock(side_effect=[0, OSError, 1])) + @patch('psutil.Process.pid', Mock(return_value=123)) + @patch('psutil.Process.is_running', Mock(return_value=False)) + def test_signal_stop_nt(self, mock_os): + mock_os.configure_mock(name="nt") + proc = PostmasterProcess(-123) + self.assertEqual(proc.signal_stop('immediate'), False) + + proc = PostmasterProcess(123) + self.assertEqual(proc.signal_stop('immediate'), None) + self.assertEqual(proc.signal_stop('immediate'), False) + self.assertEqual(proc.signal_stop('immediate'), True) + @patch('psutil.Process.__init__', Mock()) @patch('psutil.wait_procs') def test_wait_for_user_backends_to_close(self, mock_wait):