66
77__all__ = 'BaseProactorEventLoop' ,
88
9+ import io
10+ import os
911import socket
1012import warnings
1113
1214from . import base_events
1315from . import constants
16+ from . import events
1417from . import futures
1518from . import protocols
1619from . 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
411438class _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 ()
0 commit comments