Page MenuHomePhabricator

Creating a page with {{safesubst:self}} throws RevisionAccessException: No such slot: main
Closed, ResolvedPublicPRODUCTION ERROR

Description

Error

  • MediaWiki version: 1.36.0-wmf.33
  • reqId: YEELCXG5uD@t-@kj6LSU@AAAAM0 (Logstash)
message
[{reqId}] {exception_url} MediaWiki\Revision\RevisionAccessException: No such slot: main
exception.trace
from /srv/mediawiki/php-1.36.0-wmf.33/includes/Revision/RevisionSlots.php(114)
#0 /srv/mediawiki/php-1.36.0-wmf.33/includes/Revision/RevisionRecord.php(181): MediaWiki\Revision\RevisionSlots->getSlot(string)
#1 /srv/mediawiki/php-1.36.0-wmf.33/includes/Revision/RevisionRecord.php(164): MediaWiki\Revision\RevisionRecord->getSlot(string, integer, NULL)
#2 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3697): MediaWiki\Revision\RevisionRecord->getContent(string)
#3 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3548): Parser->statelessFetchTemplate(Title, Parser)
#4 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3416): Parser->fetchTemplateAndTitle(Title)
#5 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3158): Parser->getTemplateDom(Title)
#6 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/PPFrame_Hash.php(263): Parser->braceSubstitution(array, PPFrame_Hash)
#7 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(2880): PPFrame_Hash->expand(PPNode_Hash_Tree, integer)
#8 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(4572): Parser->replaceVariables(string)
#9 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(4540): Parser->pstPass2(string, User)
#10 /srv/mediawiki/php-1.36.0-wmf.33/includes/content/WikitextContent.php(159): Parser->preSaveTransform(string, Title, User, ParserOptions)
#11 /srv/mediawiki/php-1.36.0-wmf.33/includes/Storage/DerivedPageDataUpdater.php(845): WikitextContent->preSaveTransform(Title, User, ParserOptions)
#12 /srv/mediawiki/php-1.36.0-wmf.33/includes/page/WikiPage.php(2206): MediaWiki\Storage\DerivedPageDataUpdater->prepareContent(User, MediaWiki\Storage\RevisionSlotsUpdate, boolean)
#13 /srv/mediawiki/php-1.36.0-wmf.33/extensions/SpamBlacklist/includes/SpamBlacklistHooks.php(34): WikiPage->prepareContentForEdit(WikitextContent)
#14 /srv/mediawiki/php-1.36.0-wmf.33/includes/HookContainer/HookContainer.php(333): SpamBlacklistHooks::filterMergedContent(DerivativeContext, WikitextContent, Status, string, User, boolean)
#15 /srv/mediawiki/php-1.36.0-wmf.33/includes/HookContainer/HookContainer.php(140): MediaWiki\HookContainer\HookContainer->callLegacyHook(string, array, array, array)
#16 /srv/mediawiki/php-1.36.0-wmf.33/includes/HookContainer/HookRunner.php(1566): MediaWiki\HookContainer\HookContainer->run(string, array)
#17 /srv/mediawiki/php-1.36.0-wmf.33/includes/editpage/Constraint/EditFilterMergedContentHookConstraint.php(90): MediaWiki\HookContainer\HookRunner->onEditFilterMergedContent(DerivativeContext, WikitextContent, Status, string, User, boolean)
#18 /srv/mediawiki/php-1.36.0-wmf.33/includes/editpage/Constraint/EditConstraintRunner.php(88): MediaWiki\EditPage\Constraint\EditFilterMergedContentHookConstraint->checkConstraint()
#19 /srv/mediawiki/php-1.36.0-wmf.33/includes/EditPage.php(2086): MediaWiki\EditPage\Constraint\EditConstraintRunner->checkConstraints()
#20 /srv/mediawiki/php-1.36.0-wmf.33/includes/EditPage.php(1687): EditPage->internalAttemptSave(NULL, boolean)
#21 /srv/mediawiki/php-1.36.0-wmf.33/includes/EditPage.php(662): EditPage->attemptSave(NULL)
#22 /srv/mediawiki/php-1.36.0-wmf.33/includes/actions/EditAction.php(71): EditPage->edit()
#23 /srv/mediawiki/php-1.36.0-wmf.33/includes/actions/SubmitAction.php(38): EditAction->show()
#24 /srv/mediawiki/php-1.36.0-wmf.33/includes/MediaWiki.php(531): SubmitAction->show()
#25 /srv/mediawiki/php-1.36.0-wmf.33/includes/MediaWiki.php(315): MediaWiki->performAction(Article, Title)
#26 /srv/mediawiki/php-1.36.0-wmf.33/includes/MediaWiki.php(925): MediaWiki->performRequest()
#27 /srv/mediawiki/php-1.36.0-wmf.33/includes/MediaWiki.php(547): MediaWiki->main()
#28 /srv/mediawiki/php-1.36.0-wmf.33/index.php(53): MediaWiki->run()
#29 /srv/mediawiki/php-1.36.0-wmf.33/index.php(46): wfIndexMain()
#30 /srv/mediawiki/w/index.php(3): require(string)
#31 {main}

