Skip to content

Seed Revise's cache source hash with a UInt, so 32 bit runs can revise - #80

Merged
davidanthoff merged 2 commits into
mainfrom
revise-hash-seed-32bit
Aug 22, 2026
Merged

Seed Revise's cache source hash with a UInt, so 32 bit runs can revise#80
davidanthoff merged 2 commits into
mainfrom
revise-hash-seed-32bit

Conversation

@davidanthoff

@davidanthoff davidanthoff commented Aug 21, 2026

Copy link
Copy Markdown
Member

Revise passes the precompile cache header's content hash to hash as a UInt64. The seed hash takes is a UInt, 32 bits wide on a 32 bit platform, so on every x86 leg Revise's package callback dies:

MethodError: no method matching hash(::UInt64, ::UInt64)
  at cache_src_id (packages/Revise/src/pkgs.jl:138)
  at parse_pkg_files → watch_package → …

Every test item that loads a package in that process then reports the failed callback as its own error, which is how this surfaces on JuliaMCP's rc~x86 legs in items that have nothing to do with Revise.

This replaces the first version of this PR, which fixed it by editing packages/Revise/src/pkgs.jl. That was wrong — packages/ holds git subtrees that are never edited by hand. Re-vendoring is no help either: the bug is upstream's, and the vendored copy is already at 3.16.4, the latest tag, whose src/pkgs.jl:138 still reads cache_src_id(inc) = hash(inc.fsize, UInt64(inc.hash)).

So the method is supplied where testprocess/TestItemServer/src/pkg_imports.jl assembles the Revise module, right after the vendored packagedef.jl is included. It is added, not redefined: a definition with the vendored signature is a method overwrite, which Julia rejects outright while a package precompiles, and the second commit here fixes exactly that regression from the first —

WARNING: Method definition cache_src_id(Any) in module Revise at packages/Revise/src/pkgs.jl:138
         overwritten at testprocess/TestItemServer/src/pkg_imports.jl:106.
ERROR: Method overwriting is not permitted during Module precompilation.
  24769.9 ms  ? TestItemServer

Precompilation is warn-only on the path a test process takes, so TestItemServer still loaded — at the cost of reloading the whole vendored stack from source in every test process, and of a precompile banner on its stderr that landed in whatever a test item captured. That is what reddened the 64 bit legs: the assertions that a process's output carries no ANSI escapes, the coverage items whose processes then went unanswered, and the recycling items that timed out on the extra startup.

Both call sites (Revise/src/loading.jl:118, Revise/src/pkgs.jl:192) pass a Base.CacheHeaderIncludes, so a method on that type is strictly more specific and wins dispatch without touching upstream's cache_src_id(::Any). Guarded to 32 bit, where the bug is — on 64 bit the vendored definition already computes the right value, and leaving its dispatch and invalidation exactly as upstream ships them is one less thing to reason about. inc.hash is a UInt32, so UInt(inc.hash) is an exact widening on either word size; isdefined covers the internal Base type, whose shape is unchanged from 1.11 through nightly.

The suite had nothing asserting that TestItemServer's cache is actually built — the breakage only surfaced through unrelated assertions about process output — so test/test_testserver_precompile.jl precompiles the version-matched test process environment and fails on a ?. It uses Base.compilecache rather than Pkg.precompile: that rebuilds unconditionally, so a cache another item already wrote cannot short-circuit it, and it reports failure as a return value instead of exiting 0. Verified to fail on the overwriting form and pass on this one.

Smoke-tested under both 1.12 and 1.12~x86: the environment precompiles on both, the two methods coexist with the 32 bit one winning dispatch and computing the same value, and the color-output, coverage and worker-recycling items all pass.

Found while auditing the stack for Julia 1.13 readiness; like #79 this is a word-size bug the rc legs merely made visible. An upstream Revise PR follows, so the override can be dropped at the next re-vendor.

