Esempio n. 1
0
    def reset(self):
        """ Resets the buffered regions to start transmitting from the\
            beginning of its expected regions and clears the buffered out data\
            files
        """
        # reset buffered out
        self._received_data = BufferedReceivingData()

        # rewind buffered in
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                vertex.rewind(region)
Esempio n. 2
0
    def __init__(self, placements, tags, transceiver, write_reload_files,
                 application_folder_path):
        """

        :param placements: The placements of the vertices
        :type placements:\
                    :py:class:`pacman.model.placements.placements.Placements`
        :param report_states: the booleans saying what reports are needed
        :type report_states: XXXXXXXXXXX
        :param tags: The tags assigned to the vertices
        :type tags: :py:class:`pacman.model.tags.tags.Tags`
        :param transceiver: The transceiver to use for sending and receiving\
                    information
        :type transceiver: :py:class:`spinnman.transceiver.Transceiver`
        """

        self._placements = placements
        self._tags = tags
        self._transceiver = transceiver

        # params used for reload purposes
        self._write_reload_files = write_reload_files
        self._application_folder_path = application_folder_path
        self._reload_buffer_file = dict()
        self._reload_buffer_file_paths = dict()

        # Set of (ip_address, port) that are being listened to for the tags
        self._seen_tags = set()

        # Set of vertices with buffers to be sent
        self._sender_vertices = set()

        # Dictionary of sender vertex -> buffers sent
        self._sent_messages = dict()

        # storage area for received data from cores
        self._received_data = BufferedReceivingData()

        # Lock to avoid multiple messages being processed at the same time
        self._thread_lock_buffer_out = threading.Lock()
        self._thread_lock_buffer_in = threading.Lock()

        self._finished = False
    def reset(self):
        """ Resets the buffered regions to start transmitting from the\
            beginning of its expected regions and clears the buffered out data\
            files
        """
        # reset buffered out
        self._received_data = BufferedReceivingData()

        # rewind buffered in
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                vertex.rewind(region)
    def __init__(self, placements, tags, transceiver, report_states,
                 application_folder_path):
        """

        :param placements: The placements of the vertices
        :type placements:\
                    :py:class:`pacman.model.placements.placements.Placements`
        :param report_states: the booleans saying what reports are needed
        :type report_states: XXXXXXXXXXX
        :param tags: The tags assigned to the vertices
        :type tags: :py:class:`pacman.model.tags.tags.Tags`
        :param transceiver: The transceiver to use for sending and receiving\
                    information
        :type transceiver: :py:class:`spinnman.transceiver.Transceiver`
        """

        self._placements = placements
        self._tags = tags
        self._transceiver = transceiver

        # params used for reload purposes
        self._report_states = report_states
        self._application_folder_path = application_folder_path
        self._reload_buffer_file = dict()
        self._reload_buffer_file_paths = dict()

        # Set of (ip_address, port) that are being listened to for the tags
        self._seen_tags = set()

        # Set of vertices with buffers to be sent
        self._sender_vertices = set()

        # Dictionary of sender vertex -> buffers sent
        self._sent_messages = dict()

        # storage area for received data from cores
        self._received_data = BufferedReceivingData()

        # Lock to avoid multiple messages being processed at the same time
        self._thread_lock_buffer_out = threading.Lock()
        self._thread_lock_buffer_in = threading.Lock()

        self._finished = False
Esempio n. 5
0
class BufferManager(object):
    """ Manager of send buffers
    """
    def __init__(self, placements, tags, transceiver, write_reload_files,
                 application_folder_path):
        """

        :param placements: The placements of the vertices
        :type placements:\
                    :py:class:`pacman.model.placements.placements.Placements`
        :param report_states: the booleans saying what reports are needed
        :type report_states: XXXXXXXXXXX
        :param tags: The tags assigned to the vertices
        :type tags: :py:class:`pacman.model.tags.tags.Tags`
        :param transceiver: The transceiver to use for sending and receiving\
                    information
        :type transceiver: :py:class:`spinnman.transceiver.Transceiver`
        """

        self._placements = placements
        self._tags = tags
        self._transceiver = transceiver

        # params used for reload purposes
        self._write_reload_files = write_reload_files
        self._application_folder_path = application_folder_path
        self._reload_buffer_file = dict()
        self._reload_buffer_file_paths = dict()

        # Set of (ip_address, port) that are being listened to for the tags
        self._seen_tags = set()

        # Set of vertices with buffers to be sent
        self._sender_vertices = set()

        # Dictionary of sender vertex -> buffers sent
        self._sent_messages = dict()

        # storage area for received data from cores
        self._received_data = BufferedReceivingData()

        # Lock to avoid multiple messages being processed at the same time
        self._thread_lock_buffer_out = threading.Lock()
        self._thread_lock_buffer_in = threading.Lock()

        self._finished = False

    def receive_buffer_command_message(self, packet):
        """ Handle an EIEIO command message for the buffers

        :param packet: The eieio message received
        :type packet:\
                    :py:class:`spinnman.messages.eieio.command_messages.eieio_command_message.EIEIOCommandMessage`
        """
        try:
            if not self._finished:
                if isinstance(packet, SpinnakerRequestBuffers):
                    with self._thread_lock_buffer_in:
                        vertex = self._placements.get_subvertex_on_processor(
                            packet.x, packet.y, packet.p)

                        if vertex in self._sender_vertices:

                            # logger.debug(
                            #     "received send request with sequence: {1:d},"
                            #     " space available: {0:d}".format(
                            #         packet.space_available,
                            #         packet.sequence_no))

                            # noinspection PyBroadException
                            try:
                                self._send_messages(packet.space_available,
                                                    vertex, packet.region_id,
                                                    packet.sequence_no)
                            except Exception:
                                traceback.print_exc()
                elif isinstance(packet, SpinnakerRequestReadData):
                    with self._thread_lock_buffer_out:

                        # logger.debug(
                        #     "received {} read request(s) with sequence: {},"
                        #     " from chip ({},{}, core {}".format(
                        #         packet.n_requests, packet.sequence_no,
                        #         packet.x, packet.y, packet.p))
                        vertex = self._placements.get_subvertex_on_processor(
                            packet.x, packet.y, packet.p)
                        try:
                            self._retrieve_and_store_data(packet, vertex)
                        except Exception:
                            traceback.print_exc()
                elif isinstance(packet, EIEIOCommandMessage):
                    raise SpinnmanInvalidPacketException(
                        str(packet.__class__),
                        "The command packet is invalid for buffer management: "
                        "command id {0:d}".format(packet.eieio_header.command))
                else:
                    raise SpinnmanInvalidPacketException(
                        packet.__class__,
                        "The command packet is invalid for buffer management")
        except Exception:
            traceback.print_exc()

    def add_receiving_vertex(self, vertex):
        """ Add a partitioned vertex into the managed list for vertices\
            which require buffers to be received from them during runtime
        """
        tag = self._tags.get_ip_tags_for_vertex(vertex)[0]
        if (tag.ip_address, tag.port) not in self._seen_tags:
            logger.debug("Listening for receive packets using tag {} on"
                         " {}:{}".format(tag.tag, tag.ip_address, tag.port))
            self._seen_tags.add((tag.ip_address, tag.port))
            if self._transceiver is not None:
                self._transceiver.register_udp_listener(
                    self.receive_buffer_command_message,
                    UDPEIEIOConnection,
                    local_port=tag.port,
                    local_host=tag.ip_address)

    def add_sender_vertex(self, vertex):
        """ Add a partitioned vertex into the managed list for vertices
            which require buffers to be sent to them during runtime

        :param vertex: the vertex to be managed
        :type vertex:\
                    :py:class:`spinnaker.pyNN.models.abstract_models.buffer_models.abstract_sends_buffers_from_host.AbstractSendsBuffersFromHost`
        """
        self._sender_vertices.add(vertex)
        tag = self._tags.get_ip_tags_for_vertex(vertex)[0]
        if (tag.ip_address, tag.port) not in self._seen_tags:
            logger.debug("Listening for send packets using tag {} on"
                         " {}:{}".format(tag.tag, tag.ip_address, tag.port))
            self._seen_tags.add((tag.ip_address, tag.port))
            if self._transceiver is not None:
                self._transceiver.register_udp_listener(
                    self.receive_buffer_command_message,
                    UDPEIEIOConnection,
                    local_port=tag.port,
                    local_host=tag.ip_address)

        # if reload script is set up, store the buffers for future usage
        if self._write_reload_files:
            for region in vertex.get_regions():
                filename = "{}_{}".format(re.sub("[\"':]", "_", vertex.label),
                                          region)
                file_path = os.path.join(self._application_folder_path,
                                         filename)
                self._reload_buffer_file[(vertex, region)] = \
                    open(file_path, "w")
                if vertex not in self._reload_buffer_file_paths:
                    self._reload_buffer_file_paths[vertex] = dict()
                self._reload_buffer_file_paths[vertex][region] = file_path

                # If there is no transceiver, push all the output to the file
                if self._transceiver is None:
                    while vertex.is_next_timestamp(region):
                        next_timestamp = vertex.get_next_timestamp(region)
                        while vertex.is_next_key(region, next_timestamp):
                            key = vertex.get_next_key(region)
                            self._reload_buffer_file[(vertex, region)].write(
                                "{}:{}\n".format(next_timestamp, key))
                    self._reload_buffer_file[(vertex, region)].close()

    def load_initial_buffers(self):
        """ Load the initial buffers for the senders using mem writes
        """
        total_data = 0
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                total_data += vertex.get_region_buffer_size(region)

        progress_bar = ProgressBar(
            total_data, "Loading buffers ({} bytes)".format(total_data))
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                self._send_initial_messages(vertex, region, progress_bar)
        progress_bar.end()

    def reset(self):
        """ Resets the buffered regions to start transmitting from the\
            beginning of its expected regions and clears the buffered out data\
            files
        """
        # reset buffered out
        self._received_data = BufferedReceivingData()

        # rewind buffered in
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                vertex.rewind(region)

    def resume(self):
        """ Resets any data structures needed before starting running again
        """
        self._received_data.resume()

    def _create_message_to_send(self, size, vertex, region):
        """ Creates a single message to send with the given boundaries.

        :param size: The number of bytes available for the whole packet
        :type size: int
        :param vertex: The vertex to get the keys from
        :type vertex:\
                    :py:class:`spynnaker.pyNN.models.abstract_models.buffer_models.abstract_sends_buffers_from_host.AbstractSendsBuffersFromHost`
        :param region: The region of the vertex to get keys from
        :type region: int
        :return: A new message, or None if no keys can be added
        :rtype: None or\
                    :py:class:`spinnman.messages.eieio.data_messages.eieio_32bit.eieio_32bit_timed_payload_prefix_data_message.EIEIO32BitTimedPayloadPrefixDataMessage`
        """

        # If there are no more messages to send, return None
        if not vertex.is_next_timestamp(region):
            return None

        # Create a new message
        next_timestamp = vertex.get_next_timestamp(region)
        message = EIEIO32BitTimedPayloadPrefixDataMessage(next_timestamp)

        # If there is no room for the message, return None
        if message.size + _N_BYTES_PER_KEY > size:
            return None

        # Add keys up to the limit
        bytes_to_go = size - message.size
        while (bytes_to_go >= _N_BYTES_PER_KEY
               and vertex.is_next_key(region, next_timestamp)):

            key = vertex.get_next_key(region)
            message.add_key(key)
            bytes_to_go -= _N_BYTES_PER_KEY

            if self._write_reload_files:
                self._reload_buffer_file[(vertex, region)].write(
                    "{}:{}\n".format(next_timestamp, key))
        return message

    def _send_initial_messages(self, vertex, region, progress_bar):
        """ Send the initial set of messages

        :param vertex: The vertex to get the keys from
        :type vertex:\
                    :py:class:`spynnaker.pyNN.models.abstract_models.buffer_models.abstract_sends_buffers_from_host.AbstractSendsBuffersFromHost`
        :param region: The region to get the keys from
        :type region: int
        :return: A list of messages
        :rtype: list of\
                    :py:class:`spinnman.messages.eieio.data_messages.eieio_32bit.eieio_32bit_timed_payload_prefix_data_message.EIEIO32BitTimedPayloadPrefixDataMessage`
        """

        # Get the vertex load details
        # region_base_address = self._locate_region_address(region, vertex)
        region_base_address = \
            helpful_functions.locate_memory_region_for_placement(
                self._placements.get_placement_of_subvertex(vertex), region,
                self._transceiver)
        placement = self._placements.get_placement_of_subvertex(vertex)

        # Add packets until out of space
        sent_message = False
        bytes_to_go = vertex.get_region_buffer_size(region)
        if bytes_to_go % 2 != 0:
            raise exceptions.SpinnFrontEndException(
                "The buffer region of {} must be divisible by 2".format(
                    vertex))
        all_data = ""
        if vertex.is_empty(region):
            sent_message = True
        else:
            min_size_of_packet = \
                EIEIO32BitTimedPayloadPrefixDataMessage.get_min_packet_length()
            while (vertex.is_next_timestamp(region)
                   and bytes_to_go > min_size_of_packet):
                space_available = min(bytes_to_go, 280)
                next_message = self._create_message_to_send(
                    space_available, vertex, region)
                if next_message is None:
                    break

                # Write the message to the memory
                data = next_message.bytestring
                all_data += data
                sent_message = True

                # Update the positions
                bytes_to_go -= len(data)
                progress_bar.update(len(data))

        if not sent_message:
            raise exceptions.BufferableRegionTooSmall(
                "The buffer size {} is too small for any data to be added for"
                " region {} of vertex {}".format(bytes_to_go, region, vertex))

        # If there are no more messages and there is space, add a stop request
        if (not vertex.is_next_timestamp(region)
                and bytes_to_go >= EventStopRequest.get_min_packet_length()):
            data = EventStopRequest().bytestring
            # logger.debug(
            #    "Writing stop message of {} bytes to {} on {}, {}, {}".format(
            #         len(data), hex(region_base_address),
            #         placement.x, placement.y, placement.p))
            all_data += data
            bytes_to_go -= len(data)
            progress_bar.update(len(data))
            self._sent_messages[vertex] = BuffersSentDeque(
                region, sent_stop_message=True)

        # If there is any space left, add padding
        if bytes_to_go > 0:
            padding_packet = PaddingRequest()
            n_packets = bytes_to_go / padding_packet.get_min_packet_length()
            data = padding_packet.bytestring
            data *= n_packets
            all_data += data

        # Do the writing all at once for efficiency
        self._transceiver.write_memory(placement.x, placement.y,
                                       region_base_address, all_data)

    def _send_messages(self, size, vertex, region, sequence_no):
        """ Send a set of messages
        """

        # Get the sent messages for the vertex
        if vertex not in self._sent_messages:
            self._sent_messages[vertex] = BuffersSentDeque(region)
        sent_messages = self._sent_messages[vertex]

        # If the sequence number is outside the window, return no messages
        if not sent_messages.update_last_received_sequence_number(sequence_no):
            return list()

        # Remote the existing packets from the size available
        bytes_to_go = size
        for message in sent_messages.messages:
            if isinstance(message.eieio_data_message, EIEIODataMessage):
                bytes_to_go -= message.eieio_data_message.size
            else:
                bytes_to_go -= (
                    message.eieio_data_message.get_min_packet_length())

        # Add messages up to the limits
        while (vertex.is_next_timestamp(region) and not sent_messages.is_full
               and bytes_to_go > 0):

            space_available = min(
                bytes_to_go, constants.UDP_MESSAGE_MAX_SIZE -
                HostSendSequencedData.get_min_packet_length())
            # logger.debug(
            #     "Bytes to go {}, space available {}".format(
            #         bytes_to_go, space_available))
            next_message = self._create_message_to_send(
                space_available, vertex, region)
            if next_message is None:
                break
            sent_messages.add_message_to_send(next_message)
            bytes_to_go -= next_message.size
            # logger.debug("Adding additional buffer of {} bytes".format(
            #     next_message.size))

        # If the vertex is empty, send the stop messages if there is space
        if (not sent_messages.is_full and not vertex.is_next_timestamp(region)
                and bytes_to_go >= EventStopRequest.get_min_packet_length()):
            sent_messages.send_stop_message()

        # If there are no more messages, turn off requests for more messages
        if not vertex.is_next_timestamp(region) and sent_messages.is_empty():
            # logger.debug("Sending stop")
            self._send_request(vertex, StopRequests())

        # Send the messages
        for message in sent_messages.messages:
            # logger.debug("Sending message with sequence {}".format(
            #     message.sequence_no))
            self._send_request(vertex, message)

    def _send_request(self, vertex, message):
        """ Sends a request

        :param vertex: The vertex to send to
        :param message: The message to send
        """

        placement = self._placements.get_placement_of_subvertex(vertex)
        sdp_header = SDPHeader(destination_chip_x=placement.x,
                               destination_chip_y=placement.y,
                               destination_cpu=placement.p,
                               flags=SDPFlag.REPLY_NOT_EXPECTED,
                               destination_port=spinn_front_end_constants.
                               SDP_PORTS.INPUT_BUFFERING_SDP_PORT.value)
        sdp_message = SDPMessage(sdp_header, message.bytestring)
        self._transceiver.send_sdp_message(sdp_message)

    def stop(self):
        """ Indicates that the simulation has finished, so no further\
            outstanding requests need to be processed
        """
        with self._thread_lock_buffer_in:
            with self._thread_lock_buffer_out:
                self._finished = True
        if self._write_reload_files:
            for buffer_file in self._reload_buffer_file.itervalues():
                buffer_file.close()

    def get_data_for_vertex(self, placement, region_to_read, state_region):
        """ Get a pointer to the data container for all the data retrieved\
            during the simulation from a specific region area of a core

        :param placement: the placement to get the data from
        :type placement: pacman.model.placements.placement.Placement
        :param region_to_read: desired data region
        :type region_to_read: int
        :param state_region: final state storage region
        :type state_region: int
        :return: pointer to a class which inherits from\
                AbstractBufferedDataStorage
        :rtype:\
                py:class:`spinn_front_end_common.interface.buffer_management.buffer_models.abstract_buffered_data_storage.AbstractBufferedDataStorage`
        """
        # flush data here
        if not self._received_data.is_data_from_region_flushed(
                placement.x, placement.y, placement.p, region_to_read):
            if not self._received_data.is_end_buffering_state_recovered(
                    placement.x, placement.y, placement.p):

                # Get the App Data for the core
                state_region_base_address = \
                    helpful_functions.locate_memory_region_for_placement(
                        placement, state_region, self._transceiver)

                # retrieve channel state memory area
                raw_number_of_channels = self._transceiver.read_memory(
                    placement.x, placement.y, state_region_base_address, 4)
                number_of_channels = struct.unpack(
                    "<I", str(raw_number_of_channels))[0]
                channel_state_data = str(
                    self._transceiver.read_memory(
                        placement.x, placement.y, state_region_base_address,
                        EndBufferingState.size_of_region(number_of_channels)))
                end_buffering_state = EndBufferingState.create_from_bytearray(
                    channel_state_data)
                self._received_data.store_end_buffering_state(
                    placement.x, placement.y, placement.p, end_buffering_state)
            else:
                end_buffering_state = self._received_data.\
                    get_end_buffering_state(
                        placement.x, placement.y, placement.p)

            end_state = end_buffering_state.get_state_for_region(
                region_to_read)
            start_ptr = end_state.start_address
            write_ptr = end_state.current_write
            end_ptr = end_state.end_address
            read_ptr = end_state.current_read

            # current read needs to be adjusted in case the last portion of the
            # memory has already been read, but the HostDataRead packet has not
            # been processed by the chip before simulation finished
            # This situation is identified by the sequence number of the last
            # packet sent to this core and the core internal state of the
            # output buffering finite state machine
            seq_no_last_ack_packet = \
                self._received_data.last_sequence_no_for_core(
                    placement.x, placement.y, placement.p)
            seq_no_internal_fsm = end_buffering_state.buffering_out_fsm_state
            if seq_no_internal_fsm == seq_no_last_ack_packet:

                # if the last ACK packet has not been processed on the chip,
                # process it now
                last_sent_ack_sdp_packet = \
                    self._received_data.last_sent_packet_to_core(
                        placement.x, placement.y, placement.p)
                last_sent_ack_packet = \
                    create_eieio_command.read_eieio_command_message(
                        last_sent_ack_sdp_packet.data, 0)
                if not isinstance(last_sent_ack_packet, HostDataRead):
                    raise Exception(
                        "Something somewhere went terribly wrong - "
                        "I was looking for a HostDataRead packet, "
                        "while I got {0:s}".format(last_sent_ack_packet))
                for i in xrange(last_sent_ack_packet.n_requests):
                    if (region_to_read == last_sent_ack_packet.region_id(i)
                            and not end_state.is_state_updated):
                        read_ptr += last_sent_ack_packet.space_read(i)
                        if (read_ptr == write_ptr or
                            (read_ptr == end_ptr and write_ptr == start_ptr)):
                            end_state.update_last_operation(
                                spinn_front_end_constants.BUFFERING_OPERATIONS.
                                BUFFER_READ.value)
                        if read_ptr == end_ptr:
                            read_ptr = start_ptr
                        elif read_ptr > end_ptr:
                            raise Exception(
                                "Something somewhere went terribly wrong - "
                                "I was reading beyond the region area some "
                                "unknown data".format(last_sent_ack_packet))
                end_state.update_read_pointer(read_ptr)
                end_state.set_update_completed()

            # now state is updated, read back values for read pointer and
            # last operation performed
            last_operation = end_state.last_buffer_operation
            read_ptr = end_state.current_read

            # now read_ptr is updated, check memory to read
            if read_ptr < write_ptr:
                length = write_ptr - read_ptr
                data = self._transceiver.read_memory(placement.x, placement.y,
                                                     read_ptr, length)
                self._received_data.flushing_data_from_region(
                    placement.x, placement.y, placement.p, region_to_read,
                    data)

            elif read_ptr > write_ptr:
                length = end_ptr - read_ptr
                data = self._transceiver.read_memory(placement.x, placement.y,
                                                     read_ptr, length)
                self._received_data.store_data_in_region_buffer(
                    placement.x, placement.y, placement.p, region_to_read,
                    data)
                read_ptr = start_ptr
                length = write_ptr - read_ptr
                data = self._transceiver.read_memory(placement.x, placement.y,
                                                     read_ptr, length)
                self._received_data.flushing_data_from_region(
                    placement.x, placement.y, placement.p, region_to_read,
                    data)

            elif (read_ptr == write_ptr
                  and last_operation == spinn_front_end_constants.
                  BUFFERING_OPERATIONS.BUFFER_WRITE.value):
                length = end_ptr - read_ptr
                data = self._transceiver.read_memory(placement.x, placement.y,
                                                     read_ptr, length)
                self._received_data.store_data_in_region_buffer(
                    placement.x, placement.y, placement.p, region_to_read,
                    data)
                read_ptr = start_ptr
                length = write_ptr - read_ptr
                data = self._transceiver.read_memory(placement.x, placement.y,
                                                     read_ptr, length)
                self._received_data.flushing_data_from_region(
                    placement.x, placement.y, placement.p, region_to_read,
                    data)

            elif (read_ptr == write_ptr
                  and last_operation == spinn_front_end_constants.
                  BUFFERING_OPERATIONS.BUFFER_READ.value):
                data = bytearray()
                self._received_data.flushing_data_from_region(
                    placement.x, placement.y, placement.p, region_to_read,
                    data)

        # data flush has been completed - return appropriate data
        # the two returns can be exchanged - one returns data and the other
        # returns a pointer to the structure holding the data
        return self._received_data.get_region_data_pointer(
            placement.x, placement.y, placement.p, region_to_read)

    def _retrieve_and_store_data(self, packet, vertex):
        """ Following a SpinnakerRequestReadData packet, the data stored\
           during the simulation needs to be read by the host and stored in a\
           data structure, following the specifications of buffering out\
           technique

        :param packet: SpinnakerRequestReadData packet received from the\
                SpiNNaker system
        :type packet:\
                :py:class:`spinnman.messages.eieio.command_messages.spinnaker_request_read_data.SpinnakerRequestReadData`
        :param vertex: Vertex associated with the read request
        :type vertex:\
                :py:class:`pacman.model.subgraph.subvertex.PartitionedVertex`
        :return: None
        """
        x = packet.x
        y = packet.y
        p = packet.p

        # check packet sequence number
        pkt_seq = packet.sequence_no
        last_pkt_seq = self._received_data.last_sequence_no_for_core(x, y, p)
        next_pkt_seq = (last_pkt_seq + 1) % 256
        if pkt_seq != next_pkt_seq:

            # this sequence number is incorrect
            # re-sent last HostDataRead packet sent
            last_packet_sent = self._received_data.last_sent_packet_to_core(
                x, y, p)
            if last_packet_sent is not None:
                self._transceiver.send_sdp_message(last_packet_sent)
            else:
                raise Exception("Something somewhere went terribly wrong - "
                                "The packet sequence numbers have gone wrong "
                                "somewhere: the packet sent from the board "
                                "has incorrect sequence number, but the host "
                                "never sent one acknowledge - how is this "
                                "possible?")
            return

        # read data from memory, store it and create data for return ACK packet
        n_requests = packet.n_requests
        new_channel = list()
        new_region_id = list()
        new_space_read = list()
        new_n_requests = 0
        for i in xrange(n_requests):
            length = packet.space_to_be_read(i)
            if length > 0:
                new_n_requests += 1
                start_address = packet.start_address(i)
                region_id = packet.region_id(i)
                channel = packet.channel(i)
                data = self._transceiver.read_memory(x, y, start_address,
                                                     length)
                self._received_data.store_data_in_region_buffer(
                    x, y, p, region_id, data)
                new_channel.append(channel)
                new_region_id.append(region_id)
                new_space_read.append(length)

        # create return acknowledge packet with data stored
        ack_packet = HostDataRead(new_n_requests, pkt_seq, new_channel,
                                  new_region_id, new_space_read)
        ack_packet_data = ack_packet.bytestring

        # create SDP header and message
        return_message_header = SDPHeader(
            destination_port=spinn_front_end_constants.SDP_PORTS.
            OUTPUT_BUFFERING_SDP_PORT.value,
            destination_cpu=p,
            destination_chip_x=x,
            destination_chip_y=y,
            flags=SDPFlag.REPLY_NOT_EXPECTED)
        return_message = SDPMessage(return_message_header, ack_packet_data)

        # storage of last packet received
        self._received_data.store_last_received_packet_from_core(
            x, y, p, packet)
        self._received_data.update_sequence_no_for_core(x, y, p, pkt_seq)

        # store last sent message and send to the appropriate core
        self._received_data.store_last_sent_packet_to_core(
            x, y, p, return_message)
        self._transceiver.send_sdp_message(return_message)

    @property
    def sender_vertices(self):
        """ The vertices which are buffered
        :return:
        """
        return self._sender_vertices

    @property
    def reload_buffer_files(self):
        """ The file paths for each buffered region for each sender vertex
        :return:
        """
        return self._reload_buffer_file_paths
class BufferManager(object):
    """ Manager of send buffers
    """

    def __init__(self, placements, tags, transceiver, report_states,
                 application_folder_path):
        """

        :param placements: The placements of the vertices
        :type placements:\
                    :py:class:`pacman.model.placements.placements.Placements`
        :param report_states: the booleans saying what reports are needed
        :type report_states: XXXXXXXXXXX
        :param tags: The tags assigned to the vertices
        :type tags: :py:class:`pacman.model.tags.tags.Tags`
        :param transceiver: The transceiver to use for sending and receiving\
                    information
        :type transceiver: :py:class:`spinnman.transceiver.Transceiver`
        """

        self._placements = placements
        self._tags = tags
        self._transceiver = transceiver

        # params used for reload purposes
        self._report_states = report_states
        self._application_folder_path = application_folder_path
        self._reload_buffer_file = dict()
        self._reload_buffer_file_paths = dict()

        # Set of (ip_address, port) that are being listened to for the tags
        self._seen_tags = set()

        # Set of vertices with buffers to be sent
        self._sender_vertices = set()

        # Dictionary of sender vertex -> buffers sent
        self._sent_messages = dict()

        # storage area for received data from cores
        self._received_data = BufferedReceivingData()

        # Lock to avoid multiple messages being processed at the same time
        self._thread_lock_buffer_out = threading.Lock()
        self._thread_lock_buffer_in = threading.Lock()

        self._finished = False
        # self._file_debug = open("/tmp/buffer_manager_debug", "w", 0)

    def receive_buffer_command_message(self, packet):
        """ Handle an EIEIO command message for the buffers

        :param packet: The eieio message received
        :type packet:\
                    :py:class:`spinnman.messages.eieio.command_messages.eieio_command_message.EIEIOCommandMessage`
        """
        try:
            if not self._finished:
                if isinstance(packet, SpinnakerRequestBuffers):
                    with self._thread_lock_buffer_in:
                        vertex = self._placements.get_subvertex_on_processor(
                            packet.x, packet.y, packet.p)

                        if vertex in self._sender_vertices:

                            # logger.debug(
                            #     "received send request with sequence: {1:d},"
                            #     " space available: {0:d}".format(
                            #         packet.space_available,
                            #         packet.sequence_no))

                            # noinspection PyBroadException
                            try:
                                self._send_messages(
                                    packet.space_available, vertex,
                                    packet.region_id, packet.sequence_no)
                            except Exception:
                                traceback.print_exc()
                elif isinstance(packet, SpinnakerRequestReadData):
                    with self._thread_lock_buffer_out:

                        # logger.debug(
                        #     "received {} read request(s) with sequence: {},"
                        #     " from chip ({},{}, core {}".format(
                        #         packet.n_requests, packet.sequence_no,
                        #         packet.x, packet.y, packet.p))
                        vertex = self._placements.get_subvertex_on_processor(
                            packet.x, packet.y, packet.p)
                        try:
                            self._retrieve_and_store_data(packet, vertex)
                        except Exception:
                            traceback.print_exc()
                elif isinstance(packet, EIEIOCommandMessage):
                    raise SpinnmanInvalidPacketException(
                        str(packet.__class__),
                        "The command packet is invalid for buffer management: "
                        "command id {0:d}".format(packet.eieio_header.command))
                else:
                    raise SpinnmanInvalidPacketException(
                        packet.__class__,
                        "The command packet is invalid for buffer management")
        except Exception:
            traceback.print_exc()

    def add_receiving_vertex(self, vertex):
        """ Add a partitioned vertex into the managed list for vertices\
            which require buffers to be received from them during runtime
        """
        tag = self._tags.get_ip_tags_for_vertex(vertex)[0]
        if (tag.ip_address, tag.port) not in self._seen_tags:
            logger.debug("Listening for receive packets using tag {} on"
                         " {}:{}".format(tag.tag, tag.ip_address, tag.port))
            self._seen_tags.add((tag.ip_address, tag.port))
            self._transceiver.register_udp_listener(
                self.receive_buffer_command_message, UDPEIEIOConnection,
                local_port=tag.port, local_host=tag.ip_address)

    def add_sender_vertex(self, vertex):
        """ Add a partitioned vertex into the managed list for vertices
            which require buffers to be sent to them during runtime

        :param vertex: the vertex to be managed
        :type vertex:\
                    :py:class:`spinnaker.pyNN.models.abstract_models.buffer_models.abstract_sends_buffers_from_host_partitioned_vertex.AbstractSendsBuffersFromHostPartitionedVertex`
        """
        self._sender_vertices.add(vertex)
        tag = self._tags.get_ip_tags_for_vertex(vertex)[0]
        if (tag.ip_address, tag.port) not in self._seen_tags:
            logger.debug("Listening for send packets using tag {} on"
                         " {}:{}".format(tag.tag, tag.ip_address, tag.port))
            self._seen_tags.add((tag.ip_address, tag.port))
            self._transceiver.register_udp_listener(
                self.receive_buffer_command_message, UDPEIEIOConnection,
                local_port=tag.port, local_host=tag.ip_address)

        # if reload script is set up, store the buffers for future usage
        if self._report_states.transciever_report:
            for region in vertex.get_regions():
                filename = "{}_{}".format(
                    re.sub("[\"':]", "_", vertex.label), region)
                file_path = os.path.join(
                    self._application_folder_path, filename)
                self._reload_buffer_file[(vertex, region)] = \
                    open(file_path, "w")
                if vertex not in self._reload_buffer_file_paths:
                    self._reload_buffer_file_paths[vertex] = dict()
                self._reload_buffer_file_paths[vertex][region] = file_path

    def load_initial_buffers(self):
        """ Load the initial buffers for the senders using mem writes
        """
        total_data = 0
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                total_data += vertex.get_region_buffer_size(region)

        progress_bar = ProgressBar(
            total_data, "Loading buffers ({} bytes)".format(total_data))
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                self._send_initial_messages(vertex, region, progress_bar)
        progress_bar.end()

    def reset(self):
        """ Resets the buffered regions to start transmitting from the\
            beginning of its expected regions and clears the buffered out data\
            files
        """
        # reset buffered out
        self._received_data = BufferedReceivingData()

        # rewind buffered in
        for vertex in self._sender_vertices:
            for region in vertex.get_regions():
                vertex.rewind(region)

    def resume(self):
        """ Resets any data structures needed before starting running again
        """
        self._received_data.resume()

    def _create_message_to_send(self, size, vertex, region):
        """ Creates a single message to send with the given boundaries.

        :param size: The number of bytes available for the whole packet
        :type size: int
        :param vertex: The vertex to get the keys from
        :type vertex:\
                    :py:class:`spynnaker.pyNN.models.abstract_models.buffer_models.abstract_sends_buffers_from_host_partitioned_vertex.AbstractSendsBuffersFromHostPartitionedVertex`
        :param region: The region of the vertex to get keys from
        :type region: int
        :return: A new message, or None if no keys can be added
        :rtype: None or\
                    :py:class:`spinnman.messages.eieio.data_messages.eieio_32bit.eieio_32bit_timed_payload_prefix_data_message.EIEIO32BitTimedPayloadPrefixDataMessage`
        """

        # If there are no more messages to send, return None
        if not vertex.is_next_timestamp(region):
            return None

        # Create a new message
        next_timestamp = vertex.get_next_timestamp(region)
        message = EIEIO32BitTimedPayloadPrefixDataMessage(next_timestamp)

        # If there is no room for the message, return None
        if message.size + _N_BYTES_PER_KEY > size:
            return None

        # Add keys up to the limit
        bytes_to_go = size - message.size
        while (bytes_to_go >= _N_BYTES_PER_KEY and
                vertex.is_next_key(region, next_timestamp)):

            key = vertex.get_next_key(region)
            message.add_key(key)
            bytes_to_go -= _N_BYTES_PER_KEY

            if self._report_states.transciever_report:
                self._reload_buffer_file[(vertex, region)].write(
                    "{}:{}\n".format(next_timestamp, key))
        return message

    def _send_initial_messages(self, vertex, region, progress_bar):
        """ Send the initial set of messages

        :param vertex: The vertex to get the keys from
        :type vertex:\
                    :py:class:`spynnaker.pyNN.models.abstract_models.buffer_models.abstract_sends_buffers_from_host_partitioned_vertex.AbstractSendsBuffersFromHostPartitionedVertex`
        :param region: The region to get the keys from
        :type region: int
        :return: A list of messages
        :rtype: list of\
                    :py:class:`spinnman.messages.eieio.data_messages.eieio_32bit.eieio_32bit_timed_payload_prefix_data_message.EIEIO32BitTimedPayloadPrefixDataMessage`
        """

        # Get the vertex load details
        region_base_address = self._locate_region_address(region, vertex)
        placement = self._placements.get_placement_of_subvertex(vertex)

        # Add packets until out of space
        sent_message = False
        bytes_to_go = vertex.get_region_buffer_size(region)
        if bytes_to_go % 2 != 0:
            raise exceptions.SpinnFrontEndException(
                "The buffer region of {} must be divisible by 2".format(
                    vertex))
        all_data = ""
        if vertex.is_empty(region):
            sent_message = True
        else:
            min_size_of_packet = \
                EIEIO32BitTimedPayloadPrefixDataMessage.get_min_packet_length()
            while (vertex.is_next_timestamp(region) and
                    bytes_to_go > min_size_of_packet):
                space_available = min(bytes_to_go, 280)
                next_message = self._create_message_to_send(
                    space_available, vertex, region)
                if next_message is None:
                    break

                # Write the message to the memory
                data = next_message.bytestring
                all_data += data
                sent_message = True

                # Update the positions
                bytes_to_go -= len(data)
                progress_bar.update(len(data))

        if not sent_message:
            raise exceptions.BufferableRegionTooSmall(
                "The buffer size {} is too small for any data to be added for"
                " region {} of vertex {}".format(bytes_to_go, region, vertex))

        # If there are no more messages and there is space, add a stop request
        if (not vertex.is_next_timestamp(region) and
                bytes_to_go >= EventStopRequest.get_min_packet_length()):
            data = EventStopRequest().bytestring
            # logger.debug(
            #    "Writing stop message of {} bytes to {} on {}, {}, {}".format(
            #         len(data), hex(region_base_address),
            #         placement.x, placement.y, placement.p))
            all_data += data
            bytes_to_go -= len(data)
            progress_bar.update(len(data))
            self._sent_messages[vertex] = BuffersSentDeque(
                region, sent_stop_message=True)

        # If there is any space left, add padding
        if bytes_to_go > 0:
            padding_packet = PaddingRequest()
            n_packets = bytes_to_go / padding_packet.get_min_packet_length()
            data = padding_packet.bytestring
            data *= n_packets
            all_data += data

        # Do the writing all at once for efficiency
        self._transceiver.write_memory(
            placement.x, placement.y, region_base_address, all_data)

    def _send_messages(self, size, vertex, region, sequence_no):
        """ Send a set of messages
        """

        # Get the sent messages for the vertex
        if vertex not in self._sent_messages:
            self._sent_messages[vertex] = BuffersSentDeque(region)
        sent_messages = self._sent_messages[vertex]

        # If the sequence number is outside the window, return no messages
        if not sent_messages.update_last_received_sequence_number(sequence_no):
            return list()

        # Remote the existing packets from the size available
        bytes_to_go = size
        for message in sent_messages.messages:
            if isinstance(message.eieio_data_message, EIEIODataMessage):
                bytes_to_go -= message.eieio_data_message.size
            else:
                bytes_to_go -= (message.eieio_data_message
                                .get_min_packet_length())

        # Add messages up to the limits
        while (vertex.is_next_timestamp(region) and
                not sent_messages.is_full and bytes_to_go > 0):

            space_available = min(
                bytes_to_go,
                constants.UDP_MESSAGE_MAX_SIZE -
                HostSendSequencedData.get_min_packet_length())
            # logger.debug(
            #     "Bytes to go {}, space available {}".format(
            #         bytes_to_go, space_available))
            next_message = self._create_message_to_send(
                space_available, vertex, region)
            if next_message is None:
                break
            sent_messages.add_message_to_send(next_message)
            bytes_to_go -= next_message.size
            # logger.debug("Adding additional buffer of {} bytes".format(
            #     next_message.size))

        # If the vertex is empty, send the stop messages if there is space
        if (not sent_messages.is_full and
                not vertex.is_next_timestamp(region) and
                bytes_to_go >= EventStopRequest.get_min_packet_length()):
            sent_messages.send_stop_message()

        # If there are no more messages, turn off requests for more messages
        if not vertex.is_next_timestamp(region) and sent_messages.is_empty():
            # logger.debug("Sending stop")
            self._send_request(vertex, StopRequests())

        # Send the messages
        for message in sent_messages.messages:
            # logger.debug("Sending message with sequence {}".format(
            #     message.sequence_no))
            self._send_request(vertex, message)

    def _locate_region_address(self, region, vertex):
        """ Get the address of a region for a vertex

        :param region: the region to locate the base address of
        :type region: int
        :param vertex: the vertex to load a buffer for
        :type vertex:\
                    :py:class:`spynnaker.pyNN.models.abstract_models.buffer_models.abstract_sends_buffers_from_host_partitioned_vertex.AbstractSendsBuffersFromHostPartitionedVertex`
        :return: None
        """
        placement = self._placements.get_placement_of_subvertex(vertex)
        app_data_base_address = \
            self._transceiver.get_cpu_information_from_core(
                placement.x, placement.y, placement.p).user[0]

        # Get the position of the region in the pointer table
        region_offset_in_pointer_table = \
            dsg_utilities.get_region_base_address_offset(
                app_data_base_address, region)
        region_offset = buffer(self._transceiver.read_memory(
            placement.x, placement.y, region_offset_in_pointer_table, 4))
        return (struct.unpack_from("<I", region_offset)[0] +
                app_data_base_address)

    def _send_request(self, vertex, message):
        """ Sends a request

        :param vertex: The vertex to send to
        :param message: The message to send
        """

        placement = self._placements.get_placement_of_subvertex(vertex)
        sdp_header = SDPHeader(
            destination_chip_x=placement.x, destination_chip_y=placement.y,
            destination_cpu=placement.p, flags=SDPFlag.REPLY_NOT_EXPECTED,
            destination_port=spinn_front_end_constants.SDP_PORTS.
            INPUT_BUFFERING_SDP_PORT.value)
        sdp_message = SDPMessage(sdp_header, message.bytestring)
        self._transceiver.send_sdp_message(sdp_message)

    def stop(self):
        """ Indicates that the simulation has finished, so no further\
            outstanding requests need to be processed
        """
        with self._thread_lock_buffer_in:
            with self._thread_lock_buffer_out:
                self._finished = True
        if self._report_states.transciever_report:
            for buffer_file in self._reload_buffer_file.itervalues():
                buffer_file.close()

    def get_data_for_vertex(self, x, y, p, region_to_read, state_region):
        """ Get a pointer to the data container for all the data retrieved\
            during the simulation from a specific region area of a core

        :param x: x coordinate of the chip
        :type x: int
        :param y: y coordinate of the chip
        :type y: int
        :param p: processor on the specified chip
        :type p: int
        :param region_to_read: desired data region
        :type region_to_read: int
        :param state_region: final state storage region
        :type state_region: int
        :return: pointer to a class which inherits from\
                AbstractBufferedDataStorage
        :rtype:\
                py:class:`spinn_front_end_common.interface.buffer_management.buffer_models.abstract_buffered_data_storage.AbstractBufferedDataStorage`
        """
        # flush data here
        if not self._received_data.is_data_from_region_flushed(
                x, y, p, region_to_read):
            if not self._received_data.is_end_buffering_state_recovered(
                    x, y, p):

                # Get the App Data for the core
                app_data_base_address = \
                    self._transceiver.get_cpu_information_from_core(
                        x, y, p).user[0]

                # Get the position of the buffer
                state_region_base_offset_address = \
                    dsg_utilities.get_region_base_address_offset(
                        app_data_base_address, state_region)
                state_region_base_address_buf = buffer(
                    self._transceiver.read_memory(
                        x, y, state_region_base_offset_address, 4))
                state_region_base_address = struct.unpack_from(
                    "<I", state_region_base_address_buf)[0]
                state_region_base_address += app_data_base_address

                # retrieve channel state memory area
                raw_number_of_channels = self._transceiver.read_memory(
                    x, y, state_region_base_address, 4)
                number_of_channels = struct.unpack(
                    "<I", str(raw_number_of_channels))[0]
                channel_state_data = str(self._transceiver.read_memory(
                    x, y, state_region_base_address,
                    EndBufferingState.size_of_region(number_of_channels)))
                end_buffering_state = EndBufferingState.create_from_bytearray(
                    channel_state_data)
                self._received_data.store_end_buffering_state(
                    x, y, p, end_buffering_state)
            else:
                end_buffering_state = self._received_data.\
                    get_end_buffering_state(x, y, p)

            end_state = end_buffering_state.get_state_for_region(
                region_to_read)
            start_ptr = end_state.start_address
            write_ptr = end_state.current_write
            end_ptr = end_state.end_address
            read_ptr = end_state.current_read

            # current read needs to be adjusted in case the last portion of the
            # memory has already been read, but the HostDataRead packet has not
            # been processed by the chip before simulation finished
            # This situation is identified by the sequence number of the last
            # packet sent to this core and the core internal state of the
            # output buffering finite state machine
            seq_no_last_ack_packet = \
                self._received_data.last_sequence_no_for_core(x, y, p)
            seq_no_internal_fsm = end_buffering_state.buffering_out_fsm_state
            if seq_no_internal_fsm == seq_no_last_ack_packet:

                # if the last ack packet has not been processed on the chip,
                # process it now
                last_sent_ack_sdp_packet = \
                    self._received_data.last_sent_packet_to_core(x, y, p)
                last_sent_ack_packet = create_eieio_command.\
                    read_eieio_command_message(
                        last_sent_ack_sdp_packet.data, 0)
                if not isinstance(last_sent_ack_packet, HostDataRead):
                    raise Exception(
                        "Something somewhere went terribly wrong - "
                        "I was looking for a HostDataRead packet, "
                        "while I got {0:s}".format(last_sent_ack_packet))
                for i in xrange(last_sent_ack_packet.n_requests):
                    if (region_to_read == last_sent_ack_packet.region_id(i) and
                            not end_state.is_state_updated):
                        read_ptr += last_sent_ack_packet.space_read(i)
                        if (read_ptr == write_ptr or
                                (read_ptr == end_ptr and
                                 write_ptr == start_ptr)):
                            end_state.update_last_operation(
                                spinn_front_end_constants.BUFFERING_OPERATIONS.
                                BUFFER_READ.value)
                        if read_ptr == end_ptr:
                            read_ptr = start_ptr
                        elif read_ptr > end_ptr:
                            raise Exception(
                                "Something somewhere went terribly wrong - "
                                "I was reading beyond the region area some "
                                "unknown data".format(
                                    last_sent_ack_packet))
                end_state.update_read_pointer(read_ptr)
                end_state.set_update_completed()

            # now state is updated, read back values for read pointer and
            # last operation performed
            last_operation = end_state.last_buffer_operation
            read_ptr = end_state.current_read

            # now read_ptr is updated, check memory to read
            if read_ptr < write_ptr:
                length = write_ptr - read_ptr
                data = self._transceiver.read_memory(x, y, read_ptr, length)
                self._received_data.flushing_data_from_region(
                    x, y, p, region_to_read, data)

            elif read_ptr > write_ptr:
                length = end_ptr - read_ptr
                data = self._transceiver.read_memory(x, y, read_ptr, length)
                self._received_data.store_data_in_region_buffer(
                    x, y, p, region_to_read, data)
                read_ptr = start_ptr
                length = write_ptr - read_ptr
                data = self._transceiver.read_memory(x, y, read_ptr, length)
                self._received_data.flushing_data_from_region(
                    x, y, p, region_to_read, data)

            elif (read_ptr == write_ptr and
                    last_operation == spinn_front_end_constants.
                    BUFFERING_OPERATIONS.BUFFER_WRITE.value):
                length = end_ptr - read_ptr
                data = self._transceiver.read_memory(x, y, read_ptr, length)
                self._received_data.store_data_in_region_buffer(
                    x, y, p, region_to_read, data)
                read_ptr = start_ptr
                length = write_ptr - read_ptr
                data = self._transceiver.read_memory(x, y, read_ptr, length)
                self._received_data.flushing_data_from_region(
                    x, y, p, region_to_read, data)

            elif (read_ptr == write_ptr and
                    last_operation == spinn_front_end_constants.
                    BUFFERING_OPERATIONS.BUFFER_READ.value):
                data = bytearray()
                self._received_data.flushing_data_from_region(
                    x, y, p, region_to_read, data)

        # data flush has been completed - return appropriate data
        # the two returns can be exchanged - one returns data and the other
        # returns a pointer to the structure holding the data
        return self._received_data.get_region_data_pointer(
            x, y, p, region_to_read)

    def _retrieve_and_store_data(self, packet, vertex):
        """ Following a SpinnakerRequestReadData packet, the data stored\
           during the simulation needs to be read by the host and stored in a\
           data structure, following the specifications of buffering out\
           technique

        :param packet: SpinnakerRequestReadData packet received from the\
                SpiNNaker system
        :type packet:\
                :py:class:`spinnman.messages.eieio.command_messages.spinnaker_request_read_data.SpinnakerRequestReadData`
        :param vertex: Vertex associated with the read request
        :type vertex:\
                :py:class:`pacman.model.subgraph.subvertex.PartitionedVertex`
        :return: None
        """
        x = packet.x
        y = packet.y
        p = packet.p

        # check packet sequence number
        pkt_seq = packet.sequence_no
        last_pkt_seq = self._received_data.last_sequence_no_for_core(x, y, p)
        next_pkt_seq = (last_pkt_seq + 1) % 256
        if pkt_seq != next_pkt_seq:

            # this sequence number is incorrect
            # re-sent last HostDataRead packet sent
            last_packet_sent = self._received_data.last_sent_packet_to_core(
                x, y, p)
            if last_packet_sent is not None:
                self._transceiver.send_sdp_message(last_packet_sent)
            else:
                raise Exception("Something somewhere went terribly wrong - "
                                "The packet sequence numbers have gone wrong "
                                "somewhere: the packet sent from the board "
                                "has incorrect sequence number, but the host "
                                "never sent one acknowledge - how is this "
                                "possible?")
            return

        # read data from memory, store it and create data for return ack packet
        n_requests = packet.n_requests
        new_channel = list()
        new_region_id = list()
        new_space_read = list()
        new_n_requests = 0
        for i in xrange(n_requests):
            length = packet.space_to_be_read(i)
            if length > 0:
                new_n_requests += 1
                start_address = packet.start_address(i)
                region_id = packet.region_id(i)
                channel = packet.channel(i)
                data = self._transceiver.read_memory(
                    x, y, start_address, length)
                self._received_data.store_data_in_region_buffer(
                    x, y, p, region_id, data)
                new_channel.append(channel)
                new_region_id.append(region_id)
                new_space_read.append(length)

        # create return acknowledge packet with data stored
        ack_packet = HostDataRead(
            new_n_requests, pkt_seq, new_channel, new_region_id,
            new_space_read)
        ack_packet_data = ack_packet.bytestring

        # create SDP header and message
        return_message_header = SDPHeader(
            destination_port=spinn_front_end_constants.SDP_PORTS.
            OUTPUT_BUFFERING_SDP_PORT.value,
            destination_cpu=p, destination_chip_x=x, destination_chip_y=y,
            flags=SDPFlag.REPLY_NOT_EXPECTED)
        return_message = SDPMessage(return_message_header, ack_packet_data)

        # storage of last packet received
        self._received_data.store_last_received_packet_from_core(
            x, y, p, packet)
        self._received_data.update_sequence_no_for_core(x, y, p, pkt_seq)

        # store last sent message and send to the appropriate core
        self._received_data.store_last_sent_packet_to_core(
            x, y, p, return_message)
        self._transceiver.send_sdp_message(return_message)

    @property
    def sender_vertices(self):
        """ The vertices which are buffered
        :return:
        """
        return self._sender_vertices

    @property
    def reload_buffer_files(self):
        """ The file paths for each buffered region for each sender vertex
        :return:
        """
        return self._reload_buffer_file_paths