From 262d8aad033a59ba7da93b047debdacdc5cc0c15 Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Wed, 23 Oct 2013 11:02:25 -0700 Subject: [PATCH] New ycmd watchdog kills server when idle too long Defaults are kill server after 12 hours of inactivity; the reason why it's 12 hours and not less is because we don't want to kill the server when the user just left his machine (and Vim) on during the night. --- python/ycm/server/default_settings.json | 1 + python/ycm/server/watchdog_plugin.py | 78 +++++++++++++++++++++++++ python/ycm/server/ycmd.py | 6 +- python/ycm/youcompleteme.py | 8 ++- 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 python/ycm/server/watchdog_plugin.py diff --git a/python/ycm/server/default_settings.json b/python/ycm/server/default_settings.json index 4ee627a7..3cfdbc65 100644 --- a/python/ycm/server/default_settings.json +++ b/python/ycm/server/default_settings.json @@ -29,6 +29,7 @@ "server_use_vim_stdout": 0, "server_log_level": "info", "server_keep_logfiles": 0, + "server_idle_shutdown_seconds": 43200, "auto_start_csharp_server": 1, "auto_stop_csharp_server": 1, "csharp_server_port": 2000 diff --git a/python/ycm/server/watchdog_plugin.py b/python/ycm/server/watchdog_plugin.py new file mode 100644 index 00000000..82be070f --- /dev/null +++ b/python/ycm/server/watchdog_plugin.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013 Strahinja Val Markovic +# +# 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 . + +import time +import os +import copy +from ycm import utils +from threading import Thread, Lock + +# This class implements the Bottle plugin API: +# http://bottlepy.org/docs/dev/plugindev.html +# +# The idea here is to decorate every route handler automatically so that on +# every request, we log when the request was made. Then a watchdog thread checks +# every check_interval_seconds whether the server has been idle for a time +# greater that the passed-in idle_shutdown_seconds. If it has, we kill the +# server. +# +# We want to do this so that if something goes bonkers in Vim and the server +# never gets killed by the client, we don't end up with lots of zombie servers. +class WatchdogPlugin( object ): + name = 'watchdog' + api = 2 + + + def __init__( self, + idle_shutdown_seconds, + check_interval_seconds = 60 * 10 ): + self._check_interval_seconds = check_interval_seconds + self._idle_shutdown_seconds = idle_shutdown_seconds + self._last_request_time = time.time() + self._last_request_time_lock = Lock() + if idle_shutdown_seconds <= 0: + return + self._watchdog_thread = Thread( target = self._WatchdogMain ) + self._watchdog_thread.daemon = True + self._watchdog_thread.start() + + + def _GetLastRequestTime( self ): + with self._last_request_time_lock: + return copy.deepcopy( self._last_request_time ) + + + def _SetLastRequestTime( self, new_value ): + with self._last_request_time_lock: + self._last_request_time = new_value + + + def _WatchdogMain( self ): + while True: + time.sleep( self._check_interval_seconds ) + if time.time() - self._GetLastRequestTime() > self._idle_shutdown_seconds: + utils.TerminateProcess( os.getpid() ) + + + def __call__( self, callback ): + def wrapper( *args, **kwargs ): + self._SetLastRequestTime( time.time() ) + return callback( *args, **kwargs ) + return wrapper + diff --git a/python/ycm/server/ycmd.py b/python/ycm/server/ycmd.py index 65e9214c..a80e1cbc 100755 --- a/python/ycm/server/ycmd.py +++ b/python/ycm/server/ycmd.py @@ -28,6 +28,7 @@ import waitress import signal from ycm import user_options_store from ycm import extra_conf_store +from ycm.server.watchdog_plugin import WatchdogPlugin def YcmCoreSanityCheck(): @@ -55,6 +56,8 @@ def Main(): parser.add_argument( '--log', type = str, default = 'info', help = 'log level, one of ' '[debug|info|warning|error|critical]' ) + parser.add_argument( '--idle_shutdown_seconds', type = int, default = 0, + help = 'num idle seconds before server shuts down') parser.add_argument( '--options_file', type = str, default = '', help = 'file with user options, in JSON format' ) args = parser.parse_args() @@ -79,9 +82,10 @@ def Main(): # This can't be a top-level import because it transitively imports # ycm_core which we want to be imported ONLY after extra conf # preload has executed. - import handlers + from ycm.server import handlers handlers.UpdateUserOptions( options ) SetUpSignalHandler() + handlers.app.install( WatchdogPlugin( args.idle_shutdown_seconds ) ) waitress.serve( handlers.app, host = args.host, port = args.port, diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 3a238572..6967c807 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -41,9 +41,9 @@ try: except ImportError: USE_ULTISNIPS_DATA = False -SERVER_CRASH_MESSAGE_STDERR_FILE = 'The ycmd server crashed with output:\n' +SERVER_CRASH_MESSAGE_STDERR_FILE = 'The ycmd server SHUT DOWN with output:\n' SERVER_CRASH_MESSAGE_SAME_STDERR = ( - 'The ycmd server crashed, check console output for logs!' ) + 'The ycmd server shut down, check console output for logs!' ) class YouCompleteMe( object ): @@ -69,7 +69,9 @@ class YouCompleteMe( object ): _PathToServerScript(), '--port={0}'.format( server_port ), '--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_shutdown_seconds={0}'.format( + self._user_options[ 'server_idle_shutdown_seconds' ] ) ] BaseRequest.server_location = 'http://localhost:' + str( server_port )