Repository navigation
fix: keep the job worker sidecar out of module upgrades (hide-menus hook, healthcheck, grace period) - #577
Conversation
…ted modules _register_hook re-applied hide_menus() at the end of every registry load. Only a load that installed or updated modules can reset group_ids, and Odoo lists exactly those in registry.updated_modules; every other load (each HTTP and cron worker, and the job worker, reloading after another process signals a change) repeated the same ir.ui.menu and spp.hide.menu writes, racing the upgrading process for those rows during deploys.
…worker The production queue-worker healthcheck ran pgrep -f 'odoo.addons.job_worker.cli', which never matches the job_worker_runner.py command it actually runs, so the runner's degraded signal (quarantine, database-error recovery, an upgrade pause that never ends) never reached the orchestrator. Both compose files now use job_worker_healthcheck.py, which checks the runner's heartbeat file. Neither set stop_grace_period, so Docker SIGKILLed the worker 10s into a deploy, before the runner's 30s wait for running jobs; every killed job lost a retry attempt when it was reclaimed. Set it to 60s.
Document the stop-upgrade-start procedure for the queue worker, how job_worker's upgrade gate pauses a worker that is left running, and what the queue worker's healthcheck checks.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 19.0 #577 +/- ##
==========================================
+ Coverage 76.91% 77.04% +0.13%
==========================================
Files 704 727 +23
Lines 45774 47165 +1391
==========================================
+ Hits 35205 36338 +1133
- Misses 10569 10827 +258
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
gonzalesedwin1123
left a comment
There was a problem hiding this comment.
Thanks. The core fix is correct, and I checked it against the Odoo 19 source:
registry.updated_modulesis a fresh list for everyRegistry(orm/registry.py). It is only appended for packages with an install or upgrade operation (modules/loading.py,load_module_graph).- A reload after another process signals (
check_signaling()→Registry.new(db)) starts with an empty list, so HTTP, cron and job-worker reloads skip the pass. - CLI
-u/-i, thebase.module.upgradewizard,button_immediate_*and thebase.partially_updated_databaserecovery path all run an update load, so hiding is still re-applied on every path that can resetgroup_ids. With-uunder prefork, the master's preload runs the hook once, before forking. - Your
_register_hookinventory is complete. job_worker_healthcheck.pyexists in the image at that path, and it exits 1 when there's no heartbeat. The oldpgreppattern could never matchjob_worker_runner.py.- The new no-update test would fail against the old code.
Three changes before merge:
1. docker/docker-compose.nginx.yml needs the same fix. Its queue-worker has no healthcheck and no stop_grace_period. Deployments on the Nginx stack keep Docker's 10s SIGKILL, and each killed job loses a retry attempt. Please add the same healthcheck and stop_grace_period: 60s blocks. Its /tmp is a tmpfs, which suits the heartbeat file.
2. The README upgrade snippet expands $DB_NAME in the host shell.
$COMPOSE run --rm odoo odoo -d "$DB_NAME" -u <modules> --stop-after-init --no-httpDB_NAME normally lives in .env. Compose uses that file for interpolation but never exports it to the operator's shell, so this usually becomes -d "". The passthrough command already gets the database from the generated /etc/odoo/odoo.conf (ODOO_RC, db_name = ${DB_NAME}), so dropping -d "$DB_NAME" is enough.
3. Mark the README text that depends on OpenSPP/odoo-job-worker#33. That PR hasn't merged. Until it does, this README describes behaviour job_worker 19.0 doesn't have: the worker "pauses by itself", the JOB_WORKER_UPGRADE_* / JOB_WORKER_UPGRADE_PAUSE_UNHEALTHY_AFTER settings, the one-hour-unhealthy rule, and the "Upgrading modules while the worker runs" doc section. A short "requires job_worker with the upgrade gate (odoo-job-worker#33)" on those paragraphs would do. So would keeping only the stop → -u → start procedure here and adding the pause text once #33 lands.
Non-blocking suggestions:
- The named-volume caveat in the PR description should go in
docker/README.md, where operators will see it:odoo_addonsover/mnt/extra-addonsmeans existing deployments won't pick upjob_worker_healthcheck.pyor #33 from a new image. - The snippet stops only the worker, so
odookeeps serving users and running crons during the one-shot-u. If that's intended, a sentence saying so would help. Otherwise, stopodootoo. - On stop, the runner joins worker threads one after another, 30s each (
_cleanup→_stop_worker_thread), so 60s covers one or two databases. The compose comment could say "per database". - The first paragraph of the
_register_hookcomment still says hiding is re-applied "at the end of every registry load (startup, …)", which now contradicts the gate. Also,updated_moduleslasts as long as the updating process's registry. A later_setup_models__in that process, such as adding a custom field, re-runs the hook. That is harmless, but "a load that updated modules" is not quite the exact condition. - Operator-facing behaviour change: menu drift from anything other than a module update no longer self-heals on restart. A HISTORY line saying
-u spp_hide_menus_baserestores hidden menus would help.
… and grace period docker-compose.nginx.yml's queue-worker had neither, so deployments on the Nginx stack kept Docker's 10s SIGKILL and an orchestrator that never saw the runner's degraded signal. The compose comments now say the runner waits up to 30s per database, one database after another. README: the one-shot upgrade no longer passes -d "$DB_NAME" (DB_NAME lives in the env file, not the operator's shell, so it expanded to -d "") and takes the database from the generated odoo.conf instead; it now stops odoo as well as the queue worker, since requests and crons hit the same changing schema; the upgrade-pause text says it needs job_worker 19.0.1.3.0 (OpenSPP/odoo-job-worker#33); and the odoo_addons named-volume caveat moves from the PR description into the README.
… menus The comment still said hiding is re-applied at the end of every registry load. It runs in a registry that installed or updated modules, and since updated_modules lasts as long as that registry, a later model re-setup in the same process repeats the (idempotent) pass. HISTORY now tells operators that menu drift from anything but a module update no longer self-heals on restart, and that -u spp_hide_menus_base re-applies hiding.
|
Thanks for checking this against the Odoo source. All eight points are addressed in 501d1aa and 3a60630. Blocking
Suggestions
|
gonzalesedwin1123
left a comment
There was a problem hiding this comment.
Re-reviewed at 3a60630. All three asks and the five suggestions are addressed, and I re-checked the new text against the image and the compose files:
docker-compose.nginx.yml: healthcheck andstop_grace_periodmatch the production file; the heartbeat file defaults to/tmp/job_worker_heartbeat(job_worker/cli/heartbeat.py), which that service's tmpfs covers.- The one-shot without
-dis right: the entrypoint treats any argument other than a bareodooas a passthrough, generates/etc/odoo/odoo.conffromdocker/odoo.conf.templatefirst (db_name = ${DB_NAME}), and the image setsODOO_RCto it. Both production stacks use that template (docker/nginx/odoo.conf.templateis an nginx config, not an Odoo one). - Named-volume caveat,
odoostopped during the one-shot, per-database grace-period comments, the_register_hookcomment and the HISTORY recovery line all read correctly.
One precision point, non-blocking, fix now or in a follow-up as you prefer:
"job_worker 19.0.1.3.0 or later" doesn't identify the gate. odoo-job-worker#33 didn't bump the manifest; 19.0.1.3.0 was set by #32 on 2026-08-20 and the CHANGELOG still lists it as unreleased, so any image or odoo_addons volume built from 19.0 between 2026-08-20 and 2026-09-29 reports 19.0.1.3.0 without the gate. That's exactly the "volume seeded before it" case the README's grep '"version"' check is meant to catch, and it can't. Something like
docker compose -f docker/docker-compose.production.yml exec queue-worker \
test -f /mnt/extra-addons/odoo-job-worker/job_worker/cli/upgrade_gate.py && echo gate presentanswers it directly, and the two "19.0.1.3.0 or later" sentences could say "job_worker with the upgrade gate (odoo-job-worker#33, merged 2026-09-29; the manifest version did not change)". Alternatively a bump to 19.0.1.4.0 on odoo-job-worker would make the version check meaningful.
Approving; the merge is Edwin's call. Our #532 rebases onto this once it lands (renumber to 19.0.2.1.2).
Companion to OpenSPP/odoo-job-worker#33, which pauses the job worker while modules are installed or upgraded. This PR covers the OpenSPP side of running the worker as a sidecar during deploys.
1.
spp_hide_menus_base: re-hide menus only in the process that updated modules_register_hookcalledhide_menus()at the end of every registry load. That covered every upgrade path, which is why it lives there. But it also ran in every other process that reloads its registry after another one signals a change: each HTTP worker, each cron worker, and the job worker. All of them made the same writes to the sameir.ui.menu.group_idsandspp.hide.menurows, racing the upgrading process for them during deploys (lock waits, duplicate-key and serialization errors).Only a load that installed or updated modules can have reset
group_ids, and Odoo lists exactly those modules inregistry.updated_modules. The hook now runshide_menus()only when that list is non-empty. The CLI-u, the upgrade wizard and immediate install all end in such a load, so hiding is still re-applied on every path the original comment names.group_idson a plain restart, so there is nothing to re-apply.hide_menus()is not called;updated_modulesexplicitly. Before, it passed only because the test registry itself had been loaded with-i.19.0.2.1.1, with a HISTORY entry.Checked the other
_register_hooks.spp_cel_domain,spp_cel_vocabularyandspp_disability_registryare in-memory only, andspp_auditreads rules and patches classes without writing. None need changing.2. Compose: a healthcheck that works, and a grace period
docker/docker-compose.production.yml: thequeue-workerhealthcheck ranpgrep -f 'odoo.addons.job_worker.cli', but the container runspython …/job_worker_runner.py, so the pattern never matched. The runner's degraded signal (a quarantined database, stuck database-error recovery, an upgrade pause that never ends) never reached the orchestrator. It now runsjob_worker_healthcheck.py, which checks the runner's heartbeat file.docker-compose.yml:jobworkerhad no healthcheck; it now has the same one. The comment claiming databases are rediscovered "every 5 minutes" was also wrong, so it now describes the upgrade pause instead.docker/docker-compose.nginx.yml:queue-workerhad no healthcheck and no grace period; it now has both, as in the production file.stop_grace_period: 60s. Docker's 10s default SIGKILLed the worker before the runner's 30s wait for running jobs, and every killed job lost a retry attempt when it was reclaimed.3.
docker/README.md-u→ start procedure for upgrades;job_workerupgrade gate does when the worker is left running;Verified end to end
Root compose,
uiprofile, with this branch and odoo-job-worker#33 mounted:job_worker,spp_hide_menus_basewith a one-shot-ibeside the runningjobworker.opensppwithODOO_UPDATE_MODULES=all, leavingjobworkerrunning.Results:
Job worker paused for database jwe2e: modules-in-transition [auth_passkey_portal:to upgrade, …], thenresumed … after 5s; reloading the registry.jobworkerstayed healthy under the new heartbeat healthcheck.Deployment notes
job_worker19.0.1.3.0 (fix(worker): pause the job worker while modules are installed or upgraded odoo-job-worker#33, merged). Images built with the defaultODOO_JOB_WORKER_REF=19.0pick it up. A build pinned to an older commit does not. The hook fix and the compose changes stand on their own.odoo_addonsnamed volume over/mnt/extra-addons, so the addon code there, including the job worker andjob_worker_healthcheck.py, may predate the image.docker/README.mdnow says how to check, and how to re-seed the volume.