This commit is contained in:
AN Long 2025-12-07 21:10:14 -08:00 committed by GitHub
commit cdaadfbe39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 168 additions and 35 deletions

View file

@ -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)

View file

@ -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

View file

@ -0,0 +1 @@
Use :c:func:`!TransmitFile` on Windows to implement :func:`socket.sendfile`.

View file

@ -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"

View file

@ -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