Skip to content

Commit 0e64a33

Browse files
author
Nils Bars
committed
Handle database lock timeout errors gracefully
- Change statement_timeout reset from finally to else block to avoid executing SQL on an already-failed transaction - Roll back the DB session in the 500 error handler when the cause is a lock timeout, so template rendering can still query the DB
1 parent d204365 commit 0e64a33

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

webapp/ref/core/util.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,10 @@ def lock_db(connection: sqlalchemy.engine.Connection, readonly=False):
131131
"Another request may be holding the lock for too long."
132132
) from e
133133
raise
134-
finally:
135-
# Reset statement timeout to default (0 = no limit)
134+
else:
135+
# Reset statement timeout to default (0 = no limit).
136+
# Only do this on success — if the lock timed out, the transaction is
137+
# in a failed state and any further SQL would raise InFailedSqlTransaction.
136138
connection.execute(sqlalchemy.text("SET LOCAL statement_timeout = 0;"))
137139

138140
elapsed = time.monotonic() - start_time

webapp/ref/error.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
)
1414

1515
from ref.core import InconsistentStateError, failsafe
16+
from ref.core.util import DatabaseLockTimeoutError
1617

1718
error_handlers = []
1819

@@ -102,5 +103,20 @@ def internal_error(_, e):
102103
if isinstance(e, (AssertionError, InconsistentStateError)):
103104
failsafe()
104105

106+
# Roll back the session if it's in a failed state (e.g., after a database
107+
# lock timeout). Without this, rendering the error template would fail
108+
# because base.html queries the DB for settings like COURSE_NAME.
109+
orig_exception = e
110+
while orig_exception is not None:
111+
if isinstance(orig_exception, DatabaseLockTimeoutError):
112+
try:
113+
from ref import db
114+
115+
db.session.rollback()
116+
except Exception:
117+
pass
118+
break
119+
orig_exception = getattr(orig_exception, "__cause__", None)
120+
105121
text = f"Internal Error: If the problem persists, please contact the server administrator and provide the following error code {code}"
106122
return render_error_template(text, InternalServerError.code)

0 commit comments

Comments
 (0)