{"record":{"id":"d786efbd94627eb2","repo":"microsoft/garnet","slug":"inplacewriter-iheapobject-value-is-not-supported","errorCode":null,"errorMessage":"InPlaceWriter(IHeapObject value) is not supported in this ISessionFunctions implementation","messagePattern":"InPlaceWriter\\(IHeapObject value\\) is not supported in this ISessionFunctions implementation","errorType":"exception","errorClass":"NotImplementedException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/ClientSession/NoOpSessionFunctions.cs","lineNumber":35,"sourceCode":"    /// <see cref=\"InPlaceWriter{TSourceLogRecord}(ref LogRecord, ref TInput, in TSourceLogRecord, ref TOutput, ref UpsertInfo)\"/>, and\n    /// methods are implemented to allow for copy of log records via Upsert, but no other methods are implemented.\n    /// </remarks>\n    /// <typeparam name=\"TInput\"></typeparam>\n    /// <typeparam name=\"TOutput\"></typeparam>\n    /// <typeparam name=\"TContext\"></typeparam>\n    internal struct NoOpSessionFunctions<TInput, TOutput, TContext> : ISessionFunctions<TInput, TOutput, TContext>\n    {\n        public readonly bool InitialDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInfo) => true;\n\n        public readonly void PostInitialDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInfo) { }\n\n        public readonly bool InPlaceDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInfo) => true;\n\n        public readonly bool InPlaceWriter(ref LogRecord logRecord, ref TInput input, ReadOnlySpan<byte> srcValue, ref TOutput output, ref UpsertInfo upsertInfo)\n            => throw new NotImplementedException(\"InPlaceWriter(ReadOnlySpan<byte> value) is not supported in this ISessionFunctions implementation\");\n\n        public readonly bool InPlaceWriter(ref LogRecord logRecord, ref TInput input, IHeapObject srcValue, ref TOutput output, ref UpsertInfo upsertInfo)\n            => throw new NotImplementedException(\"InPlaceWriter(IHeapObject value) is not supported in this ISessionFunctions implementation\");\n\n        public readonly bool InPlaceWriter<TSourceLogRecord>(ref LogRecord dstLogRecord, ref TInput input, in TSourceLogRecord inputLogRecord, ref TOutput output, ref UpsertInfo upsertInfo)\n            where TSourceLogRecord : ISourceLogRecord\n        {\n            // This includes ETag and Expiration\n            var sizeInfo = new RecordSizeInfo() { FieldInfo = GetUpsertFieldInfo(key: dstLogRecord, inputLogRecord, ref input) };\n            dstLogRecord.PopulateRecordSizeInfoForIPU(ref sizeInfo);\n            return dstLogRecord.TryCopyFrom(in inputLogRecord, in sizeInfo);\n        }\n\n        public readonly bool CopyUpdater<TSourceLogRecord>(in TSourceLogRecord srcLogRecord, ref LogRecord dstLogRecord, in RecordSizeInfo sizeInfo, ref TInput input, ref TOutput output, ref RMWInfo rmwInfo)\n            where TSourceLogRecord : ISourceLogRecord\n            => true;\n\n        public readonly bool PostCopyUpdater<TSourceLogRecord>(in TSourceLogRecord srcLogRecord, ref LogRecord dstLogRecord, in RecordSizeInfo sizeInfo, ref TInput input, ref TOutput output, ref RMWInfo rmwInfo)\n            where TSourceLogRecord : ISourceLogRecord\n            => true;\n","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/ClientSession/NoOpSessionFunctions.cs#L17-L53","documentation":"NoOpSessionFunctions is used internally during compaction and iteration. The IHeapObject overload of InPlaceWriter throws NotImplementedException because compaction copies records via the ISourceLogRecord overload (which calls TryCopyFrom), never via a heap-object value path. IHeapObject values (CLR objects stored via the object store) require special serialization handling that NoOpSessionFunctions does not provide for in-place writes.","triggerScenarios":"Tsavorite's internal compaction code calls InPlaceWriter(ref LogRecord, ref TInput, IHeapObject srcValue, ...) on a NoOpSessionFunctions session, which happens if a heap-object-valued record goes through the in-place update path during compaction rather than the record-copy path.","commonSituations":"Compacting a Tsavorite store that uses object-mode values (IHeapObject) and the compaction pipeline hits the heap-object in-place writer path; a framework regression in how object values are handled during copy-forward compaction.","solutions":["Report as a Tsavorite framework bug if encountered during standard compaction — the code should route object values through the record-copy path.","If implementing custom ISessionFunctions, implement the IHeapObject InPlaceWriter overload even if you primarily use span-based values.","As a workaround, check if the issue reproduces with non-object (span-based) values to isolate whether it is object-store specific."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try\n{\n    store.Log.ShiftHeadAddressForCompaction(targetAddress);\n}\ncatch (NotImplementedException ex) when (ex.Message.Contains(\"IHeapObject\"))\n{\n    logger.LogCritical(\"Compaction hit unsupported IHeapObject InPlaceWriter path\");\n    throw;\n}","preventionTips":["This is a framework-internal error during compaction of object-mode values — report upstream.","Test compaction with your actual data types (including IHeapObject values) before production deployment.","Implement all IHeapObject overloads in custom ISessionFunctions implementations."],"tags":["tsavorite","compaction","session-functions","inplace-writer","heap-object","not-implemented","internal","csharp"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}