@@ -200,8 +200,14 @@ def proto(self) -> int:
200200 @property
201201 def io (self ) -> MocketSocketIO :
202202 """Get or create the socket I/O object."""
203- if self ._io is None :
204- self ._io = MocketSocketIO ((self ._host , self ._port ))
203+ if self ._io is None or getattr (self ._io , "closed" , False ):
204+ address = self ._address_key ()
205+ self ._io = Mocket .get_io (address )
206+ if self ._io is not None and getattr (self ._io , "closed" , False ):
207+ self ._io = None
208+ if self ._io is None :
209+ self ._io = MocketSocketIO (address )
210+ Mocket .set_io (address , self ._io )
205211 return self ._io
206212
207213 def fileno (self ) -> int :
@@ -214,9 +220,76 @@ def fileno(self) -> int:
214220 r_fd , _ = Mocket .get_pair (address )
215221 if not r_fd :
216222 r_fd , w_fd = os .pipe ()
223+ os .set_blocking (r_fd , False )
224+ os .set_blocking (w_fd , False )
217225 Mocket .set_pair (address , (r_fd , w_fd ))
226+ if self ._io is not None and self ._buffered_bytes ():
227+ if Mocket .pipe_uses_data (address ):
228+ self ._mirror_buffer_to_pipe ()
229+ else :
230+ self ._sync_readable_pipe ()
218231 return r_fd
219232
233+ def _address_key (self ) -> Address :
234+ """Return the current socket address tuple."""
235+ return self ._host , self ._port
236+
237+ def _buffered_bytes (self ) -> int :
238+ """Return the number of unread bytes buffered in the socket I/O."""
239+ return len (self .io .getvalue ()) - self .io .tell ()
240+
241+ def _clear_readable_pipe (self ) -> None :
242+ """Drain any stale readiness bytes from the pipe for this socket."""
243+ address = self ._address_key ()
244+ r_fd , _ = Mocket .get_pair (address )
245+ if not r_fd :
246+ return
247+
248+ while True :
249+ try :
250+ if not os .read (r_fd , self ._buflen ):
251+ break
252+ except BlockingIOError :
253+ break
254+
255+ Mocket .set_pending_readables (address , 0 )
256+
257+ def _mirror_buffer_to_pipe (self ) -> None :
258+ """Mirror unread response bytes into the pipe for small payloads."""
259+ address = self ._address_key ()
260+ _ , w_fd = Mocket .get_pair (address )
261+ if not w_fd :
262+ return
263+
264+ unread = self .io .getvalue ()[self .io .tell () :]
265+ if unread :
266+ os .write (w_fd , unread )
267+
268+ def _sync_readable_pipe (self ) -> None :
269+ """Keep the readiness pipe in sync with the unread buffer size.
270+
271+ The pipe is used only to wake selector-based async clients. Response bytes
272+ remain in the in-memory buffer so large payloads do not block on OS pipe
273+ capacity.
274+ """
275+ address = self ._address_key ()
276+ _ , w_fd = Mocket .get_pair (address )
277+ if not w_fd :
278+ return
279+
280+ pending = Mocket .get_pending_readables (address )
281+ desired = min (self ._buffered_bytes (), self ._buflen )
282+ if desired <= pending :
283+ return
284+
285+ try :
286+ written = os .write (w_fd , b"\0 " * (desired - pending ))
287+ except BlockingIOError :
288+ written = 0
289+
290+ if written :
291+ Mocket .set_pending_readables (address , pending + written )
292+
220293 def gettimeout (self ) -> float | None :
221294 """Get the socket timeout.
222295
@@ -375,10 +448,20 @@ def sendall(
375448 response = self .true_sendall (data , * args , ** kwargs )
376449
377450 if response is not None :
451+ address = self ._address_key ()
452+ # Ensure the address pipe exists before deciding whether to mirror
453+ # response bytes or only publish readiness signals.
454+ self .fileno ()
378455 self .io .seek (0 )
456+ self ._clear_readable_pipe ()
379457 self .io .write (response )
380458 self .io .truncate ()
381459 self .io .seek (0 )
460+ Mocket .set_pipe_uses_data (address , len (response ) <= self ._buflen )
461+ if Mocket .pipe_uses_data (address ):
462+ self ._mirror_buffer_to_pipe ()
463+ else :
464+ self ._sync_readable_pipe ()
382465
383466 def sendmsg (
384467 self ,
@@ -537,11 +620,32 @@ def recv(self, buffersize: int, flags: int | None = None) -> bytes:
537620 Raises:
538621 BlockingIOError: If socket is non-blocking and no data available
539622 """
540- r_fd , _ = Mocket .get_pair ((self ._host , self ._port ))
541- if r_fd :
542- return os .read (r_fd , buffersize )
623+ if buffersize is None :
624+ buffersize = self ._buflen
625+
626+ address = self ._address_key ()
627+ r_fd , _ = Mocket .get_pair (address )
628+ if r_fd and Mocket .pipe_uses_data (address ):
629+ try :
630+ pipe_data = os .read (r_fd , buffersize )
631+ except BlockingIOError :
632+ pipe_data = b""
633+ if pipe_data :
634+ # Keep in-memory buffer position in sync with bytes drained from the pipe.
635+ self .io .seek (self .io .tell () + len (pipe_data ))
636+ return pipe_data
637+
638+ pending = Mocket .get_pending_readables (address )
639+ if r_fd and self ._buffered_bytes () and pending == 0 :
640+ self ._sync_readable_pipe ()
641+ pending = Mocket .get_pending_readables (address )
642+
543643 data = self .io .read (buffersize )
544644 if data :
645+ if r_fd and pending :
646+ drained = os .read (r_fd , min (len (data ), pending ))
647+ Mocket .set_pending_readables (address , pending - len (drained ))
648+ self ._sync_readable_pipe ()
545649 return data
546650 # used by Redis mock
547651 exc = BlockingIOError ()
0 commit comments