diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c4dfe9f..cd1922b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,6 +38,48 @@ jobs:
cargo test --all --offline
sudo iptables -F OUTPUT || true
+ # Every platform a release ships a binary for is tested here.
+ #
+ # `check` above stays on Linux and owns the things that do not vary by platform —
+ # formatting, clippy, and the network-egress gate, which needs iptables. This job
+ # owns what does vary: path handling, the temporary directory, line endings, and
+ # whether the CLI actually runs. Releases shipped Windows and macOS binaries that no
+ # job had ever executed; a portability break would have reached users first.
+ platform:
+ name: test (${{ matrix.os }})
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-latest, macos-latest]
+ steps:
+ # Before checkout, deliberately: Windows runners convert LF to CRLF on the way
+ # in, which would mean testing fixtures no user's repository actually contains
+ # and shifting every byte offset the index records. Configuring git afterwards
+ # would be too late.
+ - name: Check out with the line endings as committed
+ if: runner.os == 'Windows'
+ run: git config --global core.autocrlf false
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
+ - uses: Swatinem/rust-cache@v2
+
+ - name: Test
+ run: cargo test --all
+
+ # The unit tests can pass while the binary cannot start. This is the same
+ # end-to-end path `bench-smoke` runs on Linux, reduced to what proves the CLI
+ # works on this platform at all.
+ - name: The CLI runs end to end
+ # bash, not the Windows default: PowerShell only propagates the *last*
+ # command's exit code, so a failing `init` here would pass the step silently.
+ shell: bash
+ run: |
+ set -euo pipefail
+ cargo run --release -p reify-cli -- -C fixtures/minierp init
+ cargo run --release -p reify-cli -- -C fixtures/minierp index
+ cargo run --release -p reify-cli -- -C fixtures/minierp --json context "strategic account discount"
+
deny:
runs-on: ubuntu-latest
steps:
diff --git a/README.md b/README.md
index a7f1afd..7fa0459 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@
+
@@ -382,8 +383,15 @@ Three things that only break once you leave Latin script, each of which broke he
curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh
```
-Prebuilt binaries for macOS (Apple Silicon and Intel), Linux (x86_64 and aarch64)
-and Windows (x86_64).
+Prebuilt binaries for **macOS** (Apple Silicon and Intel), **Linux** (x86_64 and
+aarch64) and **Windows** (x86_64). On Windows the line above works as written in Git
+Bash, MSYS2 or WSL; from PowerShell, take the `x86_64-pc-windows-msvc` archive from
+[the latest release](https://github.com/lambiengcode/reify/releases/latest), verify its
+`.sha256`, and put `reify.exe` somewhere on your `PATH`.
+
+Every one of those platforms runs the full test suite in CI, and the CLI is exercised
+end to end on each — a binary is not published for a platform nothing has executed.
+
Or build from source:
```bash
diff --git a/README.vi.md b/README.vi.md
index 3f68ff7..f4a742f 100644
--- a/README.vi.md
+++ b/README.vi.md
@@ -20,6 +20,7 @@
+
@@ -403,7 +404,16 @@ Ba thứ chỉ vỡ khi bạn rời khỏi hệ chữ Latinh, và cả ba đều
curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh
```
-Có sẵn binary dựng trước cho macOS (Apple Silicon và Intel) và Linux (x86_64 và aarch64).
+Có sẵn binary dựng trước cho **macOS** (Apple Silicon và Intel), **Linux** (x86_64 và
+aarch64) và **Windows** (x86_64). Trên Windows, dòng lệnh trên chạy được nguyên vẹn
+trong Git Bash, MSYS2 hoặc WSL; từ PowerShell, hãy tải archive `x86_64-pc-windows-msvc`
+ở [bản phát hành mới nhất](https://github.com/lambiengcode/reify/releases/latest), kiểm
+tra `.sha256` của nó, rồi đặt `reify.exe` vào một thư mục nằm trong `PATH`.
+
+Mọi nền tảng trong số đó đều chạy toàn bộ test suite trên CI, và CLI được chạy thử
+đầu-cuối trên từng nền tảng — không phát hành binary cho một nền tảng mà chưa có gì
+từng chạy trên đó.
+
Hoặc build từ mã nguồn:
```bash
diff --git a/README.zh.md b/README.zh.md
index 14382a1..d4134f7 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -20,6 +20,7 @@
+
@@ -390,7 +391,14 @@ C/C++、Kotlin,外加 SQL。每一种都有一个测试断言它能产出容
curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh
```
-提供 macOS(Apple Silicon 与 Intel)和 Linux(x86_64 与 aarch64)的预编译二进制。
+提供 **macOS**(Apple Silicon 与 Intel)、**Linux**(x86_64 与 aarch64)和 **Windows**
+(x86_64)的预编译二进制。在 Windows 上,上面这行命令在 Git Bash、MSYS2 或 WSL 中可以
+原样运行;若使用 PowerShell,请从[最新发布](https://github.com/lambiengcode/reify/releases/latest)
+下载 `x86_64-pc-windows-msvc` 压缩包,校验其 `.sha256`,再把 `reify.exe` 放到 `PATH` 中。
+
+以上每个平台都会在 CI 上跑完整的测试套件,并逐一做端到端的 CLI 验证 —— 不会为一个从未
+被执行过的平台发布二进制。
+
也可以从源码构建:
```bash
diff --git a/crates/reify/src/lockfile.rs b/crates/reify/src/lockfile.rs
index d27d9c8..152e042 100644
--- a/crates/reify/src/lockfile.rs
+++ b/crates/reify/src/lockfile.rs
@@ -96,9 +96,11 @@ impl Drop for IndexLock {
/// Is a process with this id running?
///
-/// `kill(pid, 0)` is the portable POSIX existence check. On other platforms this
-/// returns `false`, which errs toward reclaiming a lock rather than deadlocking a
-/// repository — the safer failure for an advisory lock.
+/// The lock is only as good as this answer. A liveness check that always says "no"
+/// does not err on the safe side — it makes every lock look stale, so the lock stops
+/// excluding anything and two indexers write the same store. That is what the
+/// `not(unix)` stub used to do, and CI on Windows found it by failing to recognise
+/// its own process as alive.
#[cfg(unix)]
fn process_is_alive(pid: u32) -> bool {
// SAFETY: `kill` with signal 0 performs no action; it only reports whether the
@@ -112,9 +114,57 @@ extern "C" {
fn libc_kill(pid: i32, sig: i32) -> i32;
}
-#[cfg(not(unix))]
+/// Win32's answer to `kill(pid, 0)`.
+///
+/// Declared by hand rather than pulling in a Windows crate, for one question asked
+/// once — the same reason `kill` is declared above rather than taking a libc
+/// dependency.
+#[cfg(windows)]
+mod win32 {
+ pub type Handle = *mut core::ffi::c_void;
+ extern "system" {
+ pub fn OpenProcess(access: u32, inherit: i32, pid: u32) -> Handle;
+ pub fn WaitForSingleObject(handle: Handle, millis: u32) -> u32;
+ pub fn CloseHandle(handle: Handle) -> i32;
+ }
+}
+
+#[cfg(windows)]
+fn process_is_alive(pid: u32) -> bool {
+ /// The narrowest right to ask "does this exist"; granted across integrity levels
+ /// where `PROCESS_QUERY_INFORMATION` is not.
+ const QUERY_LIMITED_INFORMATION: u32 = 0x1000;
+ /// Required to wait on the handle at all. Omitting it does not make the wait
+ /// stricter — it makes it fail with `WAIT_FAILED`, which reads as "not running"
+ /// and silently restores the bug this function exists to fix.
+ const SYNCHRONIZE: u32 = 0x0010_0000;
+ /// The handle is not signalled, so the process has not exited.
+ const WAIT_TIMEOUT: u32 = 258;
+
+ // SAFETY: `OpenProcess` returns null rather than an invalid handle on failure, and
+ // the handle is closed on every path that obtained one.
+ unsafe {
+ let handle = win32::OpenProcess(SYNCHRONIZE | QUERY_LIMITED_INFORMATION, 0, pid);
+ if handle.is_null() {
+ // No such process, or one this user may not query. Either way, treating
+ // the lock as reclaimable is the behaviour a dead owner should get.
+ return false;
+ }
+ // Waiting zero milliseconds asks the question without blocking. Preferred over
+ // `GetExitCodeProcess`, which reports the sentinel 259 for a running process
+ // and cannot distinguish it from one that genuinely exited with 259.
+ let state = win32::WaitForSingleObject(handle, 0);
+ win32::CloseHandle(handle);
+ state == WAIT_TIMEOUT
+ }
+}
+
+/// Any other platform. Deliberately pessimistic: without a liveness check the lock
+/// cannot be trusted, so it refuses to reclaim rather than silently allowing two
+/// indexers to share a store.
+#[cfg(not(any(unix, windows)))]
fn process_is_alive(_pid: u32) -> bool {
- false
+ true
}
#[cfg(test)]
diff --git a/site/docs.html b/site/docs.html
index 3944d1e..6ff273b 100644
--- a/site/docs.html
+++ b/site/docs.html
@@ -43,7 +43,14 @@
Install
- Prebuilt binaries for macOS (Apple Silicon and Intel) and Linux (x86_64 and aarch64).
+
+ Prebuilt binaries for macOS (Apple Silicon and Intel), Linux (x86_64 and aarch64)
+ and Windows (x86_64). On Windows the command above works in Git Bash, MSYS2 or
+ WSL; from PowerShell, take the x86_64-pc-windows-msvc archive from
+ the latest release, verify its .sha256, and put
+ reify.exe on your PATH. Every platform runs the full
+ test suite in CI.
+
❯ curl -fsSL https://raw.githubusercontent.com/lambiengcode/reify/main/install.sh | sh