fix(agent_jobs): mover cola de SQLite a ficheros JSON (cross-9p safe)
Bug: Echo (gx-cli en WSL) recibia "disk I/O error" al INSERT en la tabla `agent_jobs` de graph_explorer.db. Causa: graph_explorer.exe mantiene esa BD abierta con journal_mode=WAL desde Windows, y SQLite WAL exige mmap del .shm compartido entre procesos. Cuando un escritor accede via /mnt/c (9p) y el otro nativo NTFS, ese mmap falla. El proyecto ya habia resuelto este patron antes: el contador de mutaciones (.mutations.marker) usa fichero plano en vez de SQL por exactamente la misma razon. agent_jobs era la unica cola que se quedo en SQLite — momento de aplicar el mismo fix. Cambios: * gx-cli cmd_enricher_run: en lugar de INSERT, escribe `<app_dir>/agent_jobs_queue/<req_id>.json` con el payload del job. Atomic write (tmp + rename, atomico tanto en NTFS como en 9p). * main.cpp polling: en lugar de SELECT/DELETE sobre agent_jobs, escanea ese directorio cada frame, lee cada JSON via json_extract (sqlite3 in-memory, sin tocar archivos en disco), llama jobs_submit, y borra el fichero. Throttle a 8 jobs por frame igual que antes. * main.cpp: anyade <filesystem> y <fstream>. * tests/test_gx_cli.py: 5 tests nuevos en TestCliEnricherRun: - escribe fichero JSON con req_id como nombre - NO crea tabla agent_jobs en graph_explorer.db (regresion) - errores claros si enricher o nodo no existen - no quedan .tmp tras encolado exitoso WSL 79 / Windows 68 + 11 skipped.
This commit is contained in:
@@ -597,9 +597,15 @@ def _parse_yaml_minimal(text: str) -> dict:
|
||||
|
||||
|
||||
def cmd_enricher_run(args) -> None:
|
||||
"""Inserta un job en la cola agent_jobs. main.cpp lo recoge cada frame y
|
||||
lo somete via jobs_submit (que arranca el subprocess). Asi reusamos el
|
||||
pool de workers existente sin duplicar logica."""
|
||||
"""Encola un job en `<app_dir>/agent_jobs_queue/<req_id>.json`.
|
||||
|
||||
main.cpp escanea ese directorio cada frame, lee cada JSON, somete
|
||||
via jobs_submit y borra el fichero. Usamos directorio de ficheros
|
||||
en lugar de tabla SQLite por la misma razon que el marker
|
||||
`.mutations.marker`: graph_explorer.db esta abierta en WAL desde
|
||||
el lado Windows, y gx-cli escribiendo via /mnt/c (9p) hace que el
|
||||
mmap del .shm falle silenciosamente -> disk I/O error.
|
||||
"""
|
||||
edir = _enrichers_dir()
|
||||
if not (edir / args.enricher / "manifest.yaml").is_file():
|
||||
_die(f"enricher not found: {args.enricher}")
|
||||
@@ -616,26 +622,29 @@ def cmd_enricher_run(args) -> None:
|
||||
node_name = ""
|
||||
|
||||
req_id = f"areq_{_now_ms()}"
|
||||
payload = {
|
||||
"id": req_id,
|
||||
"enricher_id": args.enricher,
|
||||
"node_id": args.node or "",
|
||||
"node_name": node_name,
|
||||
"params_json": args.params or "{}",
|
||||
"created_at": _now_ms(),
|
||||
}
|
||||
|
||||
app_dir = os.environ.get("GX_APP_DIR", "")
|
||||
if not app_dir:
|
||||
_die("GX_APP_DIR env var is empty")
|
||||
queue_dir = Path(app_dir) / "agent_jobs_queue"
|
||||
try:
|
||||
cn = sqlite3.connect(_app_db())
|
||||
cn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS agent_jobs ("
|
||||
" id TEXT PRIMARY KEY,"
|
||||
" enricher_id TEXT NOT NULL,"
|
||||
" node_id TEXT NOT NULL DEFAULT '',"
|
||||
" node_name TEXT NOT NULL DEFAULT '',"
|
||||
" params_json TEXT NOT NULL DEFAULT '{}',"
|
||||
" created_at INTEGER NOT NULL)"
|
||||
)
|
||||
cn.execute(
|
||||
"INSERT INTO agent_jobs (id, enricher_id, node_id, node_name, "
|
||||
"params_json, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(req_id, args.enricher, args.node or "", node_name,
|
||||
args.params or "{}", _now_ms()),
|
||||
)
|
||||
cn.commit()
|
||||
cn.close()
|
||||
except sqlite3.Error as e:
|
||||
queue_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Atomic write: tmp + rename. main.cpp nunca lee un JSON a medias
|
||||
# porque el rename es atomico en NTFS y en 9p.
|
||||
tmp = queue_dir / f"{req_id}.json.tmp"
|
||||
final = queue_dir / f"{req_id}.json"
|
||||
tmp.write_text(json.dumps(payload, ensure_ascii=False),
|
||||
encoding="utf-8")
|
||||
os.replace(tmp, final)
|
||||
except OSError as e:
|
||||
_die(f"could not enqueue: {e}")
|
||||
_ok(request_id=req_id, enricher=args.enricher, node=args.node or "",
|
||||
message="job encolado, lo recoge el panel Jobs")
|
||||
|
||||
Reference in New Issue
Block a user