@@ -463,6 +463,24 @@ def new_transaction(
463
463
* args : Any ,
464
464
** kwargs : Any
465
465
) -> R :
466
+ """Start a new database transaction with the given connection.
467
+
468
+ Note: The given func may be called multiple times under certain
469
+ failure modes. This is normally fine when in a standard transaction,
470
+ but care must be taken if the connection is in `autocommit` mode that
471
+ the function will correctly handle being aborted and retried half way
472
+ through its execution.
473
+
474
+ Args:
475
+ conn
476
+ desc
477
+ after_callbacks
478
+ exception_callbacks
479
+ func
480
+ *args
481
+ **kwargs
482
+ """
483
+
466
484
start = monotonic_time ()
467
485
txn_id = self ._TXN_ID
468
486
@@ -566,7 +584,12 @@ def new_transaction(
566
584
sql_txn_timer .labels (desc ).observe (duration )
567
585
568
586
async def runInteraction (
569
- self , desc : str , func : "Callable[..., R]" , * args : Any , ** kwargs : Any
587
+ self ,
588
+ desc : str ,
589
+ func : "Callable[..., R]" ,
590
+ * args : Any ,
591
+ db_autocommit : bool = False ,
592
+ ** kwargs : Any
570
593
) -> R :
571
594
"""Starts a transaction on the database and runs a given function
572
595
@@ -576,6 +599,18 @@ async def runInteraction(
576
599
database transaction (twisted.enterprise.adbapi.Transaction) as
577
600
its first argument, followed by `args` and `kwargs`.
578
601
602
+ db_autocommit: Whether to run the function in "autocommit" mode,
603
+ i.e. outside of a transaction. This is useful for transactions
604
+ that are only a single query.
605
+
606
+ Currently, this is only implemented for Postgres. SQLite will still
607
+ run the function inside a transaction.
608
+
609
+ WARNING: This means that if func fails half way through then
610
+ the changes will *not* be rolled back. `func` may also get
611
+ called multiple times if the transaction is retried, so must
612
+ correctly handle that case.
613
+
579
614
args: positional args to pass to `func`
580
615
kwargs: named args to pass to `func`
581
616
@@ -596,6 +631,7 @@ async def runInteraction(
596
631
exception_callbacks ,
597
632
func ,
598
633
* args ,
634
+ db_autocommit = db_autocommit ,
599
635
** kwargs
600
636
)
601
637
@@ -609,7 +645,11 @@ async def runInteraction(
609
645
return cast (R , result )
610
646
611
647
async def runWithConnection (
612
- self , func : "Callable[..., R]" , * args : Any , ** kwargs : Any
648
+ self ,
649
+ func : "Callable[..., R]" ,
650
+ * args : Any ,
651
+ db_autocommit : bool = False ,
652
+ ** kwargs : Any
613
653
) -> R :
614
654
"""Wraps the .runWithConnection() method on the underlying db_pool.
615
655
@@ -618,6 +658,9 @@ async def runWithConnection(
618
658
database connection (twisted.enterprise.adbapi.Connection) as
619
659
its first argument, followed by `args` and `kwargs`.
620
660
args: positional args to pass to `func`
661
+ db_autocommit: Whether to run the function in "autocommit" mode,
662
+ i.e. outside of a transaction. This is useful for transaction
663
+ that are only a single query. Currently only affects postgres.
621
664
kwargs: named args to pass to `func`
622
665
623
666
Returns:
@@ -633,6 +676,13 @@ async def runWithConnection(
633
676
start_time = monotonic_time ()
634
677
635
678
def inner_func (conn , * args , ** kwargs ):
679
+ # We shouldn't be in a transaction. If we are then something
680
+ # somewhere hasn't committed after doing work. (This is likely only
681
+ # possible during startup, as `run*` will ensure changes are
682
+ # committed/rolled back before putting the connection back in the
683
+ # pool).
684
+ assert not self .engine .in_transaction (conn )
685
+
636
686
with LoggingContext ("runWithConnection" , parent_context ) as context :
637
687
sched_duration_sec = monotonic_time () - start_time
638
688
sql_scheduling_timer .observe (sched_duration_sec )
@@ -642,10 +692,17 @@ def inner_func(conn, *args, **kwargs):
642
692
logger .debug ("Reconnecting closed database connection" )
643
693
conn .reconnect ()
644
694
645
- db_conn = LoggingDatabaseConnection (
646
- conn , self .engine , "runWithConnection"
647
- )
648
- return func (db_conn , * args , ** kwargs )
695
+ try :
696
+ if db_autocommit :
697
+ self .engine .attempt_to_set_autocommit (conn , True )
698
+
699
+ db_conn = LoggingDatabaseConnection (
700
+ conn , self .engine , "runWithConnection"
701
+ )
702
+ return func (db_conn , * args , ** kwargs )
703
+ finally :
704
+ if db_autocommit :
705
+ self .engine .attempt_to_set_autocommit (conn , False )
649
706
650
707
return await make_deferred_yieldable (
651
708
self ._db_pool .runWithConnection (inner_func , * args , ** kwargs )
0 commit comments