diff options
| -rw-r--r-- | odoo/addons/base/ir/ir_cron.py | 18 | ||||
| -rw-r--r-- | odoo/modules/__init__.py | 2 | ||||
| -rw-r--r-- | odoo/modules/loading.py | 21 | ||||
| -rw-r--r-- | odoo/modules/registry.py | 6 |
4 files changed, 43 insertions, 4 deletions
diff --git a/odoo/addons/base/ir/ir_cron.py b/odoo/addons/base/ir/ir_cron.py index c577ea221ac..59653ae5183 100644 --- a/odoo/addons/base/ir/ir_cron.py +++ b/odoo/addons/base/ir/ir_cron.py @@ -5,7 +5,7 @@ import threading import time import psycopg2 import pytz -from datetime import datetime +from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta import odoo @@ -16,6 +16,7 @@ from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) BASE_VERSION = odoo.modules.load_information_from_description_file('base')['version'] +MAX_FAIL_TIME = timedelta(hours=5) # chosen with a fair roll of the dice class BadVersion(Exception): @@ -198,7 +199,7 @@ class ir_cron(models.Model): (version,) = cr.fetchone() cr.execute("SELECT COUNT(*) FROM ir_module_module WHERE state LIKE %s", ['to %']) (changes,) = cr.fetchone() - if not version or changes: + if version is None: raise BadModuleState() elif version != BASE_VERSION: raise BadVersion() @@ -209,6 +210,19 @@ class ir_cron(models.Model): ORDER BY priority""") jobs = cr.dictfetchall() + if changes: + if not jobs: + raise BadModuleState() + # nextcall is never updated if the cron is not executed, + # it is used as a sentinel value to check whether cron jobs + # have been locked for a long time (stuck) + parse = fields.Datetime.from_string + oldest = min([parse(job['nextcall']) for job in jobs]) + if datetime.now() - oldest > MAX_FAIL_TIME: + odoo.modules.reset_modules_state(db_name) + else: + raise BadModuleState() + for job in jobs: lock_cr = db.cursor() try: diff --git a/odoo/modules/__init__.py b/odoo/modules/__init__.py index 530f81ff5e3..b223df99280 100644 --- a/odoo/modules/__init__.py +++ b/odoo/modules/__init__.py @@ -7,7 +7,7 @@ from . import db, graph, loading, migration, module, registry -from odoo.modules.loading import load_modules +from odoo.modules.loading import load_modules, reset_modules_state from odoo.modules.module import ( adapt_version, diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index e9744844841..950704e45e9 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -431,3 +431,24 @@ def load_modules(db, force_demo=False, status=None, update_module=False): _logger.log(25, "All post-tested in %.2fs, %s queries", time.time() - t0, odoo.sql_db.sql_counter - t0_sql) finally: cr.close() + + +def reset_modules_state(db_name): + """ + Resets modules flagged as "to x" to their original state + """ + # Warning, this function was introduced in response to commit 763d714 + # which locks cron jobs for dbs which have modules marked as 'to %'. + # The goal of this function is to be called ONLY when module + # installation/upgrade/uninstallation fails, which is the only known case + # for which modules can stay marked as 'to %' for an indefinite amount + # of time + db = odoo.sql_db.db_connect(db_name) + with db.cursor() as cr: + cr.execute( + "UPDATE ir_module_module SET state='installed' WHERE state IN ('to remove', 'to upgrade')" + ) + cr.execute( + "UPDATE ir_module_module SET state='uninstalled' WHERE state='to install'" + ) + _logger.warning("Transient module states were reset") diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index c92da2df13e..50a0be95986 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -79,7 +79,11 @@ class Registry(Mapping): try: registry.setup_signaling() # This should be a method on Registry - odoo.modules.load_modules(registry._db, force_demo, status, update_module) + try: + odoo.modules.load_modules(registry._db, force_demo, status, update_module) + except Exception: + odoo.modules.reset_modules_state(db_name) + raise except Exception: _logger.exception('Failed to load registry') del cls.registries[db_name] |
