Page MenuHomePhabricator

"MWException: CAS update failed on user_touched" from VisualEditor hook
Closed, ResolvedPublicPRODUCTION ERROR

Description

Error
normalized_message
[{reqId}] {exception_url}   MWException: CAS update failed on user_touched. The version of the user to be saved is older than the current version.
exception.trace
from /srv/mediawiki/php-1.39.0-wmf.15/includes/user/User.php(2838)
#0 /srv/mediawiki/php-1.39.0-wmf.15/includes/libs/rdbms/database/Database.php(3676): User->{closure}(Wikimedia\Rdbms\DatabaseMysqli, string)
#1 /srv/mediawiki/php-1.39.0-wmf.15/includes/libs/rdbms/database/DBConnRef.php(68): Wikimedia\Rdbms\Database->doAtomicSection(string, Closure)
#2 /srv/mediawiki/php-1.39.0-wmf.15/includes/libs/rdbms/database/DBConnRef.php(603): Wikimedia\Rdbms\DBConnRef->__call(string, array)
#3 /srv/mediawiki/php-1.39.0-wmf.15/includes/user/User.php(2850): Wikimedia\Rdbms\DBConnRef->doAtomicSection(string, Closure)
#4 /srv/mediawiki/php-1.39.0-wmf.15/extensions/VisualEditor/includes/Hooks.php(352): User->saveSettings()
#5 /srv/mediawiki/php-1.39.0-wmf.15/includes/deferred/MWCallableUpdate.php(38): MediaWiki\Extension\VisualEditor\Hooks::MediaWiki\Extension\VisualEditor\{closure}()
#6 /srv/mediawiki/php-1.39.0-wmf.15/includes/deferred/DeferredUpdates.php(535): MWCallableUpdate->doUpdate()
#7 /srv/mediawiki/php-1.39.0-wmf.15/includes/deferred/DeferredUpdates.php(418): DeferredUpdates::attemptUpdate(MWCallableUpdate, Wikimedia\Rdbms\LBFactoryMulti)
#8 /srv/mediawiki/php-1.39.0-wmf.15/includes/deferred/DeferredUpdates.php(228): DeferredUpdates::run(MWCallableUpdate, Wikimedia\Rdbms\LBFactoryMulti, Monolog\Logger, BufferingStatsdDataFactory, MediaWiki\JobQueue\JobQueueGroupFactory, string)
#9 /srv/mediawiki/php-1.39.0-wmf.15/includes/deferred/DeferredUpdatesScope.php(267): DeferredUpdates::{closure}(MWCallableUpdate, integer)
#10 /srv/mediawiki/php-1.39.0-wmf.15/includes/deferred/DeferredUpdatesScope.php(196): DeferredUpdatesScope->processStageQueue(integer, integer, Closure)
#11 /srv/mediawiki/php-1.39.0-wmf.15/includes/deferred/DeferredUpdates.php(249): DeferredUpdatesScope->processUpdates(integer, Closure)
#12 /srv/mediawiki/php-1.39.0-wmf.15/includes/MediaWiki.php(1127): DeferredUpdates::doUpdates()
#13 /srv/mediawiki/php-1.39.0-wmf.15/includes/MediaWiki.php(852): MediaWiki->restInPeace()
#14 /srv/mediawiki/php-1.39.0-wmf.15/includes/MediaWiki.php(595): MediaWiki->doPostOutputShutdown()
#15 /srv/mediawiki/php-1.39.0-wmf.15/index.php(50): MediaWiki->run()
#16 /srv/mediawiki/php-1.39.0-wmf.15/index.php(46): wfIndexMain()
#17 /srv/mediawiki/w/index.php(3): require(string)
#18 {main}
Impact
Notes

~1k of these in the last month.

Details

Request URL
https://bo.wikipedia.org/w/index.php?title=*&action=edit&redlink=*&venoscript=*

Event Timeline

ppelberg added a project: Editing-team.
ppelberg moved this task from Untriaged to Upcoming on the Editing-team board.

I think the error is coming from this bit of code, it's the only thing in VisualEditor Hooks.php that generates a MWCallableUpdate:

	public static function onCustomEditor( Article $article, User $user ) {
		...
		if ( $req->getVal( 'venoscript' ) ) {
			$req->response()->setCookie( 'VEE', 'wikitext', 0, [ 'prefix' => '' ] );
			$services->getUserOptionsManager()->setOption( $user, 'visualeditor-editor', 'wikitext' );
			if ( !$services->getReadOnlyMode()->isReadOnly() && $user->isRegistered() ) {
				DeferredUpdates::addCallableUpdate( static function () use ( $user ) {
					$user->saveSettings();
				} );
			}
			return true;
		}

I don't entirely understand what "CAS update failed" means, but two things about this code look fishy: we're using the same User object in the deferred update that we got from the hook; and we're using User::saveSettings when we only want to save user preferences, we should be using something from UserOptionsManager instead.

Change 807193 had a related patch set uploaded (by Bartosz Dziewoński; author: Bartosz Dziewoński):

[mediawiki/extensions/VisualEditor@master] Update code for setting user preferences

https://gerrit.wikimedia.org/r/807193

Krinkle renamed this task from "MWException: CAS update failed on user_touched. The version of the user to be saved is older than the current version." from VisualEditor hook to "MWException: CAS update failed on user_touched" from VisualEditor hook.Jun 23 2022, 3:50 PM

@matmarex The issue with this general pattern is the delay between the setOption and saveSettings. Likely there was something else in-between for the same user (possibly something we script that changes a hidden preference), and thus the token is no longer valid by then.

The general pattern we recommend for this, and have applied to other CAS errors in the past, is to make sure the User object is created, loaded, modified, and saved in the same scope.

Indeed, given the VE hook only needs to modify options, and not the User row itself, using UserOptionsManager naturally avoids CAS errors. (The UserOptionsManager::saveOptions method does the above automatically for you: It creates a new temp User object and then immediately calls checkAndSetTouched to ensure we induce the expected CAS failures when races happen, but on a much tighter time scale there naturally.)

Change 807193 merged by jenkins-bot:

[mediawiki/extensions/VisualEditor@master] Update code for setting user preferences

https://gerrit.wikimedia.org/r/807193

I had a look today to confirm the fix, and… it looks like something else has already mostly fixed it more than a month ago, on 2022-07-08. There have been only a few of those errors after that date.

Logstash query for "MWException: CAS update failed on user_touched" AND "*VisualEditor*": https://logstash.wikimedia.org/goto/7795f5bcbe71feb9e923a2d547a7a976

image.png (535×2 px, 62 KB)

Still, there were a few every couple of days, and there have been none since 2022-08-18 when the new patch has been deployed, so I guess that's mildly reassuring.