Merge pull request #860 from aslater/master

Revised fix for ycmd terminate race condition
This commit is contained in:
Val Markovic 2014-03-05 17:02:19 -08:00
commit 321355b041
2 changed files with 44 additions and 23 deletions

View File

@ -28,9 +28,9 @@ import waitress
import signal import signal
from ycm import user_options_store from ycm import user_options_store
from ycm import extra_conf_store from ycm import extra_conf_store
from ycm import utils
from ycm.server.watchdog_plugin import WatchdogPlugin from ycm.server.watchdog_plugin import WatchdogPlugin
def YcmCoreSanityCheck(): def YcmCoreSanityCheck():
if 'ycm_core' in sys.modules: if 'ycm_core' in sys.modules:
raise RuntimeError( 'ycm_core already imported, ycmd has a bug!' ) raise RuntimeError( 'ycm_core already imported, ycmd has a bug!' )
@ -38,8 +38,25 @@ def YcmCoreSanityCheck():
# We manually call sys.exit() on SIGTERM and SIGINT so that atexit handlers are # We manually call sys.exit() on SIGTERM and SIGINT so that atexit handlers are
# properly executed. # properly executed.
def SetUpSignalHandler(): def SetUpSignalHandler(stdout, stderr, keep_logfiles):
def SignalHandler( signum, frame ): def SignalHandler( signum, frame ):
if stderr:
# Reset stderr, just in case something tries to use it
tmp = sys.stderr
sys.stderr = sys.__stderr__
tmp.close()
if stdout:
# Reset stdout, just in case something tries to use it
tmp = sys.stdout
sys.stdout = sys.__stdout__
tmp.close()
if not keep_logfiles:
if stderr:
utils.RemoveIfExists( stderr )
if stdout:
utils.RemoveIfExists( stdout )
sys.exit() sys.exit()
for sig in [ signal.SIGTERM, for sig in [ signal.SIGTERM,
@ -61,8 +78,19 @@ def Main():
help = 'num idle seconds before server shuts down') help = 'num idle seconds before server shuts down')
parser.add_argument( '--options_file', type = str, default = '', parser.add_argument( '--options_file', type = str, default = '',
help = 'file with user options, in JSON format' ) help = 'file with user options, in JSON format' )
parser.add_argument( '--stdout', type = str, default = None,
help = 'optional file to use for stdout' )
parser.add_argument( '--stderr', type = str, default = None,
help = 'optional file to use for stderr' )
parser.add_argument( '--keep_logfiles', action = 'store_true', default = None,
help = 'retain logfiles after the server exits' )
args = parser.parse_args() args = parser.parse_args()
if args.stdout is not None:
sys.stdout = open(args.stdout, "w")
if args.stderr is not None:
sys.stderr = open(args.stderr, "w")
numeric_level = getattr( logging, args.log.upper(), None ) numeric_level = getattr( logging, args.log.upper(), None )
if not isinstance( numeric_level, int ): if not isinstance( numeric_level, int ):
raise ValueError( 'Invalid log level: %s' % args.log ) raise ValueError( 'Invalid log level: %s' % args.log )
@ -86,7 +114,7 @@ def Main():
# preload has executed. # preload has executed.
from ycm.server import handlers from ycm.server import handlers
handlers.UpdateUserOptions( options ) handlers.UpdateUserOptions( options )
SetUpSignalHandler() SetUpSignalHandler(args.stdout, args.stderr, args.keep_logfiles)
handlers.app.install( WatchdogPlugin( args.idle_suicide_seconds ) ) handlers.app.install( WatchdogPlugin( args.idle_suicide_seconds ) )
waitress.serve( handlers.app, waitress.serve( handlers.app,
host = args.host, host = args.host,

View File

@ -22,6 +22,7 @@ import vim
import tempfile import tempfile
import json import json
import signal import signal
from subprocess import PIPE
from ycm import vimsupport from ycm import vimsupport
from ycm import utils from ycm import utils
from ycm.diagnostic_interface import DiagnosticInterface from ycm.diagnostic_interface import DiagnosticInterface
@ -84,26 +85,22 @@ class YouCompleteMe( object ):
self._SetupServer() self._SetupServer()
self._ycmd_keepalive.Start() self._ycmd_keepalive.Start()
def _SetupServer( self ): def _SetupServer( self ):
server_port = utils.GetUnusedLocalhostPort() server_port = utils.GetUnusedLocalhostPort()
with tempfile.NamedTemporaryFile( delete = False ) as options_file: with tempfile.NamedTemporaryFile( delete = False ) as options_file:
self._temp_options_filename = options_file.name self._temp_options_filename = options_file.name
json.dump( dict( self._user_options ), options_file ) json.dump( dict( self._user_options ), options_file )
options_file.flush() options_file.flush()
args = [ utils.PathToPythonInterpreter(), args = [ utils.PathToPythonInterpreter(),
_PathToServerScript(), _PathToServerScript(),
'--port={0}'.format( server_port ), '--port={0}'.format( server_port ),
'--options_file={0}'.format( options_file.name ), '--options_file={0}'.format( options_file.name ),
'--log={0}'.format( self._user_options[ 'server_log_level' ] ), '--log={0}'.format( self._user_options[ 'server_log_level' ] ),
'--idle_suicide_seconds={0}'.format( '--idle_suicide_seconds={0}'.format(
SERVER_IDLE_SUICIDE_SECONDS ) ] SERVER_IDLE_SUICIDE_SECONDS )]
BaseRequest.server_location = 'http://localhost:' + str( server_port ) if not self._user_options[ 'server_use_vim_stdout' ]:
if self._user_options[ 'server_use_vim_stdout' ]:
self._server_popen = utils.SafePopen( args )
else:
filename_format = os.path.join( utils.PathToTempDir(), filename_format = os.path.join( utils.PathToTempDir(),
'server_{port}_{std}.log' ) 'server_{port}_{std}.log' )
@ -111,15 +108,18 @@ class YouCompleteMe( object ):
std = 'stdout' ) std = 'stdout' )
self._server_stderr = filename_format.format( port = server_port, self._server_stderr = filename_format.format( port = server_port,
std = 'stderr' ) std = 'stderr' )
args.append('--stdout={0}'.format( self._server_stdout ))
args.append('--stderr={0}'.format( self._server_stderr ))
if self._user_options[ 'server_keep_logfiles' ]:
args.append('--keep_logfiles')
self._server_popen = utils.SafePopen( args, stdout = PIPE, stderr = PIPE)
BaseRequest.server_location = 'http://localhost:' + str( server_port )
with open( self._server_stderr, 'w' ) as fstderr:
with open( self._server_stdout, 'w' ) as fstdout:
self._server_popen = utils.SafePopen( args,
stdout = fstdout,
stderr = fstderr )
self._NotifyUserIfServerCrashed() self._NotifyUserIfServerCrashed()
def _IsServerAlive( self ): def _IsServerAlive( self ):
returncode = self._server_popen.poll() returncode = self._server_popen.poll()
# When the process hasn't finished yet, poll() returns None. # When the process hasn't finished yet, poll() returns None.
@ -151,13 +151,6 @@ class YouCompleteMe( object ):
self._server_popen.terminate() self._server_popen.terminate()
utils.RemoveIfExists( self._temp_options_filename ) utils.RemoveIfExists( self._temp_options_filename )
if not self._user_options[ 'server_keep_logfiles' ]:
if self._server_stderr:
utils.RemoveIfExists( self._server_stderr )
if self._server_stdout:
utils.RemoveIfExists( self._server_stdout )
def RestartServer( self ): def RestartServer( self ):
vimsupport.PostVimMessage( 'Restarting ycmd server...' ) vimsupport.PostVimMessage( 'Restarting ycmd server...' )
self._user_notified_about_crash = False self._user_notified_about_crash = False