Skip to content

PostgresNode::pid is improved #199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
PostgresNode::pid uses the data from "pg_ctl status" output.
  • Loading branch information
dmitry-lipetsk committed Feb 28, 2025
commit 0402c4a6145820043bf94c879921d244ddd8d09a
4 changes: 4 additions & 0 deletions testgres/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@

# logical replication settings
LOGICAL_REPL_MAX_CATCHUP_ATTEMPTS = 60

PG_CTL__STATUS__OK = 0
PG_CTL__STATUS__NODE_IS_STOPPED = 3
PG_CTL__STATUS__BAD_DATADIR = 4
157 changes: 126 additions & 31 deletions testgres/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
RECOVERY_CONF_FILE, \
PG_LOG_FILE, \
UTILS_LOG_FILE, \
PG_PID_FILE
PG_CTL__STATUS__OK, \
PG_CTL__STATUS__NODE_IS_STOPPED, \
PG_CTL__STATUS__BAD_DATADIR \

from .consts import \
MAX_LOGICAL_REPLICATION_WORKERS, \
Expand Down Expand Up @@ -132,9 +134,6 @@ class PostgresNode(object):
# a max number of node start attempts
_C_MAX_START_ATEMPTS = 5

# a max number of read pid file attempts
_C_MAX_GET_PID_ATEMPTS = 5

def __init__(self, name=None, base_dir=None, port=None, conn_params: ConnectionParams = ConnectionParams(),
bin_dir=None, prefix=None):
"""
Expand Down Expand Up @@ -211,40 +210,136 @@ def pid(self):
Return postmaster's PID if node is running, else 0.
"""

nAttempt = 0
pid_file = os.path.join(self.data_dir, PG_PID_FILE)
pid_s: str = None
self__data_dir = self.data_dir

_params = [
self._get_bin_path('pg_ctl'),
"-D", self__data_dir,
"status"
] # yapf: disable

status_code, out, error = execute_utility2(
self.os_ops,
_params,
self.utils_log_file,
verbose=True,
ignore_errors=True)

assert type(status_code) == int # noqa: E721
assert type(out) == str # noqa: E721
assert type(error) == str # noqa: E721

# -----------------
if status_code == PG_CTL__STATUS__NODE_IS_STOPPED:
return 0

# -----------------
if status_code == PG_CTL__STATUS__BAD_DATADIR:
return 0

# -----------------
if status_code != PG_CTL__STATUS__OK:
errMsg = "Getting of a node status [data_dir is {0}] failed.".format(self__data_dir)

raise ExecUtilException(
message=errMsg,
command=_params,
exit_code=status_code,
out=out,
error=error,
)

# -----------------
assert status_code == PG_CTL__STATUS__OK

if out == "":
__class__._throw_error__pg_ctl_returns_an_empty_string(
_params
)

C_PID_PREFIX = "(PID: "

i = out.find(C_PID_PREFIX)

if i == -1:
__class__._throw_error__pg_ctl_returns_an_unexpected_string(
out,
_params
)

assert i > 0
assert i < len(out)
assert len(C_PID_PREFIX) <= len(out)
assert i <= len(out) - len(C_PID_PREFIX)

i += len(C_PID_PREFIX)
start_pid_s = i

while True:
if nAttempt == __class__._C_MAX_GET_PID_ATEMPTS:
errMsg = "Can't read postmaster pid file [{0}].".format(pid_file)
raise Exception(errMsg)
if i == len(out):
__class__._throw_error__pg_ctl_returns_an_unexpected_string(
out,
_params
)

nAttempt += 1
ch = out[i]

s1 = self.status()
if s1 != NodeStatus.Running:
return 0
if ch == ")":
break

try:
lines = self.os_ops.readlines(pid_file)
except Exception:
s2 = self.status()
if s2 == NodeStatus.Running:
raise
return 0

assert lines is not None # [2025-02-27] OK?
assert type(lines) == list # noqa: E721
if len(lines) == 0:
if ch.isdigit():
i += 1
continue

pid_s = lines[0]
assert type(pid_s) == str # noqa: E721
if len(pid_s) == 0:
continue
__class__._throw_error__pg_ctl_returns_an_unexpected_string(
out,
_params
)
assert False

if i == start_pid_s:
__class__._throw_error__pg_ctl_returns_an_unexpected_string(
out,
_params
)

pid = int(pid_s)
return pid
# TODO: Let's verify a length of pid string.

pid = int(out[start_pid_s:i])

if pid == 0:
__class__._throw_error__pg_ctl_returns_a_zero_pid(
out,
_params
)

assert pid != 0
return pid

@staticmethod
def _throw_error__pg_ctl_returns_an_empty_string(_params):
errLines = []
errLines.append("Utility pg_ctl returns empty string.")
errLines.append("Command line is {0}".format(_params))
raise RuntimeError("\n".join(errLines))

@staticmethod
def _throw_error__pg_ctl_returns_an_unexpected_string(out, _params):
errLines = []
errLines.append("Utility pg_ctl returns an unexpected string:")
errLines.append(out)
errLines.append("------------")
errLines.append("Command line is {0}".format(_params))
raise RuntimeError("\n".join(errLines))

@staticmethod
def _throw_error__pg_ctl_returns_a_zero_pid(out, _params):
errLines = []
errLines.append("Utility pg_ctl returns a zero pid. Output string is:")
errLines.append(out)
errLines.append("------------")
errLines.append("Command line is {0}".format(_params))
raise RuntimeError("\n".join(errLines))

@property
def auxiliary_pids(self):
Expand Down