{"record":{"id":"7761c6ac669d0a6f","repo":"microsoft/garnet","slug":"invalid-compaction-type","errorCode":null,"errorMessage":"Invalid compaction type","messagePattern":"Invalid compaction type","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/Compaction/TsavoriteCompaction.cs","lineNumber":28,"sourceCode":"        where TStoreFunctions : IStoreFunctions\n        where TAllocator : IAllocator<TStoreFunctions>\n    {\n        /// <summary>\n        /// Compact the log until specified address, moving active records to the tail of the log. BeginAddress is shifted, but the physical log\n        /// is not deleted from disk. Caller is responsible for truncating the physical log on disk by taking a checkpoint or calling Log.Truncate\n        /// </summary>\n        /// <param name=\"cf\">User provided compaction functions (see <see cref=\"ICompactionFunctions\"/>).</param>\n        /// <param name=\"untilAddress\">Compact log until this address</param>\n        /// <param name=\"compactionType\">Compaction type (whether we lookup records or scan log for liveness checking)</param>\n        /// <returns>Address until which compaction was done</returns>\n        internal long Compact<TInput, TOutput, TContext, TCompactionFunctions>(TCompactionFunctions cf, long untilAddress, CompactionType compactionType)\n            where TCompactionFunctions : ICompactionFunctions\n        {\n            return compactionType switch\n            {\n                CompactionType.Scan => CompactScan<TInput, TOutput, TContext, TCompactionFunctions>(cf, untilAddress),\n                CompactionType.Lookup => CompactLookup<TInput, TOutput, TContext, TCompactionFunctions>(cf, untilAddress),\n                _ => throw new TsavoriteException(\"Invalid compaction type\"),\n            };\n        }\n\n        private long CompactLookup<TInput, TOutput, TContext, TCompactionFunctions>(TCompactionFunctions cf, long untilAddress)\n            where TCompactionFunctions : ICompactionFunctions\n        {\n            if (untilAddress > hlogBase.SafeReadOnlyAddress)\n                throw new TsavoriteException(\"Can compact only until Log.SafeReadOnlyAddress\");\n\n            using var storeSession = NewSession<ITsavoriteScanIterator, TInput, TOutput, TContext, NoOpSessionFunctions<TInput, TOutput, TContext>>(new());\n            var storebContext = storeSession.BasicContext;\n\n            using (var iter1 = Log.Scan(Log.BeginAddress, untilAddress))\n            {\n                long numPending = 0;\n                while (iter1.GetNext())\n                {\n                    var key = iter1.Key;","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Compaction/TsavoriteCompaction.cs#L10-L46","documentation":"Compact<TInput,TOutput,TContext,TCompactionFunctions> (TsavoriteCompaction.cs:21) dispatches on a CompactionType enum switch; the default arm throws 'Invalid compaction type'. The public enum (CompactionType.cs) defines only Scan and Lookup, so this arm is effectively unreachable through normal C# calls and exists as an exhaustive-switch guard. It fires only when the enum carries an out-of-range value (e.g. an invalid cast from an integer).","triggerScenarios":"Calling Compact with a CompactionType value that is neither Scan nor Lookup, e.g. session.BasicContext.Compact<...>((CompactionType)99, ...) or store.Log.Compact<...>(addr, (CompactionType)5). This requires an explicit invalid cast or deserialized enum value.","commonSituations":"A CompactionType is read from config/JSON/deserialization and an unrecognized token maps to an undefined numeric value; or a future library version adds a new enum member that an older binary does not handle.","solutions":["Validate the CompactionType with Enum.IsDefined before calling Compact and reject/normalize unknown values at the trust boundary.","Pass only CompactionType.Scan or CompactionType.Lookup from strongly-typed code; never cast an integer to CompactionType.","If the value originates from config, map the string token to a known enum member explicitly."],"exampleFix":"// before\nvar t = (CompactionType)configValue;            // could be 99\nstore.Log.Compact<TInput, TOutput, TContext>(untilAddress, t); // may throw 'Invalid compaction type'\n\n// after\nvar t = Enum.IsDefined(typeof(CompactionType), configValue)\n    ? (CompactionType)configValue\n    : throw new ArgumentOutOfRangeException(nameof(configValue), $\"Unknown CompactionType {configValue}\");\nstore.Log.Compact<TInput, TOutput, TContext>(untilAddress, t);","handlingStrategy":"validation","validationCode":"// Validate the enum at the trust boundary before calling Compact.\nif (!Enum.IsDefined(typeof(CompactionType), compactionType))\n    throw new ArgumentOutOfRangeException(nameof(compactionType),\n        $\"Unknown CompactionType {(int)compactionType}; expected Scan or Lookup\");\nstore.Log.Compact<TInput, TOutput, TContext>(untilAddress, compactionType);","typeGuard":"static bool IsValid(CompactionType t) => Enum.IsDefined(typeof(CompactionType), t);\n// Guard: if (!IsValid(t)) reject before Compact.","tryCatchPattern":null,"preventionTips":["Never cast arbitrary integers to CompactionType; map config strings to known members explicitly.","Validate enums from config/deserialization with Enum.IsDefined at the boundary.","Pass only CompactionType.Scan or CompactionType.Lookup from strongly-typed code."],"tags":["tsavorite","compaction","enum","validation","csharp"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}