Worker agent AI yang mengotomasi interaksi antarmuka perangkat bergerak (seperti pola pada lycorp-jp/sim-use) menjalankan ribuan perintah CLI per jam. Dalam skala produksi, worker sering mengalami degradasi performa bertahap hingga berhenti merespons (hung). Masalah ini umumnya dipicu oleh zombie process dan orphan process dari adb (Android Debug Bridge) serta xcrun simctl (iOS Simulator) akibat unhandled timeout saat eksekusi aksi atau UI assertion gagal.
Gejala: Thread Exhaustion dan Resource Starvation
Saat worker agent mengevaluasi state UI menggunakan Vision-Language Models (VLM) atau pohon hierarki DOM/Accessibility, kegagalan assertion sering kali memicu exception pada thread runtime (misalnya Python asyncio atau Node.js). Jika exception ini mengabaikan lifecycle child process, sub-proses ADB atau simctl akan tetap berjalan di background tanpa controller.
Dampak langsung pada sistem host meliputi:
- Thread Exhaustion: Thread pool worker habis karena tertahan menunggu I/O pipe stdout/stderr dari child process yang tidak pernah selesai.
- File Descriptor (FD) Leak: Socket koneksi daemon ADB dan pipe streaming video frame simulator tetap terbuka, menghabiskan alokasi limit
ulimit -nsistem. - Proses Menumpuk (Zombie & Orphan): Perintah seperti
adb exec-out screencap -patauxcrun simctl io booted screenshottetap aktif dan membebani CPU host secara kontinu.
Investigasi: Dump File Descriptor dan Process Tree
Untuk mengonfirmasi terjadinya kebocoran file descriptor dan sub-proses yang menggantung, periksa process tree dan alokasi FD pada PID worker agent.
# 1. Identifikasi process tree worker dan child process yang tertinggal
pstree -p <WORKER_PID>
# 2. Cek akumulasi child process adb dan simctl
ps aux | grep -E '(adb|simctl)' | grep -v grep
# 3. Hitung jumlah file descriptor yang terbuka pada proses worker
ls -l /proc/<WORKER_PID>/fd | wc -l
# 4. Dump detail FD untuk mendeteksi socket atau pipe bocor
lsof -p <WORKER_PID> | grep -E '(PIPE|FIFO|unix|IPv4)'Output lsof yang menunjukkan ratusan handle bertipe FIFO atau unix socket dalam status CLOSE_WAIT atau tanpa pembaca membuktikan bahwa child process spawned terputus dari event loop induknya tanpa proses termination yang bersih.
Root Cause: Unhandled Timeout pada UI Assertion
Masalah berakar pada implementasi eksekusi command standar yang tidak mengisolasi sub-proses ke dalam process group tersendiri. Ketika library otomasi membungkus command dengan timeout level aplikasi (misalnya asyncio.wait_for(task, timeout=10)), timeout tersebut membatalkan coroutine induk tetapi tidak otomatis membunuh native OS process yang sudah di-fork.
Sub-proses terus memegang handle socket adb server atau daemon simulator. Ketika worker mencoba menjalankan assertion baru, command berikutnya antre di belakang lock daemon yang sama, memicu cascading failure di seluruh pipeline worker.
Solusi 1: Process Supervisor dengan POSIX Process Group
Gunakan POSIX session ID atau process group baru (setpgrp=True / os.setsid). Dengan memisahkan sub-proses ke group ID baru, sinyal SIGTERM dan SIGKILL dapat dikirim ke seluruh proses tree sub-proses tersebut (menggunakan tanda minus pada PID: -pgid), bukan hanya ke shell wrapper pembungkusnya.
import asyncio
import os
import signal
async def run_driver_command(cmd: list[str], timeout_sec: float) -> bytes:
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
preexec_fn=os.setsid # Buat process group tersendiri
)
pgid = os.getpgid(proc.pid)
try:
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout_sec)
if proc.returncode != 0:
raise RuntimeError(f"Command failed: {stderr.decode()}")
return stdout
except asyncio.TimeoutError:
# Terminasi graceful seluruh process group
os.killpg(pgid, signal.SIGTERM)
try:
# Tunggu grace period sebelum eksekusi paksa
await asyncio.wait_for(proc.wait(), timeout=2.0)
except asyncio.TimeoutError:
# Eksekusi SIGKILL bila process abai terhadap SIGTERM
os.killpg(pgid, signal.SIGKILL)
await proc.wait()
raise TimeoutError(f"Command {cmd[0]} melampaui limit {timeout_sec}s")
finally:
# Pastikan pipe stream tertutup bersih
if proc.stdout and not proc.stdout.at_eof():
proc.stdout.feed_eof()
if proc.stderr and not proc.stderr.at_eof():
proc.stderr.feed_eof()Solusi 2: Healthcheck Socket ADB Sebelum Spawn Command
Menjalankan perintah langsung ke daemon yang sedang deadlocked akan langsung memblokir thread eksekusi. Implementasikan socket probe ringan langsung ke port ADB daemon (default: 5037) sebelum menjalankan eksekusi CLI.
import socket
def assert_adb_alive(host: str = "127.0.0.1", port: int = 5037, timeout: float = 1.0) -> None:
"""Validasi ketersediaan daemon socket ADB sebelum spawn sub-proses."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try:
sock.connect((host, port))
# Kirim query versi ADB host protocol
sock.sendall(b"000Chost:version")
status = sock.recv(4)
if status != b"OKAY":
raise ConnectionError(f"ADB daemon merespons invalid status: {status!r}")
except (socket.timeout, ConnectionRefusedError) as exc:
raise ConnectionError(f"ADB daemon tidak merespons pada {host}:{port}") from exc
finally:
sock.close()Catatan untuk iOS Simulator: Pada environment macOS,
simctlbergantung pada sistem RPClaunchd. Untuk memverifikasi status simulator sebelum eksekusi, gunakan probing perintah sinkronus ringan via status listing:xcrun simctl list devices | grep "(Booted)"dengan timeout ketat via native subprocess wrapper.
Best Practices Mencegah Resource Leak pada Automation Worker
- Gunakan Process Isolation: Selalu assign
preexec_fn=os.setsidpada sistem UNIX untuk mengelompokkan sub-proses ke dalam satu PGID mandiri. - Tutup File Descriptor: Pastikan flag
close_fds=Trueaktif saat fork child process agar subprocess tidak mewarisi alokasi open socket milik host worker. - Terapkan Context Cancellation: Ikat lifecycle child process langsung dengan lifecycle request HTTP/RPC dari Agent framework utama (seperti gRPC Context atau
asyncio.TaskGroup). - Monitor FD Metrics: Pasang alert jika metrik sistem
process_open_fdspada worker pod/instance melampaui ambang batas 70% dari limit OS.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!