@@ -145,26 +145,7 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
145
145
146
146
logger .Debug (ctx , "starting reconciliation" )
147
147
148
- // This tx holds a global lock, which prevents any other coderd replica from starting a reconciliation and
149
- // possibly getting an inconsistent view of the state.
150
- //
151
- // The lock MUST be held until ALL modifications have been effected.
152
- //
153
- // It is run with RepeatableRead isolation, so it's effectively snapshotting the data at the start of the tx.
154
- //
155
- // This is a read-only tx, so returning an error (i.e. causing a rollback) has no impact.
156
- err := c .store .InTx (func (db database.Store ) error {
157
- start := c .clock .Now ()
158
-
159
- // TODO: use TryAcquireLock here and bail out early.
160
- err := db .AcquireLock (ctx , database .LockIDReconcileTemplatePrebuilds )
161
- if err != nil {
162
- logger .Warn (ctx , "failed to acquire top-level reconciliation lock; likely running on another coderd replica" , slog .Error (err ))
163
- return nil
164
- }
165
-
166
- logger .Debug (ctx , "acquired top-level reconciliation lock" , slog .F ("acquire_wait_secs" , fmt .Sprintf ("%.4f" , c .clock .Since (start ).Seconds ())))
167
-
148
+ err := c .WithReconciliationLock (ctx , logger , func (ctx context.Context , db database.Store ) error {
168
149
state , err := c .SnapshotState (ctx , db )
169
150
if err != nil {
170
151
return xerrors .Errorf ("determine current state: %w" , err )
@@ -209,10 +190,6 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
209
190
}
210
191
211
192
return eg .Wait ()
212
- }, & database.TxOptions {
213
- Isolation : sql .LevelRepeatableRead ,
214
- ReadOnly : true ,
215
- TxIdentifier : "template_prebuilds" ,
216
193
})
217
194
if err != nil {
218
195
logger .Error (ctx , "failed to reconcile" , slog .Error (err ))
@@ -221,6 +198,35 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
221
198
return err
222
199
}
223
200
201
+ func (c * StoreReconciler ) WithReconciliationLock (ctx context.Context , logger slog.Logger , fn func (ctx context.Context , db database.Store ) error ) error {
202
+ // This tx holds a global lock, which prevents any other coderd replica from starting a reconciliation and
203
+ // possibly getting an inconsistent view of the state.
204
+ //
205
+ // The lock MUST be held until ALL modifications have been effected.
206
+ //
207
+ // It is run with RepeatableRead isolation, so it's effectively snapshotting the data at the start of the tx.
208
+ //
209
+ // This is a read-only tx, so returning an error (i.e. causing a rollback) has no impact.
210
+ return c .store .InTx (func (db database.Store ) error {
211
+ start := c .clock .Now ()
212
+
213
+ // TODO: use TryAcquireLock here and bail out early.
214
+ err := db .AcquireLock (ctx , database .LockIDReconcileTemplatePrebuilds )
215
+ if err != nil {
216
+ logger .Warn (ctx , "failed to acquire top-level reconciliation lock; likely running on another coderd replica" , slog .Error (err ))
217
+ return nil
218
+ }
219
+
220
+ logger .Debug (ctx , "acquired top-level reconciliation lock" , slog .F ("acquire_wait_secs" , fmt .Sprintf ("%.4f" , c .clock .Since (start ).Seconds ())))
221
+
222
+ return fn (ctx , db )
223
+ }, & database.TxOptions {
224
+ Isolation : sql .LevelRepeatableRead ,
225
+ ReadOnly : true ,
226
+ TxIdentifier : "template_prebuilds" ,
227
+ })
228
+ }
229
+
224
230
// SnapshotState determines the current state of prebuilds & the presets which define them.
225
231
// An application-level lock is used
226
232
func (c * StoreReconciler ) SnapshotState (ctx context.Context , store database.Store ) (* prebuilds.ReconciliationState , error ) {
0 commit comments