gunicorn #1
@@ -104,7 +104,7 @@ def _is_transient_scgi_error(exc: Exception) -> bool:
|
|||||||
if err_no in {errno.ECONNREFUSED, errno.ECONNRESET, errno.ETIMEDOUT, errno.EHOSTUNREACH, errno.ENETUNREACH}:
|
if err_no in {errno.ECONNREFUSED, errno.ECONNRESET, errno.ETIMEDOUT, errno.EHOSTUNREACH, errno.ENETUNREACH}:
|
||||||
return True
|
return True
|
||||||
msg = str(exc).lower()
|
msg = str(exc).lower()
|
||||||
return any(text in msg for text in ("connection refused", "connection reset", "timed out", "empty response"))
|
return any(text in msg for text in ("connection refused", "connection reset", "timed out", "timeout", "empty response", "pipe creation failed", "resource temporarily unavailable", "try again", "temporarily unavailable"))
|
||||||
|
|
||||||
|
|
||||||
def client_for(profile: dict) -> ScgiRtorrentClient:
|
def client_for(profile: dict) -> ScgiRtorrentClient:
|
||||||
@@ -112,32 +112,78 @@ def client_for(profile: dict) -> ScgiRtorrentClient:
|
|||||||
|
|
||||||
|
|
||||||
_UNSUPPORTED_EXEC_METHODS: set[str] = set()
|
_UNSUPPORTED_EXEC_METHODS: set[str] = set()
|
||||||
|
_EXEC_TARGET_STYLE: dict[str, int] = {}
|
||||||
|
|
||||||
def _rt_execute(c: ScgiRtorrentClient, method: str, *args):
|
def _rt_execute_preview(method_name: str, call_args: tuple) -> str:
|
||||||
"""Run rTorrent execute.* as the rTorrent user across XML-RPC variants."""
|
# Note: Skrocony opis RPC usuwa dlugie skrypty z komunikatu bledu, ale zostawia metode i pierwsze argumenty do diagnostyki.
|
||||||
method_names = [method]
|
|
||||||
if method.startswith("execute."):
|
|
||||||
execute2 = method.replace("execute.", "execute2.", 1)
|
|
||||||
if execute2 not in _UNSUPPORTED_EXEC_METHODS:
|
|
||||||
method_names.append(execute2)
|
|
||||||
errors = []
|
|
||||||
for method_name in method_names:
|
|
||||||
for call_args in (("", *args), args):
|
|
||||||
try:
|
|
||||||
return c.call(method_name, *call_args)
|
|
||||||
except Exception as exc:
|
|
||||||
message = str(exc)
|
|
||||||
if "not defined" in message.lower():
|
|
||||||
_UNSUPPORTED_EXEC_METHODS.add(method_name)
|
|
||||||
preview = ", ".join(repr(x) for x in call_args[:3])
|
preview = ", ".join(repr(x) for x in call_args[:3])
|
||||||
if len(call_args) > 3:
|
if len(call_args) > 3:
|
||||||
preview += ", ..."
|
preview += ", ..."
|
||||||
errors.append(f"{method_name}({preview}): {exc}")
|
return f"{method_name}({preview})"
|
||||||
|
|
||||||
|
|
||||||
|
def _rt_execute_target_variants(method: str, args: tuple) -> list[tuple]:
|
||||||
|
# Note: rTorrent XML-RPC w zaleznosci od wersji wymaga pustego targetu albo go odrzuca; zapamietujemy dzialajacy wariant per metoda.
|
||||||
|
variants = [("", *args), args]
|
||||||
|
preferred = _EXEC_TARGET_STYLE.get(method)
|
||||||
|
if preferred is not None and 0 <= preferred < len(variants):
|
||||||
|
return [variants[preferred]] + [v for i, v in enumerate(variants) if i != preferred]
|
||||||
|
return variants
|
||||||
|
|
||||||
|
|
||||||
|
def _is_rt_method_missing(exc: Exception) -> bool:
|
||||||
|
msg = str(exc).lower()
|
||||||
|
return "not defined" in msg or "no such method" in msg or "unknown method" in msg
|
||||||
|
|
||||||
|
|
||||||
|
def _rt_execute_methods(method: str) -> list[str]:
|
||||||
|
# Note: execute2.* jest probowane dopiero gdy podstawowe execute.* nie istnieje, zeby nie generowac falszywych bledow retry.
|
||||||
|
methods = [method]
|
||||||
|
if method.startswith("execute."):
|
||||||
|
fallback = method.replace("execute.", "execute2.", 1)
|
||||||
|
if fallback not in _UNSUPPORTED_EXEC_METHODS:
|
||||||
|
methods.append(fallback)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
|
||||||
|
def _rt_execute(c: ScgiRtorrentClient, method: str, *args):
|
||||||
|
"""Run rTorrent execute.* as the rTorrent user across XML-RPC variants."""
|
||||||
|
errors: list[str] = []
|
||||||
|
attempts = _scgi_retry_attempts()
|
||||||
|
for attempt in range(1, attempts + 1):
|
||||||
|
errors.clear()
|
||||||
|
transient_seen = False
|
||||||
|
primary_missing = False
|
||||||
|
for method_index, method_name in enumerate(_rt_execute_methods(method)):
|
||||||
|
if method_name in _UNSUPPORTED_EXEC_METHODS:
|
||||||
|
continue
|
||||||
|
if method_index > 0 and not primary_missing:
|
||||||
|
continue
|
||||||
|
for call_args in _rt_execute_target_variants(method_name, args):
|
||||||
|
try:
|
||||||
|
result = c.call(method_name, *call_args)
|
||||||
|
if method_name == method:
|
||||||
|
_EXEC_TARGET_STYLE[method_name] = 0 if call_args and call_args[0] == "" else 1
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
if _is_rt_method_missing(exc):
|
||||||
|
_UNSUPPORTED_EXEC_METHODS.add(method_name)
|
||||||
|
if method_name == method:
|
||||||
|
primary_missing = True
|
||||||
|
errors.append(f"{method_name}: method not defined")
|
||||||
|
break
|
||||||
|
transient_seen = transient_seen or _is_transient_scgi_error(exc)
|
||||||
|
errors.append(f"{_rt_execute_preview(method_name, call_args)}: {exc}")
|
||||||
|
if transient_seen and attempt < attempts:
|
||||||
|
time.sleep(_scgi_retry_delay(attempt))
|
||||||
|
continue
|
||||||
|
break
|
||||||
raise RuntimeError("rTorrent execute failed: " + "; ".join(errors))
|
raise RuntimeError("rTorrent execute failed: " + "; ".join(errors))
|
||||||
|
|
||||||
|
|
||||||
def _is_rt_timeout_error(exc: Exception) -> bool:
|
def _is_rt_timeout_error(exc: Exception) -> bool:
|
||||||
return isinstance(exc, (TimeoutError, socket.timeout)) or "timed out" in str(exc).lower()
|
msg = str(exc).lower()
|
||||||
|
return isinstance(exc, (TimeoutError, socket.timeout)) or "timed out" in msg or "timeout" in msg
|
||||||
|
|
||||||
|
|
||||||
def _rt_execute_allow_timeout(c: ScgiRtorrentClient, method: str, *args):
|
def _rt_execute_allow_timeout(c: ScgiRtorrentClient, method: str, *args):
|
||||||
@@ -193,6 +239,7 @@ def _run_remote_move(c: ScgiRtorrentClient, src: str, dst: str, poll_interval: f
|
|||||||
try:
|
try:
|
||||||
output = str(_rt_execute(c, "execute.capture", "sh", "-c", poll_script, "pytorrent-move-poll", status_path) or "").strip()
|
output = str(_rt_execute(c, "execute.capture", "sh", "-c", poll_script, "pytorrent-move-poll", status_path) or "").strip()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
# Note: Podczas masowego move rTorrent potrafi chwilowo nie utworzyc pipe dla execute.capture; polling czeka i probuje dalej.
|
||||||
if _is_rt_timeout_error(exc) or _is_transient_scgi_error(exc):
|
if _is_rt_timeout_error(exc) or _is_transient_scgi_error(exc):
|
||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
@@ -263,6 +310,7 @@ def _run_remote_rm(c: ScgiRtorrentClient, path: str, poll_interval: float = 2.0)
|
|||||||
try:
|
try:
|
||||||
output = str(_rt_execute(c, "execute.capture", "sh", "-c", poll_script, "pytorrent-rm-poll", status_path) or "").strip()
|
output = str(_rt_execute(c, "execute.capture", "sh", "-c", poll_script, "pytorrent-rm-poll", status_path) or "").strip()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
# Note: Remove uzywa tego samego bezpiecznego pollingu co move, wiec chwilowy brak pipe nie wywala calej kolejki.
|
||||||
if _is_rt_timeout_error(exc) or _is_transient_scgi_error(exc):
|
if _is_rt_timeout_error(exc) or _is_transient_scgi_error(exc):
|
||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user