{"record":{"id":"db7f252045465d7a","repo":"GopeedLab/gopeed","slug":"cannot-start-in-current-state-v","errorCode":null,"errorMessage":"cannot start in current state: %v","messagePattern":"cannot start in current state: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/protocol/http/fetcher.go","lineNumber":596,"sourceCode":"\tcase stateResolved, statePaused:\n\t\t// Normal case: resolved or resuming from pause\n\t\treturn f.doStart()\n\n\tcase stateResolving:\n\t\t// Early start: mark pending and return immediately\n\t\tf.startPending.Store(true)\n\t\treturn nil\n\n\tcase stateSlowStart, stateSteady:\n\t\t// Already downloading, this is a resume from pause\n\t\treturn f.doStart()\n\n\tcase stateError:\n\t\t// Retry after error: reset and restart\n\t\treturn f.doStart()\n\n\tdefault:\n\t\treturn fmt.Errorf(\"cannot start in current state: %v\", state)\n\t}\n}\n\nfunc (f *Fetcher) doStart() error {\n\t// Wait for resolve to complete\n\t<-f.resolvedCh\n\n\tstate := f.getState()\n\tif state == stateDone {\n\t\treturn nil\n\t}\n\n\t// If retrying after error, reset connection states for retry\n\tif state == stateError {\n\t\t// Drain any pending error from doneCh before retry\n\t\tselect {\n\t\tcase <-f.doneCh:\n\t\tdefault:","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/protocol/http/fetcher.go#L578-L614","documentation":"Fetcher.Start() (internal/protocol/http/fetcher.go:574) is a state-machine entry point. It is legal from stateResolving, stateResolved, statePaused, stateSlowStart, stateSteady, and stateError, but the default branch rejects every other state. In practice that means stateIdle (Resolve was never called) or stateDone (the download already finished).","triggerScenarios":"Calling Start() on a freshly constructed Fetcher without calling Resolve(req, opts) first (state prints as 0 / stateIdle); calling Start() a second time after Wait() returned and the fetcher reached stateDone.","commonSituations":"Retry wrappers that blindly re-Start a completed fetcher instead of building a new one; race conditions where the caller starts before the resolve goroutine is kicked off; porting code from an older version where Start implied resolve.","solutions":["Call Resolve(req, opts) first and only then Start() — the normal lifecycle is Resolve -> Start -> Wait","Never restart a finished fetcher: create a new Fetcher instance for each download attempt","If you see 'cannot start in current state: 0', the fetcher was never resolved; if the value is the done state, the task already completed","In retry loops, reconstruct the fetcher (or use Pause/Start for pause-resume flows) instead of re-Start"],"exampleFix":"// before\nfetcher.Start() // stateIdle: resolve never ran -> \"cannot start in current state\"\n\n// after\nif err := fetcher.Resolve(req, opts); err != nil {\n    return err\n}\nif err := fetcher.Start(); err != nil {\n    return err\n}","handlingStrategy":"validation","validationCode":"// Enforce the lifecycle in the caller: Resolve before Start, never restart a done fetcher\ntype dlTask struct {\n    started bool\n    done    bool\n}\n\nfunc (t *dlTask) Start(f *http.Fetcher, req *base.Request, opts *base.Options) error {\n    if t.done || f == nil {\n        return errors.New(\"task finished: build a new fetcher\")\n    }\n    if !t.started {\n        if err := f.Resolve(req, opts); err != nil {\n            return err\n        }\n        t.started = true\n    }\n    return f.Start()\n}","typeGuard":null,"tryCatchPattern":"if err := fetcher.Start(); err != nil {\n    if strings.Contains(err.Error(), \"cannot start in current state\") {\n        // idle: resolve first; done: make a new fetcher\n        if err := fetcher.Resolve(req, opts); err != nil {\n            return err\n        }\n        return fetcher.Start()\n    }\n    return err\n}","preventionTips":["Internalize the lifecycle: Resolve -> Start -> Wait; Pause/Start for pause-resume","Retry loops must construct a fresh Fetcher per attempt","Do not share one Fetcher between concurrent task runners"],"tags":["state-machine","downloader","lifecycle","fetcher"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}