diff --git a/Lib/socket.py b/Lib/socket.py index 3073c012b19..c8de9896131 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -58,6 +58,13 @@ from enum import IntEnum, IntFlag from functools import partial +try: + import _overlapped + import msvcrt +except ImportError: + _overlapped = None + msvcrt = None + try: import errno except ImportError: @@ -467,6 +474,39 @@ def _sendfile_use_send(self, file, offset=0, count=None): if total_sent > 0 and hasattr(file, 'seek'): file.seek(offset + total_sent) + if _overlapped and msvcrt: + def _sendfile_use_transmitfile(self, file, offset=0, count=None): + self._check_sendfile_params(file, offset, count) + timeout = self.gettimeout() + if timeout == 0: + raise ValueError("non-blocking sockets are not supported") + ov = _overlapped.Overlapped() + offset_low = offset & 0xffff_ffff + offset_high = (offset >> 32) & 0xffff_ffff + count = count or 0 + try: + fileno = file.fileno() + except (AttributeError, io.UnsupportedOperation) as err: + raise _GiveupOnSendfile(err) # not a regular file + try: + os.fstat(fileno) + except OSError as err: + raise _GiveupOnSendfile(err) # not a regular file + ov.TransmitFile(self.fileno(), msvcrt.get_osfhandle(fileno), + offset_low, offset_high, count, 0, 0) + timeout_ms = _overlapped.INFINITE + if timeout is not None: + timeout_ms = int(timeout * 1000) + try: + sent = ov.getresultex(timeout_ms, False) + except WindowsError as e: + if e.winerror == 258: + raise TimeoutError('timed out') + raise + if sent > 0 and hasattr(file, 'seek'): + file.seek(offset + sent) + return sent + def _check_sendfile_params(self, file, offset, count): if 'b' not in getattr(file, 'mode', 'b'): raise ValueError("file should be opened in binary mode") @@ -499,6 +539,8 @@ def sendfile(self, file, offset=0, count=None): Non-blocking sockets are not supported. """ try: + if sys.platform == "win32": + return self._sendfile_use_transmitfile(file, offset, count) return self._sendfile_use_sendfile(file, offset, count) except _GiveupOnSendfile: return self._sendfile_use_send(file, offset, count) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 934b7137096..037ba87c192 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -7055,6 +7055,15 @@ def meth_from_sock(self, sock): return getattr(sock, "_sendfile_use_sendfile") +@unittest.skipUnless(sys.platform == "win32", "Windows only test.") +class SendfileUsingTransmitfileTest(SendfileUsingSendTest): + """ + Test the TransmitFile() implementation of socket.sendfile(). + """ + def meth_from_sock(self, sock): + return getattr(sock, "_sendfile_use_transmitfile") + + @unittest.skipUnless(HAVE_SOCKET_ALG, 'AF_ALG required') class LinuxKernelCryptoAPI(unittest.TestCase): # tests for AF_ALG diff --git a/Misc/NEWS.d/next/Windows/2023-11-23-22-25-16.gh-issue-65920.6wWsHN.rst b/Misc/NEWS.d/next/Windows/2023-11-23-22-25-16.gh-issue-65920.6wWsHN.rst new file mode 100644 index 00000000000..890f816013f --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-11-23-22-25-16.gh-issue-65920.6wWsHN.rst @@ -0,0 +1 @@ +Use :c:func:`!TransmitFile` on Windows to implement :func:`socket.sendfile`. diff --git a/Modules/clinic/overlapped.c.h b/Modules/clinic/overlapped.c.h index 7e2480bdace..90743a1b0b6 100644 --- a/Modules/clinic/overlapped.c.h +++ b/Modules/clinic/overlapped.c.h @@ -561,6 +561,41 @@ exit: return return_value; } +PyDoc_STRVAR(_overlapped_Overlapped_getresultex__doc__, +"getresultex($self, milliseconds, alertable, /)\n" +"--\n" +"\n"); + +#define _OVERLAPPED_OVERLAPPED_GETRESULTEX_METHODDEF \ + {"getresultex", _PyCFunction_CAST(_overlapped_Overlapped_getresultex), METH_FASTCALL, _overlapped_Overlapped_getresultex__doc__}, + +static PyObject * +_overlapped_Overlapped_getresultex_impl(OverlappedObject *self, + DWORD milliseconds, BOOL alertable); + +static PyObject * +_overlapped_Overlapped_getresultex(OverlappedObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + DWORD milliseconds; + BOOL alertable; + + if (!_PyArg_CheckPositional("getresultex", nargs, 2, 2)) { + goto exit; + } + if (!_PyLong_UnsignedLong_Converter(args[0], &milliseconds)) { + goto exit; + } + alertable = PyLong_AsInt(args[1]); + if (alertable == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = _overlapped_Overlapped_getresultex_impl(self, milliseconds, alertable); + +exit: + return return_value; +} + PyDoc_STRVAR(_overlapped_Overlapped_ReadFile__doc__, "ReadFile($self, handle, size, /)\n" "--\n" diff --git a/Modules/overlapped.c b/Modules/overlapped.c index 29b7b356648..50a7238ec6c 100644 --- a/Modules/overlapped.c +++ b/Modules/overlapped.c @@ -876,44 +876,13 @@ _overlapped_Overlapped_cancel_impl(OverlappedObject *self) Py_RETURN_NONE; } -/*[clinic input] -_overlapped.Overlapped.getresult - - wait: BOOL(c_default='FALSE') = False - / - -Retrieve result of operation. - -If wait is true then it blocks until the operation is finished. If wait -is false and the operation is still pending then an error is raised. -[clinic start generated code]*/ - static PyObject * -_overlapped_Overlapped_getresult_impl(OverlappedObject *self, BOOL wait) -/*[clinic end generated code: output=8c9bd04d08994f6c input=aa5b03e9897ca074]*/ +check_getresult_error(OverlappedObject *self, DWORD transferred) { - DWORD transferred = 0; - BOOL ret; - DWORD err; - PyObject *addr; + PyObject *addr = NULL; + DWORD err = self->error; - if (self->type == TYPE_NONE) { - PyErr_SetString(PyExc_ValueError, "operation not yet attempted"); - return NULL; - } - - if (self->type == TYPE_NOT_STARTED) { - PyErr_SetString(PyExc_ValueError, "operation failed to start"); - return NULL; - } - - Py_BEGIN_ALLOW_THREADS - ret = GetOverlappedResult(self->handle, &self->overlapped, &transferred, - wait); - Py_END_ALLOW_THREADS - - self->error = err = ret ? ERROR_SUCCESS : GetLastError(); - switch (err) { + switch (self->error) { case ERROR_SUCCESS: case ERROR_MORE_DATA: break; @@ -1005,6 +974,82 @@ _overlapped_Overlapped_getresult_impl(OverlappedObject *self, BOOL wait) } } +/*[clinic input] +_overlapped.Overlapped.getresult + + wait: BOOL(c_default='FALSE') = False + / + +Retrieve result of operation. + +If wait is true then it blocks until the operation is finished. If wait +is false and the operation is still pending then an error is raised. +[clinic start generated code]*/ + +static PyObject * +_overlapped_Overlapped_getresult_impl(OverlappedObject *self, BOOL wait) +/*[clinic end generated code: output=8c9bd04d08994f6c input=aa5b03e9897ca074]*/ +{ + DWORD transferred = 0; + BOOL ret; + + if (self->type == TYPE_NONE) { + PyErr_SetString(PyExc_ValueError, "operation not yet attempted"); + return NULL; + } + + if (self->type == TYPE_NOT_STARTED) { + PyErr_SetString(PyExc_ValueError, "operation failed to start"); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + ret = GetOverlappedResult(self->handle, &self->overlapped, &transferred, + wait); + Py_END_ALLOW_THREADS + + self->error = ret ? ERROR_SUCCESS : GetLastError(); + + return check_getresult_error(self, transferred); +} + +/*[clinic input] +_overlapped.Overlapped.getresultex + + milliseconds: DWORD + alertable: BOOL + / + +[clinic start generated code]*/ + +static PyObject * +_overlapped_Overlapped_getresultex_impl(OverlappedObject *self, + DWORD milliseconds, BOOL alertable) +/*[clinic end generated code: output=ce0eb6ffb9618e54 input=ef4f4cab49ac1d80]*/ +{ + DWORD transferred = 0; + BOOL ret; + + if (self->type == TYPE_NONE) { + PyErr_SetString(PyExc_ValueError, "operation not yet attempted"); + return NULL; + } + + if (self->type == TYPE_NOT_STARTED) { + PyErr_SetString(PyExc_ValueError, "operation failed to start"); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + ret = GetOverlappedResultEx(self->handle, &self->overlapped, &transferred, + milliseconds, alertable); + Py_END_ALLOW_THREADS + + self->error = ret ? ERROR_SUCCESS : GetLastError(); + + return check_getresult_error(self, transferred); +} + static PyObject * do_ReadFile(OverlappedObject *self, HANDLE handle, char *bufstart, DWORD buflen) @@ -1950,6 +1995,7 @@ _overlapped_Overlapped_WSARecvFromInto_impl(OverlappedObject *self, static PyMethodDef Overlapped_methods[] = { _OVERLAPPED_OVERLAPPED_GETRESULT_METHODDEF + _OVERLAPPED_OVERLAPPED_GETRESULTEX_METHODDEF _OVERLAPPED_OVERLAPPED_CANCEL_METHODDEF _OVERLAPPED_OVERLAPPED_READFILE_METHODDEF _OVERLAPPED_OVERLAPPED_READFILEINTO_METHODDEF