Fixing a race condition in Completer
Since we use one thread per request, one thread could change the completions_cache while the other one was depending on the data in it.
This commit is contained in:
parent
6faf0e9c20
commit
9f3a3e3019
@ -18,6 +18,7 @@
|
|||||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import threading
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from ycm.utils import ToUtf8IfNeeded, ForceSemanticCompletion, RunningInsideVim
|
from ycm.utils import ToUtf8IfNeeded, ForceSemanticCompletion, RunningInsideVim
|
||||||
|
|
||||||
@ -105,22 +106,29 @@ class Completer( object ):
|
|||||||
self.triggers_for_filetype = (
|
self.triggers_for_filetype = (
|
||||||
TriggersForFiletype( user_options[ 'semantic_triggers' ] )
|
TriggersForFiletype( user_options[ 'semantic_triggers' ] )
|
||||||
if user_options[ 'auto_trigger' ] else defaultdict( set ) )
|
if user_options[ 'auto_trigger' ] else defaultdict( set ) )
|
||||||
self._completions_cache = None
|
self._completions_cache = CompletionsCache()
|
||||||
|
|
||||||
|
|
||||||
# It's highly likely you DON'T want to override this function but the *Inner
|
# It's highly likely you DON'T want to override this function but the *Inner
|
||||||
# version of it.
|
# version of it.
|
||||||
def ShouldUseNow( self, request_data ):
|
def ShouldUseNow( self, request_data ):
|
||||||
inner_says_yes = self.ShouldUseNowInner( request_data )
|
if not self.ShouldUseNowInner( request_data ):
|
||||||
if not inner_says_yes:
|
self._completions_cache.Invalidate()
|
||||||
self._completions_cache = None
|
return False
|
||||||
|
|
||||||
previous_results_were_empty = ( self._completions_cache and
|
# We have to do the cache valid check and get the completions as part of one
|
||||||
self._completions_cache.CacheValid(
|
# call because we have to ensure a different thread doesn't change the cache
|
||||||
request_data[ 'line_num' ],
|
# data.
|
||||||
request_data[ 'start_column' ] ) and
|
cache_completions = self._completions_cache.GetCompletionsIfCacheValid(
|
||||||
not self._completions_cache.raw_completions )
|
request_data[ 'line_num' ],
|
||||||
return inner_says_yes and not previous_results_were_empty
|
request_data[ 'start_column' ] )
|
||||||
|
|
||||||
|
# If None, then the cache isn't valid and we know we should return true
|
||||||
|
if cache_completions is None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
previous_results_were_valid = bool( cache_completions )
|
||||||
|
return previous_results_were_valid
|
||||||
|
|
||||||
|
|
||||||
def ShouldUseNowInner( self, request_data ):
|
def ShouldUseNowInner( self, request_data ):
|
||||||
@ -167,17 +175,19 @@ class Completer( object ):
|
|||||||
|
|
||||||
|
|
||||||
def _GetCandidatesFromSubclass( self, request_data ):
|
def _GetCandidatesFromSubclass( self, request_data ):
|
||||||
if ( self._completions_cache and
|
cache_completions = self._completions_cache.GetCompletionsIfCacheValid(
|
||||||
self._completions_cache.CacheValid( request_data[ 'line_num' ],
|
request_data[ 'line_num' ],
|
||||||
request_data[ 'start_column' ] ) ):
|
request_data[ 'start_column' ] )
|
||||||
return self._completions_cache.raw_completions
|
|
||||||
|
if cache_completions:
|
||||||
|
return cache_completions
|
||||||
else:
|
else:
|
||||||
self._completions_cache = CompletionsCache()
|
raw_completions = self.ComputeCandidatesInner( request_data )
|
||||||
self._completions_cache.raw_completions = self.ComputeCandidatesInner(
|
self._completions_cache.Update(
|
||||||
request_data )
|
request_data[ 'line_num' ],
|
||||||
self._completions_cache.line = request_data[ 'line_num' ]
|
request_data[ 'start_column' ],
|
||||||
self._completions_cache.column = request_data[ 'start_column' ]
|
raw_completions )
|
||||||
return self._completions_cache.raw_completions
|
return raw_completions
|
||||||
|
|
||||||
|
|
||||||
def ComputeCandidatesInner( self, request_data ):
|
def ComputeCandidatesInner( self, request_data ):
|
||||||
@ -277,13 +287,41 @@ class Completer( object ):
|
|||||||
|
|
||||||
class CompletionsCache( object ):
|
class CompletionsCache( object ):
|
||||||
def __init__( self ):
|
def __init__( self ):
|
||||||
self.line = -1
|
self._access_lock = threading.Lock()
|
||||||
self.column = -1
|
self.Invalidate()
|
||||||
self.raw_completions = []
|
|
||||||
self.filtered_completions = []
|
|
||||||
|
def Invalidate( self ):
|
||||||
|
with self._access_lock:
|
||||||
|
self._line = -1
|
||||||
|
self._column = -1
|
||||||
|
self._completions = []
|
||||||
|
|
||||||
|
|
||||||
|
def Update( self, line, column, completions ):
|
||||||
|
with self._access_lock:
|
||||||
|
self._line = line
|
||||||
|
self._column = column
|
||||||
|
self._completions = completions
|
||||||
|
|
||||||
|
|
||||||
|
def GetCompletions( self ):
|
||||||
|
with self._access_lock:
|
||||||
|
return self._completions
|
||||||
|
|
||||||
|
|
||||||
|
def GetCompletionsIfCacheValid( self, current_line, start_column ):
|
||||||
|
with self._access_lock:
|
||||||
|
if not self._CacheValidNoLock( current_line, start_column ):
|
||||||
|
return None
|
||||||
|
return self._completions
|
||||||
|
|
||||||
|
|
||||||
def CacheValid( self, current_line, start_column ):
|
def CacheValid( self, current_line, start_column ):
|
||||||
return current_line == self.line and start_column == self.column
|
with self._access_lock:
|
||||||
|
return self._CacheValidNoLock( current_line, start_column )
|
||||||
|
|
||||||
|
|
||||||
|
def _CacheValidNoLock( self, current_line, start_column ):
|
||||||
|
return current_line == self._line and start_column == self._column
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user