{"record":{"id":"33ba6bdc56ae67f7","repo":"microsoft/garnet","slug":"failed-to-take-cpr-snapshot-of-bftree","errorCode":null,"errorMessage":"Failed to take CPR snapshot of BfTree.","messagePattern":"Failed to take CPR snapshot of BfTree\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"libs/native/bftree-garnet/BfTreeService.cs","lineNumber":539,"sourceCode":"        /// <param name=\"handle\">Native BfTree pointer.</param>\n        /// <param name=\"snapshotPath\">Destination path for the snapshot file. The snapshot\n        /// destination is supplied at call time; the caller supplies the path it\n        /// configured for this tree.</param>\n        public static void CprSnapshotByPtr(nint handle, string snapshotPath)\n        {\n            if (handle == nint.Zero)\n                throw new ArgumentException(\"Native handle is null.\", nameof(handle));\n            if (string.IsNullOrEmpty(snapshotPath))\n                throw new ArgumentException(\"Snapshot path is required.\", nameof(snapshotPath));\n\n            var snapBytes = Encoding.UTF8.GetBytes(snapshotPath);\n            int result;\n            fixed (byte* sp = snapBytes)\n            {\n                result = NativeBfTreeMethods.bftree_cpr_snapshot(handle, sp, snapBytes.Length);\n            }\n            if (result != 0)\n                throw new InvalidOperationException(\"Failed to take CPR snapshot of BfTree.\");\n        }\n\n        /// <summary>\n        /// Recover a BfTree from a CPR snapshot file. Unified API for disk-backed and\n        /// memory-backed (cache_only) trees — the storage backend is recorded in the\n        /// snapshot and inferred by the native library.\n        /// </summary>\n        /// <param name=\"recoveryPath\">Source CPR snapshot file path.</param>\n        /// <param name=\"enableSnapshots\">Enable CPR snapshot support on the recovered tree.\n        /// Required if the recovered tree will be snapshotted later (flush/checkpoint/migration).</param>\n        /// <param name=\"storageBackend\">Storage backend of the recovered tree (for managed tracking).</param>\n        public static BfTreeService RecoverFromCprSnapshot(\n            string recoveryPath,\n            bool enableSnapshots,\n            StorageBackendType storageBackend)\n        {\n            if (string.IsNullOrEmpty(recoveryPath))\n                throw new ArgumentException(\"recoveryPath is required.\", nameof(recoveryPath));","sourceCodeStart":521,"sourceCodeEnd":557,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/native/bftree-garnet/BfTreeService.cs#L521-L557","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nBfTreeService.CprSnapshotByPtr(handle, snapPath);\n\n// after\nDirectory.CreateDirectory(Path.GetDirectoryName(snapPath)!);\ntry {\n    BfTreeService.CprSnapshotByPtr(handle, snapPath);\n} catch (InvalidOperationException ex) {\n    logger.LogError(ex, \"CPR snapshot failed for {Path}\", snapPath);\n    throw;\n}","handlingStrategy":"try-catch","validationCode":"var dir = Path.GetDirectoryName(snapshotPath);\nif (string.IsNullOrEmpty(dir) || !Directory.Exists(dir))\n    throw new InvalidOperationException($\"Snapshot directory does not exist: {dir}\");\nif (!HasWriteAccess(dir))\n    throw new UnauthorizedAccessException($\"Cannot write to snapshot dir: {dir}\");","typeGuard":"static bool CanSnapshot(nint handle, string path) =>\n    handle != nint.Zero && !string.IsNullOrWhiteSpace(path) &&\n    Directory.Exists(Path.GetDirectoryName(path));","tryCatchPattern":"try { BfTreeService.CprSnapshotByPtr(handle, snapshotPath); }\ncatch (InvalidOperationException ex)\n{\n    logger.LogError(ex, \"CPR snapshot failed for {Path}\", snapshotPath);\n    // retry once for transient IO, else rethrow\n    throw;\n}","preventionTips":["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."],"tags":["bftree","native-interop","snapshot","io","filesystem","panic"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}