2222import threading
2323import time
2424
25+ import pyomo .common .dependencies as dependencies
26+ from pyomo .common .enums import CaptureOutputMode
2527from pyomo .common .errors import DeveloperError
2628from pyomo .common .log import LoggingIntercept , LogStream
29+ from pyomo .common .shutdown import python_is_shutting_down
2730
2831_poll_interval = 0.0001
2932_poll_rampup_limit = 0.099
3538# ~(13.1 * #threads) seconds
3639_poll_timeout = 1 # 14 rounds: 0.0001 * 2**14 == 1.6384
3740_poll_timeout_deadlock = 100 # seconds
41+ _threading_deadlock = 200 # seconds; should be longer than _poll_timeout_deadlock
3842_pipe_buffersize = 1 << 16 # 65536
3943_noop = lambda : None
4044_mswindows = sys .platform .startswith ('win' )
5458
5559logger = logging .getLogger (__name__ )
5660
61+ OVERRIDE_CAPTURE_OUTPUT = CaptureOutputMode .NORMAL
62+
5763
5864class _SignalFlush :
5965 def __init__ (self , ostream , handle ):
@@ -297,14 +303,14 @@ class capture_output:
297303
298304 """
299305
300- startup_shutdown = threading .Lock ()
301-
302306 def __init__ (self , output = None , capture_fd = False ):
303307 self .output = output
304308 self .output_stream = None
305309 self .old = None
306310 self .tee = None
307- self .capture_fd = capture_fd
311+ self .capture_fd = capture_fd and (
312+ OVERRIDE_CAPTURE_OUTPUT & CaptureOutputMode .ENABLE_FD_CAPTURE
313+ )
308314 self .context_stack = []
309315
310316 def _enter_context (self , cm , prior_to = None ):
@@ -339,13 +345,12 @@ def _exit_context_stack(self, et, ev, tb):
339345 cm .__exit__ (et , ev , tb )
340346 except :
341347 _stack = self .context_stack
342- FAIL .append (
343- f"{ sys .exc_info ()[0 ].__name__ } : { sys .exc_info ()[1 ]} ({ len (_stack )+ 1 } : { cm } @{ id (cm ):x} )"
344- )
348+ _et , _e , _tb = sys .exc_info ()
349+ FAIL .append (f"{ _et .__name__ } : { _e } ({ len (_stack )+ 1 } : { cm } @{ id (cm ):x} )" )
345350 return FAIL
346351
347352 def __enter__ (self ):
348- if not capture_output . startup_shutdown .acquire (timeout = _poll_timeout_deadlock ):
353+ if not dependencies . capture_output_lock .acquire (timeout = _threading_deadlock ):
349354 # This situation *shouldn't* happen. If it does, it is
350355 # unlikely that the user can fix it (or even debug it).
351356 # Instead they should report it back to us.
@@ -358,20 +363,22 @@ def __enter__(self):
358363 # was trying to start up / run (so the other solver held
359364 # the lock, but the GC interrupted that thread and
360365 # wouldn't let go).
361- raise DeveloperError ("Deadlock starting capture_output" )
366+ if not python_is_shutting_down ():
367+ raise DeveloperError ("Deadlock starting capture_output" )
362368 try :
363369 return self ._enter_impl ()
364370 finally :
365- capture_output . startup_shutdown .release ()
371+ dependencies . capture_output_lock .release ()
366372
367373 def __exit__ (self , et , ev , tb ):
368- if not capture_output . startup_shutdown .acquire (timeout = _poll_timeout_deadlock ):
374+ if not dependencies . capture_output_lock .acquire (timeout = _threading_deadlock ):
369375 # See comments & breadcrumbs in __enter__() above.
370- raise DeveloperError ("Deadlock closing capture_output" )
376+ if not python_is_shutting_down ():
377+ raise DeveloperError ("Deadlock closing capture_output" )
371378 try :
372379 return self ._exit_impl (et , ev , tb )
373380 finally :
374- capture_output . startup_shutdown .release ()
381+ dependencies . capture_output_lock .release ()
375382
376383 def _enter_impl (self ):
377384 self .old = (sys .stdout , sys .stderr )
@@ -486,8 +493,11 @@ def _enter_impl(self):
486493 # exception.
487494 self ._exit_context_stack (* sys .exc_info ())
488495 raise
489- sys .stdout = self .tee .STDOUT
490- sys .stderr = self .tee .STDERR
496+ if OVERRIDE_CAPTURE_OUTPUT & CaptureOutputMode .ENABLE_STREAM_CAPTURE :
497+ sys .stdout = self .tee .STDOUT
498+ sys .stderr = self .tee .STDERR
499+ else :
500+ self .old = None
491501 buf = self .tee .ostreams
492502 if len (buf ) == 1 :
493503 buf = buf [0 ]
0 commit comments