1212from pythinker_code .utils .logging import logger
1313from pythinker_code .utils .subprocess_env import get_clean_env
1414
15- from .models import TaskControl
15+ from .models import TaskControl , TaskRuntime
1616from .store import BackgroundTaskStore
1717
1818
@@ -112,9 +112,9 @@ async def _terminate_process(force: bool = False) -> None:
112112 async def _check_output_limit () -> None :
113113 """Terminate the task if its output.log grew past ``max_output_bytes``.
114114
115- Writes a single marker line and records a failure the first time the
116- limit is hit; the ``output_limit_exceeded`` guard keeps it from
117- re-marking on subsequent polls or once the process is already exiting .
115+ Writes a single marker line and asks the process to terminate the first
116+ time the limit is hit; the final runtime write records the failure once
117+ the process has exited .
118118 """
119119 nonlocal output_limit_exceeded , output_limit_reason
120120 if max_output_bytes <= 0 or output_limit_exceeded :
@@ -133,14 +133,6 @@ async def _check_output_limit() -> None:
133133 marker = f"\n ... output limit exceeded ({ size } bytes); task terminated ...\n "
134134 with contextlib .suppress (OSError ), output_path .open ("ab" ) as marker_file :
135135 marker_file .write (marker .encode ("utf-8" ))
136- with store ._runtime_lock (task_id ): # pyright: ignore[reportPrivateUsage]
137- current = store .read_runtime (task_id )
138- if not current .finished_at :
139- current .status = "failed"
140- current .interrupted = True
141- current .failure_reason = output_limit_reason
142- current .updated_at = time .time ()
143- store ._write_runtime_unlocked (task_id , current ) # pyright: ignore[reportPrivateUsage]
144136 await _terminate_process (force = False )
145137
146138 async def _control_loop () -> None :
@@ -211,7 +203,6 @@ async def _input_loop() -> None:
211203 runtime .updated_at = time .time ()
212204 runtime .heartbeat_at = runtime .updated_at
213205 store .write_runtime (task_id , runtime )
214- last_known_runtime = runtime
215206
216207 heartbeat_task = asyncio .create_task (_heartbeat_loop ())
217208 control_task = asyncio .create_task (_control_loop ())
@@ -254,29 +245,32 @@ async def _input_loop() -> None:
254245 with contextlib .suppress (asyncio .CancelledError ):
255246 await task
256247
257- runtime = last_known_runtime .model_copy ()
258248 control = store .read_control (task_id )
259- runtime .finished_at = time .time ()
260- runtime .updated_at = runtime .finished_at
261- runtime .exit_code = returncode
262- runtime .heartbeat_at = runtime .finished_at
263- if output_limit_exceeded :
264- runtime .status = "failed"
265- runtime .interrupted = True
266- runtime .failure_reason = output_limit_reason
267- elif timed_out :
268- runtime .status = "failed"
269- runtime .interrupted = True
270- runtime .timed_out = True
271- runtime .failure_reason = timeout_reason
272- elif control .kill_requested_at is not None :
273- runtime .status = "killed"
274- runtime .interrupted = True
275- runtime .failure_reason = control .kill_reason or "Killed"
276- elif returncode == 0 :
277- runtime .status = "completed"
278- runtime .failure_reason = None
279- else :
280- runtime .status = "failed"
281- runtime .failure_reason = f"Command failed with exit code { returncode } "
282- store .write_runtime (task_id , runtime )
249+
250+ def finish_runtime (runtime : TaskRuntime ) -> bool :
251+ runtime .finished_at = time .time ()
252+ runtime .updated_at = runtime .finished_at
253+ runtime .exit_code = returncode
254+ runtime .heartbeat_at = runtime .finished_at
255+ if output_limit_exceeded :
256+ runtime .status = "failed"
257+ runtime .interrupted = True
258+ runtime .failure_reason = output_limit_reason
259+ elif timed_out :
260+ runtime .status = "failed"
261+ runtime .interrupted = True
262+ runtime .timed_out = True
263+ runtime .failure_reason = timeout_reason
264+ elif control .kill_requested_at is not None :
265+ runtime .status = "killed"
266+ runtime .interrupted = True
267+ runtime .failure_reason = control .kill_reason or "Killed"
268+ elif returncode == 0 :
269+ runtime .status = "completed"
270+ runtime .failure_reason = None
271+ else :
272+ runtime .status = "failed"
273+ runtime .failure_reason = f"Command failed with exit code { returncode } "
274+ return True
275+
276+ store .update_runtime (task_id , finish_runtime )
0 commit comments