{"record":{"id":"4b8c7bfdefb30160","repo":"siyuan-note/siyuan","slug":"worker-event-loop-not-initialized","errorCode":null,"errorMessage":"worker event loop not initialized","messagePattern":"worker event loop not initialized","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/worker.go","lineNumber":46,"sourceCode":"type TaskExecutor func(rt *goja.Runtime) (result any, err error)\ntype TaskCallback func(rt *goja.Runtime, result any, err error)\n\ntype Worker struct {\n\tloop *eventloop.EventLoop\n}\n\ntype TaskResult struct {\n\tvalue any\n\terr   error\n}\n\nfunc (w *Worker) Start(loop *eventloop.EventLoop) {\n\tw.loop = loop\n}\n\nfunc (w *Worker) Run(executor TaskExecutor, callback TaskCallback) error {\n\tif w.loop == nil {\n\t\treturn fmt.Errorf(\"worker event loop not initialized\")\n\t}\n\n\tsuccess := w.loop.RunOnLoop(func(rt *goja.Runtime) {\n\t\tvar result any\n\t\tvar err error\n\n\t\tdefer func() {\n\t\t\tdefer func() {\n\t\t\t\t// 捕获回调中的 panic 并保留原始调用栈，便于定位 Promise 处理异常。\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlogging.LogErrorf(\"task callback panicked: %v\\n%s\", r, debug.Stack())\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// 捕获执行器中的 panic 并保留原始调用栈，同时将错误传给回调。\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tlogging.LogErrorf(\"task executor panicked: %v\\n%s\", r, debug.Stack())\n\t\t\t\terr = fmt.Errorf(\"task executor panicked: %v\", r)","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/plugin/worker.go#L28-L64","documentation":"Worker.Run executes a task on the goja event loop previously injected via Worker.Start(loop). If Run is called before Start, w.loop is nil and the worker cannot schedule onto the runtime, so it returns this error. It is an initialization-order bug in the worker's lifecycle.","triggerScenarios":"Calling worker.Run(executor, callback) without having called worker.Start(eventLoop) first; the runtime/eventloop was torn down (e.g. plugin unloaded) and the loop reference cleared or never set on a recreated Worker; two Worker instances created but Start called on the wrong one.","commonSituations":"Kernel code invoking agent capabilities, hooks, or RPC after plugin shutdown but before the worker is re-initialized; refactoring moved Start after the first Run; a nil loop leaked from a failed plugin boot.","solutions":["Call worker.Start(loop) with the plugin's eventloop.EventLoop before the first worker.Run call.","Check plugin initialization order — ensure the JS runtime/eventloop bootstraps before any capability/hook/RPC dispatch.","If the loop may legitimately be absent (plugin unloaded), check errors.Is/inspect the returned error and queue or reject the task gracefully.","Audit teardown/reload paths so a Worker is re-Started with the new loop after a plugin reload."],"exampleFix":"// before\nw.Run(executor, cb) // loop never set\n// after\nw.Start(loop)\nw.Run(executor, cb)","handlingStrategy":"try-catch","validationCode":"// Go: ensure the loop is set before scheduling\nif w.loop == nil {\n    return fmt.Errorf(\"worker event loop not initialized\")\n}","typeGuard":null,"tryCatchPattern":"if err := w.Run(executor, callback); err != nil {\n    // loop missing: initialize via w.Start(loop) and retry, or fail the task cleanly\n}","preventionTips":["Always call Worker.Start(loop) immediately after constructing the worker.","Re-Start the worker with the new loop after plugin reload/unload cycles.","Assert loop initialization in plugin boot tests."],"tags":["goja","concurrency","initialization","worker"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}