|
23 | 23 | class StreamedLogBase: |
24 | 24 | """Base class for streaming and buffering chunked Actor run logs.""" |
25 | 25 |
|
26 | | - # Test related flag to enable propagation of logs to the `caplog` fixture during tests. |
27 | 26 | _force_propagate = False |
| 27 | + """Test related flag to enable propagation of logs to the `caplog` fixture during tests.""" |
28 | 28 |
|
29 | | - # The log stream is a long-poll request that should stay open for the whole Actor run; the server ends it |
30 | | - # with EOF once the run finishes. impit applies its `timeout` to the whole request (streamed body included), |
31 | | - # so any bounded value truncates a longer run mid-stream, which raised `impit.TimeoutException` here (#1040). |
32 | | - # `no_timeout` is the only tier above `DEFAULT_TIMEOUT_MAX`; it maps to impit's ~24h cap, which is effectively |
33 | | - # unbounded for real runs and mirrors the JS client, which sets no body timeout on its log stream. |
34 | 29 | _stream_timeout: ClassVar[Timeout] = 'no_timeout' |
| 30 | + """Timeout for the log-stream long-poll request, which stays open for the whole Actor run. |
| 31 | +
|
| 32 | + impit applies its `timeout` to the whole request including the streamed body, so any bounded value truncates a |
| 33 | + longer run mid-stream and raises `impit.TimeoutException` (#1040). `no_timeout` maps to impit's ~24h cap, which |
| 34 | + is effectively unbounded for real runs and mirrors the JS client. |
| 35 | + """ |
35 | 36 |
|
36 | 37 | def __init__(self, to_logger: logging.Logger, *, from_start: bool = True) -> None: |
37 | 38 | if self._force_propagate: |
@@ -162,10 +163,9 @@ def _stream_log(self) -> None: |
162 | 163 | # Flush the last buffered part even if the read timed out or was stopped. |
163 | 164 | self._log_buffer_content(include_last_part=True) |
164 | 165 | except impit.TimeoutException: |
165 | | - # With `no_timeout` the stream is bounded only by impit's ~24h whole-request cap, so this fires only |
166 | | - # if the run outlives that cap or the connection stalls. The stream cannot continue either way, so the |
167 | | - # thread ends quietly rather than surfacing a traceback via `threading.excepthook` (#1040). |
168 | | - pass |
| 166 | + # With `no_timeout` this fires only if the run outlives impit's ~24h cap or the connection stalls. |
| 167 | + # The stream cannot continue, so warn and let the thread end instead of leaking a traceback (#1040). |
| 168 | + self._to_logger.warning('Log streaming stopped: the log stream request timed out.') |
169 | 169 | except Exception: |
170 | 170 | # Any other failure in log redirection must not escape the background thread; log it instead. |
171 | 171 | self._to_logger.exception('Log redirection stopped due to unexpected error:') |
@@ -243,8 +243,8 @@ async def _stream_log(self) -> None: |
243 | 243 | self._log_buffer_content(include_last_part=True) |
244 | 244 | except impit.TimeoutException: |
245 | 245 | # As in `StreamedLog._stream_log`, impit's whole-request timeout on the long-lived stream is an |
246 | | - # expected terminal condition, not an error, so end the task quietly instead of logging a traceback. |
247 | | - pass |
| 246 | + # expected terminal condition, not an error, so log a warning and end the task instead of a traceback. |
| 247 | + self._to_logger.warning('Log streaming stopped: the log stream request timed out.') |
248 | 248 | except Exception: |
249 | 249 | # Exception in log redirection should not propagate further. |
250 | 250 | self._to_logger.exception('Log redirection stopped due to unexpected error:') |
0 commit comments