microsoft/FASTER · error · InvalidOperationException

AsyncReadRecordObjectsToMemory invalid for…

Error message

AsyncReadRecordObjectsToMemory invalid for BlittableAllocator

What it means

AsyncReadRecordObjectsToMemory exists to hydrate object references from the object log; BlittableAllocator stores fixed-size binary records and has no per-record objects, so it deliberately does not implement this operation and throws InvalidOperationException. Calling it is always a caller mistake - the wrong allocator path was used.

Solutions

  1. Use the normal ReadAsync API; for blittable allocators records are returned directly without object hydration.
  2. If you need object-log semantics, configure Key/Value as classes so the ObjectAllocator (not BlittableAllocator) is used.
  3. Remove/branch code paths that call the objects-to-memory read variant when IDevice/log is blittable.

Example fix

// before
// blittable store (struct Key/Value)
store.Log.AsyncReadRecordObjectsToMemory(addr, len, callback, ctx, result);

// after
var (status, output) = (await session.ReadAsync(key)).Complete(); // standard read path for blittable records
Defensive patterns

Strategy: validation

Validate before calling

if (typeof(Key).IsValueType || typeof(Value).IsValueType)
    throw new InvalidOperationException("Blittable store: use ReadAsync, not object-hydration reads");

Type guard

bool IsObjectAllocator<FasterLog>(FasterKV<K, V> store) => typeof(K).IsClass || typeof(V).IsClass; // object-hydration APIs only valid when types are classes

Try / catch

try { /* object read path */ }
catch (InvalidOperationException ex) when (ex.Message.Contains("invalid for BlittableAllocator")) { /* switch to standard ReadAsync */ }

Prevention

When it happens

Trigger: Invoking ReadAsync with the objects path / internal ReadRecordObjects variant on a FasterKV configured with blittable (non-ICanSerialize/non-object) Key/Value types.

Common situations: Generic or reflection-based helper code calling AsyncReadRecordObjectsToMemory regardless of allocator type; switching Key/Value from class to struct types (object to blittable) without updating read logic.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15). Data as JSON: /api/errors/df559233cc29eeb4. Report an issue: GitHub.

Appendix: source

Thrown at cs/src/core/Allocator/BlittableAllocator.cs:328

            ulong alignedSourceAddress, int destinationPageIndex, uint aligned_read_length,
            DeviceIOCompletionCallback callback, PageAsyncReadResult<TContext> asyncResult, IDevice device, IDevice objlogDevice)
        {
            device.ReadAsync(alignedSourceAddress, (IntPtr)pointers[destinationPageIndex],
                aligned_read_length, callback, asyncResult);
        }

        /// <summary>
        /// Invoked by users to obtain a record from disk. It uses sector aligned memory to read 
        /// the record efficiently into memory.
        /// </summary>
        /// <param name="fromLogical"></param>
        /// <param name="numBytes"></param>
        /// <param name="callback"></param>
        /// <param name="context"></param>
        /// <param name="result"></param>
        protected override void AsyncReadRecordObjectsToMemory(long fromLogical, int numBytes, DeviceIOCompletionCallback callback, AsyncIOContext<Key, Value> context, SectorAlignedMemory result = default)
        {
            throw new InvalidOperationException("AsyncReadRecordObjectsToMemory invalid for BlittableAllocator");
        }

        /// <summary>
        /// Retrieve objects from object log
        /// </summary>
        /// <param name="record"></param>
        /// <param name="ctx"></param>
        /// <returns></returns>
        protected override bool RetrievedFullRecord(byte* record, ref AsyncIOContext<Key, Value> ctx)
        {
            ctx.key = GetKey((long)record);
            ctx.value = GetValue((long)record);
            return true;
        }

        /// <summary>
        /// Whether KVS has keys to serialize/deserialize
        /// </summary>

View on GitHub (pinned to 321d872eab)