From e7f546b8b04934d097a9259d8496064c2e268c35 Mon Sep 17 00:00:00 2001 From: wolflu05 <76838159+wolflu05@users.noreply.github.com> Date: Thu, 8 Jun 2023 21:06:14 +0000 Subject: [PATCH] Skip ready functions if not in main thread or plugins are not loaded yet --- InvenTree/InvenTree/apps.py | 7 ++++++- InvenTree/InvenTree/ready.py | 33 +++++++++++++++++++++++++++++++++ InvenTree/label/apps.py | 7 ++++++- InvenTree/part/apps.py | 7 ++++++- InvenTree/plugin/apps.py | 6 +++++- InvenTree/report/apps.py | 7 ++++++- InvenTree/users/apps.py | 8 +++++++- 7 files changed, 69 insertions(+), 6 deletions(-) diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index 1ba43edee7..066214ebc3 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -14,7 +14,8 @@ from django.db.utils import IntegrityError import InvenTree.conversion import InvenTree.tasks from InvenTree.config import get_setting -from InvenTree.ready import canAppAccessDatabase, isInTestMode +from InvenTree.ready import (canAppAccessDatabase, isInMainThread, + isInTestMode, isPluginRegistryLoaded) logger = logging.getLogger("inventree") @@ -34,6 +35,10 @@ class InvenTreeConfig(AppConfig): - Collecting notification methods - Adding users set in the current environment """ + # skip loading if plugins are not loaded or we run in a background thread + if not isPluginRegistryLoaded() or not isInMainThread(): + return + if canAppAccessDatabase() or settings.TESTING_ENV: InvenTree.tasks.check_for_migrations(worker=False) diff --git a/InvenTree/InvenTree/ready.py b/InvenTree/InvenTree/ready.py index e6a4ec9ae2..0b4bdb9727 100644 --- a/InvenTree/InvenTree/ready.py +++ b/InvenTree/InvenTree/ready.py @@ -1,5 +1,6 @@ """Functions to check if certain parts of InvenTree are ready.""" +import os import sys @@ -13,6 +14,20 @@ def isImportingData(): return 'loaddata' in sys.argv +def isInMainThread(): + """Django starts two processes, one for the actual dev server and the other to reload the application. + + - The RUN_MAIN env is set in that case. However if --noreload is applied, this variable + is not set because there are no different threads. + - If this app is run in gunicorn there are several threads having "equal rights" so there is no real + main thread so we skip this check + """ + if '--noreload' in sys.argv or "gunicorn" in os.environ.get("SERVER_SOFTWARE", ""): + return True + + return os.environ.get('RUN_MAIN', None) == 'true' + + def canAppAccessDatabase(allow_test: bool = False, allow_plugins: bool = False, allow_shell: bool = False): """Returns True if the apps.py file can access database records. @@ -60,3 +75,21 @@ def canAppAccessDatabase(allow_test: bool = False, allow_plugins: bool = False, return False return True + + +def isPluginRegistryLoaded(): + """The plugin registry reloads all apps onetime after starting so that the discovered AppConfigs are added to Django. + + This triggers the ready function of AppConfig to execute twice. Add this check to prevent from running two times. + + Returns: 'False' if the apps have not been reloaded already to prevent running the ready function twice + """ + from django.conf import settings + + # If plugins are not enabled, there won't be a second load + if not settings.PLUGINS_ENABLED: + return True + + from plugin import registry + + return not registry.is_loading diff --git a/InvenTree/label/apps.py b/InvenTree/label/apps.py index 51243e238b..a53c62c253 100644 --- a/InvenTree/label/apps.py +++ b/InvenTree/label/apps.py @@ -12,7 +12,8 @@ from django.conf import settings from django.core.exceptions import AppRegistryNotReady from django.db.utils import OperationalError -from InvenTree.ready import canAppAccessDatabase +from InvenTree.ready import (canAppAccessDatabase, isInMainThread, + isPluginRegistryLoaded) logger = logging.getLogger("inventree") @@ -35,6 +36,10 @@ class LabelConfig(AppConfig): def ready(self): """This function is called whenever the label app is loaded.""" + # skip loading if plugins are not loaded or we run in a background thread + if not isPluginRegistryLoaded() or not isInMainThread(): + return + if canAppAccessDatabase(): try: diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 298ff486cd..1e0405a379 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -5,7 +5,8 @@ import logging from django.apps import AppConfig from django.db.utils import OperationalError, ProgrammingError -from InvenTree.ready import canAppAccessDatabase, isImportingData +from InvenTree.ready import (canAppAccessDatabase, isImportingData, + isInMainThread, isPluginRegistryLoaded) logger = logging.getLogger("inventree") @@ -16,6 +17,10 @@ class PartConfig(AppConfig): def ready(self): """This function is called whenever the Part app is loaded.""" + # skip loading if plugins are not loaded or we run in a background thread + if not isPluginRegistryLoaded() or not isInMainThread(): + return + if canAppAccessDatabase(): self.update_trackable_status() self.reset_part_pricing_flags() diff --git a/InvenTree/plugin/apps.py b/InvenTree/plugin/apps.py index b3b1a4cb6a..ca26228653 100644 --- a/InvenTree/plugin/apps.py +++ b/InvenTree/plugin/apps.py @@ -10,7 +10,7 @@ from django.apps import AppConfig from maintenance_mode.core import set_maintenance_mode -from InvenTree.ready import canAppAccessDatabase +from InvenTree.ready import canAppAccessDatabase, isInMainThread from plugin import registry logger = logging.getLogger('inventree') @@ -23,6 +23,10 @@ class PluginAppConfig(AppConfig): def ready(self): """The ready method is extended to initialize plugins.""" + # skip loading if we run in a background thread + if not isInMainThread(): + return + if not canAppAccessDatabase(allow_test=True, allow_plugins=True): logger.info("Skipping plugin loading sequence") # pragma: no cover else: diff --git a/InvenTree/report/apps.py b/InvenTree/report/apps.py index 5118333922..95575d3bdf 100644 --- a/InvenTree/report/apps.py +++ b/InvenTree/report/apps.py @@ -18,7 +18,12 @@ class ReportConfig(AppConfig): def ready(self): """This function is called whenever the report app is loaded.""" - from InvenTree.ready import canAppAccessDatabase + from InvenTree.ready import (canAppAccessDatabase, isInMainThread, + isPluginRegistryLoaded) + + # skip loading if plugins are not loaded or we run in a background thread + if not isPluginRegistryLoaded() or not isInMainThread(): + return # Configure logging for PDF generation (disable "info" messages) logging.getLogger('fontTools').setLevel(logging.WARNING) diff --git a/InvenTree/users/apps.py b/InvenTree/users/apps.py index 71c2d079ce..42819ee314 100644 --- a/InvenTree/users/apps.py +++ b/InvenTree/users/apps.py @@ -5,7 +5,8 @@ import logging from django.apps import AppConfig from django.db.utils import OperationalError, ProgrammingError -from InvenTree.ready import canAppAccessDatabase +from InvenTree.ready import (canAppAccessDatabase, isInMainThread, + isPluginRegistryLoaded) logger = logging.getLogger('inventree') @@ -17,6 +18,11 @@ class UsersConfig(AppConfig): def ready(self): """Called when the 'users' app is loaded at runtime""" + + # skip loading if plugins are not loaded or we run in a background thread + if not isPluginRegistryLoaded() or not isInMainThread(): + return + if canAppAccessDatabase(allow_test=True): try: