{"record":{"id":"8010a79a81cbfeb2","repo":"microsoft/garnet","slug":"getupsertfieldinfo-readonlyspan-byte-value-is-no","errorCode":null,"errorMessage":"GetUpsertFieldInfo(ReadOnlySpan<byte> value) is not supported in this ISessionFunctions implementation","messagePattern":"GetUpsertFieldInfo\\(ReadOnlySpan<byte> 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":88,"sourceCode":"        public readonly void ReadCompletionCallback(ref DiskLogRecord diskLogRecord, ref TInput input, ref TOutput output, TContext ctx, Status status, RecordMetadata recordMetadata) { }\n\n        public readonly void RMWCompletionCallback(ref DiskLogRecord diskLogRecord, ref TInput input, ref TOutput output, TContext ctx, Status status, RecordMetadata recordMetadata) { }\n\n        public readonly RecordFieldInfo GetRMWModifiedFieldInfo<TSourceLogRecord>(in TSourceLogRecord srcLogRecord, ref TInput input)\n            where TSourceLogRecord : ISourceLogRecord\n             => throw new NotImplementedException(\"GetRMWModifiedFieldInfo is not supported in this ISessionFunctions implementation\");\n        public readonly RecordFieldInfo GetRMWInitialFieldInfo<TKey>(TKey key, ref TInput input)\n            where TKey : IKey\n#if NET9_0_OR_GREATER\n                , allows ref struct\n#endif\n            => throw new NotImplementedException(\"GetRMWInitialFieldInfo is not supported in this ISessionFunctions implementation\");\n        public readonly RecordFieldInfo GetUpsertFieldInfo<TKey>(TKey key, ReadOnlySpan<byte> value, ref TInput input)\n            where TKey : IKey\n#if NET9_0_OR_GREATER\n                , allows ref struct\n#endif\n            => throw new NotImplementedException(\"GetUpsertFieldInfo(ReadOnlySpan<byte> value) is not supported in this ISessionFunctions implementation\");\n        public readonly RecordFieldInfo GetUpsertFieldInfo<TKey>(TKey key, IHeapObject value, ref TInput input)\n            where TKey : IKey\n#if NET9_0_OR_GREATER\n                , allows ref struct\n#endif\n            => throw new NotImplementedException(\"IHeapObject value) is not supported in this ISessionFunctions implementation\");\n        public readonly RecordFieldInfo GetUpsertFieldInfo<TKey, TSourceLogRecord>(TKey key, in TSourceLogRecord inputLogRecord, ref TInput input)\n            where TKey : IKey\n#if NET9_0_OR_GREATER\n                , allows ref struct\n#endif\n            where TSourceLogRecord : ISourceLogRecord\n            => new() { KeySize = key.KeyBytes.Length, ValueSize = inputLogRecord.DataHeader.ValueIsObject ? ObjectIdMap.ObjectIdSize : inputLogRecord.ValueSpan.Length, ValueIsObject = inputLogRecord.DataHeader.ValueIsObject, ExtendedNamespaceSize = RecordNamespace.GetExtendedNamespaceSize(in key) };\n\n        /// <summary>\n        /// No reads during compaction\n        /// </summary>\n        public readonly bool Reader<TSourceLogRecord>(in TSourceLogRecord srcLogRecord, ref TInput input, ref TOutput dst, ref ReadInfo readInfo)","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/ClientSession/NoOpSessionFunctions.cs#L70-L106","documentation":"GetUpsertFieldInfo returns RecordFieldInfo (key size, value size, object flags) for sizing an upsert record. NoOpSessionFunctions only implements the ISourceLogRecord-based overload (line 95-101) because compaction copies records from source log records. The ReadOnlySpan<byte> overload throws NotImplementedException because compaction never upserts a raw span value — it always has a source record to copy from.","triggerScenarios":"Tsavorite's internal code calls GetUpsertFieldInfo(TKey, ReadOnlySpan<byte> value, ref TInput) on a NoOpSessionFunctions session during compaction, meaning a raw-value upsert path was entered instead of the record-copy path.","commonSituations":"Framework bug in compaction dispatch routing a span-value upsert through a NoOp session; custom compaction logic that calls Upsert with a raw span value during a context backed by NoOpSessionFunctions.","solutions":["Report as a Tsavorite framework bug — compaction should use the ISourceLogRecord overload of GetUpsertFieldInfo, not the span-value one.","If implementing custom ISessionFunctions, implement all GetUpsertFieldInfo overloads including the span-based one.","Check whether the error correlates with specific record types or sizes that trigger a non-copy compaction path."],"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(\"GetUpsertFieldInfo\"))\n{\n    logger.LogCritical(\"Compaction hit unsupported span-value GetUpsertFieldInfo path: {Message}\", ex.Message);\n    throw;\n}","preventionTips":["Framework-internal error during compaction — the span-value GetUpsertFieldInfo should not be called on NoOpSessionFunctions. Report upstream.","Implement the ReadOnlySpan<byte> GetUpsertFieldInfo overload in custom ISessionFunctions.","Test compaction with various value sizes to exercise different sizing paths."],"tags":["tsavorite","compaction","session-functions","upsert","field-info","not-implemented","internal","csharp"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}