We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
IOError raised when completion plugins fail to start
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

raise IOError('Could not start %s' % self)
OSError: Could not start <spyder.utils.introspection.plugin_client.PluginClient object at 0xb5fc7c5e8>"

### Traceback
```python-traceback
  File "/Users/jimmyxiao/anaconda3/lib/python3.7/site-packages/spyder/utils/introspection/plugin_client.py", line 158, in _on_finished
    self.run()
  File "/Users/jimmyxiao/anaconda3/lib/python3.7/site-packages/spyder/utils/introspection/plugin_client.py", line 104, in run
    raise IOError('Could not start %s' % self)
OSError: Could not start <spyder.utils.introspection.plugin_client.PluginClient object at 0xb5fc7c5e8>
WARNING:spyder.widgets.github.backend:Failed to send bug report on Github. response={'code': 502, 'json': {'message': 'Server Error'}}
```

## Versions

* Spyder version: 3.3.2 
* Python version: 3.7.1
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Darwin 18.2.0

### Dependencies

```
pyflakes >=0.6.0  :  2.0.0 (OK)
pycodestyle >=2.3 :  2.4.0 (OK)
pygments >=2.0    :  2.3.1 (OK)
sphinx >=0.6.6    :  1.8.2 (OK)
rope >=0.9.4      :  0.11.0 (OK)
jedi >=0.9.0      :  0.13.2 (OK)
psutil >=0.3      :  5.4.8 (OK)
nbconvert >=4.0   :  5.4.0 (OK)
pandas >=0.13.1   :  0.23.4 (OK)
numpy >=1.7       :  1.15.4 (OK)
sympy >=0.7.3     :  1.3 (OK)
cython >=0.21     :  0.29.2 (OK)
qtconsole >=4.2.0 :  4.4.3 (OK)
IPython >=4.0     :  7.2.0 (OK)
matplotlib >=2.0.0:  3.0.2 (OK)
pylint >=0.25     :  2.2.2 (OK)
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `spyder/utils/introspection/plugin_client.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright © Spyder Project Contributors
4 # Licensed under the terms of the MIT License
5 # (see spyder/__init__.py for details)
6 
7 # Local imports
8 import imp
9 import os
10 import os.path as osp
11 import sys
12 import uuid
13 
14 # Third party imports
15 from qtpy.QtCore import (QObject, QProcess, QProcessEnvironment,
16                          QSocketNotifier, QTimer, Signal)
17 from qtpy.QtWidgets import QApplication
18 import zmq
19 
20 # Local imports
21 from spyder.config.base import debug_print, get_module_path
22 
23 
24 # Heartbeat timer in milliseconds
25 HEARTBEAT = 1000
26 
27 
28 class AsyncClient(QObject):
29 
30     """
31     A class which handles a connection to a client through a QProcess.
32     """
33 
34     # Emitted when the client has initialized.
35     initialized = Signal()
36 
37     # Emitted when the client errors.
38     errored = Signal()
39 
40     # Emitted when a request response is received.
41     received = Signal(object)
42 
43     def __init__(self, target, executable=None, name=None,
44                  extra_args=None, libs=None, cwd=None, env=None):
45         super(AsyncClient, self).__init__()
46         self.executable = executable or sys.executable
47         self.extra_args = extra_args
48         self.target = target
49         self.name = name or self
50         self.libs = libs
51         self.cwd = cwd
52         self.env = env
53         self.is_initialized = False
54         self.closing = False
55         self.notifier = None
56         self.process = None
57         self.context = zmq.Context()
58         QApplication.instance().aboutToQuit.connect(self.close)
59 
60         # Set up the heartbeat timer.
61         self.timer = QTimer(self)
62         self.timer.timeout.connect(self._heartbeat)
63 
64     def run(self):
65         """Handle the connection with the server.
66         """
67         # Set up the zmq port.
68         self.socket = self.context.socket(zmq.PAIR)
69         self.port = self.socket.bind_to_random_port('tcp://*')
70 
71         # Set up the process.
72         self.process = QProcess(self)
73         if self.cwd:
74             self.process.setWorkingDirectory(self.cwd)
75         p_args = ['-u', self.target, str(self.port)]
76         if self.extra_args is not None:
77             p_args += self.extra_args
78 
79         # Set up environment variables.
80         processEnvironment = QProcessEnvironment()
81         env = self.process.systemEnvironment()
82         if (self.env and 'PYTHONPATH' not in self.env) or self.env is None:
83             python_path = osp.dirname(get_module_path('spyder'))
84             # Add the libs to the python path.
85             for lib in self.libs:
86                 try:
87                     path = osp.dirname(imp.find_module(lib)[1])
88                     python_path = osp.pathsep.join([python_path, path])
89                 except ImportError:
90                     pass
91             env.append("PYTHONPATH=%s" % python_path)
92         if self.env:
93             env.update(self.env)
94         for envItem in env:
95             envName, separator, envValue = envItem.partition('=')
96             processEnvironment.insert(envName, envValue)
97         self.process.setProcessEnvironment(processEnvironment)
98 
99         # Start the process and wait for started.
100         self.process.start(self.executable, p_args)
101         self.process.finished.connect(self._on_finished)
102         running = self.process.waitForStarted()
103         if not running:
104             raise IOError('Could not start %s' % self)
105 
106         # Set up the socket notifer.
107         fid = self.socket.getsockopt(zmq.FD)
108         self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)
109         self.notifier.activated.connect(self._on_msg_received)
110 
111     def request(self, func_name, *args, **kwargs):
112         """Send a request to the server.
113 
114         The response will be a dictionary the 'request_id' and the
115         'func_name' as well as a 'result' field with the object returned by
116         the function call or or an 'error' field with a traceback.
117         """
118         if not self.is_initialized:
119             return
120         request_id = uuid.uuid4().hex
121         request = dict(func_name=func_name,
122                        args=args,
123                        kwargs=kwargs,
124                        request_id=request_id)
125         self._send(request)
126         return request_id
127 
128     def close(self):
129         """Cleanly close the connection to the server.
130         """
131         self.closing = True
132         self.is_initialized = False
133         self.timer.stop()
134 
135         if self.notifier is not None:
136             self.notifier.activated.disconnect(self._on_msg_received)
137             self.notifier.setEnabled(False)
138             self.notifier = None
139 
140         self.request('server_quit')
141 
142         if self.process is not None:
143             self.process.waitForFinished(1000)
144             self.process.close()
145         self.context.destroy()
146 
147     def _on_finished(self):
148         """Handle a finished signal from the process.
149         """
150         if self.closing:
151             return
152         if self.is_initialized:
153             debug_print('Restarting %s' % self.name)
154             debug_print(self.process.readAllStandardOutput())
155             debug_print(self.process.readAllStandardError())
156             self.is_initialized = False
157             self.notifier.setEnabled(False)
158             self.run()
159         else:
160             debug_print('Errored %s' % self.name)
161             debug_print(self.process.readAllStandardOutput())
162             debug_print(self.process.readAllStandardError())
163             self.errored.emit()
164 
165     def _on_msg_received(self):
166         """Handle a message trigger from the socket.
167         """
168         self.notifier.setEnabled(False)
169         while 1:
170             try:
171                 resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK)
172             except zmq.ZMQError:
173                 self.notifier.setEnabled(True)
174                 return
175             if not self.is_initialized:
176                 self.is_initialized = True
177                 debug_print('Initialized %s' % self.name)
178                 self.initialized.emit()
179                 self.timer.start(HEARTBEAT)
180                 continue
181             resp['name'] = self.name
182             self.received.emit(resp)
183 
184     def _heartbeat(self):
185         """Send a heartbeat to keep the server alive.
186         """
187         self._send(dict(func_name='server_heartbeat'))
188 
189     def _send(self, obj):
190         """Send an object to the server.
191         """
192         try:
193             self.socket.send_pyobj(obj, zmq.NOBLOCK)
194         except Exception as e:
195             debug_print(e)
196             self.is_initialized = False
197             self._on_finished()
198 
199 
200 class PluginClient(AsyncClient):
201 
202     def __init__(self, plugin_name, executable=None, env=None,
203                  extra_path=None):
204         cwd = os.path.dirname(__file__)
205         super(PluginClient, self).__init__(
206                 'plugin_server.py',
207                 executable=executable, cwd=cwd, env=env,
208                 extra_args=[plugin_name], libs=[plugin_name])
209         self.name = plugin_name
210 
211 
212 if __name__ == '__main__':
213     app = QApplication(sys.argv)
214     plugin = PluginClient('jedi')
215     plugin.run()
216 
217     def handle_return(value):
218         print(value)  # spyder: test-skip
219         if value['func_name'] == 'foo':
220             app.quit()
221         else:
222             plugin.request('foo')
223 
224     def handle_errored():
225         print('errored')  # spyder: test-skip
226         sys.exit(1)
227 
228     def start():
229         print('start')  # spyder: test-skip
230         plugin.request('validate')
231 
232     plugin.errored.connect(handle_errored)
233     plugin.received.connect(handle_return)
234     plugin.initialized.connect(start)
235 
236     app.exec_()
237
```
--- 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/spyder/utils/introspection/plugin_client.py b/spyder/utils/introspection/plugin_client.py
--- a/spyder/utils/introspection/plugin_client.py
+++ b/spyder/utils/introspection/plugin_client.py
@@ -101,7 +101,10 @@
         self.process.finished.connect(self._on_finished)
         running = self.process.waitForStarted()
         if not running:
-            raise IOError('Could not start %s' % self)
+            # Don't raise an error if the plugin fails to start
+            # Fixes issue 8934
+            debug_print('Could not start %s' % self)
+            return
 
         # Set up the socket notifer.
         fid = self.socket.getsockopt(zmq.FD)

{"golden_diff": "diff --git a/spyder/utils/introspection/plugin_client.py b/spyder/utils/introspection/plugin_client.py\n--- a/spyder/utils/introspection/plugin_client.py\n+++ b/spyder/utils/introspection/plugin_client.py\n@@ -101,7 +101,10 @@\n         self.process.finished.connect(self._on_finished)\n         running = self.process.waitForStarted()\n         if not running:\n-            raise IOError('Could not start %s' % self)\n+            # Don't raise an error if the plugin fails to start\n+            # Fixes issue 8934\n+            debug_print('Could not start %s' % self)\n+            return\n \n         # Set up the socket notifer.\n         fid = self.socket.getsockopt(zmq.FD)\n", "issue": "IOError raised when completion plugins fail to start\n## Description\n\n### What steps will reproduce the problem?\n\n<!--- You can use Markdown here --->\n\nraise IOError('Could not start %s' % self)\nOSError: Could not start <spyder.utils.introspection.plugin_client.PluginClient object at 0xb5fc7c5e8>\"\n\n### Traceback\n```python-traceback\n  File \"/Users/jimmyxiao/anaconda3/lib/python3.7/site-packages/spyder/utils/introspection/plugin_client.py\", line 158, in _on_finished\n    self.run()\n  File \"/Users/jimmyxiao/anaconda3/lib/python3.7/site-packages/spyder/utils/introspection/plugin_client.py\", line 104, in run\n    raise IOError('Could not start %s' % self)\nOSError: Could not start <spyder.utils.introspection.plugin_client.PluginClient object at 0xb5fc7c5e8>\nWARNING:spyder.widgets.github.backend:Failed to send bug report on Github. response={'code': 502, 'json': {'message': 'Server Error'}}\n```\n\n## Versions\n\n* Spyder version: 3.3.2 \n* Python version: 3.7.1\n* Qt version: 5.9.6\n* PyQt5 version: 5.9.2\n* Operating System: Darwin 18.2.0\n\n### Dependencies\n\n```\npyflakes >=0.6.0  :  2.0.0 (OK)\npycodestyle >=2.3 :  2.4.0 (OK)\npygments >=2.0    :  2.3.1 (OK)\nsphinx >=0.6.6    :  1.8.2 (OK)\nrope >=0.9.4      :  0.11.0 (OK)\njedi >=0.9.0      :  0.13.2 (OK)\npsutil >=0.3      :  5.4.8 (OK)\nnbconvert >=4.0   :  5.4.0 (OK)\npandas >=0.13.1   :  0.23.4 (OK)\nnumpy >=1.7       :  1.15.4 (OK)\nsympy >=0.7.3     :  1.3 (OK)\ncython >=0.21     :  0.29.2 (OK)\nqtconsole >=4.2.0 :  4.4.3 (OK)\nIPython >=4.0     :  7.2.0 (OK)\nmatplotlib >=2.0.0:  3.0.2 (OK)\npylint >=0.25     :  2.2.2 (OK)\n```\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright \u00a9 Spyder Project Contributors\n# Licensed under the terms of the MIT License\n# (see spyder/__init__.py for details)\n\n# Local imports\nimport imp\nimport os\nimport os.path as osp\nimport sys\nimport uuid\n\n# Third party imports\nfrom qtpy.QtCore import (QObject, QProcess, QProcessEnvironment,\n                         QSocketNotifier, QTimer, Signal)\nfrom qtpy.QtWidgets import QApplication\nimport zmq\n\n# Local imports\nfrom spyder.config.base import debug_print, get_module_path\n\n\n# Heartbeat timer in milliseconds\nHEARTBEAT = 1000\n\n\nclass AsyncClient(QObject):\n\n    \"\"\"\n    A class which handles a connection to a client through a QProcess.\n    \"\"\"\n\n    # Emitted when the client has initialized.\n    initialized = Signal()\n\n    # Emitted when the client errors.\n    errored = Signal()\n\n    # Emitted when a request response is received.\n    received = Signal(object)\n\n    def __init__(self, target, executable=None, name=None,\n                 extra_args=None, libs=None, cwd=None, env=None):\n        super(AsyncClient, self).__init__()\n        self.executable = executable or sys.executable\n        self.extra_args = extra_args\n        self.target = target\n        self.name = name or self\n        self.libs = libs\n        self.cwd = cwd\n        self.env = env\n        self.is_initialized = False\n        self.closing = False\n        self.notifier = None\n        self.process = None\n        self.context = zmq.Context()\n        QApplication.instance().aboutToQuit.connect(self.close)\n\n        # Set up the heartbeat timer.\n        self.timer = QTimer(self)\n        self.timer.timeout.connect(self._heartbeat)\n\n    def run(self):\n        \"\"\"Handle the connection with the server.\n        \"\"\"\n        # Set up the zmq port.\n        self.socket = self.context.socket(zmq.PAIR)\n        self.port = self.socket.bind_to_random_port('tcp://*')\n\n        # Set up the process.\n        self.process = QProcess(self)\n        if self.cwd:\n            self.process.setWorkingDirectory(self.cwd)\n        p_args = ['-u', self.target, str(self.port)]\n        if self.extra_args is not None:\n            p_args += self.extra_args\n\n        # Set up environment variables.\n        processEnvironment = QProcessEnvironment()\n        env = self.process.systemEnvironment()\n        if (self.env and 'PYTHONPATH' not in self.env) or self.env is None:\n            python_path = osp.dirname(get_module_path('spyder'))\n            # Add the libs to the python path.\n            for lib in self.libs:\n                try:\n                    path = osp.dirname(imp.find_module(lib)[1])\n                    python_path = osp.pathsep.join([python_path, path])\n                except ImportError:\n                    pass\n            env.append(\"PYTHONPATH=%s\" % python_path)\n        if self.env:\n            env.update(self.env)\n        for envItem in env:\n            envName, separator, envValue = envItem.partition('=')\n            processEnvironment.insert(envName, envValue)\n        self.process.setProcessEnvironment(processEnvironment)\n\n        # Start the process and wait for started.\n        self.process.start(self.executable, p_args)\n        self.process.finished.connect(self._on_finished)\n        running = self.process.waitForStarted()\n        if not running:\n            raise IOError('Could not start %s' % self)\n\n        # Set up the socket notifer.\n        fid = self.socket.getsockopt(zmq.FD)\n        self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)\n        self.notifier.activated.connect(self._on_msg_received)\n\n    def request(self, func_name, *args, **kwargs):\n        \"\"\"Send a request to the server.\n\n        The response will be a dictionary the 'request_id' and the\n        'func_name' as well as a 'result' field with the object returned by\n        the function call or or an 'error' field with a traceback.\n        \"\"\"\n        if not self.is_initialized:\n            return\n        request_id = uuid.uuid4().hex\n        request = dict(func_name=func_name,\n                       args=args,\n                       kwargs=kwargs,\n                       request_id=request_id)\n        self._send(request)\n        return request_id\n\n    def close(self):\n        \"\"\"Cleanly close the connection to the server.\n        \"\"\"\n        self.closing = True\n        self.is_initialized = False\n        self.timer.stop()\n\n        if self.notifier is not None:\n            self.notifier.activated.disconnect(self._on_msg_received)\n            self.notifier.setEnabled(False)\n            self.notifier = None\n\n        self.request('server_quit')\n\n        if self.process is not None:\n            self.process.waitForFinished(1000)\n            self.process.close()\n        self.context.destroy()\n\n    def _on_finished(self):\n        \"\"\"Handle a finished signal from the process.\n        \"\"\"\n        if self.closing:\n            return\n        if self.is_initialized:\n            debug_print('Restarting %s' % self.name)\n            debug_print(self.process.readAllStandardOutput())\n            debug_print(self.process.readAllStandardError())\n            self.is_initialized = False\n            self.notifier.setEnabled(False)\n            self.run()\n        else:\n            debug_print('Errored %s' % self.name)\n            debug_print(self.process.readAllStandardOutput())\n            debug_print(self.process.readAllStandardError())\n            self.errored.emit()\n\n    def _on_msg_received(self):\n        \"\"\"Handle a message trigger from the socket.\n        \"\"\"\n        self.notifier.setEnabled(False)\n        while 1:\n            try:\n                resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK)\n            except zmq.ZMQError:\n                self.notifier.setEnabled(True)\n                return\n            if not self.is_initialized:\n                self.is_initialized = True\n                debug_print('Initialized %s' % self.name)\n                self.initialized.emit()\n                self.timer.start(HEARTBEAT)\n                continue\n            resp['name'] = self.name\n            self.received.emit(resp)\n\n    def _heartbeat(self):\n        \"\"\"Send a heartbeat to keep the server alive.\n        \"\"\"\n        self._send(dict(func_name='server_heartbeat'))\n\n    def _send(self, obj):\n        \"\"\"Send an object to the server.\n        \"\"\"\n        try:\n            self.socket.send_pyobj(obj, zmq.NOBLOCK)\n        except Exception as e:\n            debug_print(e)\n            self.is_initialized = False\n            self._on_finished()\n\n\nclass PluginClient(AsyncClient):\n\n    def __init__(self, plugin_name, executable=None, env=None,\n                 extra_path=None):\n        cwd = os.path.dirname(__file__)\n        super(PluginClient, self).__init__(\n                'plugin_server.py',\n                executable=executable, cwd=cwd, env=env,\n                extra_args=[plugin_name], libs=[plugin_name])\n        self.name = plugin_name\n\n\nif __name__ == '__main__':\n    app = QApplication(sys.argv)\n    plugin = PluginClient('jedi')\n    plugin.run()\n\n    def handle_return(value):\n        print(value)  # spyder: test-skip\n        if value['func_name'] == 'foo':\n            app.quit()\n        else:\n            plugin.request('foo')\n\n    def handle_errored():\n        print('errored')  # spyder: test-skip\n        sys.exit(1)\n\n    def start():\n        print('start')  # spyder: test-skip\n        plugin.request('validate')\n\n    plugin.errored.connect(handle_errored)\n    plugin.received.connect(handle_return)\n    plugin.initialized.connect(start)\n\n    app.exec_()\n", "path": "spyder/utils/introspection/plugin_client.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright \u00a9 Spyder Project Contributors\n# Licensed under the terms of the MIT License\n# (see spyder/__init__.py for details)\n\n# Local imports\nimport imp\nimport os\nimport os.path as osp\nimport sys\nimport uuid\n\n# Third party imports\nfrom qtpy.QtCore import (QObject, QProcess, QProcessEnvironment,\n                         QSocketNotifier, QTimer, Signal)\nfrom qtpy.QtWidgets import QApplication\nimport zmq\n\n# Local imports\nfrom spyder.config.base import debug_print, get_module_path\n\n\n# Heartbeat timer in milliseconds\nHEARTBEAT = 1000\n\n\nclass AsyncClient(QObject):\n\n    \"\"\"\n    A class which handles a connection to a client through a QProcess.\n    \"\"\"\n\n    # Emitted when the client has initialized.\n    initialized = Signal()\n\n    # Emitted when the client errors.\n    errored = Signal()\n\n    # Emitted when a request response is received.\n    received = Signal(object)\n\n    def __init__(self, target, executable=None, name=None,\n                 extra_args=None, libs=None, cwd=None, env=None):\n        super(AsyncClient, self).__init__()\n        self.executable = executable or sys.executable\n        self.extra_args = extra_args\n        self.target = target\n        self.name = name or self\n        self.libs = libs\n        self.cwd = cwd\n        self.env = env\n        self.is_initialized = False\n        self.closing = False\n        self.notifier = None\n        self.process = None\n        self.context = zmq.Context()\n        QApplication.instance().aboutToQuit.connect(self.close)\n\n        # Set up the heartbeat timer.\n        self.timer = QTimer(self)\n        self.timer.timeout.connect(self._heartbeat)\n\n    def run(self):\n        \"\"\"Handle the connection with the server.\n        \"\"\"\n        # Set up the zmq port.\n        self.socket = self.context.socket(zmq.PAIR)\n        self.port = self.socket.bind_to_random_port('tcp://*')\n\n        # Set up the process.\n        self.process = QProcess(self)\n        if self.cwd:\n            self.process.setWorkingDirectory(self.cwd)\n        p_args = ['-u', self.target, str(self.port)]\n        if self.extra_args is not None:\n            p_args += self.extra_args\n\n        # Set up environment variables.\n        processEnvironment = QProcessEnvironment()\n        env = self.process.systemEnvironment()\n        if (self.env and 'PYTHONPATH' not in self.env) or self.env is None:\n            python_path = osp.dirname(get_module_path('spyder'))\n            # Add the libs to the python path.\n            for lib in self.libs:\n                try:\n                    path = osp.dirname(imp.find_module(lib)[1])\n                    python_path = osp.pathsep.join([python_path, path])\n                except ImportError:\n                    pass\n            env.append(\"PYTHONPATH=%s\" % python_path)\n        if self.env:\n            env.update(self.env)\n        for envItem in env:\n            envName, separator, envValue = envItem.partition('=')\n            processEnvironment.insert(envName, envValue)\n        self.process.setProcessEnvironment(processEnvironment)\n\n        # Start the process and wait for started.\n        self.process.start(self.executable, p_args)\n        self.process.finished.connect(self._on_finished)\n        running = self.process.waitForStarted()\n        if not running:\n            # Don't raise an error if the plugin fails to start\n            # Fixes issue 8934\n            debug_print('Could not start %s' % self)\n            return\n\n        # Set up the socket notifer.\n        fid = self.socket.getsockopt(zmq.FD)\n        self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)\n        self.notifier.activated.connect(self._on_msg_received)\n\n    def request(self, func_name, *args, **kwargs):\n        \"\"\"Send a request to the server.\n\n        The response will be a dictionary the 'request_id' and the\n        'func_name' as well as a 'result' field with the object returned by\n        the function call or or an 'error' field with a traceback.\n        \"\"\"\n        if not self.is_initialized:\n            return\n        request_id = uuid.uuid4().hex\n        request = dict(func_name=func_name,\n                       args=args,\n                       kwargs=kwargs,\n                       request_id=request_id)\n        self._send(request)\n        return request_id\n\n    def close(self):\n        \"\"\"Cleanly close the connection to the server.\n        \"\"\"\n        self.closing = True\n        self.is_initialized = False\n        self.timer.stop()\n\n        if self.notifier is not None:\n            self.notifier.activated.disconnect(self._on_msg_received)\n            self.notifier.setEnabled(False)\n            self.notifier = None\n\n        self.request('server_quit')\n\n        if self.process is not None:\n            self.process.waitForFinished(1000)\n            self.process.close()\n        self.context.destroy()\n\n    def _on_finished(self):\n        \"\"\"Handle a finished signal from the process.\n        \"\"\"\n        if self.closing:\n            return\n        if self.is_initialized:\n            debug_print('Restarting %s' % self.name)\n            debug_print(self.process.readAllStandardOutput())\n            debug_print(self.process.readAllStandardError())\n            self.is_initialized = False\n            self.notifier.setEnabled(False)\n            self.run()\n        else:\n            debug_print('Errored %s' % self.name)\n            debug_print(self.process.readAllStandardOutput())\n            debug_print(self.process.readAllStandardError())\n            self.errored.emit()\n\n    def _on_msg_received(self):\n        \"\"\"Handle a message trigger from the socket.\n        \"\"\"\n        self.notifier.setEnabled(False)\n        while 1:\n            try:\n                resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK)\n            except zmq.ZMQError:\n                self.notifier.setEnabled(True)\n                return\n            if not self.is_initialized:\n                self.is_initialized = True\n                debug_print('Initialized %s' % self.name)\n                self.initialized.emit()\n                self.timer.start(HEARTBEAT)\n                continue\n            resp['name'] = self.name\n            self.received.emit(resp)\n\n    def _heartbeat(self):\n        \"\"\"Send a heartbeat to keep the server alive.\n        \"\"\"\n        self._send(dict(func_name='server_heartbeat'))\n\n    def _send(self, obj):\n        \"\"\"Send an object to the server.\n        \"\"\"\n        try:\n            self.socket.send_pyobj(obj, zmq.NOBLOCK)\n        except Exception as e:\n            debug_print(e)\n            self.is_initialized = False\n            self._on_finished()\n\n\nclass PluginClient(AsyncClient):\n\n    def __init__(self, plugin_name, executable=None, env=None,\n                 extra_path=None):\n        cwd = os.path.dirname(__file__)\n        super(PluginClient, self).__init__(\n                'plugin_server.py',\n                executable=executable, cwd=cwd, env=env,\n                extra_args=[plugin_name], libs=[plugin_name])\n        self.name = plugin_name\n\n\nif __name__ == '__main__':\n    app = QApplication(sys.argv)\n    plugin = PluginClient('jedi')\n    plugin.run()\n\n    def handle_return(value):\n        print(value)  # spyder: test-skip\n        if value['func_name'] == 'foo':\n            app.quit()\n        else:\n            plugin.request('foo')\n\n    def handle_errored():\n        print('errored')  # spyder: test-skip\n        sys.exit(1)\n\n    def start():\n        print('start')  # spyder: test-skip\n        plugin.request('validate')\n\n    plugin.errored.connect(handle_errored)\n    plugin.received.connect(handle_return)\n    plugin.initialized.connect(start)\n\n    app.exec_()\n", "path": "spyder/utils/introspection/plugin_client.py"}]}