Commit Graph

2 Commits

Author SHA1 Message Date
ykiko
31d9c609b6 fix: data race in stateful worker between Compile and DocumentUpdate (#389)
## Summary

Fix two data races in the stateful worker that caused spurious
"redefinition" errors during rapid edits, and remove a didChange
workaround that is no longer needed after clice-io/eventide#95.

### stateful_worker.cpp

**Compile handler**: move `params` → `doc` field copy **after**
`strand.lock()`. Previously the copy happened before the lock, so a
concurrent Compile request waiting on the strand could overwrite
`doc.text` while `et::queue` was reading it on the thread pool:

```
T1: Compile A → doc.text = text_A → lock → et::queue reads doc.text
T2: Compile B → doc.text = text_B → waits for strand (overwrites!)
T3: et::queue sees text_B instead of text_A → PCH/text mismatch
```

**DocumentUpdate handler**: only mark `dirty`, stop modifying
`doc.text`/`doc.version`. The event loop notification can fire while
`et::queue` work is running on the thread pool — writing `doc.text` from
one thread while reading it from another is a data race.

### master_server.cpp

Remove the `{0,0}-{0,0}` range workaround for whole-document
`didChange`. eventide's variant deserialization now correctly rejects
`TextDocumentContentChangePartial` when the `range` field is absent
(clice-io/eventide#95), so `TextDocumentContentChangeWholeDocument` is
matched as intended.

### protocol.h

Remove `text` field from `DocumentUpdateParams` — the worker no longer
needs it since DocumentUpdate only sets the dirty flag.

### Integration tests (+312 lines)

Extend test_staleness.py from 5 to 14 tests covering document lifecycle:
- `didChange` body edit → recompilation with updated diagnostics
- `didChange` preamble edit → PCH rebuild + clean recompilation
- `didClose` + reopen → compiles fresh from disk
- `didClose` → hover returns None
- `didSave` header → dependent file recompiles
- `didSave` module → CompileGraph dependents invalidated

## Test plan

- [x] 422 unit tests pass (426 on CI with extra test suites)
- [x] 14 integration tests pass locally
- [x] Depends on clice-io/eventide#95 (merged)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Smaller document-update notifications sent to background workers (only
path and version).

* **Bug Fixes**
  * Reduced races and unnecessary work between update and compile flows.
* Prevented notifications from overwriting in-memory document text,
improving state consistency.
* Safer concurrent handling to avoid mid-request eviction of active
documents.

* **Tests**
* Added integration tests for staleness, dependency propagation, and LSP
lifecycle.
  * Updated unit tests to match revised update behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:20:13 +08:00
ykiko
94bc872cdb feat: add LSP trace recording and smoke test replay (#383)
## Summary

Add LSP trace recording and replay-based smoke testing infrastructure.

### clice changes (`src/clice.cc`)
- Add `--log-level` CLI option with validation (rejects unknown levels
instead of silently defaulting to off)
- Add `--record <path>` CLI option that wraps the transport with
`RecordingTransport` to capture client→server messages as timestamped
JSONL
- Works in both pipe and socket modes
- Fix exit code: `loop.run()` returns non-zero after `uv_stop()`,
explicitly return 0 after clean shutdown

### Compile logging (`src/compile/compilation.cpp`)
- Print compile commands at debug log level

### Replay script (`tests/replay.py`)
- Timestamp-based pacing: sleeps between messages based on recorded
intervals, faithful to original editor session
- Automatic workspace path rewriting: infers repo root from script
location, rewrites absolute paths in trace so CI replay works without
extra arguments
- Handles server→client requests (workDoneProgress/create,
registerCapability, etc.) with default responses
- Waits for all pending responses before sending shutdown/exit
- Detects server exit mid-replay and fails pending futures immediately
instead of hanging
- Reports PASS/FAIL/SKIP with stderr tail on failure

### CI & config
- Add `smoke-test` pixi task and CI workflow step (runs after
integration tests)
- `.gitattributes`: mark `tests/smoke/*.jsonl` as `linguist-generated
binary` to suppress diffs
- Add sample trace file `tests/smoke/session.jsonl`

### VSCode extension
- Add restart command (`clice.restart`)
- Support `CLICE_MODE` env var to override mode setting (for debug
launch configs)
- Split launch configs into socket/pipe variants with
`--disable-extensions`

## Test plan
- [x] `python tests/replay.py tests/smoke/session.jsonl --clice
./build/RelWithDebInfo/bin/clice` passes locally
- [ ] CI smoke test passes on Linux/macOS/Windows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:21:28 +08:00