Aplikasi AI research workbench berbasis desktop yang mengintegrasikan Tauri dan Model Context Protocol (MCP) runner sering mengeksekusi komputasi saintifik lokal secara intensif. Masalah muncul ketika sub-agent MCP mengeksekusi script eksternal (seperti NumPy, PyTorch, atau binary kompilasi C++) yang mengalami deadlock, blocking call tanpa timeout, atau thread synchronization failure. Workbench membeku, IPC socket berhenti merespons, dan resource CPU/RAM tersandera oleh zombie process.
Artikel ini menyajikan panduan implementasi sistem pemantauan dan pemulihan otomatis untuk mendeteksi stalled agent, mengeksekusi terminasi bertahap, dan melakukan rollback workspace terisolasi tanpa merusak integritas file riset pengguna.
Arsitektur Observabilitas: IPC Socket Health Check & OTel
Runner MCP berjalan sebagai child process yang dikelola oleh backend Tauri (Rust). Untuk mendeteksi sub-agent yang tidak lagi memproses event queue, kita menggabungkan export tracing lokal via OpenTelemetry (OTel) dan low-overhead ping loop melalui IPC UNIX domain socket / named pipe.
1. Structured Heartbeat via IPC
Setiap child process MCP runner wajib mengekspos endpoint internal ping-pong. Rust backend mengirimkan query heartbeat secara periodik. Jika ACK tidak diterima dalam ambang batas tertentu, circuit breaker beralih status.
use std::time::Duration;
use tokio::net::UnixStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub async fn check_subagent_liveness(socket_path: &str, timeout_dur: Duration) -> Result<bool, String> {
tokio::select! {
res = async {
let mut stream = UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?;
stream.write_all(b"PING\n").await.map_err(|e| e.to_string())?;
let mut buf = [0u8; 4];
stream.read_exact(&mut buf).await.map_err(|e| e.to_string())?;
Ok(&buf == b"PONG")
} => res,
_ = tokio::time::sleep(timeout_dur) => {
Err("Heartbeat timeout: child process stalled in blocking call".into())
}
}
}2. Local OpenTelemetry Collector
Tracing span lokal dikirimkan dari frontend (webview) dan backend Tauri ke OTel Collector lokal (sidecar binary atau direct in-process exporter) untuk memonitor tracing span tiap eksekusi tools saintifik. Jika span eksekusi tool tidak memicu status ended melebihi batas SLA tool (misalnya 120 detik), alert IPC internal dipicu untuk menghentikan workflow sebelum kehabisan memori OS.
Menangani Unhandled Rejection dan Child Process Lifecycle
Pada sub-agent berbasis Node.js atau Python runner, eksekusi kode saintifik pihak ketiga kerap menimbulkan native binding crash atau unhandled rejection yang membiarkan IPC pipe tergantung (hanging). Handle hook proses pada runner child:
// Inisialisasi fail-safe hook pada TypeScript MCP runner
process.on("unhandledRejection", (reason: unknown) => {
process.stderr.write(`[CRITICAL] Unhandled Rejection: ${String(reason)}\n`);
// Kirim sinyal failure terstruktur ke Tauri parent via stdout/named IPC sebelum exit
process.exit(101);
});Circuit Breaker dan Graceful Process Termination
Saat liveness check gagal, backend Tauri tidak boleh langsung melakukan SIGKILL. Langkah terminasi harus terstruktur untuk mencegah fragmentasi file atau file lock yang terkunci pada OS.
- State Tripped: Circuit breaker berpindah dari
CLOSEDkeOPEN. Eksekusi request MCP berikutnya ditolak seketika. - Graceful SIGTERM: Kirim sinyal
SIGTERMke Process ID (PID) sub-agent runner. Berikan grace period terukur (misal: 3–5 detik) untuk melepaskan handle file dan buffer memori. - Forced SIGKILL: Jika proses masih terdaftar di kernel process table setelah grace period habis, kirim
SIGKILL(atauTerminateProcessdi Windows) dan bersihkan zombie process handle.
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
use tokio::time::{sleep, Duration};
pub async fn terminate_stalled_process(pid: u32, grace_period: Duration) {
let nix_pid = Pid::from_raw(pid as i32);
// 1. Coba hentikan secara graceful
let _ = kill(nix_pid, Signal::SIGTERM);
let start = std::time::Instant::now();
loop {
if start.elapsed() >= grace_period {
// 2. Force kill jika batas waktu terlewati
let _ = kill(nix_pid, Signal::SIGKILL);
break;
}
// Cek apakah proses masih eksis (signal 0)
if kill(nix_pid, None).is_err() {
// Proses telah berhenti
return;
}
sleep(Duration::from_millis(200)).await;
}
}Strategi Rollback Workspace via Isolated Checkpoint
Ketika sub-agent mati di tengah penulisan file saintifik (misal modifikasi file CSV, HDF5, atau Jupyter Notebook), direktori kerja utama bisa korup. Hindari manipulasi langsung pada folder proyek utama.
Snapshotting Pola Copy-on-Write (CoW) / Staging Path
- Staging Sandbox: Sebelum tool saintifik dieksekusi, sub-agent hanya diberi akses write ke temporary staging area:
.workbench/checkpoints/{transaction_id}/. - Atomic Commit: Jika eksekusi berhasil dan validasi struktur lolos, file dipindahkan secara atomik (menggunakan
renamefilesystem system call) ke direktori proyek aktif. - Rollback Protocol: Jika circuit breaker mencatat kegagalan atau terjadi timeout/stalled process, buang direktori snapshot
{transaction_id}. Workspace pengguna kembali utuh pada state stabil terakhir.
Rekomendasi Konfigurasi Timeout
Berikut batas konfigurasi timeout standar yang disarankan untuk workbench desktop:
- IPC Liveness Heartbeat:
2000 msinterval,5000 mstimeout threshold. - Scientific Tool Execution: Default
60000 ms(dapat dinaikkan per tool annotation via metadata MCP). - Graceful Shutdown Window:
3000 msantaraSIGTERMdanSIGKILL. - Circuit Breaker Cooldown:
15000 msdalam statusOPENsebelum transisi keHALF-OPENuntuk pengujian single ping.
Postmortem Singkat: Deadlock Matplotlib Backend pada Sub-Agent
Insiden: Workbench UI membeku saat menjalankan plotting dataset 5GB via sub-agent Python MCP.
Akar Masalah: Script sub-agent memicu Matplotlib dengan GUI backend default (TkAgg/Qt) alih-alih headless backend (Agg). Sub-agent menunggu loop GUI OS yang tidak pernah diinisialisasi oleh subprocess headless Tauri, menyebabkan blocking deadlock tak terbatas.
Pencegahan & Perbaikan:
- Injeksi environment variable
MPLBACKEND=AggdanPYTHONUNBUFFERED=1saat spawning child process dari Tauri backend. - Menambahkan batas waktu timeout hard cap 30 detik untuk semua tool bertipe I/O rendering.
- Mengaktifkan IPC ping socket terpisah dari main thread subprocess runner.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!