← 返回首頁
Linghua Jin 🥥 🌴
Linghua Jin 🥥 🌴
@LinghuaJ
26🔁 4
𝕏 (Twitter)🔥🔥
AI 中文摘要Claude 生成

背景與挑戰

許多開發工具需要一個長時間執行的本地進程——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 全數消失。