summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDrummond Ogilvie <drum.ogilvie@ovo.com>2022-10-20 10:16:34 +0100
committerGitHub <noreply@github.com>2022-10-20 11:16:34 +0200
commit015f3403385896f70ba946e10ad8354f1a59a967 (patch)
tree2c84d9c90f3911fca7da69cb4269a60478cbb2cc
parente8ba3eb002b533e6640d6eeb25a66bab04602951 (diff)
downloadpylint-git-015f3403385896f70ba946e10ad8354f1a59a967.tar.gz
Swap plugin cache to pickle-able values when done (#7640)
When the dictionary has served its purpose (making plugin loading pre-and-post init a consistent behaviour), we swap it for bools indicating whether or not a module was loaded. We don't currently use the bools, but it seemed a sensible choice. The main idea is to make the dictionary fully pickle-able, so that when dill pickles the linter for multiprocessing, it doesn't crash horribly. Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-rw-r--r--doc/whatsnew/fragments/7635.bugfix7
-rw-r--r--pylint/lint/pylinter.py12
-rw-r--r--script/.contributors_aliases.json2
-rw-r--r--tests/test_check_parallel.py19
4 files changed, 37 insertions, 3 deletions
diff --git a/doc/whatsnew/fragments/7635.bugfix b/doc/whatsnew/fragments/7635.bugfix
new file mode 100644
index 000000000..72085e029
--- /dev/null
+++ b/doc/whatsnew/fragments/7635.bugfix
@@ -0,0 +1,7 @@
+Fixed a multi-processing crash that prevents using any more than 1 thread on MacOS.
+
+The returned module objects and errors that were cached by the linter plugin loader
+cannot be reliably pickled. This means that ``dill`` would throw an error when
+attempting to serialise the linter object for multi-processing use.
+
+Closes #7635.
diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py
index 12726e278..d337fd32c 100644
--- a/pylint/lint/pylinter.py
+++ b/pylint/lint/pylinter.py
@@ -296,7 +296,7 @@ class PyLinter(
str, list[checkers.BaseChecker]
] = collections.defaultdict(list)
"""Dictionary of registered and initialized checkers."""
- self._dynamic_plugins: dict[str, ModuleType | ModuleNotFoundError] = {}
+ self._dynamic_plugins: dict[str, ModuleType | ModuleNotFoundError | bool] = {}
"""Set of loaded plugin names."""
# Attributes related to registering messages and their handling
@@ -402,7 +402,15 @@ class PyLinter(
"bad-plugin-value", args=(modname, module_or_error), line=0
)
elif hasattr(module_or_error, "load_configuration"):
- module_or_error.load_configuration(self)
+ module_or_error.load_configuration(self) # type: ignore[union-attr]
+
+ # We re-set all the dictionary values to True here to make sure the dict
+ # is pickle-able. This is only a problem in multiprocessing/parallel mode.
+ # (e.g. invoking pylint -j 2)
+ self._dynamic_plugins = {
+ modname: not isinstance(val, ModuleNotFoundError)
+ for modname, val in self._dynamic_plugins.items()
+ }
def _load_reporters(self, reporter_names: str) -> None:
"""Load the reporters if they are available on _reporters."""
diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json
index 95808f651..ebd0aa76c 100644
--- a/script/.contributors_aliases.json
+++ b/script/.contributors_aliases.json
@@ -438,7 +438,7 @@
},
"me@daogilvie.com": {
"mails": ["me@daogilvie.com", "drum.ogilvie@ovo.com"],
- "name": "Drummond Ogilvie"
+ "name": "Drum Ogilvie"
},
"me@the-compiler.org": {
"mails": [
diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py
index 0c872ff8d..420ed3d93 100644
--- a/tests/test_check_parallel.py
+++ b/tests/test_check_parallel.py
@@ -11,6 +11,7 @@ from __future__ import annotations
import argparse
import multiprocessing
import os
+from pickle import PickleError
import dill
import pytest
@@ -231,6 +232,24 @@ class TestCheckParallelFramework:
assert stats.statement == 18
assert stats.warning == 0
+ def test_linter_with_unpickleable_plugins_is_pickleable(self) -> None:
+ """The linter needs to be pickle-able in order to be passed between workers"""
+ linter = PyLinter(reporter=Reporter())
+ # We load an extension that we know is not pickle-safe
+ linter.load_plugin_modules(["pylint.extensions.overlapping_exceptions"])
+ try:
+ dill.dumps(linter)
+ assert False, "Plugins loaded were pickle-safe! This test needs altering"
+ except (KeyError, TypeError, PickleError, NotImplementedError):
+ pass
+
+ # And expect this call to make it pickle-able
+ linter.load_plugin_configuration()
+ try:
+ dill.dumps(linter)
+ except KeyError:
+ assert False, "Cannot pickle linter when using non-pickleable plugin"
+
def test_worker_check_sequential_checker(self) -> None:
"""Same as test_worker_check_single_file_no_checkers with SequentialTestChecker."""
linter = PyLinter(reporter=Reporter())