microsoft/garnet · error · InvalidOperationException

Failed to take CPR snapshot of BfTree.

Error message

Failed to take CPR snapshot of BfTree.

What it means

Thrown by CprSnapshotByPtr when the native bftree_cpr_snapshot returns non-zero. Per the P/Invoke docs the native call returns 0 on success and -1 on panic or invalid/empty path. Since the managed layer pre-validates an empty path, a non-zero result here most often indicates a native panic, an IO failure writing the snapshot file, or an invalid/unwritable destination directory. It is an InvalidOperationException because all arguments passed managed validation.

Source

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

        /// <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.
        /// Required if the recovered tree will be snapshotted later (flush/checkpoint/migration).</param>
        /// <param name="storageBackend">Storage backend of the recovered tree (for managed tracking).</param>
        public static BfTreeService RecoverFromCprSnapshot(
            string recoveryPath,
            bool enableSnapshots,
            StorageBackendType storageBackend)
        {
            if (string.IsNullOrEmpty(recoveryPath))
                throw new ArgumentException("recoveryPath is required.", nameof(recoveryPath));

View on GitHub (pinned to 951b0fc683)

Solutions

  1. Verify the snapshot destination directory exists and is writable by the process user before flushing.
  2. Use an absolute path for the snapshot; log the resolved path on failure.
  3. Check available disk space and fail fast if below a threshold.
  4. Hold the caller's per-tree serialization claim (documented requirement) so concurrent snapshots cannot race.
  5. On failure, inspect native logs/diagnostics; if transient (IO), retry the flush once.

Example fix

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

// after
Directory.CreateDirectory(Path.GetDirectoryName(snapPath)!);
try {
    BfTreeService.CprSnapshotByPtr(handle, snapPath);
} catch (InvalidOperationException ex) {
    logger.LogError(ex, "CPR snapshot failed for {Path}", snapPath);
    throw;
}
Defensive patterns

Strategy: try-catch

Validate before calling

var dir = Path.GetDirectoryName(snapshotPath);
if (string.IsNullOrEmpty(dir) || !Directory.Exists(dir))
    throw new InvalidOperationException($"Snapshot directory does not exist: {dir}");
if (!HasWriteAccess(dir))
    throw new UnauthorizedAccessException($"Cannot write to snapshot dir: {dir}");

Type guard

static bool CanSnapshot(nint handle, string path) =>
    handle != nint.Zero && !string.IsNullOrWhiteSpace(path) &&
    Directory.Exists(Path.GetDirectoryName(path));

Try / catch

try { BfTreeService.CprSnapshotByPtr(handle, snapshotPath); }
catch (InvalidOperationException ex)
{
    logger.LogError(ex, "CPR snapshot failed for {Path}", snapshotPath);
    // retry once for transient IO, else rethrow
    throw;
}

Prevention

When it happens

Trigger: The destination directory does not exist or is not writable; the disk is full; the snapshot path points to a relative location resolved against an unexpected CWD; or the native library panicked (e.g. corrupted internal tree state). Also possible if two concurrent snapshots of the same tree race and the loser's internal state is perturbed — though the documented behavior is a silent no-op, not a failure.

Common situations: Containerized deployment where the snapshot volume isn't mounted at the configured path; permissions mismatch (process user cannot write the snapshot dir); disk exhaustion during a large tree snapshot; relative path bug when the working directory changed.

Related errors


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