From 54958f4e831e5bee2211fd91180cb96cfbae6641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Andr=C3=A9?= Date: Sun, 18 Oct 2026 02:07:17 +0200 Subject: [PATCH] fix(tauri): prevent Windows event-loop refcount corruption (#996) ## Summary Fix repeated Windows heap-corruption crashes in the installed Tauri build. The pinned runtime exposes clonable, thread-safe handles whose unsafe context graph contains Tao's non-atomic `Rc`. IPC-worker clone/drop traffic races the event loop's reference count and can free the runner prematurely. - Use `Arc` for that internal Windows runner counter, retaining main-thread confinement of runner state and the target's existing public threading contract. - Add an isolated regression using the real pinned Wry runtime's public handle API, with a negative control that reverses only this counter in a temporary crate copy. - Add the Windows regression to CI and document the captured failures, compatibility rationale and removal criteria. This is a local compatibility correction for the runtime CodeNomad ships. [tao#1290](https://github.com/tauri-apps/tao/pull/1290) proposed the same counter change but was closed **without merging**, because the underlying contract violation is Tauri moving/cloning the non-Send target. That distinction is recorded explicitly. Related reports: [tauri#15408](https://github.com/tauri-apps/tauri/issues/15408) and [tauri#15793](https://github.com/tauri-apps/tauri/issues/15793). ## Validation - `node scripts/test-tauri-runner-refcount.mjs --baseline`: **9 native crashes / 10 negative-control processes; 5 / 5 corrected passes**, 800,000 handle clones plus teardown per process. - `node scripts/test-tauri-input-deadlock.mjs --baseline`: keydown, keyup, char, syschar, IME and instrumented IME-boundary checks pass; the original/reverted negative controls reproduce their expected deadlocks. - `cargo build --locked --release -p codenomad-tauri`: passes. - `rustfmt --check` on the new fixture and `node --check` on its runner: pass. - Corrected release executable installed locally on the affected Windows machine, retaining the existing resources and updater verification public key; original executable and matching new symbols archived locally. The runtime reproduction needs no JavaScript or web page. The Virtua 0.53.5 upgrade is absent from the crashing `3394fc7f` build. Sustained application use remains additional validation beyond the bounded native regression. --- .github/workflows/pr-build.yml | 3 + AGENTS.md | 2 + dev-docs/TAURI_WINDOWS_RUNNER_REFCOUNT.md | 85 +++++++++++++++++++ packages/tauri-app/Cargo.lock | 9 ++ packages/tauri-app/Cargo.toml | 2 +- .../tests/windows-runner-refcount/Cargo.toml | 10 +++ .../tests/windows-runner-refcount/build.rs | 14 +++ .../tests/windows-runner-refcount/src/main.rs | 52 ++++++++++++ packages/tauri-app/vendor/README.md | 43 ++++++++-- .../src/platform_impl/windows/event_loop.rs | 3 +- .../windows/event_loop/runner.rs | 7 +- scripts/test-tauri-runner-refcount.mjs | 85 +++++++++++++++++++ 12 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 dev-docs/TAURI_WINDOWS_RUNNER_REFCOUNT.md create mode 100644 packages/tauri-app/tests/windows-runner-refcount/Cargo.toml create mode 100644 packages/tauri-app/tests/windows-runner-refcount/build.rs create mode 100644 packages/tauri-app/tests/windows-runner-refcount/src/main.rs create mode 100644 scripts/test-tauri-runner-refcount.mjs diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index c7392a1f4..14520e9ec 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -525,6 +525,9 @@ jobs: - name: Reproduce and prevent Tao input deadlocks run: node scripts/test-tauri-input-deadlock.mjs --baseline + - name: Reproduce and prevent Windows runtime handle corruption + run: node scripts/test-tauri-runner-refcount.mjs --baseline + # Exercise the selected-CLI contract on the reported macOS ARM64 platform as # well as both other hosts, independently of unrelated server-suite failures. tests-opencode-compatibility: diff --git a/AGENTS.md b/AGENTS.md index 2e13f368d..accf7ecb3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -133,6 +133,8 @@ - Tauri's Tao Windows input backport lives in `packages/tauri-app/vendor/`; preserve upstream provenance and avoid message pumping under input mutexes. Run `node scripts/test-tauri-input-deadlock.mjs --baseline` on Windows when changing it. See `packages/tauri-app/vendor/README.md` for provenance and override removal criteria. +- The pinned Tauri runtime clones native handles on worker threads, including Tao's Windows event-loop target. Its internal runner counter must remain atomic; this does not authorize off-main-thread access to the runner state or make Tao's target Send/Sync. Run `node scripts/test-tauri-runner-refcount.mjs --baseline` after changes to this compatibility fix. The isolated regression uses the actual Wry runtime without an application profile or OpenCode; see `dev-docs/TAURI_WINDOWS_RUNNER_REFCOUNT.md` and the vendor provenance (the referenced upstream proposal was not merged). + - Tao's macOS zoom getter must remain read-only: synchronous temporary style-mask changes on borderless windows generate geometry-event feedback and can starve startup navigation. Keep the exact upstream tao#1182 backport beside the Windows fix, with its provenance in `packages/tauri-app/vendor/README.md`. Run `node scripts/test-tauri-macos-window.mjs --baseline` in a graphical macOS session and retain the ARM64 CI regression. Do not disable window persistence or change the application chrome as a workaround. - Bundled runtime maintenance keeps `.node-version` exact and proposes only same-major LTS upgrades as regular, non-draft PRs without automatic merge. The scheduled npm CLI/dependency audit is warning-only: never independently replace bundled npm, auto-fix dependencies, merge or publish from that check. Audit failure is not a clean assessment; application runtime changes require reviewed builds/releases. See `dev-docs/RUNTIME_MAINTENANCE.md`. diff --git a/dev-docs/TAURI_WINDOWS_RUNNER_REFCOUNT.md b/dev-docs/TAURI_WINDOWS_RUNNER_REFCOUNT.md new file mode 100644 index 000000000..69e0a4088 --- /dev/null +++ b/dev-docs/TAURI_WINDOWS_RUNNER_REFCOUNT.md @@ -0,0 +1,85 @@ +# Windows Tauri runtime handle corruption + +## Captured failure + +The installed Windows build `0.20.1-dev-20261017-3394fc7f` crashed repeatedly +on 2026-10-18 at 00:19:50, 00:36:49 and 00:52:27 local time. Windows reported +`STATUS_HEAP_CORRUPTION` (`0xc0000374`) in `ntdll!RtlFreeHeap`. + +The first two minidumps have the same application call chain. The leading +application offsets are `+0xbb9f28`, `+0x1043`, `+0x1165`, `+0x2e87b`, +`+0x2ec26`, `+0x611cf8`, followed by the native event callback and Win32 +subclass/message dispatch. Disassembly shows the failing release reached via +a non-atomic reference-count decrement (`dec`, without `lock`). The installed +release's matching PDB was not published; these offsets are evidence, not +claimed symbol names. + +The original dumps remain in the machine's Windows CrashDumps directory and +must not be committed or uploaded: they can contain application memory. + +The installed build pins Virtua 0.51.0. The later Virtua 0.53.5 change (#991) +is absent from it. The native reproducer below does not load any JavaScript. + +## Cause and bounded correction + +`tauri-runtime-wry` 2.10.1's clonable context contains +`DispatcherMainThreadContext::window_target`. Its unsafe Send/Sync context +permits public runtime handles to move between threads, but cloning/dropping +the Windows target updates Tao's `Rc` non-atomically. +IPC-related handle traffic can therefore free the runner while the event loop +still owns it. A different ordering can leave a leaked reference instead. + +References: tauri-apps/tauri#15408 and #15793. The same two-file counter change +was proposed in tauri-apps/tao#1290, **closed without merging** because Tao's +target is not intended to cross threads. The violation is in the runtime's +unsafe wrapper; CodeNomad's reproducer needs no unsafe Send wrapper of its own. + +For the pinned runtime CodeNomad vendors a narrow compatibility correction: +the Windows runner's shared ownership uses `Arc`. Only reference counting +becomes thread-safe; runner state remains main-thread-only, and the window +target's public threading contract is not widened. Other platforms and existing +Windows input/macOS zoom backports retain their behavior. + +## Regression + +Run on Windows: + +```sh +node scripts/test-tauri-runner-refcount.mjs --baseline +node scripts/test-tauri-input-deadlock.mjs --baseline +``` + +The new fixture creates the real Wry runtime and performs 800,000 public +runtime-handle clones/drops on eight workers, followed by teardown. It opens +no visible window and uses no web page, CodeNomad data, backend or daemon. +Every corrected run must exit successfully. The optional negative control +copies the same crate and lockfile, reverses only the counter correction, +and requires a native fault within ten isolated processes. A surviving +negative-control process is reported explicitly, not counted as a fault. + +Initial diagnosis: the original build failed in all three stress runs; +the corrected build completed all five runs (four million clones total). +Debugger inspection of a failing native fixture reached `alloc::rc::Rc::drop_slow` +from a worker thread. The input regression is required alongside this test +because the existing Windows backport shares `event_loop.rs`. + +The final scripted negative control reproduced nine native crashes out of ten +processes (one survived); all five corrected processes passed. The input suite +also passed keydown, keyup, char, syschar, IME and the instrumented IME-boundary +case, while each corresponding original/reverted negative control reproduced +its expected deadlock. The release application builds with the same vendor fix. + +## Local installation + +A release binary built from this worktree with the corrected counter was +installed on the affected machine after normal window shutdown. Existing +resources and the updater verification public key were retained. Its embedded +application version is `0.20.1-dev-20261018-1f132dbb-heapfix` (the Windows PE +version resource still reports the package version, `0.20.1`). SHA-256: + +`0e8dce026bd3db3d48d77a4a82820e527e1425d8357214be8851e4ab8e15cea3` + +The original executable and matching symbols for the corrected build were +archived locally before replacement. This is a local repair, not a published +release; sustained application use is additional validation beyond the bounded +native regression. diff --git a/packages/tauri-app/Cargo.lock b/packages/tauri-app/Cargo.lock index 8d2466dfe..2fc7bbab3 100644 --- a/packages/tauri-app/Cargo.lock +++ b/packages/tauri-app/Cargo.lock @@ -565,6 +565,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "codenomad-windows-runner-fixture" +version = "0.1.0" +dependencies = [ + "tauri-runtime", + "tauri-runtime-wry", + "windows-sys 0.59.0", +] + [[package]] name = "combine" version = "4.6.7" diff --git a/packages/tauri-app/Cargo.toml b/packages/tauri-app/Cargo.toml index f49de807f..d58f0bd22 100644 --- a/packages/tauri-app/Cargo.toml +++ b/packages/tauri-app/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["src-tauri", "tests/windows-input", "tests/macos-window", "tests/panel-extension"] +members = ["src-tauri", "tests/windows-input", "tests/windows-runner-refcount", "tests/macos-window", "tests/panel-extension"] default-members = ["src-tauri"] resolver = "2" diff --git a/packages/tauri-app/tests/windows-runner-refcount/Cargo.toml b/packages/tauri-app/tests/windows-runner-refcount/Cargo.toml new file mode 100644 index 000000000..45afcd69c --- /dev/null +++ b/packages/tauri-app/tests/windows-runner-refcount/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "codenomad-windows-runner-fixture" +version = "0.1.0" +edition = "2021" +publish = false + +[target.'cfg(windows)'.dependencies] +tauri-runtime = "=2.10.1" +tauri-runtime-wry = "=2.10.1" +windows-sys = { version = "0.59", features = ["Win32_System_Diagnostics_Debug"] } diff --git a/packages/tauri-app/tests/windows-runner-refcount/build.rs b/packages/tauri-app/tests/windows-runner-refcount/build.rs new file mode 100644 index 000000000..d9a6e2050 --- /dev/null +++ b/packages/tauri-app/tests/windows-runner-refcount/build.rs @@ -0,0 +1,14 @@ +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { + // Cargo can reuse this compiled build script between the temporary + // negative control and the checkout. Resolve the current path at run time. + let manifest = std::path::PathBuf::from( + std::env::var("CARGO_MANIFEST_DIR") + .expect("Cargo provides the current manifest directory"), + ) + .join("../../src-tauri/examples/window_constraints_windows.manifest"); + println!("cargo:rerun-if-changed={}", manifest.display()); + println!("cargo:rustc-link-arg=/MANIFEST:EMBED"); + println!("cargo:rustc-link-arg=/MANIFESTINPUT:{}", manifest.display()); + } +} diff --git a/packages/tauri-app/tests/windows-runner-refcount/src/main.rs b/packages/tauri-app/tests/windows-runner-refcount/src/main.rs new file mode 100644 index 000000000..4ee662a1a --- /dev/null +++ b/packages/tauri-app/tests/windows-runner-refcount/src/main.rs @@ -0,0 +1,52 @@ +//! Exercises the real Tauri runtime's safe Send + Clone handle across workers. +//! No CodeNomad profile, backend, shared daemon, web page or visible window. +#[cfg(not(windows))] +fn main() { + eprintln!("This regression requires Windows"); + std::process::exit(1); +} + +#[cfg(windows)] +fn main() { + use std::{ + hint::black_box, + sync::{Arc, Barrier}, + thread, + time::Duration, + }; + use tauri_runtime::Runtime; + use tauri_runtime_wry::Wry; + use windows_sys::Win32::System::Diagnostics::Debug::{SetErrorMode, SEM_NOGPFAULTERRORBOX}; + + // A negative control must exit without opening a Windows error dialog. + unsafe { + SetErrorMode(SEM_NOGPFAULTERRORBOX); + } + thread::spawn(|| { + thread::sleep(Duration::from_secs(30)); + eprintln!("FAIL: runtime handle regression watchdog"); + std::process::exit(2); + }); + let runtime = Wry::<()>::new(Default::default()).expect("create isolated Wry runtime"); + let gate = Arc::new(Barrier::new(9)); + let workers: Vec<_> = (0..8) + .map(|_| { + let handle = runtime.handle(); + let gate = gate.clone(); + thread::spawn(move || { + gate.wait(); + for _ in 0..100_000 { + // IPC performs this same safe runtime-handle clone/drop traffic. + drop(black_box(handle.clone())); + } + }) + }) + .collect(); + println!("START: concurrent Tauri runtime handle cloning"); + gate.wait(); + for worker in workers { + worker.join().expect("runtime worker failed"); + } + drop(runtime); + println!("PASS: 800000 runtime handle clones and clean teardown"); +} diff --git a/packages/tauri-app/vendor/README.md b/packages/tauri-app/vendor/README.md index b38c075e5..89a796442 100644 --- a/packages/tauri-app/vendor/README.md +++ b/packages/tauri-app/vendor/README.md @@ -1,8 +1,9 @@ # Tao native window backports -`tao-0.34.6/` is the published Apache-2.0 crate (license included), with only -the four Windows source files from upstream [tao#1215] and the macOS -`is_zoomed` function from [tao#1182] changed. It is selected +`tao-0.34.6/` is the published Apache-2.0 crate (license included), with +the Windows input backport from upstream [tao#1215], the macOS +`is_zoomed` function from [tao#1182], and the local Windows runner refcount +compatibility correction below. It is selected by the Tauri workspace's `[patch.crates-io]`; there is no registry-cache mutation or build-time patching. All platforms keep the same dependency/API version. @@ -49,12 +50,44 @@ events, including reads during geometry capture, and exercises maximize/restore, fixed-size and decorated windows. Its original-crate negative control reproduces the feedback loop. -Remove the override only after both native backports are available in the -resolved supported release and both native regressions pass. +Remove the override only after the resolved dependencies cover the input, +read-only zoom and runner lifetime fixes and their native regressions pass. [tao#1215]: https://github.com/tauri-apps/tao/pull/1215 [tao#1182]: https://github.com/tauri-apps/tao/pull/1182 +## Windows runtime handle lifetime + +The pinned `tauri-runtime-wry` 2.10.1 exposes `Send + Sync + Clone` runtime +handles whose unsafe context graph contains Tao's non-atomic +`Rc`. Safe handle clones/drops on IPC workers race the main +thread's reference count, freeing the runner prematurely or leaking it. + +The local correction changes **only this internal shared counter** to `Arc` +in `event_loop.rs` and `event_loop/runner.rs`. It neither makes +`EventLoopWindowTarget` Send/Sync nor permits worker access to the runner's +`Cell`/`RefCell` state. Tauri's existing main-thread access discipline remains. + +Provenance: [tauri#15408](https://github.com/tauri-apps/tauri/issues/15408), +[tauri#15793](https://github.com/tauri-apps/tauri/issues/15793) and the proposal +[tao#1290](https://github.com/tauri-apps/tao/pull/1290). **That PR was closed +unmerged**: the maintainer correctly identified Tauri's cross-thread use of a +non-Send window target as the contract violation. This is a deliberate local +compatibility correction for the runtime we ship, not a claimed upstream merge +or a general guarantee that Tao window targets are safe to use across threads. + +`node scripts/test-tauri-runner-refcount.mjs --baseline` calls the real pinned +Wry runtime's safe public handle API on eight workers. The negative control +reverts only the counter in a temporary crate copy and requires a native fault; +the corrected build must finish every run and tear down cleanly. It uses no +CodeNomad profile, page, backend or OpenCode service. Individual negative-control +processes can survive a race that leaks instead of freeing early. + +Keep this correction until the resolved Tauri runtime no longer races Tao's +reference count (or the resolved Tao implementation safely handles that traffic). +Qualify removal with the same regression and the existing Windows input tests. +Captured crash evidence is recorded in `dev-docs/TAURI_WINDOWS_RUNNER_REFCOUNT.md`. + ## Updater Windows launch handoff `tauri-plugin-updater-2.12.0/` is the published MIT/Apache-2.0 crate (licenses diff --git a/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop.rs b/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop.rs index 975afeca9..d8751e4b5 100644 --- a/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop.rs +++ b/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop.rs @@ -15,7 +15,6 @@ use std::{ ffi::c_void, marker::PhantomData, mem, panic, - rc::Rc, sync::Arc, thread, time::{Duration, Instant}, @@ -198,7 +197,7 @@ impl EventLoop { thread::spawn(move || wait_thread(thread_id, HWND(send_thread_msg_target as _))); let wait_thread_id = get_wait_thread_id(); - let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + let runner_shared = Arc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); let thread_msg_sender = subclass_event_target_window(thread_msg_target, runner_shared.clone()); raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target, Default::default()); diff --git a/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop/runner.rs b/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop/runner.rs index ff865de3a..fdd110a8c 100644 --- a/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop/runner.rs +++ b/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/windows/event_loop/runner.rs @@ -7,7 +7,7 @@ use std::{ cell::{Cell, RefCell}, collections::{HashSet, VecDeque}, mem, panic, - rc::Rc, + sync::Arc, time::Instant, }; @@ -24,7 +24,10 @@ use crate::{ window::WindowId, }; -pub(crate) type EventLoopRunnerShared = Rc>; +// The pinned Tauri runtime clones/drops this handle through unsafe Send/Sync +// contexts on worker threads. Only the reference count may cross threads; +// the runner's interior remains confined to the event-loop thread. +pub(crate) type EventLoopRunnerShared = Arc>; pub(crate) struct EventLoopRunner { // The event loop's win32 handles thread_msg_target: HWND, diff --git a/scripts/test-tauri-runner-refcount.mjs b/scripts/test-tauri-runner-refcount.mjs new file mode 100644 index 000000000..0258346f3 --- /dev/null +++ b/scripts/test-tauri-runner-refcount.mjs @@ -0,0 +1,85 @@ +// Real Wry handle cloning. No CodeNomad profile, backend, page or shared daemon. +import assert from "node:assert/strict" +import { spawnSync } from "node:child_process" +import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises" +import os from "node:os" +import path from "node:path" +import { fileURLToPath } from "node:url" + +assert.equal(process.platform, "win32", "Run this native regression on Windows") +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..") +const workspace = path.join(root, "packages/tauri-app") +const fixture = "tests/windows-runner-refcount" +const target = path.resolve(process.env.CARGO_TARGET_DIR || path.join(workspace, "target")) +const env = { ...process.env, CARGO_TARGET_DIR: target } +delete env.NODE_OPTIONS +delete env.CODENOMAD_NATIVE_PARENT +const binary = path.join(target, "release/codenomad-windows-runner-fixture.exe") + +function cargo(args, cwd = workspace) { + const result = spawnSync("cargo", args, { cwd, env, encoding: "utf8", timeout: 600_000, maxBuffer: 8 * 1024 * 1024, windowsHide: true }) + assert.equal(result.error, undefined, String(result.error)) + assert.equal(result.status, 0, result.stdout + result.stderr) + return result.stdout +} +function run(fixed, count) { + let crashes = 0 + for (let index = 0; index < count; index++) { + const result = spawnSync(binary, [], { env, encoding: "utf8", timeout: 35_000, windowsHide: true }) + assert.equal(result.error, undefined, String(result.error)) + assert.match(result.stdout, /START: concurrent Tauri runtime handle cloning/) + if (fixed) { + assert.equal(result.status, 0, result.stdout + result.stderr) + assert.match(result.stdout, /PASS: 800000 runtime handle clones and clean teardown/) + } else if (result.status !== 0) { + // Native heap/access failures and Rust's abort on an invalid Rc refcount. + assert.ok([0xc000001d, 0xc0000005, 0xc0000374, 0xc0000409].includes(result.status >>> 0), + `Expected the native refcount failure, got ${result.status}: ${result.stdout}${result.stderr}`) + crashes++ + } else { + // A refcount race can leak rather than free early. One surviving process + // is not a clean baseline; the bounded negative-control series must crash. + assert.match(result.stdout, /PASS: 800000 runtime handle clones and clean teardown/) + } + console.log(`${fixed ? "fixed PASS" : result.status === 0 ? "negative control survived" : "negative control crash reproduced"} ${index + 1}/${count}: 0x${(result.status >>> 0).toString(16)}`) + } + if (!fixed) assert.ok(crashes > 0, "Negative control did not reproduce the native race") +} +function replaceOnce(source, before, after) { + assert.equal(source.split(before).length, 2, `Expected one counter boundary: ${before}`) + return source.replace(before, after) +} + +if (process.argv.includes("--baseline")) { + const temporaryRoot = path.join(os.tmpdir(), "opencode") + await mkdir(temporaryRoot, { recursive: true }) + const baseline = await mkdtemp(path.join(temporaryRoot, "tauri-runner-baseline-")) + try { + await cp(path.join(workspace, fixture), path.join(baseline, fixture), { recursive: true }) + await cp(path.join(workspace, "vendor/tao-0.34.6"), path.join(baseline, "tao"), { recursive: true }) + await cp(path.join(workspace, "Cargo.lock"), path.join(baseline, "Cargo.lock")) + const manifest = "src-tauri/examples/window_constraints_windows.manifest" + await mkdir(path.dirname(path.join(baseline, manifest)), { recursive: true }) + await cp(path.join(workspace, manifest), path.join(baseline, manifest)) + await writeFile(path.join(baseline, "Cargo.toml"), `[workspace]\nmembers = ["${fixture}"]\nresolver = "2"\n[patch.crates-io]\ntao = { path = "tao" }\n`) + // Reverse only this correction in a temporary copy. Existing input and macOS + // backports, dependencies and the public Wry API exercised remain identical. + const runner = path.join(baseline, "tao/src/platform_impl/windows/event_loop/runner.rs") + let source = await readFile(runner, "utf8") + source = replaceOnce(source, " sync::Arc,", " rc::Rc,") + source = replaceOnce(source, "= Arc>;", "= Rc>;") + await writeFile(runner, source) + const loopFile = path.join(baseline, "tao/src/platform_impl/windows/event_loop.rs") + await writeFile(loopFile, replaceOnce(await readFile(loopFile, "utf8"), + "let runner_shared = Arc::new(", "let runner_shared = std::rc::Rc::new(")) + cargo(["build", "--release", "-p", "codenomad-windows-runner-fixture"], baseline) + run(false, 10) + } finally { await rm(baseline, { recursive: true, force: true }) } +} + +const metadata = JSON.parse(cargo(["metadata", "--locked", "--format-version", "1"])) +const tao = metadata.packages.filter(item => item.name === "tao") +assert.equal(tao.length, 1) +assert.equal(path.resolve(tao[0].manifest_path), path.join(workspace, "vendor/tao-0.34.6/Cargo.toml")) +cargo(["build", "--locked", "--release", "-p", "codenomad-windows-runner-fixture"]) +run(true, 5)