Impact

There's about 20 of these in the last four hours. Not enough to warrant rolling back the train, but it's logspam and would be good to fix.

Notes

Event Timeline

Krinkle renamed this task from No such slot: main to RevisionAccessException: No such slot: main.Mar 4 2021, 10:35 PM
Krinkle changed Request URL from https://bn.wikipedia.org/w/index.php?title=%E0%A6%9F%E0%A7%87%E0%A6%AE%E0%A6%AA%E0%A7%8D%E0%A6%B2%E0%A7%87%E0%A6%9F:%E0%A6%89%E0%A6%87%E0%A6%95%E0%A6%BF%E0%A6%AA%E0%A7%8D%E0%A6%B0%E0%A6%95%E0%A6%B2%E0%A7%8D%E0%A6%AA_%E0%A6%A8%E0%A6%9F%E0%A6%B0_%E0%A6%A1%E0%A7%87%E0%A6%AE_%E0%A6%95%E0%A6%B2%E0%A7%87%E0%A6%9C/%E0%A6%86%E0%A6%AE%E0%A6%A8%E0%A7%8D%E0%A6%A4%E0%A7%8D%E0%A6%B0%E0%A6%A3&action=submit to https://bn.wikipedia.org/w/index.php?title=…&action=submit.
Krinkle removed Request ID.
Krinkle updated the task description. (Show Details)
Krinkle edited Stack Trace. (Show Details)
Krinkle updated the task description. (Show Details)
Krinkle subscribed.

The impact would appear to be that some pages on bn.wikipedia.org cannot be viewed nor edited.

I would expect No such slot: main to be an impossible condition in production, so this surfacing here is quite worrying. As for what the root cause is or why it's happening now, or why on bn.wikipedia, I don't know. The RevisionStore is maintained by PET and the tag is ready for triage as such.

Krinkle triaged this task as High priority.Mar 4 2021, 10:42 PM

The page itself exists, and seems fine: action=read view, action=info view.

Possibly some of the changes to EditPage? It's triggered from action=submit, but no user action should be able to delete the main slot.

When looking at the history of the page in the request url the first version shows 0 bytes. But the api returns the sha1 for the empty string, that indicates nothing is lost or byte count is wrong - https://bn.wikipedia.org/w/api.php?action=query&revids=4934210&prop=revisions&rvprop=size|sha1|slotsize|slotsha1|roles&rvslots=main

The next edit is one minute later, maybe the parse does not find the slot due to replica lag?

The RevisionStore itselfs checks for the main slots with a hint to T212428: includes/Revision/RevisionStore.php: Main slot of revision (number) not found in database!
Maybe this is related to it

