[PATCH] Fix socket handle inheritance on Windows
Greetings,
I've discovered that PostgreSQL on Windows has a handle inheritance
problem that prevents clean restarts after the postmaster is killed
while child processes are running.
The issue is that Windows handles (files, sockets, pipes, shared memory)
are inheritable by default. When backends spawn child processes
archive_command, COPY TO PROGRAM, etc.—those children inherit all the
backend's handles. Windows uses reference counting, so inherited handles
keep resources alive even after the owning process exits.
I reproduced this with sockets:
1. Started PostgreSQL on port 6565
2. Connected with psql and ran:
\copy (select 1) to program 'powershell -Command "Start-Sleep 300"'
3. Used Sysinternals handle64.exe to examine handles:
- PowerShell had inherited socket handles (\Device\Afd)
- Same handle values in both processes (proving inheritance, not
separate opens)
4. Killed the postmaster
5. netstat showed port 6565 still LISTENING on the dead postmaster PID
6. Restart failed: "Address already in use"
7. Port only freed after killing PowerShell
The socket fix adds WSA_FLAG_NO_HANDLE_INHERIT to WSASocket() in
pgwin32_socket(), and calls SetHandleInformation() in
BackendInitialize() to mark the inherited client socket non-inheritable.
The latter is needed because handles passed to children become
inheritable again on Windows.
TAP test included that verifies the port is freed immediately after
postmaster exit rather than remaining in a zombie state.
The problem affects multiple handle types:
Files: https://commitfest.postgresql.org/patch/6197/
Sockets: Fixed by attached patch
Pipes: Not yet addressed
Shared memory: Not yet addressed (causes "pre-existing shared memory
block" errors)
Patches for pipes and shared memory will follow over the next couple of
days.
--
Bryan Green
EDB: https://www.enterprisedb.com
Attachments:
0001-Fix-socket-handle-inheritance-on-Windows-preventing-.patch.baktext/plain; charset=UTF-8; name=0001-Fix-socket-handle-inheritance-on-Windows-preventing-.patch.bakDownload
From 1eb87b403b67dbb3e7d59c8c82cd3d274d2372de Mon Sep 17 00:00:00 2001
From: Bryan Green <dbryan.green@gmail.com>
Date: Wed, 5 Nov 2025 22:24:35 -0600
Subject: [PATCH] Fix socket handle inheritance on Windows preventing restart
On Windows, socket handles are inheritable by default, causing child
processes spawned by backends (e.g., via COPY TO PROGRAM) to inherit
socket handles. Windows reference counting then prevents sockets from
being freed when the owning process exits, leading to "Address already
in use" errors on restart or zombie connections in netstat.
Fix by adding WSA_FLAG_NO_HANDLE_INHERIT to socket creation in
pgwin32_socket(), and calling SetHandleInformation() in
BackendInitialize() to make the inherited client socket non-inheritable
before spawning children. The latter is needed because handles passed
to child processes become inheritable again on Windows.
---
src/backend/port/win32/socket.c | 9 +-
src/backend/tcop/backend_startup.c | 12 ++
src/bin/pg_ctl/meson.build | 1 +
.../pg_ctl/t/005_socket_handle_inheritance.pl | 134 ++++++++++++++++++
4 files changed, 154 insertions(+), 2 deletions(-)
create mode 100644 src/bin/pg_ctl/t/005_socket_handle_inheritance.pl
diff --git a/src/backend/port/win32/socket.c b/src/backend/port/win32/socket.c
index a8538afe68..3d3530e71f 100644
--- a/src/backend/port/win32/socket.c
+++ b/src/backend/port/win32/socket.c
@@ -285,7 +285,11 @@ pgwin32_waitforsinglesocket(SOCKET s, int what, int timeout)
}
/*
- * Create a socket, setting it to overlapped and non-blocking
+ * Create a socket, setting it to overlapped, non-blocking, and non-inheritable.
+ *
+ * We must prevent child processes from inheriting socket handles. Otherwise,
+ * the kernel's reference counting means listening sockets can stay bound even
+ * after postmaster exit, preventing restart.
*/
SOCKET
pgwin32_socket(int af, int type, int protocol)
@@ -293,7 +297,8 @@ pgwin32_socket(int af, int type, int protocol)
SOCKET s;
unsigned long on = 1;
- s = WSASocket(af, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
+ s = WSASocket(af, type, protocol, NULL, 0,
+ WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
if (s == INVALID_SOCKET)
{
TranslateSocketError();
diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c
index 14d5fc0b19..2a1dd6b689 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -177,6 +177,18 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
port = MyProcPort = pq_init(client_sock);
MemoryContextSwitchTo(oldcontext);
+#ifdef WIN32
+ /*
+ * On Windows, the client socket inherited from the postmaster becomes
+ * inheritable again in this process. Prevent child processes spawned
+ * by this backend from inheriting it.
+ */
+ if (!SetHandleInformation((HANDLE) port->sock, HANDLE_FLAG_INHERIT, 0))
+ ereport(WARNING,
+ (errmsg_internal("could not disable socket handle inheritance: error code %lu",
+ GetLastError())));
+#endif
+
whereToSendOutput = DestRemote; /* now safe to ereport to client */
/* set these to empty in case they are needed before we set them up */
diff --git a/src/bin/pg_ctl/meson.build b/src/bin/pg_ctl/meson.build
index e92ba50f8a..a73248e4a7 100644
--- a/src/bin/pg_ctl/meson.build
+++ b/src/bin/pg_ctl/meson.build
@@ -27,6 +27,7 @@ tests += {
't/002_status.pl',
't/003_promote.pl',
't/004_logrotate.pl',
+ 't/005_socket_handle_inheritance.pl',
],
},
}
diff --git a/src/bin/pg_ctl/t/005_socket_handle_inheritance.pl b/src/bin/pg_ctl/t/005_socket_handle_inheritance.pl
new file mode 100644
index 0000000000..a58787ce89
--- /dev/null
+++ b/src/bin/pg_ctl/t/005_socket_handle_inheritance.pl
@@ -0,0 +1,134 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Test that socket handles are not inherited by child processes on Windows.
+#
+# Without the fix, child processes spawned via COPY TO PROGRAM inherit socket
+# handles from the backend. Windows reference counting prevents these sockets
+# from being freed when the postmaster exits, leaving the port bound to a dead
+# process (a "zombie" binding). This test verifies that after killing the
+# postmaster while a child process is still running, the listening port is
+# immediately freed rather than remaining in a zombie state.
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Time::HiRes qw(sleep);
+
+# This test is Windows-specific
+if ($^O ne 'MSWin32')
+{
+ plan skip_all => 'test is specific to Windows socket handle inheritance';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+# Get the port number for verification
+my $port = $node->port;
+
+# Spawn a long-running child process via COPY TO PROGRAM that will outlive
+# the postmaster. Without the fix, this child inherits socket handles.
+my $marker_file = $node->data_dir . '/ps_marker.txt';
+unlink $marker_file if -e $marker_file;
+
+$node->safe_psql(
+ 'postgres',
+ qq{\\copy (select 1) to program 'powershell -Command "echo marker > $marker_file; Start-Sleep 120"'}
+);
+
+# Wait for PowerShell to spawn
+my $ps_spawned = 0;
+for (my $i = 0; $i < 100; $i++)
+{
+ if (-e $marker_file)
+ {
+ $ps_spawned = 1;
+ last;
+ }
+ sleep 0.1;
+}
+
+ok($ps_spawned, 'child process spawned successfully');
+
+# Stop the postmaster (simulates a crash), leaving the child process running.
+$node->stop('immediate');
+sleep 0.5;
+
+# Verify that the listening port is freed immediately. With the bug, the port
+# remains bound to the dead postmaster PID because the child process inherited
+# the socket handles. With the fix, the port is freed because socket handles
+# were not inherited.
+my $netstat_output = `netstat -ano | findstr ":$port.*LISTENING"`;
+
+if ($netstat_output)
+{
+ fail('listening port remains bound after postmaster exit (zombie port)');
+ diag("Port is still bound - socket handles were inherited by child process");
+ diag("netstat output:\n$netstat_output");
+
+ if ($netstat_output =~ /LISTENING\s+(\d+)/)
+ {
+ my $bound_pid = $1;
+ my $process_name = get_process_name($bound_pid);
+
+ if ($process_name eq 'unknown' || $process_name eq '')
+ {
+ diag("Port bound to dead process (PID $bound_pid) - zombie binding detected");
+ }
+ else
+ {
+ diag("Port bound to: $process_name (PID $bound_pid)");
+ }
+ }
+}
+else
+{
+ pass('listening port freed immediately after postmaster exit');
+}
+
+# Additional verification: Confirm the port is actually available for binding.
+# This tests the real-world scenario that matters to users.
+my $can_bind = test_port_available($port);
+ok($can_bind, "port $port is available for new connections");
+
+# Cleanup
+cleanup_powershell_processes();
+unlink $marker_file if -e $marker_file;
+
+done_testing();
+
+# Test if port can actually be bound
+sub test_port_available
+{
+ my ($port) = @_;
+
+ use Socket;
+
+ socket(my $sock, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or return 0;
+ setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, 1);
+
+ my $addr = sockaddr_in($port, INADDR_ANY);
+ my $result = bind($sock, $addr);
+ close($sock);
+
+ return $result ? 1 : 0;
+}
+
+# Get process name by PID
+sub get_process_name
+{
+ my ($pid) = @_;
+ my $name = `powershell -Command "(Get-Process -Id $pid -ErrorAction SilentlyContinue).ProcessName" 2>nul`;
+ chomp $name;
+ return $name || 'unknown';
+}
+
+# Clean up test child processes
+sub cleanup_powershell_processes
+{
+ system('powershell -Command "Get-Process powershell -ErrorAction SilentlyContinue | Where-Object {$_.Id -ne $PID} | Stop-Process -Force -ErrorAction SilentlyContinue" 2>nul');
+}
\ No newline at end of file
--
2.46.0.windows.1
On 11/5/2025 11:06 PM, Bryan Green wrote:
Greetings,
I've discovered that PostgreSQL on Windows has a handle inheritance
problem that prevents clean restarts after the postmaster is killed
while child processes are running.The issue is that Windows handles (files, sockets, pipes, shared memory)
are inheritable by default. When backends spawn child processes
archive_command, COPY TO PROGRAM, etc.—those children inherit all the
backend's handles. Windows uses reference counting, so inherited handles
keep resources alive even after the owning process exits.I reproduced this with sockets:
1. Started PostgreSQL on port 6565
2. Connected with psql and ran:
\copy (select 1) to program 'powershell -Command "Start-Sleep 300"'
3. Used Sysinternals handle64.exe to examine handles:
- PowerShell had inherited socket handles (\Device\Afd)
- Same handle values in both processes (proving inheritance, not
separate opens)
4. Killed the postmaster
5. netstat showed port 6565 still LISTENING on the dead postmaster PID
6. Restart failed: "Address already in use"
7. Port only freed after killing PowerShellThe socket fix adds WSA_FLAG_NO_HANDLE_INHERIT to WSASocket() in
pgwin32_socket(), and calls SetHandleInformation() in
BackendInitialize() to mark the inherited client socket non-inheritable.
The latter is needed because handles passed to children become
inheritable again on Windows.TAP test included that verifies the port is freed immediately after
postmaster exit rather than remaining in a zombie state.The problem affects multiple handle types:
Files: https://commitfest.postgresql.org/patch/6197/
Sockets: Fixed by attached patch
Pipes: Not yet addressed
Shared memory: Not yet addressed (causes "pre-existing shared memory
block" errors)Patches for pipes and shared memory will follow over the next couple of
days.
Incorrect extension on the patch. Attached is the correct patch.
--
Bryan Green
EDB: https://www.enterprisedb.com
Attachments:
0001-Fix-socket-handle-inheritance-on-Windows-preventing-.patchtext/plain; charset=UTF-8; name=0001-Fix-socket-handle-inheritance-on-Windows-preventing-.patchDownload
From 1eb87b403b67dbb3e7d59c8c82cd3d274d2372de Mon Sep 17 00:00:00 2001
From: Bryan Green <dbryan.green@gmail.com>
Date: Wed, 5 Nov 2025 22:24:35 -0600
Subject: [PATCH] Fix socket handle inheritance on Windows preventing restart
On Windows, socket handles are inheritable by default, causing child
processes spawned by backends (e.g., via COPY TO PROGRAM) to inherit
socket handles. Windows reference counting then prevents sockets from
being freed when the owning process exits, leading to "Address already
in use" errors on restart or zombie connections in netstat.
Fix by adding WSA_FLAG_NO_HANDLE_INHERIT to socket creation in
pgwin32_socket(), and calling SetHandleInformation() in
BackendInitialize() to make the inherited client socket non-inheritable
before spawning children. The latter is needed because handles passed
to child processes become inheritable again on Windows.
---
src/backend/port/win32/socket.c | 9 +-
src/backend/tcop/backend_startup.c | 12 ++
src/bin/pg_ctl/meson.build | 1 +
.../pg_ctl/t/005_socket_handle_inheritance.pl | 134 ++++++++++++++++++
4 files changed, 154 insertions(+), 2 deletions(-)
create mode 100644 src/bin/pg_ctl/t/005_socket_handle_inheritance.pl
diff --git a/src/backend/port/win32/socket.c b/src/backend/port/win32/socket.c
index a8538afe68..3d3530e71f 100644
--- a/src/backend/port/win32/socket.c
+++ b/src/backend/port/win32/socket.c
@@ -285,7 +285,11 @@ pgwin32_waitforsinglesocket(SOCKET s, int what, int timeout)
}
/*
- * Create a socket, setting it to overlapped and non-blocking
+ * Create a socket, setting it to overlapped, non-blocking, and non-inheritable.
+ *
+ * We must prevent child processes from inheriting socket handles. Otherwise,
+ * the kernel's reference counting means listening sockets can stay bound even
+ * after postmaster exit, preventing restart.
*/
SOCKET
pgwin32_socket(int af, int type, int protocol)
@@ -293,7 +297,8 @@ pgwin32_socket(int af, int type, int protocol)
SOCKET s;
unsigned long on = 1;
- s = WSASocket(af, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
+ s = WSASocket(af, type, protocol, NULL, 0,
+ WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
if (s == INVALID_SOCKET)
{
TranslateSocketError();
diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c
index 14d5fc0b19..2a1dd6b689 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -177,6 +177,18 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
port = MyProcPort = pq_init(client_sock);
MemoryContextSwitchTo(oldcontext);
+#ifdef WIN32
+ /*
+ * On Windows, the client socket inherited from the postmaster becomes
+ * inheritable again in this process. Prevent child processes spawned
+ * by this backend from inheriting it.
+ */
+ if (!SetHandleInformation((HANDLE) port->sock, HANDLE_FLAG_INHERIT, 0))
+ ereport(WARNING,
+ (errmsg_internal("could not disable socket handle inheritance: error code %lu",
+ GetLastError())));
+#endif
+
whereToSendOutput = DestRemote; /* now safe to ereport to client */
/* set these to empty in case they are needed before we set them up */
diff --git a/src/bin/pg_ctl/meson.build b/src/bin/pg_ctl/meson.build
index e92ba50f8a..a73248e4a7 100644
--- a/src/bin/pg_ctl/meson.build
+++ b/src/bin/pg_ctl/meson.build
@@ -27,6 +27,7 @@ tests += {
't/002_status.pl',
't/003_promote.pl',
't/004_logrotate.pl',
+ 't/005_socket_handle_inheritance.pl',
],
},
}
diff --git a/src/bin/pg_ctl/t/005_socket_handle_inheritance.pl b/src/bin/pg_ctl/t/005_socket_handle_inheritance.pl
new file mode 100644
index 0000000000..a58787ce89
--- /dev/null
+++ b/src/bin/pg_ctl/t/005_socket_handle_inheritance.pl
@@ -0,0 +1,134 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Test that socket handles are not inherited by child processes on Windows.
+#
+# Without the fix, child processes spawned via COPY TO PROGRAM inherit socket
+# handles from the backend. Windows reference counting prevents these sockets
+# from being freed when the postmaster exits, leaving the port bound to a dead
+# process (a "zombie" binding). This test verifies that after killing the
+# postmaster while a child process is still running, the listening port is
+# immediately freed rather than remaining in a zombie state.
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Time::HiRes qw(sleep);
+
+# This test is Windows-specific
+if ($^O ne 'MSWin32')
+{
+ plan skip_all => 'test is specific to Windows socket handle inheritance';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+# Get the port number for verification
+my $port = $node->port;
+
+# Spawn a long-running child process via COPY TO PROGRAM that will outlive
+# the postmaster. Without the fix, this child inherits socket handles.
+my $marker_file = $node->data_dir . '/ps_marker.txt';
+unlink $marker_file if -e $marker_file;
+
+$node->safe_psql(
+ 'postgres',
+ qq{\\copy (select 1) to program 'powershell -Command "echo marker > $marker_file; Start-Sleep 120"'}
+);
+
+# Wait for PowerShell to spawn
+my $ps_spawned = 0;
+for (my $i = 0; $i < 100; $i++)
+{
+ if (-e $marker_file)
+ {
+ $ps_spawned = 1;
+ last;
+ }
+ sleep 0.1;
+}
+
+ok($ps_spawned, 'child process spawned successfully');
+
+# Stop the postmaster (simulates a crash), leaving the child process running.
+$node->stop('immediate');
+sleep 0.5;
+
+# Verify that the listening port is freed immediately. With the bug, the port
+# remains bound to the dead postmaster PID because the child process inherited
+# the socket handles. With the fix, the port is freed because socket handles
+# were not inherited.
+my $netstat_output = `netstat -ano | findstr ":$port.*LISTENING"`;
+
+if ($netstat_output)
+{
+ fail('listening port remains bound after postmaster exit (zombie port)');
+ diag("Port is still bound - socket handles were inherited by child process");
+ diag("netstat output:\n$netstat_output");
+
+ if ($netstat_output =~ /LISTENING\s+(\d+)/)
+ {
+ my $bound_pid = $1;
+ my $process_name = get_process_name($bound_pid);
+
+ if ($process_name eq 'unknown' || $process_name eq '')
+ {
+ diag("Port bound to dead process (PID $bound_pid) - zombie binding detected");
+ }
+ else
+ {
+ diag("Port bound to: $process_name (PID $bound_pid)");
+ }
+ }
+}
+else
+{
+ pass('listening port freed immediately after postmaster exit');
+}
+
+# Additional verification: Confirm the port is actually available for binding.
+# This tests the real-world scenario that matters to users.
+my $can_bind = test_port_available($port);
+ok($can_bind, "port $port is available for new connections");
+
+# Cleanup
+cleanup_powershell_processes();
+unlink $marker_file if -e $marker_file;
+
+done_testing();
+
+# Test if port can actually be bound
+sub test_port_available
+{
+ my ($port) = @_;
+
+ use Socket;
+
+ socket(my $sock, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or return 0;
+ setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, 1);
+
+ my $addr = sockaddr_in($port, INADDR_ANY);
+ my $result = bind($sock, $addr);
+ close($sock);
+
+ return $result ? 1 : 0;
+}
+
+# Get process name by PID
+sub get_process_name
+{
+ my ($pid) = @_;
+ my $name = `powershell -Command "(Get-Process -Id $pid -ErrorAction SilentlyContinue).ProcessName" 2>nul`;
+ chomp $name;
+ return $name || 'unknown';
+}
+
+# Clean up test child processes
+sub cleanup_powershell_processes
+{
+ system('powershell -Command "Get-Process powershell -ErrorAction SilentlyContinue | Where-Object {$_.Id -ne $PID} | Stop-Process -Force -ErrorAction SilentlyContinue" 2>nul');
+}
\ No newline at end of file
--
2.46.0.windows.1
On Fri, Nov 7, 2025 at 7:53 AM Bryan Green <dbryan.green@gmail.com> wrote:
The socket fix adds WSA_FLAG_NO_HANDLE_INHERIT to WSASocket() in
pgwin32_socket(), and calls SetHandleInformation() in
BackendInitialize() to mark the inherited client socket non-inheritable.
The latter is needed because handles passed to children become
inheritable again on Windows.
Right, this is a problem too and your analysis makes sense.
- s = WSASocket(af, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
+ s = WSASocket(af, type, protocol, NULL, 0,
+ WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
I wonder if we should control this with a new be-more-like-Unix
SOCK_CLOEXEC flag. In practice we might never want sockets created
this way to be inherited across CreateProcess, so we might always pass
SOCK_CLOEXEC in places like libpq (we already do that). But in theory
at least, someone might have a reason to want an inheritable socket
(no doubt with some complications), and it seems attractive to work
the same way cross-platform and also mirror the O_CLOEXEC behaviour
your other thread fixes, and I'm also looking further ahead to the
case of accepted sockets that have some additional needs (see below),
so it might be good to be explicit and consistent about the flags.
+#ifdef WIN32
+ /*
+ * On Windows, the client socket inherited from the postmaster becomes
+ * inheritable again in this process. Prevent child processes spawned
+ * by this backend from inheriting it.
+ */
+ if (!SetHandleInformation((HANDLE) port->sock, HANDLE_FLAG_INHERIT, 0))
+ ereport(WARNING,
+ (errmsg_internal("could not disable socket handle
inheritance: error code %lu",
+ GetLastError())));
+#endif
On Unix we also need the equivalent, but it's in pq_init():
#ifndef WIN32
/* Don't give the socket to any subprograms we execute. */
if (fcntl(port->sock, F_SETFD, FD_CLOEXEC) < 0)
elog(FATAL, "fcntl(F_SETFD) failed on socket: %m");
#endif
Would it make sense to have a socket_set_cloexec() function, following
the example of socket_set_nonblocking(), so we could harmonise the
calling code?
I think we'll also need an accept4() function for Windows that accepts
SOCK_CLOEXEC and converts it to WSA_FLAG_NO_HANDLE_INHERIT, now that
you've pointed this out. It's not needed for your problem report
here, and doesn't even make sense for EXEC_BACKEND by definition, but
I think we'll probably want it for multithreaded PostgreSQL on
Windows. With backends as threads, at least in one experimental
architecture with listener in the main/only sub-postmaster process,
there is a race window between ye olde accept() and
socket_set_cloexec() that leaks handles if a subprocess is created
concurrently by any thread. Adopting accept4() is pretty trivial
(something like v5-0001[1]/messages/by-id/CA+hUKGKPNFcfBQduqof4-7C=avjcSfdkKBGvQoRuAvfocnvY0A@mail.gmail.com), it just wasn't quite compelling enough to
commit before multithreading started heating up as a topic. I hadn't
previously grokked that Windows will need to be able to reach its
equivalent flag too for the reason you've pointed out.
I mention that as context for my suggestion that we might want an
explicit SOCK_CLOEXEC flag, because it finishes up being conditional
in the accept4() case assuming that design: multi-process mode needs
it except in EXEC_BACKEND builds which have to call
socket_set_cloexit() in the exec'd child by definition, while
in-development multi-threaded mode needs it on all platforms. (The
fact that macOS alone has so far refused to implement it is a bit
annoying, and potential workarounds are expensive, but that's a topic
for another day.)
[1]: /messages/by-id/CA+hUKGKPNFcfBQduqof4-7C=avjcSfdkKBGvQoRuAvfocnvY0A@mail.gmail.com
On 11/6/25 19:03, Thomas Munro wrote:
On Fri, Nov 7, 2025 at 7:53 AM Bryan Green <dbryan.green@gmail.com> wrote:
The socket fix adds WSA_FLAG_NO_HANDLE_INHERIT to WSASocket() in
pgwin32_socket(), and calls SetHandleInformation() in
BackendInitialize() to mark the inherited client socket non-inheritable.
The latter is needed because handles passed to children become
inheritable again on Windows.Right, this is a problem too and your analysis makes sense.
- s = WSASocket(af, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED); + s = WSASocket(af, type, protocol, NULL, 0, + WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);I wonder if we should control this with a new be-more-like-Unix
SOCK_CLOEXEC flag. In practice we might never want sockets created
this way to be inherited across CreateProcess, so we might always pass
SOCK_CLOEXEC in places like libpq (we already do that). But in theory
at least, someone might have a reason to want an inheritable socket
(no doubt with some complications), and it seems attractive to work
the same way cross-platform and also mirror the O_CLOEXEC behaviour
your other thread fixes, and I'm also looking further ahead to the
case of accepted sockets that have some additional needs (see below),
so it might be good to be explicit and consistent about the flags.+#ifdef WIN32 + /* + * On Windows, the client socket inherited from the postmaster becomes + * inheritable again in this process. Prevent child processes spawned + * by this backend from inheriting it. + */ + if (!SetHandleInformation((HANDLE) port->sock, HANDLE_FLAG_INHERIT, 0)) + ereport(WARNING, + (errmsg_internal("could not disable socket handle inheritance: error code %lu", + GetLastError()))); +#endifOn Unix we also need the equivalent, but it's in pq_init():
#ifndef WIN32
/* Don't give the socket to any subprograms we execute. */
if (fcntl(port->sock, F_SETFD, FD_CLOEXEC) < 0)
elog(FATAL, "fcntl(F_SETFD) failed on socket: %m");
#endifWould it make sense to have a socket_set_cloexec() function, following
the example of socket_set_nonblocking(), so we could harmonise the
calling code?
Yes, I think it would.
I think we'll also need an accept4() function for Windows that accepts
SOCK_CLOEXEC and converts it to WSA_FLAG_NO_HANDLE_INHERIT, now that
you've pointed this out. It's not needed for your problem report
here, and doesn't even make sense for EXEC_BACKEND by definition, but
I think we'll probably want it for multithreaded PostgreSQL on
Windows. With backends as threads, at least in one experimental
architecture with listener in the main/only sub-postmaster process,
there is a race window between ye olde accept() and
socket_set_cloexec() that leaks handles if a subprocess is created
concurrently by any thread. Adopting accept4() is pretty trivial
(something like v5-0001[1]), it just wasn't quite compelling enough to
commit before multithreading started heating up as a topic. I hadn't
previously grokked that Windows will need to be able to reach its
equivalent flag too for the reason you've pointed out.
I will look over [1].
I mention that as context for my suggestion that we might want an
explicit SOCK_CLOEXEC flag, because it finishes up being conditional
in the accept4() case assuming that design: multi-process mode needs
it except in EXEC_BACKEND builds which have to call
socket_set_cloexit() in the exec'd child by definition, while
in-development multi-threaded mode needs it on all platforms. (The
fact that macOS alone has so far refused to implement it is a bit
annoying, and potential workarounds are expensive, but that's a topic
for another day.)
If you agree, and I think you do, I will implement the SOCK_CLOEXEC
abstraction and the socket_set_cloexec helper for this. My bug fix will
be the first user.
I also need to handle the handles for shared memory and pipes. I will
probably just write those up tonight and share for review and discussion.
[1] /messages/by-id/CA+hUKGKPNFcfBQduqof4-7C=avjcSfdkKBGvQoRuAvfocnvY0A@mail.gmail.com
Thanks for your excellent review and suggestions.
--
Bryan Green
EDB: https://www.enterprisedb.com
On Fri, Nov 7, 2025 at 2:35 PM Bryan Green <dbryan.green@gmail.com> wrote:
If you agree, and I think you do, I will implement the SOCK_CLOEXEC
abstraction and the socket_set_cloexec helper for this. My bug fix will
be the first user.
+1
Thanks for working on all this stuff.
I also need to handle the handles for shared memory and pipes. I will
probably just write those up tonight and share for review and discussion.
Cool.