22
33# prefer slower Python-based io module 
44import  _pyio  as  io 
5+ import  errno 
56import  socket 
7+ import  sys 
68
9+ from  OpenSSL .SSL  import  SysCallError 
10+ 
11+ 
12+ # Define a variable to hold the platform-specific "not a socket" error. 
13+ _not_a_socket_err  =  None 
14+ 
15+ if  sys .platform  ==  'win32' :
16+     # On Windows, try to get the named constant from the socket module. 
17+     # If that fails, fall back to the known numeric value. 
18+     try :
19+         _not_a_socket_err  =  socket .WSAENOTSOCK 
20+     except  AttributeError :
21+         _not_a_socket_err  =  10038 
22+ else :
23+     # On other platforms, the relevant error is EBADF (Bad file descriptor), 
24+     # which is already in your list of handled errors. 
25+     pass 
26+ 
27+ # Expose the error constant for use in the module's public API if needed. 
28+ WSAENOTSOCK  =  _not_a_socket_err 
729
830# Write only 16K at a time to sockets 
931SOCK_WRITE_BLOCKSIZE  =  16384 
@@ -30,10 +52,59 @@ def _flush_unlocked(self):
3052                # ssl sockets only except 'bytes', not bytearrays 
3153                # so perhaps we should conditionally wrap this for perf? 
3254                n  =  self .raw .write (bytes (self ._write_buf ))
33-             except  io .BlockingIOError  as  e :
34-                 n  =  e .characters_written 
55+             except  (io .BlockingIOError , OSError , SysCallError ) as  e :
56+                 # Check for a different error attribute depending 
57+                 # on the exception type 
58+                 if  isinstance (e , io .BlockingIOError ):
59+                     n  =  e .characters_written 
60+                 else :
61+                     error_code  =  (
62+                         e .errno  if  isinstance (e , OSError ) else  e .args [0 ]
63+                     )
64+                     if  error_code  in  {
65+                         errno .EBADF ,
66+                         errno .ENOTCONN ,
67+                         errno .EPIPE ,
68+                         WSAENOTSOCK ,  # Windows-specific error 
69+                     }:
70+                         # The socket is gone, so just ignore this error. 
71+                         return 
72+                     raise 
73+             else :
74+                 # The 'try' block completed without an exception 
75+                 if  n  is  None :
76+                     # This could happen with non-blocking write 
77+                     # when nothing was written 
78+                     break 
79+ 
3580            del  self ._write_buf [:n ]
3681
82+     def  close (self ):
83+         """ 
84+         Close the stream and its underlying file object. 
85+ 
86+         This method is designed to be idempotent (it can be called multiple 
87+         times without side effects). It gracefully handles a race condition 
88+         where the underlying socket may have already been closed by the remote 
89+         client or another thread. 
90+ 
91+         A SysCallError or OSError with errno.EBADF or errno.ENOTCONN is caught 
92+         and ignored, as these indicate a normal, expected connection teardown. 
93+         Other exceptions are re-raised. 
94+         """ 
95+         if  self .closed :  # pylint: disable=W0125 
96+             return 
97+ 
98+         try :
99+             super ().close ()
100+         except  (OSError , SysCallError ) as  e :
101+             error_code  =  e .errno  if  isinstance (e , OSError ) else  e .args [0 ]
102+             if  error_code  in  {errno .EBADF , errno .ENOTCONN }:
103+                 # The socket is already closed, which is expected during 
104+                 # a race condition. 
105+                 return 
106+             raise 
107+ 
37108
38109class  StreamReader (io .BufferedReader ):
39110    """Socket stream reader.""" 
0 commit comments