{"record":{"id":"9ad55099b6905813","repo":"hashicorp/terraform","slug":"error-reading-source-block-d-w","errorCode":null,"errorMessage":"error reading source block %d: %w","messagePattern":"error reading source block (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/oci/multipart_upload.go","lineNumber":212,"sourceCode":"func SplitSizeToOffsetsAndLimits(size int64) ([]int64, int64, error) {\n\tpartSize := DefaultFilePartSize\n\ttotalParts := (size + partSize - 1) / partSize\n\tif totalParts > MaxCount {\n\t\treturn nil, 0, fmt.Errorf(\"file exceeds maximum part count\")\n\t}\n\toffsets := make([]int64, totalParts)\n\tfor i := range offsets {\n\t\toffsets[i] = int64(i) * partSize\n\t}\n\treturn offsets, partSize, nil\n}\n\nfunc (ctx *objectStorageMultiPartUploadContext) uploadPartsWorker() {\n\tfor block := range ctx.sourceBlocks {\n\t\tbuffer := make([]byte, block.section.Size())\n\t\t_, err := block.section.Read(buffer)\n\t\tif err != nil {\n\t\t\tctx.errChan <- fmt.Errorf(\"error reading source block %d: %w\", block.blockNumber, err)\n\t\t\treturn\n\t\t}\n\t\ttmpLength := int64(len(buffer))\n\t\tsum := md5.Sum(buffer)\n\t\tuploadPartRequest := &objectstorage.UploadPartRequest{\n\t\t\tUploadId:       ctx.multipartUploadResponse.UploadId,\n\t\t\tObjectName:     ctx.multipartUploadResponse.Object,\n\t\t\tNamespaceName:  ctx.multipartUploadResponse.Namespace,\n\t\t\tBucketName:     ctx.multipartUploadResponse.Bucket,\n\t\t\tContentLength:  &tmpLength,\n\t\t\tUploadPartBody: io.NopCloser(bytes.NewReader(buffer)),\n\t\t\tUploadPartNum:  block.blockNumber,\n\t\t\tContentMD5:     common.String(base64.StdEncoding.EncodeToString(sum[:])),\n\t\t\tRequestMetadata: common.RequestMetadata{\n\t\t\t\tRetryPolicy: getDefaultRetryPolicy(),\n\t\t\t},\n\t\t}\n","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/oci/multipart_upload.go#L194-L230","documentation":"Raised inside uploadPartsWorker when reading a source block from its in-memory io.SectionReader fails. Each block is an io.SectionReader over a bytes.Reader built from the in-memory state payload (multipart_upload.go:182), so a read failure here is unexpected: it indicates the section was misconfigured or the underlying buffer changed/shrank under the worker. The error names the offending block number.","triggerScenarios":"block.section.Read(buffer) at multipart_upload.go:210 returns a non-nil, non-io.EOF error: the SectionReader is asked for more bytes than remain, or the underlying slice was mutated/replaced concurrently with the read (data race on MultipartUploadData.Data).","commonSituations":"Concurrent mutation of the Data slice while multipart upload workers are reading it (sharing the buffer across goroutines unsafely); a miscomputed offset/size in SplitSizeToOffsetsAndLimits producing a SectionReader beyond the buffer length; a custom integration reusing the Data buffer before upload completes.","solutions":["Treat MultipartUploadData.Data as immutable for the whole upload; never write to it after multiPartUploadImpl starts.","If you construct MultipartUploadData in custom code, pass a defensive copy so the caller cannot mutate it mid-upload.","Verify SplitSizeToOffsetsAndLimits offsets stay within len(Data); under a stock Terraform build this is guaranteed, so a read error suggests external mutation or memory corruption.","Run with the race detector (-race) in tests to catch concurrent buffer access."],"exampleFix":"// before: shared buffer mutated while workers read it\ndata := getStateBytes()\nmu := MultipartUploadData{client: c, Data: data}\ngo mutate(data)          // data race -> error reading source block\nmu.multiPartUploadImpl(ctx)\n// after: pass an immutable copy\noriginal := getStateBytes()\ndata := make([]byte, len(original))\ncopy(data, original)\nmu := MultipartUploadData{client: c, Data: data}\nmu.multiPartUploadImpl(ctx)","handlingStrategy":"retry","validationCode":"// Ensure Data is immutable for the upload duration; pass a copy when constructing:\nbuf := make([]byte, len(original))\ncopy(buf, original)\nmu := MultipartUploadData{client: c, Data: buf}\n// offsets guaranteed within len(buf) by SplitSizeToOffsetsAndLimits.","typeGuard":"// A read error here is almost always a data race; classify:\nif errors.Is(err, io.EOF) { /* section shorter than expected -> miscomputed offset */ }","tryCatchPattern":"if _, err := block.section.Read(buffer); err != nil {\n    ctx.errChan <- fmt.Errorf(\"error reading source block %d: %w\", block.blockNumber, err)\n    return\n}","preventionTips":["Treat MultipartUploadData.Data as immutable during upload.","Pass a defensive copy to avoid external mutation.","Run tests with -race to catch concurrent buffer access."],"tags":["oci","multipart-upload","io","concurrency","data-race","terraform-state"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}