# Socket Daemon

`core/daemon` 讓所有開啟同一資料目錄的 process 共用一個 unix socket；本頁說明 `daemon.New` 如何決定該 process 成為 server 或 client。

## 滿足的需求

| # | 需求 | 做法 |
|---|---|---|
| 1 | 每個 ToriiDB 位置一個共用 socket | 第一個 process 開啟唯一的 `Store`，其他 process 透過 socket 與它溝通 |
| 2 | 不同位置使用不同 socket | Socket 名稱是解析後絕對目錄的 hash |
| 3 | 同位置重複 `New` 共用 socket | `New` 先撥號；socket 存活即為 client 模式 |
| 4 | 呼叫端不必判斷角色 | `Get`/`Set`/`Del`/... 在兩種模式行為一致 |

傳輸是 unix socket，搭配自訂的長度前綴 JSON frame，不使用 HTTP。

## 模式判定

`daemon.New(dir, apiKey...)` 依序執行：

1. 拒絕空的 `dir`。
2. `resolveDir`：`CheckDir(dir, true)` → `filepath.Abs` → `filepath.EvalSymlinks`。
3. `resolveSocket`：`/tmp/toriidb-<uid>-<hex(sha256(realDir)[:8])>.sock`。
4. `probeSocket`：`net.DialTimeout("unix", path, 500ms)`。

| 探測結果 | 模式 | `New` 的動作 |
|---|---|---|
| 撥號成功 | `ModeClient` | 建立連線池 client，不開啟 store |
| `ECONNREFUSED` 或 `ENOENT` | `ModeServer` | 開啟 `store.New(realDir, apiKey...)` |
| 其他撥號錯誤 | 錯誤 | 回傳 `probe socket <path>: ...` |

| 設計規則 | 原因 |
|---|---|
| Socket 目錄固定為 `/tmp` | `os.TempDir()` 讀 `$TMPDIR`，socket 會變成取決於環境而非位置 |
| 路徑必須經過 `EvalSymlinks` | macOS 的 `/tmp` 是 `/private/tmp` 的 symlink；`relx`、`./relx`、`/tmp/relx/`、`/private/tmp/relx` 與 symlink 必須收斂 |
| 存活只以撥號判定，不用 `os.Stat` | `kill -9` 會留下 socket 檔；殘留檔案不能阻止新 server 啟動 |
| 檔名含 `uid` | `/tmp` 由多個使用者共用 |
| `New` 吃 `dir` 而非 `*store.Store` | 探測必須在開啟 store 之前；client 不可對同一目錄再開一個 store |

## 相關頁面

- [生命週期](/zh/socket-daemon-lifecycle)
- [資料方法](/zh/socket-daemon-methods)
- [傳輸協定](/zh/socket-daemon-protocol)
