@@ -44,25 +44,42 @@ def __init__(
4444 self .mode = getattr (original , "mode" , "w" )
4545
4646 def write (self , data : str ) -> int :
47- """Write to contextvar buffer if set, otherwise to original stream ."""
47+ """Write to the task buffer, falling back if a stale buffer was closed ."""
4848 buffer = self ._buffer_var .get ()
4949 if buffer is not None :
50- return buffer .write (data )
50+ try :
51+ return buffer .write (data )
52+ except ValueError :
53+ # Background tasks inherit contextvars. They can outlive the
54+ # execution capture that installed this buffer, leaving a
55+ # closed StringIO in their copied context. Preserve logging
56+ # and exception reporting by routing those late writes to the
57+ # process stream instead.
58+ if not getattr (buffer , "closed" , False ):
59+ raise
5160 return self ._original .write (data )
5261
5362 def writelines (self , lines : list [str ]) -> None :
54- """Write multiple lines."""
63+ """Write multiple lines, falling back if a stale buffer was closed ."""
5564 buffer = self ._buffer_var .get ()
5665 if buffer is not None :
57- buffer .writelines (lines )
58- else :
59- self ._original .writelines (lines )
66+ try :
67+ buffer .writelines (lines )
68+ return
69+ except ValueError :
70+ if not getattr (buffer , "closed" , False ):
71+ raise
72+ self ._original .writelines (lines )
6073
6174 def flush (self ) -> None :
62- """Flush both buffer and original stream."""
75+ """Flush the active buffer when usable, then the original stream."""
6376 buffer = self ._buffer_var .get ()
6477 if buffer is not None :
65- buffer .flush ()
78+ try :
79+ buffer .flush ()
80+ except ValueError :
81+ if not getattr (buffer , "closed" , False ):
82+ raise
6683 self ._original .flush ()
6784
6885 def fileno (self ) -> int :
0 commit comments