mirror of
https://github.com/inventree/InvenTree.git
synced 2026-06-11 19:27:02 +00:00
Make plugin registry hash independent of plugin discovery order (#12151)
* Make plugin registry hash independent of plugin discovery order calculate_plugin_hash() iterates self.plugins.items() in insertion order, which is the plugin discovery order of the local process. Two processes can hold the same registry state (same plugins, versions, active flags) in a different order and compute different hashes, ping-ponging the _PLUGIN_REGISTRY_HASH setting and triggering endless registry reloads in check_reload(). Sort by slug before hashing so the hash represents the registry state rather than the iteration order of any particular process. Add a regression test that reverses the plugin dict and asserts the hash is unchanged. * Address review comments: explicit sort key, guard against vacuous test --------- Co-authored-by: Nasawa <christopher@anigeek.com>
This commit is contained in:
committed by
GitHub
parent
73bfa53a35
commit
7cca9cb326
@@ -1027,7 +1027,11 @@ class PluginsRegistry:
|
|||||||
data = md5()
|
data = md5()
|
||||||
|
|
||||||
# Hash for all loaded plugins
|
# Hash for all loaded plugins
|
||||||
for slug, plug in self.plugins.items():
|
# Note: Sort by slug, so the hash is independent of discovery order.
|
||||||
|
# Different processes can discover the same plugins in a different
|
||||||
|
# order, and the hash must represent the registry *state*, not the
|
||||||
|
# iteration order of any particular process.
|
||||||
|
for slug, plug in sorted(self.plugins.items(), key=lambda item: item[0]):
|
||||||
data.update(str(slug).encode())
|
data.update(str(slug).encode())
|
||||||
data.update(str(plug.name).encode())
|
data.update(str(plug.name).encode())
|
||||||
data.update(str(plug.version).encode())
|
data.update(str(plug.version).encode())
|
||||||
|
|||||||
@@ -538,6 +538,30 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
|
|||||||
registry.registry_hash = 'abc'
|
registry.registry_hash = 'abc'
|
||||||
self.assertTrue(registry.check_reload())
|
self.assertTrue(registry.check_reload())
|
||||||
|
|
||||||
|
def test_registry_hash_order_independence(self):
|
||||||
|
"""Test that the registry hash does not depend on plugin iteration order.
|
||||||
|
|
||||||
|
Different processes (gunicorn workers, background worker, shell) can
|
||||||
|
discover the same set of plugins in a different order. If the hash
|
||||||
|
depends on iteration order, processes disagree about the hash for the
|
||||||
|
same registry state, and ping-pong each other into endless reloads
|
||||||
|
via check_reload.
|
||||||
|
"""
|
||||||
|
original_plugins = registry.plugins
|
||||||
|
|
||||||
|
# Reversing a dict with fewer than 2 entries would not change anything
|
||||||
|
self.assertGreater(len(original_plugins), 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
hash_original = registry.calculate_plugin_hash()
|
||||||
|
|
||||||
|
# Simulate a process which discovered the same plugins in reverse order
|
||||||
|
registry.plugins = dict(reversed(list(original_plugins.items())))
|
||||||
|
|
||||||
|
self.assertEqual(hash_original, registry.calculate_plugin_hash())
|
||||||
|
finally:
|
||||||
|
registry.plugins = original_plugins
|
||||||
|
|
||||||
def test_builtin_mandatory_plugins(self):
|
def test_builtin_mandatory_plugins(self):
|
||||||
"""Test that mandatory builtin plugins are always loaded."""
|
"""Test that mandatory builtin plugins are always loaded."""
|
||||||
from plugin.models import PluginConfig
|
from plugin.models import PluginConfig
|
||||||
|
|||||||
Reference in New Issue
Block a user