From 09a08d32401d5889af517d5daa7259b077078f00 Mon Sep 17 00:00:00 2001 From: micbou Date: Thu, 26 Jan 2017 18:54:57 +0100 Subject: [PATCH] Import requests module lazily The requests module is slow to load so we should import it only when needed to reduce startup time. --- autoload/youcompleteme.vim | 15 ++++++++---- python/ycm/client/base_request.py | 38 +++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index d7d583b7..8656f38e 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -82,7 +82,7 @@ function! youcompleteme#Enable() " Note that these events will NOT trigger for the file vim is started with; " so if you do "vim foo.cc", these events will not trigger when that buffer " is read. This is because youcompleteme#Enable() is called on VimEnter and - " that happens *after" BufRead/BufEnter has already triggered for the + " that happens *after* BufRead/FileType has already triggered for the " initial file. " We also need to trigger buf init code on the FileType event because when " the user does :enew and then :set ft=something, we need to run buf init @@ -97,10 +97,15 @@ function! youcompleteme#Enable() autocmd CompleteDone * call s:OnCompleteDone() augroup END - " Calling this once solves the problem of BufRead/BufEnter not triggering for - " the first loaded file. This should be the last command executed in this - " function! - call s:OnBufferRead() + " BufRead/FileType events are not triggered for the first loaded file. + " However, we don't directly call the s:OnBufferRead function because it would + " send requests that can't succeed as the server is not ready yet and would + " slow down startup. + call s:DisableOnLargeFile( expand( '%' ) ) + + if s:AllowedToCompleteInCurrentBuffer() + call s:SetCompleteFunc() + endif endfunction diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index ad8f7645..99067b68 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -25,20 +25,16 @@ from builtins import * # noqa import contextlib import logging -import requests import urllib.parse import json from future.utils import native from base64 import b64decode, b64encode -from requests_futures.sessions import FuturesSession -from ycm.unsafe_thread_pool_executor import UnsafeThreadPoolExecutor from ycm import vimsupport from ycmd.utils import ToBytes from ycmd.hmac_utils import CreateRequestHmac, CreateHmac, SecureBytesEqual from ycmd.responses import ServerError, UnknownExtraConf _HEADERS = {'content-type': 'application/json'} -_EXECUTOR = UnsafeThreadPoolExecutor( max_workers = 30 ) _CONNECT_TIMEOUT_SEC = 0.01 # Setting this to None seems to screw up the Requests/urllib3 libs. _READ_TIMEOUT_SEC = 30 @@ -104,14 +100,14 @@ class BaseRequest( object ): request_uri = _BuildUri( handler ) if method == 'POST': sent_data = _ToUtf8Json( data ) - return BaseRequest.session.post( + return BaseRequest.Session().post( request_uri, data = sent_data, headers = BaseRequest._ExtraHeaders( method, request_uri, sent_data ), timeout = ( _CONNECT_TIMEOUT_SEC, timeout ) ) - return BaseRequest.session.get( + return BaseRequest.Session().get( request_uri, headers = BaseRequest._ExtraHeaders( method, request_uri ), timeout = ( _CONNECT_TIMEOUT_SEC, timeout ) ) @@ -129,7 +125,31 @@ class BaseRequest( object ): BaseRequest.hmac_secret ) ) return headers - session = FuturesSession( executor = _EXECUTOR ) + + # These two methods exist to avoid importing the requests module at startup; + # reducing loading time since this module is slow to import. + @classmethod + def Requests( cls ): + try: + return cls.requests + except AttributeError: + import requests + cls.requests = requests + return requests + + + @classmethod + def Session( cls ): + try: + return cls.session + except AttributeError: + from ycm.unsafe_thread_pool_executor import UnsafeThreadPoolExecutor + from requests_futures.sessions import FuturesSession + executor = UnsafeThreadPoolExecutor( max_workers = 30 ) + cls.session = FuturesSession( executor = executor ) + return cls.session + + server_location = '' hmac_secret = '' @@ -161,7 +181,7 @@ def BuildRequestData( filepath = None ): def JsonFromFuture( future ): response = future.result() _ValidateResponseObject( response ) - if response.status_code == requests.codes.server_error: + if response.status_code == BaseRequest.Requests().codes.server_error: raise MakeServerException( response.json() ) # We let Requests handle the other status types, we only handle the 500 @@ -199,7 +219,7 @@ def HandleServerException( display = True, truncate = False ): _LoadExtraConfFile( e.extra_conf_file ) else: _IgnoreExtraConfFile( e.extra_conf_file ) - except requests.exceptions.ConnectionError: + except BaseRequest.Requests().exceptions.ConnectionError: # We don't display this exception to the user since it is likely to happen # for each subsequent request (typically if the server crashed) and we # don't want to spam the user with it.