Skip to content

Commit 9051720

Browse files
committed
Make transaction modifying queries non-interruptible
1 parent b66c218 commit 9051720

File tree

3 files changed

+40
-27
lines changed

3 files changed

+40
-27
lines changed

src/Database/PostgreSQL/PQTypes/Internal/Connection.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,14 +334,16 @@ runQueryImpl
334334
-> IO (Int, ForeignPtr PGresult)
335335
-> IO (Int, ForeignPtr PGresult, ConnectionStats -> ConnectionStats)
336336
runQueryImpl Connection {..} sql execSql = do
337-
E.mask $ \restore -> do
337+
E.uninterruptibleMask $ \restore -> do
338338
-- While the query runs, the current thread will not be able to receive
339339
-- asynchronous exceptions. This prevents clients of the library from
340340
-- interrupting execution of the query. To remedy that we spawn a separate
341341
-- thread for the query execution and while we wait for its completion, we
342342
-- are able to receive asynchronous exceptions (assuming that threaded GHC
343343
-- runtime system is used) and react appropriately.
344-
queryRunner <- async . restore $ do
344+
queryRunner <- asyncWithUnmask $ \unmask -> unmask $ do
345+
-- Uncoditionally unmask asynchronous exceptions here so that 'cancel'
346+
-- operation potentially invoked below works as expected.
345347
t1 <- getMonotonicTime
346348
(paramCount, res) <- execSql
347349
t2 <- getMonotonicTime
@@ -370,7 +372,7 @@ runQueryImpl Connection {..} sql execSql = do
370372
-- for the query runner thread to terminate. It is paramount we make the
371373
-- exception handler uninterruptible as we can't exit from the main block
372374
-- until the query runner thread has terminated.
373-
E.onException (restore $ wait queryRunner) . E.uninterruptibleMask_ $ do
375+
E.onException (restore $ wait queryRunner) $ do
374376
c_PQcancel connPtr >>= \case
375377
-- If query cancellation request was successfully processed, there is
376378
-- nothing else to do apart from waiting for the runner to terminate.

src/Database/PostgreSQL/PQTypes/Internal/State.hs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,31 +47,36 @@ initConnectionState
4747
initConnectionState ics = \case
4848
AcquireOnDemand -> pure OnDemand
4949
AcquireAndHold tsIsolationLevel tsPermissions -> do
50-
let isolationLevel = case tsIsolationLevel of
51-
DefaultLevel -> ""
52-
ReadCommitted -> "ISOLATION LEVEL READ COMMITTED"
53-
RepeatableRead -> "ISOLATION LEVEL REPEATABLE READ"
54-
Serializable -> "ISOLATION LEVEL SERIALIZABLE"
55-
permissions = case tsPermissions of
56-
DefaultPermissions -> ""
57-
ReadOnly -> "READ ONLY"
58-
ReadWrite -> "READ WRITE"
50+
let initSql =
51+
smconcat
52+
[ "BEGIN"
53+
, case tsIsolationLevel of
54+
DefaultLevel -> ""
55+
ReadCommitted -> "ISOLATION LEVEL READ COMMITTED"
56+
RepeatableRead -> "ISOLATION LEVEL REPEATABLE READ"
57+
Serializable -> "ISOLATION LEVEL SERIALIZABLE"
58+
, case tsPermissions of
59+
DefaultPermissions -> ""
60+
ReadOnly -> "READ ONLY"
61+
ReadWrite -> "READ WRITE"
62+
]
5963
(conn, cdata) <- takeConnection ics
60-
_ <- liftBase . runQueryIO @SQL conn $ "BEGIN" <+> isolationLevel <+> permissions
64+
_ <- liftBase . uninterruptibleMask_ $ runQueryIO @SQL conn initSql
6165
pure $ Acquired tsIsolationLevel tsPermissions conn cdata
6266

6367
finalizeConnectionState
64-
:: (HasCallStack, MonadBase IO m, MonadMask m)
68+
:: (HasCallStack, MonadBase IO m)
6569
=> InternalConnectionSource m cdata
6670
-> ExitCase r
6771
-> ConnectionState cdata
6872
-> m ()
6973
finalizeConnectionState ics ec = \case
7074
OnDemand -> pure ()
7175
Acquired _ _ conn cdata -> do
72-
_ <- liftBase . runQueryIO @SQL conn $ case ec of
73-
ExitCaseSuccess _ -> "COMMIT"
74-
_ -> "ROLLBACK"
76+
let finalizeSql = case ec of
77+
ExitCaseSuccess _ -> "COMMIT"
78+
_ -> "ROLLBACK"
79+
_ <- liftBase . uninterruptibleMask_ $ runQueryIO @SQL conn finalizeSql
7580
putConnection ics (conn, cdata) ec
7681
Finalized -> error "finalized connection"
7782

@@ -184,7 +189,7 @@ withConnection ConnectionData {..} action = do
184189
Finalized -> error "finalized connection"
185190

186191
initConnectionData
187-
:: MonadBase IO m
192+
:: (MonadBase IO m, MonadMask m)
188193
=> ConnectionSourceM m
189194
-> ConnectionAcquisitionMode
190195
-> m (ConnectionData m)

src/Database/PostgreSQL/PQTypes/Transaction.hs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,18 @@ withSavepoint (Savepoint savepoint) m = do
4747

4848
----------------------------------------
4949

50+
-- Note: below functions that modify transaction state need to not be
51+
-- interruptible so we don't end up in unexpected transaction state.
52+
5053
-- | Begin transaction using given transaction settings.
51-
begin :: (HasCallStack, MonadDB m, MonadThrow m) => m ()
52-
begin = do
54+
begin :: (HasCallStack, MonadDB m, MonadMask m) => m ()
55+
begin = uninterruptibleMask_ $ do
5356
getConnectionAcquisitionMode >>= \case
5457
AcquireOnDemand -> do
5558
throwDB $ HPQTypesError "Can't begin a transaction in OnDemand mode"
5659
AcquireAndHold isolationLevel permissions -> do
5760
runSQL_ $
58-
mintercalate
59-
" "
61+
smconcat
6062
[ "BEGIN"
6163
, case isolationLevel of
6264
DefaultLevel -> ""
@@ -70,8 +72,8 @@ begin = do
7072
]
7173

7274
-- | Commit active transaction using given transaction settings.
73-
commit :: (HasCallStack, MonadDB m, MonadThrow m) => m ()
74-
commit = do
75+
commit :: (HasCallStack, MonadDB m, MonadMask m) => m ()
76+
commit = uninterruptibleMask_ $ do
7577
getConnectionAcquisitionMode >>= \case
7678
AcquireOnDemand -> do
7779
throwDB $ HPQTypesError "Can't commit a transaction in OnDemand mode"
@@ -80,8 +82,8 @@ commit = do
8082
begin
8183

8284
-- | Rollback active transaction using given transaction settings.
83-
rollback :: (HasCallStack, MonadDB m, MonadThrow m) => m ()
84-
rollback = do
85+
rollback :: (HasCallStack, MonadDB m, MonadMask m) => m ()
86+
rollback = uninterruptibleMask_ $ do
8587
getConnectionAcquisitionMode >>= \case
8688
AcquireOnDemand -> do
8789
throwDB $ HPQTypesError "Can't rollback a transaction in OnDemand mode"
@@ -96,4 +98,8 @@ unsafeWithoutTransaction
9698
unsafeWithoutTransaction action = do
9799
getConnectionAcquisitionMode >>= \case
98100
AcquireOnDemand -> action
99-
AcquireAndHold {} -> bracket_ (runSQL_ "COMMIT") begin action
101+
AcquireAndHold {} ->
102+
bracket_
103+
(uninterruptibleMask_ $ runSQL_ "COMMIT")
104+
begin
105+
action

0 commit comments

Comments
 (0)