@@ -1002,32 +1002,117 @@ def test_supervise_output_turns_every_line_into_json(tmp_path):
10021002from reflex_base.utils import log
10031003
10041004if not log.is_output_supervised():
1005- sys.exit(log.supervise_output([sys.executable, __file__]))
1005+ sys.exit(log.supervise_output([sys.executable, __file__, *sys.argv[1:] ]))
10061006for i in range(200):
1007- print(f"line {i:03d} " + "x" * 1000)
1007+ print(f"line {i:03d} " + "x" * 1000, file=getattr(sys, sys.argv[1]) )
10081008"""
10091009
10101010
1011- def test_supervise_output_drains_everything_for_a_slow_consumer (tmp_path ):
1011+ @pytest .mark .parametrize ("drain_max_seconds" , [log ._DRAIN_MAX_SECONDS , 0.05 ])
1012+ @pytest .mark .parametrize ("stream" , ["stdout" , "stderr" ])
1013+ def test_supervise_output_drains_everything_for_a_slow_consumer (
1014+ tmp_path , drain_max_seconds , stream
1015+ ):
10121016 """Output still in the pipes at exit reaches a consumer that reads late."""
10131017 script = tmp_path / "burst.py"
1014- script .write_text (_BURST_SCRIPT )
1018+ script .write_text (
1019+ f"from reflex_base.utils import log\n "
1020+ f"log._DRAIN_MAX_SECONDS = { drain_max_seconds } \n " + _BURST_SCRIPT
1021+ )
10151022 with subprocess .Popen (
1016- [sys .executable , str (script )],
1017- stdout = subprocess .PIPE ,
1023+ [sys .executable , str (script ), stream ],
1024+ stdout = subprocess .PIPE if stream == "stdout" else None ,
1025+ stderr = subprocess .PIPE if stream == "stderr" else None ,
10181026 env = {** os .environ , "REFLEX_LOG_JSON" : "true" },
10191027 ) as proc :
1020- assert proc .stdout is not None
1028+ output = proc .stdout if stream == "stdout" else proc .stderr
1029+ assert output is not None
10211030 lines = []
10221031 # A consumer slower than the writer keeps the pipes full at exit.
1023- for line in proc . stdout :
1032+ for line in output :
10241033 lines .append (line )
10251034 time .sleep (0.015 )
10261035 proc .wait (timeout = 30 )
1036+ assert proc .returncode == 0
10271037 assert len (lines ) == 200
10281038 assert json .loads (lines [- 1 ])["message" ].startswith ("line 199 " )
10291039
10301040
1041+ def test_output_pump_write_snapshots_use_a_consistent_cutoff (monkeypatch ):
1042+ """Snapshots measure a blocked or completed write at their own cutoff."""
1043+ pump = log ._OutputPump (0 , 1 , "info" , "stdout" )
1044+ snapshots = []
1045+
1046+ def write (fd , data ):
1047+ """Capture a snapshot while the simulated write is in progress.
1048+
1049+ Args:
1050+ fd: The output file descriptor.
1051+ data: The bytes to write.
1052+
1053+ Returns:
1054+ The number of bytes written.
1055+ """
1056+ snapshots .append (pump .writing_snapshot ())
1057+ return len (data )
1058+
1059+ monkeypatch .setattr (
1060+ time , "monotonic" , mock .Mock (side_effect = [10.0 , 15.0 , 20.0 , 25.0 ])
1061+ )
1062+ monkeypatch .setattr (os , "write" , write )
1063+ pump ._write ([b"line" ], [], final = False )
1064+ assert snapshots == [(15.0 , 5.0 )]
1065+ assert pump .writing_snapshot () == (25.0 , 10.0 )
1066+
1067+
1068+ _STALLED_CONSUMER_SCRIPT = """
1069+ import os
1070+ import subprocess
1071+ import sys
1072+ from pathlib import Path
1073+
1074+ from reflex_base.utils import log
1075+
1076+ log._DRAIN_WALL_SECONDS = 0.5
1077+ if not log.is_output_supervised():
1078+ sys.exit(log.supervise_output([sys.executable, __file__]))
1079+ writer = subprocess.Popen([
1080+ sys.executable,
1081+ "-c",
1082+ "import os\\ nwhile True:\\ n os.write(int(os.environ['TEST_OUTPUT_FD']), (b'x' * 1023 + b'\\ \\ n') * 64)",
1083+ ])
1084+ Path("writer.pid").write_text(str(writer.pid))
1085+ """
1086+
1087+
1088+ @pytest .mark .parametrize ("stream_fd" , [1 , 2 ])
1089+ def test_supervise_output_bounds_shutdown_for_a_stalled_consumer (tmp_path , stream_fd ):
1090+ """A lingering writer and an unread output pipe cannot prevent shutdown."""
1091+ script = tmp_path / "stalled.py"
1092+ script .write_text (_STALLED_CONSUMER_SCRIPT )
1093+ with subprocess .Popen (
1094+ [sys .executable , str (script )],
1095+ stdout = subprocess .PIPE ,
1096+ stderr = subprocess .PIPE ,
1097+ cwd = tmp_path ,
1098+ env = {
1099+ ** os .environ ,
1100+ "REFLEX_LOG_JSON" : "true" ,
1101+ "TEST_OUTPUT_FD" : str (stream_fd ),
1102+ },
1103+ ) as proc :
1104+ try :
1105+ assert proc .wait (timeout = 5 ) == 0
1106+ finally :
1107+ if proc .poll () is None :
1108+ proc .kill ()
1109+ proc .wait (timeout = 5 )
1110+ pid_file = tmp_path / "writer.pid"
1111+ if pid_file .exists ():
1112+ with contextlib .suppress (OSError ):
1113+ os .kill (int (pid_file .read_text ()), signal .SIGTERM )
1114+
1115+
10311116_LINGERING_SCRIPT = """
10321117import subprocess
10331118import sys
0 commit comments