{"record":{"id":"10c3c12f727f803f","repo":"microsoft/garnet","slug":"bitop-diff-operation-requires-at-least-two-source","errorCode":null,"errorMessage":"BITOP DIFF operation requires at least two source bitmaps","messagePattern":"BITOP DIFF operation requires at least two source bitmaps","errorType":"exception","errorClass":"GarnetException","httpStatus":null,"severity":"error","filePath":"libs/server/Resp/Bitmap/BitmapManagerBitOp.cs","lineNumber":35,"sourceCode":"public static void InvokeBitOperationUnsafe(BitmapOperation op, int srcCount, byte** srcPtrs, byte** srcEndPtrs, byte* dstPtr, int dstLength, int shortestSrcLength)\n{\n    Debug.Assert(op is BitmapOperation.NOT or BitmapOperation.AND or BitmapOperation.OR or BitmapOperation.XOR or BitmapOperation.DIFF);\n    Debug.Assert(srcCount > 0);\n    Debug.Assert(dstLength >= 0 && shortestSrcLength >= 0);\n    Debug.Assert(dstLength >= shortestSrcLength);\n\n    if (srcCount == 1)\n    {\n        if (op == BitmapOperation.DIFF) throw new GarnetException(\"BITOP DIFF operation requires at least two source bitmaps\");\n\n        var srcBitmap = new ReadOnlySpan<byte>(srcPtrs[0], checked((int)(srcEndPtrs[0] - srcPtrs[0])));\n        var dstBitmap = new Span<byte>(dstPtr, dstLength);\n\n        if (op == BitmapOperation.NOT)\n        {\n            TensorPrimitives.OnesComplement(srcBitmap, dstBitmap);\n        }\n        else\n        {\n            srcBitmap.CopyTo(dstBitmap);\n        }\n    }","sourceCodeStart":null,"sourceCodeEnd":null,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/server/Resp/Bitmap/BitmapManagerBitop.cs#L35","documentation":"Thrown by InvokeBitOperationUnsafe when BITOP DIFF is invoked with exactly one resolved source bitmap (srcCount == 1). DIFF (and-not) is n-ary and needs >= 2 operands; with one source it is undefined, so Garnet rejects it. The command parser already rejects BITOP DIFF with fewer than 3 tokens (dest + <2 sources), but that check counts arguments, not existing keys - so the throw fires when you supply >=2 source key names but only one actually exists in the store (keysFound == 1).","triggerScenarios":"Client sends 'BITOP DIFF dest src1 src2' where only src1 (or only src2) exists in the database; the storage layer resolves just one source bitmap and calls InvokeBitOperationUnsafe with srcCount == 1.","commonSituations":"Running DIFF against keys where one source was never written or has expired; a race where a source key is deleted between the arg-count check and resolution.","solutions":["Ensure at least two DIFF source keys exist before issuing the command (EXISTS src1 src2).","If operating on possibly-absent keys, pre-populate the missing sources (e.g., empty bitmaps) or choose a different op.","Handle the error at the client and retry once the second source is present."],"exampleFix":"# before\nBITOP DIFF dest src1 src2   # only src1 exists -> error\n# after\nSET src2 \"\"                 # ensure the second source exists (empty bitmap)\nBITOP DIFF dest src1 src2","handlingStrategy":"validation","validationCode":"// Before BITOP DIFF, ensure >= 2 of the named source keys exist\nif (op == \"DIFF\") {\n    var existing = await client.ExistsAsync(srcKeys);\n    if (existing < 2) return BadRequest(\"BITOP DIFF needs at least two existing source bitmaps\");\n}","typeGuard":null,"tryCatchPattern":"catch (GarnetException ex) when (ex.Message == \"BITOP DIFF operation requires at least two source bitmaps\") {\n    // One or more DIFF source keys were missing; create them or pick another op\n    logger.LogWarning(\"BITOP DIFF had too few existing sources\");\n    return RetryOrCreateSources();\n}","preventionTips":["Treat DIFF as requiring two present sources, not just two named args.","Seed expected source bitmaps before running DIFF against them."],"tags":["bitmap","bitop","validation","redis-error"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}