@@ -159,27 +159,13 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
159159
160160 def __init__ (self , loop , sock , protocol , waiter = None ,
161161 extra = None , server = None ):
162- self ._loop_reading_cb = None
162+ self ._pending_data = None
163163 self ._paused = True
164164 super ().__init__ (loop , sock , protocol , waiter , extra , server )
165165
166- self ._reschedule_on_resume = False
167166 self ._loop .call_soon (self ._loop_reading )
168167 self ._paused = False
169168
170- def set_protocol (self , protocol ):
171- if isinstance (protocol , protocols .BufferedProtocol ):
172- self ._loop_reading_cb = self ._loop_reading__get_buffer
173- else :
174- self ._loop_reading_cb = self ._loop_reading__data_received
175-
176- super ().set_protocol (protocol )
177-
178- if self .is_reading ():
179- # reset reading callback / buffers / self._read_fut
180- self .pause_reading ()
181- self .resume_reading ()
182-
183169 def is_reading (self ):
184170 return not self ._paused and not self ._closing
185171
@@ -188,32 +174,39 @@ def pause_reading(self):
188174 return
189175 self ._paused = True
190176
191- if self ._read_fut is not None and not self ._read_fut .done ():
192- # TODO: This is an ugly hack to cancel the current read future
193- # *and* avoid potential race conditions, as read cancellation
194- # goes through `future.cancel()` and `loop.call_soon()`.
195- # We then use this special attribute in the reader callback to
196- # exit *immediately* without doing any cleanup/rescheduling.
197- self ._read_fut .__asyncio_cancelled_on_pause__ = True
198-
199- self ._read_fut .cancel ()
200- self ._read_fut = None
201- self ._reschedule_on_resume = True
177+ # bpo-33694: Don't cancel self._read_fut because cancelling an
178+ # overlapped WSASend() loss silently data with the current proactor
179+ # implementation.
180+ #
181+ # If CancelIoEx() fails with ERROR_NOT_FOUND, it means that WSASend()
182+ # completed (even if HasOverlappedIoCompleted() returns 0), but
183+ # Overlapped.cancel() currently silently ignores the ERROR_NOT_FOUND
184+ # error. Once the overlapped is ignored, the IOCP loop will ignores the
185+ # completion I/O event and so not read the result of the overlapped
186+ # WSARecv().
202187
203188 if self ._loop .get_debug ():
204189 logger .debug ("%r pauses reading" , self )
205190
206191 def resume_reading (self ):
207192 if self ._closing or not self ._paused :
208193 return
194+
209195 self ._paused = False
210- if self ._reschedule_on_resume :
211- self ._loop .call_soon (self ._loop_reading , self ._read_fut )
212- self ._reschedule_on_resume = False
196+ if self ._read_fut is None :
197+ self ._loop .call_soon (self ._loop_reading , None )
198+
199+ data = self ._pending_data
200+ self ._pending_data = None
201+ if data is not None :
202+ # Call the protocol methode after calling _loop_reading(),
203+ # since the protocol can decide to pause reading again.
204+ self ._loop .call_soon (self ._data_received , data )
205+
213206 if self ._loop .get_debug ():
214207 logger .debug ("%r resumes reading" , self )
215208
216- def _loop_reading__on_eof (self ):
209+ def _eof_received (self ):
217210 if self ._loop .get_debug ():
218211 logger .debug ("%r received EOF" , self )
219212
@@ -227,18 +220,30 @@ def _loop_reading__on_eof(self):
227220 if not keep_open :
228221 self .close ()
229222
230- def _loop_reading (self , fut = None ):
231- self ._loop_reading_cb ( fut )
232-
233- def _loop_reading__data_received ( self , fut ):
234- if ( fut is not None and
235- getattr ( fut , '__asyncio_cancelled_on_pause__' , False )):
223+ def _data_received (self , data ):
224+ if self ._paused :
225+ # Don't call any protocol method while reading is paused.
226+ # The protocol will be called on resume_reading().
227+ assert self . _pending_data is None
228+ self . _pending_data = data
236229 return
237230
238- if self . _paused :
239- self ._reschedule_on_resume = True
231+ if not data :
232+ self ._eof_received ()
240233 return
241234
235+ if isinstance (self ._protocol , protocols .BufferedProtocol ):
236+ try :
237+ protocols ._feed_data_to_bufferred_proto (self ._protocol , data )
238+ except Exception as exc :
239+ self ._fatal_error (exc ,
240+ 'Fatal error: protocol.buffer_updated() '
241+ 'call failed.' )
242+ return
243+ else :
244+ self ._protocol .data_received (data )
245+
246+ def _loop_reading (self , fut = None ):
242247 data = None
243248 try :
244249 if fut is not None :
@@ -261,8 +266,12 @@ def _loop_reading__data_received(self, fut):
261266 # we got end-of-file so no need to reschedule a new read
262267 return
263268
264- # reschedule a new read
265- self ._read_fut = self ._loop ._proactor .recv (self ._sock , 32768 )
269+ # bpo-33694: buffer_updated() has currently no fast path because of
270+ # a data loss issue caused by overlapped WSASend() cancellation.
271+
272+ if not self ._paused :
273+ # reschedule a new read
274+ self ._read_fut = self ._loop ._proactor .recv (self ._sock , 32768 )
266275 except ConnectionAbortedError as exc :
267276 if not self ._closing :
268277 self ._fatal_error (exc , 'Fatal read error on pipe transport' )
@@ -277,92 +286,11 @@ def _loop_reading__data_received(self, fut):
277286 if not self ._closing :
278287 raise
279288 else :
280- self ._read_fut .add_done_callback (self ._loop_reading__data_received )
289+ if not self ._paused :
290+ self ._read_fut .add_done_callback (self ._loop_reading )
281291 finally :
282- if data :
283- self ._protocol .data_received (data )
284- elif data == b'' :
285- self ._loop_reading__on_eof ()
286-
287- def _loop_reading__get_buffer (self , fut ):
288- if (fut is not None and
289- getattr (fut , '__asyncio_cancelled_on_pause__' , False )):
290- return
291-
292- if self ._paused :
293- self ._reschedule_on_resume = True
294- return
295-
296- nbytes = None
297- if fut is not None :
298- assert self ._read_fut is fut or (self ._read_fut is None and
299- self ._closing )
300- self ._read_fut = None
301- try :
302- if fut .done ():
303- nbytes = fut .result ()
304- else :
305- # the future will be replaced by next proactor.recv call
306- fut .cancel ()
307- except ConnectionAbortedError as exc :
308- if not self ._closing :
309- self ._fatal_error (
310- exc , 'Fatal read error on pipe transport' )
311- elif self ._loop .get_debug ():
312- logger .debug ("Read error on pipe transport while closing" ,
313- exc_info = True )
314- except ConnectionResetError as exc :
315- self ._force_close (exc )
316- except OSError as exc :
317- self ._fatal_error (exc , 'Fatal read error on pipe transport' )
318- except futures .CancelledError :
319- if not self ._closing :
320- raise
321-
322- if nbytes is not None :
323- if nbytes == 0 :
324- # we got end-of-file so no need to reschedule a new read
325- self ._loop_reading__on_eof ()
326- else :
327- try :
328- self ._protocol .buffer_updated (nbytes )
329- except Exception as exc :
330- self ._fatal_error (
331- exc ,
332- 'Fatal error: '
333- 'protocol.buffer_updated() call failed.' )
334- return
335-
336- if self ._closing or nbytes == 0 :
337- # since close() has been called we ignore any read data
338- return
339-
340- try :
341- buf = self ._protocol .get_buffer (- 1 )
342- if not len (buf ):
343- raise RuntimeError ('get_buffer() returned an empty buffer' )
344- except Exception as exc :
345- self ._fatal_error (
346- exc , 'Fatal error: protocol.get_buffer() call failed.' )
347- return
348-
349- try :
350- # schedule a new read
351- self ._read_fut = self ._loop ._proactor .recv_into (self ._sock , buf )
352- self ._read_fut .add_done_callback (self ._loop_reading__get_buffer )
353- except ConnectionAbortedError as exc :
354- if not self ._closing :
355- self ._fatal_error (exc , 'Fatal read error on pipe transport' )
356- elif self ._loop .get_debug ():
357- logger .debug ("Read error on pipe transport while closing" ,
358- exc_info = True )
359- except ConnectionResetError as exc :
360- self ._force_close (exc )
361- except OSError as exc :
362- self ._fatal_error (exc , 'Fatal read error on pipe transport' )
363- except futures .CancelledError :
364- if not self ._closing :
365- raise
292+ if data is not None :
293+ self ._data_received (data )
366294
367295
368296class _ProactorBaseWritePipeTransport (_ProactorBasePipeTransport ,
0 commit comments