Skip to content

Commit 3d7eff5

Browse files
bajrangCoderclaude
andcommitted
fix: keep overlapping sdl.js loads from replacing each other
Removing a <script> tag does not cancel a load in flight, so after an unmount and remount a stale sdl.js could finish last, overwrite the shared window.acodePythonSdl, and closing the console would then stop the wrong SDL instance. - each load is tagged with a unique data-load-id; sdl.js registers its API under that id (via document.currentScript) instead of one global - main.js keeps the API from its own current load for stop/dispose and ignores loads that finish after a newer one started or after destroy - destroy bumps the run id so runs waiting for sdl.js never start Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 68709cb commit 3d7eff5

3 files changed

Lines changed: 34 additions & 9 deletions

File tree

‎plugin.zip‎

186 Bytes
Binary file not shown.

‎src/main.js‎

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class Python {
3333
/** number of code runs still waiting for the worker */
3434
#running = 0;
3535
#sdlPromise = null;
36+
/** SDL API from this instance's current sdl.js load */
37+
#sdl = null;
38+
/** id of the current sdl.js load; older loads are ignored */
39+
#sdlLoadId = null;
3640
$sdlScript = null;
3741
/** bumped by every run and when the console closes, to drop stale runs */
3842
#runId = 0;
@@ -57,7 +61,7 @@ class Python {
5761
const onhide = $page.onhide;
5862
$page.onhide = () => {
5963
this.#runId += 1;
60-
window.acodePythonSdl?.stop();
64+
this.#sdl?.stop();
6165
this.#state = this.NOT_INTIALIZED;
6266
this.#worker?.terminate();
6367
// runs on the terminated worker never reply
@@ -161,7 +165,7 @@ class Python {
161165

162166
async run() {
163167
const runId = ++this.#runId;
164-
window.acodePythonSdl?.stop();
168+
this.#sdl?.stop();
165169
this.#showPage();
166170
this.#inputCount = 0;
167171
this.#append(this.$input);
@@ -242,13 +246,28 @@ class Python {
242246
}
243247
}
244248

249+
/**
250+
* Loads sdl.js. Each load registers its API under its own id, so a stale
251+
* load that finishes late (e.g. after an unmount and remount) cannot
252+
* replace the API this instance uses.
253+
*/
245254
#loadSdl() {
246255
this.#sdlPromise ??= new Promise((resolve, reject) => {
256+
const loadId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
257+
this.#sdlLoadId = loadId;
247258
const $script = tag("script", { src: `${this.baseUrl}sdl.js` });
259+
$script.dataset.loadId = loadId;
248260
this.$sdlScript = $script;
249261
$script.onload = () => {
250-
if (window.acodePythonSdl) resolve(window.acodePythonSdl);
251-
else reject(new Error("sdl.js did not initialize"));
262+
const loads = window.acodePythonSdlLoads;
263+
const sdl = loads?.[loadId];
264+
if (loads) delete loads[loadId];
265+
if (!sdl) {
266+
reject(new Error("sdl.js did not initialize"));
267+
return;
268+
}
269+
if (this.#sdlLoadId === loadId) this.#sdl = sdl;
270+
resolve(sdl);
252271
};
253272
$script.onerror = () => reject(new Error("failed to load sdl.js"));
254273
document.head.append($script);
@@ -303,8 +322,12 @@ class Python {
303322

304323
this.$wrapBtn?.remove();
305324
this.$status?.remove();
306-
window.acodePythonSdl?.dispose?.();
307-
// a later init loads a fresh sdl.js instead of the disposed one
325+
// runs still waiting for sdl.js must not start after this
326+
this.#runId += 1;
327+
this.#sdl?.dispose();
328+
this.#sdl = null;
329+
// a later init loads a fresh sdl.js; a load still in flight is ignored
330+
this.#sdlLoadId = null;
308331
this.#sdlPromise = null;
309332
this.$sdlScript?.remove();
310333
this.#worker?.terminate();

‎src/sdl.js‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,10 @@ function dispose() {
250250
// let the runtime be freed instead of reused after a remount
251251
runtimePromise = null;
252252
runner = null;
253-
if (self.acodePythonSdl === api) delete self.acodePythonSdl;
254253
}
255254

256-
const api = { run, stop, dispose, SdlUnavailableError };
257-
self.acodePythonSdl = api;
255+
// Register under the id main.js put on this script tag rather than one shared
256+
// global, so a slow, stale load can never replace another load's API.
257+
const loadId = document.currentScript?.dataset.loadId;
258+
self.acodePythonSdlLoads ??= {};
259+
self.acodePythonSdlLoads[loadId] = { run, stop, dispose, SdlUnavailableError };

0 commit comments

Comments
 (0)