From 0003a0238f7e5635ff00885c4d5bbe5382fafe3b Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 4 Aug 2025 23:50:04 +0900 Subject: [PATCH 01/20] Add unix domain socket for Windows --- Lib/test/test_socket.py | 6 +++++- Modules/socketmodule.c | 2 ++ Modules/socketmodule.h | 3 +++ PC/pyconfig.h | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 3dd67b2a2aba97e..d8094cda10ff954 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -5131,7 +5131,7 @@ def __init__(self, methodName='runTest'): def _check_defaults(self, sock): self.assertIsInstance(sock, socket.socket) - if hasattr(socket, 'AF_UNIX'): + if sys.platform != 'win32' and hasattr(socket, 'AF_UNIX'): self.assertEqual(sock.family, socket.AF_UNIX) else: self.assertEqual(sock.family, socket.AF_INET) @@ -6188,6 +6188,8 @@ def bind(self, sock, path): else: raise + @unittest.skipIf(sys.platform == 'win32', + 'Windows will raise Error if is not bound') def testUnbound(self): # Issue #30205 (note getsockname() can return None on OS X) self.assertIn(self.sock.getsockname(), ('', None)) @@ -6227,6 +6229,8 @@ def testUnencodableAddr(self): @unittest.skipIf(sys.platform in ('linux', 'android'), 'Linux behavior is tested by TestLinuxAbstractNamespace') + @unittest.skipIf(sys.platform == 'win32', + 'Windows allow bind on empty path') def testEmptyAddress(self): # Test that binding empty address fails. self.assertRaises(OSError, self.sock.bind, "") diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index f3ad01854de93b2..967a097b8651f41 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -334,6 +334,8 @@ typedef struct { /* IMPORTANT: make sure the list ordered by descending build_number */ static FlagRuntimeInfo win_runtime_flags[] = { + /* available starting with Windows 10 1803 */ + {17134, "AF_UNIX"}, /* available starting with Windows 10 1709 */ {16299, "TCP_KEEPIDLE"}, {16299, "TCP_KEEPINTVL"}, diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index 7fd929af5f27b40..b566d34a462d09b 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -94,8 +94,11 @@ typedef int socklen_t; # include #endif /* MS_WINDOWS */ +#define HAVE_AFUNIX_H 1 #ifdef HAVE_SYS_UN_H # include +#elif HAVE_AFUNIX_H +# include #else # undef AF_UNIX #endif diff --git a/PC/pyconfig.h b/PC/pyconfig.h index 0e8379387cd0251..c1212de445948c5 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h @@ -671,6 +671,9 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */ /* Define if you have the header file. */ /* #define HAVE_SYS_UN_H 1 */ +/* Define if you have the header file. */ +#define HAVE_AFUNIX_H 1 + /* Define if you have the header file. */ /* #define HAVE_SYS_UTIME_H 1 */ From 2eb7917f4906e444cc4990bfcf9e59ce712e276c Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 5 Aug 2025 00:48:28 +0900 Subject: [PATCH 02/20] Skip some tests on socketserver test --- Lib/test/test_socketserver.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index 0f62f9eb200e42d..e165f4e0f409df8 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -8,6 +8,7 @@ import select import signal import socket +import sys import threading import unittest import socketserver @@ -218,18 +219,24 @@ def test_ForkingUDPServer(self): self.dgram_examine) @requires_unix_sockets + @unittest.skipIf(sys.platform=="win32", + "Unix with Dadagram is not supported on Windows") def test_UnixDatagramServer(self): self.run_server(socketserver.UnixDatagramServer, socketserver.DatagramRequestHandler, self.dgram_examine) @requires_unix_sockets + @unittest.skipIf(sys.platform=="win32", + "Unix with Dadagram is not supported on Windows") def test_ThreadingUnixDatagramServer(self): self.run_server(socketserver.ThreadingUnixDatagramServer, socketserver.DatagramRequestHandler, self.dgram_examine) @requires_unix_sockets + @unittest.skipIf(sys.platform=="win32", + "Unix with Dadagram is not supported on Windows") @requires_forking def test_ForkingUnixDatagramServer(self): self.run_server(socketserver.ForkingUnixDatagramServer, From 49a71497fc252a4fa4e4ae1d3d16efb01879723f Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 5 Aug 2025 23:56:16 +0900 Subject: [PATCH 03/20] Fix more tests --- Lib/test/test_asyncio/test_base_events.py | 2 ++ Lib/test/test_asyncio/test_events.py | 5 +++++ Lib/test/test_asyncio/utils.py | 8 ++++++++ Lib/test/test_pathlib/test_pathlib.py | 1 + Lib/test/test_stat.py | 1 + 5 files changed, 17 insertions(+) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 8c02de77c247404..34d96dd1025b812 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1939,6 +1939,8 @@ def test_create_datagram_endpoint_sock(self): self.assertEqual('CLOSED', protocol.state) @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') + @unittest.skipIf(sys.platform == 'win32', 'AF_UNIX support for asyncio is ' + 'not implemented on Windows for now') def test_create_datagram_endpoint_sock_unix(self): fut = self.loop.create_datagram_endpoint( lambda: MyDatagramProto(create_future=True, loop=self.loop), diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 919d543b0329e9f..5fc9556f9920f1c 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1035,6 +1035,9 @@ def test_create_server_reuse_port(self): server.close() def _make_unix_server(self, factory, **kwargs): + if sys.platform == 'win32': + raise unittest.SkipTest('AF_UNIX support for asyncio is not ' + 'implemented on Windows for now') path = test_utils.gen_unix_socket_path() self.addCleanup(lambda: os.path.exists(path) and os.unlink(path)) @@ -1072,6 +1075,8 @@ def test_create_unix_server(self): server.close() @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets') + @unittest.skipIf(sys.platform == 'win32', 'AF_UNIX support for asyncio is ' + 'not implemented on Windows for now') def test_create_unix_server_path_socket_error(self): proto = MyProto(loop=self.loop) sock = socket.socket() diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py index a480e16e81bb91a..05010b89929d695 100644 --- a/Lib/test/test_asyncio/utils.py +++ b/Lib/test/test_asyncio/utils.py @@ -200,6 +200,8 @@ def app(environ, start_response): if hasattr(socket, 'AF_UNIX'): class UnixHTTPServer(socketserver.UnixStreamServer, HTTPServer): + if sys.platform == 'win32': + allow_reuse_address = False def server_bind(self): socketserver.UnixStreamServer.server_bind(self) @@ -243,6 +245,9 @@ def gen_unix_socket_path(): @contextlib.contextmanager def unix_socket_path(): + if sys.platform == 'win32': + raise unittest.SkipTest('AF_UNIX support for asyncio is not ' + 'implemented on Windows for now') path = gen_unix_socket_path() try: yield path @@ -255,6 +260,9 @@ def unix_socket_path(): @contextlib.contextmanager def run_test_unix_server(*, use_ssl=False): + if sys.platform == 'win32': + raise unittest.SkipTest('AF_UNIX support for asyncio is not ' + 'implemented on Windows for now') with unix_socket_path() as path: yield from _run_test_server(address=path, use_ssl=use_ssl, server_cls=SilentUnixWSGIServer, diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index a1105aae6351b6c..e925f4f8508d86d 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -2747,6 +2747,7 @@ def test_is_socket_false(self): @unittest.skipIf( is_wasi, "Cannot create socket on WASI." ) + @unittest.skipIf(sys.platform=='win32', "didn't work on Windows") def test_is_socket_true(self): P = self.cls(self.base, 'mysock') sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index 5fd25d5012c4253..1f17177b4c5b3fe 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -215,6 +215,7 @@ def test_devices(self): break @socket_helper.skip_unless_bind_unix_socket + @unittest.skipIf(sys.platform=='win32', "didn't work on Windows") def test_socket(self): with socket.socket(socket.AF_UNIX) as s: s.bind(TESTFN) From e1c5ceb0c84cd72ffa94bed740d488c67f0fe049 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 6 Aug 2025 00:27:49 +0900 Subject: [PATCH 04/20] Detect if we have AF_UNIX header --- .../next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst | 1 + PC/pyconfig.h | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst diff --git a/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst b/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst new file mode 100644 index 000000000000000..8fa31e146e8bb24 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst @@ -0,0 +1 @@ +Add Unix domain socket on Windows. Patched by AN Long. diff --git a/PC/pyconfig.h b/PC/pyconfig.h index c1212de445948c5..6fc834755266e57 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h @@ -672,7 +672,9 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */ /* #define HAVE_SYS_UN_H 1 */ /* Define if you have the header file. */ +#if defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4) #define HAVE_AFUNIX_H 1 +#endif /* Define if you have the header file. */ /* #define HAVE_SYS_UTIME_H 1 */ From 175a96e34924cc4ce81ad0cd8b1e8fdbcf439d1b Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 6 Aug 2025 00:34:18 +0900 Subject: [PATCH 05/20] Suppress compiler warning --- Modules/socketmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 967a097b8651f41..752040096aee867 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -1902,7 +1902,7 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, addr->sun_path[path.len] = 0; /* including the tailing NUL */ - *len_ret = path.len + offsetof(struct sockaddr_un, sun_path) + 1; + *len_ret = (int)path.len + offsetof(struct sockaddr_un, sun_path) + 1; } addr->sun_family = s->sock_family; memcpy(addr->sun_path, path.buf, path.len); From a70f8346fcc1a297f907c481afb9cdbc4be9e8d5 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 6 Aug 2025 01:12:26 +0900 Subject: [PATCH 06/20] Fix test in mp module --- Lib/test/_test_multiprocessing.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index a1259ff1d63d18f..8582f2a8a97efc8 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -5536,11 +5536,6 @@ def test_invalid_family(self): with self.assertRaises(ValueError): multiprocessing.connection.Listener(r'\\.\test') - @unittest.skipUnless(WIN32, "skipped on non-Windows platforms") - def test_invalid_family_win32(self): - with self.assertRaises(ValueError): - multiprocessing.connection.Listener('/var/test.pipe') - # # Issue 12098: check sys.flags of child matches that for parent # From 5f93fbbe9c87443071a885c0b0db4e7c3537548d Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 6 Aug 2025 23:06:03 +0900 Subject: [PATCH 07/20] Disable reuse_address in http.server on Windows with AF_UNIX --- Lib/http/server.py | 6 ++++++ Lib/test/test_asyncio/utils.py | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index a2ffbe2e44df640..5a6e515c101d7dd 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -117,6 +117,12 @@ class HTTPServer(socketserver.TCPServer): allow_reuse_address = True # Seems to make sense in testing environment allow_reuse_port = False + def __init__(self, *args, **kwargs): + if sys.platform == 'win32' and self.address_family == socket.AF_UNIX: + self.allow_reuse_address = False + + super().__init__(*args, **kwargs) + def server_bind(self): """Override server_bind to store the server name.""" socketserver.TCPServer.server_bind(self) diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py index 05010b89929d695..9f9913aec077d71 100644 --- a/Lib/test/test_asyncio/utils.py +++ b/Lib/test/test_asyncio/utils.py @@ -200,8 +200,6 @@ def app(environ, start_response): if hasattr(socket, 'AF_UNIX'): class UnixHTTPServer(socketserver.UnixStreamServer, HTTPServer): - if sys.platform == 'win32': - allow_reuse_address = False def server_bind(self): socketserver.UnixStreamServer.server_bind(self) From c759ee74321d5dc956947da185f44b4f753c0301 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 6 Aug 2025 23:14:38 +0900 Subject: [PATCH 08/20] Fix build on wasi --- Modules/socketmodule.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index b566d34a462d09b..2edc704405f447e 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -94,7 +94,6 @@ typedef int socklen_t; # include #endif /* MS_WINDOWS */ -#define HAVE_AFUNIX_H 1 #ifdef HAVE_SYS_UN_H # include #elif HAVE_AFUNIX_H From 7ce46a7f983f4027a0df0ec2affd5566b45f48b0 Mon Sep 17 00:00:00 2001 From: AN Long Date: Thu, 7 Aug 2025 02:06:59 +0900 Subject: [PATCH 09/20] Fix build --- PC/pyconfig.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/PC/pyconfig.h b/PC/pyconfig.h index 6fc834755266e57..c1212de445948c5 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h @@ -672,9 +672,7 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */ /* #define HAVE_SYS_UN_H 1 */ /* Define if you have the header file. */ -#if defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4) #define HAVE_AFUNIX_H 1 -#endif /* Define if you have the header file. */ /* #define HAVE_SYS_UTIME_H 1 */ From 4d4562421d3cb29a933d5787c6e4a8e6ccba4de8 Mon Sep 17 00:00:00 2001 From: AN Long Date: Fri, 8 Aug 2025 00:54:29 +0900 Subject: [PATCH 10/20] Fix test in logging --- Lib/http/server.py | 4 +++- Lib/test/test_logging.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 5a6e515c101d7dd..5d51008e3820013 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -118,7 +118,9 @@ class HTTPServer(socketserver.TCPServer): allow_reuse_port = False def __init__(self, *args, **kwargs): - if sys.platform == 'win32' and self.address_family == socket.AF_UNIX: + if sys.platform == 'win32' and hasattr(socket, 'AF_UNIX') and\ + self.address_family == socket.AF_UNIX: + # reuse address with AF_UNIX is not supported on Windows self.allow_reuse_address = False super().__init__(*args, **kwargs) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 275f7ce47d09b58..e5339b861d4273c 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -2089,6 +2089,8 @@ def test_output(self): self.assertEqual(self.log_output, b'<11>h\xc3\xa4m-sp\xc3\xa4m') def test_udp_reconnection(self): + if self.server_exception: + self.skipTest(self.server_exception) logger = logging.getLogger("slh") self.sl_hdlr.close() self.handled.clear() From fb216528aee9a1317fa2b564c2c6e5619c7ee2e8 Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 9 Aug 2025 00:28:37 +0900 Subject: [PATCH 11/20] Skip testSurrogateescapeBind on Windows --- Lib/test/test_socket.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index d8094cda10ff954..fdd9efbca0618a5 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -6208,6 +6208,8 @@ def testBytesAddr(self): self.addCleanup(os_helper.unlink, path) self.assertEqual(self.sock.getsockname(), path) + @unittest.skipIf(sys.platform == 'win32', + 'surrogateescape file path is not supported on Windows') def testSurrogateescapeBind(self): # Test binding to a valid non-ASCII pathname, with the # non-ASCII bytes supplied using surrogateescape encoding. From 510ef3ffe08c48796f505f1321bd05d3e50d08af Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 9 Aug 2025 23:30:52 +0900 Subject: [PATCH 12/20] Skip another test --- Lib/test/test_socket.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index fdd9efbca0618a5..079058544e7f2eb 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -6219,6 +6219,9 @@ def testSurrogateescapeBind(self): self.addCleanup(os_helper.unlink, path) self.assertEqual(self.sock.getsockname(), path) + @unittest.skipIf(sys.platform == 'win32', + 'Windows have a bug which can\'t unlink sock file with ' + 'TESTFN_UNENCODABLE in it\'s name') def testUnencodableAddr(self): # Test binding to a pathname that cannot be encoded in the # file system encoding. From dd07aecac839198f00d5ed7c48d16c2e146f9c40 Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 11 Aug 2025 22:35:25 +0900 Subject: [PATCH 13/20] Update tests --- Lib/test/test_pathlib/test_pathlib.py | 22 +++++++++++++++++++++- Lib/test/test_socket.py | 11 +++++++++++ Lib/test/test_stat.py | 9 +++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index e925f4f8508d86d..a4a08dcf1ddc32f 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -2747,7 +2747,8 @@ def test_is_socket_false(self): @unittest.skipIf( is_wasi, "Cannot create socket on WASI." ) - @unittest.skipIf(sys.platform=='win32', "didn't work on Windows") + @unittest.skipIf(sys.platform=='win32', + "detecting if file is socket is not supported by Windows") def test_is_socket_true(self): P = self.cls(self.base, 'mysock') sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) @@ -2764,6 +2765,25 @@ def test_is_socket_true(self): self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False) self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False) + @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") + @unittest.skipUnless(sys.platform=='win32', + "socket file on Windows is a normal file") + def test_is_socket_on_windows(self): + P = self.cls(self.base, 'mysock') + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(sock.close) + try: + sock.bind(str(P)) + except OSError as e: + if (isinstance(e, PermissionError) or + "AF_UNIX path too long" in str(e)): + self.skipTest("cannot bind Unix socket: " + str(e)) + self.assertFalse(P.is_socket()) + self.assertFalse(P.is_fifo()) + self.assertTrue(P.is_file()) + self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False) + self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False) + def test_is_block_device_false(self): P = self.cls(self.base) self.assertFalse((P / 'fileA').is_block_device()) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 079058544e7f2eb..9e406cda60048f8 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -6194,6 +6194,11 @@ def testUnbound(self): # Issue #30205 (note getsockname() can return None on OS X) self.assertIn(self.sock.getsockname(), ('', None)) + @unittest.skipUnless(sys.platform == 'win32', + 'Windows-specific behavior') + def test_unbound_on_windows(self): + self.assertRaisesRegex(OSError, 'WinError 10022', self.sock.getsockname) + def testStrAddr(self): # Test binding to and retrieving a normal string pathname. path = os.path.abspath(os_helper.TESTFN) @@ -6240,6 +6245,12 @@ def testEmptyAddress(self): # Test that binding empty address fails. self.assertRaises(OSError, self.sock.bind, "") + @unittest.skipUnless(sys.platform == 'win32', + 'Windows-specified behavior') + def test_empty_address_on_windows(self): + self.sock.bind('') + self.assertEqual(self.sock.getsockname(), '') + class BufferIOTest(SocketConnectedTest): """ diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index 1f17177b4c5b3fe..4a7113cf2fe175c 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -223,6 +223,15 @@ def test_socket(self): self.assertEqual(modestr[0], 's') self.assertS_IS("SOCK", st_mode) + @socket_helper.skip_unless_bind_unix_socket + @unittest.skipUnless(sys.platform=='win32', "didn't work on Windows") + def test_socket_on_windows(self): + with socket.socket(socket.AF_UNIX) as s: + s.bind(TESTFN) + st_mode, modestr = self.get_mode() + self.assertNotEqual(modestr[0], 's') + self.assertS_IS("REG", st_mode) + def test_module_attributes(self): for key, value in self.stat_struct.items(): modvalue = getattr(self.statmod, key) From 9c68464dce8cdf2c423387a3b10d70503ad3c722 Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 11 Aug 2025 23:58:00 +0900 Subject: [PATCH 14/20] Using __has_include to detect header files --- PC/pyconfig.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/PC/pyconfig.h b/PC/pyconfig.h index c1212de445948c5..300acf6aaf368ca 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h @@ -672,7 +672,11 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */ /* #define HAVE_SYS_UN_H 1 */ /* Define if you have the header file. */ -#define HAVE_AFUNIX_H 1 +#if defined(__has_include) && __has_include() + #define HAVE_AFUNIX_H 1 +#else + #define HAVE_AFUNIX_H 0 +#endif /* Define if you have the header file. */ /* #define HAVE_SYS_UTIME_H 1 */ From ea593f75607808cf7ed489bb8ec2a932e19510be Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 23 Jun 2026 21:50:03 +0900 Subject: [PATCH 15/20] Re-raise unexpected OSError in test_is_socket_on_windows --- Lib/test/test_pathlib/test_pathlib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 5e779cc21a3748f..126d3d9e5854f8b 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -2891,6 +2891,7 @@ def test_is_socket_on_windows(self): if (isinstance(e, PermissionError) or "AF_UNIX path too long" in str(e)): self.skipTest("cannot bind Unix socket: " + str(e)) + raise self.assertFalse(P.is_socket()) self.assertFalse(P.is_fifo()) self.assertTrue(P.is_file()) From 13c8b06f3e539995111e48b47bbdb5028feada74 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 23 Jun 2026 21:52:27 +0900 Subject: [PATCH 16/20] fix typos --- Lib/test/test_socket.py | 6 +++--- Lib/test/test_socketserver.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 7fc7cb410a8272f..104d4e969b355ca 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -6369,8 +6369,8 @@ def testSurrogateescapeBind(self): self.assertEqual(self.sock.getsockname(), path) @unittest.skipIf(sys.platform == 'win32', - 'Windows have a bug which can\'t unlink sock file with ' - 'TESTFN_UNENCODABLE in it\'s name') + 'Windows has a bug which can\'t unlink sock file with ' + 'TESTFN_UNENCODABLE in its name') def testUnencodableAddr(self): # Test binding to a pathname that cannot be encoded in the # file system encoding. @@ -6390,7 +6390,7 @@ def testEmptyAddress(self): self.assertRaises(OSError, self.sock.bind, "") @unittest.skipUnless(sys.platform == 'win32', - 'Windows-specified behavior') + 'Windows-specific behavior') def test_empty_address_on_windows(self): self.sock.bind('') self.assertEqual(self.sock.getsockname(), '') diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index 24aed2ba6428a8d..23871b3f906a571 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -225,7 +225,7 @@ def test_ForkingUDPServer(self): @requires_unix_sockets @unittest.skipIf(sys.platform=="win32", - "Unix with Dadagram is not supported on Windows") + "Unix with Datagram is not supported on Windows") @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") def test_UnixDatagramServer(self): @@ -235,7 +235,7 @@ def test_UnixDatagramServer(self): @requires_unix_sockets @unittest.skipIf(sys.platform=="win32", - "Unix with Dadagram is not supported on Windows") + "Unix with Datagram is not supported on Windows") @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") def test_ThreadingUnixDatagramServer(self): @@ -246,7 +246,7 @@ def test_ThreadingUnixDatagramServer(self): @warnings_helper.ignore_fork_in_thread_deprecation_warnings() @requires_unix_sockets @unittest.skipIf(sys.platform=="win32", - "Unix with Dadagram is not supported on Windows") + "Unix with Datagram is not supported on Windows") @requires_forking def test_ForkingUnixDatagramServer(self): self.run_server(socketserver.ForkingUnixDatagramServer, From cf4f1127bb76a857fb17802e716a26c1d8479836 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 23 Jun 2026 22:13:29 +0900 Subject: [PATCH 17/20] update docs for AF_UNIX on Windows --- Doc/library/socket.rst | 4 ++++ Doc/whatsnew/3.16.rst | 8 ++++++++ .../Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst | 3 ++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 836aa91bb0885b1..33358b00bcf6752 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -378,6 +378,10 @@ The AF_* and SOCK_* constants are now :class:`AddressFamily` and defined then this protocol is unsupported. More constants may be available depending on the system. + .. versionchanged:: next + :const:`AF_UNIX` is now available on Windows. It requires Windows 10 + version 1803 or newer. + .. data:: AF_UNSPEC :const:`AF_UNSPEC` means that diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 64a986f2487d5cb..d48175f9e8d7835 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -150,6 +150,14 @@ shlex (Contributed by Jay Berry in :gh:`148846`.) +socket +------ + +* Unix domain sockets (:const:`~socket.AF_UNIX`) are now supported on Windows, + which requires Windows 10 version 1803 or newer. + (Contributed by AN Long in :gh:`77589`.) + + tkinter ------- diff --git a/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst b/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst index 8fa31e146e8bb24..5ccae1e2a9dc6b3 100644 --- a/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst +++ b/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst @@ -1 +1,2 @@ -Add Unix domain socket on Windows. Patched by AN Long. +Unix domain sockets (:const:`socket.AF_UNIX`) are now supported on Windows, +which requires Windows 10 version 1803 or newer. Patch by AN Long. From 1e895c8aa93096b6076f7d01da65a29013b7e01b Mon Sep 17 00:00:00 2001 From: An Long Date: Tue, 23 Jun 2026 22:29:55 +0900 Subject: [PATCH 18/20] follow pep8 --- Lib/test/test_pathlib/test_pathlib.py | 4 ++-- Lib/test/test_socketserver.py | 6 +++--- Lib/test/test_stat.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 126d3d9e5854f8b..0a22916f90c2589 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -2860,7 +2860,7 @@ def test_is_socket_false(self): @unittest.skipIf( is_wasi, "Cannot create socket on WASI." ) - @unittest.skipIf(sys.platform=='win32', + @unittest.skipIf(sys.platform == 'win32', "detecting if file is socket is not supported by Windows") def test_is_socket_true(self): P = self.cls(self.base, 'mysock') @@ -2879,7 +2879,7 @@ def test_is_socket_true(self): self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False) @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") - @unittest.skipUnless(sys.platform=='win32', + @unittest.skipUnless(sys.platform == 'win32', "socket file on Windows is a normal file") def test_is_socket_on_windows(self): P = self.cls(self.base, 'mysock') diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index 23871b3f906a571..6c0476517d90ff9 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -224,7 +224,7 @@ def test_ForkingUDPServer(self): self.dgram_examine) @requires_unix_sockets - @unittest.skipIf(sys.platform=="win32", + @unittest.skipIf(sys.platform == "win32", "Unix with Datagram is not supported on Windows") @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") @@ -234,7 +234,7 @@ def test_UnixDatagramServer(self): self.dgram_examine) @requires_unix_sockets - @unittest.skipIf(sys.platform=="win32", + @unittest.skipIf(sys.platform == "win32", "Unix with Datagram is not supported on Windows") @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") @@ -245,7 +245,7 @@ def test_ThreadingUnixDatagramServer(self): @warnings_helper.ignore_fork_in_thread_deprecation_warnings() @requires_unix_sockets - @unittest.skipIf(sys.platform=="win32", + @unittest.skipIf(sys.platform == "win32", "Unix with Datagram is not supported on Windows") @requires_forking def test_ForkingUnixDatagramServer(self): diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index f337de7dc6b1dfc..05efd3759fb1280 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -220,7 +220,7 @@ def test_devices(self): break @socket_helper.skip_unless_bind_unix_socket - @unittest.skipIf(sys.platform=='win32', "didn't work on Windows") + @unittest.skipIf(sys.platform == 'win32', "didn't work on Windows") def test_socket(self): with socket.socket(socket.AF_UNIX) as s: s.bind(TESTFN) @@ -229,7 +229,7 @@ def test_socket(self): self.assertS_IS("SOCK", st_mode) @socket_helper.skip_unless_bind_unix_socket - @unittest.skipUnless(sys.platform=='win32', "didn't work on Windows") + @unittest.skipUnless(sys.platform == 'win32', "didn't work on Windows") def test_socket_on_windows(self): with socket.socket(socket.AF_UNIX) as s: s.bind(TESTFN) From 603143e1eac373b23f255dccd68d011161c6dc45 Mon Sep 17 00:00:00 2001 From: An Long Date: Tue, 23 Jun 2026 22:42:05 +0900 Subject: [PATCH 19/20] match errno instead of error message --- Lib/test/test_socket.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 104d4e969b355ca..9e82589bc84e13f 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -6341,7 +6341,9 @@ def testUnbound(self): @unittest.skipUnless(sys.platform == 'win32', 'Windows-specific behavior') def test_unbound_on_windows(self): - self.assertRaisesRegex(OSError, 'WinError 10022', self.sock.getsockname) + with self.assertRaises(OSError) as cm: + self.sock.getsockname() + self.assertEqual(cm.exception.winerror, errno.WSAEINVAL) def testStrAddr(self): # Test binding to and retrieving a normal string pathname. From d9b5e1936c149c06b29a3f65ae175c9dcfb945b3 Mon Sep 17 00:00:00 2001 From: An Long Date: Tue, 23 Jun 2026 22:44:22 +0900 Subject: [PATCH 20/20] Update news entry --- Doc/whatsnew/3.16.rst | 2 +- .../next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index d48175f9e8d7835..b3ef0d5585d777f 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -155,7 +155,7 @@ socket * Unix domain sockets (:const:`~socket.AF_UNIX`) are now supported on Windows, which requires Windows 10 version 1803 or newer. - (Contributed by AN Long in :gh:`77589`.) + (Contributed by An Long in :gh:`77589`.) tkinter diff --git a/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst b/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst index 5ccae1e2a9dc6b3..63c85a574163a16 100644 --- a/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst +++ b/Misc/NEWS.d/next/Windows/2025-08-05-23-59-59.gh-issue-77589.soytRy.rst @@ -1,2 +1,2 @@ Unix domain sockets (:const:`socket.AF_UNIX`) are now supported on Windows, -which requires Windows 10 version 1803 or newer. Patch by AN Long. +which requires Windows 10 version 1803 or newer. Patch by An Long.