v2rayA/v2rayA · error

ListSet: failed to clear old servers of subscription %d: %w

Error message

ListSet: failed to clear old servers of subscription %d: %w

What it means

ListSet wraps the error from DELETE FROM servers WHERE type='subscription_server'. This delete of the subscription's old servers runs after outbound_connections are cleared (which satisfied the FK); a failure here aborts ListSet before inserting the new server rows. The %w cause carries the real DB error.

Source

Thrown at service/db/listOp.go:79

		if err != nil {
			return err
		}
		rows, _ := result.RowsAffected()
		if rows == 0 {
			return fmt.Errorf("ListSet: subscription at index %d not found", index)
		}

		// Update servers within this subscription.
		// Clean up outbound_connections first to satisfy foreign key constraint;
		// otherwise the delete fails and the insert below duplicates the list.
		if _, err := db.Exec(`
			DELETE FROM outbound_connections
			WHERE server_id IN (SELECT id FROM servers WHERE type = 'subscription_server' AND sub_id = ?)
		`, subID); err != nil {
			return fmt.Errorf("ListSet: failed to clear outbound connections of subscription %d: %w", index, err)
		}
		if _, err := db.Exec("DELETE FROM servers WHERE type = 'subscription_server' AND sub_id = ?", subID); err != nil {
			return fmt.Errorf("ListSet: failed to clear old servers of subscription %d: %w", index, err)
		}

		servers := parsed.Get("servers").Array()
		for j, s := range servers {
			_, err := db.Exec(
				"INSERT INTO servers (type, sub_id, config_json, sort) VALUES ('subscription_server', ?, ?, ?)",
				subID, s.Raw, j,
			)
			if err != nil {
				return fmt.Errorf("ListSet: failed to update subscription server %d/%d: %w", index, j, err)
			}
		}
		return nil

	default:
		return fmt.Errorf("ListSet: unsupported bucket/key: %s/%s", bucket, key)
	}
}

View on GitHub (pinned to 71e5442fc5)

Solutions

  1. Read the wrapped cause to identify the driver-level failure
  2. Verify the servers table schema (columns type, sub_id, config_json, sort) exists
  3. Eliminate concurrent writers or use a proper transaction/locking mode
  4. Retry after DB access is restored

Example fix

// before
if _, err := db.Exec("DELETE FROM servers WHERE type = 'subscription_server' AND sub_id = ?", subID); err != nil { ... }
// after
// same call, but ensure single-writer access / WAL mode: PRAGMA journal_mode=WAL; then retry ListSet
Defensive patterns

Strategy: try-catch

Validate before calling

var ok int
db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='servers'").Scan(&ok)
if ok == 0 { return errors.New("servers table missing; run migrations") }

Try / catch

err := ListSet("touch", "servers", val)
if err != nil {
    if errors.Is(err, sql.ErrConnDone) || strings.Contains(err.Error(), "locked") { time.Sleep(retryDelay); retry() }
    return err
}

Prevention

When it happens

Trigger: ListSet on touch/servers or touch/subscriptions where the DELETE FROM servers for the given subID fails: locked DB, missing servers table, connection error.

Common situations: Concurrent writers locking the SQLite file; migration never created the servers table; DB file corruption or read-only filesystem; expired/closed connection in db.Exec.

Related errors


AI-assisted analysis of v2rayA/v2rayA@71e5442fc5 (2026-09-05). Data as JSON: /api/errors/fe4e2183c62e57b4. Report an issue: GitHub.