{"record":{"id":"aea87e34b46899b1","repo":"GopeedLab/gopeed","slug":"engine-loop-terminated-aea87e","errorCode":null,"errorMessage":"engine loop terminated","messagePattern":"engine loop terminated","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/engine/inject/stream/module.go","lineNumber":531,"sourceCode":"\t\t\t\t}\n\t\t\t\tonFulfilled := runtime.ToValue(func(call goja.FunctionCall) goja.Value {\n\t\t\t\t\tsend(call.Argument(0), nil)\n\t\t\t\t\treturn goja.Undefined()\n\t\t\t\t})\n\t\t\t\tonRejected := runtime.ToValue(func(call goja.FunctionCall) goja.Value {\n\t\t\t\t\tsend(nil, exportJSError(call.Argument(0)))\n\t\t\t\t\treturn goja.Undefined()\n\t\t\t\t})\n\t\t\t\tif _, err := thenFn(value, onFulfilled, onRejected); err != nil {\n\t\t\t\t\tsend(nil, err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tsend(value, nil)\n\t})\n\tif !ok {\n\t\treturn nil, errors.New(\"engine loop terminated\")\n\t}\n\tres := <-ch\n\treturn res.value, res.err\n}\n\nfunc exportJSError(value goja.Value) error {\n\tif value == nil || goja.IsUndefined(value) || goja.IsNull(value) {\n\t\treturn errors.New(\"promise rejected\")\n\t}\n\tif err, ok := value.Export().(error); ok {\n\t\treturn err\n\t}\n\tstack := value.String()\n\tif ro, ok := value.(*goja.Object); ok {\n\t\tstackVal := ro.Get(\"stack\")\n\t\tif stackVal != nil && stackVal.String() != \"\" {\n\t\t\tstack = stackVal.String()\n\t\t}","sourceCodeStart":513,"sourceCodeEnd":549,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/engine/inject/stream/module.go#L513-L549","documentation":"The stream module's loop helper (pkg/download/engine/inject/stream/module.go:530-532) returns 'engine loop terminated' when loop.RunOnLoop reports the JS event loop is no longer running. All stream-module operations that hop onto the loop fail with this after the owning Engine is closed (Engine.Close -> loop.Terminate, engine/engine.go:135-137) or the loop has otherwise exited.","triggerScenarios":"Stream module operations (opening/reading an injected stream from script) still in flight when the parent Engine is Closed; shutdown code closing the engine while a resolver script's stream read callback is queued; an engine pooled and closed by one request while another request still streams from it.","commonSituations":"Extension lifecycle bugs: engine closed on script completion but stream kept by the fetcher for later range reads; graceful-shutdown ordering that closes the engine before download tasks are torn down; races between task cancel and stream finalizers.","solutions":["Keep the Engine alive as long as any stream/blob source derived from it is needed; close it only after those sources are released","Route all stream-module calls through a guard that checks an engine-closed flag before invoking them","On this error, recreate the engine and re-resolve to obtain fresh streams — existing stream handles are dead","Fix shutdown order: cancel tasks -> release blob sources -> Engine.Close()"],"exampleFix":"// before\n_, _ = e.RunString(resolverScript)\ne.Close() // stream source still referenced by a task\nv, err := streamModule.SomeOp(...) // \"engine loop terminated\"\n\n// after\ne.Close() only after registry.Release(blobURL) for all sources from e","handlingStrategy":"try-catch","validationCode":"if engineClosed.Load() { return nil, errors.New(\"engine closed\") }\nval, err := streamModule.Call(...) // guard before hopping onto the loop","typeGuard":null,"tryCatchPattern":"v, err := streamOp()\nif err != nil && strings.Contains(err.Error(), \"engine loop terminated\") {\n    // engine gone: recreate engine, re-resolve, obtain a new stream source\n}","preventionTips":["Keep the Engine alive until every stream/blob source it produced is released","Shutdown order: cancel tasks -> release sources -> Engine.Close","Don't cache stream handles across engine restarts"],"tags":["engine","stream","lifecycle","shutdown"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}