[FIX] purchase_ux: reuse the stored PDF of the purchase order report - #367
Closed
jcadhoc wants to merge 1 commit into
Closed
[FIX] purchase_ux: reuse the stored PDF of the purchase order report#367jcadhoc wants to merge 1 commit into
jcadhoc wants to merge 1 commit into
Conversation
Rendering a PDF report is circular: wkhtmltopdf resolves the report asset links against base_url, that is, against the very same Odoo instance. So a render occupies one HTTP worker and needs another free one to answer itself. On an instance with workers = 3, three concurrent renders exhaust the three workers, nobody calls accept() and everything stays blocked until limit_time_real (300s); the liveness probe kills the container at 120s, so the pod never recovers by itself and the service is down for 2 to 4 minutes. What brings three renders together is the mail composer: attachment_ids is a compute with store=True and readonly=False, so the web client recomputes it on every round trip while the composer is opening, and each recompute rendered the same purchase order PDF again. Measured on production logs, the same document is generated 2 to 6 times per opening of the composer, and the only instances that went down are the ones that produced bursts of 3 or more renders within 4 seconds. Rather than overriding that compute, use what core already offers: with 'attachment' and 'attachment_use' set on the report, _render_qweb_pdf_prepare_streams stores the generated PDF on the record and the next render returns the stored one without calling wkhtmltopdf. The write_date in the attachment name is what invalidates it, so an edited purchase order is rendered fresh instead of being frozen, and the expression tolerates an empty write_date so printing can never break because of it. Applied to the two reports the purchase order mail templates carry: action_report_purchase_order and report_purchase_quotation. Change note: al mandar una orden de compra por mail, el PDF se genera una sola vez en lugar de 2 a 6 veces, y reimprimir la misma versión del documento reusa el PDF ya generado. Si la OC se edita, se vuelve a generar. Como efecto visible, el PDF queda guardado como adjunto de la OC, con la fecha de modificación en el nombre del archivo.
Contributor
Contributor
|
@roboadhoc r+ bump |
roboadhoc
pushed a commit
that referenced
this pull request
Sep 2, 2026
Rendering a PDF report is circular: wkhtmltopdf resolves the report asset links against base_url, that is, against the very same Odoo instance. So a render occupies one HTTP worker and needs another free one to answer itself. On an instance with workers = 3, three concurrent renders exhaust the three workers, nobody calls accept() and everything stays blocked until limit_time_real (300s); the liveness probe kills the container at 120s, so the pod never recovers by itself and the service is down for 2 to 4 minutes. What brings three renders together is the mail composer: attachment_ids is a compute with store=True and readonly=False, so the web client recomputes it on every round trip while the composer is opening, and each recompute rendered the same purchase order PDF again. Measured on production logs, the same document is generated 2 to 6 times per opening of the composer, and the only instances that went down are the ones that produced bursts of 3 or more renders within 4 seconds. Rather than overriding that compute, use what core already offers: with 'attachment' and 'attachment_use' set on the report, _render_qweb_pdf_prepare_streams stores the generated PDF on the record and the next render returns the stored one without calling wkhtmltopdf. The write_date in the attachment name is what invalidates it, so an edited purchase order is rendered fresh instead of being frozen, and the expression tolerates an empty write_date so printing can never break because of it. Applied to the two reports the purchase order mail templates carry: action_report_purchase_order and report_purchase_quotation. Change note: al mandar una orden de compra por mail, el PDF se genera una sola vez en lugar de 2 a 6 veces, y reimprimir la misma versión del documento reusa el PDF ya generado. Si la OC se edita, se vuelve a generar. Como efecto visible, el PDF queda guardado como adjunto de la OC, con la fecha de modificación en el nombre del archivo. closes #367 Signed-off-by: Luciano Esperlazza <les@adhoc.inc>
roboadhoc
added a commit
that referenced
this pull request
Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Sending a purchase order by email renders the same PDF 2 to 6 times, and that burst is what takes the instance down.
Rendering a PDF is circular: wkhtmltopdf resolves the report asset links against
base_url, that is, against the very same Odoo instance. A render occupies one HTTP worker and needs another free one to answer itself. Withworkers = 3, three concurrent renders exhaust the three, nobody callsaccept(), and everything stays blocked untillimit_time_real(300s) — but the liveness probe kills the container at 120s, so the pod never recovers by itself.The bursts come from the composer:
attachment_idsis a compute withstore=True, readonly=False, so the web client recomputes it on every round trip while the composer opens, and each recompute rendered the report again.The change
Two fields on the two reports the purchase order mail templates carry. No Python.
With
attachmentandattachment_useset,_render_qweb_pdf_prepare_streamsstores the generated PDF on the record and the next render returns the stored one without calling wkhtmltopdf. This is core's own mechanism — the same oneaccountuses for invoices.The
write_datein the attachment name is the invalidation: an edited purchase order gets a new name and is rendered fresh, so the PDF is never frozen. The expression tolerates an emptywrite_dateso printing can never break because of it.Why this and not a code fix
The first version of this fix overrode
_compute_attachment_idsand post-processed the report html to inline the asset bundles — around 290 lines across two modules. It was replaced by this: core already persists and reuses report PDFs, so the same behaviour is two data fields.Raising
workerswas ruled out: an instance with 8 effective workers (workers = 4,replicas = 2) hit the same deadlock, and it would mean paying for capacity to render the same PDF several times.Measurements
From the access logs of a production 19.0 instance, over 9 hours:
/web/assets/*report_assets*from the loopback address — exactly 2 self-requests per render.attachmentdoes.What this does not do
The circular fetch of the two CSS bundles is still there — one render still asks itself for them. This removes the bursts, not the circularity. Closing that properly is a separate discussion on the platform side.
Trade-offs
Test plan
The PDF report has been generatedin the log per document version, instead of one per round trip./web/assets/*report_assets*during those openings: two for the first render, none for the reuses.print_report_name, unchanged.report_purchase_quotation.Verified against core: with these two fields set, render 1 calls wkhtmltopdf, renders 2 and 3 call it zero times, and a change to the record makes render 4 call it again.
Internal reference: https://www.adhoc.inc/odoo/helpdesk.ticket/126274