Catch and log all server exceptions

This commit is contained in:
micbou 2016-11-05 14:57:02 +01:00
parent 48b7ccef76
commit fd41d52dfe
No known key found for this signature in database
GPG Key ID: C7E8FD1F3BDA1E05
10 changed files with 113 additions and 82 deletions

View File

@ -23,6 +23,8 @@ from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from builtins import * # noqa from builtins import * # noqa
import contextlib
import logging
import requests import requests
import urllib.parse import urllib.parse
import json import json
@ -41,6 +43,7 @@ _EXECUTOR = UnsafeThreadPoolExecutor( max_workers = 30 )
# Setting this to None seems to screw up the Requests/urllib3 libs. # Setting this to None seems to screw up the Requests/urllib3 libs.
_DEFAULT_TIMEOUT_SEC = 30 _DEFAULT_TIMEOUT_SEC = 30
_HMAC_HEADER = 'x-ycm-hmac' _HMAC_HEADER = 'x-ycm-hmac'
_logger = logging.getLogger( __name__ )
class BaseRequest( object ): class BaseRequest( object ):
@ -193,7 +196,32 @@ def JsonFromFuture( future ):
return None return None
def HandleServerException( exception, truncate = False ): @contextlib.contextmanager
def HandleServerException( display = True, truncate = False ):
try:
yield
except UnknownExtraConf as e:
if vimsupport.Confirm( str( e ) ):
_LoadExtraConfFile( e.extra_conf_file )
else:
_IgnoreExtraConfFile( e.extra_conf_file )
except Exception as e:
_logger.exception( 'Error while handling server response' )
if display:
DisplayServerException( e, truncate )
def _LoadExtraConfFile( filepath ):
BaseRequest.PostDataToHandler( { 'filepath': filepath },
'load_extra_conf_file' )
def _IgnoreExtraConfFile( filepath ):
BaseRequest.PostDataToHandler( { 'filepath': filepath },
'ignore_extra_conf_file' )
def DisplayServerException( exception, truncate = False ):
serialized_exception = str( exception ) serialized_exception = str( exception )
# We ignore the exception about the file already being parsed since it comes # We ignore the exception about the file already being parsed since it comes

View File

@ -23,9 +23,6 @@ from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from builtins import * # noqa from builtins import * # noqa
from requests.exceptions import ReadTimeout
from ycmd.responses import ServerError
from ycm.client.base_request import ( BaseRequest, BuildRequestData, from ycm.client.base_request import ( BaseRequest, BuildRequestData,
HandleServerException ) HandleServerException )
from ycm import vimsupport from ycm import vimsupport
@ -53,11 +50,9 @@ class CommandRequest( BaseRequest ):
'completer_target': self._completer_target, 'completer_target': self._completer_target,
'command_arguments': self._arguments 'command_arguments': self._arguments
} ) } )
try: with HandleServerException():
self._response = self.PostDataToHandler( request_data, self._response = self.PostDataToHandler( request_data,
'run_completer_command' ) 'run_completer_command' )
except ( ServerError, ReadTimeout ) as e:
HandleServerException( e )
def Response( self ): def Response( self ):

View File

@ -23,11 +23,8 @@ from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from builtins import * # noqa from builtins import * # noqa
from requests.exceptions import ReadTimeout
from ycm.client.base_request import ( BaseRequest, BuildRequestData, from ycm.client.base_request import ( BaseRequest, BuildRequestData,
HandleServerException ) HandleServerException )
from ycmd.responses import ServerError
class CompleterAvailableRequest( BaseRequest ): class CompleterAvailableRequest( BaseRequest ):
@ -40,11 +37,9 @@ class CompleterAvailableRequest( BaseRequest ):
def Start( self ): def Start( self ):
request_data = BuildRequestData() request_data = BuildRequestData()
request_data.update( { 'filetypes': self.filetypes } ) request_data.update( { 'filetypes': self.filetypes } )
try: with HandleServerException():
self._response = self.PostDataToHandler( request_data, self._response = self.PostDataToHandler( request_data,
'semantic_completion_available' ) 'semantic_completion_available' )
except ( ServerError, ReadTimeout ) as e:
HandleServerException( e )
def Response( self ): def Response( self ):

View File

@ -23,13 +23,10 @@ from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from builtins import * # noqa from builtins import * # noqa
from requests.exceptions import ReadTimeout
from ycmd.utils import ToUnicode from ycmd.utils import ToUnicode
from ycm.client.base_request import ( BaseRequest, JsonFromFuture, from ycm.client.base_request import ( BaseRequest, JsonFromFuture,
HandleServerException, HandleServerException,
MakeServerException ) MakeServerException )
from ycmd.responses import ServerError
TIMEOUT_SECONDS = 0.5 TIMEOUT_SECONDS = 0.5
@ -53,16 +50,15 @@ class CompletionRequest( BaseRequest ):
def RawResponse( self ): def RawResponse( self ):
if not self._response_future: if not self._response_future:
return [] return []
try: with HandleServerException( truncate = True ):
response = JsonFromFuture( self._response_future ) response = JsonFromFuture( self._response_future )
errors = response[ 'errors' ] if 'errors' in response else [] errors = response[ 'errors' ] if 'errors' in response else []
for e in errors: for e in errors:
HandleServerException( MakeServerException( e ) ) with HandleServerException( truncate = True ):
raise MakeServerException( e )
return JsonFromFuture( self._response_future )[ 'completions' ] return response[ 'completions' ]
except ( ServerError, ReadTimeout ) as e:
HandleServerException( e, truncate = True )
return [] return []

View File

@ -0,0 +1,52 @@
# Copyright (C) 2016 YouCompleteMe contributors
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import * # noqa
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
HandleServerException )
class DebugInfoRequest( BaseRequest ):
def __init__( self ):
super( DebugInfoRequest, self ).__init__()
self._response = None
def Start( self ):
request_data = BuildRequestData()
with HandleServerException( display = False ):
self._response = self.PostDataToHandler( request_data, 'debug_info' )
def Response( self ):
if not self._response:
return 'Server errored, no debug info from server'
return self._response
def SendDebugInfoRequest():
request = DebugInfoRequest()
# This is a blocking call.
request.Start()
return request.Response()

View File

@ -23,10 +23,6 @@ from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from builtins import * # noqa from builtins import * # noqa
from requests.exceptions import ReadTimeout
from ycm import vimsupport
from ycmd.responses import UnknownExtraConf, ServerError
from ycm.client.base_request import ( BaseRequest, BuildRequestData, from ycm.client.base_request import ( BaseRequest, BuildRequestData,
JsonFromFuture, HandleServerException ) JsonFromFuture, HandleServerException )
@ -61,16 +57,8 @@ class EventNotification( BaseRequest ):
if not self._response_future or self._event_name != 'FileReadyToParse': if not self._response_future or self._event_name != 'FileReadyToParse':
return [] return []
try: with HandleServerException( truncate = True ):
try: self._cached_response = JsonFromFuture( self._response_future )
self._cached_response = JsonFromFuture( self._response_future )
except UnknownExtraConf as e:
if vimsupport.Confirm( str( e ) ):
_LoadExtraConfFile( e.extra_conf_file )
else:
_IgnoreExtraConfFile( e.extra_conf_file )
except ( ServerError, ReadTimeout ) as e:
HandleServerException( e )
return self._cached_response if self._cached_response else [] return self._cached_response if self._cached_response else []
@ -80,13 +68,3 @@ def SendEventNotificationAsync( event_name,
extra_data = None ): extra_data = None ):
event = EventNotification( event_name, filepath, extra_data ) event = EventNotification( event_name, filepath, extra_data )
event.Start() event.Start()
def _LoadExtraConfFile( filepath ):
BaseRequest.PostDataToHandler( { 'filepath': filepath },
'load_extra_conf_file' )
def _IgnoreExtraConfFile( filepath ):
BaseRequest.PostDataToHandler( { 'filepath': filepath },
'ignore_extra_conf_file' )

View File

@ -23,9 +23,7 @@ from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from builtins import * # noqa from builtins import * # noqa
from requests.exceptions import ReadTimeout from ycm.client.base_request import BaseRequest, HandleServerException
from ycm.client.base_request import BaseRequest
TIMEOUT_SECONDS = 0.1 TIMEOUT_SECONDS = 0.1
@ -36,12 +34,8 @@ class ShutdownRequest( BaseRequest ):
def Start( self ): def Start( self ):
try: with HandleServerException( display = False ):
self.PostDataToHandler( {}, self.PostDataToHandler( {}, 'shutdown', TIMEOUT_SECONDS )
'shutdown',
TIMEOUT_SECONDS )
except ReadTimeout:
pass
def SendShutdownRequest(): def SendShutdownRequest():

View File

@ -26,7 +26,6 @@ from builtins import * # noqa
import vim import vim
from ycm import vimsupport from ycm import vimsupport
from ycmd import utils from ycmd import utils
from ycmd.responses import ServerError
from ycmd.completers.completer import Completer from ycmd.completers.completer import Completer
from ycm.client.base_request import BaseRequest, HandleServerException from ycm.client.base_request import BaseRequest, HandleServerException
@ -115,9 +114,7 @@ class OmniCompleter( Completer ):
'query': query 'query': query
} }
try: with HandleServerException():
return BaseRequest.PostDataToHandler( request_data, return BaseRequest.PostDataToHandler( request_data,
'filter_and_sort_candidates' ) 'filter_and_sort_candidates' )
except ServerError as e: return candidates
HandleServerException( e )
return candidates

View File

@ -128,13 +128,13 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_test(
# The first call raises a warning # The first call raises a warning
post_vim_message.assert_has_exact_calls( [ post_vim_message.assert_has_exact_calls( [
call( ERROR_TEXT, truncate = False ) call( ERROR_TEXT, truncate = True )
] ) ] )
# Subsequent calls don't re-raise the warning # Subsequent calls don't re-raise the warning
ycm.HandleFileParseRequest() ycm.HandleFileParseRequest()
post_vim_message.assert_has_exact_calls( [ post_vim_message.assert_has_exact_calls( [
call( ERROR_TEXT, truncate = False ) call( ERROR_TEXT, truncate = True )
] ) ] )
# But it does if a subsequent event raises again # But it does if a subsequent event raises again
@ -142,8 +142,8 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_test(
ok_( ycm.FileParseRequestReady() ) ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest() ycm.HandleFileParseRequest()
post_vim_message.assert_has_exact_calls( [ post_vim_message.assert_has_exact_calls( [
call( ERROR_TEXT, truncate = False ), call( ERROR_TEXT, truncate = True ),
call( ERROR_TEXT, truncate = False ) call( ERROR_TEXT, truncate = True )
] ) ] )
@ -159,9 +159,9 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_NonNative_test(
vim_command.assert_not_called() vim_command.assert_not_called()
@patch( 'ycm.client.event_notification._LoadExtraConfFile', @patch( 'ycm.client.base_request._LoadExtraConfFile',
new_callable = ExtendedMock ) new_callable = ExtendedMock )
@patch( 'ycm.client.event_notification._IgnoreExtraConfFile', @patch( 'ycm.client.base_request._IgnoreExtraConfFile',
new_callable = ExtendedMock ) new_callable = ExtendedMock )
@YouCompleteMeInstance() @YouCompleteMeInstance()
def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test( def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(

View File

@ -38,16 +38,17 @@ from ycm import base, paths, vimsupport
from ycmd import utils from ycmd import utils
from ycmd import server_utils from ycmd import server_utils
from ycmd.request_wrap import RequestWrap from ycmd.request_wrap import RequestWrap
from ycmd.responses import ServerError
from ycm.diagnostic_interface import DiagnosticInterface from ycm.diagnostic_interface import DiagnosticInterface
from ycm.omni_completer import OmniCompleter from ycm.omni_completer import OmniCompleter
from ycm import syntax_parse from ycm import syntax_parse
from ycm.client.ycmd_keepalive import YcmdKeepalive from ycm.client.ycmd_keepalive import YcmdKeepalive
from ycm.client.base_request import BaseRequest, BuildRequestData from ycm.client.base_request import ( BaseRequest, BuildRequestData,
HandleServerException )
from ycm.client.completer_available_request import SendCompleterAvailableRequest from ycm.client.completer_available_request import SendCompleterAvailableRequest
from ycm.client.command_request import SendCommandRequest from ycm.client.command_request import SendCommandRequest
from ycm.client.completion_request import ( CompletionRequest, from ycm.client.completion_request import ( CompletionRequest,
ConvertCompletionDataToVimData ) ConvertCompletionDataToVimData )
from ycm.client.debug_info_request import SendDebugInfoRequest
from ycm.client.omni_completion_request import OmniCompletionRequest from ycm.client.omni_completion_request import OmniCompletionRequest
from ycm.client.event_notification import ( SendEventNotificationAsync, from ycm.client.event_notification import ( SendEventNotificationAsync,
EventNotification ) EventNotification )
@ -302,13 +303,10 @@ class YouCompleteMe( object ):
def GetDefinedSubcommands( self ): def GetDefinedSubcommands( self ):
if self.IsServerAlive(): if self.IsServerAlive():
try: with HandleServerException():
return BaseRequest.PostDataToHandler( BuildRequestData(), return BaseRequest.PostDataToHandler( BuildRequestData(),
'defined_subcommands' ) 'defined_subcommands' )
except ServerError: return []
return []
else:
return []
def GetCurrentCompletionRequest( self ): def GetCurrentCompletionRequest( self ):
@ -636,14 +634,13 @@ class YouCompleteMe( object ):
def ShowDetailedDiagnostic( self ): def ShowDetailedDiagnostic( self ):
if not self.IsServerAlive(): if not self.IsServerAlive():
return return
try: with HandleServerException():
debug_info = BaseRequest.PostDataToHandler( BuildRequestData(), detailed_diagnostic = BaseRequest.PostDataToHandler(
'detailed_diagnostic' ) BuildRequestData(), 'detailed_diagnostic' )
if 'message' in debug_info:
vimsupport.PostVimMessage( debug_info[ 'message' ], if 'message' in detailed_diagnostic:
vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ],
warning = False ) warning = False )
except ServerError as e:
vimsupport.PostVimMessage( str( e ) )
def DebugInfo( self ): def DebugInfo( self ):
@ -651,8 +648,7 @@ class YouCompleteMe( object ):
if self._client_logfile: if self._client_logfile:
debug_info += 'Client logfile: {0}\n'.format( self._client_logfile ) debug_info += 'Client logfile: {0}\n'.format( self._client_logfile )
if self.IsServerAlive(): if self.IsServerAlive():
debug_info += BaseRequest.PostDataToHandler( BuildRequestData(), debug_info += SendDebugInfoRequest()
'debug_info' )
else: else:
debug_info += 'Server crashed, no debug info from server' debug_info += 'Server crashed, no debug info from server'
debug_info += '\nServer running at: {0}\n'.format( debug_info += '\nServer running at: {0}\n'.format(