The RevisionStore itselfs checks for the main slots with a hint to T212428: includes/Revision/RevisionStore.php: Main slot of revision (number) not found in database!
Maybe this is related to it

Seems unrelated. T212428 is triggered when the database appears to be inconsistent/corrupt (due to a botched update, or possibly a race condition).

Here, the issue is that some hook tries to access the main slot before it has been placed in the RevisionRecord while saving an edit.

EDIT: could be related afterall - the complain is while loading a template, not for the page being edited.

Brief analysis of the stack trace, bottom-up:

#22 /srv/mediawiki/php-1.36.0-wmf.33/includes/actions/EditAction.php(71): EditPage->edit()
#21 /srv/mediawiki/php-1.36.0-wmf.33/includes/EditPage.php(662): EditPage->attemptSave(NULL)
#20 /srv/mediawiki/php-1.36.0-wmf.33/includes/EditPage.php(1687): EditPage->internalAttemptSave(NULL, boolean)
#19 /srv/mediawiki/php-1.36.0-wmf.33/includes/EditPage.php(2086): MediaWiki\EditPage\Constraint\EditConstraintRunner->checkConstraints()

This call to checkConstraints() is in the $isNew branch, indicating that the page is being created. Are we seeing the same error with checkConstraints() being called later in another branch in internalAttemptSave?

#18 /srv/mediawiki/php-1.36.0-wmf.33/includes/editpage/Constraint/EditConstraintRunner.php(88): MediaWiki\EditPage\Constraint\EditFilterMergedContentHookConstraint->checkConstraint()
#17 /srv/mediawiki/php-1.36.0-wmf.33/includes/editpage/Constraint/EditFilterMergedContentHookConstraint.php(90): MediaWiki\HookContainer\HookRunner->onEditFilterMergedContent(DerivativeContext, WikitextContent, Status, string, User, boolean)
#16 /srv/mediawiki/php-1.36.0-wmf.33/includes/HookContainer/HookRunner.php(1566): MediaWiki\HookContainer\HookContainer->run(string, array)
#15 /srv/mediawiki/php-1.36.0-wmf.33/includes/HookContainer/HookContainer.php(140): MediaWiki\HookContainer\HookContainer->callLegacyHook(string, array, array, array)
#14 /srv/mediawiki/php-1.36.0-wmf.33/includes/HookContainer/HookContainer.php(333): SpamBlacklistHooks::filterMergedContent(DerivativeContext, WikitextContent, Status, string, User, boolean)
#13 /srv/mediawiki/php-1.36.0-wmf.33/extensions/SpamBlacklist/includes/SpamBlacklistHooks.php(34): WikiPage->prepareContentForEdit(WikitextContent)

SpamBlacklist wants to look at the content that is going to be saved, after pre-save-transform (PST).

#12 /srv/mediawiki/php-1.36.0-wmf.33/includes/page/WikiPage.php(2206): MediaWiki\Storage\DerivedPageDataUpdater->prepareContent(User, MediaWiki\Storage\RevisionSlotsUpdate, boolean)
#11 /srv/mediawiki/php-1.36.0-wmf.33/includes/Storage/DerivedPageDataUpdater.php(845): WikitextContent->preSaveTransform(Title, User, ParserOptions)
#10 /srv/mediawiki/php-1.36.0-wmf.33/includes/content/WikitextContent.php(159): Parser->preSaveTransform(string, Title, User, ParserOptions)

The Parser is doing PST

#9 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(4540): Parser->pstPass2(string, User)
#8 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(4572): Parser->replaceVariables(string)
#7 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(2880): PPFrame_Hash->expand(PPNode_Hash_Tree, integer)
#6 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/PPFrame_Hash.php(263): Parser->braceSubstitution(array, PPFrame_Hash)
#5 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3158): Parser->getTemplateDom(Title)
#4 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3416): Parser->fetchTemplateAndTitle(Title)
#3 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3548): Parser->statelessFetchTemplate(Title, Parser)

