Page MenuHomePhabricator

pywikibot OAuth should properly handle expired tokens, instead of endless loop.
Closed, ResolvedPublic

Description

When the the tokens expire or invalidated for any reason, an exception should be raised instead of this endless loop:

[2016-02-04 06:01:18,875: INFO/Worker-10] Sleeping for 5.5 seconds, 2016-02-04 06:01:18
[2016-02-04 06:01:23,762: WARNING/Worker-6] API error badtoken: Invalid token
[2016-02-04 06:01:23,763: VERBOSE/Worker-6] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
[2016-02-04 06:01:23,828: INFO/Worker-6] Sleeping for 9.7 seconds, 2016-02-04 06:01:23
[2016-02-04 06:01:25,667: WARNING/Worker-15] API error badtoken: Invalid token
[2016-02-04 06:01:25,668: VERBOSE/Worker-15] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
[2016-02-04 06:01:25,725: VERBOSE/Worker-15] Sleeping for 2.5 seconds, 2016-02-04 06:01:25
[2016-02-04 06:01:28,709: WARNING/Worker-10] API error badtoken: Invalid token
[2016-02-04 06:01:28,709: VERBOSE/Worker-10] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
[2016-02-04 06:01:28,784: INFO/Worker-10] Sleeping for 5.6 seconds, 2016-02-04 06:01:28
[2016-02-04 06:01:30,087: WARNING/Worker-2] API error badtoken: Invalid token
[2016-02-04 06:01:30,087: VERBOSE/Worker-2] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
[2016-02-04 06:01:33,719: WARNING/Worker-6] API error badtoken: Invalid token
[2016-02-04 06:01:33,720: VERBOSE/Worker-6] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
[2016-02-04 06:01:33,722: VERBOSE/Worker-6] Found 1 commons:commons processes running, including this one.
[2016-02-04 06:01:33,795: INFO/Worker-6] Sleeping for 9.8 seconds, 2016-02-04 06:01:33
[2016-02-04 06:01:34,726: WARNING/Worker-15] API error badtoken: Invalid token
[2016-02-04 06:01:34,727: VERBOSE/Worker-15] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
[2016-02-04 06:01:34,787: INFO/Worker-15] Sleeping for 3.5 seconds, 2016-02-04 06:01:34

Event Timeline

zhuyifei1999 raised the priority of this task from to Needs Triage.
zhuyifei1999 updated the task description. (Show Details)

Why do you think this has something to do with OAuth? The log just shows pywikibot retrying after the API returns a 'badtoken' response, as it's supposed to. I'm not sure why it's continuously failing, but that might have something to do with the multithreaded nature of your bot (two threads get a token, one uses them, the other gets a badtoken response).

Why do you think this has something to do with OAuth? The log just shows pywikibot retrying after the API returns a 'badtoken' response, as it's supposed to. I'm not sure why it's continuously failing, but that might have something to do with the multithreaded nature of your bot (two threads get a token, one uses them, the other gets a badtoken response).

Well, you cannot get an valid crsf token from an invalid oauth token. And no, multiprocessing should be irrelevant, as none of the upload attempts succeeded (the token being consumed). Even when only one process working, the same issues happen as well:

1[2016-02-03 22:48:14,739: WARNING/Worker-10] video:17801kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.064966%
2[2016-02-03 22:48:16,859: WARNING/Worker-10] -1: Configuring Pywikibot...
3[2016-02-03 22:48:16,871: VERBOSE/Worker-10] Found 1 commons:commons processes running, including this one.
4[2016-02-03 22:48:16,884: INFO/Worker-10] Resetting dropped connection: commons.wikimedia.org
5[2016-02-03 22:48:16,994: WARNING/Worker-10] -1: Uploading...
6[2016-02-03 22:48:17,100: WARNING/Worker-10] -1: Uploading...
7[2016-02-03 22:48:17,218: INFO/Worker-10] Sleeping for 9.7 seconds, 2016-02-03 22:48:17
8[2016-02-03 22:48:30,963: WARNING/Worker-10] API error badtoken: Invalid token
9[2016-02-03 22:48:30,963: VERBOSE/Worker-10] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
10[2016-02-03 22:48:31,047: INFO/Worker-10] Sleeping for 5.8 seconds, 2016-02-03 22:48:31
11[2016-02-03 22:48:41,128: WARNING/Worker-10] API error badtoken: Invalid token
12[2016-02-03 22:48:41,131: VERBOSE/Worker-10] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
13[2016-02-03 22:48:41,199: INFO/Worker-10] Sleeping for 5.7 seconds, 2016-02-03 22:48:41
14[2016-02-03 22:48:50,963: WARNING/Worker-10] API error badtoken: Invalid token
15[2016-02-03 22:48:50,964: VERBOSE/Worker-10] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
16[2016-02-03 22:48:51,022: INFO/Worker-10] Sleeping for 5.9 seconds, 2016-02-03 22:48:51
17[2016-02-03 22:49:00,407: WARNING/Worker-10] API error badtoken: Invalid token
18[2016-02-03 22:49:00,408: VERBOSE/Worker-10] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
19[2016-02-03 22:49:00,468: INFO/Worker-10] Sleeping for 6.4 seconds, 2016-02-03 22:49:00
20[2016-02-03 22:49:11,783: WARNING/Worker-10] API error badtoken: Invalid token
21[2016-02-03 22:49:11,784: VERBOSE/Worker-10] Bad token error for Yann. Tokens for "csrf" used in request; invalidated them.
22[2016-02-03 22:49:11,864: INFO/Worker-10] Sleeping for 5.0 seconds, 2016-02-03 22:49:11
23[2016-02-03 22:49:21,202: WARNING/Worker-10] API error badtoken: Invalid token

