@@ -25,7 +25,7 @@ import com.digitalasset.canton.resource.DbStorage.Implicits.BuilderChain.toSQLAc
2525import com .digitalasset .canton .topology .PartyId
2626import com .digitalasset .canton .tracing .TraceContext
2727import org .lfdecentralizedtrust .splice .store .events .SpliceCreatedEvent
28- import slick .dbio .DBIOAction
28+ import slick .dbio .{ DBIOAction , Effect , NoStream }
2929import slick .jdbc .canton .ActionBasedSQLInterpolation .Implicits .actionBasedSQLInterpolationCanton
3030import slick .jdbc .{GetResult , JdbcProfile }
3131
@@ -158,12 +158,32 @@ class AcsSnapshotStore(
158158 join creates_to_insert on inserted_rows.create_id = creates_to_insert.row_id
159159 having min(inserted_rows.row_id) is not null;
160160 """ ).toActionBuilder.asUpdate
161- storage.update(statement, " insertNewSnapshot" )
161+ storage.update(withExclusiveSnapshotDataLock( statement) , " insertNewSnapshot" )
162162 }.andThen { _ =>
163163 AcsSnapshotStore .PreventConcurrentSnapshotsSemaphore .release()
164164 }
165165 }
166166
167+ /** Wraps the given action in a transaction that holds an exclusive lock on the acs_snapshot_data table.
168+ *
169+ * Note: The acs_snapshot_data table must not have interleaved rows from two different acs snapshots.
170+ * In rare cases, it can happen that the application crashes while writing a snapshot, then
171+ * restarts and starts writing a different snapshot while the previous statement is still running.
172+ * The exclusive lock prevents this.
173+ * Once obtained, a lock is held for the remainder of the current transaction.
174+ * In case the application crashes while holding the lock, the server should close the connection
175+ * and abort the transaction as soon as it detects a disconnect.
176+ * See [[com.digitalasset.canton.platform.store.backend.postgresql.PostgresDataSourceConfig ]] for our connection keepalive settings.
177+ * With default settings, the server should detect a dead connection within ~15sec.
178+ */
179+ private def withExclusiveSnapshotDataLock [T , E <: Effect ](
180+ action : DBIOAction [T , NoStream , E ]
181+ ): DBIOAction [T , NoStream , E & Effect .Write & Effect .Transactional ] =
182+ (for {
183+ _ <- sqlu " LOCK TABLE acs_snapshot_data IN EXCLUSIVE MODE "
184+ result <- action
185+ } yield result).transactionally
186+
167187 def deleteSnapshot (
168188 snapshot : AcsSnapshot
169189 )(implicit
0 commit comments