# 策展 · X (Twitter) 🔥

> 作者：Linghua Jin 🥥 🌴 (@LinghuaJ) · 平台：X (Twitter) · 日期：2026-03-25

> 原始來源：https://x.com/linghuaj/status/2036474800236057041

## 中文摘要

許多開發工具需要一個長時間執行的本地進程——LSP 伺服器、檔案監控程式、索引服務。「不可見」守護程序（daemon）的真正難題不在於構建它，而在於讓它自動啟動、升級時自動重啟、設定更改時自動重新載入、正常關閉——整個過程對使用者透明。CocoIndex 在建立 cocoindex-code（一個基於 AST 的語意程式搜尋工具，用於加速和改進程式編寫 Agent 的品質）的過程中學到了這一點。該工具最初是一個單體進程，每個 session 都要載入 ML 模型、索引程式庫並提供搜尋服務。團隊將重型任務遷移到持久的守護程序，同時提供 CLI（ccc）和輕量級 MCP 客戶端作為前端。

**使用者旅程設計**

- ccc init：純本地操作，建立設定檔和 .gitignore 項目，無需啟動守護程序
- ccc index：建置搜尋索引，守護程序在此自動啟動
- ccc search "auth logic"：語意搜尋，守護程序處理請求
- ccc search --refresh "auth"：重新索引然後搜尋（兩次守護程序請求）

使用者永遠不需手動啟動、停止或重啟守護程序——它只是運作。

**自動啟動與版本握手**

首次執行 ccc index 或 ccc search 時，客戶端嘗試連接到守護程序的 Unix socket。若失敗，則啟動守護程序作為獨立背景進程並等待 socket 出現。這個邏輯內建在最低層的連接函數中，而非獨立的「確保守護程序」步驟。每次連接都從版本握手開始——客戶端發送其版本，守護程序回應其版本。版本不匹配時會觸發重啟（首次呼叫）或報錯（後續呼叫），確保使用者升級套件後無需手動重啟。

**雙層設定與狀態管理策略**

- **全域設定**（~/.cocoindex_code/global_settings.yml）：嵌入式模型、API 金鑰、環境變數。這些影響守護程序範圍的狀態，守護程序必須重啟。客戶端比較檔案的 mtime（以微秒整數記錄），偵測變化。
- **專案設定**（$PROJECT/.cocoindex_code/settings.yml）：包含/排除模式、語言覆蓋。這些僅影響個別操作行為。每次索引都從磁碟讀取新的設定檔，無快取失效或重啟。

**核心設計簡化：per-request 連接**

初期設計採用持久連接——客戶端連接一次，守護程序透過循環讀取多個請求。但這導致三個棧疊的 bug：

- 連接洩漏：CLI 命令未總是關閉連接，導致處理器任務永遠等待
- 守護程序訊號無效：asyncio.Event 在執行器執行緒中調用（非執行緒安全），shutdown 信號永遠被看不見
- 殭屍進程檢測失敗：os.kill(pid, 0) 對殭屍進程返回 True，客戶端持續等待已退出的進程

團隊捨棄持久連接，改採 per-request 模式：每個請求開啟新連接 → 握手 → 單個請求 → 回應 → 關閉。處理器變成直線程式：讀取兩則訊息、回應、關閉，就這樣。複合操作（如搜尋並刷新）只需開啟多個連接——沒有共享狀態。Unix socket 設定開銷約 0.1ms，握手是微小的 msgspec 承載，CLI 命令都是人工啟動的，開銷可忽略。

**關閉：圍繞資源關閉設計，不是執行緒信號**

Per-request 連接解決了最難的關閉問題（無需信號空閒任務）。接受循環在背景執行緒執行，阻塞在 listener.accept()。停止它的方法不是設定旗標，而是關閉 listener——accept() 拋出 OSError，執行緒立即退出。整個關閉序列優先順序是：停止接受 → 取消處理器 → 釋放資源 → 移除 socket 和 PID 檔案 → os._exit(0)（跳過緩慢的 Python 清理）。PID 檔案作為退出訊號，客戶端輪詢其缺失來確認守護程序完成清理。

**效果對比**

| 面向 | 之前 | 之後 |
|---|---|---|
| 連接處理器 | ~70 行，recv 循環、輪詢、shutdown 事件 | ~40 行，直線讀-回應-關閉 |
| 客戶端 | DaemonClient 類、__enter__/__exit__、.close() | 模組層級函數，無狀態 |
| 關閉概念 | threading.Event、執行器輪詢、5 步升級 | listener.close()、loop.stop()、os._exit(0) |
| 守護程序停止時間 | ~15 秒（最後落回 SIGKILL） | <1 秒 |

**建議模式**

讓守護程序不可見：自動首次啟動、版本不匹配時自動重啟、可能時自動重新載入設定。偏好無狀態 IPC：per-request 連接防止洩漏、握手捕捉過時狀態更快。區分狀態類別：什麼必須在守護程序中（已載入 ML 模型、開啟的資料庫）vs 什麼可以重新讀取（設定檔、檔案清單）。只為前者重啟。圍繞資源關閉設計關閉，不是執行緒信號。當偵錯變複雜時，質疑設計——團隊用精心的執行緒、事件類型、進程活躍檢查修復了三個棧疊 bug，後來移除持久連接，三個 bug 全數消失。

## 標籤

MCP, CLI, Agent, 開源專案, CocoIndex
