{"record":{"id":"0bb52472e50fa86a","repo":"hcengineering/platform","slug":"upload-already-terminated-or-completed","errorCode":null,"errorMessage":"upload already terminated or completed","messagePattern":"upload already terminated or completed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"foundations/stream/internal/pkg/mediaconvert/multipart.go","lineNumber":82,"sourceCode":"\n\tlogger := log.FromContext(ctx).With(zap.String(\"multipart\", \"upload\"), zap.String(\"uploadID\", uploadID))\n\n\treturn &MultipartUpload{\n\t\tlogger:      logger,\n\t\tbuffer:      bytes.NewBuffer(nil),\n\t\tinfo:        info,\n\t\tstorage:     multipartStorage,\n\t\tobjectName:  objectName,\n\t\tuploadID:    uploadID,\n\t\tparts:       make([]storage.MultipartPart, 0),\n\t\tnextPartNum: 1,\n\t}, nil\n}\n\n// Write writes chunk of data to the storage\nfunc (w *MultipartUpload) Write(ctx context.Context, data []byte) error {\n\tif w.terminated || w.completed {\n\t\treturn errors.New(\"upload already terminated or completed\")\n\t}\n\n\tif err := ctx.Err(); err != nil {\n\t\treturn err\n\t}\n\n\t_, err := w.buffer.Write(data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to write to buffer\")\n\t}\n\tw.bytesWritten += int64(len(data))\n\n\t// flush parts of at least minPartSize\n\tfor w.buffer.Len() >= minPartSize {\n\t\tpartNum := w.nextPartNum\n\t\tpartData := w.buffer.Next(minPartSize)\n\n\t\tif err := ctx.Err(); err != nil {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/stream/internal/pkg/mediaconvert/multipart.go#L64-L100","documentation":"MultipartUpload.Write refuses new data once the upload has been terminated or completed. The struct guards with terminated/completed flags so that writes after lifecycle end fail fast instead of corrupting state or sending parts to storage after finalization.","triggerScenarios":"Calling Write after Complete() finished the upload; calling Write after Terminate()/Abort(); a retry loop continuing to push chunks after a concurrent goroutine completed or terminated the upload (data race on the flags).","commonSituations":"Retry logic that doesn't check upload state between attempts; concurrent writers sharing one MultipartUpload without synchronization; resuming a finished tus/stream upload and writing trailing bytes.","solutions":["Check and respect the upload lifecycle: stop writing once Complete/Terminate was issued","Serialize access: ensure only one goroutine writes to a given MultipartUpload, or guard calls with a mutex","Fix retry logic to create a new MultipartUpload (or resume properly) instead of reusing a finished one","Log/inspect the code path that called Write after completion — it usually indicates a duplicated callback"],"exampleFix":"// before\nawait upload.Write(ctx, chunk) // after upload.Complete(ctx)\n// after\nif err := ctx.Err(); err == nil && !upload.IsFinished() {\n    err = upload.Write(ctx, chunk)\n}","handlingStrategy":"validation","validationCode":"type finished interface{ IsFinished() bool }\nfunc canWrite(u *MultipartUpload) bool {\n    return u != nil && !u.terminated && !u.completed\n}","typeGuard":"func writable(u *MultipartUpload) bool {\n    return u != nil && !u.terminated && !u.completed\n}","tryCatchPattern":"if err := upload.Write(ctx, chunk); err != nil {\n    if err.Error() == \"upload already terminated or completed\" {\n        return ErrUploadClosed // stop retrying on this handle\n    }\n    return err\n}","preventionTips":["Guard state flags with a mutex if the upload is shared across goroutines","Stop the producer pipeline when Complete/Terminate is issued","Don't reuse a MultipartUpload handle across retry attempts","Log lifecycle transitions to spot stray writers"],"tags":["upload","multipart","lifecycle","go"],"backgroundTag":"upload-already-completed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}