Skip to content

Race Condition and Performance Regression in wasm-bindgen util.rs #4502

Open
@Chain-Fox

Description

@Chain-Fox

Describe the Bug

There is a race condition and a related performance issue in the following section of the wasm-bindgen codebase:

impl<T: Hash> fmt::Display for ShortHash<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
static HASHED: AtomicBool = AtomicBool::new(false);
static HASH: AtomicUsize = AtomicUsize::new(0);
// Try to amortize the cost of loading env vars a lot as we're gonna be
// hashing for a lot of symbols.
if !HASHED.load(SeqCst) {
let mut h = DefaultHasher::new();
env::var("CARGO_PKG_NAME")
.expect("should have CARGO_PKG_NAME env var")
.hash(&mut h);
env::var("CARGO_PKG_VERSION")
.expect("should have CARGO_PKG_VERSION env var")
.hash(&mut h);
// This may chop off 32 bits on 32-bit platforms, but that's ok, we
// just want something to mix in below anyway.
HASH.store(h.finish() as usize, SeqCst);
HASHED.store(true, SeqCst);
}
let mut h = DefaultHasher::new();
HASH.load(SeqCst).hash(&mut h);
self.0.hash(&mut h);
write!(f, "{:016x}", h.finish())
}
}

  1. Race Condition:
    The affected code attempts to lazily compute and cache a hash value, but the current implementation is not thread-safe. Consider the following interleaving of two threads:

Thread 1 Thread 2
check Hashed check Hashed
recompute Hash recompute Hash

If two threads call the fmt function concurrently, they may both observe the Hashed flag as false and proceed to recompute the hash independently. This violates the intent of computing the hash only once and leads to redundant work. While the result is ultimately consistent (assuming a stable environment), this introduces unnecessary overhead.

  1. Memory Ordering Issue
    The code currently uses Ordering::SeqCst, which is overly strict for this scenario. A combination of Ordering::Acquire and Ordering::Release is sufficient to ensure correct synchronization while allowing better performance on most architectures. But for now OnceLock is the most concise implementation.

Steps to Reproduce

Found from static analysis

Expected Behavior

No race condition. Performance increase.

Actual Behavior

In a benchmark on x86 Linux, replacing SeqCst atomics with OnceLock not only eliminated potential race conditions by ensuring the hash is computed only once, but also yielded a slight performance improvement.

Additional Context

I will submit a PR with a fix

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions