Fix: move document files once per request, after its writes commit

A tag PATCH moved files from inside the tags.set() transaction while holding the document row lock. A concurrent save holding media.lock and waiting on that row deadlocked with it, and every later save on the instance queued behind media.lock until the webserver restarted.

FileMoveMiddleware opens a file move scope around every request, in sync or async mode, so each changed document is moved once after the view returns and never while a transaction holds row locks.
This commit is contained in:
stumpylog committed 2026-11-03 15:09:29 -08:00
1 parent 8f873604d3
commit 5bcde3205f
5 files changed
+342

No files matched your search

+36
View File
@@ -1,3 +1,5 @@
from asgiref.sync import iscoroutinefunction
from asgiref.sync import markcoroutinefunction
from compression_middleware.middleware import CompressionMiddleware
from django.conf import settings
@@ -30,3 +32,37 @@ class ApiVersionMiddleware:
response["X-Version"] = version.__full_version_str__
return response
class FileMoveMiddleware:
"""
Open a document file move scope around each request, so every document the
request changes is moved once, after its writes have committed, before the
response is returned.
Supports both sync and async middleware chains.
"""
sync_capable = True
async_capable = True
def __init__(self, get_response) -> None:
self.get_response = get_response
self.async_mode = iscoroutinefunction(get_response)
if self.async_mode:
markcoroutinefunction(self)
def __call__(self, request):
if self.async_mode:
return self.__acall__(request)
from documents.file_moves import defer_file_moves
with defer_file_moves():
return self.get_response(request)
async def __acall__(self, request):
from documents.file_moves import adefer_file_moves
async with adefer_file_moves():
return await self.get_response(request)