The Parser is expanding a template. During PST, this would only happen for template substitution. statelessFetchTemplate() uses RevisionStore to load the template's content from the database.

#2 /srv/mediawiki/php-1.36.0-wmf.33/includes/parser/Parser.php(3697): MediaWiki\Revision\RevisionRecord->getContent(string)
#1 /srv/mediawiki/php-1.36.0-wmf.33/includes/Revision/RevisionRecord.php(164): MediaWiki\Revision\RevisionRecord->getSlot(string, integer, NULL)
#0 /srv/mediawiki/php-1.36.0-wmf.33/includes/Revision/RevisionRecord.php(181): MediaWiki\Revision\RevisionSlots->getSlot(string)

We got an incomplete revision from the database. This indicates data corruption, specifically a revision row with no associated slot rows (or content rows). However, if that was the case, I would expect to see "'Main slot of revision not found in database. See T212428." from RevisionStore::constructSlotRecords().

I will make a patch that improves reporting, so we get the ID of the offending revision.

Change 670425 had a related patch set uploaded (by Daniel Kinzler; owner: Daniel Kinzler):
[mediawiki/core@master] RevisionRecord: report revision ID when failing to access slot

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

I will make a patch that improves reporting, so we get the ID of the offending revision.

It was probably the same page that was being created, considering that when the content was successfully added (in oldid 4934216) it had the text {{safesubst<noinclude/>:BASEPAGENAME}}.

I was able to reproduce the error locally by creating [[Template:Test]] with {{safesubst:test}}. On preview this shows a template loop error, but on save, it throws the exception.

tstarling renamed this task from RevisionAccessException: No such slot: main to Creating a page with {{safesubst:self}} throws RevisionAccessException: No such slot: main.Mar 16 2021, 10:51 PM

DerivedPageDataUpdater says

		// NOTE: user and timestamp must be set, so they can be used for
		// {{subst:REVISIONUSER}} and {{subst:REVISIONTIMESTAMP}} in PST!
		$this->revision->setTimestamp( MWTimestamp::now( TS_MW ) );
		$this->revision->setUser( $user );

This is how it justifies sending a partially filled RevisionRecord to the parser. Similar code in RenderedRevision::setRevisionInternal() checks isReadyForInsertion(), and thereby avoids using revisions with no slots, but DerivedPageDataUpdater cannot implement such protection without breaking this feature of allowing {{subst:REVISIONUSER}} on page creation.

So I think the solution is to make Parser check RevisionRecord::hasSlot(). Parser is meant to tolerate errors, it's not meant to throw an exception when something goes slightly wrong, it's meant to show an error box or something. There's only one call to RevisionRecord::getContent() in Parser.

Change 672847 had a related patch set uploaded (by Tim Starling; owner: Tim Starling):
[mediawiki/core@master] When the parser fetches revision content, guard against empty slots

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

In T276476#6920215, @IN wrote:

I am not sure how this problem is triggered

I changed the task title to say how it is triggered. Here's a log entry from me triggering it: https://logstash.wikimedia.org/app/discover#/doc/logstash-*/logstash-deploy-2021.03.17?id=IXqNQngBA6MeBtBqjQHO

Change 672847 merged by jenkins-bot:
[mediawiki/core@master] When the parser fetches revision content, guard against empty slots

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

Change 676838 had a related patch set uploaded (by Umherirrender; author: Umherirrender):

[mediawiki/core@master] Add parser test for {{safesubst:self}}

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

Change 676838 merged by jenkins-bot:

[mediawiki/core@master] Add parser test for {{safesubst:self}}

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

Change #670425 abandoned by Daniel Kinzler:

[mediawiki/core@master] RevisionRecord: report revision ID when failing to access slot

Reason:

no longer needed

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