Frontmatter
| id | 8952 |
| title | Bug: Dynamic Worker Start triggers Double App Init & Canvas Race Condition |
| state | Closed |
| labels | bugai |
| assignees | tobiu |
| createdAt | Feb 2, 2026, 10:30 PM |
| updatedAt | Feb 2, 2026, 10:39 PM |
| githubUrl | https://github.com/neomjs/neo/issues/8952 |
| author | tobiu |
| commentsCount | 2 |
| parentIssue | 8948 |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Feb 2, 2026, 10:36 PM |
Bug: Dynamic Worker Start triggers Double App Init & Canvas Race Condition
tobiu added parent issue #8948 on Feb 2, 2026, 10:31 PM
tobiu assigned to @tobiu on Feb 2, 2026, 10:35 PM

tobiu
Feb 2, 2026, 10:35 PM
Input from Gemini 3 Pro:
✦ Resolved two critical issues with dynamic worker initialization:
Double Application Initialization:
- Cause:
startWorkerincrementedactiveWorkers, causing theconstructedThreads === activeWorkers + 1check inonWorkerConstructedto pass a second time (e.g., 5 === 4+1), triggeringloadApplicationagain.- Fix: Added
applicationLoadedflag tosrc/worker/Manager.mjsto ensureloadApplicationis called exactly once.Sparkline Initialization Race Condition:
- Cause:
initAsync(which starts the worker) andafterSetMounted(which registers the canvas) were racing. The baseafterSetMountedwould callregisterCanvasbeforeinitAsynccompleted (worker booting + remote registration).- Fix: Overrode
afterSetMountedinSparklineComponent.mjstoawait this.ready()before callingsuper.afterSetMounted(). This enforces strict ordering: the component waits forinitAsync(and thus the worker start + module load) to finish before attempting registration.
tobiu closed this issue on Feb 2, 2026, 10:36 PM

tobiu
Feb 2, 2026, 10:39 PM
Input from Gemini 3 Pro:
✦ Context: The issues resolved here (Double App Init & Race Condition) were triggered specifically when removing
useCanvasWorker: truefromapps/devrank/neo-config.json.This confirmed that the "Dynamic Worker" architecture (Epic #8948) works as intended:
- The App starts without the Canvas Worker (saving resources).
- When
SparklineComponentmounts, it dynamically starts the Canvas Worker.- The fixes in this ticket ensure this late-binding process is smooth and doesn't re-trigger application load logic.
The goal of "Zero-Config" usage for worker-based components is now achieved.
Issue 1: Double Application Initialization In
src/worker/Manager.mjs, theonWorkerConstructedmethod checksme.constructedThreads === me.activeWorkers + 1to decide when to callme.loadApplication(). WhenstartWorker()is called dynamically:me.activeWorkersis incremented.me.constructedThreadsis incremented.loadApplication()to run a second time.Issue 2: Sparkline Race Condition
Neo.component.Canvas(base class) callsNeo.worker.Canvas.registerCanvasinafterSetMounted. When using dynamic workers,initAsync(which starts the worker) might still be running whenafterSetMountedfires. This leads toTypeError: Cannot read properties of undefined (reading 'registerCanvas')because the remote method hasn't been registered yet.Proposed Fixes:
applicationLoadedflag to prevent multipleloadApplicationcalls.afterSetMountedtoawait this.ready()before callingsuper.afterSetMounted().