Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for a race condition when starting language server #1780

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions ycmd/completers/language_server/language_server_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,6 @@ def __init__( self, user_options, connection_type = 'stdio' ):
self._server_keep_logfiles = user_options[ 'server_keep_logfiles' ]
self._stdout_file = None
self._stderr_file = None
self._server_started = False

self._Reset()

Expand Down Expand Up @@ -1053,6 +1052,7 @@ def ServerReset( self ):
self._extra_conf_dir = None
self._semantic_token_atlas = None
self._server_workspace_dirs = set()
self._server_started = False


def GetCompleterName( self ):
Expand Down Expand Up @@ -1912,21 +1912,29 @@ def _StartAndInitializeServer( self, request_data, *args, **kwargs ):
StartServer. In general, completers don't need to call this as it is called
automatically in OnFileReadyToParse, but this may be used in completer
subcommands that require restarting the underlying server."""
self._server_started = False
self._extra_conf_dir = self._GetSettingsFromExtraConf( request_data )
try:
# Only attempt to start the server once - _server_started must be
# checked and set in a mutex to prevent race conditions.
with self._server_info_mutex:
if self._server_started:
LOGGER.debug( 'Server %s already started from another thread',
self.GetServerName() )
return
self._server_started = True

# Only attempt to start the server once. Set this after above call as it may
# throw an exception
self._server_started = True
self._extra_conf_dir = self._GetSettingsFromExtraConf( request_data )

if self.StartServer( request_data, *args, **kwargs ):
self._SendInitialize( request_data )
if self.StartServer( request_data, *args, **kwargs ):
self._SendInitialize( request_data )

except Exception:
LOGGER.exception( 'Error while starting %s', self.GetServerName() )
# reset _server_started
self.Shutdown()


def OnFileReadyToParse( self, request_data ):
if not self.ServerIsHealthy() and not self._server_started:
# We have to get the settings before starting the server, as this call
# might throw UnknownExtraConf.
if not self.ServerIsHealthy():
self._StartAndInitializeServer( request_data )

if not self.ServerIsHealthy():
Expand Down Expand Up @@ -2033,10 +2041,9 @@ def _AwaitServerMessages( self, request_data, timeout ):
# restarted while this loop is running.
self._initialize_event.wait( timeout=timeout )

# If the timeout is hit waiting for the server to be ready, after we
# tried to start the server, we return False and kill the message
# poll.
return not self._server_started or self._initialize_event.is_set()
# If the timeout is hit waiting for the server to be ready and
# initialized, we return False and kill the message poll.
return self._initialize_event.is_set()

if not self.GetConnection():
# The server isn't running or something. Don't re-poll, as this will
Expand Down
4 changes: 2 additions & 2 deletions ycmd/tests/language_server/language_server_completer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1482,11 +1482,11 @@ def test_LanguageServerCompleter_Diagnostics_PercentEncodeCannonical(

@IsolatedYcmd()
@patch.object( completer, 'MESSAGE_POLL_TIMEOUT', 0.01 )
def test_LanguageServerCompleter_PollForMessages_ServerNotStarted(
def test_LanguageServerCompleter_PollForMessages_ServerStartTooLong(
self, app ):
server = MockCompleter()
request_data = RequestWrap( BuildRequest() )
assert_that( server.PollForMessages( request_data ), equal_to( True ) )
assert_that( server.PollForMessages( request_data ), equal_to( False ) )


@IsolatedYcmd()
Expand Down