Repository: pydicom/pynetdicom

Issue:
`send_c_find` doesn't respect `dimse_timeout`
In cases where `dimse_timeout` is set, the expected behaviour when no response comes from the peer is to exit. This is handled in `dimse.py:284` by the timer. However, `None, None` is returned regardless of the error which caused no message to occur, leading the loop in `send_c_find` to repeat infinitely (`association.py:1138`).
In general, this repeating until a message arrives no matter what seems dubious in other cases as well. Most of the errors which cause `recieve_msg` to return `None, None` seem to be incorrigible - dul is dead, message is malformed or not `P_DATA`, etc.

Discussion:

Patch:
diff --git a/docs/changelog/v1.2.0.rst b/docs/changelog/v1.2.0.rst
index e7e28c5d4..50561031a 100644
--- a/docs/changelog/v1.2.0.rst
+++ b/docs/changelog/v1.2.0.rst
@@ -8,6 +8,12 @@ Fixes
 * Fixed `network_timeout` not working when acting as the association requestor
   and an A-ABORT not being sent when the timeout expired. (:issue:`286`)
 * Fixed changing `network_timeout` value not propagating to the actual timer.
+* All DIMSE service requests should now properly abort the association if no
+  response is received within the DIMSE timeout window and return/yield
+  expected values. (:issue:`119`)
+* Any outgoing primitives are now sent to the peer before incoming PDUs are
+  processed (:issue:`120`)
+
 
 Enhancements
 ............
@@ -38,6 +44,12 @@ Enhancements
     destination for the request's C-STORE sub-operations over a new association
     within the same AE instance by running a non-blocking Storage SCP.
     (:issue:`5`, :issue:`181`)
+* Received P-DATA primitives are now processed when received rather than when
+  processing is requested.
+* Added ``DIMSEServiceProvider.get_msg()``,
+  ``DIMSEServiceProvider.receive_primitive()``,
+  ``DIMSEServiceProvider.peek_msg()`` and ``DIMSEServiceProvider.msg_queue``
+  to be used with the new P-DATA processing.
 
 Changes
 .......
@@ -66,3 +78,4 @@ Changes
   returns a list of all the AE's active Association threads.
 * ``AE.associate()`` is now synchronous until association negotiation has
   completed or the connection closed (:issue:`108`).
+* Removed ``DIMSEServiceProvider.receive_msg()``.
diff --git a/pynetdicom/acse.py b/pynetdicom/acse.py
index d27cd0dab..6a341b2e6 100644
--- a/pynetdicom/acse.py
+++ b/pynetdicom/acse.py
@@ -355,7 +355,10 @@ class ACSE(object):
         )
 
         # pylint: disable=protected-access
-        assoc._accepted_cx = [cx for cx in result if cx.result == 0x00]
+        # Accepted contexts are stored as {context ID : context}
+        assoc._accepted_cx = {
+            cx.context_id:cx for cx in result if cx.result == 0x00
+        }
         assoc._rejected_cx = [cx for cx in result if cx.result != 0x00]
         # pylint: enable=protected-access
 
@@ -431,9 +434,11 @@ class ACSE(object):
                 )
 
                 # pylint: disable=protected-access
-                assoc._accepted_cx = [
-                    cx for cx in negotiated_contexts if cx.result == 0x00
-                ]
+                # Accepted contexts are stored as {context ID : context}
+                assoc._accepted_cx = {
+                    cx.context_id:cx
+                    for cx in negotiated_contexts if cx.result == 0x00
+                }
                 assoc._rejected_cx = [
                     cx for cx in negotiated_contexts if cx.result != 0x00
                 ]
diff --git a/pynetdicom/association.py b/pynetdicom/association.py
index c2c5bcd69..603130477 100644
--- a/pynetdicom/association.py
+++ b/pynetdicom/association.py
@@ -152,8 +152,10 @@ class Association(threading.Thread):
 
         # Kills the thread loop in run()
         self._kill = False
+        # Flag for whether or not the DUL thread has been started
+        self._started_dul = False
 
-        # Send A-ABORT when an A-ASSOCIATE request is received
+        # Send A-ABORT/A-P-ABORT when an A-ASSOCIATE request is received
         self._a_abort_assoc_rq = False
         self._a_p_abort_assoc_rq = False
 
@@ -178,7 +180,8 @@ class Association(threading.Thread):
     @property
     def accepted_contexts(self):
         """Return a list of accepted Presentation Contexts."""
-        return self._accepted_cx
+        # Accepted contexts are stored internally as {context ID : context}
+        return sorted(self._accepted_cx.values(), key=lambda x: x.context_id)
 
     @property
     def acse_timeout(self):
@@ -191,6 +194,17 @@ class Association(threading.Thread):
         self.dul.artim_timer.timeout = value
         self._acse_timeout = value
 
+    @property
+    def dimse_timeout(self):
+        """Return the DIMSE timeout in seconds."""
+        return self._dimse_timeout
+
+    @dimse_timeout.setter
+    def dimse_timeout(self, value):
+        """Set the DIMSE timeout using numeric or None."""
+        self.dimse.dimse_timeout = value
+        self._dimse_timeout = value
+
     @property
     def network_timeout(self):
         """Return the network timeout in seconds."""
@@ -392,6 +406,7 @@ class Association(threading.Thread):
         """
         # Start the DUL thread if not already started
         self.dul.start()
+        self._started_dul = True
         # Give the DUL time to start up, tends to cause intermittent
         # test failures otherwise
         time.sleep(0.05)
@@ -401,8 +416,9 @@ class Association(threading.Thread):
     def run(self):
         """The main Association control."""
         # Start the DUL thread if not already started
-        if not self.dul.is_alive():
+        if not self._started_dul:
             self.dul.start()
+            self._started_dul = True
             # Give the DUL time to start up, tends to cause intermittent
             # test failures otherwise
             time.sleep(0.05)
@@ -471,13 +487,11 @@ class Association(threading.Thread):
         while not self._kill:
             time.sleep(0.001)
 
-            # Check with the DIMSE provider for incoming messages
-            #   all messages should be a DIMSEMessage subclass
-            msg, msg_context_id = self.dimse.receive_msg(wait=False)
+            # Check with the DIMSE provider to see if a completely decoded
+            #   message is available
+            msg_context_id, msg = self.dimse.get_msg(block=False)
 
-            # DIMSE message received
-            # TODO: This is not a good way to handle messages, they should
-            #   be managed via their message ID and/or SOP Class UIDs
+            # DIMSE message received, should be a service request
             if msg:
                 # Use the Message's Affected SOP Class UID or Requested SOP
                 #   Class UID to determine which service to use
@@ -490,14 +504,10 @@ class Association(threading.Thread):
                     # N-GET, N-SET, N-ACTION, N-DELETE use RequestedSOPClassUID
                     class_uid = msg.RequestedSOPClassUID
                 else:
-                    # FIXME: C-CANCEL requests are not being handled correctly
-                    #   need a way to identify which service it belongs to
-                    #   or maybe just call the callback now?
-                    LOGGER.warning(
-                        "C-CANCEL requests are not implemented"
-                    )
-                    self.abort()
-                    return
+                    # Received a C-CANCEL request outside of service
+                    #   operation, ignore it
+                    LOGGER.warning("Received unexpected C-CANCEL request")
+                    continue
 
                 # SOP Class Common Extended Negotiation
                 try:
@@ -511,25 +521,28 @@ class Association(threading.Thread):
                 # Convert the SOP/Service UID to the corresponding service
                 service_class = uid_to_service_class(class_uid)(self)
 
-                # TODO: Index contexts in a dict using context ID
-                for context in self.accepted_contexts:
-                    if context.context_id == msg_context_id:
-                        # Run corresponding Service Class in SCP mode
-                        try:
-                            service_class.SCP(msg, context, info)
-                        except NotImplementedError:
-                            # SCP isn't implemented
-                            LOGGER.warning(
-                                "No service class implementation for '{}'"
-                                .format(context.abstract_syntax)
-                            )
-                            self.abort()
-                            return
-                        break
-                else:
-                    LOGGER.info("Received message with invalid or rejected "
-                                "context ID %d", msg_context_id)
+                try:
+                    context = self._accepted_cx[msg_context_id]
+                except KeyError:
+                    LOGGER.info(
+                        "Received DIMSE message with invalid or rejected "
+                        "context ID: %d", msg_context_id
+                    )
                     LOGGER.debug("%s", msg)
+                    self.abort()
+                    return
+
+                # Run corresponding Service Class in SCP mode
+                try:
+                    service_class.SCP(msg, context, info)
+                except NotImplementedError:
+                    # SCP isn't implemented
+                    LOGGER.warning(
+                        "No service class implementation for '{}'"
+                        .format(context.abstract_syntax)
+                    )
+                    self.abort()
+                    return
 
             # Check for release request
             if self.acse.is_release_requested(self):
@@ -790,12 +803,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then returns an
-            empty ``Dataset``. If a valid response was received from the peer
-            then returns a ``Dataset`` containing at least a (0000,0900)
-            *Status* element, and, depending on the returned Status value, may
-            optionally contain additional elements (see DICOM Standard Part 7,
-            Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a valid response was received from
+            the peer then returns a ``Dataset`` containing at least a
+            (0000,0900) *Status* element, and, depending on the returned
+            Status value, may optionally contain additional elements (see
+            DICOM Standard Part 7, Annex C).
 
             The DICOM Standard Part 7, Table 9.3-13 indicates that the Status
             value of a C-ECHO response "shall have a value of Success". However
@@ -854,7 +867,14 @@ class Association(threading.Thread):
 
         # Send C-ECHO request to the peer via DIMSE and wait for the response
         self.dimse.send_msg(primitive, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset()
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
@@ -920,12 +940,12 @@ class Association(threading.Thread):
         Yields
         ------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see the DICOM Standard, Part 7,
-            Section 9.1.2.1.5 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            yields an empty ``Dataset``. If a response was received from the
+            peer then yields a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see the DICOM Standard,
+            Part 7, Section 9.1.2.1.5 and Annex C).
 
             The status for the requested C-FIND operation should be one of the
             following values, but as the returned value depends
@@ -1092,18 +1112,21 @@ class Association(threading.Thread):
         ii = 1
         while True:
             # Wait for C-FIND response
-            rsp, _ = self.dimse.receive_msg(wait=True)
+            cx_id, rsp = self.dimse.get_msg(block=True)
 
-            # If no response received, start loop again
-            if not rsp:
-                LOGGER.error("Connection closed or timed-out")
-                self.abort()
+            # If `rsp` is None then the DIMSE timeout expired so abort
+            if rsp is None:
+                if self.is_established:
+                    LOGGER.error("Connection closed or timed-out")
+                    self.abort()
+                yield Dataset(), None
                 return
             elif not rsp.is_valid_response:
                 LOGGER.error(
                     'Received an invalid C-FIND response from the peer'
                 )
                 self.abort()
+                yield Dataset(), None
                 return
 
             # Status may be 'Failure', 'Cancel', 'Success' or 'Pending'
@@ -1206,12 +1229,12 @@ class Association(threading.Thread):
         Yields
         ------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value may optionally contain
-            additional elements (see DICOM Standard Part 7, Section 9.1.2.1.5
-            and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            yields an empty ``Dataset``. If a response was received from the
+            peer then yields a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value may
+            optionally contain additional elements (see DICOM Standard Part 7,
+            Section 9.1.2.1.5 and Annex C).
 
             The status for the requested C-GET operation should be one of the
             following values, but as the returned value depends on the
@@ -1369,12 +1392,14 @@ class Association(threading.Thread):
         while True:
             # Wait for DIMSE message, may be either a C-GET response or a
             #   C-STORE request
-            rsp, _ = self.dimse.receive_msg(wait=True)
+            cx_id, rsp = self.dimse.get_msg(block=True)
 
-            # If nothing received from the peer, try again
-            if not rsp:
-                LOGGER.error("Connection closed or timed-out")
-                self.abort()
+            # If `rsp` is None then the DIMSE timeout expired so abort
+            if rsp is None:
+                if self.is_established:
+                    LOGGER.error("Connection closed or timed-out")
+                    self.abort()
+                yield Dataset(), None
                 return
 
             # Received a C-GET response from the peer
@@ -1508,12 +1533,12 @@ class Association(threading.Thread):
         Yields
         ------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see DICOM Standard Part 7, Section
-            9.1.4 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            yields an empty ``Dataset``. If a response was received from the
+            peer then yields a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see DICOM Standard Part 7,
+            Section 9.1.4 and Annex C).
 
             The status for the requested C-MOVE operation should be one of the
             following values, but as the returned value depends
@@ -1656,12 +1681,14 @@ class Association(threading.Thread):
         # Get the responses from peer
         operation_no = 1
         while True:
-            rsp, _ = self.dimse.receive_msg(wait=True)
+            cx_id, rsp = self.dimse.get_msg(block=True)
 
-            # If nothing received from the peer, try again
-            if not rsp:
-                LOGGER.error("Connection closed or timed-out")
-                self.abort()
+            # If `rsp` is None then the DIMSE timeout expired so abort
+            if rsp is None:
+                if self.is_established:
+                    LOGGER.error("Connection closed or timed-out")
+                    self.abort()
+                yield Dataset(), None
                 return
 
             # Received a C-MOVE response from the peer
@@ -1767,12 +1794,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then returns an
-            empty ``Dataset``. If a valid response was received from the peer
-            then returns a ``Dataset`` containing at least a (0000,0900)
-            *Status* element, and, depending on the returned value, may
-            optionally contain additional elements (see DICOM Standard Part 7,
-            Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a valid response was received
+            from the peer then returns a ``Dataset`` containing at least a
+            (0000,0900) *Status* element, and, depending on the returned
+            value, may optionally contain additional elements (see DICOM
+            Standard Part 7, Annex C).
 
             The status for the requested C-STORE operation should be one of the
             following, but as the value depends on the peer SCP this can't be
@@ -1901,7 +1928,14 @@ class Association(threading.Thread):
 
         # Send C-STORE request to the peer via DIMSE and wait for the response
         self.dimse.send_msg(req, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset()
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
@@ -1933,12 +1967,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see the DICOM Standard, Part 7,
-            Section 9.1.2.1.5 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a response was received from the
+            peer then returns a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see the DICOM Standard,
+            Part 7, Section 9.1.2.1.5 and Annex C).
 
             General N-ACTION (DICOM Standard Part 7, Section 10.1.4 and
             Annex C)
@@ -2020,7 +2054,14 @@ class Association(threading.Thread):
 
         # Send N-ACTION request to the peer via DIMSE and wait for the response
         self.dimse.send_msg(req, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset(), None
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
@@ -2068,12 +2109,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see the DICOM Standard, Part 7,
-            Section 9.1.2.1.5 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a response was received from the
+            peer then returns a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see the DICOM Standard,
+            Part 7, Section 9.1.2.1.5 and Annex C).
 
             General N-CREATE (DICOM Standard Part 7, Section 10.1.5 and
             Annex C)
@@ -2153,7 +2194,14 @@ class Association(threading.Thread):
 
         # Send N-CREATE request to the peer via DIMSE and wait for the response
         self.dimse.send_msg(req, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset(), None
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
@@ -2198,12 +2246,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see the DICOM Standard, Part 7,
-            Section 9.1.2.1.5 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a response was received from the
+            peer then returns a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see the DICOM Standard,
+            Part 7, Section 9.1.2.1.5 and Annex C).
 
             General N-DELETE (DICOM Standard Part 7, Section 10.1.6 and
             Annex C)
@@ -2260,7 +2308,14 @@ class Association(threading.Thread):
 
         # Send N-DELETE request to the peer via DIMSE and wait for the response
         self.dimse.send_msg(req, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset()
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
@@ -2292,12 +2347,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see the DICOM Standard, Part 7,
-            Section 9.1.2.1.5 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a response was received from the
+            peer then returns a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see the DICOM Standard,
+            Part 7, Section 9.1.2.1.5 and Annex C).
 
             General N-EVENT-REPORT (DICOM Standard Part 7, Section 10.1.1
             and Annex C)
@@ -2381,7 +2436,14 @@ class Association(threading.Thread):
         # Send N-EVENT-REPORT request to the peer via DIMSE and wait for
         # the response primitive
         self.dimse.send_msg(req, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset(), None
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
@@ -2431,12 +2493,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see the DICOM Standard, Part 7,
-            Section 9.1.2.1.5 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a response was received from the
+            peer then returns a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see the DICOM Standard,
+            Part 7, Section 9.1.2.1.5 and Annex C).
 
             General N-GET (DICOM Standard Part 7, Section 10.1.2 and Annex C)
 
@@ -2509,7 +2571,14 @@ class Association(threading.Thread):
 
         # Send N-GET request to the peer via DIMSE and wait for the response
         self.dimse.send_msg(req, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset(), None
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
@@ -2557,12 +2626,12 @@ class Association(threading.Thread):
         Returns
         -------
         status : pydicom.dataset.Dataset
-            If the peer timed out or sent an invalid response then yields an
-            empty ``Dataset``. If a response was received from the peer then
-            yields a ``Dataset`` containing at least a (0000,0900) *Status*
-            element, and depending on the returned value, may optionally
-            contain additional elements (see the DICOM Standard, Part 7,
-            Section 9.1.2.1.5 and Annex C).
+            If the peer timed out, aborted or sent an invalid response then
+            returns an empty ``Dataset``. If a response was received from the
+            peer then returns a ``Dataset`` containing at least a (0000,0900)
+            *Status* element, and depending on the returned value, may
+            optionally contain additional elements (see the DICOM Standard,
+            Part 7, Section 9.1.2.1.5 and Annex C).
 
             General N-SET (DICOM Standard Part 7, Section 10.1.3 and Annex C)
 
@@ -2647,7 +2716,14 @@ class Association(threading.Thread):
 
         # Send N-SET request to the peer via DIMSE and wait for the response
         self.dimse.send_msg(req, context.context_id)
-        rsp, _ = self.dimse.receive_msg(wait=True)
+        cx_id, rsp = self.dimse.get_msg(block=True)
+
+        # If `rsp` is None then the DIMSE timeout expired so abort
+        if rsp is None:
+            if self.is_established:
+                LOGGER.error("Connection closed or timed-out")
+                self.abort()
+            return Dataset(), None
 
         # Determine validity of the response and get the status
         status = self._check_received_status(rsp)
diff --git a/pynetdicom/dimse.py b/pynetdicom/dimse.py
index 7678976de..1790187e5 100644
--- a/pynetdicom/dimse.py
+++ b/pynetdicom/dimse.py
@@ -3,6 +3,10 @@ Implementation of the DIMSE service provider.
 """
 from io import BytesIO
 import logging
+try:
+    import queue
+except ImportError:
+    import Queue as queue  # Python 2 compatibility
 import time
 
 # pylint: disable=no-name-in-module
@@ -200,11 +204,85 @@ class DIMSEServiceProvider(object):
             None indicates no timeout.
         maximum_pdu_size : int
             The maximum PDU size when sending DIMSE messages, default 16382.
+        msg_queue: queue.queue of dimse_messages.DIMSEMessage
+            A queue holding decoded DIMSE Message primitives received from the
+            peer.
         """
         self.dimse_timeout = dimse_timeout
         self.dul = dul
         self.maximum_pdu_size = maximum_pdu_size
         self.message = None
+        self.msg_queue = queue.Queue()
+
+    def get_msg(self, block=False):
+        """Get the next available DIMSE message.
+
+        Parameters
+        ----------
+        block : bool
+            If True then the function will block until either a message is
+            available or `dimse_timeout` expires, otherwise non-blocking.
+
+        Returns
+        -------
+        int, dimse_messages.DIMSEMessage or None, None
+            The next available (context ID, DIMSE message), which is taken off
+            the queue, or (None, None) if no messages are available within
+            the `dimse_timeout` period.
+        """
+        try:
+            return self.msg_queue.get(block=block, timeout=self.dimse_timeout)
+        except queue.Empty:
+            return None, None
+
+    def peek_msg(self):
+        """Return the first message in the message queue or None.
+
+        Returns
+        -------
+        int, dimse_messages.DIMSEMessage or None, None
+            The first (context ID, message) in the queue if one is available,
+            otherwise (None, None). No messages are taken out of the queue.
+        """
+        try:
+            return self.msg_queue.queue[0]
+        except (queue.Empty, IndexError):
+            return None, None
+
+    def receive_primitive(self, primitive):
+        """Process a P-DATA primitive received from the remote.
+
+        A DIMSE message is split into one or more P-DATA primitives, which
+        must be sent in sequential order. While waiting for all the P-DATA
+        primitives associated with a message the encoded data is stored in
+        self.message, which is decoded only when complete and converted
+        into a DIMSE Message primitive which is added to the `msg_queue`.
+
+        This makes it possible to process incoming P-DATA primitives into
+        DIMSE messages while a service class implementation is running.
+
+        Parameters
+        ----------
+        primitive : pdu_primitives.P_DATA
+            A P-DATA primitive received from the peer to be processed.
+        """
+        if self.message is None:
+            self.message = DIMSEMessage()
+
+        if self.message.decode_msg(primitive):
+            # Callback
+            # TODO: Make this a package level option to increase speed
+            self.on_receive_dimse_message(self.message)
+
+            context_id = self.message.context_id
+            primitive = self.message.message_to_primitive()
+            self.msg_queue.put((context_id, primitive))
+
+            # Fix for memory leak, Issue #41
+            #   Reset the DIMSE message, ready for the next one
+            self.message.encoded_command_set = BytesIO()
+            self.message.data_set = BytesIO()
+            self.message = None
 
     def send_msg(self, primitive, context_id):
         """Send a DIMSE-C or DIMSE-N message to the peer AE.
@@ -234,100 +312,6 @@ class DIMSEServiceProvider(object):
         for pdata in dimse_msg.encode_msg(context_id, self.maximum_pdu_size):
             self.dul.send_pdu(pdata)
 
-    def receive_msg(self, wait=False):
-        """Receive a DIMSE message from the peer.
-
-        Set the DIMSE provider in a mode ready to receive a response from the
-        peer
-
-        Parameters
-        ----------
-        wait : bool, optional
-            Wait until a response has been received (default: False).
-
-        Returns
-        -------
-        dimse_messages.DIMSEMessage, int or None, None
-            Returns the complete DIMSE message and its presentation context ID
-            or None, None.
-        """
-        if self.message is None:
-            self.message = DIMSEMessage()
-
-        timeout = Timer(self.dimse_timeout)
-        timeout.start()
-
-        if wait:
-            # Loop until complete DIMSE message is received
-            #   message may be split into 1 or more fragments
-            while True:
-
-                # Fix for issue #38
-                # Because we only progress once the next PDU arrives to be
-                #   peeked at, the DIMSE timeout in receive_pdu() doesn't
-                #   actually do anything.
-                if timeout.expired:
-                    return None, None
-
-                # Race condition: sometimes the DUL will be killed before the
-                #   loop exits
-                if not self.dul.is_alive():
-                    return None, None
-
-                time.sleep(0.001)
-
-                nxt = self.dul.peek_next_pdu()
-                if nxt is None:
-                    continue
-
-                if nxt.__class__ is not P_DATA:
-                    continue
-
-                pdu = self.dul.receive_pdu(wait, self.dimse_timeout)
-
-                if self.message.decode_msg(pdu):
-                    # Callback
-                    # TODO: Make this a package level option to increase speed
-                    self.on_receive_dimse_message(self.message)
-
-                    context_id = self.message.context_id
-                    primitive = self.message.message_to_primitive()
-
-                    # Fix for memory leak, Issue #41
-                    #   Reset the DIMSE message, ready for the next one
-                    self.message.encoded_command_set = BytesIO()
-                    self.message.data_set = BytesIO()
-                    self.message = None
-
-                    return primitive, context_id
-
-        else:
-            # Check to make sure the next PDU is a P-DATA-TF PDU
-            #   if not then return
-            pdu = self.dul.peek_next_pdu()
-            if not pdu or pdu.__class__ != P_DATA:
-                return None, None
-
-            pdu = self.dul.receive_pdu(wait, self.dimse_timeout)
-
-            if self.message.decode_msg(pdu):
-                # Callback
-                # TODO: Make this a package level option to increase speed
-                self.on_receive_dimse_message(self.message)
-
-                context_id = self.message.context_id
-                primitive = self.message.message_to_primitive()
-
-                # Fix for memory leak, Issue #41
-                #   Reset the DIMSE message, ready for the next one
-                self.message.encoded_command_set = BytesIO()
-                self.message.data_set = BytesIO()
-                self.message = None
-
-                return primitive, context_id
-
-            return None, None
-
     # Debugging and AE callbacks
     def on_send_dimse_message(self, message):
         """Controls which debugging function is called when sending.
diff --git a/pynetdicom/dul.py b/pynetdicom/dul.py
index f9c1c337e..767b210aa 100644
--- a/pynetdicom/dul.py
+++ b/pynetdicom/dul.py
@@ -386,10 +386,10 @@ class DULServiceProvider(Thread):
 
             # Check the connection for incoming data
             try:
-                if self._is_transport_event():
-                    self._idle_timer.restart()
-                elif self._check_incoming_primitive():
+                if self._check_incoming_primitive():
                     pass
+                elif self._is_transport_event():
+                    self._idle_timer.restart()
 
                 if self._is_artim_expired():
                     self._kill_thread = True
diff --git a/pynetdicom/fsm.py b/pynetdicom/fsm.py
index c5eb2c6e0..2d3abd4a0 100644
--- a/pynetdicom/fsm.py
+++ b/pynetdicom/fsm.py
@@ -459,8 +459,8 @@ def DT_2(dul):
     str
         Sta6, the next state of the state machine
     """
-    # Send P-DATA indication primitive to DUL
-    dul.to_user_queue.put(dul.primitive)
+    # Send P-DATA indication primitive directly to DIMSE for processing
+    dul.assoc.dimse.receive_primitive(dul.primitive)
 
     return 'Sta6'
 


Test patch:
diff --git a/pynetdicom/tests/parrot.py b/pynetdicom/tests/parrot.py
new file mode 100644
index 000000000..8aba89223
--- /dev/null
+++ b/pynetdicom/tests/parrot.py
@@ -0,0 +1,228 @@
+"""The Parrot testing server."""
+try:
+    import queue
+except ImportError:
+    import Queue as queue  # Python 2 compatibility
+import select
+import socket
+try:
+    from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
+except ImportError:
+    from socketserver import TCPServer, ThreadingMixIn, BaseRequestHandler
+from struct import pack, unpack
+import threading
+import time
+
+from pynetdicom.transport import AssociationServer
+
+
+def start_server(commands):
+    server = ThreadedParrot(('', 11112), commands)
+    thread = threading.Thread(target=server.serve_forever)
+    thread.daemon = True
+    thread.start()
+
+    return server
+
+
+class ParrotRequest(BaseRequestHandler):
+    @property
+    def commands(self):
+        """Return a list of command tuples."""
+        return self.server.commands
+
+    @property
+    def socket(self):
+        """Return the socket to the remote."""
+        return self.request
+
+    @property
+    def ready(self):
+        """Return True if there is data available to be read.
+
+        Returns
+        -------
+        bool
+            True if the socket has data ready to be read, False otherwise.
+        """
+        try:
+            # Use a timeout of 0 so we get an "instant" result
+            ready, _, _ = select.select([self.socket], [], [], 0.5)
+        except (socket.error, socket.timeout, ValueError):
+            return False
+
+        return bool(ready)
+
+    @property
+    def read_data(self):
+        bytestream = bytearray()
+
+        # Try and read the PDU type and length from the socket
+        try:
+            bytestream.extend(self.recv(6))
+        except (socket.error, socket.timeout):
+            pass
+        try:
+            # Byte 1 is always the PDU type
+            # Byte 2 is always reserved
+            # Bytes 3-6 are always the PDU length
+            pdu_type, _, pdu_length = unpack('>BBL', bytestream)
+        except struct.error:
+            pass
+
+        # Try and read the rest of the PDU
+        try:
+            bytestream += self.recv(pdu_length)
+        except (socket.error, socket.timeout):
+            pass
+
+        return bytestream
+
+    def recv(self, nr_bytes):
+        """Read `nr_bytes` from the socket.
+
+        *Events Emitted*
+
+        - None
+
+        Parameters
+        ----------
+        nr_bytes : int
+            The number of bytes to attempt to read from the socket.
+
+        Returns
+        -------
+        bytearray
+            The data read from the socket.
+        """
+        bytestream = bytearray()
+        nr_read = 0
+        # socket.recv() returns when the network buffer has been emptied
+        #   not necessarily when the number of bytes requested have been
+        #   read. Its up to us to keep calling recv() until we have all the
+        #   data we want
+        # **BLOCKING** until either all the data is read or an error occurs
+        while nr_read < nr_bytes:
+            # Python docs recommend reading a relatively small power of 2
+            #   such as 4096
+            bufsize = 4096
+            if (nr_bytes - nr_read) < bufsize:
+                bufsize = nr_bytes - nr_read
+
+            bytes_read = self.socket.recv(bufsize)
+
+            # If socket.recv() reads 0 bytes then the connection has been
+            #   broken, so return what we have so far
+            if not bytes_read:
+                return bytestream
+
+            bytestream.extend(bytes_read)
+            nr_read += len(bytes_read)
+
+        return bytestream
+
+    def send(self, bytestream):
+        """Try and send `bystream` to the remote.
+
+        Parameters
+        ----------
+        bytestream : bytes
+            The data to send to the remote.
+
+        Returns
+        -------
+        bool
+            True if the data was sent successfully, False otherwise.
+        """
+        total_sent = 0
+        length_data = len(bytestream)
+        try:
+            while total_sent < length_data:
+                # Returns the number of bytes sent
+                nr_sent = self.socket.send(bytestream)
+                total_sent += nr_sent
+        except (socket.error, socket.timeout):
+            # Evt17: Transport connection closed
+            return False
+
+        return True
+
+    def handle(self):
+        # self.server: ThreadedParrot
+        # self.client_address: remote's (host, port)
+        # self.request: socket
+        self.received = []
+        self.sent = []
+        for (cmd, data) in self.commands:
+            pass
+            if cmd == 'recv':
+                self.kill_read = False
+                while not self.kill_read:
+                    if self.ready:
+                        self.received.append(bytes(self.read_data))
+                        self.kill_read = True
+            elif cmd == 'send':
+                self.send(data)
+                self.sent.append(data)
+            elif cmd == 'wait':
+                time.sleep(data)
+
+        # Disconnects automatically when this method ends!
+
+
+class Parrot(AssociationServer):
+    def __init__(self, address, commands):
+        """Create a new AssociationServer, bind a socket and start listening.
+
+        Parameters
+        ----------
+        ae : ae.ApplicationEntity
+            The parent AE that's running the server.
+        address : 2-tuple
+            The (host, port) that the server should run on.
+        ssl_context : ssl.SSLContext, optional
+            If TLS is to be used then this should be the ssl.SSLContext used
+            to wrap the client sockets, otherwise if None then no TLS will be
+            used (default).
+        """
+        self.commands = commands
+        self.ssl_context = None
+        self.allow_reuse_address = True
+
+        TCPServer.__init__(
+            self, address, ParrotRequest, bind_and_activate=True
+        )
+
+        self.timeout = 60
+        self.handlers = []
+
+    def server_bind(self):
+        """Bind the socket and set the socket options.
+
+        - socket.SO_REUSEADDR is set to 1
+        - socket.SO_RCVTIMEO is set to AE.network_timeout unless the value is
+          None in which case it will be left unset.
+        """
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.socket.setsockopt(socket.SOL_SOCKET,
+                               socket.SO_RCVTIMEO,
+                               pack('ll', 2, 0))
+
+        self.socket.bind(self.server_address)
+        self.server_address = self.socket.getsockname()
+
+    def finish_request(self, request, client_address):
+        """Finish one request by instantiating RequestHandlerClass."""
+        handler = self.RequestHandlerClass(request, client_address, self)
+        self.handlers.append(handler)
+
+    def shutdown(self):
+        """Completely shutdown the server and close it's socket."""
+        TCPServer.shutdown(self)
+        self.server_close()
+
+    process_request = TCPServer.process_request
+
+
+class ThreadedParrot(ThreadingMixIn, Parrot):
+    process_request_thread = ThreadingMixIn.process_request_thread
diff --git a/pynetdicom/tests/test_assoc.py b/pynetdicom/tests/test_assoc.py
index e6588a98d..63df02da5 100644
--- a/pynetdicom/tests/test_assoc.py
+++ b/pynetdicom/tests/test_assoc.py
@@ -7,6 +7,7 @@ import os
 import select
 import socket
 from struct import pack
+import sys
 import time
 import threading
 
@@ -77,10 +78,13 @@ from .dummy_c_scp import (
     DummyVerificationSCP, DummyStorageSCP, DummyFindSCP, DummyGetSCP,
     DummyMoveSCP, DummyBaseSCP
 )
+from .encoded_pdu_items import a_associate_ac
+from .parrot import start_server, ThreadedParrot
+
 
 LOGGER = logging.getLogger('pynetdicom')
 LOGGER.setLevel(logging.CRITICAL)
-#LOGGER.setLevel(logging.DEBUG)
+LOGGER.setLevel(logging.DEBUG)
 
 TEST_DS_DIR = os.path.join(os.path.dirname(__file__), 'dicom_files')
 BIG_DATASET = dcmread(os.path.join(TEST_DS_DIR, 'RTImageStorage.dcm')) # 2.1 M
@@ -96,6 +100,9 @@ class DummyDIMSE(object):
     def send_msg(self, rsp, context_id):
         self.status = rsp.Status
 
+    def get_msg(self, block=False):
+        return None, None
+
 
 class TestCStoreSCP(object):
     """Tests for Association._c_store_scp"""
@@ -375,13 +382,13 @@ class TestCStoreSCP(object):
                 assert args[1].ErrorComment == "Unable to decode the dataset"
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 status = Dataset()
                 status.Status = 0x0000
 
                 rsp = DummyMessage()
 
-                return rsp, None
+                return None, rsp
 
         req = C_STORE()
         req.MessageID = 1
@@ -816,6 +823,55 @@ class TestAssociation(object):
 
         assert assoc.dul.socket == 'abc'
 
+    @pytest.mark.skipif(sys.version_info[:2] == (3, 4), reason='no caplog')
+    def test_unexpected_cancel(self, caplog):
+        """Test receiving an unexpected C-CANCEL request."""
+        with caplog.at_level(logging.WARNING, logger='pynetdicom'):
+            ae = AE()
+            ae.add_requested_context(VerificationSOPClass)
+            ae.add_supported_context(VerificationSOPClass)
+            scp = ae.start_server(('', 11112), block=False)
+
+            assoc = ae.associate('localhost', 11112)
+            assoc.send_c_cancel_move(1)
+
+            assoc.release()
+
+            assert 'Received unexpected C-CANCEL request' in caplog.text
+
+            scp.shutdown()
+
+    @pytest.mark.skipif(sys.version_info[:2] == (3, 4), reason='no caplog')
+    def test_invalid_context(self, caplog):
+        """Test receiving an unexpected C-CANCEL request."""
+        with caplog.at_level(logging.INFO, logger='pynetdicom'):
+            ae = AE()
+            ae.add_requested_context(VerificationSOPClass)
+            ae.add_requested_context(CTImageStorage)
+            ae.add_supported_context(VerificationSOPClass)
+            scp = ae.start_server(('', 11112), block=False)
+
+            assoc = ae.associate('localhost', 11112)
+            assoc.dimse_timeout = 0.1
+            assert assoc.is_established
+            assoc._accepted_cx[3] = assoc._rejected_cx[0]
+            assoc._accepted_cx[3].result = 0x00
+            assoc._accepted_cx[3]._as_scu = True
+            assoc._accepted_cx[3]._as_scp = True
+            ds = Dataset()
+            ds.SOPClassUID = CTImageStorage
+            ds.SOPInstanceUID = '1.2.3.4'
+            ds.file_meta = Dataset()
+            ds.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
+            result = assoc.send_c_store(ds)
+            time.sleep(0.1)
+            assert assoc.is_aborted
+            assert (
+                'Received DIMSE message with invalid or rejected context ID'
+            ) in caplog.text
+
+            scp.shutdown()
+
 
 class TestAssociationSendCEcho(object):
     """Run tests on Assocation send_c_echo."""
@@ -880,7 +936,7 @@ class TestAssociationSendCEcho(object):
         assoc = ae.associate('localhost', 11112)
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         if assoc.is_established:
@@ -905,7 +961,7 @@ class TestAssociationSendCEcho(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return None, DummyResponse()
 
         assoc.dimse = DummyDIMSE()
         if assoc.is_established:
@@ -1092,7 +1148,7 @@ class TestAssociationSendCEcho(object):
         assert assoc.is_released
         self.scp.stop()
 
-    def test_network_times_out(self):
+    def test_network_times_out_requestor(self):
         """Regression test for #286."""
         ae = AE()
         ae.add_requested_context(VerificationSOPClass)
@@ -1110,6 +1166,24 @@ class TestAssociationSendCEcho(object):
 
         scp.shutdown()
 
+    def test_network_times_out_acceptor(self):
+        """Regression test for #286."""
+        ae = AE()
+        ae.add_requested_context(VerificationSOPClass)
+        ae.add_supported_context(VerificationSOPClass)
+        scp = ae.start_server(('', 11113), block=False)
+
+        assoc = ae.associate('localhost', 11113)
+        ae.network_timeout = 0.5
+        assoc.network_timeout = 60
+        assert assoc.network_timeout == 60
+        assert assoc.is_established
+        time.sleep(1.0)
+        assert assoc.is_aborted
+
+        scp.shutdown()
+
+
 class TestAssociationSendCStore(object):
     """Run tests on Assocation send_c_store."""
     def setup(self):
@@ -1222,7 +1296,7 @@ class TestAssociationSendCStore(object):
         assoc = ae.associate('localhost', 11112)
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1247,7 +1321,7 @@ class TestAssociationSendCStore(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return DummyResponse(), None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1694,7 +1768,7 @@ class TestAssociationSendCFind(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return DummyResponse(), None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1767,13 +1841,14 @@ class TestAssociationSendCFind(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
 
         results = assoc.send_c_find(self.ds, query_model='P')
+        assert next(results) == (Dataset(), None)
         with pytest.raises(StopIteration):
             next(results)
 
@@ -1805,12 +1880,12 @@ class TestAssociationSendCFind(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 rsp = C_FIND()
                 rsp.Status = 0xFF00
                 rsp.MessageIDBeingRespondedTo = 1
                 rsp.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
-                return rsp, 1
+                return 1, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -2361,13 +2436,14 @@ class TestAssociationSendCGet(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
 
         results = assoc.send_c_get(self.ds, query_model='P')
+        assert next(results) == (Dataset(), None)
         with pytest.raises(StopIteration):
             next(results)
 
@@ -2413,11 +2489,11 @@ class TestAssociationSendCGet(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 rsp = C_GET()
                 rsp.Status = 0xC000
                 rsp.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
-                return rsp, 1
+                return 1, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -3041,13 +3117,14 @@ class TestAssociationSendCMove(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
 
         results = assoc.send_c_move(self.ds, b'TEST', query_model='P')
+        assert next(results) == (Dataset(), None)
         with pytest.raises(StopIteration):
             next(results)
 
@@ -3087,11 +3164,11 @@ class TestAssociationSendCMove(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 rsp = C_MOVE()
                 rsp.Status = 0xC000
                 rsp.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
-                return rsp, 1
+                return 1, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
diff --git a/pynetdicom/tests/test_assoc_n.py b/pynetdicom/tests/test_assoc_n.py
index 8205c4694..c262d98f9 100644
--- a/pynetdicom/tests/test_assoc_n.py
+++ b/pynetdicom/tests/test_assoc_n.py
@@ -38,8 +38,8 @@ from .dummy_n_scp import (
 )
 
 LOGGER = logging.getLogger('pynetdicom')
-#LOGGER.setLevel(logging.CRITICAL)
-LOGGER.setLevel(logging.DEBUG)
+LOGGER.setLevel(logging.CRITICAL)
+#LOGGER.setLevel(logging.DEBUG)
 
 
 class TestAssociationSendNEventReport(object):
@@ -162,7 +162,7 @@ class TestAssociationSendNEventReport(object):
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
 
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -193,7 +193,7 @@ class TestAssociationSendNEventReport(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return None, DummyResponse()
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -328,13 +328,13 @@ class TestAssociationSendNEventReport(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 status = Dataset()
                 status.Status = 0x0000
 
                 rsp = DummyMessage()
 
-                return rsp, None
+                return None, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -454,7 +454,7 @@ class TestAssociationSendNGet(object):
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
 
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -483,7 +483,7 @@ class TestAssociationSendNGet(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return None, DummyResponse()
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -603,13 +603,13 @@ class TestAssociationSendNGet(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 status = Dataset()
                 status.Status = 0x0000
 
                 rsp = DummyMessage()
 
-                return rsp, None
+                return None, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -766,7 +766,7 @@ class TestAssociationSendNSet(object):
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
 
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -797,7 +797,7 @@ class TestAssociationSendNSet(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return None, DummyResponse()
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -932,13 +932,13 @@ class TestAssociationSendNSet(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 status = Dataset()
                 status.Status = 0x0000
 
                 rsp = DummyMessage()
 
-                return rsp, None
+                return None, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1103,7 +1103,7 @@ class TestAssociationSendNAction(object):
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
 
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1134,7 +1134,7 @@ class TestAssociationSendNAction(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return None, DummyResponse()
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1269,13 +1269,13 @@ class TestAssociationSendNAction(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 status = Dataset()
                 status.Status = 0x0000
 
                 rsp = DummyMessage()
 
-                return rsp, None
+                return None, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1438,7 +1438,7 @@ class TestAssociationSendNCreate(object):
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
 
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1469,7 +1469,7 @@ class TestAssociationSendNCreate(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return None, DummyResponse()
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1604,13 +1604,13 @@ class TestAssociationSendNCreate(object):
             def send_msg(*args, **kwargs):
                 return
 
-            def receive_msg(*args, **kwargs):
+            def get_msg(*args, **kwargs):
                 status = Dataset()
                 status.Status = 0x0000
 
                 rsp = DummyMessage()
 
-                return rsp, None
+                return None, rsp
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1749,7 +1749,7 @@ class TestAssociationSendNDelete(object):
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
 
-            def receive_msg(*args, **kwargs): return None, None
+            def get_msg(*args, **kwargs): return None, None
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
@@ -1776,7 +1776,7 @@ class TestAssociationSendNDelete(object):
 
         class DummyDIMSE():
             def send_msg(*args, **kwargs): return
-            def receive_msg(*args, **kwargs): return DummyResponse(), None
+            def get_msg(*args, **kwargs): return None, DummyResponse()
 
         assoc.dimse = DummyDIMSE()
         assert assoc.is_established
diff --git a/pynetdicom/tests/test_dimse_provider.py b/pynetdicom/tests/test_dimse_provider.py
index ca39352e0..1f78039c3 100644
--- a/pynetdicom/tests/test_dimse_provider.py
+++ b/pynetdicom/tests/test_dimse_provider.py
@@ -78,7 +78,7 @@ class TestDIMSEProvider(object):
 
     def test_receive_not_pdata(self):
         """Test we get back None if not a P_DATA"""
-        assert self.dimse.receive_msg(True) == (None, None)
+        assert self.dimse.get_msg(True) == (None, None)
 
     @pytest.mark.parametrize("primitive, cls_name", REFERENCE_MSG)
     def test_send_msg(self, primitive, cls_name):
@@ -106,54 +106,17 @@ class TestDIMSEProvider(object):
         self.dimse.on_send_dimse_message = test_callback
         self.dimse.send_msg(primitive, 1)
 
-    # Receive tests
-    def test_receive_timeout(self):
-        """Test the DIMSE timeout on Receive works"""
-        pass
-
-    def test_receive_c_echo(self):
-        """Check receiving DIMSE C-ECHO messages."""
-        pass
-
-    def test_receive_c_store(self):
-        """Check receiving DIMSE C-STORE messages."""
-        pass
-
-    def test_receive_c_find(self):
-        """Check receiving DIMSE C-FIND messages."""
-        pass
-
-    def test_receive_c_get(self):
-        """Check receiving DIMSE C-GET messages."""
-        pass
+    def test_peek_empty(self):
+        """Test peek_msg with nothing on the queue."""
+        dimse = DIMSEServiceProvider(None, 0.5)
+        assert dimse.peek_msg() == (None, None)
 
-    def test_receive_c_move(self):
-        """Check receiving DIMSE C-MOVE messages."""
-        pass
-
-    def test_receive_n_event_report(self):
-        """Check receiving DIMSE N-EVENT-REPORT messages."""
-        pass
-
-    def test_receive_n_get(self):
-        """Check receiving DIMSE N-GET messages."""
-        pass
-
-    def test_receive_n_set(self):
-        """Check receiving DIMSE N-SET messages."""
-        pass
-
-    def test_receive_n_action(self):
-        """Check receiving DIMSE N-ACTION messages."""
-        pass
-
-    def test_receive_n_create(self):
-        """Check receiving DIMSE N-CREATE messages."""
-        pass
-
-    def test_receive_n_delete(self):
-        """Check receiving DIMSE N-DELETE messages."""
-        pass
+    def test_peek_item(self):
+        """Test peek_msg with nothing on the queue."""
+        dimse = DIMSEServiceProvider(None, 0.5)
+        primitive = C_STORE()
+        dimse.msg_queue.put((14, primitive))
+        assert dimse.peek_msg() == (14, primitive)
 
 
 class TestDIMSEProviderCallbacks(object):
diff --git a/pynetdicom/tests/test_dul.py b/pynetdicom/tests/test_dul.py
index 113922eb9..7cf3a727d 100644
--- a/pynetdicom/tests/test_dul.py
+++ b/pynetdicom/tests/test_dul.py
@@ -3,13 +3,18 @@
 
 import logging
 import socket
+import threading
+import time
 
 import pytest
 
+from pynetdicom import AE
 from pynetdicom.dul import DULServiceProvider
 from pynetdicom.pdu import A_ASSOCIATE_RQ, A_ASSOCIATE_AC, A_ASSOCIATE_RJ, \
                             A_RELEASE_RQ, A_RELEASE_RP, P_DATA_TF, A_ABORT_RQ
 from pynetdicom.pdu_primitives import A_ASSOCIATE, A_RELEASE, A_ABORT, P_DATA
+from .encoded_pdu_items import a_associate_ac
+from .parrot import start_server, ThreadedParrot
 
 LOGGER = logging.getLogger('pynetdicom')
 LOGGER.setLevel(logging.CRITICAL)
@@ -46,6 +51,11 @@ class DummyDUL(DULServiceProvider):
 
 class TestDUL(object):
     """Run tests on DUL service provider."""
+    def teardown(self):
+        for thread in threading.enumerate():
+            if isinstance(thread, ThreadedParrot):
+                thread.shutdown()
+
     def test_primitive_to_event(self):
         """Test that parameter returns expected results"""
         dul = DummyDUL()
@@ -73,3 +83,67 @@ class TestDUL(object):
 
         with pytest.raises(ValueError):
             p2e('TEST')
+
+    def test_recv_failure_aborts(self):
+        """Test connection close during PDU recv causes abort."""
+        commands = [
+            ('recv', None),  # recv a-associate-rq
+            ('send', a_associate_ac),
+            ('wait', 0.1),  # Don't want to accidentally kill the DUL
+            ('send', b"\x07\x00\x00\x00\x00\x04"),
+            ('wait', 0.3)
+        ]
+        scp = start_server(commands)
+
+        ae = AE()
+        ae.network_timeout = 0.2
+        ae.add_requested_context('1.2.840.10008.1.1')
+        assoc = ae.associate('localhost', 11112)
+        assert assoc.is_established
+        time.sleep(0.4)
+        assert assoc.is_aborted
+
+        scp.shutdown()
+
+    def test_recv_short_aborts(self):
+        """Test receiving short PDU causes abort."""
+        commands = [
+            ('recv', None),  # recv a-associate-rq
+            ('send', a_associate_ac),
+            ('wait', 0.1),  # Don't want to accidentally kill the DUL
+            ('send', b"\x07\x00\x00\x00\x00\x04\x00\x00"),
+            ('wait', 0.3),
+        ]
+        scp = start_server(commands)
+
+        ae = AE()
+        ae.network_timeout = 0.2
+        ae.add_requested_context('1.2.840.10008.1.1')
+        assoc = ae.associate('localhost', 11112)
+        assert assoc.is_established
+
+        time.sleep(0.4)
+        assert assoc.is_aborted
+
+        scp.shutdown()
+
+    def test_recv_bad_pdu_aborts(self):
+        """Test receiving undecodable PDU causes abort."""
+        commands = [
+            ('recv', None),  # recv a-associate-rq
+            ('send', a_associate_ac),
+            ('wait', 0.1),  # Don't want to accidentally kill the DUL
+            ('send', b"\x07\x00\x00\x00\x00\x02\x00\x00"),
+        ]
+        scp = start_server(commands)
+
+        ae = AE()
+        ae.network_timeout = 0.2
+        ae.add_requested_context('1.2.840.10008.1.1')
+        assoc = ae.associate('localhost', 11112)
+        assert assoc.is_established
+
+        time.sleep(0.4)
+        assert assoc.is_aborted
+
+        scp.shutdown()
diff --git a/pynetdicom/tests/test_fsm.py b/pynetdicom/tests/test_fsm.py
index dd396aa6f..1dae1ee9e 100644
--- a/pynetdicom/tests/test_fsm.py
+++ b/pynetdicom/tests/test_fsm.py
@@ -1,17 +1,9 @@
 """Unit tests for fsm.py"""
 
 import logging
-try:
-    import queue
-except ImportError:
-    import Queue as queue  # Python 2 compatibility
 import select
 import socket
-try:
-    from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
-except ImportError:
-    from socketserver import TCPServer, ThreadingMixIn, BaseRequestHandler
-from struct import pack, unpack
+from struct import pack
 import threading
 import time
 
@@ -28,13 +20,14 @@ from pynetdicom.pdu_primitives import (
 )
 from pynetdicom.pdu import A_RELEASE_RQ
 from pynetdicom.sop_class import VerificationSOPClass
-from pynetdicom.transport import AssociationSocket, AssociationServer
+from pynetdicom.transport import AssociationSocket
 from pynetdicom.utils import validate_ae_title
 from .dummy_c_scp import DummyVerificationSCP, DummyBaseSCP
 from .encoded_pdu_items import (
     a_associate_ac, a_associate_rq, a_associate_rj, p_data_tf, a_abort,
     a_release_rq, a_release_rp,
 )
+from .parrot import ThreadedParrot
 
 
 LOGGER = logging.getLogger("pynetdicom")
@@ -356,7 +349,9 @@ class TestStateBase(object):
     def print_fsm_scp(self, fsm, scp=None):
         """Print out some of the quantities we're interested in."""
         print('Transitions', fsm._transitions)
-        print('Changes', fsm._changes)
+        print('Changes')
+        for change in fsm._changes:
+            print('\t{}'.format(change))
         print('Events', fsm._events)
 
         if scp and scp.handlers:
@@ -375,7 +370,7 @@ class TestStateBase(object):
         sock.connect(('', 11112))
 
         ae = AE()
-        ae.add_requested_context(VerificationSOPClass)
+        ae.add_supported_context(VerificationSOPClass)
         ae.acse_timeout = 5
         ae.dimse_timeout = 5
 
@@ -402,216 +397,13 @@ class TestStateBase(object):
 
         cx = build_context(VerificationSOPClass)
         cx.context_id = 1
-        assoc.requestor.requested_contexts = [cx]
+        assoc.acceptor.supported_contexts = [cx]
 
 
         fsm = self.monkey_patch(assoc.dul.state_machine)
         return assoc, fsm
 
 
-class ParrotRequest(BaseRequestHandler):
-    @property
-    def commands(self):
-        """Return a list of command tuples."""
-        return self.server.commands
-
-    @property
-    def socket(self):
-        """Return the socket to the remote."""
-        return self.request
-
-    @property
-    def ready(self):
-        """Return True if there is data available to be read.
-
-        Returns
-        -------
-        bool
-            True if the socket has data ready to be read, False otherwise.
-        """
-        try:
-            # Use a timeout of 0 so we get an "instant" result
-            ready, _, _ = select.select([self.socket], [], [], 0.5)
-        except (socket.error, socket.timeout, ValueError):
-            return False
-
-        return bool(ready)
-
-    @property
-    def read_data(self):
-        bytestream = bytearray()
-
-        # Try and read the PDU type and length from the socket
-        try:
-            bytestream.extend(self.recv(6))
-        except (socket.error, socket.timeout):
-            pass
-        try:
-            # Byte 1 is always the PDU type
-            # Byte 2 is always reserved
-            # Bytes 3-6 are always the PDU length
-            pdu_type, _, pdu_length = unpack('>BBL', bytestream)
-        except struct.error:
-            pass
-
-        # Try and read the rest of the PDU
-        try:
-            bytestream += self.recv(pdu_length)
-        except (socket.error, socket.timeout):
-            pass
-
-        return bytestream
-
-    def recv(self, nr_bytes):
-        """Read `nr_bytes` from the socket.
-
-        *Events Emitted*
-
-        - None
-
-        Parameters
-        ----------
-        nr_bytes : int
-            The number of bytes to attempt to read from the socket.
-
-        Returns
-        -------
-        bytearray
-            The data read from the socket.
-        """
-        bytestream = bytearray()
-        nr_read = 0
-        # socket.recv() returns when the network buffer has been emptied
-        #   not necessarily when the number of bytes requested have been
-        #   read. Its up to us to keep calling recv() until we have all the
-        #   data we want
-        # **BLOCKING** until either all the data is read or an error occurs
-        while nr_read < nr_bytes:
-            # Python docs recommend reading a relatively small power of 2
-            #   such as 4096
-            bufsize = 4096
-            if (nr_bytes - nr_read) < bufsize:
-                bufsize = nr_bytes - nr_read
-
-            bytes_read = self.socket.recv(bufsize)
-
-            # If socket.recv() reads 0 bytes then the connection has been
-            #   broken, so return what we have so far
-            if not bytes_read:
-                return bytestream
-
-            bytestream.extend(bytes_read)
-            nr_read += len(bytes_read)
-
-        return bytestream
-
-    def send(self, bytestream):
-        """Try and send `bystream` to the remote.
-
-        Parameters
-        ----------
-        bytestream : bytes
-            The data to send to the remote.
-
-        Returns
-        -------
-        bool
-            True if the data was sent successfully, False otherwise.
-        """
-        total_sent = 0
-        length_data = len(bytestream)
-        try:
-            while total_sent < length_data:
-                # Returns the number of bytes sent
-                nr_sent = self.socket.send(bytestream)
-                total_sent += nr_sent
-        except (socket.error, socket.timeout):
-            # Evt17: Transport connection closed
-            return False
-
-        return True
-
-    def handle(self):
-        # self.server: ThreadedParrot
-        # self.client_address: remote's (host, port)
-        # self.request: socket
-        self.received = []
-        self.sent = []
-        for (cmd, data) in self.commands:
-            pass
-            if cmd == 'recv':
-                self.kill_read = False
-                while not self.kill_read:
-                    if self.ready:
-                        self.received.append(bytes(self.read_data))
-                        self.kill_read = True
-            elif cmd == 'send':
-                self.send(data)
-                self.sent.append(data)
-            elif cmd == 'wait':
-                time.sleep(data)
-
-        # Disconnects automatically when this method ends!
-
-
-class Parrot(AssociationServer):
-    def __init__(self, address, commands):
-        """Create a new AssociationServer, bind a socket and start listening.
-
-        Parameters
-        ----------
-        ae : ae.ApplicationEntity
-            The parent AE that's running the server.
-        address : 2-tuple
-            The (host, port) that the server should run on.
-        ssl_context : ssl.SSLContext, optional
-            If TLS is to be used then this should be the ssl.SSLContext used
-            to wrap the client sockets, otherwise if None then no TLS will be
-            used (default).
-        """
-        self.commands = commands
-        self.ssl_context = None
-        self.allow_reuse_address = True
-
-        TCPServer.__init__(
-            self, address, ParrotRequest, bind_and_activate=True
-        )
-
-        self.timeout = 60
-        self.handlers = []
-
-    def server_bind(self):
-        """Bind the socket and set the socket options.
-
-        - socket.SO_REUSEADDR is set to 1
-        - socket.SO_RCVTIMEO is set to AE.network_timeout unless the value is
-          None in which case it will be left unset.
-        """
-        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        self.socket.setsockopt(socket.SOL_SOCKET,
-                               socket.SO_RCVTIMEO,
-                               pack('ll', 2, 0))
-
-        self.socket.bind(self.server_address)
-        self.server_address = self.socket.getsockname()
-
-    def finish_request(self, request, client_address):
-        """Finish one request by instantiating RequestHandlerClass."""
-        handler = self.RequestHandlerClass(request, client_address, self)
-        self.handlers.append(handler)
-
-    def shutdown(self):
-        """Completely shutdown the server and close it's socket."""
-        TCPServer.shutdown(self)
-        self.server_close()
-
-    process_request = TCPServer.process_request
-
-
-class ThreadedParrot(ThreadingMixIn, Parrot):
-    process_request_thread = ThreadingMixIn.process_request_thread
-
-
 class TestState01(TestStateBase):
     """Tests for State 01: Idle."""
     def test_evt01(self):
@@ -5052,6 +4844,7 @@ class TestState09(TestStateBase):
         self.assoc.start()
         time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_associate('request'))
         time.sleep(0.1)
 
@@ -5253,6 +5046,7 @@ class TestState09(TestStateBase):
         self.assoc.start()
         time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_associate('accept'))
         time.sleep(0.1)
 
@@ -5296,6 +5090,7 @@ class TestState09(TestStateBase):
         self.assoc.start()
         time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_associate('reject'))
         time.sleep(0.1)
 
@@ -5339,6 +5134,7 @@ class TestState09(TestStateBase):
         self.assoc.start()
         time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_pdata())
         time.sleep(0.1)
 
@@ -5430,6 +5226,7 @@ class TestState09(TestStateBase):
         self.assoc.start()
         time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
         time.sleep(0.1)
 
@@ -5570,6 +5367,7 @@ class TestState09(TestStateBase):
         self.assoc.start()
         time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(True))
         time.sleep(0.1)
 
@@ -5617,6 +5415,7 @@ class TestState09(TestStateBase):
         self.assoc.start()
         time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         self.assoc.dul.send_pdu(self.get_abort())
         time.sleep(0.1)
 
@@ -5831,21 +5630,24 @@ class TestState10(TestStateBase):
         # Evt1: A-ASSOCIATE (rq) primitive from <local user>
         commands = [
             ('send', a_associate_rq),
-            ('recv', None),
-            ('recv', None),
-            ('send', a_release_rq),
+            ('recv', None),  # recv a-associate-ac
+            ('recv', None),  # recv a-release-rq
+            ('send', a_release_rq),  # collide
             ('wait', 0.2)
         ]
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
         time.sleep(0.1)
-
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_associate('request'))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -5889,6 +5691,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -5935,6 +5742,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -5988,6 +5800,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -6041,10 +5858,9 @@ class TestState10(TestStateBase):
         assoc.start()
 
         time.sleep(0.1)
-
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_associate('accept'))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -6088,10 +5904,9 @@ class TestState10(TestStateBase):
         assoc.start()
 
         time.sleep(0.1)
-
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_associate('reject'))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -6135,10 +5950,9 @@ class TestState10(TestStateBase):
         assoc.start()
 
         time.sleep(0.1)
-
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_pdata())
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -6175,6 +5989,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -6228,10 +6047,9 @@ class TestState10(TestStateBase):
         assoc.start()
 
         time.sleep(0.1)
-
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -6268,6 +6086,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -6313,6 +6136,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -6363,10 +6191,9 @@ class TestState10(TestStateBase):
         assoc.start()
 
         time.sleep(0.1)
-
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_release(True))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -6411,10 +6238,9 @@ class TestState10(TestStateBase):
         assoc.start()
 
         time.sleep(0.1)
-
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_abort())
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -6454,6 +6280,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -6495,6 +6326,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -6589,6 +6425,11 @@ class TestState10(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
         time.sleep(0.1)
@@ -7485,13 +7326,17 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_associate('request'))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7537,12 +7382,15 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7585,12 +7433,15 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7640,9 +7491,14 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
-        time.sleep(0.1)
+        time.sleep(0.2)
 
         assoc.dul.send_pdu(self.get_release(False))
 
@@ -7686,13 +7542,17 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_associate('accept'))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7729,13 +7589,17 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_associate('reject'))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7772,13 +7636,17 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_pdata())
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7817,9 +7685,14 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
+
+        assoc.acse.is_release_requested = is_release_requested
         assoc.start()
 
-        time.sleep(0.1)
+        time.sleep(0.2)
 
         assoc.dul.send_pdu(self.get_release(False))
 
@@ -7863,13 +7736,17 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7908,12 +7785,15 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -7956,12 +7836,15 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -8003,13 +7886,17 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_release(True))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -8051,13 +7938,17 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
+        time.sleep(0.1)
         assoc.dul.send_pdu(self.get_abort())
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -8099,12 +7990,15 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -8142,12 +8036,15 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -8191,7 +8088,7 @@ class TestState12(TestStateBase):
 
         assoc.acse.is_release_requested = is_release_requested
         assoc.start()
-        time.sleep(0.1)
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
         time.sleep(0.1)
         assoc.dul.artim_timer.timeout = 0.05
@@ -8234,12 +8131,15 @@ class TestState12(TestStateBase):
         scp = self.start_server(commands)
 
         assoc, fsm = self.get_acceptor_assoc()
-        assoc.start()
+        def is_release_requested(assoc):
+            """Override ACSE.is_release_requested."""
+            return False
 
-        time.sleep(0.1)
+        assoc.acse.is_release_requested = is_release_requested
+        assoc.start()
 
+        time.sleep(0.2)
         assoc.dul.send_pdu(self.get_release(False))
-
         time.sleep(0.1)
 
         #self.print_fsm_scp(fsm, scp)
@@ -8992,17 +8892,50 @@ class TestParrotAttack(TestStateBase):
         ]
 
     def test_acceptor(self):
+        """Test hitting the acceptor with PDUs."""
+        # Also a regression test for #120
+        # C-ECHO-RQ
+        # 80 total length
+        echo_rq = (
+            b"\x04\x00\x00\x00\x00\x4a" # P-DATA-TF 74
+            b"\x00\x00\x00\x46\x01" # PDV Item 70
+            b"\x03"  # PDV: 2 -> 69
+            b"\x00\x00\x00\x00\x04\x00\x00\x00\x42\x00\x00\x00"  # 12 Command Group Length
+            b"\x00\x00\x02\x00\x12\x00\x00\x00\x31\x2e\x32\x2e\x38"
+            b"\x34\x30\x2e\x31\x30\x30\x30\x38\x2e\x31\x2e\x31\x00"  # 26
+            b"\x00\x00\x00\x01\x02\x00\x00\x00\x30\x00"  # 10 Command Field
+            b"\x00\x00\x10\x01\x02\x00\x00\x00\x01\x00"  # 10 Message ID
+            b"\x00\x00\x00\x08\x02\x00\x00\x00\x01\x01"  # 10 Command Data Set Type
+        )
+
+        # Send associate request then c-echo requests then release request
         commands = [
             ('send', a_associate_rq),
             ('recv', None),
-            ('send', p_data_tf),
-            ('send', p_data_tf),
-            ('send', p_data_tf),
-            ('send', p_data_tf),
-            ('send', p_data_tf),
-            ('send', p_data_tf),
-            ('send', p_data_tf),
-            ('send', p_data_tf),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
+            ('send', echo_rq),
+            ('recv', None),
             ('send', a_release_rq),
             ('wait', 0.1)
         ]
@@ -9013,26 +8946,42 @@ class TestParrotAttack(TestStateBase):
 
         time.sleep(0.5)
 
-        #self.print_fsm_scp(self.fsm, scp)
+        #self.print_fsm_scp(fsm, scp=None)
 
         scp.shutdown()
 
-        assert fsm._changes[:14] == [
+        assert [
             ('Sta1', 'Evt5', 'AE-5'),
             ('Sta2', 'Evt6', 'AE-6'),
             ('Sta3', 'Evt7', 'AE-7'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
+            ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
+            ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
+            ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
+            ('Sta6', 'Evt10', 'DT-2'),
+            ('Sta6', 'Evt9', 'DT-1'),
             ('Sta6', 'Evt12', 'AR-2'),
             ('Sta8', 'Evt14', 'AR-4'),
             ('Sta13', 'Evt17', 'AR-5'),
-        ]
+        ] == fsm._changes[:30]
 
 
 class TestStateMachineFunctionalRequestor(object):
diff --git a/pynetdicom/tests/test_service_qr.py b/pynetdicom/tests/test_service_qr.py
index a24c37bc0..e30636f4f 100644
--- a/pynetdicom/tests/test_service_qr.py
+++ b/pynetdicom/tests/test_service_qr.py
@@ -108,7 +108,7 @@ class TestQRFindServiceClass(object):
         req.Priority = 2
         req.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
         assoc.dimse.send_msg(req, 1)
-        rsp, _ = assoc.dimse.receive_msg(True)
+        cx_id, rsp = assoc.dimse.get_msg(True)
         assert rsp.Status == 0xC310
 
         assoc.release()
@@ -568,7 +568,7 @@ class TestQRGetServiceClass(object):
         req.Priority = 2
         req.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
         assoc.dimse.send_msg(req, 1)
-        status, _ = assoc.dimse.receive_msg(True)
+        cx_id, status = assoc.dimse.get_msg(True)
         assert status.Status == 0xC410
 
         assoc.release()
@@ -1716,7 +1716,7 @@ class TestQRMoveServiceClass(object):
         # Encoded as Implicit VR Little
         req.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
         assoc.dimse.send_msg(req, 1)
-        status, _ = assoc.dimse.receive_msg(True)
+        cx_id, status = assoc.dimse.get_msg(True)
         assert status.Status == 0xC510
 
         assoc.release()
diff --git a/pynetdicom/tests/test_service_relevant_patient.py b/pynetdicom/tests/test_service_relevant_patient.py
index b85969abf..34e50a635 100644
--- a/pynetdicom/tests/test_service_relevant_patient.py
+++ b/pynetdicom/tests/test_service_relevant_patient.py
@@ -75,7 +75,7 @@ class TestRelevantPatientServiceClass(object):
         req.Priority = 2
         req.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
         assoc.dimse.send_msg(req, 1)
-        rsp, _ = assoc.dimse.receive_msg(True)
+        cx_id, rsp = assoc.dimse.get_msg(True)
         assert rsp.Status == 0xC310
 
         assoc.release()
diff --git a/pynetdicom/tests/test_service_storage.py b/pynetdicom/tests/test_service_storage.py
index 7f986a9ff..050daf472 100644
--- a/pynetdicom/tests/test_service_storage.py
+++ b/pynetdicom/tests/test_service_storage.py
@@ -75,7 +75,7 @@ class TestStorageServiceClass(object):
 
         # Send C-STORE request to DIMSE and get response
         assoc.dimse.send_msg(req, 1)
-        rsp, _ = assoc.dimse.receive_msg(True)
+        cx_id, rsp = assoc.dimse.get_msg(True)
 
         assert rsp.Status == 0xC210
         assert rsp.ErrorComment == 'Unable to decode the dataset'
diff --git a/pynetdicom/tests/test_service_substance_admin.py b/pynetdicom/tests/test_service_substance_admin.py
index 39a413559..34fedf1e9 100644
--- a/pynetdicom/tests/test_service_substance_admin.py
+++ b/pynetdicom/tests/test_service_substance_admin.py
@@ -73,7 +73,7 @@ class TestQRFindServiceClass(object):
         req.Priority = 2
         req.Identifier = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49')
         assoc.dimse.send_msg(req, 1)
-        rsp, _ = assoc.dimse.receive_msg(True)
+        cx_id, rsp = assoc.dimse.get_msg(True)
         assert rsp.Status == 0xC310
 
         assoc.release()