Repository: spyder-ide/spyder

Issue:
LSP cycling : restarting ... ready ... restarting ...
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [x ] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [x] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [x] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [x] Restarted Spyder
    * [ ] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [ ] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description
I use latest version (last commit e3326bd531d16ff3551855a56882f8df129f5dfc) with conda and for some reason the LSP is not working and I have a notification on the bottom bar LSP python restarting and then ready and it cycles for ever


### What steps reproduce the problem?

1. Just launch spyder via python bootstrap.py

### What is the expected output? What do you see instead?
LSP working normally. Linting and completion and everything related to LSP is not working but everything else seems OK !!


### Paste Traceback/Error Below (if applicable)

see the spyder-debug.log

[spyder-debug.log](https://github.com/spyder-ide/spyder/files/4450984/spyder-debug.log)

## Versions

* Spyder version: 5.0.0.dev0 - Revision e3326bd53
* Python version: Python 3.7.6 64bits
* Qt version: 5.12.5
* PyQt version: 5.12.3 on [Linux]
* Operating System name/version: Archlinux with conda

### Dependencies

[conda_list.log](https://github.com/spyder-ide/spyder/files/4451002/conda_list.log)



Discussion:
Please upload or send us privately these files:

    /home/phil/Cours/Python L2/GitHub_Info_L2/JeuHanoi.py
    /home/phil/Papers/Annales_SFT_2020/Recueil.py

so we can understand what's happening in your case.
Another way you can help us is by opening a terminal and starting Spyder with this command:

    spyder --debug-info verbose --debug-output file

After doing that, a file called `spyder-debug.log` will be generated in the same directory where you ran that command. Please upload it here.

And before closing Spyder, please also go to the the menu `Tools > LSP logs` (which is only shown in debug mode):

![Selección_002](https://user-images.githubusercontent.com/365293/78836131-16664a80-79b7-11ea-80d7-4f93c77cc6df.png)

click on the two entries shown on it to open those files in the editor, and upload their contents here too.

Thanks!
Thanks for the reply,

The bug is not related with my py files because it could be reproduced with no files in the history or previously loaded. I think I have already provided a log file in the first post. Have a look at 

https://github.com/spyder-ide/spyder/files/4450984/spyder-debug.log

and 

https://github.com/spyder-ide/spyder/files/4451002/conda_list.log

I have done as you mentionned and here are the LSP_log with the following command line :

which python
/home/phil/miniconda3/envs/myenv/bin/python

and then : 

python bootstrap.py -- --debug-info verbose --debug-output file

[client_python_41809.log](https://github.com/spyder-ide/spyder/files/4454878/client_python_41809.log)
[server_python_41809.log](https://github.com/spyder-ide/spyder/files/4454879/server_python_41809.log)
[spyder-debug.log](https://github.com/spyder-ide/spyder/files/4454881/spyder-debug.log)

it seems that the pyls is not found : 
"/home/phil/miniconda3/envs/myenv/bin/python: No module named pyls"

I uninstalled all spyder-kernels from my conda myenv and remove my ~/.config/spyder-py3-dev/ and reclone the whole repo... still the same

Any clue ?




I finally found the solution ... I think it's a misunderstanding between conda/requirements.txt and the installation via the github sources. So to be clear you have to install python-language-server from conda if you want to use 

`python bootstrap.py`

Thanks for everything. I really hope that Spyder will switch to a newer version of pyqt and then be able to use spyder with the python installed by default in my Archlinux. I'm not a huge fan of conda ...

I really love the spyder-IDE it's my every day IDE for python. Great work !!
> So to be clear you have to install python-language-server from conda if you want to use
> 
> python bootstrap.py

Sure you do. Glad you found the solution!
>So to be clear you have to install python-language-server from conda if you want to use

Should not this work also with pip @ccordoba12 ?
Sure, but it looks like @Ticonderoga was mixing system and conda deps.
Hello, :)

My Spyder Script hangs and becomes really slow for about 15 seconds, several times a day. I noticed that at the bottom of the script during those slow times it says LSP Restarting. This lasts for a short while and then the Spyder speeds back up again to normal typing speed and the LSP says 'ready'. I have not noticed this on any other release, what is happening please?

On a separate issue Spyder gives me notifications for errors (red cross at the start of a line) where there is no error. If I restart Spyder then the red cross disappears (since as mentioned there is no error).

Please could you address both of the above issues?
> My Spyder Script hangs and becomes really slow for about 15 seconds, several times a day. I noticed that at the bottom of the script during those slow times it says LSP Restarting. This lasts for a short while and then the Spyder speeds back up again to normal typing speed and the LSP says 'ready'. I have not noticed this on any other release, what is happening please?

We still don't know why that happens, sorry. It seems to arise after some period of time and in a random way. That's why it's been so hard for us to track.

> On a separate issue Spyder gives me notifications for errors (red cross at the start of a line) where there is no error. If I restart Spyder then the red cross disappears (since as mentioned there is no error).

Some of those errors should be fixed in our next version (4.1.3), to be released next week. If they keep appearing for you, please open a new issue about them with a clear way to reproduce them.
Thank you very much. 

My Spyder keeps hanging for more than 3 minutes and needs fully closing & re-opening because of the LSP Restarting issue. I have lost quite a bit of unsaved work, since even though I do get autosave pop up window (image attached) asking 'what would you like to do', unfortunately there is no other option but to use the one and only 'cancel' button. Is it normal that I can't open an autosaved file? If not how do I do it please / why is only the cancel button available? 

Have a lovely day and thank you,

<img width="673" alt="Screenshot 2020-05-03 at 14 38 56" src="https://user-images.githubusercontent.com/1406362/81006148-edff2e00-8e46-11ea-9ecd-7625afbd9bfb.png">

In your picture, you should be able to scroll to the right and see more buttons. Strangely enough, there is no horizontal scroll bar, is that something specific to Mac? I haven't used one in ages. In case Spyder will crash again in the future, I think the autosave files are stored in the folder `~/.spyder/autosave` on a Mac, and you can copy them yourself if you want.
I was not able to scroll or use any buttons but the cancel one (as a matter of fact you can see I did not have any other columns than the one given), and no Restore button available. 

I believe this issue has now been resolved by the developers just now since for the first time (and I have had to do many re-starts due to this LSP Restarting issue), when I re-opened Spyder following another crash, 15 min after posting my original post, miraculously I now see the buttons :). 

Thank you to whoever sorted this out, much appreciated. 

<img width="668" alt="Screenshot 2020-05-04 at 20 58 07" src="https://user-images.githubusercontent.com/1406362/81012999-60c1d680-8e52-11ea-9325-22c614e5173c.png">

I am facing similar issues with Spyder. I am running latest version. Spyder freezes with LSP Python: restarting and I have to force quit the app which causes me to lose my unsaved changes and session variables. I am really getting frustrated as it happens almost every day.
Hey all, I'm still having a similar issue to what singhalarchit describes. Unlike Ticonderoga who opened this issue, I'm using the Conda installed version and do not have a mix of system and conda dependencies.
> I am facing similar issues with Spyder. I am running latest version. Spyder freezes with LSP Python: restarting and I have to force quit the app which causes me to lose my unsaved changes and session variables. I am really getting frustrated as it happens almost every day.

I also have this problem.
I am having this  issue too. Spyder LSP icon in the status bar is restarting many times while coding and when it happens, the editor freeze and next go to definition, linting and autocomplete stop. However, clicking on this restarting LSP button, Spyder works normally, but after some minutes this happens again.

I only have the latest Spyder 4.1.3 installed with Anaconda in Win10.


Resolving patch:
diff --git a/spyder/plugins/completion/languageserver/__init__.py b/spyder/plugins/completion/languageserver/__init__.py
--- a/spyder/plugins/completion/languageserver/__init__.py
+++ b/spyder/plugins/completion/languageserver/__init__.py
@@ -9,14 +9,6 @@
 LSP client, code introspection and linting utilities.
 """
 
-from spyder.config.base import DEV
-
-
-# Language server communication verbosity at server logs.
-TRACE = 'messages'
-if DEV:
-    TRACE = 'verbose'
-
 # Supported LSP programming languages
 LSP_LANGUAGES = [
     'Bash', 'C#', 'Cpp', 'CSS/LESS/SASS', 'Go', 'GraphQL', 'Groovy', 'Elixir',
diff --git a/spyder/plugins/completion/languageserver/client.py b/spyder/plugins/completion/languageserver/client.py
--- a/spyder/plugins/completion/languageserver/client.py
+++ b/spyder/plugins/completion/languageserver/client.py
@@ -16,12 +16,12 @@
 import os
 import os.path as osp
 import signal
-import subprocess
 import sys
 import time
 
 # Third-party imports
-from qtpy.QtCore import QObject, Signal, QSocketNotifier, Slot
+from qtpy.QtCore import (QObject, QProcess, QProcessEnvironment,
+                         QSocketNotifier, Signal, Slot)
 import zmq
 import psutil
 
@@ -29,7 +29,7 @@
 from spyder.config.base import (DEV, get_conf_path, get_debug_level,
                                 running_under_pytest)
 from spyder.plugins.completion.languageserver import (
-    CLIENT_CAPABILITES, SERVER_CAPABILITES, TRACE,
+    CLIENT_CAPABILITES, SERVER_CAPABILITES,
     TEXT_DOCUMENT_SYNC_OPTIONS, LSPRequestTypes,
     ClientConstants)
 from spyder.plugins.completion.languageserver.decorators import (
@@ -38,7 +38,6 @@
 from spyder.plugins.completion.languageserver.providers import (
     LSPMethodProviderMixIn)
 from spyder.py3compat import PY2
-from spyder.utils.environ import clean_env
 from spyder.utils.misc import getcwd_or_home, select_port
 
 # Conditional imports
@@ -54,6 +53,10 @@
 SERVER_READY = 'server_ready'
 LOCALHOST = '127.0.0.1'
 
+# Language server communication verbosity at server logs.
+TRACE = 'messages'
+if DEV:
+    TRACE = 'verbose'
 
 logger = logging.getLogger(__name__)
 
@@ -70,8 +73,8 @@ class LSPClient(QObject, LSPMethodProviderMixIn):
     sig_server_error = Signal(str)
 
     #: Signal to warn the user when either the transport layer or the
-    #  server are down
-    sig_lsp_down = Signal(str)
+    #  server went down
+    sig_went_down = Signal(str)
 
     def __init__(self, parent,
                  server_settings={},
@@ -83,8 +86,8 @@ def __init__(self, parent,
         self.zmq_out_socket = None
         self.zmq_in_port = None
         self.zmq_out_port = None
-        self.transport_client = None
-        self.lsp_server = None
+        self.transport = None
+        self.server = None
         self.stdio_pid = None
         self.notifier = None
         self.language = language
@@ -155,7 +158,8 @@ def __init__(self, parent,
             self.external_server = True
         self.transport_unresponsive = False
 
-    def start(self):
+    def create_transport_sockets(self):
+        """Create PyZMQ sockets for transport."""
         self.zmq_out_socket = self.context.socket(zmq.PAIR)
         self.zmq_out_port = self.zmq_out_socket.bind_to_random_port(
             'tcp://{}'.format(LOCALHOST))
@@ -166,22 +170,31 @@ def start(self):
         self.transport_args += ['--zmq-in-port', self.zmq_out_port,
                                 '--zmq-out-port', self.zmq_in_port]
 
-        # The server stdout needs to be set to 'None' by default on Windows
-        # to correctly handle errors that come from the server.
-        # Fixes spyder-ide/spyder#11506
-        server_log = None if os.name == 'nt' else subprocess.PIPE
+    @Slot(QProcess.ProcessError)
+    def handle_process_errors(self, error):
+        """Handle errors with the transport layer or server processes."""
+        self.sig_went_down.emit(self.language)
+
+    def start_server(self):
+        """Start server."""
+        # This is not necessary if we're trying to connect to an
+        # external server
+        if self.external_server:
+            return
 
-        pid = os.getpid()
+        # Set server log file
+        server_log_file = None
         if get_debug_level() > 0:
             # Create server log file
-            server_log_fname = 'server_{0}_{1}.log'.format(self.language, pid)
+            server_log_fname = 'server_{0}_{1}.log'.format(self.language,
+                                                           os.getpid())
             server_log_file = get_conf_path(osp.join('lsp_logs',
                                                      server_log_fname))
+
             if not osp.exists(osp.dirname(server_log_file)):
                 os.makedirs(osp.dirname(server_log_file))
-            server_log = open(server_log_file, 'w')
+
             if self.stdio:
-                server_log.close()
                 if self.language == 'python':
                     self.server_args += ['--log-file', server_log_file]
                 self.transport_args += ['--server-log-file', server_log_file]
@@ -193,93 +206,81 @@ def start(self):
                 elif get_debug_level() == 3:
                     self.server_args.append('-vv')
 
-        server_stdin = subprocess.PIPE
-        server_stdout = server_log
-        server_stderr = subprocess.STDOUT
-
-        if not self.external_server:
-            logger.info('Starting server: {0}'.format(
-                ' '.join(self.server_args)))
-            creation_flags = 0
-            if os.name == 'nt':
-                creation_flags = (subprocess.CREATE_NEW_PROCESS_GROUP
-                                  | 0x08000000)  # CREATE_NO_WINDOW
-
-            if os.environ.get('CI') and os.name == 'nt':
-                # The following patching avoids:
-                #
-                # OSError: [WinError 6] The handle is invalid
-                #
-                # while running our tests in CI services on Windows
-                # (they run fine locally).
-                # See this comment for an explanation:
-                # https://stackoverflow.com/q/43966523/
-                # 438386#comment74964124_43966523
-                def patched_cleanup():
-                    pass
-                subprocess._cleanup = patched_cleanup
+        logger.info('Starting server: {0}'.format(' '.join(self.server_args)))
 
-            # Set the PyLS current working to an empty dir inside
-            # our config one. This avoids the server to pick up user
-            # files such as random.py or string.py instead of the
-            # standard library modules named the same.
-            if self.language == 'python':
-                cwd = get_conf_path('empty_cwd')
-                if not osp.exists(cwd):
-                    os.mkdir(cwd)
-            else:
-                cwd = None
+        # Set the PyLS current working to an empty dir inside
+        # our config one. This avoids the server to pick up user
+        # files such as random.py or string.py instead of the
+        # standard library modules named the same.
+        if self.language == 'python':
+            cwd = get_conf_path('empty_cwd')
+            if not osp.exists(cwd):
+                os.mkdir(cwd)
+        else:
+            cwd = None
 
-            self.lsp_server = subprocess.Popen(
-                self.server_args,
-                stdout=server_stdout,
-                stdin=server_stdin,
-                stderr=server_stderr,
-                creationflags=creation_flags,
-                cwd=cwd)
+        # Setup server
+        self.server = QProcess(self)
+        self.server.errorOccurred.connect(self.handle_process_errors)
+        self.server.setWorkingDirectory(cwd)
+        self.server.setProcessChannelMode(QProcess.MergedChannels)
+        if server_log_file is not None:
+            self.server.setStandardOutputFile(server_log_file)
 
-        client_log = subprocess.PIPE
-        if get_debug_level() > 0:
-            # Client log file
-            client_log_fname = 'client_{0}_{1}.log'.format(self.language, pid)
-            client_log_file = get_conf_path(osp.join('lsp_logs',
-                                                     client_log_fname))
-            if not osp.exists(osp.dirname(client_log_file)):
-                os.makedirs(osp.dirname(client_log_file))
-            client_log = open(client_log_file, 'w')
-
-        new_env = dict(os.environ)
-        python_path = os.pathsep.join(sys.path)[1:]
-        new_env['PYTHONPATH'] = python_path
-
-        # This allows running LSP tests directly without having to install
-        # Spyder
-        if running_under_pytest():
-            new_env['PYTHONPATH'] = os.pathsep.join(sys.path)[:]
-
-        # On some CI systems there are unicode characters inside PYTHOPATH
-        # which raise errors if not removed
-        if PY2:
-            new_env = clean_env(new_env)
+        # Start server
+        self.server.start(self.server_args[0], self.server_args[1:])
 
+    def start_transport(self):
+        """Start transport layer."""
         self.transport_args = list(map(str, self.transport_args))
         logger.info('Starting transport: {0}'
                     .format(' '.join(self.transport_args)))
+
+        self.transport = QProcess(self)
+        self.transport.errorOccurred.connect(self.handle_process_errors)
+
+        # Modifying PYTHONPATH to run transport in development mode or
+        # tests
+        if DEV or running_under_pytest():
+            env = QProcessEnvironment()
+            if running_under_pytest():
+                env.insert('PYTHONPATH', os.pathsep.join(sys.path)[:])
+            else:
+                env.insert('PYTHONPATH', os.pathsep.join(sys.path)[1:])
+            self.transport.setProcessEnvironment(env)
+
+        # Set transport log file
+        transport_log_file = None
+        if get_debug_level() > 0:
+            transport_log_fname = 'transport_{0}_{1}.log'.format(
+                self.language, os.getpid())
+            transport_log_file = get_conf_path(
+                osp.join('lsp_logs', transport_log_fname))
+            if not osp.exists(osp.dirname(transport_log_file)):
+                os.makedirs(osp.dirname(transport_log_file))
+
+        # Set channel properties
         if self.stdio:
-            transport_stdin = subprocess.PIPE
-            transport_stdout = subprocess.PIPE
-            transport_stderr = client_log
             self.transport_args += self.server_args
+            self.transport.setProcessChannelMode(QProcess.SeparateChannels)
+            if transport_log_file is not None:
+                self.transport.setStandardErrorFile(transport_log_file)
         else:
-            transport_stdout = client_log
-            transport_stdin = subprocess.PIPE
-            transport_stderr = subprocess.STDOUT
-        self.transport_client = subprocess.Popen(self.transport_args,
-                                                 stdout=transport_stdout,
-                                                 stdin=transport_stdin,
-                                                 stderr=transport_stderr,
-                                                 env=new_env)
+            self.transport.setProcessChannelMode(QProcess.MergedChannels)
+            if transport_log_file is not None:
+                self.transport.setStandardOutputFile(transport_log_file)
+
+        # Start transport
+        self.transport.start(self.transport_args[0], self.transport_args[1:])
 
+    def start(self):
+        """Start client."""
+        # NOTE: DO NOT change the order in which these methods are called.
+        self.create_transport_sockets()
+        self.start_server()
+        self.start_transport()
+
+        # Create notifier
         fid = self.zmq_in_socket.getsockopt(zmq.FD)
         self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)
         self.notifier.activated.connect(self.on_msg_received)
@@ -288,74 +289,75 @@ def patched_cleanup():
         logger.debug('LSP {} client started!'.format(self.language))
 
     def stop(self):
+        """Stop transport and server."""
         logger.info('Stopping {} client...'.format(self.language))
         if self.notifier is not None:
             self.notifier.activated.disconnect(self.on_msg_received)
             self.notifier.setEnabled(False)
             self.notifier = None
-        if self.transport_client is not None:
-            self.transport_client.kill()
+        if self.transport is not None:
+            self.transport.kill()
         self.context.destroy()
-        if self.lsp_server is not None:
-            self.lsp_server.kill()
+        if self.server is not None:
+            self.server.kill()
 
     def is_transport_alive(self):
         """Detect if transport layer is alive."""
-        alive = True
-        if self.transport_client is not None:
-            if self.transport_client.poll() is not None:
-                alive = False
-
-        return alive
+        state = self.transport.state()
+        return state != QProcess.NotRunning
 
     def is_stdio_alive(self):
         """Check if an stdio server is alive."""
         alive = True
-        if self.stdio_pid is not None:
-            if not psutil.pid_exists(self.stdio_pid):
+        if not psutil.pid_exists(self.stdio_pid):
+            alive = False
+        else:
+            try:
+                pid_status = psutil.Process(self.stdio_pid).status()
+            except psutil.NoSuchProcess:
+                pid_status = ''
+            if pid_status == psutil.STATUS_ZOMBIE:
                 alive = False
-            else:
-                try:
-                    pid_status = psutil.Process(self.stdio_pid).status()
-                except psutil.NoSuchProcess:
-                    pid_status = ''
-                if pid_status == psutil.STATUS_ZOMBIE:
-                    alive = False
-
         return alive
 
-    def is_tcp_alive(self):
+    def is_server_alive(self):
         """Detect if a tcp server is alive."""
-        alive = True
-        if self.lsp_server is not None:
-            if self.lsp_server.poll() is not None:
-                alive = False
-
-        return alive
-
-    def send(self, method, params, kind):
-        # Detect when the transport layer is down to show a message
-        # to our users about it.
-        if not self.is_transport_alive():
+        state = self.server.state()
+        return state != QProcess.NotRunning
+
+    def is_down(self):
+        """
+        Detect if the transport layer or server are down to inform our
+        users about it.
+        """
+        is_down = False
+        if self.transport and not self.is_transport_alive():
             logger.debug(
                 "Transport layer for {} is down!!".format(self.language))
             if not self.transport_unresponsive:
                 self.transport_unresponsive = True
-                self.sig_lsp_down.emit(self.language)
-            return
+                self.sig_went_down.emit(self.language)
+            is_down = True
 
-        if not self.is_tcp_alive():
+        if self.server and not self.is_server_alive():
             logger.debug("LSP server for {} is down!!".format(self.language))
             if not self.server_unresponsive:
                 self.server_unresponsive = True
-                self.sig_lsp_down.emit(self.language)
-            return
+                self.sig_went_down.emit(self.language)
+            is_down = True
 
-        if not self.is_stdio_alive():
+        if self.stdio_pid and not self.is_stdio_alive():
             logger.debug("LSP server for {} is down!!".format(self.language))
             if not self.server_unresponsive:
                 self.server_unresponsive = True
-                self.sig_lsp_down.emit(self.language)
+                self.sig_went_down.emit(self.language)
+            is_down = True
+
+        return is_down
+
+    def send(self, method, params, kind):
+        """Send message to transport."""
+        if self.is_down():
             return
 
         if ClientConstants.CANCEL in params:
@@ -393,7 +395,7 @@ def send(self, method, params, kind):
                 return int(_id)
             except zmq.error.Again:
                 if time.time() > timeout_time:
-                    self.sig_lsp_down.emit(self.language)
+                    self.sig_went_down.emit(self.language)
                     return
                 # The send queue is full! wait 0.1 seconds before retrying.
                 if self.initialized:
@@ -402,6 +404,7 @@ def send(self, method, params, kind):
 
     @Slot()
     def on_msg_received(self):
+        """Process received messages."""
         self.notifier.setEnabled(False)
         while True:
             try:
@@ -476,7 +479,7 @@ def perform_request(self, method, params):
     @send_request(method=LSPRequestTypes.INITIALIZE)
     def initialize(self, params, *args, **kwargs):
         self.stdio_pid = params['pid']
-        pid = self.transport_client.pid if not self.external_server else None
+        pid = self.transport.processId() if not self.external_server else None
         params = {
             'processId': pid,
             'rootUri': pathlib.Path(osp.abspath(self.folder)).as_uri(),
diff --git a/spyder/plugins/completion/languageserver/plugin.py b/spyder/plugins/completion/languageserver/plugin.py
--- a/spyder/plugins/completion/languageserver/plugin.py
+++ b/spyder/plugins/completion/languageserver/plugin.py
@@ -107,28 +107,24 @@ def restart_lsp(self, language, force=False):
                 try:
                     self.clients_hearbeat[language].stop()
                     client['instance'].disconnect()
+                    client['instance'].stop()
                 except (TypeError, KeyError, RuntimeError):
                     pass
                 self.clients_hearbeat[language] = None
                 self.report_lsp_down(language)
 
-            # Check if the restart was successful
-            self.check_restart(client, language, kind='tcp')
-
-    def check_restart(self, client, language, kind):
+    def check_restart(self, client, language):
         """
         Check if a server restart was successful in order to stop
         further attempts.
-
-        `kind` can only be "tcp" or "stdio".
         """
         status = client['status']
-        if kind == 'tcp':
-            check = client['instance'].is_tcp_alive()
-        elif kind == 'stdio':
-            check = client['instance'].is_stdio_alive()
-        else:
-            check = False
+        instance = client['instance']
+
+        # This check is only necessary for stdio servers
+        check = True
+        if instance.stdio_pid:
+            check = instance.is_stdio_alive()
 
         if status == self.RUNNING and check:
             logger.info("Restart successful!")
@@ -146,10 +142,8 @@ def check_heartbeat(self, language):
         status = client['status']
         instance = client.get('instance', None)
         if instance is not None:
-            tcp_check = not instance.is_tcp_alive()
-            stdio_check = not instance.is_stdio_alive()
-            if (tcp_check or stdio_check or status != self.RUNNING):
-                instance.sig_lsp_down.emit(language)
+            if instance.is_down() or status != self.RUNNING:
+                instance.sig_went_down.emit(language)
 
     def set_status(self, language, status):
         """
@@ -162,15 +156,14 @@ def on_initialize(self, options, language):
         """
         Update the status bar widget on client initilization.
         """
+        # Set status after the server was started correctly.
         if not self.clients_restarting.get(language, False):
             self.set_status(language, _('ready'))
 
-        # This is the only place where we can detect if restarting
-        # a stdio server was successful because its pid is updated
-        # on initialization.
+        # Set status after a restart.
         if self.clients_restarting.get(language):
             client = self.clients[language]
-            self.check_restart(client, language, kind='stdio')
+            self.check_restart(client, language)
 
     def handle_lsp_down(self, language):
         """
@@ -421,7 +414,7 @@ def register_client_instance(self, instance):
                 self.update_configuration)
             self.main.sig_main_interpreter_changed.connect(
                 self.update_configuration)
-            instance.sig_lsp_down.connect(self.handle_lsp_down)
+            instance.sig_went_down.connect(self.handle_lsp_down)
             instance.sig_initialize.connect(self.on_initialize)
 
             if self.main.editor:


Test patch: