[Python-checkins] cpython: Issue #14310: inter-process socket duplication for windows

kristjan.jonsson python-checkins at python.org
Sat Apr 7 13:24:49 CEST 2012


http://hg.python.org/cpython/rev/51b4bddd0e92
changeset:   76150:51b4bddd0e92
user:        Kristján Valur Jónsson <kristjan at ccpgames.com>
date:        Sat Apr 07 11:23:31 2012 +0000
summary:
  Issue #14310: inter-process socket duplication for windows

files:
  Doc/library/socket.rst  |   25 ++++++
  Lib/socket.py           |   10 ++-
  Lib/test/test_socket.py |  105 ++++++++++++++++++++++++++++
  Misc/NEWS               |    3 +
  Modules/socketmodule.c  |   73 +++++++++++++++++-
  5 files changed, 208 insertions(+), 8 deletions(-)


diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
--- a/Doc/library/socket.rst
+++ b/Doc/library/socket.rst
@@ -680,6 +680,16 @@
    .. versionadded:: 3.3
 
 
+.. function:: fromshare(data)
+
+   Instantiate a socket from data obtained from :meth:`~socket.share`.
+   The socket is assumed to be in blocking mode.
+
+   Availability: Windows.
+
+   .. versionadded:: 3.3
+
+
 .. data:: SocketType
 
    This is a Python type object that represents the socket object type. It is the
@@ -1082,6 +1092,21 @@
    are disallowed.  If *how* is :const:`SHUT_RDWR`, further sends and receives are
    disallowed.
 
+
+.. method:: socket.share(process_id)
+
+    :platform: Windows
+
+    Duplacet a socket and prepare it for sharing with a target process.  The
+    target process must be provided with *process_id*.  The resulting bytes object
+    can then be passed to the target process using some form of interprocess
+    communication and the socket can be recreated there using :func:`fromshare`.
+    Once this method has been called, it is safe to close the socket since
+    the operating system has already duplicated it for the target process.
+
+   .. versionadded:: 3.3
+
+
 Note that there are no methods :meth:`read` or :meth:`write`; use
 :meth:`~socket.recv` and :meth:`~socket.send` without *flags* argument instead.
 
diff --git a/Lib/socket.py b/Lib/socket.py
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -12,6 +12,7 @@
 socket() -- create a new socket object
 socketpair() -- create a pair of new socket objects [*]
 fromfd() -- create a socket object from an open file descriptor [*]
+fromshare() -- create a socket object from data received from socket.share() [*]
 gethostname() -- return the current hostname
 gethostbyname() -- map a hostname to its IP number
 gethostbyaddr() -- map an IP number or hostname to DNS info
@@ -209,7 +210,6 @@
         self._closed = True
         return super().detach()
 
-
 def fromfd(fd, family, type, proto=0):
     """ fromfd(fd, family, type[, proto]) -> socket object
 
@@ -219,6 +219,14 @@
     nfd = dup(fd)
     return socket(family, type, proto, nfd)
 
+if hasattr(_socket.socket, "share"):
+    def fromshare(info):
+        """ fromshare(info) -> socket object
+
+        Create a socket object from a the bytes object returned by
+        socket.share(pid).
+        """
+        return socket(0, 0, 0, info)
 
 if hasattr(_socket, "socketpair"):
 
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -26,6 +26,10 @@
     import fcntl
 except ImportError:
     fcntl = False
+try:
+    import multiprocessing
+except ImportError:
+    multiprocessing = False
 
 HOST = support.HOST
 MSG = 'Michael Gilfix was here\u1234\r\n'.encode('utf-8') ## test unicode string and carriage return
@@ -4643,6 +4647,106 @@
         socket.setdefaulttimeout(t)
 
 
+ at unittest.skipUnless(os.name == "nt", "Windows specific")
+ at unittest.skipUnless(multiprocessing, "need multiprocessing")
+class TestSocketSharing(SocketTCPTest):
+    # This must be classmethod and not staticmethod or multiprocessing
+    # won't be able to bootstrap it.
+    @classmethod
+    def remoteProcessServer(cls, q):
+        # Recreate socket from shared data
+        sdata = q.get()
+        message = q.get()
+
+        s = socket.fromshare(sdata)
+        s2, c = s.accept()
+
+        # Send the message
+        s2.sendall(message)
+        s2.close()
+        s.close()
+
+    def testShare(self):
+        # Transfer the listening server socket to another process
+        # and service it from there.
+
+        # Create process:
+        q = multiprocessing.Queue()
+        p = multiprocessing.Process(target=self.remoteProcessServer, args=(q,))
+        p.start()
+
+        # Get the shared socket data
+        data = self.serv.share(p.pid)
+
+        # Pass the shared socket to the other process
+        addr = self.serv.getsockname()
+        self.serv.close()
+        q.put(data)
+
+        # The data that the server will send us
+        message = b"slapmahfro"
+        q.put(message)
+
+        # Connect
+        s = socket.create_connection(addr)
+        #  listen for the data
+        m = []
+        while True:
+            data = s.recv(100)
+            if not data:
+                break
+            m.append(data)
+        s.close()
+        received = b"".join(m)
+        self.assertEqual(received, message)
+        p.join()
+
+    def testShareLength(self):
+        data = self.serv.share(os.getpid())
+        self.assertRaises(ValueError, socket.fromshare, data[:-1])
+        self.assertRaises(ValueError, socket.fromshare, data+b"foo")
+
+    def compareSockets(self, org, other):
+        # socket sharing is expected to work only for blocking socket
+        # since the internal python timout value isn't transfered.
+        self.assertEqual(org.gettimeout(), None)
+        self.assertEqual(org.gettimeout(), other.gettimeout())
+
+        self.assertEqual(org.family, other.family)
+        self.assertEqual(org.type, other.type)
+        # If the user specified "0" for proto, then
+        # internally windows will have picked the correct value.
+        # Python introspection on the socket however will still return
+        # 0.  For the shared socket, the python value is recreated
+        # from the actual value, so it may not compare correctly.
+        if org.proto != 0:
+            self.assertEqual(org.proto, other.proto)
+
+    def testShareLocal(self):
+        data = self.serv.share(os.getpid())
+        s = socket.fromshare(data)
+        try:
+            self.compareSockets(self.serv, s)
+        finally:
+            s.close()
+
+    def testTypes(self):
+        families = [socket.AF_INET, socket.AF_INET6]
+        types = [socket.SOCK_STREAM, socket.SOCK_DGRAM]
+        for f in families:
+            for t in types:
+                source = socket.socket(f, t)
+                try:
+                    data = source.share(os.getpid())
+                    shared = socket.fromshare(data)
+                    try:
+                        self.compareSockets(source, shared)
+                    finally:
+                        shared.close()
+                finally:
+                    source.close()
+
+
 def test_main():
     tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest,
              TestExceptions, BufferIOTest, BasicTCPTest2, BasicUDPTest, UDPTimeoutTest ]
@@ -4699,6 +4803,7 @@
         # These are slow when setitimer() is not available
         InterruptedRecvTimeoutTest,
         InterruptedSendTimeoutTest,
+        TestSocketSharing,
     ])
 
     thread_info = support.threading_setup()
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -232,6 +232,9 @@
 - Issue #14210: pdb now has tab-completion not only for command names, but
   also for their arguments, wherever possible.
 
+- Issue #14310: Sockets can now be with other processes on Windows using
+  the api socket.socket.share() and socket.fromshare().
+
 Build
 -----
 
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -3771,6 +3771,34 @@
 Control the socket with WSAIoctl syscall. Currently supported 'cmd' values are\n\
 SIO_RCVALL:  'option' must be one of the socket.RCVALL_* constants.\n\
 SIO_KEEPALIVE_VALS:  'option' is a tuple of (onoff, timeout, interval).");
+#endif
+
+#if defined(MS_WINDOWS)
+static PyObject*
+sock_share(PySocketSockObject *s, PyObject *arg)
+{
+    WSAPROTOCOL_INFO info;
+    DWORD processId;
+    int result;
+
+    if (!PyArg_ParseTuple(arg, "I", &processId))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    result = WSADuplicateSocket(s->sock_fd, processId, &info);
+    Py_END_ALLOW_THREADS
+    if (result == SOCKET_ERROR)
+        return set_error();
+    return PyBytes_FromStringAndSize((const char*)&info, sizeof(info));
+}
+PyDoc_STRVAR(sock_share_doc,
+"share(process_id) -> bytes\n\
+\n\
+Share the socket with another process.  The target process id\n\
+must be provided and the resulting bytes object passed to the target\n\
+process.  There the shared socket can be instantiated by calling\n\
+socket.fromshare().");
+
 
 #endif
 
@@ -3803,6 +3831,10 @@
     {"ioctl",             (PyCFunction)sock_ioctl, METH_VARARGS,
                       sock_ioctl_doc},
 #endif
+#if defined(MS_WINDOWS)
+    {"share",         (PyCFunction)sock_share, METH_VARARGS,
+                      sock_share_doc},
+#endif
     {"listen",            (PyCFunction)sock_listen, METH_O,
                       listen_doc},
     {"recv",              (PyCFunction)sock_recv, METH_VARARGS,
@@ -3930,13 +3962,40 @@
         return -1;
 
     if (fdobj != NULL && fdobj != Py_None) {
-        fd = PyLong_AsSocket_t(fdobj);
-        if (fd == (SOCKET_T)(-1) && PyErr_Occurred())
-            return -1;
-        if (fd == INVALID_SOCKET) {
-            PyErr_SetString(PyExc_ValueError,
-                            "can't use invalid socket value");
-            return -1;
+#ifdef MS_WINDOWS
+        /* recreate a socket that was duplicated */
+        if (PyBytes_Check(fdobj)) {
+            WSAPROTOCOL_INFO info;
+            if (PyBytes_GET_SIZE(fdobj) != sizeof(info)) {
+                PyErr_Format(PyExc_ValueError,
+                    "socket descriptor string has wrong size, "
+                    "should be %zu bytes.", sizeof(info));
+                return -1;
+            }
+            memcpy(&info, PyBytes_AS_STRING(fdobj), sizeof(info));
+            Py_BEGIN_ALLOW_THREADS
+            fd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
+                     FROM_PROTOCOL_INFO, &info, 0, WSA_FLAG_OVERLAPPED);
+            Py_END_ALLOW_THREADS
+            if (fd == INVALID_SOCKET) {
+                set_error();
+                return -1;
+            }
+            family = info.iAddressFamily;
+            type = info.iSocketType;
+            proto = info.iProtocol;
+        }
+        else
+#endif
+        {
+            fd = PyLong_AsSocket_t(fdobj);
+            if (fd == (SOCKET_T)(-1) && PyErr_Occurred())
+                return -1;
+            if (fd == INVALID_SOCKET) {
+                PyErr_SetString(PyExc_ValueError,
+                                "can't use invalid socket value");
+                return -1;
+            }
         }
     }
     else {

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list