-
Notifications
You must be signed in to change notification settings - Fork 5
Description
While investigating GLEECBTC/gleec-wallet#3302 the agent suggested the following improvements in the SDK to avoid the __wbindgen_throw errors often seen when something goes wrong in web console.
Though the info below relates specifically to this occuring during duplicated coin activation, similar errors can appear when other ops go wrong.
Summary
When multiple activation tasks are initiated in quick succession, the WASM layer sometimes throws: "Error: closure invoked recursively or after being dropped" from kdflib.js/kdflib_bg.wasm. This indicates a callback retained by the Rust/WASM side was either called after disposal or re-entered synchronously. It manifests around task-based activations and results in noisy errors and unstable behavior.
Context and symptoms
- Rapid, concurrent coin activations (e.g., user opens Swap before initial activations complete) produce multiple TASK events and occasional IndexedDB uniqueness violations. Around the same window, we see the WASM closure error.
- This matches the flow described in app issue Premature Swap page entry prompts competing activations #3302.
Observed logs excerpt:
Error on platform coin KMD creation: Error saving HD account to storage: Error uploading an item: "DomException { obj: Object { obj: JsValue(ConstraintError: Unable to add key to index 'wallet_account_id': at least one key does not satisfy the uniqueness requirements.) } }"
Error: closure invoked recursively or after being dropped
kdf/kdf/bin/kdflib.js 2032:15 __wbindgen_throw
kdf/kdf/bin/kdflib_bg.wasm 1:28921225 25632
kdf/kdf/bin/kdflib_bg.wasm 1:28805865 23864
kdf/kdf/bin/kdflib.js 494:10 __wbg_adapter_53
kdf/kdf/bin/kdflib.js 163:20 real
Reproduction steps
- Import/login to a mature wallet.
- Before the default/platform coins finish activation, navigate to Swap and select coins.
- Observe multiple TASK events for the same tickers and occasional IndexedDB uniqueness errors.
- Shortly after, the WASM layer emits the closure error above.
Expected behavior
- A single activation task per asset; downstream listeners are invoked exactly once per event.
- Disposed/unsubscribed closures are never called; no re-entrant invocation into the same closure stack frame.
Actual behavior
- Multiple activation attempts can occur near-simultaneously; during teardown or re-entrancy, the WASM glue attempts to call a dropped or currently-executing closure, causing a hard error.
Proposed SDK changes (preferred fix)
- Retain callback lifetimes for as long as Rust/WASM may call them (e.g., use
.forget()where appropriate or manage ownership so closures aren’t dropped while still referenced). - Make event dispatch re-entrancy safe:
- Avoid synchronous re-entry into the same JS closure; schedule with microtasks/Timers to break the call stack when needed.
- Ensure internal locks/guards prevent a single listener from being invoked while already executing.
- Guard invocation after unsubscribe/dispose:
- Null or invalidate pointers at the boundary.
- Check for null/disposed before calling into JS closures.
- For task-based activation flows, ensure a single backend task per asset is created; subsequent requests should join the in-flight activation rather than re-init a new task.
App-side mitigations (already in progress)
- Deduplicate in-flight activations in the app to reduce concurrent requests and listener churn.
- Treat IndexedDB uniqueness violations (ConstraintError) as idempotent: wait for coin availability instead of spawning new activation attempts.
These mitigations reduce frequency but cannot prevent the WASM-side from invoking dropped closures; the robust fix must live in the SDK glue layer.
Acceptance criteria
- Under rapid activation triggers, no occurrences of "closure invoked recursively or after being dropped" in logs.
- A single activation task per asset even with multiple concurrent callers.
- No callback invocations after a listener has been unsubscribed/disposed.
Environment
- Flutter Web (Dart/JS) using
kdflib_bg.wasm(wasm-bindgenglue). - Wallet: mature accounts; default/platform coins (e.g., KMD, BTC-segwit).
References
- App issue detailing trigger window and symptoms: Premature Swap page entry prompts competing activations #3302