def test_decode_message_set_partial(): encoded = b''.join([ struct.pack('>q', 0), # Msg Offset struct.pack('>i', 18), # Msg Size struct.pack('>i', 1474775406), # CRC struct.pack('>bb', 0, 0), # Magic, flags struct.pack('>i', 2), # Length of key b'k1', # Key struct.pack('>i', 2), # Length of value b'v1', # Value struct.pack('>q', 1), # Msg Offset struct.pack('>i', 24), # Msg Size (larger than remaining MsgSet size) struct.pack('>i', -16383415), # CRC struct.pack('>bb', 0, 0), # Magic, flags struct.pack('>i', 2), # Length of key b'k2', # Key struct.pack('>i', 8), # Length of value b'ar', # Value (truncated) ]) msgs = MessageSet.decode(encoded, bytes_to_read=len(encoded)) assert len(msgs) == 2 msg1, msg2 = msgs returned_offset1, message1_size, decoded_message1 = msg1 returned_offset2, message2_size, decoded_message2 = msg2 assert returned_offset1 == 0 message1 = Message(b'v1', key=b'k1') message1.encode() assert decoded_message1 == message1 assert returned_offset2 is None assert message2_size is None assert decoded_message2 == PartialMessage()
def test_decode_message(): encoded = b''.join([ struct.pack('>i', -1427009701), # CRC struct.pack('>bb', 0, 0), # Magic, flags struct.pack('>i', 3), # Length of key b'key', # key struct.pack('>i', 4), # Length of value b'test', # value ]) decoded_message = Message.decode(encoded) msg = Message(b'test', key=b'key') msg.encode() # crc is recalculated during encoding assert decoded_message == msg
def drain_ready(self): """Compress batch to be ready for send""" memview = self._buffer.getbuffer() self._drain_waiter.set_result(None) if self._compression_type: _, compressor, attrs = self._COMPRESSORS[self._compression_type] msg = Message(compressor(memview[4:].tobytes()), attributes=attrs, magic=self._version_id) encoded = msg.encode() # if compressed message is longer than original # we should send it as is (not compressed) header_size = 16 # 4(all size) + 8(offset) + 4(compressed size) if len(encoded) + header_size < len(memview): # write compressed message set (with header) to buffer # using memory view (for avoid memory copying) memview[:4] = Int32.encode(len(encoded) + 12) memview[4:12] = Int64.encode(0) # offset 0 memview[12:16] = Int32.encode(len(encoded)) memview[16:16 + len(encoded)] = encoded self._buffer.seek(0) return # update batch size (first 4 bytes of buffer) memview[:4] = Int32.encode(self._buffer.tell() - 4) self._buffer.seek(0)
def _build(self): if self._closed: self._buffer.seek(0) return self._buffer self._closed = True memview = self._buffer.getbuffer() if self._compression_type: _, compressor, attrs = self._COMPRESSORS[self._compression_type] msg = Message(compressor(memview[4:].tobytes()), attributes=attrs, magic=self._magic) encoded = msg.encode() # if compressed message is longer than original # we should send it as is (not compressed) header_size = 16 # 4(all size) + 8(offset) + 4(compressed size) if len(encoded) + header_size < len(memview): # write compressed message set (with header) to buffer # using memory view (for avoid memory copying) memview[:4] = Int32.encode(len(encoded) + 12) memview[4:12] = Int64.encode(0) # offset 0 memview[12:16] = Int32.encode(len(encoded)) memview[16:16 + len(encoded)] = encoded memview.release() self._buffer.seek(16 + len(encoded)) self._buffer.truncate() self._buffer.seek(0) return self._buffer # update batch size (first 4 bytes of buffer) memview[:4] = Int32.encode(self._buffer.tell() - 4) self._buffer.seek(0) return self._buffer
def append(self, key, value, timestamp_ms): """Append message (key and value) to batch Returns: None if batch is full or asyncio.Future that will resolved when message is delivered """ if self._is_full(key, value): return None # `.encode()` is a weak method for some reason, so we need to save # reference before calling it. if self._version_id == 0: msg_inst = Message(value, key=key, magic=self._version_id) else: msg_inst = Message(value, key=key, magic=self._version_id, timestamp=timestamp_ms) encoded = msg_inst.encode() msg = Int64.encode(self._relative_offset) + Int32.encode(len(encoded)) msg += encoded self._buffer.write(msg) future = asyncio.Future(loop=self._loop) self._msg_futures.append(future) self._relative_offset += 1 return future
def test_encode_message_v0(): message = Message(b'test', key=b'key') encoded = message.encode() expect = b''.join([ struct.pack('>i', -1427009701), # CRC struct.pack('>bb', 0, 0), # Magic, flags struct.pack('>i', 3), # Length of key b'key', # key struct.pack('>i', 4), # Length of value b'test', # value ]) assert encoded == expect
def test_encode_message_v1(): message = Message(b'test', key=b'key', magic=1, timestamp=1234) encoded = message.encode() expect = b''.join([ struct.pack('>i', 1331087195), # CRC struct.pack('>bb', 1, 0), # Magic, flags struct.pack('>q', 1234), # Timestamp struct.pack('>i', 3), # Length of key b'key', # key struct.pack('>i', 4), # Length of value b'test', # value ]) assert encoded == expect
def test_decode_message_set(): encoded = b''.join([ struct.pack('>q', 0), # MsgSet Offset struct.pack('>i', 18), # Msg Size struct.pack('>i', 1474775406), # CRC struct.pack('>bb', 0, 0), # Magic, flags struct.pack('>i', 2), # Length of key b'k1', # Key struct.pack('>i', 2), # Length of value b'v1', # Value struct.pack('>q', 1), # MsgSet Offset struct.pack('>i', 18), # Msg Size struct.pack('>i', -16383415), # CRC struct.pack('>bb', 0, 0), # Magic, flags struct.pack('>i', 2), # Length of key b'k2', # Key struct.pack('>i', 2), # Length of value b'v2', # Value ]) msgs = MessageSet.decode(encoded, bytes_to_read=len(encoded)) assert len(msgs) == 2 msg1, msg2 = msgs returned_offset1, message1_size, decoded_message1 = msg1 returned_offset2, message2_size, decoded_message2 = msg2 assert returned_offset1 == 0 message1 = Message(b'v1', key=b'k1') message1.encode() assert decoded_message1 == message1 assert returned_offset2 == 1 message2 = Message(b'v2', key=b'k2') message2.encode() assert decoded_message2 == message2
def append(self, *, timestamp, key, value): if not self._has_room_for(key, value): return 0 # `.encode()` is a weak method for some reason, so we need to save # reference before calling it. if self._magic == 0: msg_inst = Message(value, key=key, magic=self._magic) else: msg_inst = Message(value, key=key, magic=self._magic, timestamp=timestamp) encoded = msg_inst.encode() msg = Int64.encode(self._relative_offset) + Int32.encode(len(encoded)) msg += encoded actual_size = self._buffer.write(msg) self._relative_offset += 1 return actual_size
def test_send_without_response(self): """Imitate producer without acknowledge, in this case client produces messages and kafka does not send response, and we make sure that futures do not stuck in queue forever""" host, port = self.kafka_host, self.kafka_port conn = yield from create_conn(host, port, loop=self.loop) # prepare message msg = Message(b'foo') request = ProduceRequest(required_acks=0, timeout=10 * 1000, topics=[(b'foo', [(0, [(0, msg.encode())])])]) # produce messages without acknowledge for i in range(100): conn.send(request, expect_response=False) # make sure futures no stuck in queue self.assertEqual(len(conn._requests), 0) conn.close()
def drain_ready(self): """Compress batch to be ready for send""" memview = self._buffer.getbuffer() self._drain_waiter.set_result(None) if self._compression_type: _, compressor, attrs = self._COMPRESSORS[self._compression_type] msg = Message(compressor(memview[4:].tobytes()), attributes=attrs) encoded = msg.encode() # if compressed message is longer than original # we should send it as is (not compressed) header_size = 16 # 4(all size) + 8(offset) + 4(compressed size) if len(encoded) + header_size < len(memview): # write compressed message set (with header) to buffer # using memory view (for avoid memory copying) memview[:4] = Int32.encode(len(encoded) + 12) memview[4:12] = Int64.encode(0) # offset 0 memview[12:16] = Int32.encode(len(encoded)) memview[16:16+len(encoded)] = encoded self._buffer.seek(0) return # update batch size (first 4 bytes of buffer) memview[:4] = Int32.encode(self._buffer.tell()-4) self._buffer.seek(0)