fix(spp_base_common): make the menu-icon hook best-effort on database errors - #525
gonzalesedwin1123 wants to merge 4 commits into
Conversation
… errors update_menu_icons() ran search, env.ref and write against the cursor with no guard, so any psycopg2 error propagated out of ir.module.module.next() and left the caller's transaction aborted. Decorating app icons is cosmetic: the whole pass now runs in its own savepoint and a psycopg2.Error is logged and skipped. Missing menu xmlids use raise_if_not_found=False instead of catching ValueError. Tests cover a missing xmlid, a SQL failure raised inside the lookup (next() still returns its action and the cursor stays usable) and a cursor that is already aborted when the hook starts. Closes #383
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 19.0 #525 +/- ##
==========================================
+ Coverage 76.91% 76.93% +0.02%
==========================================
Files 704 732 +28
Lines 45774 47914 +2140
==========================================
+ Hits 35205 36862 +1657
- Misses 10569 11052 +483
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…guard Odoo's cr.savepoint() flushes pending ORM writes before opening the savepoint, so a failure in the caller's own writes was caught by the hook and logged as a menu icon problem while the caller only found out at its next statement. Flush first, so only the decoration is best-effort. Review follow-ups: document the deliberate swallowing of retryable errors, make the warning operator-facing, rename the worker to _write_menu_icons, and tighten the tests (atomicity of the pass asserted, action assertion no longer tied to open todos, public transaction-status accessor, savepoints released, functools.wraps on the lookup stand-in, new test for the flush).
Review round (3 reviewers) and fix-ups → e913d3bThree independent reviews were run on 9a14fec: the repo's code-reviewer profile, an adversarial staff-engineer pass over seven attack surfaces (savepoint/flush semantics on an aborted transaction, cursor state for Verdicts: no blocking findings. Negative control confirmed the tests bite: with only the guard reverted, test_03 errors with One Important finding, fixed in e913d3b: Optional items adopted: docstring records why retryable Not adopted: renaming Local: |
Summary
Closes #383.
update_menu_icons()inspp_base_common/models/ir_module_module.pyransearch,env.refandwriteagainst the cursor with no guard. Anypsycopg2error propagated out ofir.module.module.next()and left the caller's transaction aborted. Decorating app icons is cosmetic, so the whole pass now runs in its own savepoint, and apsycopg2.Erroris logged at WARNING and skipped. Missing menu xmlids useraise_if_not_found=Falseinstead of catchingValueError.psycopg2.Errorrather thanDatabaseError:InterfaceError("connection already closed") is a sibling class, not a subclass (same lesson as fix(worker): survive a job-level database error instead of quarantining the DB odoo-job-worker#22).search([])andwriteare just as exposed on a poisoned cursor as the lookup, and one database error abandoning the rest of a cosmetic pass is acceptable.spp_base_common19.0.2.0.2 → 19.0.2.0.3, HISTORY fragment added.README.rstleft to CI's generator.Caveat on the issue's premise
On Odoo 19,
ir.module.module.next()is called from exactly one place,_button_immediate_function(the install/upgrade/uninstall buttons), aftercr.reset(). Plain registry load never runs this hook, so the "kills a job worker on registry load" framing inherited from odoo-job-worker#22 does not match this code path. The hardening is still right on the hook's own contract, which is what the tests assert.The hook that does run on every registry load with the same unguarded
env.ref→_xmlid_lookupshape isspp_hide_menus_base's_register_hook→hide_menus()(present since 2026-06-04, before the 2026-07-30 incident). That is almost certainly the frame the incident traceback showed. It is a different module with its own version, so it is filed separately rather than folded in here; see #526.Tests
./spp t spp_base_common: 10 tests, 0 failed, 0 errors (baseline 7). New tests intest_ir_module_module.py:ICON_MAPis skipped and the other menus are still decorated;next()still returns its action, and the cursor is usable afterwards (savepoint contained it);TRANSACTION_STATUS_INERRORasserted as precondition):update_menu_icons()does not raise.Both guard tests were RED before the fix (
UndefinedTable/InFailedSqlTransactionpropagating out of the hook). Two test-design notes worth knowing: Odoo'sassertRaiseswraps its body in a savepoint and rolls it back, so it cannot be used to poison a cursor; and every menu inICON_MAPis also inspp_hide_menus_base.MENU_APP, so the failing lookup targets a test-only xmlid to keep the siblingnext()override out of the picture.Lint:
pre-commit run(ruff, ruff-format, pylint_odoo, OpenSPP checks, bandit, semgrep) clean on the changed files.