fix(connector): cancel the export poll when the panel closes or reloads
Deploy / deploy (push) Successful in 6m46s

app.scheduleTask outlives the panel instance that armed it. The repeating task
id was held in the pipelineState closure, so closing or reloading the panel took
the id with it and left nothing able to cancel the task: it kept firing every
15 s for the rest of the AE session, and every tick landing while a modal dialog
was open raised "Cannot run a script while a modal dialog is waiting for
response."

The id now lives in global scope beside the tick function that scheduleTask
already evaluates there, so the next panel instance can still reach it.
getPollTaskId and setPollTaskId wrap the access and treat anything that is not a
positive number as no task. Panel startup cancels whatever a previous instance
left armed before pointing the tick at itself, rather than letting the old task
fire into a fresh, empty state whose shot code went with the closure that armed
it, and panel.onClose stops the poll on the way out.

The export is unaffected either way: it runs on the farm, and the header picks
its status back up from the server on the next Refresh. Section 12.1 of
RenderPipeline2.md records that.

PANEL_VERSION goes to 2.6.1 per the documented rule, so Pipeline → Machines does
not show two workstations on one version behaving differently.

This change was authored in the working tree, not by Claude; it is committed
here alongside the status and client-portal work at the author's request, and
the message was written from reading the diff. It has not been exercised in a
live After Effects session as part of this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
twotalesanimation
2026-08-31 18:18:08 +02:00
parent 28e8c47907
commit a6ee1917e7
2 changed files with 44 additions and 7 deletions
+5
View File
@@ -1292,6 +1292,11 @@ The panel keeps all existing capabilities (episode/shot discovery, overlays, pic
| Latest Export + QC status | new E2 `exports/latest` — e.g. `v004 — READY_FOR_QC`, `v004 — RENDERING 42% (ETA 1:36 on RENDER-01)`, `v003 — QC FAILED: "grain mismatch"` |
While an export is active the panel polls E2 every 15 s to live-update the header.
The poll stops when the export leaves an active status, and also when the panel is
closed or reloaded — `app.scheduleTask` outlives the panel instance that armed it,
so the task id is kept in ExtendScript global scope where the next instance can
still cancel it. The export is unaffected either way: it runs on the farm, and the
header picks its status back up on the next Refresh.
### 12.2 Buttons and their API calls
+39 -7
View File
@@ -32,7 +32,7 @@ var SETTINGS_KEY = "cfg";
// Bump on every change deployed to a workstation. The panel reports this to
// the server on load, so Pipeline → Machines shows which version each machine
// is actually running and flags the ones left behind.
var PANEL_VERSION = "2.6.0";
var PANEL_VERSION = "2.6.1";
(function VFXReviewConnector(thisObj) {
var uiState = {
@@ -65,7 +65,6 @@ var PANEL_VERSION = "2.6.0";
var pipelineState = {
pollShotCode: null,
pollTaskId: 0,
prefilledShotCode: null
};
@@ -3862,13 +3861,34 @@ var PANEL_VERSION = "2.6.0";
}
}
// A scheduled task outlives the panel instance that armed it. Held only in
// this closure, the id of a repeating task goes with the instance when the
// panel is closed or reloaded, and nothing can cancel it again: it fires
// every 15 s for the rest of the AE session, and every tick that lands
// while a modal dialog is open raises "Cannot run a script while a modal
// dialog is waiting for response." The id therefore lives beside the tick
// in global scope, where the next instance can still reach it.
var POLL_TASK_ID_KEY = "__vfxrExportPollTaskId";
function getPollTaskId() {
var taskId = $.global[POLL_TASK_ID_KEY];
return (typeof taskId === "number" && taskId > 0) ? taskId : 0;
}
function setPollTaskId(taskId) {
$.global[POLL_TASK_ID_KEY] = taskId || 0;
}
function stopExportPolling() {
if (pipelineState.pollTaskId) {
var taskId = getPollTaskId();
if (taskId) {
try {
app.cancelTask(pipelineState.pollTaskId);
app.cancelTask(taskId);
} catch (cancelError) {
}
pipelineState.pollTaskId = 0;
setPollTaskId(0);
}
pipelineState.pollShotCode = null;
}
@@ -3902,9 +3922,9 @@ var PANEL_VERSION = "2.6.0";
stopExportPolling();
pipelineState.pollShotCode = shotCode;
try {
pipelineState.pollTaskId = app.scheduleTask("__vfxrExportPollTick()", 15000, true);
setPollTaskId(app.scheduleTask("__vfxrExportPollTick()", 15000, true));
} catch (scheduleError) {
pipelineState.pollTaskId = 0;
setPollTaskId(0);
}
}
@@ -5023,6 +5043,11 @@ var PANEL_VERSION = "2.6.0";
return win;
}
// A previous instance may have left a repeating poll armed, and its shot
// code went with that closure. Cancel it before the tick is pointed at this
// instance, rather than letting it fire into a fresh, empty state.
stopExportPolling();
// app.scheduleTask evaluates in global scope — expose the poll tick there
$.global.__vfxrExportPollTick = pollExportTick;
@@ -5033,6 +5058,13 @@ var PANEL_VERSION = "2.6.0";
diagLog("panel v" + PANEL_VERSION + " loaded on " + getHostName());
reportPanelVersion();
// Closing the panel leaves an armed task running with no UI left to update.
// The export itself is unaffected: it runs on the farm, and the panel picks
// its status back up from the server on the next Refresh.
panel.onClose = function () {
stopExportPolling();
};
if (panel instanceof Window) {
panel.center();
panel.show();