diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index f5f4840c..1b922973 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -41,7 +41,8 @@ class BaseRequest( object ): return {} - def PostDataToHandler( self, data, handler ): + @staticmethod + def PostDataToHandler( data, handler ): response = requests.post( _BuildUri( handler ), data = json.dumps( data ), headers = HEADERS ) diff --git a/python/ycm/retries.py b/python/ycm/retries.py new file mode 100644 index 00000000..1d7131d5 --- /dev/null +++ b/python/ycm/retries.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# +# Copyright 2012 by Jeff Laughlin Consulting LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +import sys +from time import sleep + +# Source: https://gist.github.com/n1ywb/2570004 + +def example_exc_handler(tries_remaining, exception, delay): + """Example exception handler; prints a warning to stderr. + + tries_remaining: The number of tries remaining. + exception: The exception instance which was raised. + """ + print >> sys.stderr, "Caught '%s', %d tries remaining, sleeping for %s seconds" % (exception, tries_remaining, delay) + + +def retries(max_tries, delay=1, backoff=2, exceptions=(Exception,), hook=None): + """Function decorator implementing retrying logic. + + delay: Sleep this many seconds * backoff * try number after failure + backoff: Multiply delay by this factor after each failure + exceptions: A tuple of exception classes; default (Exception,) + hook: A function with the signature myhook(tries_remaining, exception); + default None + + The decorator will call the function up to max_tries times if it raises + an exception. + + By default it catches instances of the Exception class and subclasses. + This will recover after all but the most fatal errors. You may specify a + custom tuple of exception classes with the 'exceptions' argument; the + function will only be retried if it raises one of the specified + exceptions. + + Additionally you may specify a hook function which will be called prior + to retrying with the number of remaining tries and the exception instance; + see given example. This is primarily intended to give the opportunity to + log the failure. Hook is not called after failure if no retries remain. + """ + def dec(func): + def f2(*args, **kwargs): + mydelay = delay + tries = range(max_tries) + tries.reverse() + for tries_remaining in tries: + try: + return func(*args, **kwargs) + except exceptions as e: + if tries_remaining > 0: + if hook is not None: + hook(tries_remaining, e, mydelay) + sleep(mydelay) + mydelay = mydelay * backoff + else: + raise + else: + break + return f2 + return dec diff --git a/python/ycm/server/default_settings.json b/python/ycm/server/default_settings.json index f6030fa1..b3822b6d 100644 --- a/python/ycm/server/default_settings.json +++ b/python/ycm/server/default_settings.json @@ -1 +1 @@ -{ "filepath_completion_use_working_dir": 0, "min_num_of_chars_for_completion": 2, "semantic_triggers": {}, "collect_identifiers_from_comments_and_strings": 0, "filetype_specific_completion_to_disable": {}, "collect_identifiers_from_tags_files": 0, "extra_conf_globlist": [ "~\/repos\/*", "\/home\/strahinja\/googrepos\/*", "~\/local_googrepos\/*", "~\/.ycm_extra_conf.py" ], "global_ycm_extra_conf": "\/usr\/lib\/youcompleteme\/ycm_extra_conf.py", "confirm_extra_conf": 1, "complete_in_comments": 0, "complete_in_strings": 1, "min_num_identifier_candidate_chars": 0, "max_diagnostics_to_display": 30, "auto_stop_csharp_server": 1, "seed_identifiers_with_syntax": 0, "csharp_server_port": 2000, "filetype_whitelist": { "*": "1" }, "auto_start_csharp_server": 1, "filetype_blacklist": { "tagbar": "1", "notes": "1", "markdown": "1", "unite": "1", "text": "1" } } \ No newline at end of file +{ "filepath_completion_use_working_dir": 0, "min_num_of_chars_for_completion": 2, "semantic_triggers": {}, "collect_identifiers_from_comments_and_strings": 0, "filetype_specific_completion_to_disable": {}, "collect_identifiers_from_tags_files": 0, "extra_conf_globlist": [ "~\/repos\/*", "\/home\/strahinja\/googrepos\/*", "~\/local_googrepos\/*", "~\/.ycm_extra_conf.py" ], "global_ycm_extra_conf": "", "confirm_extra_conf": 1, "complete_in_comments": 0, "complete_in_strings": 1, "min_num_identifier_candidate_chars": 0, "max_diagnostics_to_display": 30, "auto_stop_csharp_server": 1, "seed_identifiers_with_syntax": 0, "csharp_server_port": 2000, "filetype_whitelist": { "*": "1" }, "auto_start_csharp_server": 1, "filetype_blacklist": { "tagbar": "1", "notes": "1", "markdown": "1", "unite": "1", "text": "1" } } \ No newline at end of file diff --git a/python/ycm/server/server.py b/python/ycm/server/server.py index df3d8507..1b8387dd 100755 --- a/python/ycm/server/server.py +++ b/python/ycm/server/server.py @@ -32,7 +32,6 @@ sys.path.insert( 0, os.path.join( import logging import time -import httplib import json import bottle from bottle import run, request, response @@ -107,20 +106,20 @@ def GetCompletions(): return _JsonResponse( completer.CandidatesFromStoredRequest() ) -@app.route( '/user_options' ) -def UserOptions(): +@app.get( '/user_options' ) +def GetUserOptions(): + LOGGER.info( 'Received user options GET request') + return _JsonResponse( dict( SERVER_STATE.user_options ) ) + + +@app.post( '/user_options' ) +def SetUserOptions(): global SERVER_STATE - if request.method == 'GET': - LOGGER.info( 'Received user options GET request') - return SERVER_STATE.user_options - elif request.method == 'POST': - LOGGER.info( 'Received user options POST request') - data = request.json - SERVER_STATE = server_state.ServerState( data ) - user_options_store.SetAll( data ) - else: - response.status = httplib.BAD_REQUEST + LOGGER.info( 'Received user options POST request') + data = request.json + SERVER_STATE = server_state.ServerState( data ) + user_options_store.SetAll( data ) @app.post( '/filetype_completion_available') diff --git a/python/ycm/server/tests/basic_test.py b/python/ycm/server/tests/basic_test.py index e6ab628d..8f8160fa 100644 --- a/python/ycm/server/tests/basic_test.py +++ b/python/ycm/server/tests/basic_test.py @@ -95,3 +95,15 @@ def FiletypeCompletionAvailable_Works_test(): ok_( app.post_json( '/filetype_completion_available', request_data ).json ) + + +def UserOptions_Works_test(): + app = TestApp( server.app ) + options = app.get( '/user_options' ).json + ok_( len( options ) ) + + options[ 'foobar' ] = 'zoo' + + app.post_json( '/user_options', options ) + eq_( options, app.get( '/user_options' ).json ) + diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index b585cf9c..3d3ca2be 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -21,6 +21,8 @@ import os import vim import ycm_core import subprocess +import threading +from ycm.retries import retries from ycm import vimsupport from ycm import utils from ycm.completers.all.omni_completer import OmniCompleter @@ -41,6 +43,7 @@ class YouCompleteMe( object ): self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() + self._options_thread = None self._SetupServer() @@ -73,6 +76,25 @@ class YouCompleteMe( object ): stderr = fstderr, shell = True ) + self._StartOptionsThread() + + + def _StartOptionsThread( self ): + def OptionsThreadMain( options ): + @retries( 5, delay = 0.5 ) + def PostOptionsToServer(): + BaseRequest.PostDataToHandler( options, 'user_options' ) + + PostOptionsToServer() + + self._options_thread = threading.Thread( + target = OptionsThreadMain, + args = ( dict( self._user_options ), ) ) + + self._options_thread.daemon = True + self._options_thread.start() + + def CreateCompletionRequest( self ): # We have to store a reference to the newly created CompletionRequest