def _apply_committed_log_entries_to_state_machine(self): """Apply committed log entries to the state machine.""" # if there is no new commands to apply then exit from the function if self._server_state.no_commands_to_apply(): return for index in six.moves.range(self._server_state.last_applied() + 1, self._server_state.commit_index() + 1): _, _, payload = self._log.entry_at_index(index, decode=True) command_type, command_id, params = commands.decode_command( payload) if command_type == commands.NO_OPERATION: continue client_id = self._queued_commands.get(command_id, (None, -1))[0] try: self._state_machine.apply_command(command_type, *params) except datatree.FsmException as e: self._queue_command_response_if_leader(command_id, client_id, e.errno) else: self._queue_command_response_if_leader(command_id, client_id, 0) self._server_state.update_last_applied()
def _send_write_responses(self, first_non_ack_command_index): """Send acknowledgments for write requests. Once the leader committed commands it sends the acknowledgments to the corresponding client. :param first_non_ack_command_index: the first command index which the server didn't send an acknowledgment. :type first_non_ack_command_index: int """ for index in six.moves.range(first_non_ack_command_index, self._server_state.commit_index() + 1): command = self._log.entry_at_index(index, decode=True)[2] command_type, command_id, payload = commands.decode_command( command) if command_type == commands.NO_OPERATION: self._queued_commands.pop(command_id, None) continue client_id, status = self._queued_commands.get(command_id, (None, -1)) if client_id: response = commands.build_response(command_type, command_id, status) self._socket_for_commands.send_multipart((client_id, response)) del self._queued_commands[command_id]
def _process_response_message(self, socket_dealer, event): """Process a response message. Decode the message and set the result. :param socket_dealer: The zmq.DEALER socket. :type socket_dealer: zmq.sugar.socket.Socket :param event: The corresponding event, it should only be zmq.POLLIN. :type event: int """ response_message = socket_dealer.recv() _, response_id, response = commands.decode_command(response_message) self._sync_response[response_id]._set_result(response_message) del self._sync_response[response_id]
def get(self, timeout=None): """Get the result. If timeout is None then the method will block indefinitely. If the method time out it raises ChillaxTimeoutException. :param timeout: The timeout in seconds. :type timeout: int :return: The result according to the command. :rtype: object """ if not self._event.wait(timeout=timeout): raise ChillaxTimeoutException() return commands.decode_command(self._result)
def _process_command_message(self, socket, event): """Processes a command from a client. In case of a read command the server respond immediately to the client otherwise it just add the command in the log so that it will be replicated. :param socket: The zmq.ROUTER socket. :type socket: zmq.sugar.socket.Socket :param event: The corresponding event, it should only be zmq.POLLIN. :type event: int """ assert event == zmq.POLLIN zmq_message = socket.recv_multipart() client_identifier, command = zmq_message[0], zmq_message[1] command_type, command_id, payload = commands.decode_command(command) # If it is a read command then just send immediately the result. if commands.is_read_command(command_type): try: data = self._state_machine.apply_command(command_type, *payload) except datatree.FsmException as e: response = commands.build_response(command_type, command_id, e.errno) else: response = commands.build_response(command_type, command_id, 0, data) socket.send_multipart((client_identifier, response)) else: # If it is a write command then: # 1. add the command to the commands queue # 2. append it to the log, it will be piggybacked # with the next heartbeat among the cluster self._queued_commands[command_id] = (client_identifier, -1) self._log.append_entry(self._server_state.term(), command) if self.is_standalone(): # If it's a standalone server then we can directly commit # the command. new_commit_index = self._server_state.commit_index() + 1 self._server_state.update_commit_index(new_commit_index) self._apply_committed_log_entries_to_state_machine() self._send_write_responses( max(1, self._server_state.commit_index() - 1))