All upload attempts after this failed. However, it used to work anyways:

[2016-01-28 18:50:27,038: WARNING/Worker-14] -1: Configuring Pywikibot...
[2016-01-28 18:50:27,061: VERBOSE/Worker-14] Found 1 commons:commons processes running, including this one.
[2016-01-28 18:50:27,120: INFO/Worker-14] Starting new HTTPS connection (1): commons.wikimedia.org
[2016-01-28 18:50:27,358: WARNING/Worker-14] -1: Uploading...
[2016-01-28 18:50:27,374: WARNING/Worker-14] -1: Uploading...
[2016-01-28 18:50:27,905: INFO/Worker-14] Sleeping for 9.2 seconds, 2016-01-28 18:50:27
[2016-01-28 18:51:11,702: INFO/Worker-14] Upload successful.
[2016-01-28 18:51:11,758: WARNING/Worker-14] 100: Upload success!
[2016-01-28 18:51:11,762: WARNING/Worker-14] -1: Cleaning up...
[2016-01-28 18:51:11,875: WARNING/Worker-14] 100: Done!

And two processes uploading at the same time had happened and succeeded, though I can't find the entries in these 200M log

In case it's confusing, this in not really multithreading, but multiprocessing via os.fork():

110:41:37 0 ✓ zhuyifei1999@encoding01: ~$ sudo service celeryd status
2● celeryd.service - LSB: celery task worker daemon
3 Loaded: loaded (/etc/init.d/celeryd)
4 Active: active (running) since Thu 2016-02-04 10:41:37 UTC; 3s ago
5 Process: 26320 ExecStart=/etc/init.d/celeryd start (code=exited, status=0/SUCCESS)
6 CGroup: /system.slice/celeryd.service
7 ├─26350 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
8 ├─26361 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
9 ├─26370 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
10 ├─26371 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
11 ├─26372 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
12 ├─26373 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
13 ├─26374 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
14 ├─26375 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
15 ├─26376 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
16 ├─26377 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
17 ├─26378 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
18 ├─26379 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
19 ├─26380 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
20 ├─26381 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
21 ├─26382 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
22 ├─26383 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
23 ├─26384 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
24 ├─26385 /usr/bin/python -m celery worker --app=worker -n celery1@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery1.log --pidfile=/var/run/celery/celery1.pid
25 ├─26386 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
26 ├─26387 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
27 ├─26388 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
28 ├─26389 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
29 ├─26390 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
30 ├─26391 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
31 ├─26392 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
32 ├─26393 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
33 ├─26394 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
34 ├─26395 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
35 ├─26396 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
36 ├─26397 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
37 ├─26398 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
38 ├─26399 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
39 ├─26400 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
40 └─26401 /usr/bin/python -m celery worker --app=worker -n celery2@encoding01 --loglevel=INFO --logfile=/var/log/celery/celery2.log --pidfile=/var/run/celery/celery2.pid
41
42Feb 04 10:41:34 encoding01 celeryd[26320]: celery init v10.1.
43Feb 04 10:41:34 encoding01 celeryd[26320]: Using config script: /etc/default/celeryd
44Feb 04 10:41:34 encoding01 su[26336]: Successful su for celery by root
45Feb 04 10:41:34 encoding01 su[26336]: + ??? root:celery
46Feb 04 10:41:34 encoding01 su[26336]: pam_unix(su:session): session opened for user celery by (uid=0)
47Feb 04 10:41:36 encoding01 celeryd[26320]: Stale pidfile exists. Removing it.
48Feb 04 10:41:37 encoding01 celeryd[26320]: celery multi v3.1.20 (Cipater)
49Feb 04 10:41:37 encoding01 celeryd[26320]: > Starting nodes...
50Feb 04 10:41:37 encoding01 celeryd[26320]: > celery1@encoding01: OK
51Feb 04 10:41:37 encoding01 celeryd[26320]: > celery2@encoding01: OK
52Feb 04 10:41:37 encoding01 celeryd[26320]: Stale pidfile exists. Removing it.
53Feb 04 10:41:37 encoding01 systemd[1]: Started LSB: celery task worker daemon.

