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
- Verify the snapshot destination directory exists and is writable by the process user before flushing.
- Use an absolute path for the snapshot; log the resolved path on failure.
- Check available disk space and fail fast if below a threshold.
- Hold the caller's per-tree serialization claim (documented requirement) so concurrent snapshots cannot race.
- 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
- Create and chmod the snapshot directory before first flush.
- Hold the per-tree serialization claim to prevent concurrent snapshot races.
- Monitor disk space; fail fast below a threshold.
- Keep the previous good snapshot until the new one succeeds.
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
- Native handle is null.
- Snapshot path is required.
- filePath is required for disk-backed trees.
- Failed to create BfTree instance.
- bftree_scan_with_count returned a null handle.
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/33ba6bdc56ae67f7.
Report an issue: GitHub.