2828    Union ,
2929)
3030
31- from  pymongo  import  ssl_support 
31+ from  pymongo  import  _csot ,  ssl_support 
3232from  pymongo ._asyncio_task  import  create_task 
3333from  pymongo .errors  import  _OperationCancelled 
3434from  pymongo .socket_checker  import  _errno_from_exception 
@@ -316,6 +316,42 @@ async def _async_receive(conn: socket.socket, length: int, loop: AbstractEventLo
316316    return  mv 
317317
318318
319+ _PYPY  =  "PyPy"  in  sys .version 
320+ 
321+ 
322+ def  wait_for_read (conn : Connection , deadline : Optional [float ]) ->  None :
323+     """Block until at least one byte is read, or a timeout, or a cancel.""" 
324+     sock  =  conn .conn 
325+     timed_out  =  False 
326+     # Check if the connection's socket has been manually closed 
327+     if  sock .fileno () ==  - 1 :
328+         return 
329+     while  True :
330+         # SSLSocket can have buffered data which won't be caught by select. 
331+         if  hasattr (sock , "pending" ) and  sock .pending () >  0 :
332+             readable  =  True 
333+         else :
334+             # Wait up to 500ms for the socket to become readable and then 
335+             # check for cancellation. 
336+             if  deadline :
337+                 remaining  =  deadline  -  time .monotonic ()
338+                 # When the timeout has expired perform one final check to 
339+                 # see if the socket is readable. This helps avoid spurious 
340+                 # timeouts on AWS Lambda and other FaaS environments. 
341+                 if  remaining  <=  0 :
342+                     timed_out  =  True 
343+                 timeout  =  max (min (remaining , _POLL_TIMEOUT ), 0 )
344+             else :
345+                 timeout  =  _POLL_TIMEOUT 
346+             readable  =  conn .socket_checker .select (sock , read = True , timeout = timeout )
347+         if  conn .cancel_context .cancelled :
348+             raise  _OperationCancelled ("operation cancelled" )
349+         if  readable :
350+             return 
351+         if  timed_out :
352+             raise  socket .timeout ("timed out" )
353+ 
354+ 
319355def  receive_data (conn : Connection , length : int , deadline : Optional [float ]) ->  memoryview :
320356    buf  =  bytearray (length )
321357    mv  =  memoryview (buf )
@@ -324,18 +360,25 @@ def receive_data(conn: Connection, length: int, deadline: Optional[float]) -> me
324360    # check for the cancellation signal after each timeout. Alternatively we 
325361    # could close the socket but that does not reliably cancel recv() calls 
326362    # on all OSes. 
363+     # When the timeout has expired we perform one final non-blocking recv. 
364+     # This helps avoid spurious timeouts when the response is actually already 
365+     # buffered on the client. 
327366    orig_timeout  =  conn .conn .gettimeout ()
328367    try :
329368        while  bytes_read  <  length :
330-             if  deadline  is  not   None :
331-                 # CSOT: Update timeout. When the timeout has expired perform one 
332-                 # final non-blocking recv. This helps avoid spurious timeouts when 
333-                 # the response is actually already buffered on the client. 
334-                 short_timeout  =  min (max (deadline  -  time .monotonic (), 0 ), _POLL_TIMEOUT )
335-             else :
336-                 short_timeout  =  _POLL_TIMEOUT 
337-             conn .set_conn_timeout (short_timeout )
338369            try :
370+                 # Use the legacy wait_for_read cancellation approach on PyPy due to PYTHON-5011. 
371+                 if  _PYPY :
372+                     wait_for_read (conn , deadline )
373+                     if  _csot .get_timeout () and  deadline  is  not   None :
374+                         conn .set_conn_timeout (max (deadline  -  time .monotonic (), 0 ))
375+                 else :
376+                     if  deadline  is  not   None :
377+                         short_timeout  =  min (max (deadline  -  time .monotonic (), 0 ), _POLL_TIMEOUT )
378+                     else :
379+                         short_timeout  =  _POLL_TIMEOUT 
380+                     conn .set_conn_timeout (short_timeout )
381+ 
339382                chunk_length  =  conn .conn .recv_into (mv [bytes_read :])
340383            except  BLOCKING_IO_ERRORS :
341384                if  conn .cancel_context .cancelled :
@@ -345,6 +388,9 @@ def receive_data(conn: Connection, length: int, deadline: Optional[float]) -> me
345388            except  socket .timeout :
346389                if  conn .cancel_context .cancelled :
347390                    raise  _OperationCancelled ("operation cancelled" ) from  None 
391+                 if  _PYPY :
392+                     # We reached the true deadline. 
393+                     raise 
348394                continue 
349395            except  OSError  as  exc :
350396                if  conn .cancel_context .cancelled :
0 commit comments