Well, you cannot get an valid crsf token from an invalid oauth token.

In that case, I would expect an error 'Can't retrieve CSRF token, API returned ...' rather than a badtoken error -- or more specifically: I would expect a mwoauth-invalid-authorization error response, which would

raise NoUsername('Failed OAuth authentication for %s: %s'
                   % (self.site, info))

Can you run your script with -debug? That should create a log file in logs/ that shows the exact API requests made, and their response.

Can you run your script with -debug? That should create a log file in logs/ that shows the exact API requests made, and their response.

Is it possible to set this flag at runtime?

Probably. Pywikibot uses the logging module, which you can reconfigure the logging using the standard features it provides (see the python documentation), so running

import logging
logging.basicConfig(level=logging.DEBUG)

Whether you can also easily turn it off again: I don't know.

Ok, I'll try next time this happens. @Yann has already refreshed his oauth tokens, so the old invalid tokens are no longer accessible, as the tool stores oauth tokens nowhere except in redis session storage (already invalidated), redis celery broker (task queue, already popped), and in worker memory when the task is running (already SIGINT-ed).

Hmm... MediaWiki seems to return csrf tokens correctly, yet they are reported invalid:
(before this is a gigantic upload request of 64MB chunk)

2016-02-08 17:26:37             api.py, 1977 in             submit: DEBUG    API response received from commons:commons:
{"servedby":"mw1137","error":{"code":"badtoken","info":"Invalid token","*":"See https://commons.wikimedia.org/w/api.php for API usage"}}
2016-02-08 17:26:37             api.py, 2084 in             submit: WARNING  API error badtoken: Invalid token
2016-02-08 17:26:37             api.py, 2145 in             submit: VERBOSE  Bad token error for Sporti. Tokens for "csrf" used in request; invalidated them.
2016-02-08 17:26:37             api.py, 1944 in             submit: DEBUG    API request to commons:commons (uses get: False):
Headers: {u'Content-Type': u'application/x-www-form-urlencoded'}
URI: u'/w/api.php'
Body: 'maxlag=5&format=json&rawcontinue=&meta=tokens%7Cuserinfo&action=query&type=csrf&uiprop=blockinfo%7Chasmsg'
2016-02-08 17:26:37             api.py, 1977 in             submit: DEBUG    API response received from commons:commons:
{"query":{"tokens":{"csrftoken":"338b076f59e12239a5d3e0e23289883456b8cfcd+\\"},"userinfo":{"id":234501,"name":"Sporti"}}}

(Ctrl-C happened right after this)

Perhaps this is blocked by a MediaWiki-extensions-OAuth bug?

Perhaps this is blocked by a MediaWiki-extensions-OAuth bug?

I don't know much about pywikibot, but this doesn't seem likely. If OAuth was not correctly see the authorization, the you would get an OAuth error, not a session error.

Does pywikibot use OAuth for every api call? OAuth uses a slightly different session than a normal, cookie-based session, so mixing the two could give you unpredictable results.

We've been dealing with numerous session issues in the last week related to session manager, so there's a change you were simply logged out between getting the csrf token and using it?

Sorry, I was discussing this on irc #pywikibot and didn't see this responce. T126257: The API should not require CSRF tokens for an OAuth request was created during the discussion as a potential workaround.

Does pywikibot use OAuth for every api call? OAuth uses a slightly different session than a normal, cookie-based session, so mixing the two could give you unpredictable results.

Yes. I'm not entirely sure about cookies, but from the logs it seems every Api calls are OAuth-signed. @valhallasw you can explain this :)

We've been dealing with numerous session issues in the last week related to session manager, so there's a change you were simply logged out between getting the csrf token and using it?

Um, there's only a few seconds between the requests, with no other api requests in between (from the logs).

This is probably a session handling bug that will be fixed by https://gerrit.wikimedia.org/r/#/c/268702/ (or by deploying SessionManager, whichever happens first).

Yes. I'm not entirely sure about cookies, but from the logs it seems every Api calls are OAuth-signed. @valhallasw you can explain this :)

Well, I don't know the specific oauth implementation, but we do handle cookies as one would expect in a browser. There's a requests.session shared between all requests; it uses a cookie jar (programmatically accessible via pywikibot.comms.http.session.cookies) which in the default configuration is backed by the file pywikibot.comms.http.session.cookies.filename.

It might be interesting to dump the contents of the cookie (to check if the username is as expected), or to clear it using pywikibot.comms.http.session.cookies.clear(), but I'm not sure how much introspection you can do.

zhuyifei1999 claimed this task.

No more appearance recently after the deployment of above patch. I'll reopen if the same situation is seen again.

zhuyifei1999 set Security to None.