Skip to content

Commit d42b903

Browse files
committed
Guard socket operations against closed file descriptors
1 parent 3b81fff commit d42b903

1 file changed

Lines changed: 57 additions & 2 deletions

File tree

priv/_erlang_impl/_transport.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)