agalwood/Motrix · critical · AppError

EngineProcessTerminationFailed

EngineProcessTerminationFailed

Error message

aria2 process ${expectedPid} did not exit after force termination

What it means

Thrown as an AppError (code EngineProcessTerminationFailed) by Aria2ProcessManager.forceTerminateVerified after it sends a force-kill (SIGKILL via kill() or inspector.forceTerminate) and then polls inspector.isAlive(expectedPid) in a loop for up to 5 seconds (100ms intervals). If the process is still alive after the 5s deadline, this error fires. This indicates the OS could not terminate the process — typically a permissions issue or an uninterruptible state.

Source

Thrown at src/core/engine/aria2/aria2-process-manager.ts:272

        'The process changed or is not verified as Motrix-owned'
      )
    }

    if (
      processInfo.ownership === EngineProcessOwnership.CurrentApp &&
      this.getPid() === expectedPid
    ) {
      this.kill()
    } else {
      this.inspector.forceTerminate(expectedPid)
    }

    const deadline = Date.now() + 5_000
    while (this.inspector.isAlive(expectedPid) && Date.now() < deadline) {
      await new Promise<void>((resolve) => setTimeout(resolve, 100))
    }
    if (this.inspector.isAlive(expectedPid)) {
      throw new AppError(
        ErrorCode.EngineProcessTerminationFailed,
        `aria2 process ${expectedPid} did not exit after force termination`
      )
    }
    await this.clearOwnershipRecord(expectedPid)
  }

  private toProcessInfo(
    inspected: InspectedProcess,
    ownership: EngineProcessOwnership,
    safeToTerminate: boolean
  ): EngineProcessInfo {
    return {
      pid: inspected.pid,
      name: inspected.name,
      executableName: inspected.executablePath
        ? path.basename(inspected.executablePath)
        : null,

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Manually kill the process with elevated privileges: sudo kill -9 <expectedPid>
  2. Check the process state (ps -o pid,stat,cmd -p <expectedPid>) — if it's in D state, address the underlying I/O issue or reboot
  3. If the PID was reused, verify with inspectPort and update the expected PID
  4. Restart the application or the host to clear the stuck process
  5. In containers, ensure the init process (tini/dumb-init) forwards signals correctly
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await processManager.forceTerminateVerified(expectedPid, port, expected)
} catch (e) {
  if (e instanceof AppError && e.code === ErrorCode.EngineProcessTerminationFailed) {
    // Process won't die — escalate to OS-level kill or prompt user to restart
    log.error({ pid: expectedPid }, 'force termination failed, manual intervention required')
    await promptUserRestart()
  } else throw e
}

Prevention

When it happens

Trigger: forceTerminateVerified kills the verified process, then enters a wait loop (deadline = now + 5000ms, polling every 100ms); after the deadline, inspector.isAlive(expectedPid) still returns true.

Common situations: The process is running as a different user/root and the current process lacks kill permission; the process is in an uninterruptible sleep state (D state, e.g. stuck on a disk I/O or NFS); zombie process that hasn't been reaped by its parent; containerized environment where kill signals are restricted; the PID was reused by a new process.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/df3a030e966829c6. Report an issue: GitHub.