feat: Add optional non blocking refresh for sync auth code#1368
feat: Add optional non blocking refresh for sync auth code#1368clundin25 merged 34 commits intogoogleapis:mainfrom
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.
rename this variable to something like is_fresh?
google/auth/credentials.py
Outdated
| self.refresh(request) | ||
|
|
||
| if self.token_state == TokenState.INVALID: | ||
| self.refresh(request) |
There was a problem hiding this comment.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
is request immutable or can we create a copy here?
There was a problem hiding this comment.
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.