GopeedLab/gopeed · error

blob source not found

Error message

blob source not found

What it means

Sentinel returned by Registry.getByID (registry.go:638-642) when the id parsed out of a blob URL has no entry in the sources map. The URL is well-formed for this registry, but the source it names no longer (or never did) exist. Note CreateOpener on a nil Registry also returns this error (registry.go:133-135).

Source

Thrown at internal/blob/registry.go:32

	"path"
	"strconv"
	"strings"
	"sync"
	"time"
)

const urlPathPrefix = "/__blob/"

const rangeSourceFailureLimit = 2

// unclaimedSourceTTL bounds how long a session-backed source may keep its
// engine alive without ever being claimed by a download task.
var unclaimedSourceTTL = 10 * time.Minute

var (
	ErrInvalidURL      = errors.New("invalid blob url")
	ErrInvalidOptions  = errors.New("invalid blob options")
	ErrSourceNotFound  = errors.New("blob source not found")
	ErrSourceRevoked   = errors.New("blob source revoked")
	ErrSourceClosed    = errors.New("blob source closed")
	ErrRangeNotAllowed = errors.New("blob range not allowed")
)

type SessionRef interface {
	Retain()
	Release()
}

type OpenRequest struct {
	Offset int64
	End    int64
}

type OpenFunc func(ctx context.Context, req OpenRequest) (io.ReadCloser, error)

type CreateOptions struct {

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Treat this error as terminal for the source: re-create the source (CreateOpener/CreateBlob) and re-create/patch the task with the new URL
  2. Call Downloader-level create promptly after creating the source so it gets Acquired before the unclaimed TTL fires
  3. Pair every Acquire with exactly one Release and remember the first final Release deletes the source
  4. On shutdown, stop issuing blob operations before calling Registry.Close

Example fix

// before
if err := registry.Acquire(blobURL); err != nil { log.Fatal(err) } // crashes after restart

// after
if err := registry.Acquire(blobURL); err != nil {
    if errors.Is(err, blob.ErrSourceNotFound) {
        blobURL, err = registry.CreateOpener(open, opts) // recreate and rebind
        if err != nil { return err }
    } else {
        return err
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if !registry.IsURL(blobURL) { /* will be ErrInvalidURL, not NotFound */ }
// Nothing can prove existence cheaper than the call itself; just handle the error.

Try / catch

if err := registry.Acquire(blobURL); err != nil {
    if errors.Is(err, blob.ErrSourceNotFound) {
        // terminal: recreate source and rebind task URL
        blobURL, err = registry.CreateOpener(open, opts)
        if err != nil { return err }
        err = registry.Acquire(blobURL)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling Acquire/Release/Metadata/Revoke after the source was already deleted: the final Release deletes the source (registry.go:229), Revoke removes it, Registry.Close clears all sources, or a session-backed source expired via the 10-minute unclaimedSourceTTL (registry.go:27,177-185). Also using an rrId/blob URL against a fresh Registry after an app restart, or calling CreateOpener on a nil (*Registry)(nil) receiver.

Common situations: Re-resolving or resuming a task after process restart with a blob URL captured from the previous run; double-Release (first Release succeeds and deletes, second gets NotFound); delaying task creation more than 10 minutes after a session-backed source was created; racing with Registry.Close during shutdown.

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/f6743e1c7a7f0be4. Report an issue: GitHub.