refactor(errors): replace silent exception handling in console and server
This commit is contained in:
1 parent
41deb48005
commit
c76c9bb7e8
20 files changed
+267
-107
No files matched your search
@@ -15,8 +15,11 @@
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from admin_console.core.state import state
|
||||
except ImportError:
|
||||
@@ -76,7 +79,8 @@ class IPCService:
|
||||
if isinstance(payload, str):
|
||||
try:
|
||||
payload = json.loads(payload)
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Non-JSON payload string: cleaned as-is below.
|
||||
pass
|
||||
|
||||
if tr_type in ("tool", "action") and isinstance(payload, dict):
|
||||
@@ -102,7 +106,8 @@ class IPCService:
|
||||
if isinstance(pl, str):
|
||||
try:
|
||||
pl = json.loads(pl)
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Non-JSON payload string: cleaned as-is below.
|
||||
pass
|
||||
|
||||
if tr_dict.get("type") == "llm_call":
|
||||
@@ -190,7 +195,14 @@ class IPCService:
|
||||
try:
|
||||
cb(event_type, data)
|
||||
except Exception:
|
||||
pass
|
||||
# One broken subscriber must not block the
|
||||
# others, but a silent drop hides it entirely.
|
||||
logger.warning(
|
||||
"IPC subscriber %r failed for event %s",
|
||||
cb,
|
||||
event_type,
|
||||
exc_info=True,
|
||||
)
|
||||
except (ValueError, UnicodeDecodeError, TypeError) as e:
|
||||
print(f"IPC parse error: {e}")
|
||||
except asyncio.CancelledError:
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
@@ -25,6 +26,8 @@ from fastapi import HTTPException
|
||||
|
||||
from artemis.config import IMAGES_DIR, TRACES_PATH, WORKSPACE_ROOT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MediaService:
|
||||
"""Service handling media indexing, local file security, payload unwrapping and notes/plans."""
|
||||
@@ -76,7 +79,9 @@ class MediaService:
|
||||
str(result),
|
||||
)
|
||||
return result
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Filesystem probe failed (missing/locked file): serve the
|
||||
# original path and let the media endpoint report the error.
|
||||
pass
|
||||
return p
|
||||
|
||||
@@ -167,8 +172,9 @@ class MediaService:
|
||||
url = cls.path_to_video_url(item)
|
||||
idx[item.stem] = url
|
||||
idx[item.name] = url
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
# Unreadable directory entry: index the rest of the tree.
|
||||
logger.warning("Video index scan failed under %s: %s", d, exc)
|
||||
return idx
|
||||
|
||||
@classmethod
|
||||
@@ -219,7 +225,8 @@ class MediaService:
|
||||
item = cls.ensure_browser_playable_video(item)
|
||||
v_url = cls.path_to_video_url(item)
|
||||
break
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Unreadable session dir: no recording to resolve.
|
||||
pass
|
||||
|
||||
if not v_url and row_dict.get("initial_goal"):
|
||||
@@ -274,7 +281,8 @@ class MediaService:
|
||||
obj.startswith("[") and obj.endswith("]")
|
||||
):
|
||||
return MediaService.unwrap_payload(json.loads(obj))
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Looks like JSON but is not: treat it as a plain string.
|
||||
pass
|
||||
|
||||
# Check if the string is a Base64 image
|
||||
@@ -284,7 +292,8 @@ class MediaService:
|
||||
try:
|
||||
_, base64_data = obj.split(";base64,", 1)
|
||||
is_base64_img = True
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Malformed data URI: fall through to plain-string handling.
|
||||
pass
|
||||
elif obj.startswith("iVBORw0KGgo") or obj.startswith("/9j/"):
|
||||
is_base64_img = True
|
||||
@@ -301,8 +310,10 @@ class MediaService:
|
||||
image_path.write_bytes(image_bytes)
|
||||
|
||||
return f"image://{image_hash}"
|
||||
except Exception:
|
||||
pass
|
||||
except (ValueError, OSError) as exc:
|
||||
# Invalid base64 or image cache write failure: fall back
|
||||
# to the truncated raw string below.
|
||||
logger.debug("Could not persist inline image: %s", exc)
|
||||
|
||||
if len(obj) > 2000:
|
||||
preview_start = obj[:100].replace("\n", " ")
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModelService:
|
||||
"""Service handling agent architecture profile detection and metadata formatting."""
|
||||
@@ -44,8 +47,10 @@ class ModelService:
|
||||
elif llm_cfg and llm_cfg.default:
|
||||
provider = str(llm_cfg.default.provider)
|
||||
model_id = str(llm_cfg.default.model)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Unreadable/invalid LLM config: fall back to display defaults
|
||||
# (cached for the TTL, so this logs at most once per window).
|
||||
logger.warning("Could not resolve LLM config for display: %s", exc)
|
||||
cls._llm_info_cache = (now, provider, model_id)
|
||||
return provider, model_id
|
||||
|
||||
@@ -94,7 +99,8 @@ class ModelService:
|
||||
return "pro"
|
||||
elif "flash" in p_str:
|
||||
return "flash"
|
||||
except Exception:
|
||||
except (ValueError, TypeError):
|
||||
# Malformed device_info JSON: try the other profile sources.
|
||||
pass
|
||||
|
||||
if (row_dict.get("status") == "running") and running_profile:
|
||||
@@ -132,7 +138,8 @@ class ModelService:
|
||||
for x in ("planner", "validator", "operator", "checker")
|
||||
):
|
||||
return "pro"
|
||||
except Exception:
|
||||
except (ValueError, TypeError):
|
||||
# Malformed trace payload JSON: skip this trace.
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import asyncio
|
||||
import codecs
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import shutil
|
||||
@@ -48,6 +49,8 @@ from artemis.runtime import (
|
||||
trace_store,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TaskQueueService:
|
||||
"""Service managing FIFO task execution, background worker, subprocess lifecycle,
|
||||
@@ -76,7 +79,14 @@ class TaskQueueService:
|
||||
try:
|
||||
cb(event_type, data)
|
||||
except Exception:
|
||||
pass
|
||||
# One broken subscriber must not block the others, but a
|
||||
# silent drop hides it entirely.
|
||||
logger.warning(
|
||||
"Event subscriber %r failed for event %s",
|
||||
cb,
|
||||
event_type,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _broadcast_startup_progress(
|
||||
@@ -194,7 +204,9 @@ class TaskQueueService:
|
||||
if log_file is not None:
|
||||
try:
|
||||
log_file.write(text)
|
||||
except Exception:
|
||||
except (OSError, ValueError):
|
||||
# Best-effort tee into stdout.log; console output above
|
||||
# already carried the text.
|
||||
pass
|
||||
|
||||
try:
|
||||
@@ -214,7 +226,8 @@ class TaskQueueService:
|
||||
if log_file is not None:
|
||||
try:
|
||||
log_file.close()
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Flush-on-close of the best-effort tee failed; nothing to do.
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@@ -265,7 +278,8 @@ class TaskQueueService:
|
||||
return proc.returncode if proc.returncode is not None else -15
|
||||
except (psutil.NoSuchProcess, ProcessLookupError):
|
||||
return proc.returncode if proc.returncode is not None else -15
|
||||
except Exception:
|
||||
except psutil.Error:
|
||||
# Transient probe failure (e.g. AccessDenied): keep waiting.
|
||||
pass
|
||||
else:
|
||||
return proc.returncode if proc.returncode is not None else -15
|
||||
@@ -279,7 +293,9 @@ class TaskQueueService:
|
||||
state.worker_task = loop.create_task(cls.queue_worker())
|
||||
try:
|
||||
state.wake_event.set()
|
||||
except Exception:
|
||||
except AttributeError:
|
||||
# Partially initialized state (unit tests): the worker
|
||||
# loop polls anyway.
|
||||
pass
|
||||
except RuntimeError:
|
||||
pass
|
||||
@@ -515,8 +531,11 @@ class TaskQueueService:
|
||||
if st:
|
||||
st["pid"] = proc.pid
|
||||
trace_store.write_status(str(sess_id), st)
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
# Status probes fall back to DB PID bookkeeping, but note it.
|
||||
print(
|
||||
f"[QueueWorker] Could not record worker pid in status.json for {sess_id}: {exc}"
|
||||
)
|
||||
cls._broadcast_startup_progress(
|
||||
sess_id, "process_ready", "Execution process started"
|
||||
)
|
||||
@@ -583,8 +602,12 @@ class TaskQueueService:
|
||||
str(sess_id),
|
||||
canonical_mcp_status,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
# A stale status.json would leave MCP pollers seeing
|
||||
# "running" forever, so surface the failure.
|
||||
print(
|
||||
f"[QueueWorker] Could not persist terminal MCP status for {sess_id}: {exc}"
|
||||
)
|
||||
rec_info = session_repo.get_video_recording_for_session(sess_id)
|
||||
rec_status = (rec_info or {}).get("status")
|
||||
recovered_video_path = None
|
||||
@@ -848,8 +871,9 @@ class TaskQueueService:
|
||||
active_owners: dict[str, Any] = {}
|
||||
try:
|
||||
active_owners = DeviceExecutionLock.get_active_owners()
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
# Unreadable lock dir: fall back to the single-owner probe below.
|
||||
print(f"[stop_tasks] Could not enumerate device owners: {exc}")
|
||||
fallback = DeviceExecutionLock.get_active_owner()
|
||||
if fallback and not active_owners:
|
||||
active_owners["default"] = fallback
|
||||
@@ -872,8 +896,10 @@ class TaskQueueService:
|
||||
"cancelled",
|
||||
error="Task stopped from the Artemis frontend.",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
print(
|
||||
f"[stop_tasks] Could not persist cancelled MCP status for {sid}: {exc}"
|
||||
)
|
||||
cls._broadcast_event(
|
||||
"session_ended",
|
||||
{
|
||||
@@ -895,7 +921,8 @@ class TaskQueueService:
|
||||
if run_proc is not None and run_proc.returncode is None:
|
||||
try:
|
||||
run_proc.kill()
|
||||
except Exception:
|
||||
except (ProcessLookupError, OSError):
|
||||
# Process already exited between the check and the kill.
|
||||
pass
|
||||
# active_runs entries are popped by each run's finalizer once the
|
||||
# process exit is observed; clearing them here would free the device
|
||||
@@ -903,7 +930,8 @@ class TaskQueueService:
|
||||
if state.current_process:
|
||||
try:
|
||||
state.current_process.kill()
|
||||
except Exception:
|
||||
except (ProcessLookupError, OSError):
|
||||
# Process already exited between the check and the kill.
|
||||
pass
|
||||
state.current_process = None
|
||||
|
||||
@@ -915,7 +943,9 @@ class TaskQueueService:
|
||||
if PAUSE_FILE.exists():
|
||||
try:
|
||||
PAUSE_FILE.unlink()
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Best-effort cleanup of the pause marker; a leftover file
|
||||
# only pauses until the next resume request.
|
||||
pass
|
||||
|
||||
cls.ensure_worker_running()
|
||||
@@ -926,8 +956,9 @@ class TaskQueueService:
|
||||
active_owners = {}
|
||||
try:
|
||||
active_owners = DeviceExecutionLock.get_active_owners()
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
# Unreadable lock dir: the local-process fallbacks below still apply.
|
||||
print(f"[stop_tasks] Could not enumerate device owners: {exc}")
|
||||
|
||||
owner = None
|
||||
if target_sid:
|
||||
@@ -1066,7 +1097,8 @@ class TaskQueueService:
|
||||
try:
|
||||
local_proc.kill()
|
||||
stopped = True
|
||||
except Exception:
|
||||
except (ProcessLookupError, OSError):
|
||||
# Process already exited; terminate_tree above may have got it.
|
||||
pass
|
||||
stopped = True
|
||||
is_local_owner = True
|
||||
@@ -1097,8 +1129,11 @@ class TaskQueueService:
|
||||
if row_pid and session_repo.process_is_alive(row_pid):
|
||||
try:
|
||||
process_supervisor.terminate_tree(int(row_pid))
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Report it: a surviving worker keeps the device busy.
|
||||
print(
|
||||
f"[stop_tasks] Could not terminate worker pid {row_pid}: {exc}"
|
||||
)
|
||||
DeviceExecutionLock.cleanup_stale_locks()
|
||||
stopped = True
|
||||
|
||||
@@ -1176,7 +1211,9 @@ class TaskQueueService:
|
||||
if PAUSE_FILE.exists():
|
||||
try:
|
||||
PAUSE_FILE.unlink()
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Best-effort cleanup of the pause marker; a leftover file
|
||||
# only pauses until the next resume request.
|
||||
pass
|
||||
|
||||
cls.ensure_worker_running()
|
||||
|
||||
Reference in new issue
Block a user