benbjohnson/litestream · error

close database: %w

Error message

close database: %w

What it means

Wraps db.Close failure in Store.DisableDB. Fires when disabling a database: the DB was open and eligible for disable, but closing its resources (WAL monitoring, open handles) failed, so the disable operation is reported as failed instead of silently leaving it half-closed.

Source

Thrown at store.go:415

		return fmt.Errorf("open database: %w", err)
	}

	return nil
}

// DisableDB stops replication for a database.
func (s *Store) DisableDB(ctx context.Context, path string) error {
	db := s.FindDB(path)
	if db == nil {
		return fmt.Errorf("database not found: %s", path)
	}

	if !db.IsOpen() {
		return fmt.Errorf("database already disabled: %s", path)
	}

	if err := db.Close(ctx); err != nil {
		return fmt.Errorf("close database: %w", err)
	}

	return nil
}

// SyncDBResult holds the result of a sync operation.
type SyncDBResult struct {
	TXID           uint64
	ReplicatedTXID uint64
	Changed        bool
}

// SyncDB forces an immediate sync for a database. If wait is true, blocks
// until both WAL-to-LTX and LTX-to-remote sync complete. If wait is false,
// only performs the WAL-to-LTX sync and lets the replica monitor handle upload.
// Lock waits are context-aware: the timeout is honored while waiting for
// the database sync executor and the replica sync lock.
func (s *Store) SyncDB(ctx context.Context, path string, wait bool) (SyncDBResult, error) {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Inspect the wrapped cause to identify whether it was cancellation or a replica/executor error
  2. Retry DisableDB with a fresh, longer-timeout context after in-flight syncs drain
  3. Check replica/storage connectivity if the cause points at replica cleanup
  4. Ensure no other goroutine is calling SyncDB concurrently with the disable

Example fix

// before
ctx, cancel := context.WithTimeout(ctx, 1*time.Millisecond) // too short
if err := store.DisableDB(ctx, path); err != nil { ... }
// after
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := store.DisableDB(ctx, path); err != nil { ... }
Defensive patterns

Strategy: retry

Try / catch

if err := store.DisableDB(ctx, path); err != nil {
    var cause error
    errors.As(err, &cause)
    if errors.Is(cause, context.DeadlineExceeded) || errors.Is(cause, context.Canceled) {
        ctx2, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()
        return store.DisableDB(ctx2, path)
    }
    return err
}

Prevention

When it happens

Trigger: Store.DisableDB(ctx, path) where db.Close fails — sync executor or replica sync holding the lock, ctx cancelled mid-close, or an internal close error on the SQLite handle.

Common situations: Disabling a database during active replication traffic; a context deadline expiring while Close waits for the sync executor; shutdown racing a disable command.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/4f0250f51f2cd37d. Report an issue: GitHub.