@@ -227,15 +227,26 @@ public AccessToken generateToken(CredentialAccessBoundary accessBoundary)
227227 */
228228 @ VisibleForTesting
229229 void refreshCredentialsIfRequired () throws IOException {
230- RefreshType refreshType = determineRefreshType ();
230+ RefreshType refreshType ;
231+ RefreshTask currentRefreshTask ;
232+
233+ // Synchronize both the decision to refresh and the task creation to prevent a race
234+ // condition. Without this, multiple threads might concurrently determine a refresh is
235+ // needed (e.g., in ASYNC mode) and return ASYNC. The first thread would start the refresh
236+ // task. If that task completes quickly and clears the `refreshTask` reference, a subsequent
237+ // thread that also determined a refresh was needed would then see `refreshTask` as null
238+ // and incorrectly start a second, redundant refresh task.
239+ synchronized (refreshLock ) {
240+ refreshType = determineRefreshType ();
231241
232- if (refreshType == RefreshType .NONE ) {
233- // No refresh needed, token is still valid.
234- return ;
235- }
242+ if (refreshType == RefreshType .NONE ) {
243+ // No refresh needed, token is still valid.
244+ return ;
245+ }
236246
237- // If a refresh is required, create or retrieve the refresh task.
238- RefreshTask currentRefreshTask = getOrCreateRefreshTask ();
247+ // If a refresh is required, create or retrieve the refresh task.
248+ currentRefreshTask = getOrCreateRefreshTask ();
249+ }
239250
240251 // Handle the refresh based on the determined refresh type.
241252 switch (refreshType ) {
@@ -283,16 +294,14 @@ void refreshCredentialsIfRequired() throws IOException {
283294 }
284295 }
285296
297+ // Assumes the caller holds refreshLock.
286298 private RefreshType determineRefreshType () {
287- AccessToken intermediateAccessToken ;
288- synchronized (refreshLock ) {
289- if (intermediateCredentials == null
290- || intermediateCredentials .intermediateAccessToken == null ) {
291- // A blocking refresh is needed if the intermediate access token doesn't exist.
292- return RefreshType .BLOCKING ;
293- }
294- intermediateAccessToken = intermediateCredentials .intermediateAccessToken ;
299+ if (intermediateCredentials == null
300+ || intermediateCredentials .intermediateAccessToken == null ) {
301+ // A blocking refresh is needed if the intermediate access token doesn't exist.
302+ return RefreshType .BLOCKING ;
295303 }
304+ AccessToken intermediateAccessToken = intermediateCredentials .intermediateAccessToken ;
296305
297306 Date expirationTime = intermediateAccessToken .getExpirationTime ();
298307 if (expirationTime == null ) {
@@ -322,23 +331,22 @@ private RefreshType determineRefreshType() {
322331 * responsibility of the caller to execute it. The task will clear the single flight slot upon
323332 * completion.
324333 */
334+ // Assumes the caller holds refreshLock.
325335 private RefreshTask getOrCreateRefreshTask () {
326- synchronized (refreshLock ) {
327- if (refreshTask != null ) {
328- // An existing refresh task is already in progress. Return a NEW RefreshTask instance with
329- // the existing task, but set isNew to false. This indicates to the caller that a new
330- // refresh task was NOT created.
331- return new RefreshTask (refreshTask .task , false );
332- }
336+ if (refreshTask != null ) {
337+ // An existing refresh task is already in progress. Return a NEW RefreshTask instance with
338+ // the existing task, but set isNew to false. This indicates to the caller that a new
339+ // refresh task was NOT created.
340+ return new RefreshTask (refreshTask .task , false );
341+ }
333342
334- final ListenableFutureTask <IntermediateCredentials > task =
335- ListenableFutureTask .create (this ::fetchIntermediateCredentials );
343+ final ListenableFutureTask <IntermediateCredentials > task =
344+ ListenableFutureTask .create (this ::fetchIntermediateCredentials );
336345
337- // Store the new refresh task in the refreshTask field before returning. This ensures that
338- // subsequent calls to this method will return the existing task while it's still in progress.
339- refreshTask = new RefreshTask (task , true );
340- return refreshTask ;
341- }
346+ // Store the new refresh task in the refreshTask field before returning. This ensures that
347+ // subsequent calls to this method will return the existing task while it's still in progress.
348+ refreshTask = new RefreshTask (task , true );
349+ return refreshTask ;
342350 }
343351
344352 /**
0 commit comments