Worker daemon yang bertugas mengemulasikan input sistem operasi (seperti mouse movement, klik, atau key stroke otomatis ala tool automation Neverclick) memiliki risiko crash yang unik. Jika worker mengalami event loop locking atau thread blocking saat memproses native hook OS, seluruh pipeline input sistem dapat mengalami freeze. Artikel ini membahas langkah konkret melakukan incident handling, monitoring metrik event loop, serta implementasi auto-rollback berbasis script watchdog mandiri.

Anatomi Masalah: Input Freeze dan Event Loop Locking

Pada daemon emulasi input, antrean tugas (task queue) diterjemahkan ke dalam native OS events (misalnya melalui Win32 SendInput / low-level hooks di Windows atau X11/uinput di Linux). Saat deployment versi baru membawa regresi—misalnya pemanggilan sinkron yang memblokir I/O di dalam thread pemrosesan pesan input—event loop berhenti melakukan polling. Akibatnya:

  • Native hook OS tidak segera mengembalikan kendali via callback, memicu latensi global pada cursor.
  • Internal input queue worker membengkak tanpa kendali hingga menghabiskan alokasi memori.
  • Daemon berhenti merespons sinyal terminasi standar (SIGTERM), menahan status crash dalam kondisi zombie loop.

Metrik Kritis Observasi Event Loop

Untuk mendeteksi incident handling worker emulasi input sedini mungkin, pantau tiga metrik utama melalui instrumentation agent atau logging pipeline lokal:

1. Event Loop Lag / Dispatch Latency

Waktu yang dibutuhkan sebuah dummy task di dalam event queue untuk dijadwalkan dan dieksekusi. Jika baseline latensi normal berkisar antara 1–5 ms, kenaikan menuju >250 ms menandakan thread terblokir oleh heavy task atau sinkronisasi I/O.

2. OS Input Hook Latency

Durasi proses callback saat sistem menerima input event sebelum dikembalikan ke subsistem window manager. Keterlambatan melebihi ambang batas threshold OS (misalnya batas LowLevelHooksTimeout pada Windows) menyebabkan OS mencabut hook secara paksa.

3. CPU Spike Terkonsentrasi pada Single Core

Event loop yang terjebak dalam tight polling loop tanpa penundaan (misal: loop while(true) tanpa blocking wait atau sleep cooperatif) menghasilkan utilisasi 100% pada core worker tertentu tanpa memproses tugas di queue.

Implementasi Health Check Daemon

Health check internal harus mengevaluasi ketersediaan event loop secara nyata, bukan sekadar respons status HTTP statis. Implementasi Python minimal berikut menjalankan pemantauan round-trip event loop sederhana:

import asyncio
import time

class LoopMonitor:
    def __init__(self, threshold_seconds=0.5):
        self.threshold = threshold_seconds
        self.last_tick = time.monotonic()
        self.is_healthy = True

    async def heartbeat(self):
        while True:
            now = time.monotonic()
            lag = now - self.last_tick - 0.1
            if lag > self.threshold:
                self.is_healthy = False
            else:
                self.is_healthy = True
            self.last_tick = now
            await asyncio.sleep(0.1)

    def write_healthfile(self, filepath="/tmp/worker_health"):
        with open(filepath, "w") as f:
            f.write("OK" if self.is_healthy else "STALL")

Watchdog dan Auto-Rollback Script

Saat daemon membeku, proses tersebut tidak dapat memulihkan dirinya sendiri. Diperlukan watchdog eksternal yang memantau heartbeat file atau interaksi input sintetik. Jika watchdog mendeteksi freeze berkelanjutan selama kurun waktu tertentu sesudah release baru, proses dimatikan paksa (SIGKILL) dan versi sebelumnya dipulihkan.

#!/usr/bin/env bash
set -euo pipefail

HEALTH_FILE="/tmp/worker_health"
MAX_FAILURES=3
FAIL_COUNT=0
SERVICE_NAME="input-worker"
PREV_VERSION_TAG="v1.4.2"

while true; do
    if [ ! -f "$HEALTH_FILE" ] || [ "$(cat "$HEALTH_FILE")" != "OK" ]; then
        FAIL_COUNT=$((FAIL_COUNT + 1))
        echo "[WARNING] Health check failed: count $FAIL_COUNT/$MAX_FAILURES"
    else
        FAIL_COUNT=0
    fi

    if [ "$FAIL_COUNT" -ge "$MAX_FAILURES" ]; then
        echo "[CRITICAL] Event loop freeze terdeteksi. Memulai auto-rollback..."
        systemctl stop "$SERVICE_NAME" || pkill -9 -f "$SERVICE_NAME"
        
        # Rollback symlink direktori rilis
        ln -sfn "/opt/input-worker/releases/$PREV_VERSION_TAG" /opt/input-worker/current
        
        systemctl start "$SERVICE_NAME"
        echo "[RECOVERY] Rollback ke $PREV_VERSION_TAG selesai."
        exit 1
    fi

    sleep 2
done

Postmortem Ringan & Pencegahan Regresi Event Queue

Setelah status sistem dipulihkan lewat rollback otomatis, lakukan investigasi untuk menemukan akar masalah sebelum merilis patch baru:

  1. Isolasi Thread Emulasi: Jangan pernah memproses logika kalkulasi koordinat berat, akses filesystem, atau pemanggilan network HTTP di dalam thread yang sama dengan event queue dispatcher OS. Pisahkan tugas komputasi ke worker thread terpisah via IPC atau threading pool.
  2. Beri Batasan Queue (Bounded Queue): Terapkan backpressure. Jika kapasitas queue input mencapai ambang batas (misal: 1000 event pending), tolak input baru daripada membiarkan memory exhaustion memperburuk latensi loop.
  3. Automated Canary Testing: Jalankan uji regresi di environment headless/virtual display sebelum promosi ke production. Ukur latensi loop dummy selama runtime uji beban input berlangsung.