## Summary - The `eventide` dep was renamed to [kotatsu](https://github.com/clice-io/kotatsu) with a broad rename of CMake identifiers, namespaces, header paths, and a few module reorgs (`serde` → `codec`, `reflection` → `meta`, `common` → `support`). Align clice to the new names. - CMake: FetchContent target, option prefix (`ETD_*` → `KOTA_*`, `ETD_SERDE_*` → `KOTA_CODEC_*`), target names (`eventide::{ipc::lsp,serde::toml,deco,zest}` → `kota::{ipc::lsp,codec::toml,deco,zest}`). - Namespaces: `eventide::` → `kota::`, `eventide::serde::` → `kota::codec::`, `eventide::refl::` → `kota::meta::`. The short `et` alias is dropped — all usages now spell `kota::` directly. - Headers: `eventide/*` → `kota/*`, including special cases `serde/serde/raw_value.h` → `codec/raw_value.h`, `ipc/json_codec.h` → `ipc/codec/json.h`, `common/meta.h` → `support/type_traits.h`, `common/ranges.h` → `support/ranges.h`. - Kotatsu split `JsonPeer` / `BincodePeer` out of `ipc/peer.h` into the codec-specific headers; added `kota/ipc/codec/{json,bincode}.h` includes where those types are used. - Depends on clice-io/kotatsu#110 (already merged) to prevent `-Wall -Wextra -Werror` from transitively propagating out of `kota::project_options`. ## Test plan - [x] `pixi run unit-test RelWithDebInfo` — 518/518 pass (9 skipped, unchanged from main) - [x] `pixi run integration-test RelWithDebInfo` — 119/119 pass - [x] `pixi run smoke-test RelWithDebInfo` — 2/2 pass - [x] `pixi run format` clean ## Notes - `tests/smoke/rapid_edit.jsonl` was intentionally left untouched: the embedded `#include "eventide/..."` strings are frozen snapshots of file contents the client sent at record time, not clice source. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated internal dependencies from `eventide` to `kota`, including async runtime, IPC transport, serialization codec, and metadata libraries. * Updated build configuration and CMake variables to align with the new dependency. * **Refactor** * Migrated internal implementation to use `kota` namespace and APIs throughout the codebase. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
184 lines
10 KiB
Markdown
184 lines
10 KiB
Markdown
# Server Architecture
|
|
|
|
clice uses a **multi-process architecture** where a single **Master Server** coordinates multiple **Worker** processes. This design isolates Clang AST operations (which are memory-heavy and may crash) from the main LSP event loop.
|
|
|
|
## Overview
|
|
|
|
```
|
|
┌──────────────┐ JSON/LSP ┌────────────────┐ Bincode/IPC ┌──────────────────┐
|
|
│ LSP Client │ ◄──────────► │ Master Server │ ◄─────────────► │ Stateful Workers │
|
|
│ (Editor) │ (stdio) │ │ (stdio) │ (AST cache) │
|
|
└──────────────┘ │ - Lifecycle │ └──────────────────┘
|
|
│ - Documents │
|
|
│ - CDB │ Bincode/IPC ┌──────────────────┐
|
|
│ - Build drain │ ◄─────────────► │ Stateless Workers│
|
|
│ - Indexing │ (stdio) │ (one-shot tasks)│
|
|
└────────────────┘ └──────────────────┘
|
|
```
|
|
|
|
## Master Server
|
|
|
|
The master server (`src/server/master_server.cpp`) is the central coordinator. It runs a single-threaded async event loop and never touches Clang directly. Its responsibilities:
|
|
|
|
### LSP Lifecycle
|
|
|
|
The server progresses through these states:
|
|
|
|
1. **Uninitialized** — waiting for `initialize` request
|
|
2. **Initialized** — capabilities exchanged, waiting for `initialized` notification
|
|
3. **Ready** — workers spawned, workspace loaded, accepting requests
|
|
4. **ShuttingDown** — `shutdown` received, draining work
|
|
5. **Exited** — `exit` received, stopping the event loop
|
|
|
|
On `initialized`, the master:
|
|
|
|
- Loads configuration from `clice.toml` (or uses defaults)
|
|
- Starts the worker pool (spawns stateful + stateless processes)
|
|
- Loads `compile_commands.json` and builds an include graph
|
|
- Starts the background indexer coroutine (if enabled)
|
|
|
|
### Document Management
|
|
|
|
Each open document is tracked in a `DocumentState` with:
|
|
|
|
- Current `version` and `text` (kept in sync via `didOpen`/`didChange`)
|
|
- A `generation` counter to detect stale compile results
|
|
- Build state flags (`build_running`, `build_requested`, `drain_scheduled`)
|
|
|
|
When a document is opened or changed:
|
|
|
|
1. The include graph is re-scanned (via dependency directives)
|
|
2. The compile unit is registered/updated in the `CompileGraph`
|
|
3. A debounced build is scheduled
|
|
|
|
### Build Drain
|
|
|
|
The `run_build_drain` coroutine implements debounced compilation:
|
|
|
|
1. Wait for the debounce timer (default 200ms) to expire
|
|
2. Ensure PCH/PCM dependencies are ready via `CompileGraph`
|
|
3. Send a `compile` request to the assigned stateful worker
|
|
4. Publish diagnostics from the result (or clear them on failure)
|
|
5. If more edits arrived during compilation (`build_requested`), loop back to step 2
|
|
|
|
This ensures rapid typing doesn't trigger a compile per keystroke.
|
|
|
|
### Request Routing
|
|
|
|
Feature requests are split between two worker types:
|
|
|
|
**Stateful workers** (affinity-routed by file path):
|
|
|
|
- `textDocument/hover`
|
|
- `textDocument/semanticTokens/full`
|
|
- `textDocument/inlayHint`
|
|
- `textDocument/foldingRange`
|
|
- `textDocument/documentSymbol`
|
|
- `textDocument/documentLink`
|
|
- `textDocument/codeAction`
|
|
- `textDocument/definition`
|
|
|
|
**Stateless workers** (round-robin):
|
|
|
|
- `textDocument/completion`
|
|
- `textDocument/signatureHelp`
|
|
|
|
All feature responses use `RawValue` passthrough — the worker serializes the LSP result to JSON, and the master forwards the raw JSON bytes to the client without deserializing. This avoids bincode↔JSON conversion overhead and serde annotation conflicts.
|
|
|
|
## Worker Pool
|
|
|
|
The worker pool (`src/server/worker_pool.cpp`) manages spawning and communicating with worker processes. Each worker is a child process of the same `clice` binary, launched with `--mode stateful-worker` or `--mode stateless-worker`.
|
|
|
|
### Communication
|
|
|
|
Workers communicate with the master via **stdio pipes** using a **bincode** serialization format (via `kota::ipc::BincodePeer`). This is more compact and faster than JSON for internal IPC, while the master handles JSON for the external LSP protocol.
|
|
|
|
### Stateful Worker Routing
|
|
|
|
Stateful workers use **affinity routing**: each file is consistently assigned to the same worker so that the worker retains the cached AST. Assignment uses a **least-loaded** strategy for new files, with **LRU tracking** to manage ownership.
|
|
|
|
When a worker exceeds its document capacity (currently hardcoded at 16 documents), it evicts the least-recently-used document and notifies the master via an `evicted` notification.
|
|
|
|
### Stateless Worker Routing
|
|
|
|
Stateless workers use simple **round-robin** dispatch. Each request includes the full source text and compilation arguments, so any worker can handle it independently.
|
|
|
|
## Stateful Worker
|
|
|
|
The stateful worker (`src/server/stateful_worker.cpp`) caches compiled ASTs in memory. Key behavior:
|
|
|
|
- **Compile**: Parses source code into a `CompilationUnit`, caches the AST, and returns diagnostics as a `RawValue` (JSON bytes)
|
|
- **Feature queries**: Look up the cached AST and invoke the corresponding `feature::*` function (hover, semantic tokens, etc.), serializing the result to JSON
|
|
- **Document updates**: Received as notifications — the worker updates the stored text and marks the document as `dirty`, causing feature queries to return `null` until recompilation
|
|
- **Eviction**: LRU-based; evicts the oldest document when capacity is exceeded, notifying the master
|
|
- **Concurrency**: Each document has a per-document `kota::mutex` (strand) to serialize compilation and feature queries. Heavy work (compilation, feature extraction) runs on a thread pool via `kota::queue`.
|
|
|
|
## Stateless Worker
|
|
|
|
The stateless worker (`src/server/stateless_worker.cpp`) handles one-shot requests that don't benefit from cached ASTs:
|
|
|
|
- **Completion**: Creates a fresh compilation with `CompilationKind::Completion` and invokes `feature::code_complete`
|
|
- **Signature help**: Similar to completion, using `feature::signature_help`
|
|
- **Build PCH**: Compiles a precompiled header to a temporary file
|
|
- **Build PCM**: Compiles a C++20 module interface to a temporary file
|
|
- **Index**: Compiles a file for indexing (TUIndex generation — currently a stub)
|
|
|
|
All requests are dispatched to a thread pool via `kota::queue`.
|
|
|
|
## Compile Graph
|
|
|
|
The compile graph (`src/server/compile_graph.cpp`) tracks compilation unit dependencies as a DAG. It handles:
|
|
|
|
- **Registration**: Each file registers its included dependencies
|
|
- **Cascade invalidation**: When a file changes, all transitive dependents are marked dirty and their ongoing compilations are cancelled
|
|
- **Dependency compilation**: Before compiling a file, `compile_deps` ensures all dependencies (PCH, PCMs) are built first
|
|
- **Cancellation**: Uses `kota::cancellation_source` to abort in-flight compilations when files are invalidated
|
|
|
|
## Configuration
|
|
|
|
The server reads configuration from `clice.toml` (or `.clice/config.toml`) in the workspace root. If no config file exists, sensible defaults are computed from system resources:
|
|
|
|
| Setting | Default | Description |
|
|
| ------------------------ | --------------------- | ------------------------------------------- |
|
|
| `stateful_worker_count` | CPU cores / 4 | Number of stateful worker processes |
|
|
| `stateless_worker_count` | CPU cores / 4 | Number of stateless worker processes |
|
|
| `worker_memory_limit` | 4 GB | Memory limit per stateful worker |
|
|
| `compile_commands_path` | auto-detect | Path to `compile_commands.json` |
|
|
| `cache_dir` | `<workspace>/.clice/` | Cache directory for PCH/PCM files |
|
|
| `debounce_ms` | 200 | Debounce interval for recompilation |
|
|
| `enable_indexing` | true | Enable background indexing |
|
|
| `idle_timeout_ms` | 3000 | Idle time before background indexing starts |
|
|
|
|
String values support `${workspace}` substitution.
|
|
|
|
## IPC Protocol
|
|
|
|
The master and workers communicate using custom RPC messages defined in `src/server/protocol.h`. Each message type has a `RequestTraits` or `NotificationTraits` specialization that defines the method name and result type.
|
|
|
|
### Stateful Worker Messages
|
|
|
|
| Method | Direction | Purpose |
|
|
| ----------------------------- | ------------ | ------------------------------------- |
|
|
| `clice/worker/compile` | Request | Compile source and return diagnostics |
|
|
| `clice/worker/hover` | Request | Get hover info at position |
|
|
| `clice/worker/semanticTokens` | Request | Get semantic tokens for file |
|
|
| `clice/worker/inlayHints` | Request | Get inlay hints for range |
|
|
| `clice/worker/foldingRange` | Request | Get folding ranges |
|
|
| `clice/worker/documentSymbol` | Request | Get document symbols |
|
|
| `clice/worker/documentLink` | Request | Get document links |
|
|
| `clice/worker/codeAction` | Request | Get code actions for range |
|
|
| `clice/worker/goToDefinition` | Request | Go to definition at position |
|
|
| `clice/worker/documentUpdate` | Notification | Update document text (marks dirty) |
|
|
| `clice/worker/evict` | Notification | Master → Worker: evict a document |
|
|
| `clice/worker/evicted` | Notification | Worker → Master: document was evicted |
|
|
|
|
### Stateless Worker Messages
|
|
|
|
| Method | Direction | Purpose |
|
|
| ---------------------------- | --------- | ---------------------------- |
|
|
| `clice/worker/completion` | Request | Code completion at position |
|
|
| `clice/worker/signatureHelp` | Request | Signature help at position |
|
|
| `clice/worker/buildPCH` | Request | Build precompiled header |
|
|
| `clice/worker/buildPCM` | Request | Build C++20 module interface |
|
|
| `clice/worker/index` | Request | Index a translation unit |
|