8000 Cache settings with lru_cache (#509) · python-lsp/python-lsp-server@467471b · GitHub
[go: up one dir, main page]

Skip to content

Commit 467471b

Browse files
lgeigergatesn
authored andcommitted
Cache settings with lru_cache (#509)
`Config.settings()` takes a few milliseconds to run and reads config files from disk. This function runs multiple times during initialization and before every linting request. This means we can very effectively cache the results. This PR uses `lru_cache` to cache the the result of `Config.settings()`. The cache is invalidated when the config is updated or a config file is changed. Fixes #305
1 parent 831092e commit 467471b

File tree

5 files changed

+29
-25
lines changed

5 files changed

+29
-25
lines changed

pyls/config/config.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Copyright 2017 Palantir Technologies, Inc.
22
import logging
33
import pkg_resources
4+
try:
5+
from functools import lru_cache
6+
except ImportError:
7+
from backports.functools_lru_cache import lru_cache
48

59
import pluggy
610

@@ -47,7 +51,7 @@ def __init__(self, root_uri, init_opts, process_id):
4751
try:
4852
entry_point.load()
4953
except ImportError as e:
50-
log.warn("Failed to load %s entry point '%s': %s", PYLS, entry_point.name, e)
54+
log.warning("Failed to load %s entry point '%s': %s", PYLS, entry_point.name, e)
5155
self._pm.set_blocked(entry_point.name)
5256

5357
# Load the entry points into pluggy, having blocked any failing ones
@@ -82,6 +86,7 @@ def root_uri(self):
8286
def process_id(self):
8387
return self._process_id
8488

89+
@lru_cache(maxsize=32)
8590
def settings(self, document_path=None):
8691
"""Settings are constructed from a few sources:
8792
@@ -90,7 +95,8 @@ def settings(self, document_path=None):
9095
3. LSP settings, given to us from didChangeConfiguration
9196
4. Project settings, found in config files in the current project.
9297
93-
TODO(gatesn): We should update a cached view of this whenever any configs change
98+
Since this function is nondeterministic, it is important to call
99+
settings.cache_clear() when the config is updated
94100
"""
95101
settings = {}
96102
sources = self._settings.get('configurationSources', DEFAULT_CONFIG_SOURCES)
@@ -130,6 +136,7 @@ def plugin_settings(self, plugin, document_path=None):
130136

131137
def update(self, settings):
132138
"""Recursively merge the given settings into the current settings."""
139+
self.settings.cache_clear()
133140
self._settings = settings
134141
log.info("Updated settings to %s", self._settings)
135142
self._update_disabled_plugins()

pyls/config/source.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ def __init__(self, root_path):
1717
'XDG_CONFIG_HOME', os.path.expanduser('~/.config')
1818
)
1919

20-
self._modified_times = {}
21-
self._configs_cache = {}
22-
2320
def user_config(self):
2421
"""Return user-level (i.e. home directory) configuration."""
2522
raise NotImplementedError()
@@ -29,20 +26,10 @@ def project_config(self, document_path):
2926
raise NotImplementedError()
3027

3128
def read_config_from_files(self, files):
32-
files = tuple([f for f in files if os.path.exists(f) and not os.path.isdir(f)])
33-
modified = tuple([os.path.getmtime(f) for f in files])
34-
35-
if files in self._modified_times and modified == self._modified_times[files]:
36-
log.debug("Using cached configuration for %s", files)
37-
return self._configs_cache[files]
38-
3929
config = configparser.RawConfigParser()
40-
found_files = []
4130
for filename in files:
42-
found_files.extend(config.read(filename))
43-
44-
self._configs_cache[files] = config
45-
self._modified_times[files] = modified
31+
if os.path.exists(filename) and not os.path.isdir(filename):
32+
config.read(filename)
4633

4734
return config
4835

pyls/python_ls.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
LINT_DEBOUNCE_S = 0.5 # 500 ms
1818
PARENT_PROCESS_WATCH_INTERVAL = 10 # 10 s
1919
MAX_WORKERS = 64
20-
# We also watch for changes in config files
21-
PYTHON_FILE_EXTENSIONS = ('.py', '.pyi', 'pycodestyle.cfg', 'setup.cfg', 'tox.ini', '.flake8')
20+
PYTHON_FILE_EXTENSIONS = ('.py', '.pyi')
21+
CONFIG_FILEs = ('pycodestyle.cfg', 'setup.cfg', 'tox.ini', '.flake8')
2222

2323

2424
class _StreamHandlerWrapper(socketserver.StreamRequestHandler, object):
@@ -299,13 +299,21 @@ def m_workspace__did_change_configuration(self, settings=None):
299299
for doc_uri in self.workspace.documents:
300300
self.lint(doc_uri, is_saved=False)
301301

302-
def m_workspace__did_change_watched_files(self, changes=None, **_kwargs):
303-
changed_py_files = set(d['uri'] for d in changes if d['uri'].endswith(PYTHON_FILE_EXTENSIONS))
304-
# Only externally changed python files and lint configs may result in changed diagnostics.
305-
if not changed_py_files:
302+
def m_workspace__did_change_watched_files(self, changes=[], **_kwargs):
303+
changed_py_files = set()
304+
config_changed = False
305+
for d in changes:
306+
if d['uri'].endswith(PYTHON_FILE_EXTENSIONS):
307+
changed_py_files.add(d['uri'])
308+
elif d['uri'].endswith(CONFIG_FILEs):
309+
config_changed = True
310+
311+
if config_changed:
312+
self.settings.cache_clear()
313+
elif not changed_py_files:
314+
# Only externally changed python files and lint configs may result in changed diagnostics.
306315
return
307-
# TODO: We currently don't cache settings therefor we can just lint again.
308-
# Here would be the right point to update the settings after a change to config files.
316+
309317
for doc_uri in self.workspace.documents:
310318
# Changes in doc_uri are already handled by m_text_document__did_save
311319
if doc_uri not in changed_py_files:

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
'configparser; python_version<"3.0"',
3636
'future>=0.14.0',
3737
'futures; python_version<"3.2"',
38+
'backports.functools_lru_cache; python_version<"3.2"',
3839
'jedi>=0.13.2',
3940
'python-jsonrpc-server>=0.1.0',
4041
'pluggy'

test/plugins/test_pycodestyle_lint.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def test_pycodestyle_config(workspace):
8282
# Now we'll add config file to ignore it
8383
with open(os.path.join(workspace.root_path, conf_file), 'w+') as f:
8484
f.write(content)
85+
config.settings.cache_clear()
8586

8687
# And make sure we don't get any warnings
8788
diags = pycodestyle_lint.pyls_lint(config, doc)

0 commit comments

Comments
 (0)
0