Note for the x86 legs: they will keep failing "Coverage without coverage roots" after this merges. That failure's stack trace points into ~/.julia/packages/TestItemControllers/…, i.e. the registered TestItemControllers that juliati runs the suite on, not the checkout — it is present on main today and clears only once a release carrying this fix is registered.

🤖 Generated with Claude Code

@davidanthoff
davidanthoff force-pushed the revise-hash-seed-32bit branch from 80087b7 to 5c4f64d Compare August 21, 2026 18:56
Revise passes the precompile cache header's content hash to `hash` as a
`UInt64`. The seed `hash` takes is a `UInt`, 32 bits wide on a 32 bit platform,
so on every `x86` leg Revise's package callback dies with

    MethodError: no method matching hash(::UInt64, ::UInt64)
      at cache_src_id (packages/Revise/src/pkgs.jl:138)

and every test item that loads a package in that process reports the failed
callback as its own error — which is how it surfaced on JuliaMCP's `rc~x86`
legs, in items that have nothing to do with Revise.

The bug is upstream's and the vendored copy is already at 3.16.4, the latest
tag, so re-vendoring is no help; `packages/` is not edited by hand. The method
is replaced instead where `pkg_imports.jl` assembles the `Revise` module, right
after the vendored `packagedef.jl` is included. Truncating with `% UInt` is
enough: the value only identifies a source snapshot within one session, and on
64 bit it is the value Revise already computes.

No unit test — `cache_src_id` lives in the module the test process assembles,
and reaching it from this suite means loading the whole vendored stack for a
one-line method. The `x86` legs are the check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@davidanthoff
davidanthoff force-pushed the revise-hash-seed-32bit branch from 5c4f64d to 27c4080 Compare August 22, 2026 00:22
@davidanthoff davidanthoff changed the title Seed the cache source hash with a UInt, so 32 bit runs can revise Seed Revise's cache source hash with a UInt, so 32 bit runs can revise Aug 22, 2026
Redefining `cache_src_id` after the vendored `packagedef.jl` is included has the
signature the vendored definition already has, which makes it a method overwrite —
and Julia rejects method overwriting outright while a package precompiles:

    WARNING: Method definition cache_src_id(Any) in module Revise at
             packages/Revise/src/pkgs.jl:138 overwritten at
             testprocess/TestItemServer/src/pkg_imports.jl:106.
    ERROR: Method overwriting is not permitted during Module precompilation.
      24769.9 ms  ? TestItemServer

Precompilation is warn-only on the path a test process takes, so the package still
loaded — at the cost of reloading the whole vendored stack from source in every test
process, and of a precompile banner on its stderr that landed in whatever a test item
captured. That is what reddened the 64 bit legs: the assertions that a process's output
carries no ANSI escapes, the coverage items whose processes then went unanswered, and
the recycling items that timed out on the extra startup.

Add the method rather than replace it: both call sites pass a
`Base.CacheHeaderIncludes`, so a method on that type is strictly more specific and wins
dispatch without touching upstream's `cache_src_id(::Any)`. Guarded to 32 bit, where the
bug is — on 64 bit the vendored definition already computes the right value, and leaving
its dispatch and invalidation exactly as upstream ships them is one less thing to reason
about. `inc.hash` is a `UInt32`, so `UInt(inc.hash)` is an exact widening on either word
size; `isdefined` covers the internal Base type.

The suite had nothing asserting that TestItemServer's cache is actually built — this
only surfaced through unrelated assertions about process output — so add a test item
that precompiles the version-matched test process environment and fails on a `?`.
`Base.compilecache` rather than `Pkg.precompile`, because it rebuilds unconditionally
and reports failure as a return value instead of exiting 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@davidanthoff
davidanthoff merged commit ee7febd into main Aug 22, 2026
15 of 17 checks passed
@davidanthoff
davidanthoff deleted the revise-hash-seed-32bit branch August 22, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant