We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Don't crash the socket for unrecognized responses
Before #400 when testing against the core agent I discovered the python agent crashes when the core agent doesn't return the expected response, like:

```
2019-11-25T18:08:49+0000 DEBUG  scout_apm.core.socket CoreAgentSocket thread exception: error('unpack requires a buffer of 4 bytes')
Traceback (most recent call last):
  File "/Users/chainz/Documents/Projects/scout_apm_python/src/scout_apm/core/socket.py", line 102, in run
    result = self._send(body)
  File "/Users/chainz/Documents/Projects/scout_apm_python/src/scout_apm/core/socket.py", line 171, in _send
    self._read_response()
  File "/Users/chainz/Documents/Projects/scout_apm_python/src/scout_apm/core/socket.py", line 183, in _read_response
    size = struct.unpack(">I", raw_size)[0]
struct.error: unpack requires a buffer of 4 bytes
```

I think we should make this more robust - we can avoid trying to unpack a response that isn't the right length. The Core agent was sending back nothing on parse error.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/core/socket.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import json
5 import logging
6 import os
7 import socket
8 import struct
9 import threading
10 import time
11 
12 from scout_apm.compat import queue
13 from scout_apm.core.commands import Register
14 from scout_apm.core.config import scout_config
15 
16 SECOND = 1  # time unit - monkey-patched in tests to make them run faster
17 
18 logger = logging.getLogger(__name__)
19 
20 
21 class CoreAgentSocket(threading.Thread):
22     _instance = None
23     _instance_lock = threading.Lock()
24 
25     @classmethod
26     def instance(cls):
27         with cls._instance_lock:
28             # No instance exists yet.
29             if cls._instance is None:
30                 cls._instance = cls()
31                 return cls._instance
32 
33             # An instance exists but is no longer running.
34             if not cls._instance.running():
35                 cls._instance = cls()
36                 return cls._instance
37 
38             # An instance exists and is running (or in the process of
39             # starting or in the process of stopping). In any case,
40             # return this instance.
41             return cls._instance
42 
43     def __init__(self, *args, **kwargs):
44         super(CoreAgentSocket, self).__init__()
45         # Socket related
46         self.socket_path = scout_config.value("socket_path")
47         self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
48 
49         # Threading control related
50         self._started_event = threading.Event()
51         self._stop_event = threading.Event()
52         self._stopped_event = threading.Event()
53 
54         # Command queues
55         self.command_queue = queue.Queue(maxsize=500)
56 
57         # Set Thread options
58         self.daemon = True
59 
60         # Set the started event here to avoid races in the class instance()
61         # method. If there is an exception in the socket thread then it will
62         # clear this event on exit.
63         self._started_event.set()
64 
65         # Now call start() which eventually launches run() in another thread.
66         self.start()
67 
68     def __del__(self):
69         self.stop()
70 
71     def running(self):
72         return self._started_event.is_set()
73 
74     def stop(self):
75         if self._started_event.is_set():
76             self._stop_event.set()
77             self.command_queue.put(None, False)  # unblock self.command_queue.get
78             stopped = self._stopped_event.wait(2 * SECOND)
79             if stopped:
80                 return True
81             else:
82                 logger.debug("CoreAgentSocket Failed to stop thread within timeout!")
83                 return False
84         else:
85             return True
86 
87     def run(self):
88         """
89         Called by the threading system
90         """
91 
92         try:
93             self._connect()
94             self._register()
95             while True:
96                 try:
97                     body = self.command_queue.get(block=True, timeout=1 * SECOND)
98                 except queue.Empty:
99                     body = None
100 
101                 if body is not None:
102                     result = self._send(body)
103                     if result:
104                         self.command_queue.task_done()
105                     else:
106                         # Something was wrong with the socket.
107                         self._disconnect()
108                         self._connect()
109                         self._register()
110 
111                 # Check for stop event after a read from the queue. This is to
112                 # allow you to open a socket, immediately send to it, and then
113                 # stop it. We do this in the Metadata send at application start
114                 # time
115                 if self._stop_event.is_set():
116                     logger.debug("CoreAgentSocket thread stopping.")
117                     break
118         except Exception as exc:
119             logger.debug("CoreAgentSocket thread exception: %r", exc, exc_info=exc)
120         finally:
121             self._started_event.clear()
122             self._stop_event.clear()
123             self._stopped_event.set()
124             logger.debug("CoreAgentSocket thread stopped.")
125 
126     def send(self, command):
127         try:
128             self.command_queue.put(command, False)
129         except queue.Full as exc:
130             # TODO mark the command as not queued?
131             logger.debug("CoreAgentSocket error on send: %r", exc, exc_info=exc)
132 
133     def _send(self, command):
134         msg = command.message()
135 
136         try:
137             data = json.dumps(msg)
138         except (ValueError, TypeError) as exc:
139             logger.debug(
140                 "Exception when serializing command message: %r", exc, exc_info=exc
141             )
142             return False
143 
144         try:
145             self.socket.sendall(self._message_length(data))
146         except OSError as exc:
147             logger.debug(
148                 "CoreAgentSocket exception on length _send: "
149                 "%r on PID: %s on thread: %s",
150                 exc,
151                 os.getpid(),
152                 threading.current_thread(),
153                 exc_info=exc,
154             )
155             return None
156 
157         try:
158             self.socket.sendall(data.encode())
159         except OSError as exc:
160             logger.debug(
161                 "CoreAgentSocket exception on data _send: "
162                 "%r on PID: %s on thread: %s",
163                 exc,
164                 os.getpid(),
165                 threading.current_thread(),
166                 exc_info=exc,
167             )
168             return None
169 
170         # TODO do something with the response sent back in reply to command
171         self._read_response()
172 
173         return True
174 
175     def _message_length(self, body):
176         length = len(body)
177         return struct.pack(">I", length)
178 
179     def _read_response(self):
180         try:
181             raw_size = self.socket.recv(4)
182             size = struct.unpack(">I", raw_size)[0]
183             message = bytearray(0)
184 
185             while len(message) < size:
186                 recv = self.socket.recv(size)
187                 message += recv
188 
189             return message
190         except OSError as exc:
191             logger.debug(
192                 "CoreAgentSocket error on read response: %r", exc, exc_info=exc
193             )
194             return None
195 
196     def _register(self):
197         self._send(
198             Register(
199                 app=scout_config.value("name"),
200                 key=scout_config.value("key"),
201                 hostname=scout_config.value("hostname"),
202             )
203         )
204 
205     def _connect(self, connect_attempts=5, retry_wait_secs=1):
206         for attempt in range(1, connect_attempts + 1):
207             logger.debug(
208                 "CoreAgentSocket attempt %d, connecting to %s, PID: %s, Thread: %s",
209                 attempt,
210                 self.socket_path,
211                 os.getpid(),
212                 threading.current_thread(),
213             )
214             try:
215                 self.socket.connect(self.socket_path)
216                 self.socket.settimeout(3 * SECOND)
217                 logger.debug("CoreAgentSocket is connected")
218                 return True
219             except socket.error as exc:
220                 logger.debug("CoreAgentSocket connection error: %r", exc, exc_info=exc)
221                 # Return without waiting when reaching the maximum number of attempts.
222                 if attempt >= connect_attempts:
223                     return False
224                 time.sleep(retry_wait_secs * SECOND)
225 
226     def _disconnect(self):
227         logger.debug("CoreAgentSocket disconnecting from %s", self.socket_path)
228         try:
229             self.socket.close()
230         except socket.error as exc:
231             logger.debug(
232                 "CoreAgentSocket exception on disconnect: %r", exc, exc_info=exc
233             )
234         finally:
235             self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
236
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/core/socket.py b/src/scout_apm/core/socket.py
--- a/src/scout_apm/core/socket.py
+++ b/src/scout_apm/core/socket.py
@@ -179,6 +179,9 @@
     def _read_response(self):
         try:
             raw_size = self.socket.recv(4)
+            if len(raw_size) != 4:
+                # Ignore invalid responses
+                return None
             size = struct.unpack(">I", raw_size)[0]
             message = bytearray(0)

{"golden_diff": "diff --git a/src/scout_apm/core/socket.py b/src/scout_apm/core/socket.py\n--- a/src/scout_apm/core/socket.py\n+++ b/src/scout_apm/core/socket.py\n@@ -179,6 +179,9 @@\n     def _read_response(self):\n         try:\n             raw_size = self.socket.recv(4)\n+            if len(raw_size) != 4:\n+                # Ignore invalid responses\n+                return None\n             size = struct.unpack(\">I\", raw_size)[0]\n             message = bytearray(0)\n", "issue": "Don't crash the socket for unrecognized responses\nBefore #400 when testing against the core agent I discovered the python agent crashes when the core agent doesn't return the expected response, like:\r\n\r\n```\r\n2019-11-25T18:08:49+0000 DEBUG  scout_apm.core.socket CoreAgentSocket thread exception: error('unpack requires a buffer of 4 bytes')\r\nTraceback (most recent call last):\r\n  File \"/Users/chainz/Documents/Projects/scout_apm_python/src/scout_apm/core/socket.py\", line 102, in run\r\n    result = self._send(body)\r\n  File \"/Users/chainz/Documents/Projects/scout_apm_python/src/scout_apm/core/socket.py\", line 171, in _send\r\n    self._read_response()\r\n  File \"/Users/chainz/Documents/Projects/scout_apm_python/src/scout_apm/core/socket.py\", line 183, in _read_response\r\n    size = struct.unpack(\">I\", raw_size)[0]\r\nstruct.error: unpack requires a buffer of 4 bytes\r\n```\r\n\r\nI think we should make this more robust - we can avoid trying to unpack a response that isn't the right length. The Core agent was sending back nothing on parse error.\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport json\nimport logging\nimport os\nimport socket\nimport struct\nimport threading\nimport time\n\nfrom scout_apm.compat import queue\nfrom scout_apm.core.commands import Register\nfrom scout_apm.core.config import scout_config\n\nSECOND = 1  # time unit - monkey-patched in tests to make them run faster\n\nlogger = logging.getLogger(__name__)\n\n\nclass CoreAgentSocket(threading.Thread):\n    _instance = None\n    _instance_lock = threading.Lock()\n\n    @classmethod\n    def instance(cls):\n        with cls._instance_lock:\n            # No instance exists yet.\n            if cls._instance is None:\n                cls._instance = cls()\n                return cls._instance\n\n            # An instance exists but is no longer running.\n            if not cls._instance.running():\n                cls._instance = cls()\n                return cls._instance\n\n            # An instance exists and is running (or in the process of\n            # starting or in the process of stopping). In any case,\n            # return this instance.\n            return cls._instance\n\n    def __init__(self, *args, **kwargs):\n        super(CoreAgentSocket, self).__init__()\n        # Socket related\n        self.socket_path = scout_config.value(\"socket_path\")\n        self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n\n        # Threading control related\n        self._started_event = threading.Event()\n        self._stop_event = threading.Event()\n        self._stopped_event = threading.Event()\n\n        # Command queues\n        self.command_queue = queue.Queue(maxsize=500)\n\n        # Set Thread options\n        self.daemon = True\n\n        # Set the started event here to avoid races in the class instance()\n        # method. If there is an exception in the socket thread then it will\n        # clear this event on exit.\n        self._started_event.set()\n\n        # Now call start() which eventually launches run() in another thread.\n        self.start()\n\n    def __del__(self):\n        self.stop()\n\n    def running(self):\n        return self._started_event.is_set()\n\n    def stop(self):\n        if self._started_event.is_set():\n            self._stop_event.set()\n            self.command_queue.put(None, False)  # unblock self.command_queue.get\n            stopped = self._stopped_event.wait(2 * SECOND)\n            if stopped:\n                return True\n            else:\n                logger.debug(\"CoreAgentSocket Failed to stop thread within timeout!\")\n                return False\n        else:\n            return True\n\n    def run(self):\n        \"\"\"\n        Called by the threading system\n        \"\"\"\n\n        try:\n            self._connect()\n            self._register()\n            while True:\n                try:\n                    body = self.command_queue.get(block=True, timeout=1 * SECOND)\n                except queue.Empty:\n                    body = None\n\n                if body is not None:\n                    result = self._send(body)\n                    if result:\n                        self.command_queue.task_done()\n                    else:\n                        # Something was wrong with the socket.\n                        self._disconnect()\n                        self._connect()\n                        self._register()\n\n                # Check for stop event after a read from the queue. This is to\n                # allow you to open a socket, immediately send to it, and then\n                # stop it. We do this in the Metadata send at application start\n                # time\n                if self._stop_event.is_set():\n                    logger.debug(\"CoreAgentSocket thread stopping.\")\n                    break\n        except Exception as exc:\n            logger.debug(\"CoreAgentSocket thread exception: %r\", exc, exc_info=exc)\n        finally:\n            self._started_event.clear()\n            self._stop_event.clear()\n            self._stopped_event.set()\n            logger.debug(\"CoreAgentSocket thread stopped.\")\n\n    def send(self, command):\n        try:\n            self.command_queue.put(command, False)\n        except queue.Full as exc:\n            # TODO mark the command as not queued?\n            logger.debug(\"CoreAgentSocket error on send: %r\", exc, exc_info=exc)\n\n    def _send(self, command):\n        msg = command.message()\n\n        try:\n            data = json.dumps(msg)\n        except (ValueError, TypeError) as exc:\n            logger.debug(\n                \"Exception when serializing command message: %r\", exc, exc_info=exc\n            )\n            return False\n\n        try:\n            self.socket.sendall(self._message_length(data))\n        except OSError as exc:\n            logger.debug(\n                \"CoreAgentSocket exception on length _send: \"\n                \"%r on PID: %s on thread: %s\",\n                exc,\n                os.getpid(),\n                threading.current_thread(),\n                exc_info=exc,\n            )\n            return None\n\n        try:\n            self.socket.sendall(data.encode())\n        except OSError as exc:\n            logger.debug(\n                \"CoreAgentSocket exception on data _send: \"\n                \"%r on PID: %s on thread: %s\",\n                exc,\n                os.getpid(),\n                threading.current_thread(),\n                exc_info=exc,\n            )\n            return None\n\n        # TODO do something with the response sent back in reply to command\n        self._read_response()\n\n        return True\n\n    def _message_length(self, body):\n        length = len(body)\n        return struct.pack(\">I\", length)\n\n    def _read_response(self):\n        try:\n            raw_size = self.socket.recv(4)\n            size = struct.unpack(\">I\", raw_size)[0]\n            message = bytearray(0)\n\n            while len(message) < size:\n                recv = self.socket.recv(size)\n                message += recv\n\n            return message\n        except OSError as exc:\n            logger.debug(\n                \"CoreAgentSocket error on read response: %r\", exc, exc_info=exc\n            )\n            return None\n\n    def _register(self):\n        self._send(\n            Register(\n                app=scout_config.value(\"name\"),\n                key=scout_config.value(\"key\"),\n                hostname=scout_config.value(\"hostname\"),\n            )\n        )\n\n    def _connect(self, connect_attempts=5, retry_wait_secs=1):\n        for attempt in range(1, connect_attempts + 1):\n            logger.debug(\n                \"CoreAgentSocket attempt %d, connecting to %s, PID: %s, Thread: %s\",\n                attempt,\n                self.socket_path,\n                os.getpid(),\n                threading.current_thread(),\n            )\n            try:\n                self.socket.connect(self.socket_path)\n                self.socket.settimeout(3 * SECOND)\n                logger.debug(\"CoreAgentSocket is connected\")\n                return True\n            except socket.error as exc:\n                logger.debug(\"CoreAgentSocket connection error: %r\", exc, exc_info=exc)\n                # Return without waiting when reaching the maximum number of attempts.\n                if attempt >= connect_attempts:\n                    return False\n                time.sleep(retry_wait_secs * SECOND)\n\n    def _disconnect(self):\n        logger.debug(\"CoreAgentSocket disconnecting from %s\", self.socket_path)\n        try:\n            self.socket.close()\n        except socket.error as exc:\n            logger.debug(\n                \"CoreAgentSocket exception on disconnect: %r\", exc, exc_info=exc\n            )\n        finally:\n            self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n", "path": "src/scout_apm/core/socket.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport json\nimport logging\nimport os\nimport socket\nimport struct\nimport threading\nimport time\n\nfrom scout_apm.compat import queue\nfrom scout_apm.core.commands import Register\nfrom scout_apm.core.config import scout_config\n\nSECOND = 1  # time unit - monkey-patched in tests to make them run faster\n\nlogger = logging.getLogger(__name__)\n\n\nclass CoreAgentSocket(threading.Thread):\n    _instance = None\n    _instance_lock = threading.Lock()\n\n    @classmethod\n    def instance(cls):\n        with cls._instance_lock:\n            # No instance exists yet.\n            if cls._instance is None:\n                cls._instance = cls()\n                return cls._instance\n\n            # An instance exists but is no longer running.\n            if not cls._instance.running():\n                cls._instance = cls()\n                return cls._instance\n\n            # An instance exists and is running (or in the process of\n            # starting or in the process of stopping). In any case,\n            # return this instance.\n            return cls._instance\n\n    def __init__(self, *args, **kwargs):\n        super(CoreAgentSocket, self).__init__()\n        # Socket related\n        self.socket_path = scout_config.value(\"socket_path\")\n        self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n\n        # Threading control related\n        self._started_event = threading.Event()\n        self._stop_event = threading.Event()\n        self._stopped_event = threading.Event()\n\n        # Command queues\n        self.command_queue = queue.Queue(maxsize=500)\n\n        # Set Thread options\n        self.daemon = True\n\n        # Set the started event here to avoid races in the class instance()\n        # method. If there is an exception in the socket thread then it will\n        # clear this event on exit.\n        self._started_event.set()\n\n        # Now call start() which eventually launches run() in another thread.\n        self.start()\n\n    def __del__(self):\n        self.stop()\n\n    def running(self):\n        return self._started_event.is_set()\n\n    def stop(self):\n        if self._started_event.is_set():\n            self._stop_event.set()\n            self.command_queue.put(None, False)  # unblock self.command_queue.get\n            stopped = self._stopped_event.wait(2 * SECOND)\n            if stopped:\n                return True\n            else:\n                logger.debug(\"CoreAgentSocket Failed to stop thread within timeout!\")\n                return False\n        else:\n            return True\n\n    def run(self):\n        \"\"\"\n        Called by the threading system\n        \"\"\"\n\n        try:\n            self._connect()\n            self._register()\n            while True:\n                try:\n                    body = self.command_queue.get(block=True, timeout=1 * SECOND)\n                except queue.Empty:\n                    body = None\n\n                if body is not None:\n                    result = self._send(body)\n                    if result:\n                        self.command_queue.task_done()\n                    else:\n                        # Something was wrong with the socket.\n                        self._disconnect()\n                        self._connect()\n                        self._register()\n\n                # Check for stop event after a read from the queue. This is to\n                # allow you to open a socket, immediately send to it, and then\n                # stop it. We do this in the Metadata send at application start\n                # time\n                if self._stop_event.is_set():\n                    logger.debug(\"CoreAgentSocket thread stopping.\")\n                    break\n        except Exception as exc:\n            logger.debug(\"CoreAgentSocket thread exception: %r\", exc, exc_info=exc)\n        finally:\n            self._started_event.clear()\n            self._stop_event.clear()\n            self._stopped_event.set()\n            logger.debug(\"CoreAgentSocket thread stopped.\")\n\n    def send(self, command):\n        try:\n            self.command_queue.put(command, False)\n        except queue.Full as exc:\n            # TODO mark the command as not queued?\n            logger.debug(\"CoreAgentSocket error on send: %r\", exc, exc_info=exc)\n\n    def _send(self, command):\n        msg = command.message()\n\n        try:\n            data = json.dumps(msg)\n        except (ValueError, TypeError) as exc:\n            logger.debug(\n                \"Exception when serializing command message: %r\", exc, exc_info=exc\n            )\n            return False\n\n        try:\n            self.socket.sendall(self._message_length(data))\n        except OSError as exc:\n            logger.debug(\n                \"CoreAgentSocket exception on length _send: \"\n                \"%r on PID: %s on thread: %s\",\n                exc,\n                os.getpid(),\n                threading.current_thread(),\n                exc_info=exc,\n            )\n            return None\n\n        try:\n            self.socket.sendall(data.encode())\n        except OSError as exc:\n            logger.debug(\n                \"CoreAgentSocket exception on data _send: \"\n                \"%r on PID: %s on thread: %s\",\n                exc,\n                os.getpid(),\n                threading.current_thread(),\n                exc_info=exc,\n            )\n            return None\n\n        # TODO do something with the response sent back in reply to command\n        self._read_response()\n\n        return True\n\n    def _message_length(self, body):\n        length = len(body)\n        return struct.pack(\">I\", length)\n\n    def _read_response(self):\n        try:\n            raw_size = self.socket.recv(4)\n            if len(raw_size) != 4:\n                # Ignore invalid responses\n                return None\n            size = struct.unpack(\">I\", raw_size)[0]\n            message = bytearray(0)\n\n            while len(message) < size:\n                recv = self.socket.recv(size)\n                message += recv\n\n            return message\n        except OSError as exc:\n            logger.debug(\n                \"CoreAgentSocket error on read response: %r\", exc, exc_info=exc\n            )\n            return None\n\n    def _register(self):\n        self._send(\n            Register(\n                app=scout_config.value(\"name\"),\n                key=scout_config.value(\"key\"),\n                hostname=scout_config.value(\"hostname\"),\n            )\n        )\n\n    def _connect(self, connect_attempts=5, retry_wait_secs=1):\n        for attempt in range(1, connect_attempts + 1):\n            logger.debug(\n                \"CoreAgentSocket attempt %d, connecting to %s, PID: %s, Thread: %s\",\n                attempt,\n                self.socket_path,\n                os.getpid(),\n                threading.current_thread(),\n            )\n            try:\n                self.socket.connect(self.socket_path)\n                self.socket.settimeout(3 * SECOND)\n                logger.debug(\"CoreAgentSocket is connected\")\n                return True\n            except socket.error as exc:\n                logger.debug(\"CoreAgentSocket connection error: %r\", exc, exc_info=exc)\n                # Return without waiting when reaching the maximum number of attempts.\n                if attempt >= connect_attempts:\n                    return False\n                time.sleep(retry_wait_secs * SECOND)\n\n    def _disconnect(self):\n        logger.debug(\"CoreAgentSocket disconnecting from %s\", self.socket_path)\n        try:\n            self.socket.close()\n        except socket.error as exc:\n            logger.debug(\n                \"CoreAgentSocket exception on disconnect: %r\", exc, exc_info=exc\n            )\n        finally:\n            self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n", "path": "src/scout_apm/core/socket.py"}]}