neon-bindings/neon · error
Attempted to settle JsFuture multiple times
Error message
Attempted to settle JsFuture multiple times
What it means
`JsFuture::to_future` installs `then`/`catch` callbacks that each `take()` a shared `Mutex<Option<...>>` to run the continuation exactly once. The `.expect` fires when the state slot is already empty — i.e. the promise's then/catch callbacks were both invoked, which the JavaScript promise spec forbids. This is a defensive invariant check against a spec-violating or foreign promise-like object.
Solutions
- Ensure the object passed to to_future is a genuine native Promise, not a hand-rolled thenable
- Fix or drop custom thenable/polyfill implementations that settle twice
- Validate inputs with `instanceof Promise` before calling to_future in generic APIs
Example fix
Only call `to_future` on real native Promise objects (`cx.promise()` results or values verified as Promise instances); replace or fix any thenable that invokes its callbacks more than once.
Defensive patterns
Strategy: validation
Prevention
- Document that to_future requires spec-compliant promises
- Add input validation (type/instance checks) at API boundaries accepting promises
When it happens
Trigger: Inside the generated resolve/reject callbacks, `take_state()` panics if `lock.take()` returns None — the callback is invoked a second time (or both resolve and reject fire), which only happens if `self` is not a spec-compliant native Promise.
Common situations: Passing a non-promise 'thenable' with broken semantics (calls then twice) into to_future; misbehaved mocks or polyfills; embedding engines whose promise implementation violates the spec.
AI-assisted analysis of neon-bindings/neon@38960e4381 (2026-09-13).
Data as JSON: /api/errors/caa8fb784b6fb808.
Report an issue: GitHub.
Appendix: source
Thrown at crates/neon/src/types_impl/promise.rs:201
let (tx, rx) = oneshot::channel();
let take_state = {
// Note: If this becomes a bottleneck, `unsafe` could be used to avoid it.
// The promise spec guarantees that it will only be used once.
let state = Arc::new(Mutex::new(Some((f, tx))));
move || {
state
.lock()
.ok()
.and_then(|mut lock| lock.take())
// This should never happen because `self` is a native `Promise`
// and settling multiple times is a violation of the spec.
.expect("Attempted to settle JsFuture multiple times")
}
};
let resolve = JsFunction::new(cx, {
let take_state = take_state.clone();
move |mut cx| {
let (f, tx) = take_state();
let v = cx.argument::<JsValue>(0)?;View on GitHub (pinned to 38960e4381)