def _handle_conn(self, conn) -> None: # Can only handle one connection at a time logging.info('Servicing incoming connection') self.sock = conn while True: try: (typ, payload) = self._recv_packet() except IOError as e: logging.info('Connection closed') break if typ in PKT_CHANGE_TYPES: change = change_from_packet(typ, payload) if typ == PacketType.CHANGE: logging.debug('Received CHANGE {}'.format(change.version)) else: logging.info('Received SNAPSHOT {}'.format(change.version)) self.backend.add_change(change) self._send_packet(PacketType.ACK, struct.pack("!I", self.backend.version)) elif typ == PacketType.REWIND: logging.info('Received REWIND') to_version, = struct.unpack('!I', payload) if to_version != self.backend.prev_version: logging.info( 'Cannot rewind to version {}'.format(to_version)) self._send_packet(PacketType.NACK, struct.pack("!I", self.backend.version)) else: self.backend.rewind() self._send_packet(PacketType.ACK, struct.pack("!I", self.backend.version)) elif typ == PacketType.REQ_METADATA: logging.debug('Received REQ_METADATA') blob = struct.pack("!IIIQ", 0x01, self.backend.version, self.backend.prev_version, self.backend.version_count) self._send_packet(PacketType.METADATA, blob) elif typ == PacketType.RESTORE: logging.info('Received RESTORE') for change in self.backend.stream_changes(): (typ, payload) = packet_from_change(change) self._send_packet(typ, payload) self._send_packet(PacketType.DONE, b'') elif typ == PacketType.COMPACT: logging.info('Received COMPACT') stats = self.backend.compact() self._send_packet(PacketType.COMPACT_RES, json.dumps(stats).encode()) elif typ == PacketType.ACK: logging.debug('Received ACK') elif typ == PacketType.NACK: logging.debug('Received NACK') elif typ == PacketType.METADATA: logging.debug('Received METADATA') elif typ == PacketType.COMPACT_RES: logging.debug('Received COMPACT_RES') else: raise Exception( 'Unknown or unexpected packet type {}'.format(typ)) self.conn = None
def add_change(self, entry: Change) -> bool: typ, payload = packet_from_change(entry) self._send_packet(typ, payload) # Wait for change to be acknowledged before continuing. (typ, _) = self._recv_packet() assert(typ == PacketType.ACK) return True
def add_change(self, entry: Change) -> bool: typ, payload = packet_from_change(entry) base_version = self.version retry = 0 retry_delay = RECONNECT_DELAY need_connect = False while True: # Retry loop try: if need_connect: self.connect() # Request metadata, to know where we stand self._request_metadata() if self.version == entry.version: # If the current version at the server side matches the version of the # entry, the packet was succesfully sent and processed and the error # happened afterward. Nothing left to do. return True elif base_version == self.version: # The other acceptable option is that the current version still matches # that on the server side. Then we retry. pass else: raise Exception( 'Unexpected backup version {} after reconnect'. format(self.version)) self._send_packet(typ, payload) # Wait for change to be acknowledged before continuing. (typ, _) = self._recv_packet() assert (typ == PacketType.ACK) except (BrokenPipeError, OSError): pass else: break if retry == RECONNECT_TRIES: logging.error( 'Connection was lost while sending change (giving up after {} retries)' .format(retry)) raise IOError('Connection was lost while sending change') retry += 1 logging.warning( 'Connection was lost while sending change (retry {} of {}, will try again after {} seconds)' .format(retry, RECONNECT_TRIES, retry_delay)) time.sleep(retry_delay) retry_delay *= RECONNECT_DELAY_BACKOFF need_connect = True self.prev_version = self.version self.version = entry.version return True