Skip to content

Commit e3f5117

Browse files
committed
Add db rollback on gevent timeout error
1 parent 1a297c5 commit e3f5117

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

‎server/mergin/sync/public_api_controller.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import uuid
1414
from datetime import datetime
1515

16+
import gevent
1617
import psycopg2
1718
from blinker import signal
1819
from connexion import NoContent, request
@@ -951,6 +952,9 @@ def project_push(namespace, project_name):
951952
f"Failed to upload a new project version using transaction id: {upload.id}: {str(err)}"
952953
)
953954
abort(422, "Failed to upload a new project version. Please try later.")
955+
except gevent.timeout.Timeout:
956+
db.session.rollback()
957+
raise
954958
finally:
955959
upload.clear()
956960

@@ -1165,6 +1169,10 @@ def push_finish(transaction_id):
11651169
f"transaction id: {transaction_id}.: {str(err)}"
11661170
)
11671171
abort(422, "Failed to create new version: {}".format(str(err)))
1172+
# catch exception during pg transaction so we can rollback and prevent PendingRollbackError during upload clean up
1173+
except gevent.timeout.Timeout:
1174+
db.session.rollback()
1175+
raise
11681176
finally:
11691177
# remove artifacts
11701178
upload.clear()

‎server/mergin/tests/test_middleware.py‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
44

55
import gevent
6+
import psycogreen.gevent
67
import pytest
8+
import sqlalchemy
79

8-
from ..app import create_simple_app, GeventTimeoutMiddleware
10+
from ..app import create_simple_app, GeventTimeoutMiddleware, db
911
from ..config import Configuration
1012

1113

@@ -31,3 +33,39 @@ def ping():
3133
if use_middleware
3234
else 200
3335
)
36+
37+
38+
def test_catch_timeout():
39+
"""Test proper handling of gevent timeout with db.session.rollback"""
40+
psycogreen.gevent.patch_psycopg()
41+
Configuration.GEVENT_WORKER = True
42+
Configuration.GEVENT_REQUEST_TIMEOUT = 1
43+
application = create_simple_app()
44+
45+
def unhandled():
46+
try:
47+
db.session.execute("SELECT pg_sleep(1.1);")
48+
finally:
49+
db.session.execute("SELECT 1;")
50+
return ""
51+
52+
def timeout():
53+
try:
54+
db.session.execute("SELECT pg_sleep(1.1);")
55+
except gevent.timeout.Timeout:
56+
db.session.rollback()
57+
raise
58+
finally:
59+
db.session.execute("SELECT 1;")
60+
return ""
61+
62+
application.add_url_rule("/unhandled", "unhandled", unhandled)
63+
application.add_url_rule("/timeout", "timeout", timeout)
64+
app_context = application.app_context()
65+
app_context.push()
66+
67+
assert application.test_client().get("/timeout").status_code == 504
68+
69+
# in case of missing rollback sqlalchemy would raise error
70+
with pytest.raises(sqlalchemy.exc.PendingRollbackError):
71+
application.test_client().get("/unhandled")

0 commit comments

Comments
 (0)