{"record":{"id":"927f4646fa490d7d","repo":"kopia/kopia","slug":"error-writing-blob","errorCode":null,"errorMessage":"error writing blob","messagePattern":"error writing blob","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/providervalidation/providervalidation.go","lineNumber":337,"sourceCode":"\t\tfor clock.Now().Before(c.deadline) {\n\t\t\tseed := rand.Int63() //nolint:gosec\n\t\t\tdata := c.dataFromSeed(seed, data0)\n\n\t\t\tid := c.prefix + blob.ID(hex.EncodeToString(data[0:16]))\n\n\t\t\tc.mu.Lock()\n\t\t\tc.blobSeeds[id] = seed\n\t\t\tc.blobIDs = append(c.blobIDs, id)\n\t\t\tc.mu.Unlock()\n\n\t\t\t// sleep for a short time so that readers can start getting the blob when it's still\n\t\t\t// not written.\n\t\t\tc.randomSleep()\n\n\t\t\tlog(ctx).Debugf(\"PutBlob worker %v writing %v (%v bytes)\", worker, id, len(data))\n\n\t\t\tif err := c.st.pickOne().PutBlob(ctx, id, gather.FromSlice(data), blob.PutOptions{}); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"error writing blob\")\n\t\t\t}\n\n\t\t\tc.mu.Lock()\n\t\t\tc.blobWritten[id] = true\n\t\t\tc.mu.Unlock()\n\n\t\t\tlog(ctx).Debugf(\"PutBlob worker %v finished writing %v (%v bytes)\", worker, id, len(data))\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\nfunc (c *concurrencyTest) randomSleep() {\n\ttime.Sleep(time.Duration(rand.Intn(int(500 * time.Millisecond)))) //nolint:gosec,mnd\n}\n\nfunc (c *concurrencyTest) pickBlob() (blob.ID, int64, bool) {","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/kopia/kopia/blob/82495e54b584c1ef6073c9e1be048f57f8aef078/internal/providervalidation/providervalidation.go#L319-L355","documentation":"Wrapper in the concurrent PutBlob worker of the provider validation stress phase: PutBlob failed while writing a generated blob to the storage under test; the write worker returns and the validation reports the backend as unreliable under concurrency.","triggerScenarios":"st.pickOne().PutBlob(ctx, id, gather.FromSlice(data), blob.PutOptions{}) returns a non-nil error during the concurrent write phase.","commonSituations":"Rate limiting (HTTP 429/503) from cloud storage under parallel load; expired credentials mid-test; adapter not safe for concurrent use.","solutions":["Read the wrapped cause — if it is a rate-limit error, reduce concurrency or add retries with backoff in the provider.","Verify credentials/quota are valid for the whole ConcurrencyTestDuration.","Ensure the provider implementation is goroutine-safe.","Enable provider-level retry for transient HTTP errors."],"exampleFix":"// before\nif err := c.st.pickOne().PutBlob(ctx, id, gather.FromSlice(data), blob.PutOptions{}); err != nil {\n    return errors.Wrap(err, \"error writing blob\")\n}\n// after\nerr := retry(3, func() error {\n    return c.st.pickOne().PutBlob(ctx, id, gather.FromSlice(data), blob.PutOptions{})\n})\nif err != nil {\n    return errors.Wrap(err, \"error writing blob\")\n}","handlingStrategy":"retry","validationCode":"if err := ctx.Err(); err != nil {\n    return err // fail fast if the test context is already cancelled\n}","typeGuard":null,"tryCatchPattern":"err := st.PutBlob(ctx, id, data, blob.PutOptions{})\nif err != nil {\n    if isTransient(err) { err = retryWithBackoff(3, put) }\n    if err != nil { return fmt.Errorf(\"error writing blob: %w\", err) }\n}","preventionTips":["Add transient-error retry (429/503/timeouts) in the provider layer.","Validate credentials and quota last the full test duration.","Avoid shared mutable state across goroutines in adapters.","Respect the context cancellation to avoid cascading write errors."],"tags":["blob-storage","write-error","concurrency"],"backgroundTag":"database-write-failed","analyzedSha":"82495e54b584c1ef6073c9e1be048f57f8aef078","analyzedAt":"2026-09-07T20:35:21.689Z","contentChangedAt":"2026-09-07T20:35:21.689Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}