@@ -84,7 +84,10 @@ export interface RunOpsCapableClient {
8484 * per-call `tx` so they share one transaction (see `runInTransaction`).
8585 */
8686export interface RunOpsTransactionalClient extends RunOpsCapableClient {
87- $transaction : < R > ( fn : ( tx : RunOpsCapableClient ) => Promise < R > ) => Promise < R > ;
87+ $transaction : < R > (
88+ fn : ( tx : RunOpsCapableClient ) => Promise < R > ,
89+ options ?: { timeout ?: number ; maxWait ?: number ; isolationLevel ?: unknown }
90+ ) => Promise < R > ;
8891}
8992
9093/**
@@ -99,6 +102,8 @@ export type RunStoreSchemaVariant = "legacy" | "dedicated";
99102// (apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts) — keep the values in sync.
100103export const CONNECTED_RUNS_LIMIT = 5 ;
101104
105+ export const RUN_OPS_WRITE_TX_TIMEOUT_MS = 15_000 ;
106+
102107export type PostgresRunStoreOptions = {
103108 prisma : RunOpsCapableClient ;
104109 readOnlyPrisma : RunOpsCapableClient ;
@@ -661,18 +666,28 @@ export class PostgresRunStore implements RunStore {
661666 // (snapshot + completed-waitpoints, run + associated-waitpoint) which must commit together.
662667 #withOptionalTransaction< R > (
663668 tx : PrismaClientOrTransaction | undefined ,
664- fn : ( client : PrismaClientOrTransaction ) => Promise < R >
669+ fn : ( client : PrismaClientOrTransaction ) => Promise < R > ,
670+ options ?: { timeout ?: number ; maxWait ?: number }
665671 ) : Promise < R > {
666672 const alreadyInTransaction =
667673 tx !== undefined && typeof ( tx as { $transaction ?: unknown } ) . $transaction !== "function" ;
668674 if ( alreadyInTransaction ) {
669675 return fn ( tx ) ;
670676 }
671- return ( this . prisma as RunOpsTransactionalClient ) . $transaction ( ( t ) =>
672- fn ( t as unknown as PrismaClientOrTransaction )
677+ return ( this . prisma as RunOpsTransactionalClient ) . $transaction (
678+ ( t ) => fn ( t as unknown as PrismaClientOrTransaction ) ,
679+ options
673680 ) ;
674681 }
675682
683+ #writeClientWithoutTransaction(
684+ tx : PrismaClientOrTransaction | undefined
685+ ) : PrismaClientOrTransaction {
686+ const alreadyInTransaction =
687+ tx !== undefined && typeof ( tx as { $transaction ?: unknown } ) . $transaction !== "function" ;
688+ return ( alreadyInTransaction ? tx : this . prisma ) as PrismaClientOrTransaction ;
689+ }
690+
676691 async createRun (
677692 params : CreateRunInput ,
678693 tx ?: PrismaClientOrTransaction
@@ -694,19 +709,31 @@ export class PostgresRunStore implements RunStore {
694709 } ;
695710
696711 if ( this . schemaVariant === "dedicated" ) {
697- // The run + its associated RUN-type waitpoint are two writes here (the legacy branch below nests
698- // them). Commit them together so a crash / lagging read never leaves a run without its waitpoint.
699- return this . #withOptionalTransaction( tx , async ( c ) => {
700- const run = ( await c . taskRun . create ( {
712+ if ( ! params . associatedWaitpoint ) {
713+ const run = ( await this . #writeClientWithoutTransaction( tx ) . taskRun . create ( {
701714 data : {
702715 ...params . data ,
703716 executionSnapshots : { create : snapshotCreate } ,
704717 } ,
705718 } ) ) as TaskRun ;
719+ return { ...run , associatedWaitpoint : null } ;
720+ }
706721
707- const associatedWaitpoint = await this . #createAssociatedWaitpoint( c , run . id , params ) ;
708- return { ...run , associatedWaitpoint } ;
709- } ) ;
722+ return this . #withOptionalTransaction(
723+ tx ,
724+ async ( c ) => {
725+ const run = ( await c . taskRun . create ( {
726+ data : {
727+ ...params . data ,
728+ executionSnapshots : { create : snapshotCreate } ,
729+ } ,
730+ } ) ) as TaskRun ;
731+
732+ const associatedWaitpoint = await this . #createAssociatedWaitpoint( c , run . id , params ) ;
733+ return { ...run , associatedWaitpoint } ;
734+ } ,
735+ { timeout : RUN_OPS_WRITE_TX_TIMEOUT_MS }
736+ ) ;
710737 }
711738
712739 return client . taskRun . create ( {
@@ -784,15 +811,25 @@ export class PostgresRunStore implements RunStore {
784811 const client = tx ?? this . prisma ;
785812
786813 if ( this . schemaVariant === "dedicated" ) {
787- // Run + associated RUN-type waitpoint are two writes here; commit them together (see createRun).
788- return this . #withOptionalTransaction( tx , async ( c ) => {
789- const run = ( await c . taskRun . create ( {
814+ if ( ! params . associatedWaitpoint ) {
815+ const run = ( await this . #writeClientWithoutTransaction( tx ) . taskRun . create ( {
790816 data : { ...params . data } ,
791817 } ) ) as TaskRun ;
818+ return { ...run , associatedWaitpoint : null } ;
819+ }
792820
793- const associatedWaitpoint = await this . #createAssociatedWaitpoint( c , run . id , params ) ;
794- return { ...run , associatedWaitpoint } ;
795- } ) ;
821+ return this . #withOptionalTransaction(
822+ tx ,
823+ async ( c ) => {
824+ const run = ( await c . taskRun . create ( {
825+ data : { ...params . data } ,
826+ } ) ) as TaskRun ;
827+
828+ const associatedWaitpoint = await this . #createAssociatedWaitpoint( c , run . id , params ) ;
829+ return { ...run , associatedWaitpoint } ;
830+ } ,
831+ { timeout : RUN_OPS_WRITE_TX_TIMEOUT_MS }
832+ ) ;
796833 }
797834
798835 return client . taskRun . create ( {
0 commit comments