Skip to content

[FIX] base_bg: reaper timeout config, duplicate timeout DM, notification escaping - #442

Open
nicomacr wants to merge 5 commits into
ingadhoc:19.0from
adhoc-dev:19.0-t-72869-nmr
Open

[FIX] base_bg: reaper timeout config, duplicate timeout DM, notification escaping#442
nicomacr wants to merge 5 commits into
ingadhoc:19.0from
adhoc-dev:19.0-t-72869-nmr

Conversation

@nicomacr

@nicomacr nicomacr commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Three pre-existing fixes in base_bg/models/bg_job.py, found while reviewing #437 (they predate that PR). One commit per fix, plus two follow-up commits from the review round:

1. The reaper failed every running job when limit_time_real_cron is unset or 0.
The cutoff was computed as tools.config.get("limit_time_real_cron") or 0. With the core default (-1, meaning "use --limit-time-real") the cutoff lands one second in the future, and with 0 it lands at now: in both cases each reaper run marked every running job as failed and canceled the rest of its batch. The limit is now resolved the way the running server does — prefork: 0/unset means no limit, -1 delegates to --limit-time-real (PreforkServer.__init__); threaded: the value only applies when > 0, anything else falls back to --limit-time-real (ThreadedServer.process_limit). And when no limit applies, the orphan sweep keeps running instead of skipping the reaper entirely: a stuck running job whose records are gone can never finish on its own and would block its batch forever.

2. A permanently timed-out job DM'd its creator twice.
_handle_job_error -> _give_up -> fail() already posts "Job X failed: Job timed out", and the reaper loop posted a second "Job X timed out" right after. The loop's re-notification is gone; to keep the clickable job reference it provided, fail() now renders the job name as its html link (which also brings that link to every failure DM).

3. name / error_message reached the DM unescaped.
fail() interpolated them with plain % and _notify_user wrapped the result in Markup(): an error message with angle brackets (any traceback with an object repr) broke the render, and a crafted job name was stored HTML injected into the creator's DM. The failure message is now built as Markup with escaped interpolations, and _notify_user escapes its input — a plain-string result renders inert, while methods that mean HTML keep working by returning a Markup with its own interpolations escaped. The contract is documented in the README, and the two bg-enqueued methods in this repo that built HTML with plain % are aligned to it in their own commits (account_reconcile_bg, which also picks up the new fail(notify=) signature, and account_statement_import_sheet_file_bg).

Merge together with ingadhoc/odoo-saas-adhoc#3182 (same branch name, bundled build): ai_app_adhoc returns a plain str with HTML from a bg-enqueued method and needs the Markup treatment before this lands.

Test plan: --test-tags /base_bg,/account_reconcile_bg on a 19.0 database: 59 + 4 tests, 0 failed (new since the review: reaper threaded-mode fallback at 0, orphan sweep with no time limit).

Task: https://www.adhoc.inc/odoo/project.task/72869

@roboadhoc

Copy link
Copy Markdown
Contributor

Pull request status dashboard

The reaper computed its cutoff as `tools.config.get("limit_time_real_cron") or 0`.
With the core default (-1, meaning "use --limit-time-real") that yields a cutoff
one second in the future, and with 0 a cutoff of now: in both cases every running
job was marked failed (and the rest of its batch canceled) on each reaper run.

Resolve the limit the way the running server does — prefork: 0/unset means no
limit and -1 delegates to --limit-time-real (PreforkServer.__init__); threaded:
the value only applies when > 0, anything else falls back to --limit-time-real
(ThreadedServer.process_limit). And when no limit applies, keep the orphan sweep
running instead of skipping the reaper entirely: a stuck running job whose
records are gone can never finish on its own and would block its batch forever.
When the reaper gave up on a timed-out job, the user got two DMs: one from
_handle_job_error -> _give_up -> fail() ("Job X failed: Job timed out") and a
second one from the reaper loop itself ("Job X timed out").

Drop the loop's re-notification and keep fail()'s single message, which also
carries the related-record links. To preserve the one thing the loop message
added — a clickable reference to the job — fail() now renders the job name as
its html link.
fail() interpolated the job name and error message with plain % into a string
that _notify_user then wrapped in Markup(): an error_message with angle
brackets (any traceback with an object repr) broke the message render, and a
crafted job name was stored HTML injected into the user's DM (XSS).

Build the failure message as Markup with escaped interpolations (the job link
and the related-record links, already safe, pass through), and make
_notify_user escape its input: a plain-string result is now rendered inert,
while methods that mean HTML keep working by returning a Markup with its own
interpolations escaped. Document the contract in the README's Usage section;
the bg-enqueued methods that build HTML are aligned to it in the follow-up
commits.
The background reconciliation result interpolated the statement line name and
the exception text with plain % before wrapping in Markup(), injecting user
data as HTML into the DM that base_bg posts. Interpolate through the Markup
template instead, which escapes the values.

Also align the fail() override with the new base signature (notify keyword),
so a registry call passing notify= does not TypeError with both modules
installed.
… import DM

The background import result built its HTML with an f-string interpolating the
statement name before wrapping in Markup(), injecting user data as HTML into
the DM that base_bg posts. Interpolate through the Markup template instead,
which escapes the values, and mark the message translatable while at it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants