vitessio/vitess · error

AbortBackup cannot be called on read-only backup

Error message

AbortBackup cannot be called on read-only backup

What it means

S3BackupHandle.AbortBackup deletes the backup (RemoveBackup) but is only valid on a handle opened for writing. If the handle was obtained in read-only mode (e.g. via StartBackup's read path or a handle opened to restore/read), the guard in s3.go:326 rejects the call outright because aborting would destroy a backup another process may be reading.

Source

Thrown at go/vt/mysqlctl/s3backupstorage/s3.go:326

// Wait is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) Wait() {
	bh.waitGroup.Wait()
}

// EndBackup is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) EndBackup(ctx context.Context) error {
	if bh.readOnly {
		return errors.New("EndBackup cannot be called on read-only backup")
	}
	bh.Wait()
	return bh.Error()
}

// AbortBackup is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) AbortBackup(ctx context.Context) error {
	if bh.readOnly {
		return errors.New("AbortBackup cannot be called on read-only backup")
	}
	return bh.bs.RemoveBackup(ctx, bh.dir, bh.name)
}

// ReadFile is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) ReadFile(ctx context.Context, filename string) (io.ReadCloser, error) {
	if !bh.readOnly {
		return nil, errors.New("ReadFile cannot be called on read-write backup")
	}
	object := objName(bh.dir, bh.name, filename)
	sendStats := bh.bs.params.Stats.Scope(stats.Operation("AWS:Request:Send"))
	out, err := (&timedS3Client{client: bh.s3Client, sendStats: sendStats}).GetObject(ctx, &s3.GetObjectInput{
		Bucket:               &bucket,
		Key:                  &object,
		SSECustomerAlgorithm: bh.bs.s3SSE.customerAlg,
		SSECustomerKey:       bh.bs.s3SSE.customerKey,
		SSECustomerKeyMD5:    bh.bs.s3SSE.customerMd5,
	})

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Only call AbortBackup on handles returned from StartBackup (write mode)
  2. For read-only handles, just call Close() and use RemoveBackup on the storage engine if deletion is truly intended
  3. Re-open the handle in write mode via the storage engine's API if you legitimately own the backup and need to abort
  4. Check bh.readOnly (or your own tracking) before dispatching to AbortBackup

Example fix

// before
handle, _ := bs.StartBackup(ctx, dir, name)
_ = handle.AbortBackup(ctx) // wrong handle mode
// after
if !handle.ReadOnly() {
    _ = handle.AbortBackup(ctx)
} else {
    _ = handle.Close()
}
Defensive patterns

Strategy: validation

Validate before calling

if bh.ReadOnly() {
    // do not abort; close instead
    return bh.Close()
}
return bh.AbortBackup(ctx)

Type guard

func isWritableHandle(bh backupstorage.BackupHandle) bool {
    h, ok := bh.(*s3backupstorage.S3BackupHandle)
    return ok && !h.ReadOnly()
}

Try / catch

if err := bh.AbortBackup(ctx); err != nil {
    if strings.Contains(err.Error(), "cannot be called on read-only backup") {
        err = bh.Close() // correct operation for read-only handles
    }
    return err
}

Prevention

When it happens

Trigger: Calling AbortBackup on an *S3BackupHandle that was created with readOnly=true — e.g. grabbing a backup handle to inspect/read contents and then attempting to abort/clean it up instead of just closing it.

Common situations: Cleanup logic written against write-mode handles being reused for read-mode handles; a tool that lists backups, opens them read-only, then tries to abort partially-uploaded ones; confusing backupstorage.BackupHandle semantics between reader and writer roles.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/706dac666479f0b0. Report an issue: GitHub.