From 94c321d05ce02ee73304e6cf99187e2d4d96e409 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sat, 31 Oct 2026 23:13:40 +0800 Subject: [PATCH] fix(ci): fail Windows signing on error and retry transient HTTP failures res/job.py sign_files exited 0 when a file was left unsigned: a sign task that never reached "done" only broke out of the current directory, and a failed download of the signed file was ignored, keeping the unsigned original in place. Both now exit non-zero, so the Windows signing steps fail the job. Retry requests.get/post on connection-level errors with the same wrapper the signing service's client uses, rewinding upload streams before each retry. Tunable via HTTP_RETRY_MAX_TRIES and HTTP_RETRY_SLEEP_SECS. Co-Authored-By: Claude Opus 5.5 --- res/job.py | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/res/job.py b/res/job.py index e53105fd3..22703f86e 100755 --- a/res/job.py +++ b/res/job.py @@ -6,8 +6,48 @@ import time import argparse import logging import shutil +import sys import zipfile + +def _file_stream_positions(files): + if not files: + return [] + values = files.values() if hasattr(files, "values") else (item[1] for item in files) + positions = [] + for value in values: + stream = value[1] if isinstance(value, tuple) and len(value) > 1 else value + if hasattr(stream, "tell") and hasattr(stream, "seek"): + try: + positions.append((stream, stream.tell())) + except (OSError, ValueError): + pass + return positions + + +def _with_simple_retries(fn, max_attempts=8, sleep_secs=1.0): + def _wrapper(*args, **kwargs): + file_positions = _file_stream_positions(kwargs.get("files")) + for i in range(max_attempts): + try: + if i: + for stream, position in file_positions: + stream.seek(position) + return fn(*args, **kwargs) + except Exception as e: + logging.warning(f"{fn.__name__} attempt {i+1}/{max_attempts} failed: {e}") + if i == max_attempts - 1: + raise + time.sleep(sleep_secs) + return _wrapper + + +HTTP_RETRY_MAX_TRIES = int(os.getenv("HTTP_RETRY_MAX_TRIES") or "8") +HTTP_RETRY_SLEEP_SECS = float(os.getenv("HTTP_RETRY_SLEEP_SECS") or "1") + +requests.get = _with_simple_retries(requests.get, max_attempts=HTTP_RETRY_MAX_TRIES, sleep_secs=HTTP_RETRY_SLEEP_SECS) +requests.post = _with_simple_retries(requests.post, max_attempts=HTTP_RETRY_MAX_TRIES, sleep_secs=HTTP_RETRY_SLEEP_SECS) + logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]", @@ -164,10 +204,13 @@ def sign_one_file(file_path): n += 1 status = get_status(task_id) if status and status.get("state") == "done": - download_one_file( + downloaded = download_one_file( task_id, os.path.basename(file_path), os.path.dirname(file_path) ) delete_task(task_id) + if not downloaded: + logging.error(f"Failed to download signed {file_path}") + return False logging.info(f"Signed {file_path}") return True return False @@ -217,7 +260,7 @@ def sign_files(dir_path, only_ext=None): if ext in SIGN_EXTENSIONS: if not sign_one_file(file_path): logging.error(f"Failed to sign {file_path}") - break + sys.exit(1) def main():