From 2b0b93a29e5203819782a7ae1e4843f40972d008 Mon Sep 17 00:00:00 2001 From: TecnologiaIG Date: Tue, 21 Apr 2026 20:48:25 -0600 Subject: [PATCH] [FIX] fs_attachment: bound GC cursor with per-transaction timeouts The secondary cursor opened by ``FsFileGC._in_new_cursor`` had no statement or idle-in-transaction timeout, so a slow external storage backend (observed on Azure Blob, same class of issue on any fsspec backend with network latency) could leave it parked while waiting on I/O. The cursor then kept the row lock on ``fs_file_gc`` taken by the ``ON CONFLICT DO NOTHING`` INSERT in ``_mark_for_gc``, serialising every concurrent attachment write behind the ``store_fname`` unique constraint. In production we observed six ``idle in transaction`` cursors holding this lock for 4-10 minutes each; every queued ``POST /mail/attachment/upload`` and ``/web/binary/upload_attachment`` hit the Odoo session ``statement_timeout`` (900s on Odoo.sh) and returned a 500 HTML page, which the frontend ``FileInput.uploadFiles`` passes straight to ``JSON.parse``, surfacing as:: Unexpected token '<', " --- fs_attachment/__manifest__.py | 2 +- fs_attachment/models/fs_file_gc.py | 12 ++++++++++++ .../readme/newsfragments/gc_cursor_timeout.bugfix | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 fs_attachment/readme/newsfragments/gc_cursor_timeout.bugfix diff --git a/fs_attachment/__manifest__.py b/fs_attachment/__manifest__.py index bb8fe1ca10..4375ae4bcf 100644 --- a/fs_attachment/__manifest__.py +++ b/fs_attachment/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Base Attachment Object Store", "summary": "Store attachments on external object store", - "version": "16.0.2.0.1", + "version": "16.0.2.0.2", "author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)", "license": "AGPL-3", "development_status": "Beta", diff --git a/fs_attachment/models/fs_file_gc.py b/fs_attachment/models/fs_file_gc.py index 6ab70ec38e..02cc01e7ac 100644 --- a/fs_attachment/models/fs_file_gc.py +++ b/fs_attachment/models/fs_file_gc.py @@ -44,6 +44,18 @@ def _in_new_cursor(self) -> Cursor: with closing(self.env.registry.cursor()) as cr: try: + # Bound this cursor so it cannot hold a row lock on + # fs_file_gc indefinitely. Without these guards, a slow + # external storage backend (e.g. S3, Azure Blob) can leave + # the cursor "idle in transaction" while waiting on I/O, + # serialising every attachment write behind the unique + # constraint lock on fs_file_gc.store_fname. Every queued + # POST /mail/attachment/upload then times out at the + # session statement_timeout and returns a 500 HTML page, + # which the frontend tries to JSON.parse and fails with + # "Unexpected token '<', \"