hcengineering/platform · error
upload already terminated or completed
Error message
upload already terminated or completed
What it means
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.
Source
Thrown at foundations/stream/internal/pkg/mediaconvert/multipart.go:82
logger := log.FromContext(ctx).With(zap.String("multipart", "upload"), zap.String("uploadID", uploadID))
return &MultipartUpload{
logger: logger,
buffer: bytes.NewBuffer(nil),
info: info,
storage: multipartStorage,
objectName: objectName,
uploadID: uploadID,
parts: make([]storage.MultipartPart, 0),
nextPartNum: 1,
}, nil
}
// Write writes chunk of data to the storage
func (w *MultipartUpload) Write(ctx context.Context, data []byte) error {
if w.terminated || w.completed {
return errors.New("upload already terminated or completed")
}
if err := ctx.Err(); err != nil {
return err
}
_, err := w.buffer.Write(data)
if err != nil {
return errors.Wrap(err, "failed to write to buffer")
}
w.bytesWritten += int64(len(data))
// flush parts of at least minPartSize
for w.buffer.Len() >= minPartSize {
partNum := w.nextPartNum
partData := w.buffer.Next(minPartSize)
if err := ctx.Err(); err != nil {View on GitHub (pinned to 63e28dc964)
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
Example fix
// before
await upload.Write(ctx, chunk) // after upload.Complete(ctx)
// after
if err := ctx.Err(); err == nil && !upload.IsFinished() {
err = upload.Write(ctx, chunk)
} Defensive patterns
Strategy: validation
Validate before calling
type finished interface{ IsFinished() bool }
func canWrite(u *MultipartUpload) bool {
return u != nil && !u.terminated && !u.completed
} Type guard
func writable(u *MultipartUpload) bool {
return u != nil && !u.terminated && !u.completed
} Try / catch
if err := upload.Write(ctx, chunk); err != nil {
if err.Error() == "upload already terminated or completed" {
return ErrUploadClosed // stop retrying on this handle
}
return err
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- cannot complete terminated upload
- cannot complete upload with no parts
- Failed to initialize multipart upload
- Failed to complete multipart upload
- Failed to parse response for part ${partNumber}
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/0bb52472e50fa86a.
Report an issue: GitHub.