Skip to content

Commit ff133bf

Browse files
committed
fix: retrying transaction on 40001 errors
1 parent 00b35da commit ff133bf

File tree

8 files changed

+50
-13
lines changed

8 files changed

+50
-13
lines changed

nix/overlays/haskell-packages.nix

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ let
6262
}) "--subpath=."
6363
{ }
6464
);
65-
hasql-transaction = lib.dontCheck (prev.callHackageDirect {
66-
pkg = "hasql-transaction";
67-
ver = "1.2.1";
68-
sha256 = "sha256-7Q7gt5ts4OoGU58dp6PJFZmVjfwjozANHNg2u1PJf6Q=";
69-
}
70-
{ });
65+
hasql-transaction = lib.dontCheck (prev.callHackageDirect
66+
{
67+
pkg = "hasql-transaction";
68+
ver = "1.2.1";
69+
sha256 = "sha256-7Q7gt5ts4OoGU58dp6PJFZmVjfwjozANHNg2u1PJf6Q=";
70+
}
71+
{ });
7172

7273
# Needed for hasql 1.9
7374
text-builder = lib.dontCheck prev.text-builder_1_0_0_3;

nix/tools/withTools.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ let
8585
>&2 echo "${commandName}: You can tail the logs with: tail -f $tmpdir/db.log"
8686
8787
if test "$_arg_replica" = "on"; then
88-
replica_slot="replica_$RANDOM"
88+
replica_slot="rr_$RANDOM"
8989
replica_dir="$tmpdir/$replica_slot"
9090
replica_host="$tmpdir/socket_$replica_slot"
9191
@@ -100,7 +100,7 @@ let
100100
101101
log "Starting replica on $replica_host"
102102
103-
pg_ctl -D "$replica_dir" -l "$replica_dblog" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $replica_host -c log_statement=\"all\" " \
103+
pg_ctl -D "$replica_dir" -l "$replica_dblog" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $replica_host -c log_statement=\"all\" -c max_standby_streaming_delay=\"3s\"" \
104104
>> "$setuplog"
105105
106106
>&2 echo "${commandName}: Replica enabled. You can connect to it with: psql 'postgres:///$PGDATABASE?host=$replica_host' -U postgres"

src/PostgREST/AppState.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
408408
qSchemaCache = do
409409
conf@AppConfig{..} <- getConfig appState
410410
(resultTime, result) <-
411-
timeItT $ usePool appState (SQL.transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
411+
timeItT $ usePool appState (SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
412412
case result of
413413
Left e -> do
414414
putSCacheStatus appState SCPending

src/PostgREST/CLI.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ main CLI{cliCommand, cliPath} = do
5353
dumpSchema :: AppState -> IO LBS.ByteString
5454
dumpSchema appState = do
5555
conf@AppConfig{..} <- AppState.getConfig appState
56-
result <- AppState.usePool appState (SQL.transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
56+
result <- AppState.usePool appState (SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
5757
case result of
5858
Left e -> do
5959
let observer = AppState.getObserver appState

src/PostgREST/Config/Database.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pgVersionStatement = SQL.Statement sql HE.noParams versionRow
9292
-- A setting on the database only will have no effect: ALTER DATABASE postgres SET <prefix>jwt_aud = 'xx'
9393
queryDbSettings :: Maybe Text -> Bool -> Session [(Text, Text)]
9494
queryDbSettings preConfFunc prepared =
95-
SQL.transaction SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ SQL.Statement sql (arrayParam HE.text) decodeSettings prepared
95+
SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ SQL.Statement sql (arrayParam HE.text) decodeSettings prepared
9696
where
9797
sql = encodeUtf8 [trimming|
9898
WITH
@@ -132,7 +132,7 @@ queryDbSettings preConfFunc prepared =
132132

133133
queryRoleSettings :: PgVersion -> Bool -> Session (RoleSettings, RoleIsolationLvl)
134134
queryRoleSettings pgVer prepared =
135-
SQL.transaction SQL.ReadCommitted SQL.Read $ SQL.statement mempty $ SQL.Statement sql HE.noParams (processRows <$> rows) prepared
135+
SQL.transactionNoRetry SQL.ReadCommitted SQL.Read $ SQL.statement mempty $ SQL.Statement sql HE.noParams (processRows <$> rows) prepared
136136
where
137137
sql = encodeUtf8 [trimming|
138138
with

src/PostgREST/Query.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ data QueryResult
7676
query :: AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> Query
7777
query _ _ _ (NoDb x) _ = NoDbQuery $ NoDbResult x
7878
query config AuthResult{..} apiReq (Db plan) sCache =
79-
DbQuery isoLvl txMode dbHandler SQL.transaction mainSQLQuery
79+
DbQuery isoLvl txMode dbHandler SQL.transactionNoRetry mainSQLQuery
8080
where
8181
isoLvl = planIsoLvl config authRole plan
8282
txMode = planTxMode plan

test/io/replica.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ $$ language sql;
1010

1111
create table replica.items as select x as id from generate_series(1, 10) x;
1212

13+
create table replica.conflict as select x as id from generate_series(1, 1000000) x;
14+
15+
create view replica.conflict_view as select * from replica.conflict where (pg_sleep(0.01) is not null);
16+
1317
DROP ROLE IF EXISTS postgrest_test_anonymous;
1418
CREATE ROLE postgrest_test_anonymous;
1519

test/io/test_replica.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"IO tests for PostgREST started on replicas"
22

3+
import os
34
import pytest
5+
import time
46

57
from config import *
68
from util import *
@@ -26,3 +28,33 @@ def test_sanity_replica(replicaenv):
2628

2729
response = postgrest.session.get("/items?select=count")
2830
assert response.text == '[{"count":10}]'
31+
32+
33+
def test_conflict_replica(replicaenv):
34+
"Test that PostgREST does not retry the transaction on conflict with recovery (PG error code 40001)"
35+
36+
with run(env=replicaenv["replica"]) as postgrest:
37+
38+
def conflict():
39+
response = postgrest.session.get("/conflict_view")
40+
# Checks that the transaction stops and returns the 40001 error instead of retrying
41+
assert response.json()["code"] == "40001"
42+
assert response.status_code == 500
43+
44+
t = Thread(target=conflict)
45+
t.start()
46+
47+
# make sure the request has started
48+
time.sleep(0.1)
49+
50+
prienv = replicaenv["primary"]
51+
connopts = f'-d {prienv["PGDATABASE"]} -U postgres -h {prienv["PGHOST"]}'
52+
53+
# Delete the table data while the request with the lock is running to trigger the recovery conflict
54+
os.system(
55+
f'psql {connopts} --set ON_ERROR_STOP=1 -a -c "DELETE FROM replica.conflict;"'
56+
)
57+
# Vacuum the table to accelerate the process
58+
os.system(f"vacuumdb {connopts} -t replica.conflict")
59+
60+
t.join()

0 commit comments

Comments
 (0)