microsoft/garnet · error · ArgumentException

Snapshot path is required.

Error message

Snapshot path is required.

What it means

Thrown by CprSnapshotByPtr when snapshotPath is null or empty. The native bftree_cpr_snapshot requires a non-empty UTF-8 destination path; an empty one would yield a native failure (it returns -1 on invalid/empty path), so the managed layer rejects it early with a clear ArgumentException instead.

Source

Thrown at libs/native/bftree-garnet/BfTreeService.cs:530

        /// TreeHandle but not the managed <see cref="BfTreeService"/> instance.
        ///
        /// <para><b>Caller contract:</b> this method does NOT self-serialize. bftree's internal
        /// <c>snapshot_in_progress</c> flag makes a <c>cpr_snapshot</c> that races another
        /// snapshot on the same tree <b>silently no-op</b> (no file written) while this method
        /// still returns success. Callers MUST hold external per-tree serialization (e.g.
        /// RangeIndex's per-tree snapshot claim) around this call so concurrent snapshots of the
        /// same handle cannot race.</para>
        /// </summary>
        /// <param name="handle">Native BfTree pointer.</param>
        /// <param name="snapshotPath">Destination path for the snapshot file. The snapshot
        /// destination is supplied at call time; the caller supplies the path it
        /// configured for this tree.</param>
        public static void CprSnapshotByPtr(nint handle, string snapshotPath)
        {
            if (handle == nint.Zero)
                throw new ArgumentException("Native handle is null.", nameof(handle));
            if (string.IsNullOrEmpty(snapshotPath))
                throw new ArgumentException("Snapshot path is required.", nameof(snapshotPath));

            var snapBytes = Encoding.UTF8.GetBytes(snapshotPath);
            int result;
            fixed (byte* sp = snapBytes)
            {
                result = NativeBfTreeMethods.bftree_cpr_snapshot(handle, sp, snapBytes.Length);
            }
            if (result != 0)
                throw new InvalidOperationException("Failed to take CPR snapshot of BfTree.");
        }

        /// <summary>
        /// Recover a BfTree from a CPR snapshot file. Unified API for disk-backed and
        /// memory-backed (cache_only) trees — the storage backend is recorded in the
        /// snapshot and inferred by the native library.
        /// </summary>
        /// <param name="recoveryPath">Source CPR snapshot file path.</param>
        /// <param name="enableSnapshots">Enable CPR snapshot support on the recovered tree.

View on GitHub (pinned to 951b0fc683)

Solutions

  1. Ensure every tree that will be snapshotted has a non-empty snapshot path configured at creation.
  2. Validate snapshotPath with !string.IsNullOrWhiteSpace(path) before calling.
  3. Resolve the path from a central config and fail fast at startup if it is unset.
  4. If the path is optional for this tree, skip the snapshot call rather than pass an empty string.

Example fix

// before
BfTreeService.CprSnapshotByPtr(handle, configuredPath);

// after
if (!string.IsNullOrWhiteSpace(configuredPath))
    BfTreeService.CprSnapshotByPtr(handle, configuredPath);
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(snapshotPath))
    throw new ArgumentException("Snapshot path is required.", nameof(snapshotPath));

Type guard

static bool IsValidSnapshotPath(string path) => !string.IsNullOrWhiteSpace(path) && Path.IsPathRooted(path);

Try / catch

try { BfTreeService.CprSnapshotByPtr(handle, snapshotPath); }
catch (ArgumentException ex) when (ex.ParamName == nameof(snapshotPath)) { /* log config error */ }

Prevention

When it happens

Trigger: Calling CprSnapshotByPtr(handle, null) or CprSnapshotByPtr(handle, ""), e.g. when the per-tree snapshot destination path was never configured or was read from a missing config key.

Common situations: A new tree created without a configured snapshot directory; a config migration that left the snapshot path blank; whitespace-only paths are NOT caught here (only null/empty), so a path of spaces would pass this guard but fail natively.

Related errors


AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13). Data as JSON: /api/errors/e41f2cf5d51cfe1a. Report an issue: GitHub.