@@ -52,6 +52,15 @@ type lockHandle struct {
5252 owner string
5353}
5454
55+ // syncResult is the shared outcome of a single in-flight on-demand sync.
56+ // Every caller that dedups onto the same request blocks on done and then
57+ // reads err, so all waiters observe the same result reliably, including
58+ // early failures (e.g. lock rejection) before the sync goroutine starts.
59+ type syncResult struct {
60+ done chan struct {}
61+ err error
62+ }
63+
5564/*
5665BaseOnDemand tracks requests that can be an image/signature/sbom.
5766
@@ -60,7 +69,7 @@ process just the first one, also keep track of all background retrying routines.
6069*/
6170type BaseOnDemand struct {
6271 services []Service
63- // map[request]chan err
72+ // map[request]*syncResult for in-flight syncs; struct{}{} for background retries
6473 requestStore * sync.Map
6574 distributedLock distributedLockBackend
6675 distributedLockOwner string
@@ -220,43 +229,46 @@ func (onDemand *BaseOnDemand) runLockHeartbeat(ctx context.Context,
220229}
221230
222231func (onDemand * BaseOnDemand ) SyncImage (ctx context.Context , repo , reference string ) error {
223- return onDemand .runWithLock (ctx , "image" , repo , reference , func (syncResult chan error ) {
224- onDemand .syncImage (ctx , repo , reference , syncResult )
232+ return onDemand .runWithLock (ctx , "image" , repo , reference , func (result * syncResult ) {
233+ onDemand .syncImage (ctx , repo , reference , result )
225234 })
226235}
227236
228237func (onDemand * BaseOnDemand ) SyncReferrers (ctx context.Context , repo string ,
229238 subjectDigestStr string , referenceTypes []string ,
230239) error {
231- return onDemand .runWithLock (ctx , "referrers" , repo , subjectDigestStr , func (syncResult chan error ) {
232- onDemand .syncReferrers (ctx , repo , subjectDigestStr , referenceTypes , syncResult )
240+ return onDemand .runWithLock (ctx , "referrers" , repo , subjectDigestStr , func (result * syncResult ) {
241+ onDemand .syncReferrers (ctx , repo , subjectDigestStr , referenceTypes , result )
233242 })
234243}
235244
236245// runWithLock combines in-process dedup, distributed locking, and the
237246// heartbeat goroutine. The run closure executes the actual sync and is
238- // expected to deliver the result on syncResult and close it .
247+ // expected to set result.err and close result.done .
239248func (onDemand * BaseOnDemand ) runWithLock (ctx context.Context , kind , repo , reference string ,
240- run func (chan error ),
249+ run func (* syncResult ),
241250) error {
242251 req := request {repo : repo , reference : reference }
243- syncResult := make (chan error )
252+ result := & syncResult { done : make (chan struct {})}
244253
245- val , loaded := onDemand .requestStore .LoadOrStore (req , syncResult )
254+ val , loaded := onDemand .requestStore .LoadOrStore (req , result )
246255 if loaded {
247256 onDemand .log .Info ().Str ("repo" , repo ).Str ("reference" , reference ).Str ("kind" , kind ).
248- Msg ("on-demand sync already in flight on this replica, waiting on channel " )
257+ Msg ("on-demand sync already in flight on this replica, waiting on result " )
249258
250- existing , _ := val .(chan error )
259+ existing , _ := val .(* syncResult )
251260
252- return <- existing
261+ <- existing .done
262+
263+ return existing .err
253264 }
254265
255266 defer onDemand .requestStore .Delete (req )
256267
257268 handle , release , err := onDemand .acquireDistributedLock (ctx , kind , repo , reference )
258269 if err != nil {
259- close (syncResult )
270+ result .err = err
271+ close (result .done )
260272
261273 return err
262274 }
@@ -273,15 +285,17 @@ func (onDemand *BaseOnDemand) runWithLock(ctx context.Context, kind, repo, refer
273285 go onDemand .runLockHeartbeat (heartbeatCtx , handle .key , handle .owner , syncLockHeartbeatInterval )
274286 }
275287
276- go run (syncResult )
288+ go run (result )
289+
290+ <- result .done
277291
278- return <- syncResult
292+ return result . err
279293}
280294
281295func (onDemand * BaseOnDemand ) syncReferrers (ctx context.Context , repo , subjectDigestStr string ,
282- referenceTypes []string , syncResult chan error ,
296+ referenceTypes []string , result * syncResult ,
283297) {
284- defer close (syncResult )
298+ defer close (result . done )
285299
286300 var err error
287301
@@ -355,11 +369,11 @@ func (onDemand *BaseOnDemand) syncReferrers(ctx context.Context, repo, subjectDi
355369 }
356370 }
357371
358- syncResult <- err
372+ result . err = err
359373}
360374
361- func (onDemand * BaseOnDemand ) syncImage (ctx context.Context , repo , reference string , syncResult chan error ) {
362- defer close (syncResult )
375+ func (onDemand * BaseOnDemand ) syncImage (ctx context.Context , repo , reference string , result * syncResult ) {
376+ defer close (result . done )
363377
364378 var err error
365379
@@ -433,5 +447,5 @@ func (onDemand *BaseOnDemand) syncImage(ctx context.Context, repo, reference str
433447 }
434448 }
435449
436- syncResult <- err
450+ result . err = err
437451}
0 commit comments