Skip to content

Windows: embedding VM churn retains ~40-260 B/iteration (leak canary envelope); pre-existing, GC-reclaimable, non-detached #317

Closed

Description

@ianm199

Discovery

The first-ever Windows run of the workspace suite (windows-check, added in #310) failed leak_canaries::embedding_lifecycle_is_steady_state: vm_churn grew 12,352 bytes over 64 iterations (~193/iter) against the 4,096-byte tolerance. macOS measures ~0 for the same scenario.

Probe data (scratch/windows-leak-probe, counting global allocator, 64 iters after 2 warmups)

Round 1 (windows-latest, MSVC 14.51):

  • current_exe_only: 0/iter
  • current_exe_parent_lossy_vec: 0/iter
  • env_var_os_x4: 0/iter
  • vm_create_drop_only: ~128/iter
  • vm_create_exec_drop: ~258/iter
  • vm_v51_create_drop: ~103/iter

Round 2 (same runner class):

  • vm_create_drop_only: ~151/iter
  • vm_create_exec_drop: ~190/iter
  • vm_v51_create_drop: ~88/iter
  • vm_env_plain_paths_no_bang (env hook supplies ./?.lua, bypasses default paths): ~39/iter
  • vm_env_paths_with_bang (same + ! substitution active): ~37/iter
  • vm_create_gc_collect_drop (full collect before drop): ~30/iter
  • vm_two_per_iter: ~212/iter (2 VMs)

macOS same binary shape: 0-19/iter depending on scenario/order.

What this rules out

  • NOT introduced by the unresolved external symbol localtime_s #308 sweep: env-path VMs that bypass the new Windows loadlib defaults and make setprogdir a no-op still retain; std::env::current_exe, the lossy conversion, and env::var_os retain zero in isolation; ! substitution adds nothing (37 vs 39).
  • NOT Possible memory leak: GC objects allocated without an active HeapGuard are never freed, even on Lua/Heap drop #249-class: the detached-allocation tripwire is 0 in every run — nothing escapes the heap guards.
  • Retention scales with interned-string volume (default 5.4 paths ~150/iter vs tiny env paths ~39/iter) and is mostly reclaimed by an explicit collectgarbage('collect') before drop.
  • Counter is layout-based (Layout::size), so allocator size-class rounding cannot explain it; these are real Rust-side allocations not freed by VM drop, on Windows more than macOS, run-to-run variance ~±20%.

Interim

leak_canaries TOLERANCE gets a measured Windows envelope (65,536 bytes/scenario ≈ 1KB/iter headroom) with a pointer to this issue; unix stays at 4,096; the detached tripwire is unchanged everywhere. The envelope still catches #249-class regressions (~29KB/iter → 1.8MB/scenario) by two orders of magnitude.

Next steps

  • Count allocation COUNT alongside bytes in the canary to see if it's one object or many.
  • Per-size histogram of unfreed allocations at scenario end (dhat-style) on a Windows box.
  • Check teardown ordering of interner/registry tables vs heap drop on Windows (timing/order dependence would explain the run-to-run variance).

Activity

  1. added 2 commits that reference this issue on Jul 21, 2026
  2. ianm199 commented on Jul 21, 2026

    @ianm199
    OwnerAuthor

    Investigated by the night-shift (Fable queue run on the prod box). Headline: the retention is platform-neutral, not Windows-specific — reproduced on Linux at ~165 B/iteration (live bytes +10,608 over 64 iterations after warmup), inside this issue's 40-260 envelope. Root cause: the std-file LStream side table outlives VM churn in the embedding lifecycle. Bounded fix + extended leak-canary regression: PR #319.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions