最近我一直在苦恼这个问题。
背景:
- 使用oauth2client库来管理用户的令牌。这些令牌会被用来执行各种后台任务,而且还会同时周期性地运行。
- 每次要为一个用户运行其中之一任务时,我们都会从存储中获取Credentials对象,并在过期时间快到的时候进行刷新。否则就重复使用当前访问令牌。
- 有时候,同一个用户的多个任务会同时运行。
- 一段时间内,一切都很正常,令牌会被正常刷新。
- 间歇性地,似乎突然出现了一个"invalid_grant"错误,并且完全废除了存储中的刷新令牌。(关于什么时候/如何/为什么会出现这种情况是我希望通过这个问题弄清楚的。)
搜索一下,就会发现有很多与此问题有关的线程/报告。但是迄今为止,我找到的所有内容都不适用于我们的情况。我会尝试列出我迄今为止研究过的内容:
- 用户已撤销权限
这是最明显的,并且是最有文献记录的,也很容易重现的问题。但不幸地是,在我们的情况下,我们的用户(或者在测试过程中的我们自己)根本没有撤销权限。
- 刷新一个"旧"访问令牌
起初我认为每个用户同时只能有一个有效的访问令牌。 但这是错误的,并且在OAuth2 Playground上进行了验证。
每个用户每个客户端最多有25个活动访问令牌的限制。一旦达到该限制,即使它们的到期日期还没有过期,旧的访问令牌也会被静默地废止。
对于我们来说,这是死胡同,因为我们的问题发生在刷新时,而不是使用最旧的访问令牌。而且这个限制只影响访问令牌,而不是刷新令牌。
- 在短时间内请求刷新太多次
一点都没有文献记录。只是随便提到,没有参考文献。尝试模拟在7秒内刷新25次,但是所有步骤都进行得很顺利。但由于没有参考文献,这只是一个盲目的猜测。而且我们的后台任务只能最多每几分钟运行一次10个任务。我们继续寻找原因。
- 并发刷新导致竞争条件,无法确定哪个令牌会成功
我已经在这里提出了一个问题,但这不是情况。通过同时运行两个任务来测试在AppEngine上。
我已经快要绝望地试图解决这个问题。我们无法轻易地重现它是一种痛苦。我真的很想知道可能导致这种问题的见解,但我错过了什么?
这是我们的刷新代码:
def refresh_oauth_credentials(user, credentials, force=False):
if not credentials:
return None
logging.debug(credentials.token_expiry)
do_refresh = credentials._expires_in() < 300
if force or do_refresh:
h = httplib2.Http()
try:
logging.debug('Refreshing %s\'s oauth2 credentials...' % user.email)
credentials.refresh(h)
except AccessTokenRefreshError:
logging.warning('Failed to refresh.')
return None
return credentials