{"record":{"id":"d4bd73c07ee0e55f","repo":"crowdsecurity/crowdsec","slug":"pull-already-in-progress","errorCode":null,"errorMessage":"pull already in progress","messagePattern":"pull already in progress","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/apiserver/apic.go","lineNumber":578,"sourceCode":"}\n\n// we receive a list of decisions and links for blocklist and we need to create a list of alerts :\n// one alert for \"community blocklist\"\n// one alert per list we're subscribed to\nfunc (a *apic) PullTop(ctx context.Context, forcePull bool) error {\n\tvar err error\n\n\thasPulledAllowlists := false\n\n\t// A mutex with TryLock would be a bit simpler\n\t// But go does not guarantee that TryLock will be able to acquire the lock even if it is available\n\tselect {\n\tcase a.isPulling <- true:\n\t\tdefer func() {\n\t\t\t<-a.isPulling\n\t\t}()\n\tdefault:\n\t\treturn errors.New(\"pull already in progress\")\n\t}\n\n\tif !forcePull {\n\t\tif lastPullIsOld, err := a.CAPIPullIsOld(ctx); err != nil {\n\t\t\treturn err\n\t\t} else if !lastPullIsOld {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tlog.Debug(\"Acquiring lock for pullCAPI\")\n\n\terr = a.dbClient.AcquirePullCAPILock(ctx)\n\tif a.dbClient.IsLocked(err) {\n\t\tlog.Info(\"PullCAPI is already running, skipping\")\n\t\treturn nil\n\t}\n","sourceCodeStart":560,"sourceCodeEnd":596,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiserver/apic.go#L560-L596","documentation":"PullTop serializes CAPI pulls using a buffered channel (isPulling) of capacity 1 as a mutex. If a pull is already running, acquiring the slot falls into the default branch and returns this error instead of queueing a second concurrent pull.","triggerScenarios":"Calling PullTop (directly or via Pull / ManagementCmd) while a previous CAPI pull is still in flight; e.g. a manual cscli update triggering a pull while the periodic pull loop is running, or two overlapping pulls with forcePull.","commonSituations":"Slow CAPI downloads (large community blocklist, slow network) overlapping the next scheduled pull; crowdscaled deployments where a signal-triggered pull races the timer; tests reusing an APIC instance without cleanup.","solutions":["Wait and retry the pull after the current one finishes (backoff/retry loop on this error).","Increase the pull interval in config so pulls don't overlap.","Check for stuck pulls: if the first pull never completes (hung network), restart crowdsec; consider increasing HTTP timeouts."],"exampleFix":"// before\nif err := apic.PullTop(ctx, true); err != nil { return err }\n// after\nif err := apic.PullTop(ctx, true); err != nil {\n    if err.Error() == \"pull already in progress\" {\n        log.Info(\"CAPI pull already running, skipping\")\n        return nil\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":"// no pre-call validation possible; check an external in-progress flag if you manage one\nif pulling.Swap(true) { skip }","typeGuard":null,"tryCatchPattern":"err := apic.PullTop(ctx, false)\nif errors.Is(err, errPullInProgress) || strings.Contains(err.Error(), \"already in progress\") {\n    time.Sleep(time.Minute)\n    return apic.PullTop(ctx, false)\n}","preventionTips":["Keep pull intervals comfortably larger than worst-case pull duration.","Don't trigger manual pulls at the same cadence as the scheduled one.","Monitor pull duration/alert on pulls that never finish."],"tags":["capi","concurrency","pull"],"backgroundTag":"invalid-state-transition","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}