@@ -159,27 +159,13 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
159
159
160
160
def __init__ (self , loop , sock , protocol , waiter = None ,
161
161
extra = None , server = None ):
162
- self ._loop_reading_cb = None
162
+ self ._pending_data = None
163
163
self ._paused = True
164
164
super ().__init__ (loop , sock , protocol , waiter , extra , server )
165
165
166
- self ._reschedule_on_resume = False
167
166
self ._loop .call_soon (self ._loop_reading )
168
167
self ._paused = False
169
168
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
-
183
169
def is_reading (self ):
184
170
return not self ._paused and not self ._closing
185
171
@@ -188,32 +174,39 @@ def pause_reading(self):
188
174
return
189
175
self ._paused = True
190
176
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().
202
187
203
188
if self ._loop .get_debug ():
204
189
logger .debug ("%r pauses reading" , self )
205
190
206
191
def resume_reading (self ):
207
192
if self ._closing or not self ._paused :
208
193
return
194
+
209
195
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
+
213
206
if self ._loop .get_debug ():
214
207
logger .debug ("%r resumes reading" , self )
215
208
216
- def _loop_reading__on_eof (self ):
209
+ def _eof_received (self ):
217
210
if self ._loop .get_debug ():
218
211
logger .debug ("%r received EOF" , self )
219
212
@@ -227,18 +220,30 @@ def _loop_reading__on_eof(self):
227
220
if not keep_open :
228
221
self .close ()
229
222
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
236
229
return
237
230
238
- if self . _paused :
239
- self ._reschedule_on_resume = True
231
+ if not data :
232
+ self ._eof_received ()
240
233
return
241
234
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 ):
242
247
data = None
243
248
try :
244
249
if fut is not None :
@@ -261,8 +266,12 @@ def _loop_reading__data_received(self, fut):
261
266
# we got end-of-file so no need to reschedule a new read
262
267
return
263
268
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 )
266
275
except ConnectionAbortedError as exc :
267
276
if not self ._closing :
268
277
self ._fatal_error (exc , 'Fatal read error on pipe transport' )
@@ -277,92 +286,11 @@ def _loop_reading__data_received(self, fut):
277
286
if not self ._closing :
278
287
raise
279
288
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 )
281
291
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 )
366
294
367
295
368
296
class _ProactorBaseWritePipeTransport (_ProactorBasePipeTransport ,
0 commit comments