Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Async refresh dry run in parallel with sync refresh #2849

Merged
merged 11 commits into from
Dec 2, 2024
5 changes: 3 additions & 2 deletions internal/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ type DialSettings struct {
RequestReason string

// New Auth library Options
AuthCredentials *auth.Credentials
EnableNewAuthLibrary bool
AuthCredentials *auth.Credentials
EnableNewAuthLibrary bool
EnableAsyncRefreshDryRun func()
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
}

// GetScopes returns the user-provided scopes, if set, or else falls back to the
Expand Down
21 changes: 21 additions & 0 deletions option/internaloption/internaloption.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,27 @@ func (w enableNewAuthLibrary) Apply(o *internal.DialSettings) {
o.EnableNewAuthLibrary = bool(w)
}

// EnableAsyncRefreshDryRun returns a ClientOption that specifies if libraries in this
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
// module should asynchronously refresh auth token in parallel to sync refresh
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
//
// errHandler function will be called when there is an error while refreshing
// the token asynchronously
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
//
// This is an experimental option and will be removed in the future
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
func EnableAsyncRefreshDryRun(errHandler func()) option.ClientOption {
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
return enableAsyncRefreshDryRun{
errHandler: errHandler,
}
}

type enableAsyncRefreshDryRun struct {
errHandler func()
}

func (w enableAsyncRefreshDryRun) Apply(o *internal.DialSettings) {
o.EnableAsyncRefreshDryRun = w.errHandler
}

// EmbeddableAdapter is a no-op option.ClientOption that allow libraries to
// create their own client options by embedding this type into their own
// client-specific option wrapper. See example for usage.
Expand Down
38 changes: 37 additions & 1 deletion transport/grpc/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,37 @@ func prepareDialOptsNewAuth(ds *internal.DialSettings) []grpc.DialOption {
return append(opts, ds.GRPCDialOpts...)
}

// dryRunAsync is a wrapper for oauth2.TokenSource that performs a sync refresh
// after an async refresh. Token generated by async refresh is not used
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
//
// This is an experimental feature and may be removed or changed in the future.
// It is a temporary struct to determine if the async refresh
// is working properly.
type dryRunAsync struct {
asyncTokenSource oauth2.TokenSource
syncTokenSource oauth2.TokenSource
errHandler func()
}

func newDryRunAsync(ts oauth2.TokenSource, errHandler func()) dryRunAsync {
tp := auth.NewCachedTokenProvider(oauth2adapt.TokenProviderFromTokenSource(ts), nil)
asyncTs := oauth2adapt.TokenSourceFromTokenProvider(tp)
return dryRunAsync{
syncTokenSource: ts,
asyncTokenSource: asyncTs,
errHandler: errHandler,
}
}

// Token returns a token or an error.
func (async dryRunAsync) Token() (*oauth2.Token, error) {
_, err := async.asyncTokenSource.Token()
if err != nil {
async.errHandler()
}
return async.syncTokenSource.Token()
}

func dial(ctx context.Context, insecure bool, o *internal.DialSettings) (*grpc.ClientConn, error) {
if o.HTTPClient != nil {
return nil, errors.New("unsupported HTTP client specified")
Expand Down Expand Up @@ -298,8 +329,13 @@ func dial(ctx context.Context, insecure bool, o *internal.DialSettings) (*grpc.C
if err != nil {
return nil, err
}

ts := creds.TokenSource
bhshkh marked this conversation as resolved.
Show resolved Hide resolved
if o.EnableAsyncRefreshDryRun != nil {
ts = newDryRunAsync(ts, o.EnableAsyncRefreshDryRun)
}
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(grpcTokenSource{
TokenSource: oauth.TokenSource{TokenSource: creds.TokenSource},
TokenSource: oauth.TokenSource{TokenSource: ts},
quotaProject: internal.GetQuotaProject(creds, o.QuotaProject),
requestReason: o.RequestReason,
}))
Expand Down