Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a19fb3c

Browse files
authored
bpo-32622: Native sendfile on windows (python#5565)
* Support sendfile on Windows Proactor event loop naively.
1 parent 5fb632e commit a19fb3c

File tree

7 files changed

+431
-93
lines changed

7 files changed

+431
-93
lines changed

Lib/asyncio/proactor_events.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66

77
__all__ = 'BaseProactorEventLoop',
88

9+
import io
10+
import os
911
import socket
1012
import warnings
1113

1214
from . import base_events
1315
from . import constants
16+
from . import events
1417
from . import futures
1518
from . import protocols
1619
from . import sslproto
@@ -107,6 +110,11 @@ def _fatal_error(self, exc, message='Fatal error on pipe transport'):
107110
self._force_close(exc)
108111

109112
def _force_close(self, exc):
113+
if self._empty_waiter is not None:
114+
if exc is None:
115+
self._empty_waiter.set_result(None)
116+
else:
117+
self._empty_waiter.set_exception(exc)
110118
if self._closing:
111119
return
112120
self._closing = True
@@ -327,13 +335,19 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
327335

328336
_start_tls_compatible = True
329337

338+
def __init__(self, *args, **kw):
339+
super().__init__(*args, **kw)
340+
self._empty_waiter = None
341+
330342
def write(self, data):
331343
if not isinstance(data, (bytes, bytearray, memoryview)):
332344
raise TypeError(
333345
f"data argument must be a bytes-like object, "
334346
f"not {type(data).__name__}")
335347
if self._eof_written:
336348
raise RuntimeError('write_eof() already called')
349+
if self._empty_waiter is not None:
350+
raise RuntimeError('unable to write; sendfile is in progress')
337351

338352
if not data:
339353
return
@@ -393,6 +407,8 @@ def _loop_writing(self, f=None, data=None):
393407
self._maybe_pause_protocol()
394408
else:
395409
self._write_fut.add_done_callback(self._loop_writing)
410+
if self._empty_waiter is not None and self._write_fut is None:
411+
self._empty_waiter.set_result(None)
396412
except ConnectionResetError as exc:
397413
self._force_close(exc)
398414
except OSError as exc:
@@ -407,6 +423,17 @@ def write_eof(self):
407423
def abort(self):
408424
self._force_close(None)
409425

426+
def _make_empty_waiter(self):
427+
if self._empty_waiter is not None:
428+
raise RuntimeError("Empty waiter is already set")
429+
self._empty_waiter = self._loop.create_future()
430+
if self._write_fut is None:
431+
self._empty_waiter.set_result(None)
432+
return self._empty_waiter
433+
434+
def _reset_empty_waiter(self):
435+
self._empty_waiter = None
436+
410437

411438
class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
412439
def __init__(self, *args, **kw):
@@ -447,7 +474,7 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport,
447474
transports.Transport):
448475
"""Transport for connected sockets."""
449476

450-
_sendfile_compatible = constants._SendfileMode.FALLBACK
477+
_sendfile_compatible = constants._SendfileMode.TRY_NATIVE
451478

452479
def _set_extra(self, sock):
453480
self._extra['socket'] = sock
@@ -556,6 +583,47 @@ async def sock_connect(self, sock, address):
556583
async def sock_accept(self, sock):
557584
return await self._proactor.accept(sock)
558585

586+
async def _sock_sendfile_native(self, sock, file, offset, count):
587+
try:
588+
fileno = file.fileno()
589+
except (AttributeError, io.UnsupportedOperation) as err:
590+
raise events.SendfileNotAvailableError("not a regular file")
591+
try:
592+
fsize = os.fstat(fileno).st_size
593+
except OSError as err:
594+
raise events.SendfileNotAvailableError("not a regular file")
595+
blocksize = count if count else fsize
596+
if not blocksize:
597+
return 0 # empty file
598+
599+
blocksize = min(blocksize, 0xffff_ffff)
600+
end_pos = min(offset + count, fsize) if count else fsize
601+
offset = min(offset, fsize)
602+
total_sent = 0
603+
try:
604+
while True:
605+
blocksize = min(end_pos - offset, blocksize)
606+
if blocksize <= 0:
607+
return total_sent
608+
await self._proactor.sendfile(sock, file, offset, blocksize)
609+
offset += blocksize
610+
total_sent += blocksize
611+
finally:
612+
if total_sent > 0:
613+
file.seek(offset)
614+
615+
async def _sendfile_native(self, transp, file, offset, count):
616+
resume_reading = transp.is_reading()
617+
transp.pause_reading()
618+
await transp._make_empty_waiter()
619+
try:
620+
return await self.sock_sendfile(transp._sock, file, offset, count,
621+
fallback=False)
622+
finally:
623+
transp._reset_empty_waiter()
624+
if resume_reading:
625+
transp.resume_reading()
626+
559627
def _close_self_pipe(self):
560628
if self._self_reading_future is not None:
561629
self._self_reading_future.cancel()

Lib/asyncio/windows_events.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import _winapi
55
import errno
66
import math
7+
import msvcrt
78
import socket
89
import struct
910
import weakref
@@ -527,6 +528,27 @@ def finish_connect(trans, key, ov):
527528

528529
return self._register(ov, conn, finish_connect)
529530

531+
def sendfile(self, sock, file, offset, count):
532+
self._register_with_iocp(sock)
533+
ov = _overlapped.Overlapped(NULL)
534+
offset_low = offset & 0xffff_ffff
535+
offset_high = (offset >> 32) & 0xffff_ffff
536+
ov.TransmitFile(sock.fileno(),
537+
msvcrt.get_osfhandle(file.fileno()),
538+
offset_low, offset_high,
539+
count, 0, 0)
540+
541+
def finish_sendfile(trans, key, ov):
542+
try:
543+
return ov.getresult()
544+
except OSError as exc:
545+
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
546+
_overlapped.ERROR_OPERATION_ABORTED):
547+
raise ConnectionResetError(*exc.args)
548+
else:
549+
raise
550+
return self._register(ov, sock, finish_sendfile)
551+
530552
def accept_pipe(self, pipe):
531553
self._register_with_iocp(pipe)
532554
ov = _overlapped.Overlapped(NULL)

0 commit comments

Comments
 (0)