| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.blimp.auth; | |
| 6 | |
| 7 import android.content.Intent; | |
| 8 import android.os.Handler; | |
| 9 import android.os.Message; | |
| 10 | |
| 11 import org.chromium.base.ThreadUtils; | |
| 12 import org.chromium.base.VisibleForTesting; | |
| 13 | |
| 14 import java.util.Random; | |
| 15 | |
| 16 /** | |
| 17 * Wraps an existing {@link TokenSource} and adds exponential fallback retry sup
port to it. The | |
| 18 * underlying {@link TokenSource} will be queried and all calls will be proxied
except transient | |
| 19 * failures, which will cause a retry after a random exponentially increasing de
lay. | |
| 20 * | |
| 21 * Because callbacks to {@link TokenSource#Callback#onTokenUnavailable(boolean)}
with | |
| 22 * {@code isTransient} set to {@code true} will be captured here, this {@link To
kenSource} currently | |
| 23 * won't expose any transient errors to the caller. | |
| 24 */ | |
| 25 public class RetryingTokenSource extends Handler implements TokenSource, TokenSo
urce.Callback { | |
| 26 private static final int MSG_QUERY_TOKEN = 1; | |
| 27 private static final int BASE_BACKOFF_DELAY_MS = 500; | |
| 28 private static final int MAX_EXPONENT = 10; | |
| 29 | |
| 30 private static final Random sRandom = new Random(); | |
| 31 | |
| 32 /** The maximum number of times to attempt connection before failing. */ | |
| 33 @VisibleForTesting | |
| 34 public static final int MAX_NUMBER_OF_RETRIES = 8; | |
| 35 | |
| 36 private final TokenSource mTokenSource; | |
| 37 | |
| 38 private TokenSource.Callback mCallback; | |
| 39 private int mAttemptNumber; | |
| 40 | |
| 41 /** | |
| 42 * Creates a {@link RetryingTokenSource} that proxies most {@link TokenSourc
e} communication to | |
| 43 * {@code tokenSource}. | |
| 44 * @param tokenSource A {@link TokenSource} that does the actual underlying
token management. | |
| 45 */ | |
| 46 public RetryingTokenSource(TokenSource tokenSource) { | |
| 47 mTokenSource = tokenSource; | |
| 48 mTokenSource.setCallback(this); | |
| 49 } | |
| 50 | |
| 51 // TokenSource implementation. | |
| 52 @Override | |
| 53 public void destroy() { | |
| 54 ThreadUtils.assertOnUiThread(); | |
| 55 | |
| 56 mTokenSource.destroy(); | |
| 57 removeMessages(MSG_QUERY_TOKEN); | |
| 58 } | |
| 59 | |
| 60 @Override | |
| 61 public void setCallback(TokenSource.Callback callback) { | |
| 62 ThreadUtils.assertOnUiThread(); | |
| 63 | |
| 64 mCallback = callback; | |
| 65 } | |
| 66 | |
| 67 @Override | |
| 68 public void getToken() { | |
| 69 ThreadUtils.assertOnUiThread(); | |
| 70 | |
| 71 // Reset all exponential backoff states. | |
| 72 removeMessages(MSG_QUERY_TOKEN); | |
| 73 mAttemptNumber = 0; | |
| 74 | |
| 75 // Start the TokenSource#getToken() exponential backoff calls. | |
| 76 getTokenWithBackoff(); | |
| 77 } | |
| 78 | |
| 79 @Override | |
| 80 public boolean isRetrievingToken() { | |
| 81 ThreadUtils.assertOnUiThread(); | |
| 82 | |
| 83 return mTokenSource.isRetrievingToken() || hasMessages(MSG_QUERY_TOKEN); | |
| 84 } | |
| 85 | |
| 86 @Override | |
| 87 public int tokenIsInvalid(String token) { | |
| 88 ThreadUtils.assertOnUiThread(); | |
| 89 | |
| 90 return mTokenSource.tokenIsInvalid(token); | |
| 91 } | |
| 92 | |
| 93 @Override | |
| 94 public void onAccountSelected(Intent data) { | |
| 95 ThreadUtils.assertOnUiThread(); | |
| 96 | |
| 97 mTokenSource.onAccountSelected(data); | |
| 98 } | |
| 99 | |
| 100 // TokenSource.Callback implementation. | |
| 101 @Override | |
| 102 public void onTokenReceived(String token) { | |
| 103 mCallback.onTokenReceived(token); | |
| 104 } | |
| 105 | |
| 106 @Override | |
| 107 public void onTokenUnavailable(boolean isTransient) { | |
| 108 if (isTransient && mAttemptNumber < MAX_NUMBER_OF_RETRIES) { | |
| 109 getTokenWithBackoff(); | |
| 110 } else { | |
| 111 mCallback.onTokenUnavailable(false); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 @Override | |
| 116 public void onNeedsAccountToBeSelected(Intent intent) { | |
| 117 mCallback.onNeedsAccountToBeSelected(intent); | |
| 118 } | |
| 119 | |
| 120 // Handler overrides. | |
| 121 @Override | |
| 122 public void handleMessage(Message msg) { | |
| 123 if (msg.what != MSG_QUERY_TOKEN) return; | |
| 124 mTokenSource.getToken(); | |
| 125 } | |
| 126 | |
| 127 /** | |
| 128 * @param delay The suggested time (in ms) to wait before attempting to quer
y for the token | |
| 129 * again. | |
| 130 * @return The actual time (in ms) to wait before attempting to query f
or a token again. | |
| 131 */ | |
| 132 @VisibleForTesting | |
| 133 protected int finalizeRetryDelay(int delay) { | |
| 134 return delay; | |
| 135 } | |
| 136 | |
| 137 private void getTokenWithBackoff() { | |
| 138 int delayMs = 0; | |
| 139 | |
| 140 // For the first attempt, don't delay. | |
| 141 if (mAttemptNumber > 0) { | |
| 142 // Find a random value between the previous and current max delay va
lues. | |
| 143 int prevMaxDelay = getMaxDelay(mAttemptNumber - 1); | |
| 144 int currMaxDelay = getMaxDelay(mAttemptNumber); | |
| 145 | |
| 146 assert currMaxDelay > prevMaxDelay; | |
| 147 int delayWindow = currMaxDelay - prevMaxDelay; | |
| 148 | |
| 149 delayMs = sRandom.nextInt(delayWindow) + prevMaxDelay; | |
| 150 } | |
| 151 | |
| 152 sendEmptyMessageDelayed(MSG_QUERY_TOKEN, finalizeRetryDelay(delayMs)); | |
| 153 mAttemptNumber++; | |
| 154 } | |
| 155 | |
| 156 /** | |
| 157 * Helper method for calculating the max delay for any given attempt. | |
| 158 * @param attempt The current attempt at calling {@link TokenSource#getToken
()} on the internal | |
| 159 * {@link TokenSource} | |
| 160 * @return The maximum possible delay (in ms) to use for this attempt
. | |
| 161 */ | |
| 162 private static int getMaxDelay(int attempt) { | |
| 163 // For the first attempt, use no delay. | |
| 164 if (attempt == 0) return 0; | |
| 165 | |
| 166 // Figure out the delay multiplier 2^(retry attempt number). | |
| 167 int multiplier = 1 << Math.min(MAX_EXPONENT, attempt - 1); | |
| 168 return multiplier * BASE_BACKOFF_DELAY_MS; | |
| 169 } | |
| 170 } | |
| OLD | NEW |