예제 #1
0
 def on_messages(self, messages: List[SequencedMessage]):
     byte_size = 0
     for message in messages:
         byte_size += message.size_bytes
     self._client_tokens += FlowControlRequest(
         allowed_bytes=-byte_size, allowed_messages=-len(messages)
     )
예제 #2
0
 def to_optional(self) -> Optional[FlowControlRequest]:
     allowed_messages = _clamp(self._request.allowed_messages)
     allowed_bytes = _clamp(self._request.allowed_bytes)
     if allowed_messages == 0 and allowed_bytes == 0:
         return None
     request = FlowControlRequest()
     request._pb.allowed_messages = allowed_messages
     request._pb.allowed_bytes = allowed_bytes
     return request
async def test_handle_reset(
    subscriber: SinglePartitionSingleSubscriber,
    underlying,
    transformer,
    ack_set_tracker,
):
    ack_called_queue = asyncio.Queue()
    ack_result_queue = asyncio.Queue()
    ack_set_tracker.ack.side_effect = make_queue_waiter(
        ack_called_queue, ack_result_queue
    )
    async with subscriber:
        message_1 = SequencedMessage(cursor=Cursor(offset=1), size_bytes=5)
        underlying.read.return_value = message_1
        read_1: Message = await subscriber.read()
        ack_set_tracker.track.assert_has_calls([call(1)])
        assert read_1.message_id == "1"
        assert read_1.ack_id == ack_id(0, 1)

        await subscriber.handle_reset()
        ack_set_tracker.clear_and_commit.assert_called_once()

        # Message ACKed after reset. Its flow control tokens are refilled
        # but offset not committed (verified below after message 2).
        read_1.ack()

        message_2 = SequencedMessage(cursor=Cursor(offset=2), size_bytes=10)
        underlying.read.return_value = message_2
        read_2: Message = await subscriber.read()
        ack_set_tracker.track.assert_has_calls([call(1), call(2)])
        assert read_2.message_id == "2"
        assert read_2.ack_id == ack_id(1, 2)
        read_2.ack()
        await ack_called_queue.get()
        await ack_result_queue.put(None)
        underlying.allow_flow.assert_has_calls(
            [
                call(FlowControlRequest(allowed_messages=1000, allowed_bytes=1000,)),
                call(FlowControlRequest(allowed_messages=1, allowed_bytes=5,)),
                call(FlowControlRequest(allowed_messages=1, allowed_bytes=10,)),
            ]
        )
        ack_set_tracker.ack.assert_has_calls([call(2)])
예제 #4
0
 async def __aenter__(self):
     await self._ack_set_tracker.__aenter__()
     await self._underlying.__aenter__()
     self._looper_future = asyncio.ensure_future(self._looper())
     await self._underlying.allow_flow(
         FlowControlRequest(
             allowed_messages=self._flow_control_settings.messages_outstanding,
             allowed_bytes=self._flow_control_settings.bytes_outstanding,
         )
     )
     return self
예제 #5
0
def test_add_remove():
    batcher = FlowControlBatcher()
    batcher.add(FlowControlRequest(allowed_bytes=10, allowed_messages=3))
    restart_1 = batcher.request_for_restart()
    assert restart_1.allowed_bytes == 10
    assert restart_1.allowed_messages == 3
    batcher.on_messages(
        [SequencedMessage(size_bytes=2),
         SequencedMessage(size_bytes=3)])
    restart_2 = batcher.request_for_restart()
    assert restart_2.allowed_bytes == 5
    assert restart_2.allowed_messages == 1
예제 #6
0
def test_restart_clears_send():
    batcher = FlowControlBatcher()
    batcher.add(FlowControlRequest(allowed_bytes=10, allowed_messages=3))
    assert batcher.should_expedite()
    to_send = batcher.release_pending_request()
    assert to_send.allowed_bytes == 10
    assert to_send.allowed_messages == 3
    restart_1 = batcher.request_for_restart()
    assert restart_1.allowed_bytes == 10
    assert restart_1.allowed_messages == 3
    assert not batcher.should_expedite()
    assert batcher.release_pending_request() is None
async def test_out_of_order_receipt_failure(
    subscriber: Subscriber,
    default_connection,
    initial_request,
    asyncio_sleep,
    sleep_queues,
):
    write_called_queue = asyncio.Queue()
    write_result_queue = asyncio.Queue()
    flow = FlowControlRequest(allowed_messages=100, allowed_bytes=100)
    message_1 = SequencedMessage(cursor=Cursor(offset=3), size_bytes=5)
    message_2 = SequencedMessage(cursor=Cursor(offset=5), size_bytes=10)
    default_connection.write.side_effect = make_queue_waiter(
        write_called_queue, write_result_queue
    )
    read_called_queue = asyncio.Queue()
    read_result_queue = asyncio.Queue()
    default_connection.read.side_effect = make_queue_waiter(
        read_called_queue, read_result_queue
    )
    read_result_queue.put_nowait(SubscribeResponse(initial={}))
    write_result_queue.put_nowait(None)
    async with subscriber:
        # Set up connection
        await write_called_queue.get()
        await read_called_queue.get()
        default_connection.write.assert_has_calls([call(initial_request)])

        # Send tokens.
        flow_fut = asyncio.ensure_future(subscriber.allow_flow(flow))
        assert not flow_fut.done()

        # Handle the inline write since initial tokens are 100% of outstanding.
        await write_called_queue.get()
        await write_result_queue.put(None)
        await flow_fut
        default_connection.write.assert_has_calls(
            [call(initial_request), call(as_request(flow))]
        )

        read_fut = asyncio.ensure_future(subscriber.read())

        # Send out of order messages to the subscriber.
        await read_result_queue.put(as_response([message_2, message_1]))
        # Wait for the next read call
        await read_called_queue.get()

        try:
            await read_fut
            assert False
        except GoogleAPICallError as e:
            assert e.grpc_status_code == StatusCode.FAILED_PRECONDITION
        pass
예제 #8
0
 async def _handle_ack(self, message: requests.AckRequest):
     offset = int(message.ack_id)
     await self._underlying.allow_flow(
         FlowControlRequest(
             allowed_messages=1,
             allowed_bytes=self._messages_by_offset[offset].size_bytes,
         )
     )
     del self._messages_by_offset[offset]
     try:
         await self._ack_set_tracker.ack(offset)
     except GoogleAPICallError as e:
         self.fail(e)
 def _handle_ack(self, message: requests.AckRequest):
     flow_control = FlowControlRequest()
     flow_control._pb.allowed_messages = 1
     flow_control._pb.allowed_bytes = self._messages_by_ack_id[
         message.ack_id].size_bytes
     self._underlying.allow_flow(flow_control)
     del self._messages_by_ack_id[message.ack_id]
     # Always refill flow control tokens, but do not commit offsets from outdated generations.
     ack_id = _AckId.parse(message.ack_id)
     if ack_id.generation == self._ack_generation_id:
         try:
             self._ack_set_tracker.ack(ack_id.offset)
         except GoogleAPICallError as e:
             self.fail(e)
예제 #10
0
    async def stop_processing(self, error: GoogleAPICallError):
        await self._stop_loopers()
        if is_reset_signal(error):
            # Discard undelivered messages and refill flow control tokens.
            while not self._message_queue.empty():
                batch: List[SequencedMessage.meta.pb] = self._message_queue.get_nowait()
                allowed_bytes = sum(message.size_bytes for message in batch)
                self._outstanding_flow_control.add(
                    FlowControlRequest(
                        allowed_messages=len(batch), allowed_bytes=allowed_bytes,
                    )
                )

            await self._reset_handler.handle_reset()
            self._last_received_offset = None
 async def reinitialize(
     self,
     connection: Connection[SubscribeRequest, SubscribeResponse],
     last_error: Optional[GoogleAPICallError],
 ):
     self._reinitializing = True
     await self._stop_loopers()
     if last_error and is_reset_signal(last_error):
         # Discard undelivered messages and refill flow control tokens.
         while not self._message_queue.empty():
             msg = self._message_queue.get_nowait()
             self._outstanding_flow_control.add(
                 FlowControlRequest(
                     allowed_messages=1,
                     allowed_bytes=msg.size_bytes,
                 ))
         await self._reset_handler.handle_reset()
         self._last_received_offset = None
     initial = deepcopy(self._base_initial)
     if self._last_received_offset is not None:
         initial.initial_location = SeekRequest(cursor=Cursor(
             offset=self._last_received_offset + 1))
     else:
         initial.initial_location = SeekRequest(
             named_target=SeekRequest.NamedTarget.COMMITTED_CURSOR)
     await connection.write(SubscribeRequest(initial=initial))
     response = await connection.read()
     if "initial" not in response:
         self._connection.fail(
             FailedPrecondition(
                 "Received an invalid initial response on the subscribe stream."
             ))
         return
     tokens = self._outstanding_flow_control.request_for_restart()
     if tokens is not None:
         await connection.write(SubscribeRequest(flow_control=tokens))
     self._reinitializing = False
     self._start_loopers()
async def test_message_receipt(
    subscriber: Subscriber,
    default_connection,
    base_initial_subscribe,
    initial_request,
    asyncio_sleep,
    sleep_queues,
):
    write_called_queue = asyncio.Queue()
    write_result_queue = asyncio.Queue()
    flow = FlowControlRequest(allowed_messages=100, allowed_bytes=100)
    message_1 = SequencedMessage(cursor=Cursor(offset=3), size_bytes=5)
    message_2 = SequencedMessage(cursor=Cursor(offset=5), size_bytes=10)
    default_connection.write.side_effect = make_queue_waiter(
        write_called_queue, write_result_queue
    )
    read_called_queue = asyncio.Queue()
    read_result_queue = asyncio.Queue()
    default_connection.read.side_effect = make_queue_waiter(
        read_called_queue, read_result_queue
    )
    read_result_queue.put_nowait(SubscribeResponse(initial={}))
    write_result_queue.put_nowait(None)
    async with subscriber:
        # Set up connection
        await write_called_queue.get()
        await read_called_queue.get()
        default_connection.write.assert_has_calls([call(initial_request)])

        # Send tokens.
        flow_fut = asyncio.ensure_future(subscriber.allow_flow(flow))
        assert not flow_fut.done()

        # Handle the inline write since initial tokens are 100% of outstanding.
        await write_called_queue.get()
        await write_result_queue.put(None)
        await flow_fut
        default_connection.write.assert_has_calls(
            [call(initial_request), call(as_request(flow))]
        )

        message1_fut = asyncio.ensure_future(subscriber.read())

        # Send messages to the subscriber.
        await read_result_queue.put(as_response([message_1, message_2]))
        # Wait for the next read call
        await read_called_queue.get()

        assert (await message1_fut) == message_1
        assert (await subscriber.read()) == message_2

        # Fail the connection with a retryable error
        await read_called_queue.get()
        await read_result_queue.put(InternalServerError("retryable"))
        await sleep_queues[_MIN_BACKOFF_SECS].called.get()
        await sleep_queues[_MIN_BACKOFF_SECS].results.put(None)
        # Reinitialization
        await write_called_queue.get()
        seek_to_cursor_request = make_initial_subscribe_request(
            base_initial_subscribe,
            SeekRequest(cursor=Cursor(offset=message_2.cursor.offset + 1)),
        )
        default_connection.write.assert_has_calls(
            [
                call(initial_request),
                call(as_request(flow)),
                call(seek_to_cursor_request),
            ]
        )
        await write_result_queue.put(None)
        await read_called_queue.get()
        await read_result_queue.put(SubscribeResponse(initial={}))
        # Re-sending flow tokens on the new stream.
        await write_called_queue.get()
        await write_result_queue.put(None)
        default_connection.write.assert_has_calls(
            [
                call(initial_request),
                call(as_request(flow)),
                call(seek_to_cursor_request),
                call(
                    as_request(
                        FlowControlRequest(allowed_messages=98, allowed_bytes=85)
                    )
                ),
            ]
        )
 def __init__(self):
     self.request = FlowControlRequest()
async def test_basic_flow_control_after_timeout(
    subscriber: Subscriber,
    default_connection,
    initial_request,
    asyncio_sleep,
    sleep_queues,
):
    sleep_called = sleep_queues[FLUSH_SECONDS].called
    sleep_results = sleep_queues[FLUSH_SECONDS].results
    write_called_queue = asyncio.Queue()
    write_result_queue = asyncio.Queue()
    flow_1 = FlowControlRequest(allowed_messages=100, allowed_bytes=100)
    flow_2 = FlowControlRequest(allowed_messages=5, allowed_bytes=10)
    flow_3 = FlowControlRequest(allowed_messages=10, allowed_bytes=5)
    default_connection.write.side_effect = make_queue_waiter(
        write_called_queue, write_result_queue
    )
    read_called_queue = asyncio.Queue()
    read_result_queue = asyncio.Queue()
    default_connection.read.side_effect = make_queue_waiter(
        read_called_queue, read_result_queue
    )
    read_result_queue.put_nowait(SubscribeResponse(initial={}))
    write_result_queue.put_nowait(None)
    async with subscriber:
        # Set up connection
        await write_called_queue.get()
        await read_called_queue.get()
        default_connection.write.assert_has_calls([call(initial_request)])

        # Send tokens.
        flow_fut1 = asyncio.ensure_future(subscriber.allow_flow(flow_1))
        assert not flow_fut1.done()

        # Handle the inline write since initial tokens are 100% of outstanding.
        await write_called_queue.get()
        await write_result_queue.put(None)
        await flow_fut1
        default_connection.write.assert_has_calls(
            [call(initial_request), call(as_request(flow_1))]
        )

        # Should complete without writing to the connection
        await subscriber.allow_flow(flow_2)
        await subscriber.allow_flow(flow_3)

        # Wait for writes to be waiting
        await sleep_called.get()
        asyncio_sleep.assert_called_with(FLUSH_SECONDS)

        # Handle the connection write
        await sleep_results.put(None)
        await write_called_queue.get()
        await write_result_queue.put(None)
        # Called with aggregate
        default_connection.write.assert_has_calls(
            [
                call(initial_request),
                call(as_request(flow_1)),
                call(
                    as_request(
                        FlowControlRequest(allowed_messages=15, allowed_bytes=15)
                    )
                ),
            ]
        )
async def test_flow_resent_on_restart(
    subscriber: Subscriber,
    default_connection,
    initial_request,
    asyncio_sleep,
    sleep_queues,
):
    write_called_queue = asyncio.Queue()
    write_result_queue = asyncio.Queue()
    flow_1 = FlowControlRequest(allowed_messages=100, allowed_bytes=100)
    flow_2 = FlowControlRequest(allowed_messages=5, allowed_bytes=10)
    flow_3 = FlowControlRequest(allowed_messages=10, allowed_bytes=5)
    default_connection.write.side_effect = make_queue_waiter(
        write_called_queue, write_result_queue
    )
    read_called_queue = asyncio.Queue()
    read_result_queue = asyncio.Queue()
    default_connection.read.side_effect = make_queue_waiter(
        read_called_queue, read_result_queue
    )
    read_result_queue.put_nowait(SubscribeResponse(initial={}))
    write_result_queue.put_nowait(None)
    async with subscriber:
        # Set up connection
        await write_called_queue.get()
        await read_called_queue.get()
        default_connection.write.assert_has_calls([call(initial_request)])

        # Send tokens.
        flow_fut1 = asyncio.ensure_future(subscriber.allow_flow(flow_1))
        assert not flow_fut1.done()

        # Handle the inline write since initial tokens are 100% of outstanding.
        await write_called_queue.get()
        await write_result_queue.put(None)
        await flow_fut1
        default_connection.write.assert_has_calls(
            [call(initial_request), call(as_request(flow_1))]
        )

        # Should complete without writing to the connection
        await subscriber.allow_flow(flow_2)
        await subscriber.allow_flow(flow_3)

        # Fail the connection with a retryable error
        await read_called_queue.get()
        await read_result_queue.put(InternalServerError("retryable"))
        await sleep_queues[_MIN_BACKOFF_SECS].called.get()
        await sleep_queues[_MIN_BACKOFF_SECS].results.put(None)
        # Reinitialization
        await write_called_queue.get()
        await write_result_queue.put(None)
        await read_called_queue.get()
        await read_result_queue.put(SubscribeResponse(initial={}))
        # Re-sending flow tokens on the new stream
        await write_called_queue.get()
        await write_result_queue.put(None)
        default_connection.write.assert_has_calls(
            [
                call(initial_request),
                call(as_request(flow_1)),
                call(initial_request),
                call(
                    as_request(
                        FlowControlRequest(allowed_messages=115, allowed_bytes=115)
                    )
                ),
            ]
        )
예제 #16
0
def initial_flow_request(flow_control_settings):
    return FlowControlRequest(
        allowed_messages=flow_control_settings.messages_outstanding,
        allowed_bytes=flow_control_settings.bytes_outstanding,
    )
async def test_handle_reset_signal(
    subscriber: Subscriber,
    default_connection,
    initial_request,
    asyncio_sleep,
    sleep_queues,
    reset_handler,
):
    write_called_queue = asyncio.Queue()
    write_result_queue = asyncio.Queue()
    flow = FlowControlRequest(allowed_messages=100, allowed_bytes=100)
    message_1 = SequencedMessage(cursor=Cursor(offset=2), size_bytes=5)
    message_2 = SequencedMessage(cursor=Cursor(offset=4), size_bytes=10)
    # Ensure messages with earlier offsets can be handled post-reset.
    message_3 = SequencedMessage(cursor=Cursor(offset=1), size_bytes=20)
    default_connection.write.side_effect = make_queue_waiter(
        write_called_queue, write_result_queue
    )
    read_called_queue = asyncio.Queue()
    read_result_queue = asyncio.Queue()
    default_connection.read.side_effect = make_queue_waiter(
        read_called_queue, read_result_queue
    )
    read_result_queue.put_nowait(SubscribeResponse(initial={}))
    write_result_queue.put_nowait(None)
    async with subscriber:
        # Set up connection
        await write_called_queue.get()
        await read_called_queue.get()
        default_connection.write.assert_has_calls([call(initial_request)])

        # Send tokens.
        flow_fut = asyncio.ensure_future(subscriber.allow_flow(flow))
        assert not flow_fut.done()

        # Handle the inline write since initial tokens are 100% of outstanding.
        await write_called_queue.get()
        await write_result_queue.put(None)
        await flow_fut
        default_connection.write.assert_has_calls(
            [call(initial_request), call(as_request(flow))]
        )

        # Send messages to the subscriber.
        await read_result_queue.put(as_response([message_1, message_2]))

        # Read one message.
        await read_called_queue.get()
        assert (await subscriber.read()) == message_1

        # Fail the connection with an error containing the RESET signal.
        await read_called_queue.get()
        await read_result_queue.put(make_reset_signal())
        await sleep_queues[_MIN_BACKOFF_SECS].called.get()
        await sleep_queues[_MIN_BACKOFF_SECS].results.put(None)
        # Reinitialization.
        await write_called_queue.get()
        await write_result_queue.put(None)
        await read_called_queue.get()
        await read_result_queue.put(SubscribeResponse(initial={}))
        # Re-sending flow tokens on the new stream.
        await write_called_queue.get()
        await write_result_queue.put(None)
        reset_handler.handle_reset.assert_called_once()
        default_connection.write.assert_has_calls(
            [
                call(initial_request),
                call(as_request(flow)),
                call(initial_request),
                call(
                    as_request(
                        # Tokens for undelivered message_2 refilled.
                        FlowControlRequest(allowed_messages=99, allowed_bytes=95)
                    )
                ),
            ]
        )

        # Ensure the subscriber accepts an earlier message.
        await read_result_queue.put(as_response([message_3]))
        await read_called_queue.get()
        assert (await subscriber.read()) == message_3