Skip to content

Report a shared value locked by its own thread as locked under sync - #1190

Merged
schungx merged 1 commit into
rhaiscript:mainfrom
yuvalrakavy:fix-shared-value-is-locked-under-sync
Oct 17, 2026
Merged

schungx merged 1 commit into
rhaiscript:mainfrom
yuvalrakavy:fix-shared-value-is-locked-under-sync

Conversation

@yuvalrakavy

Copy link
Copy Markdown
Contributor

Split out of #1189 at the maintainer's request. Refs #1188 (the panic half); the abort half is #1189.

Under sync, Dynamic::is_locked returns false for every shared value, on the reasoning that access waits for the RwLock. Under not(unchecked) it does not wait indefinitely: locked_read tries a few times and gives up, and type_id() / type_name() then unwrap the None and panic. That always happens when the current thread itself holds the value locked for write:

#[derive(Clone)]
struct Thing(i64);

fn with_arg(ctx: NativeCallContext, t: Thing, f: FnPtr) -> Result<Dynamic, Box<EvalAltResult>> {
    f.call_within_context(&ctx, (t,))
}

let mut e = Engine::new();
e.register_type_with_name::<Thing>("Thing");
e.register_fn("with_arg", with_arg);
e.register_fn("get", |t: &mut Thing| t.0);
let mut scope = Scope::new();
scope.push("t", Thing(7));
// `||` takes no argument but `with_arg` passes one; `t` is captured, so the method call holds it locked.
e.eval_with_scope::<Dynamic>(&mut scope, "t.with_arg(|| t.get())");

Without sync, #1161's ensure_no_data_race turns this into ErrorDataRace. Under sync it does not, because is_locked says the value is free; the call goes on to resolve_fn, which hashes each argument's type_id(), and that read gives up and panics.

The change makes is_locked under sync (and not unchecked) return true exactly when locked_read would give up, so the existing data-race check covers sync as well. Under sync + unchecked access blocks rather than gives up, so is_locked stays false.

This changes the documented meaning of a public method under sync (the doc said shared values "are never locked" there), which is why it is its own PR for you to shape. Another option is to keep is_locked as it is and make ensure_no_data_race probe locked_read itself.

Test: test_closures_capturing_locked_method_receiver in tests/closures.rs, gated on no_closure (the closure must capture the variable to share it; that was the CI failure on the no_closure row), no_object (the method-call syntax) and unchecked. Under sync without this change it panics in type_id; with it, ErrorDataRace under both feature sets.

Checks: cargo test --features sync, cargo test --test closures with default features and under the no_closure CI row, cargo +nightly fmt --check. CHANGELOG line under 1.27.0's bug fixes, linking this PR.

🤖 Generated with Claude Code

Under `sync`, Dynamic::is_locked returned false for every shared value, on
the reasoning that access waits for the RwLock. Under `not(unchecked)` it does
not wait indefinitely: locked_read tries a few times and gives up, and
type_id()/type_name() then unwrap the None and panic. That always happens
when the current thread itself holds the value locked for write, so a method
call on a shared variable with a closure argument capturing the same
variable (`t.with_arg(|| t + 1)`) panicked while resolving the function,
where the same script without `sync` gets ErrorDataRace from
ensure_no_data_race.

Make is_locked report exactly the case locked_read gives up on, so the
existing data-race check covers it under `sync` too. Under `sync` +
`unchecked` access blocks rather than gives up, so is_locked stays false.

This changes the documented meaning of a public method under `sync`.

The regression test needs closures (to share the variable), the dot syntax
(the method call) and a checked build, so it is gated on `no_closure`,
`no_object` and `unchecked`.

Refs rhaiscript#1188 (the panic half).
@yuvalrakavy

Copy link
Copy Markdown
Contributor Author

NoStdBuild: same nightly change as on #1189 (rust-lang/rust#148413); the harness fix is #1191.

@schungx schungx added the bug label Oct 17, 2026
@schungx
schungx merged commit c1b933c into rhaiscript:main Oct 17, 2026
54 of 56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants