AbdulElahGwaith's picture
Upload folder using huggingface_hub
7b715bc verified
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Logging;
using Google.Apis.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
namespace Google.Apis.Auth.OAuth2
{
/// <summary>
/// Encapsulation of token refresh behaviour. This isn't entirely how we'd design the code now (in terms of the
/// callback in particular) but it fits in with the exposed API surface of ServiceCredential and UserCredential.
/// </summary>
internal sealed class TokenRefreshManager
{
// Immutable state
private readonly object _lock = new object();
private readonly IClock _clock;
private readonly ILogger _logger;
private readonly Func<CancellationToken, Task<bool>> _refreshAction;
// Mutable state, guarded with _lock.
private TokenResponse _token;
private Task<TokenResponse> _refreshTask;
/// <summary>
/// Creates a manager which executes the given refresh action when required.
/// </summary>
/// <param name="refreshAction">The refresh action which will populate the Token property when successful.</param>
/// <param name="clock">The clock to consult for timeouts.</param>
/// <param name="logger">The logger to use to record refreshes.</param>
internal TokenRefreshManager(Func<CancellationToken, Task<bool>> refreshAction, IClock clock, ILogger logger)
{
_refreshAction = refreshAction;
_clock = clock;
_logger = logger;
}
internal TokenResponse Token
{
get
{
lock (_lock)
{
return _token;
}
}
// The token may be set due to operations other than GetAccessTokenForRequestAsync, but we don't need to
// null out _refreshTask if so.
set
{
lock (_lock)
{
_token = value;
}
}
}
internal async Task<string> GetAccessTokenForRequestAsync(CancellationToken cancellationToken)
{
Task<TokenResponse> refreshTask;
lock (_lock)
{
// If current token doesn't need refreshing, then return it.
if (_token != null && !_token.ShouldBeRefreshed(_clock))
{
return _token.AccessToken;
}
// Token refresh required, so start a task if not already started
if (_refreshTask == null)
{
// Task.Run is required if the refresh completes synchronously,
// otherwise _refreshTask is updated in an incorrect order.
// And Task.Run also means it can be run here in the lock.
_refreshTask = Task.Run(RefreshTokenAsync);
// Let's make sure that exceptions in _refreshTask are always observed.
// Note that we don't keep a reference to this new task as we don't really
// care about the errors, and we want calling code explicitly awaiting on _refreshTask
// to actually fail if there's an error. We just schedule it to run and that's enough for
// avoiding exception observavility issues.
_refreshTask.ContinueWith(LogException, TaskContinuationOptions.OnlyOnFaulted);
}
// If current token is still valid, then return it.
// The refresh above was pre-emptive.
if (_token != null && _token.MayBeUsed(_clock))
{
return _token.AccessToken;
}
refreshTask = _refreshTask;
async Task LogException(Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Debug($"An error occured on a background token refresh task.{Environment.NewLine}{ex}");
}
}
}
refreshTask = refreshTask.WithCancellationToken(cancellationToken);
// Note that strictly speaking, the token returned here may already need redreshing,
// be invalid or be really expired.
// This may happen for tokens that are short lived enough, or in systems with significant load
// where maybe the token itself was obtained quickly but the task could not acquire a thread fast enough
// in which to resume.
// We don't retry as the conditions under which this may happen are not inmediately recoverable and possibly rare,
// and the token will be refreshed again on a subsequent token request.
// Also, note that the token is unusable only if it's really expired, if it needs refreshing or has become invalid
// it still may be used if fast enough.
return (await refreshTask.ConfigureAwait(false)).AccessToken;
}
internal static readonly TimeSpan[] RefreshTimeouts = new[] { TimeSpan.FromSeconds(12), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5) };
private async Task<TokenResponse> RefreshTokenAsync()
{
_logger.Debug("Token has expired, trying to get a new one.");
try
{
List<string> errors = null;
foreach (var timeout in RefreshTimeouts)
{
var token = new CancellationTokenSource(timeout).Token;
try
{
var success = await _refreshAction(token).ConfigureAwait(false);
if (success)
{
_logger.Info("New access token was received successfully");
return Token;
}
else
{
// If unsuccessful, but didn't timeout, then retry if all retries haven't been exhausted.
(errors = errors ?? new List<string>()).Add("refresh error");
}
}
catch (OperationCanceledException)
{
// Do nothing, attempt another refresh if all retries haven't been exhausted.
_logger.Debug("Token refresh time-out after {0} seconds", (int)timeout.TotalSeconds);
(errors = errors ?? new List<string>()).Add("timeout");
}
}
throw new InvalidOperationException($"The access token has expired and could not be refreshed. Errors: {string.Join(", ", errors)}");
}
finally
{
// If the task completed successfully, Token will have been set.
// Otherwise, we'll want to start a new refresh task next time we're asked for a token.
lock (_lock)
{
_refreshTask = null;
}
}
}
}
}