{"record":{"id":"cf0210cde122d472","repo":"can1357/oh-my-pi","slug":"rpc-chunk-sequence-mismatch-cf0210","errorCode":null,"errorMessage":"RPC chunk sequence mismatch","messagePattern":"RPC chunk sequence mismatch","errorType":"exception","errorClass":"RpcError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/client.py","lineNumber":197,"sourceCode":"        except (binascii.Error, ValueError) as exc:\n            raise RpcError(\"Invalid RPC chunk data\") from exc\n        if base64.b64encode(chunk).decode(\"ascii\") != data:\n            raise RpcError(\"Invalid RPC chunk data\")\n        if len(chunk) > _RPC_CHUNK_PAYLOAD_BYTES:\n            raise RpcError(\"RPC chunk payload exceeds the transport limit\")\n\n        if self._pending is None:\n            if index != 0:\n                raise RpcError(\"RPC chunk sequence must start at index 0\")\n            self._pending = _PendingRpcChunks(chunk_id, count, byte_length)\n        pending = self._pending\n        if (\n            pending.chunk_id != chunk_id\n            or pending.count != count\n            or pending.byte_length != byte_length\n            or pending.next_index != index\n        ):\n            raise RpcError(\"RPC chunk sequence mismatch\")\n        pending.chunks.append(chunk)\n        pending.received_bytes += len(chunk)\n        pending.next_index += 1\n        if pending.received_bytes > pending.byte_length:\n            raise RpcError(\"RPC chunk sequence exceeds its declared length\")\n        if pending.next_index < pending.count:\n            return None\n        if pending.received_bytes != pending.byte_length:\n            raise RpcError(\"RPC chunk sequence length mismatch\")\n\n        self._pending = None\n        try:\n            decoded = b\"\".join(pending.chunks).decode(\"utf-8\")\n            frame = json.loads(decoded)\n        except (UnicodeDecodeError, json.JSONDecodeError) as exc:\n            raise RpcError(\"Failed to decode reassembled RPC frame\") from exc\n        if not isinstance(frame, dict):\n            raise RpcError(\"RPC frame must be a JSON object\")","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/client.py#L179-L215","documentation":"A chunk arrived whose chunkId, count, byteLength, or index does not match the currently pending reassembly state. Each field must stay constant across the sequence and index must arrive in strict order; any deviation means two sequences interleaved, a sequence restarted, or a chunk was duplicated/reordered. The library rejects it to protect the integrity of the frame being reassembled.","triggerScenarios":"push() of an rpc_chunk whose chunkId differs from the pending sequence's id, whose count or byteLength differs from the values declared by chunk 0, or whose index != pending.next_index (duplicate, skipped, or reordered chunk).","commonSituations":"Two large messages sent concurrently with interleaved chunks over one channel; a sender retrying from index 0 after a failure without resetting the client state; duplicated frames from an at-least-once queue; sender computing byteLength differently per chunk (e.g. UTF-16 length vs byte length).","solutions":["Serialize chunked messages: send all chunks of one sequence back-to-back; never interleave two sequences on one client.","Make the sender deterministic: same chunkId, count, and byteLength for every chunk of a sequence.","Guard against duplicate delivery by tracking chunkIds already completed and dropping re-sent sequences.","Ensure byteLength is computed once from the full payload bytes (len(payload.encode('utf-8'))), not per chunk.","On mismatch, abort and request the sender retransmit the complete sequence."],"exampleFix":"// before\n# interleaving two messages\nsend_chunks(msg_a)\nfor i, c in enumerate(msg_b_chunks):\n    client.push(c)  # pushed before msg_a finished -> mismatch\n\n// after\nfinish_sequence(msg_a)          # push all msg_a chunks, await result\nfor c in msg_b_chunks:\n    client.push(c)              # then send msg_b contiguously","handlingStrategy":"validation","validationCode":"def assert_consistent(chunks: list[dict]) -> bool:\n    first = chunks[0]\n    key = (\"chunkId\", \"count\", \"byteLength\")\n    return all(\n        all(c.get(k) == first.get(k) for k in key) and c.get(\"index\") == i\n        for i, c in enumerate(chunks)\n    )","typeGuard":"def matches_pending(frame: dict, pending_chunk_id: str) -> bool:\n    return isinstance(frame, dict) and frame.get(\"chunkId\") == pending_chunk_id","tryCatchPattern":"try:\n    client.push(frame)\nexcept RpcError as exc:\n    if str(exc) == \"RPC chunk sequence mismatch\":\n        logger.error(\"sequence mismatch: got chunkId=%s index=%s\", frame.get(\"chunkId\"), frame.get(\"index\"))\n        client.reset_pending()  # or drop client state\n        request_retransmit(frame.get(\"chunkId\"))\n    else:\n        raise","preventionTips":["Never interleave two chunked messages on one client; queue complete sequences.","Compute chunkId/count/byteLength once per message and reuse for every chunk.","Deduplicate frames if your transport is at-least-once (track completed chunkIds)."],"tags":["rpc","chunking","concurrency","ordering"],"backgroundTag":"chunk-sequence-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}