| 327 | return normalized_parts |
| 328 | |
| 329 | def _read_until_marker(self, marker: str) -> tuple[str, bool]: |
| 330 | if self.session is None: |
| 331 | raise RuntimeError("No session available.") |
| 332 | |
| 333 | start_time = time.time() |
| 334 | stop_signal_sent = False |
| 335 | stop_deadline: float | None = None |
| 336 | output = "" |
| 337 | |
| 338 | while True: |
| 339 | if self.session.recv_ready(): |
| 340 | output += self.session.recv(8192).decode("utf-8", "ignore") |
| 341 | if marker in output: |
| 342 | return output, stop_signal_sent |
| 343 | else: |
| 344 | time.sleep(0.1) |
| 345 | |
| 346 | now = time.time() |
| 347 | if not stop_signal_sent and now - start_time > self.runtime.ssh.timeout: |
| 348 | self.session.send("\x03") |
| 349 | stop_signal_sent = True |
| 350 | stop_deadline = now + self.runtime.ssh.timeout |
| 351 | |
| 352 | if stop_signal_sent and stop_deadline is not None and now > stop_deadline: |
| 353 | raise TimeoutError( |
| 354 | "Command timeout and could not recover the shell session." |
| 355 | ) |
| 356 | |
| 357 | @staticmethod |
| 358 | def _extract_exit_code(raw_output: str, marker: str) -> int: |