Description
Summary
Invoking CBC (at least version 2.10.3) through Pyomo with tee=True
doesn't read cbc's command-line output as it's written - instead, nothing shows up until cbc finishes. Weirdly, this seems specific to CBC! SCIP actually works as you'd expect - you can tee its output and watch it come in.
This is caused (I think) by CBC buffering output on its side, because it detects that stdout is a pipe rather than a tty. Python shows this behavior, for example.
Steps to reproduce the issue
To reproduce with CBC, run:
sf = pe.SolverFactory("cbc")
sf.solve(model, tee=True)
For a minimal repro only on TeeStream, you can use this:
from pyomo.common.tee import TeeStream
import subprocess
import sys
cmd = "python -c \"import time; [(print(i+1), time.sleep(1)) for i in range(5)]\""
ostreams = [sys.stdout]
with TeeStream(*ostreams) as t:
subprocess.run(
cmd,
shell=True,
stdout=t.STDOUT,
stderr=t.STDERR,
universal_newlines=True,
bufsize=0
)
t.STDOUT.flush()
t.STDERR.flush()
Observe (minimal repro on TeeStream
)
Output
Expected Output
(venv) sterlind@[redacted]:~/projects/[redacted]$ python notebooks/scratch.py
1
<1 sec delay>
2
<1 sec delay>
3
<...>
4
<...>
5
Actual Output
(venv) sterlind@[redacted]:~/projects/[redacted]$ python notebooks/scratch.py
<5 second delay>
1
2
3
4
5
Information on your system
Pyomo version: 6.5.0
Python version: 3.8.10
Operating system: Ubuntu 20.02 on Windows 11/WSL2
How Pyomo was installed (PyPI, conda, source): PyPI
Solver (if applicable): CBC (possibly others, anything using isatty()
or similar magic to set buffering)
Additional information
As a workaround, I use a cbc wrapper:
#!/bin/bash
# Use stdbuf -o0 to execute cbc with unbuffered output
exec stdbuf -o0 /usr/bin/cbc "$@"
from pyomo.common import Executable
# Set cbc path to cbc_wrapper.sh (in the same directory as this script.)
Executable('cbc').set_path(os.path.join(os.path.dirname(__file__), 'cbc_wrapper.sh'))