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()
|
||||
|
||||
# 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(plug.name).encode())
|
||||
data.update(str(plug.version).encode())
|
||||
|
||||
@@ -538,6 +538,30 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
|
||||
registry.registry_hash = 'abc'
|
||||
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):
|
||||
"""Test that mandatory builtin plugins are always loaded."""
|
||||
from plugin.models import PluginConfig
|
||||
|
||||
Reference in New Issue
Block a user