-
Notifications
You must be signed in to change notification settings - Fork 314
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: Add optional non blocking refresh for sync auth code #1368
Conversation
6dffc03
to
b244471
Compare
google/auth/credentials.py
Outdated
if not self.expiry: | ||
return TokenState.FRESH | ||
|
||
refresh_window = _helpers.utcnow() >= (self.expiry - _helpers.REFRESH_THRESHOLD) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename this variable to something like is_fresh?
google/auth/credentials.py
Outdated
else: | ||
self.refresh(request) | ||
|
||
if self.token_state == TokenState.INVALID: | ||
self.refresh(request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiple requests may end up doing this refresh right?
Can we have a lock on this sync refresh?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is using a bounded queue which should reduce duplicate work. There is a risk that there is a small amount of duplicate work, if two threads queue work at the same time. The queue itself is thread safe + has locks.
I believe using the queue to reduce duplicate work is acceptable, and that multiple threads will perform the refresh occasionally.
There should be no data corruption due to this
d99a734
to
aab77bb
Compare
google/auth/credentials.py
Outdated
def with_non_blocking_refresh(self): | ||
self._use_non_blocking_refresh = True | ||
|
||
def get_background_refresh_error(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer to remove this from the public API:
- it is not relevant without the feature flag
- it isn't clear when you'd want to call it
- it's hard to implement safely
google/auth/credentials.py
Outdated
class TokenState(Enum): | ||
""" | ||
Tracks the state of a token. | ||
FRESH: The token is valid. It is not expired or close to expired, or the token has no expiry. To make it mutually exclusive to STALE. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry about this: the "To make it mutually exclusive to STALE" part wasn't meant to be part of the comment.
google/auth/_refresh_worker.py
Outdated
# rety this error. | ||
err, self._worker._error_info = self._worker._error_info, None | ||
|
||
raise e.RefreshError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this has a kind of weird 'every 2nd call fails' pattern that doesn't seem quite right.
I would suggest we try to background refresh once, log and record the error, then do foreground refreshes from then on until we get a new token.
You might return false
from start_refresh
to cause the caller to call refresh
on their own thread. The caller would be responsible for calling "clear error" any time they have a good token from refresh()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good!
google/auth/_refresh_worker.py
Outdated
self._worker = RefreshThread(cred=cred, request=request) | ||
self._worker.start() | ||
|
||
def has_error(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove this and get_error
from the public API since they are subject to the lock and difficult to reason about outside of this class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay that sounds good!
google/auth/credentials.py
Outdated
self.refresh(request) | ||
|
||
def _non_blocking_refresh(self, request): | ||
if ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
following the comment above, this would become:
if self.token_state == TokenState.FRESH:
return
need_refresh = True
if self.token_state == TokenState.STALE:
need_refresh = not self.refresh_worker.start_refresh(self, request)
if need_refresh:
self.refresh(request)
self._refresh_worker.clear_error()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! I kept the INVALID
case, should a refresh was never called in the STALE state.
self._worker = None | ||
self._lock = threading.Lock() # protects access to worker threads. | ||
|
||
def start_refresh(self, cred, request): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is request
immutable or can we create a copy here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you expand on why copy it? I don't think it is necessary, as the request should not be re-used in other places.
I think copying the request would be a breaking change.
I'd have to do some work to understand the implication of deep copying a request object.
No description provided.