|
4 | 4 | import copy
|
5 | 5 | import errno
|
6 | 6 | import logging
|
7 |
| -import io |
8 | 7 | from random import shuffle, uniform
|
9 | 8 | import socket
|
10 | 9 | import time
|
|
18 | 17 | from kafka.protocol.api import RequestHeader
|
19 | 18 | from kafka.protocol.admin import SaslHandShakeRequest
|
20 | 19 | from kafka.protocol.commit import GroupCoordinatorResponse, OffsetFetchRequest
|
| 20 | +from kafka.protocol.frame import KafkaBytes |
21 | 21 | from kafka.protocol.metadata import MetadataRequest
|
22 | 22 | from kafka.protocol.fetch import FetchRequest
|
23 | 23 | from kafka.protocol.types import Int32
|
@@ -231,9 +231,9 @@ def __init__(self, host, port, afi, **configs):
|
231 | 231 | if self.config['ssl_context'] is not None:
|
232 | 232 | self._ssl_context = self.config['ssl_context']
|
233 | 233 | self._sasl_auth_future = None
|
234 |
| - self._rbuffer = io.BytesIO() |
| 234 | + self._header = KafkaBytes(4) |
| 235 | + self._rbuffer = None |
235 | 236 | self._receiving = False
|
236 |
| - self._next_payload_bytes = 0 |
237 | 237 | self.last_attempt = 0
|
238 | 238 | self._processing = False
|
239 | 239 | self._correlation_id = 0
|
@@ -637,17 +637,19 @@ def close(self, error=None):
|
637 | 637 | self.state = ConnectionStates.DISCONNECTED
|
638 | 638 | self.last_attempt = time.time()
|
639 | 639 | self._sasl_auth_future = None
|
640 |
| - self._receiving = False |
641 |
| - self._next_payload_bytes = 0 |
642 |
| - self._rbuffer.seek(0) |
643 |
| - self._rbuffer.truncate() |
| 640 | + self._reset_buffer() |
644 | 641 | if error is None:
|
645 | 642 | error = Errors.Cancelled(str(self))
|
646 | 643 | while self.in_flight_requests:
|
647 | 644 | ifr = self.in_flight_requests.popleft()
|
648 | 645 | ifr.future.failure(error)
|
649 | 646 | self.config['state_change_callback'](self)
|
650 | 647 |
|
| 648 | + def _reset_buffer(self): |
| 649 | + self._receiving = False |
| 650 | + self._header.seek(0) |
| 651 | + self._rbuffer = None |
| 652 | + |
651 | 653 | def send(self, request):
|
652 | 654 | """send request, return Future()
|
653 | 655 |
|
@@ -721,116 +723,123 @@ def recv(self):
|
721 | 723 | # fail all the pending request futures
|
722 | 724 | if self.in_flight_requests:
|
723 | 725 | self.close(Errors.ConnectionError('Socket not connected during recv with in-flight-requests'))
|
724 |
| - return None |
| 726 | + return () |
725 | 727 |
|
726 | 728 | elif not self.in_flight_requests:
|
727 | 729 | log.warning('%s: No in-flight-requests to recv', self)
|
728 |
| - return None |
| 730 | + return () |
729 | 731 |
|
730 | 732 | elif self._requests_timed_out():
|
731 | 733 | log.warning('%s timed out after %s ms. Closing connection.',
|
732 | 734 | self, self.config['request_timeout_ms'])
|
733 | 735 | self.close(error=Errors.RequestTimedOutError(
|
734 | 736 | 'Request timed out after %s ms' %
|
735 | 737 | self.config['request_timeout_ms']))
|
736 |
| - return None |
| 738 | + return () |
737 | 739 |
|
| 740 | + # TODO: manpreet: Decide to return response/None |
| 741 | + # return response |
738 | 742 | return self._recv()
|
739 | 743 |
|
740 | 744 | def _recv(self):
|
741 |
| - # Not receiving is the state of reading the payload header |
742 |
| - if not self._receiving: |
| 745 | + responses = [] |
| 746 | + SOCK_CHUNK_BYTES = 4096 |
| 747 | + while True: |
743 | 748 | try:
|
744 |
| - bytes_to_read = 4 - self._rbuffer.tell() |
745 |
| - data = self._sock.recv(bytes_to_read) |
| 749 | + data = self._sock.recv(SOCK_CHUNK_BYTES) |
746 | 750 | # We expect socket.recv to raise an exception if there is not
|
747 | 751 | # enough data to read the full bytes_to_read
|
748 | 752 | # but if the socket is disconnected, we will get empty data
|
749 | 753 | # without an exception raised
|
750 | 754 | if not data:
|
751 | 755 | log.error('%s: socket disconnected', self)
|
752 | 756 | self.close(error=Errors.ConnectionError('socket disconnected'))
|
753 |
| - return None |
754 |
| - self._rbuffer.write(data) |
| 757 | + break |
| 758 | + else: |
| 759 | + responses.extend(self.receive_bytes(data)) |
| 760 | + if len(data) < SOCK_CHUNK_BYTES: |
| 761 | + break |
755 | 762 | except SSLWantReadError:
|
756 |
| - return None |
| 763 | + break |
757 | 764 | except ConnectionError as e:
|
758 | 765 | if six.PY2 and e.errno == errno.EWOULDBLOCK:
|
759 |
| - return None |
760 |
| - log.exception('%s: Error receiving 4-byte payload header -' |
| 766 | + break |
| 767 | + log.exception('%s: Error receiving network data' |
761 | 768 | ' closing socket', self)
|
762 | 769 | self.close(error=Errors.ConnectionError(e))
|
763 |
| - return None |
764 |
| - except BlockingIOError: |
765 |
| - if six.PY3: |
766 |
| - return None |
767 |
| - raise |
768 |
| - |
769 |
| - if self._rbuffer.tell() == 4: |
770 |
| - self._rbuffer.seek(0) |
771 |
| - self._next_payload_bytes = Int32.decode(self._rbuffer) |
772 |
| - # reset buffer and switch state to receiving payload bytes |
773 |
| - self._rbuffer.seek(0) |
774 |
| - self._rbuffer.truncate() |
775 |
| - self._receiving = True |
776 |
| - elif self._rbuffer.tell() > 4: |
777 |
| - raise Errors.KafkaError('this should not happen - are you threading?') |
778 |
| - |
779 |
| - if self._receiving: |
780 |
| - staged_bytes = self._rbuffer.tell() |
781 |
| - try: |
782 |
| - bytes_to_read = self._next_payload_bytes - staged_bytes |
783 |
| - data = self._sock.recv(bytes_to_read) |
784 |
| - # We expect socket.recv to raise an exception if there is not |
785 |
| - # enough data to read the full bytes_to_read |
786 |
| - # but if the socket is disconnected, we will get empty data |
787 |
| - # without an exception raised |
788 |
| - if bytes_to_read and not data: |
789 |
| - log.error('%s: socket disconnected', self) |
790 |
| - self.close(error=Errors.ConnectionError('socket disconnected')) |
791 |
| - return None |
792 |
| - self._rbuffer.write(data) |
793 |
| - except SSLWantReadError: |
794 |
| - return None |
795 |
| - except ConnectionError as e: |
796 |
| - # Extremely small chance that we have exactly 4 bytes for a |
797 |
| - # header, but nothing to read in the body yet |
798 |
| - if six.PY2 and e.errno == errno.EWOULDBLOCK: |
799 |
| - return None |
800 |
| - log.exception('%s: Error in recv', self) |
801 |
| - self.close(error=Errors.ConnectionError(e)) |
802 |
| - return None |
| 770 | + break |
803 | 771 | except BlockingIOError:
|
804 | 772 | if six.PY3:
|
805 |
| - return None |
| 773 | + break |
806 | 774 | raise
|
| 775 | + return responses |
807 | 776 |
|
808 |
| - staged_bytes = self._rbuffer.tell() |
809 |
| - if staged_bytes > self._next_payload_bytes: |
810 |
| - self.close(error=Errors.KafkaError('Receive buffer has more bytes than expected?')) |
811 |
| - |
812 |
| - if staged_bytes != self._next_payload_bytes: |
813 |
| - return None |
| 777 | + def receive_bytes(self, data): |
| 778 | + i = 0 |
| 779 | + n = len(data) |
| 780 | + responses = [] |
| 781 | + if self._sensors: |
| 782 | + self._sensors.bytes_received.record(n) |
| 783 | + while i < n: |
| 784 | + |
| 785 | + # Not receiving is the state of reading the payload header |
| 786 | + if not self._receiving: |
| 787 | + bytes_to_read = min(4 - self._header.tell(), n - i) |
| 788 | + self._header.write(data[i:i+bytes_to_read]) |
| 789 | + i += bytes_to_read |
| 790 | + |
| 791 | + if self._header.tell() == 4: |
| 792 | + self._header.seek(0) |
| 793 | + nbytes = Int32.decode(self._header) |
| 794 | + # reset buffer and switch state to receiving payload bytes |
| 795 | + self._rbuffer = KafkaBytes(nbytes) |
| 796 | + self._receiving = True |
| 797 | + elif self._header.tell() > 4: |
| 798 | + raise Errors.KafkaError('this should not happen - are you threading?') |
| 799 | + |
| 800 | + |
| 801 | + if self._receiving: |
| 802 | + total_bytes = len(self._rbuffer) |
| 803 | + staged_bytes = self._rbuffer.tell() |
| 804 | + bytes_to_read = min(total_bytes - staged_bytes, n - i) |
| 805 | + self._rbuffer.write(data[i:i+bytes_to_read]) |
| 806 | + i += bytes_to_read |
| 807 | + |
| 808 | + staged_bytes = self._rbuffer.tell() |
| 809 | + if staged_bytes > total_bytes: |
| 810 | + self.close(error=Errors.KafkaError('Receive buffer has more bytes than expected?')) |
| 811 | + |
| 812 | + if staged_bytes != total_bytes: |
| 813 | + break |
814 | 814 |
|
815 |
| - self._receiving = False |
816 |
| - self._next_payload_bytes = 0 |
817 |
| - if self._sensors: |
818 |
| - self._sensors.bytes_received.record(4 + self._rbuffer.tell()) |
819 |
| - self._rbuffer.seek(0) |
820 |
| - response = self._process_response(self._rbuffer) |
821 |
| - self._rbuffer.seek(0) |
822 |
| - self._rbuffer.truncate() |
823 |
| - return response |
| 815 | + self._receiving = False |
| 816 | + self._rbuffer.seek(0) |
| 817 | + resp = self._process_response(self._rbuffer) |
| 818 | + if resp is not None: |
| 819 | + responses.append(resp) |
| 820 | + self._reset_buffer() |
| 821 | + return responses |
824 | 822 |
|
825 | 823 | def _process_response(self, read_buffer):
|
826 | 824 | assert not self._processing, 'Recursion not supported'
|
827 | 825 | self._processing = True
|
828 |
| - ifr = self.in_flight_requests.popleft() |
| 826 | + recv_correlation_id = Int32.decode(read_buffer) |
| 827 | + |
| 828 | + if not self.in_flight_requests: |
| 829 | + error = Errors.CorrelationIdError( |
| 830 | + '%s: No in-flight-request found for server response' |
| 831 | + ' with correlation ID %d' |
| 832 | + % (self, recv_correlation_id)) |
| 833 | + self.close(error) |
| 834 | + self._processing = False |
| 835 | + return None |
| 836 | + else: |
| 837 | + ifr = self.in_flight_requests.popleft() |
| 838 | + |
829 | 839 | if self._sensors:
|
830 | 840 | self._sensors.request_time.record((time.time() - ifr.timestamp) * 1000)
|
831 | 841 |
|
832 | 842 | # verify send/recv correlation ids match
|
833 |
| - recv_correlation_id = Int32.decode(read_buffer) |
834 | 843 |
|
835 | 844 | # 0.8.2 quirk
|
836 | 845 | if (self.config['api_version'] == (0, 8, 2) and
|
|
0 commit comments