@@ -35,6 +35,7 @@ import (
3535 "go.podman.io/image/v5/types"
3636 "go.podman.io/storage/pkg/fileutils"
3737 "go.podman.io/storage/pkg/homedir"
38+ "golang.org/x/sync/semaphore"
3839)
3940
4041const (
@@ -86,8 +87,19 @@ type extensionSignatureList struct {
8687 Signatures []extensionSignature `json:"signatures"`
8788}
8889
89- // bearerToken records a cached token we can use to authenticate.
90+ // bearerToken records a cached token we can use to authenticate, or a pending process to obtain one.
91+ //
92+ // The goroutine obtaining the token holds lock to block concurrent token requests, and fills the structure (err and possibly the other fields)
93+ // before releasing the lock.
94+ // Other goroutines obtain lock to block on the token request, if any; and then inspect err to see if the token is usable.
95+ // If it is not, they try to get a new one.
9096type bearerToken struct {
97+ // lock is held while obtaining the token. Potentially nested inside dockerClient.tokenCacheLock.
98+ // This is a counting semaphore only because we need a cancellable lock operation.
99+ lock * semaphore.Weighted
100+
101+ // The following fields can only be accessed with lock held.
102+ err error // nil if the token was successfully obtained (but may be expired); an error if the next lock holder _must_ obtain a new token.
91103 token string
92104 expirationTime time.Time
93105}
@@ -756,31 +768,53 @@ func (c *dockerClient) obtainBearerToken(ctx context.Context, challenge challeng
756768 scopes = append (scopes , * extraScope )
757769 }
758770
759- var token * bearerToken
760- var inCache bool
761- func () { // A scope for defer
771+ token , newEntry , err := func () (* bearerToken , bool , error ) { // A scope for defer
762772 c .tokenCacheLock .Lock ()
763773 defer c .tokenCacheLock .Unlock ()
764- token , inCache = c .tokenCache [cacheKey ]
765- }()
766- if ! inCache || time .Now ().After (token .expirationTime ) {
767- token = & bearerToken {}
768-
769- var err error
770- if c .auth .IdentityToken != "" {
771- err = c .getBearerTokenOAuth2 (ctx , token , challenge , scopes )
774+ token , ok := c .tokenCache [cacheKey ]
775+ if ok {
776+ return token , false , nil
772777 } else {
773- err = c .getBearerToken (ctx , token , challenge , scopes )
778+ token = & bearerToken {
779+ lock : semaphore .NewWeighted (1 ),
780+ }
781+ // If this is a new *bearerToken, lock the entry before adding it to the cache, so that any other goroutine that finds
782+ // this entry blocks until we obtain the token for the first time, and does not see an empty object
783+ // (and does not try to obtain the token itself when we are going to do so).
784+ if err := token .lock .Acquire (ctx , 1 ); err != nil {
785+ // We do not block on this Acquire, so we don’t really expect to fail here — but if ctx is canceled,
786+ // there is no point in trying to continue anyway.
787+ return nil , false , err
788+ }
789+ c .tokenCache [cacheKey ] = token
790+ return token , true , nil
774791 }
775- if err != nil {
792+ }()
793+ if err != nil {
794+ return "" , err
795+ }
796+ if ! newEntry {
797+ // If this is an existing *bearerToken, obtain the lock only after releasing c.tokenCacheLock,
798+ // so that users of other cacheKey values are not blocked for the whole duration of our HTTP roundtrip.
799+ if err := token .lock .Acquire (ctx , 1 ); err != nil {
776800 return "" , err
777801 }
802+ }
778803
779- func () { // A scope for defer
780- c .tokenCacheLock .Lock ()
781- defer c .tokenCacheLock .Unlock ()
782- c .tokenCache [cacheKey ] = token
783- }()
804+ defer token .lock .Release (1 )
805+
806+ if ! newEntry && token .err == nil && ! time .Now ().After (token .expirationTime ) {
807+ return token .token , nil // We have a usable token already.
808+ }
809+
810+ if c .auth .IdentityToken != "" {
811+ err = c .getBearerTokenOAuth2 (ctx , token , challenge , scopes )
812+ } else {
813+ err = c .getBearerToken (ctx , token , challenge , scopes )
814+ }
815+ token .err = err
816+ if token .err != nil {
817+ return "" , token .err
784818 }
785819 return token .token , nil
786820}
0 commit comments