From: Álvaro Herrera <alvherre@kurilemu.de>
To: Bryan Green <dbryan.green@gmail.com>
Cc: pgsql-hackers@lists.postgresql.org, Sutou Kouhei <kou@clear-code.com>
Cc: michael@paquier.xyz
Cc: robertmhaas@gmail.com
Cc: horikyota.ntt@gmail.com
Cc: kuroda.hayato@fujitsu.com
Cc: shlok.kyal.oss@gmail.com
Cc: tgl@sss.pgh.pa.us
Subject: Re: pg_ctl start may return 0 even if the postmaster has been already started on Windows
Date: Mon, 2 Feb 2026 14:28:36 +0100
Message-ID: <202602021323.y4ctu6t5biso@alvherre.pgsql> (raw)
In-Reply-To: <07d00685-26b1-461d-99bf-7e973236e875@gmail.com>
Hello,
Somebody seems to have trimmed the CC field upthread for no apparent
reason. I put it back the way I think it was when that happened.
Here's a rebased version of this patch. This is pretty much just
Bryan's patch, which I agree is a good way forward, though perhaps not
very backpatchable. I suspect we need to consider this one for mastern
only, and use a less intrusive version for backpatching -- perhaps
Kyotaro's earlier version.
Regarding this patch, I haven't really reviewed it -- but I wonder why
we need two global variables to keep postmaster's PID: the newly
introduced pm_pid (which was previously a local variable only) and the
preexisting postmasterPID. I suspect this needs to be cleaned up.
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"Los minutos y los segundos son mercadería de la ciudad, donde un infeliz se
afana por no perder ni siquiera un segundo y no advierto que obrando de ese
modo pierde una vida." ("La vuelta de Don Camilo", G. Guareschi)
Attachments:
[text/x-diff] v3-0001-Fix-pg_ctl-on-Windows-to-reliably-detect-already-.patch (21.1K, ../202602021323.y4ctu6t5biso@alvherre.pgsql/2-v3-0001-Fix-pg_ctl-on-Windows-to-reliably-detect-already-.patch)
download | inline diff:
From 566ef628e5ac01f59b66fe717ba686e7b45ff05b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Mon, 2 Feb 2026 14:16:08 +0100
Subject: [PATCH v3] Fix pg_ctl on Windows to reliably detect already-running
postmaster.
The previous implementation invoked postgres.exe via cmd.exe, which meant
pg_ctl got the PID of the shell wrapper rather than the actual postmaster
process. This caused problems in wait_for_postmaster_start(), which tries
to verify that the PID in postmaster.pid matches the one we started. The
mismatch led to a timing window where pg_ctl could incorrectly report
success when attempting to start an already-running cluster.
Fix by replacing the cmd.exe wrapper with a direct CreateProcess() call.
This gives us the real postgres.exe PID immediately, eliminating the
need for process tree walking or other heuristics to find the actual
postmaster. We handle I/O redirection using Windows handle-based APIs
instead of shell syntax, which is more reliable anyway.
Author: Bryan Green <dbryan.green@gmail.com>
Reported-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Suggested-by: Sutou Kouhei <kou@clear-code.com>
Discussion: https://postgr.es/m/20240713.064114.901257637518958249.kou@clear-code.com
---
src/bin/pg_ctl/pg_ctl.c | 522 ++++++++++++++++++++++++++++------------
1 file changed, 369 insertions(+), 153 deletions(-)
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.cindex 122856b599e..3004d836eb3 100644--- a/src/bin/pg_ctl/pg_ctl.c+++ b/src/bin/pg_ctl/pg_ctl.c@@ -73,6 +73,7 @@ typedef enum
StaticAssertDecl(USECS_PER_SEC % WAITS_PER_SEC == 0,
"WAITS_PER_SEC must divide USECS_PER_SEC evenly");
+static pid_t pm_pid = 0;
static bool do_wait = true;
static int wait_seconds = DEFAULT_WAIT;
static bool wait_seconds_arg = false;
@@ -100,7 +101,6 @@ static char version_file[MAXPGPATH];
static char pid_file[MAXPGPATH];
static char promote_file[MAXPGPATH];
static char logrotate_file[MAXPGPATH];
-
static volatile pid_t postmasterPID = -1;
#ifdef WIN32
@@ -143,12 +143,14 @@ static void WINAPI pgwin32_ServiceMain(DWORD, LPTSTR *);
static void pgwin32_doRunAsService(void);
static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_service);
static PTOKEN_PRIVILEGES GetPrivilegesToDelete(HANDLE hToken);
+static void InheritStdHandles(STARTUPINFO *si);+static HANDLE create_restricted_token(void);
#endif
static pid_t get_pgpid(bool is_status_request);
static char **readfile(const char *path, int *numlines);
static void free_readfile(char **optlines);
-static pid_t start_postmaster(void);+static void start_postmaster(void);
static void read_post_opts(void);
static WaitPMResult wait_for_postmaster_start(pid_t pm_pid, bool do_checkpoint);
@@ -422,56 +424,319 @@ free_readfile(char **optlines)
free(optlines);
}
+
/*
* start/test/stop routines
*/
+#ifdef WIN32+/*+ * Helper function to drop privileges before launching postgres.+ */+static HANDLE+create_restricted_token(void)+{+ HANDLE origToken;+ HANDLE restrictedToken;+ SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};+ SID_AND_ATTRIBUTES dropSid;+ PTOKEN_PRIVILEGES delPrivs;+ BOOL b;++ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken))+ {+ write_stderr(_("%s: could not open process token: error code %lu\n"),+ progname, GetLastError());+ return NULL;+ }++ ZeroMemory(&dropSid, sizeof(dropSid));+ if (!AllocateAndInitializeSid(&NtAuthority, 2,+ SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0,+ 0, &dropSid.Sid))+ {+ write_stderr(_("%s: could not allocate SIDs: error code %lu\n"),+ progname, GetLastError());+ CloseHandle(origToken);+ return NULL;+ }++ delPrivs = GetPrivilegesToDelete(origToken);+ if (delPrivs == NULL)+ {+ FreeSid(dropSid.Sid);+ CloseHandle(origToken);+ return NULL;+ }++ b = CreateRestrictedToken(origToken,+ 0,+ 1,+ &dropSid,+ delPrivs->PrivilegeCount, delPrivs->Privileges,+ 0, NULL,+ &restrictedToken);++ free(delPrivs);+ FreeSid(dropSid.Sid);+ CloseHandle(origToken);++ if (!b)+ {+ write_stderr(_("%s: could not create restricted token: error code %lu\n"),+ progname, GetLastError());+ return NULL;+ }++ return restrictedToken;+}+++static pid_t+start_postmaster_win32(void)+{+ HANDLE hOutputFile = INVALID_HANDLE_VALUE;+ HANDLE hErrorFile = INVALID_HANDLE_VALUE;+ HANDLE hInputFile = INVALID_HANDLE_VALUE;+ HANDLE restrictedToken = NULL;+ STARTUPINFO si;+ PROCESS_INFORMATION pi;+ char cmd[MAXPGPATH * 2 + 256];+ SECURITY_ATTRIBUTES sa;+ DWORD creation_flags;+ DWORD create_error;+ BOOL ret;+ char *cmdline;+ int cmdlen;+ BOOL own_handles = FALSE;++ cmdlen = snprintf(cmd, sizeof(cmd), "\"%s\"%s%s%s%s",+ exec_path,+ pgdata_opt ? " " : "",+ pgdata_opt ? pgdata_opt : "",+ post_opts ? " " : "",+ post_opts ? post_opts : "");++ if (cmdlen >= sizeof(cmd))+ {+ write_stderr(_("%s: command line too long\n"), progname);+ return 0;+ }++ cmdline = pg_strdup(cmd);++ restrictedToken = create_restricted_token();+ if (restrictedToken == NULL)+ {+ free(cmdline);+ exit(1);+ }++ ZeroMemory(&si, sizeof(si));+ si.cb = sizeof(si);++ if (log_file != NULL)+ {+ ZeroMemory(&sa, sizeof(sa));+ sa.nLength = sizeof(sa);+ sa.bInheritHandle = TRUE;+ sa.lpSecurityDescriptor = NULL;++ hInputFile = CreateFile("NUL",+ GENERIC_READ,+ FILE_SHARE_READ | FILE_SHARE_WRITE,+ &sa,+ OPEN_EXISTING,+ FILE_ATTRIBUTE_NORMAL,+ NULL);++ if (hInputFile == INVALID_HANDLE_VALUE)+ {+ write_stderr(_("%s: could not open NUL device: error code %lu\n"),+ progname, GetLastError());+ CloseHandle(restrictedToken);+ free(cmdline);+ return 0;+ }++ hOutputFile = CreateFile(log_file,+ GENERIC_WRITE,+ FILE_SHARE_READ | FILE_SHARE_WRITE,+ &sa,+ OPEN_ALWAYS,+ FILE_ATTRIBUTE_NORMAL,+ NULL);++ if (hOutputFile == INVALID_HANDLE_VALUE)+ {+ write_stderr(_("%s: could not open log file \"%s\": error code %lu\n"),+ progname, log_file, GetLastError());+ CloseHandle(hInputFile);+ CloseHandle(restrictedToken);+ free(cmdline);+ return 0;+ }++ if (SetFilePointer(hOutputFile, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER &&+ GetLastError() != NO_ERROR)+ {+ write_stderr(_("%s: could not seek to end of log file: error code %lu\n"),+ progname, GetLastError());+ CloseHandle(hOutputFile);+ CloseHandle(hInputFile);+ CloseHandle(restrictedToken);+ free(cmdline);+ return 0;+ }++ hErrorFile = hOutputFile;++ si.dwFlags = STARTF_USESTDHANDLES;+ si.hStdInput = hInputFile;+ si.hStdOutput = hOutputFile;+ si.hStdError = hErrorFile;++ creation_flags = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP | CREATE_SUSPENDED;++ own_handles = TRUE;+ }+ else+ {+ InheritStdHandles(&si);+ creation_flags = CREATE_SUSPENDED;+ }++ ZeroMemory(&pi, sizeof(pi));++ ret = CreateProcessAsUser(restrictedToken, /* restricted security token */+ NULL, /* application name */+ cmdline, /* command line */+ NULL, /* process security attributes */+ NULL, /* thread security attributes */+ TRUE, /* inherit handles */+ creation_flags, /* creation flags */+ NULL, /* environment */+ pg_data, /* current directory */+ &si, /* startup info */+ &pi); /* process info */++ create_error = GetLastError();++ if (own_handles)+ {+ if (hInputFile != INVALID_HANDLE_VALUE)+ CloseHandle(hInputFile);+ if (hOutputFile != INVALID_HANDLE_VALUE)+ CloseHandle(hOutputFile);+ }++ CloseHandle(restrictedToken);+ free(cmdline);++ if (!ret)+ {+ write_stderr(_("%s: could not start server: error code %lu\n"),+ progname, create_error);+ return 0;+ }++ ResumeThread(pi.hThread);+ CloseHandle(pi.hThread);++ /*+ * When there's no log file, wait briefly to see if the process exits+ * immediately. This distinguishes postgres -C queries from actual server+ * startup. Two seconds is long enough to catch quick exits even on slow+ * CI systems, but still fast enough not to be annoying for normal server+ * startup.+ */+ if (log_file == NULL)+ {+ DWORD wait_result;+ DWORD exit_code;++ wait_result = WaitForSingleObject(pi.hProcess, 2000);++ if (wait_result == WAIT_TIMEOUT)+ {+ /* Still running after 2 seconds - assume real server startup */+ postmasterProcess = pi.hProcess;+ return (pid_t) pi.dwProcessId;+ }+ else if (wait_result == WAIT_OBJECT_0)+ {+ /* Process exited quickly - server didn't stay running */+ exit_code = 0;+ GetExitCodeProcess(pi.hProcess, &exit_code);+ CloseHandle(pi.hProcess);+ write_stderr(_("%s: could not start server\n"), progname);+ return 0;+ }+ else+ {+ /* Wait failed */+ write_stderr(_("%s: error waiting for server: error code %lu\n"),+ progname, GetLastError());+ CloseHandle(pi.hProcess);+ return 0;+ }+ }++ postmasterProcess = pi.hProcess;+ return (pid_t) pi.dwProcessId;+}+#endif+
/*
- * Start the postmaster and return its PID.+ * start_postmaster
*
- * Currently, on Windows what we return is the PID of the shell process- * that launched the postmaster (and, we trust, is waiting for it to exit).- * So the PID is usable for "is the postmaster still running" checks,- * but cannot be compared directly to postmaster.pid.+ * Wrapper around the platform-specific code to launch the postmaster.
*
- * On Windows, we also save aside a handle to the shell process in- * "postmasterProcess", which the caller should close when done with it.+ * On Unix, we fork and exec. There's no extra process layer;+ * we get the postgres PID directly.+ *+ * On Windows, we use CreateProcess to launch postgres.exe directly, which+ * gives us its PID immediately.
*/
-static pid_t+static void
start_postmaster(void)
{
+#ifdef WIN32+ pm_pid = start_postmaster_win32();+ if (pm_pid == 0)+ exit(1);+#else
char *cmd;
+ int cmdlen;+ pid_t fork_pid;-#ifndef WIN32- pid_t pm_pid;-- /* Flush stdio channels just before fork, to avoid double-output problems */+ /* Flush stdio to avoid double-output problems after fork */
fflush(NULL);
#ifdef EXEC_BACKEND
pg_disable_aslr();
#endif
- pm_pid = fork();- if (pm_pid < 0)+ fork_pid = fork();+ if (fork_pid < 0)
{
- /* fork failed */
write_stderr(_("%s: could not start server: %m\n"),
progname);
exit(1);
}
- if (pm_pid > 0)+ if (fork_pid > 0)
{
- /* fork succeeded, in parent */- return pm_pid;+ /* Parent process */+ pm_pid = fork_pid;+ return;
}
- /* fork succeeded, in child */+ /* Child process */
/*
- * If possible, detach the postmaster process from the launching process- * group and make it a group leader, so that it doesn't get signaled along- * with the current group that launched it.+ * Detach from the parent's process group so we don't get signaled along+ * with it. This is the equivalent of what CREATE_NEW_PROCESS_GROUP does+ * on Windows.
*/
#ifdef HAVE_SETSID
if (setsid() < 0)
@@ -483,17 +748,44 @@ start_postmaster(void)
#endif
/*
- * Since there might be quotes to handle here, it is easier simply to pass- * everything to a shell to process them. Use exec so that the postmaster- * has the same PID as the current child process.+ * Build the shell command. We use "exec" so the shell replaces itself+ * with postgres, rather than keeping the shell process around.
*/
if (log_file != NULL)
- cmd = psprintf("exec \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1",- exec_path, pgdata_opt, post_opts,- DEVNULL, log_file);+ {+ cmdlen = snprintf(NULL, 0, "exec \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1",+ exec_path,+ pgdata_opt ? pgdata_opt : "",+ post_opts ? post_opts : "",+ DEVNULL, log_file);+ }
else
- cmd = psprintf("exec \"%s\" %s%s < \"%s\" 2>&1",- exec_path, pgdata_opt, post_opts, DEVNULL);+ {+ cmdlen = snprintf(NULL, 0, "exec \"%s\" %s%s < \"%s\" 2>&1",+ exec_path,+ pgdata_opt ? pgdata_opt : "",+ post_opts ? post_opts : "",+ DEVNULL);+ }++ cmd = pg_malloc(cmdlen + 1);++ if (log_file != NULL)+ {+ snprintf(cmd, cmdlen + 1, "exec \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1",+ exec_path,+ pgdata_opt ? pgdata_opt : "",+ post_opts ? post_opts : "",+ DEVNULL, log_file);+ }+ else+ {+ snprintf(cmd, cmdlen + 1, "exec \"%s\" %s%s < \"%s\" 2>&1",+ exec_path,+ pgdata_opt ? pgdata_opt : "",+ post_opts ? post_opts : "",+ DEVNULL);+ }
(void) execl("/bin/sh", "/bin/sh", "-c", cmd, (char *) NULL);
@@ -502,93 +794,26 @@ start_postmaster(void)
progname);
exit(1);
- return 0; /* keep dumb compilers quiet */--#else /* WIN32 */-- /*- * As with the Unix case, it's easiest to use the shell (CMD.EXE) to- * handle redirection etc. Unfortunately CMD.EXE lacks any equivalent of- * "exec", so we don't get to find out the postmaster's PID immediately.- */- PROCESS_INFORMATION pi;- const char *comspec;-- /* Find CMD.EXE location using COMSPEC, if it's set */- comspec = getenv("COMSPEC");- if (comspec == NULL)- comspec = "CMD";-- if (log_file != NULL)- {- /*- * First, open the log file if it exists. The idea is that if the- * file is still locked by a previous postmaster run, we'll wait until- * it comes free, instead of failing with ERROR_SHARING_VIOLATION.- * (It'd be better to open the file in a sharing-friendly mode, but we- * can't use CMD.EXE to do that, so work around it. Note that the- * previous postmaster will still have the file open for a short time- * after removing postmaster.pid.)- *- * If the log file doesn't exist, we *must not* create it here. If we- * were launched with higher privileges than the restricted process- * will have, the log file might end up with permissions settings that- * prevent the postmaster from writing on it.- */- int fd = open(log_file, O_RDWR, 0);-- if (fd == -1)- {- /*- * ENOENT is expectable since we didn't use O_CREAT. Otherwise- * complain. We could just fall through and let CMD.EXE report- * the problem, but its error reporting is pretty miserable.- */- if (errno != ENOENT)- {- write_stderr(_("%s: could not open log file \"%s\": %m\n"),- progname, log_file);- exit(1);- }- }- else- close(fd);-- cmd = psprintf("\"%s\" /C \"\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1\"",- comspec, exec_path, pgdata_opt, post_opts, DEVNULL, log_file);- }- else- cmd = psprintf("\"%s\" /C \"\"%s\" %s%s < \"%s\" 2>&1\"",- comspec, exec_path, pgdata_opt, post_opts, DEVNULL);-- if (!CreateRestrictedProcess(cmd, &pi, false))- {- write_stderr(_("%s: could not start server: error code %lu\n"),- progname, GetLastError());- exit(1);- }- /* Don't close command process handle here; caller must do so */- postmasterProcess = pi.hProcess;- CloseHandle(pi.hThread);- return pi.dwProcessId; /* Shell's PID, not postmaster's! */
#endif /* WIN32 */
}
-
/*
- * Wait for the postmaster to become ready.+ * wait_for_postmaster_start
*
- * On Unix, pm_pid is the PID of the just-launched postmaster. On Windows,- * it may be the PID of an ancestor shell process, so we can't check the- * contents of postmaster.pid quite as carefully.+ * Wait for the postmaster to finish starting up and become ready to+ * accept connections.
*
- * On Windows, the static variable postmasterProcess is an implicit argument- * to this routine; it contains a handle to the postmaster process or an- * ancestor shell process thereof.+ * On entry, pm_pid should be the PID of the postmaster we just launched.+ * We poll postmaster.pid to see when it's been created and whether the+ * PID in it matches pm_pid. Once we see a matching PID and the status+ * line says "ready", we're done.
*
- * Note that the checkpoint parameter enables a Windows service control- * manager checkpoint, it's got nothing to do with database checkpoints!!+ * On Windows, we also use the postmasterProcess handle (set by+ * start_postmaster_win32) to detect if the process dies unexpectedly.+ *+ * Returns POSTMASTER_READY if all is well, POSTMASTER_STILL_STARTING if+ * we ran out of time, or POSTMASTER_FAILED if the postmaster died.
*/
static WaitPMResult
wait_for_postmaster_start(pid_t pm_pid, bool do_checkpoint)
@@ -601,63 +826,48 @@ wait_for_postmaster_start(pid_t pm_pid, bool do_checkpoint)
int numlines;
/*
- * Try to read the postmaster.pid file. If it's not valid, or if the- * status line isn't there yet, just keep waiting.+ * Try to read postmaster.pid. If it's not there yet, or doesn't have+ * enough lines, just move on to the next iteration.
*/
if ((optlines = readfile(pid_file, &numlines)) != NULL &&
numlines >= LOCK_FILE_LINE_PM_STATUS)
{
- /* File is complete enough for us, parse it */
pid_t pmpid;
time_t pmstart;
/*
- * Make sanity checks. If it's for the wrong PID, or the recorded- * start time is before pg_ctl started, then either we are looking- * at the wrong data directory, or this is a pre-existing pidfile- * that hasn't (yet?) been overwritten by our child postmaster.- * Allow 2 seconds slop for possible cross-process clock skew.+ * Check that the PID and start time in the file match what we+ * expect. A mismatch means either we're looking at the wrong+ * data directory, or this is a stale pidfile left over from a+ * previous postmaster. We allow 2 seconds slop in the timestamp+ * comparison to handle clock skew between processes.
*/
pmpid = atol(optlines[LOCK_FILE_LINE_PID - 1]);
pmstart = atoll(optlines[LOCK_FILE_LINE_START_TIME - 1]);
- if (pmstart >= start_time - 2 &&-#ifndef WIN32- pmpid == pm_pid-#else- /* Windows can only reject standalone-backend PIDs */- pmpid > 0-#endif- )++ if (pmstart >= start_time - 2 && pmpid == pm_pid)
{
/*
- * OK, seems to be a valid pidfile from our child. Check the- * status line (this assumes a v10 or later server).+ * It's the right pidfile. Check whether the postmaster is+ * ready to accept connections.
*/
char *pmstatus = optlines[LOCK_FILE_LINE_PM_STATUS - 1];
if (strcmp(pmstatus, PM_STATUS_READY) == 0 ||
strcmp(pmstatus, PM_STATUS_STANDBY) == 0)
{
- /* postmaster is done starting up */
free_readfile(optlines);
return POSTMASTER_READY;
}
}
+ free_readfile(optlines);
}
- /*- * Free the results of readfile.- *- * This is safe to call even if optlines is NULL.- */- free_readfile(optlines);+
/*
- * Check whether the child postmaster process is still alive. This- * lets us exit early if the postmaster fails during startup.- *- * On Windows, we may be checking the postmaster's parent shell, but- * that's fine for this purpose.+ * Check whether the postmaster process is still alive. If it's gone,+ * there's no point in waiting any longer.
*/
{
bool pm_died;
@@ -670,7 +880,10 @@ wait_for_postmaster_start(pid_t pm_pid, bool do_checkpoint)
#endif
if (pm_died)
{
- /* See if postmaster terminated intentionally */+ /*+ * Postmaster died. Check if it was an intentional shutdown+ * during recovery (which is OK) or an actual failure.+ */
if (get_control_dbstate() == DB_SHUTDOWNED_IN_RECOVERY)
return POSTMASTER_SHUTDOWN_IN_RECOVERY;
else
@@ -678,19 +891,18 @@ wait_for_postmaster_start(pid_t pm_pid, bool do_checkpoint)
}
}
- /* Startup still in process; wait, printing a dot once per second */+ /* Still starting up. Print a dot once per second for user feedback */
if (i % WAITS_PER_SEC == 0)
{
#ifdef WIN32
+ /*+ * On Windows, if we're running as a service, increment the+ * checkpoint counter so the service control manager doesn't think+ * we've hung. (This has nothing to do with database checkpoints,+ * it's a Windows service thing.)+ */
if (do_checkpoint)
{
- /*- * Increment the wait hint by 6 secs (connection timeout +- * sleep). We must do this to indicate to the SCM that our- * startup time is changing, otherwise it'll usually send a- * stop signal after 20 seconds, despite incrementing the- * checkpoint counter.- */
status.dwWaitHint += 6000;
status.dwCheckPoint++;
SetServiceStatus(hStatus, (LPSERVICE_STATUS) &status);
@@ -703,7 +915,7 @@ wait_for_postmaster_start(pid_t pm_pid, bool do_checkpoint)
pg_usleep(USECS_PER_SEC / WAITS_PER_SEC);
}
- /* out of patience; report that postmaster is still starting up */+ /* Ran out of time */
return POSTMASTER_STILL_STARTING;
}
@@ -932,7 +1144,6 @@ static void
do_start(void)
{
pid_t old_pid = 0;
- pid_t pm_pid;
if (ctl_command != RESTART_COMMAND)
{
@@ -971,7 +1182,12 @@ do_start(void)
}
#endif
- pm_pid = start_postmaster();+ start_postmaster();+ if (pm_pid == 0)+ {+ write_stderr(_("%s: could not start server\n"), progname);+ exit(1);+ }
if (do_wait)
{
--
2.47.3
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: alvherre@kurilemu.de, dbryan.green@gmail.com, kou@clear-code.com, michael@paquier.xyz, robertmhaas@gmail.com, horikyota.ntt@gmail.com, kuroda.hayato@fujitsu.com, shlok.kyal.oss@gmail.com, tgl@sss.pgh.pa.us
Subject: Re: pg_ctl start may return 0 even if the postmaster has been already started on Windows
In-Reply-To: <202602021323.y4ctu6t5biso@alvherre.pgsql>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox