@@ -80,12 +80,23 @@ def _read_ready(self):
8080 if self ._conn_lost :
8181 return
8282
83+ # Guard against closed socket (fd == -1)
84+ if self ._sock .fileno () == - 1 :
85+ return
86+
8387 for _ in range (self ._max_reads_per_call ):
8488 try :
8589 data = self ._sock .recv (self .max_size )
8690 except (BlockingIOError , InterruptedError ):
8791 # EAGAIN - no more data available
8892 return
93+ except OSError as exc :
94+ # Handle bad file descriptor (socket closed)
95+ if exc .errno == errno .EBADF :
96+ self ._conn_lost += 1
97+ return
98+ self ._fatal_error (exc , 'Fatal read error' )
99+ return
89100 except Exception as exc :
90101 self ._fatal_error (exc , 'Fatal read error' )
91102 return
@@ -140,6 +151,11 @@ def _write_ready_cb(self):
140151
141152 Drains buffer until EAGAIN with a budget to avoid starvation.
142153 """
154+ # Guard against closed socket (fd == -1)
155+ if self ._sock .fileno () == - 1 :
156+ self ._conn_lost += 1
157+ return
158+
143159 for _ in range (self ._max_writes_per_call ):
144160 remaining = len (self ._buffer ) - self ._buffer_offset
145161 if remaining <= 0 :
@@ -155,6 +171,14 @@ def _write_ready_cb(self):
155171 except (BlockingIOError , InterruptedError ):
156172 # EAGAIN - socket buffer full
157173 return
174+ except OSError as exc :
175+ # Handle bad file descriptor (socket closed)
176+ if exc .errno == errno .EBADF :
177+ self ._conn_lost += 1
178+ return
179+ self ._loop .remove_writer (self ._fileno )
180+ self ._fatal_error (exc , 'Fatal write error' )
181+ return
158182 except Exception as exc :
159183 self ._loop .remove_writer (self ._fileno )
160184 self ._fatal_error (exc , 'Fatal write error' )
@@ -198,10 +222,17 @@ def close(self):
198222
199223 def _call_connection_lost (self , exc ):
200224 """Call protocol.connection_lost()."""
225+ # Guard against double cleanup
226+ if self ._conn_lost > 1 :
227+ return
228+ self ._conn_lost += 1
201229 try :
202230 self ._protocol .connection_lost (exc )
203231 finally :
204- self ._sock .close ()
232+ try :
233+ self ._sock .close ()
234+ except OSError :
235+ pass
205236
206237 def _fatal_error (self , exc , message = 'Fatal error' ):
207238 """Handle fatal errors."""
@@ -310,13 +341,21 @@ def _read_ready(self):
310341 if self ._conn_lost :
311342 return
312343
344+ # Guard against closed socket (fd == -1)
345+ if self ._sock .fileno () == - 1 :
346+ return
347+
313348 for _ in range (self ._max_reads_per_call ):
314349 try :
315350 data , addr = self ._sock .recvfrom (self .max_size )
316351 except (BlockingIOError , InterruptedError ):
317352 # EAGAIN - no more data available
318353 return
319354 except OSError as exc :
355+ # Handle bad file descriptor (socket closed)
356+ if exc .errno == errno .EBADF :
357+ self ._conn_lost += 1
358+ return
320359 self ._protocol .error_received (exc )
321360 return
322361 except Exception as exc :
@@ -362,6 +401,11 @@ def sendto(self, data, addr=None):
362401
363402 def _write_ready (self ):
364403 """Called when socket is ready for writing."""
404+ # Guard against closed socket (fd == -1)
405+ if self ._sock .fileno () == - 1 :
406+ self ._conn_lost += 1
407+ return
408+
365409 while self ._buffer :
366410 data , addr = self ._buffer [0 ]
367411 try :
@@ -375,6 +419,10 @@ def _write_ready(self):
375419 except (BlockingIOError , InterruptedError ):
376420 return
377421 except OSError as exc :
422+ # Handle bad file descriptor (socket closed)
423+ if exc .errno == errno .EBADF :
424+ self ._conn_lost += 1
425+ return
378426 self ._buffer .popleft ()
379427 self ._protocol .error_received (exc )
380428 return
@@ -400,10 +448,17 @@ def close(self):
400448
401449 def _call_connection_lost (self , exc ):
402450 """Call protocol.connection_lost()."""
451+ # Guard against double cleanup
452+ if self ._conn_lost > 1 :
453+ return
454+ self ._conn_lost += 1
403455 try :
404456 self ._protocol .connection_lost (exc )
405457 finally :
406- self ._sock .close ()
458+ try :
459+ self ._sock .close ()
460+ except OSError :
461+ pass
407462
408463 def _fatal_error (self , exc , message = 'Fatal error on datagram transport' ):
409464 """Handle fatal errors."""
0 commit comments