Page MenuHomePhabricator

MediumSpecificBagOStuff->guessSerialValueSize infinite loop when storing Title object (Special:Homepage throws "Maximum function nesting reached")
Closed, ResolvedPublicBUG REPORT

Description

Special:Homapage errors out with

[a3777da0e7bef2b536b810f3] /w/index.php?title=Special:Homepage&source=personaltoolslink&namespace=-1 Error: Maximum function nesting level of '200' reached, aborting!

Backtrace:

from /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1028)
#0 /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1063): MediumSpecificBagOStuff->guessSerialValueSize(Closure, integer, integer)
...
#173 /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1063): MediumSpecificBagOStuff->guessSerialValueSize(Closure, integer, integer)
#174 /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1063): MediumSpecificBagOStuff->guessSerialValueSize(array, integer, integer)
#175 /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1063): MediumSpecificBagOStuff->guessSerialValueSize(array, integer, integer)
#176 /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1063): MediumSpecificBagOStuff->guessSerialValueSize(Title, integer, integer)
#177 /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1063): MediumSpecificBagOStuff->guessSerialValueSize(GrowthExperiments\NewcomerTasks\Task\Task, integer, integer)
#178 /vagrant/mediawiki/includes/libs/objectcache/MediumSpecificBagOStuff.php(1063): MediumSpecificBagOStuff->guessSerialValueSize(array, integer, integer)
#179 /vagrant/mediawiki/includes/libs/objectcache/MemcachedPeclBagOStuff.php(281): MediumSpecificBagOStuff->guessSerialValueSize(GrowthExperiments\NewcomerTasks\Task\TaskSet)
#180 /vagrant/mediawiki/includes/libs/objectcache/wancache/WANObjectCache.php(1873): MemcachedPeclBagOStuff->setNewPreparedValues(array)
#181 /vagrant/mediawiki/includes/libs/objectcache/wancache/WANObjectCache.php(1710): WANObjectCache->checkAndSetCooloff(string, string, GrowthExperiments\NewcomerTasks\Task\TaskSet, double, boolean)
#182 /vagrant/mediawiki/includes/libs/objectcache/wancache/WANObjectCache.php(1518): WANObjectCache->fetchOrRegenerate(string, integer, Closure, array, array)
#183 /vagrant/mediawiki/extensions/GrowthExperiments/includes/NewcomerTasks/TaskSuggester/CacheDecorator.php(181): WANObjectCache->getWithSetCallback(string, integer, Closure, array)
#184 /vagrant/mediawiki/extensions/GrowthExperiments/includes/NewcomerTasks/TaskSuggester/QualityGateDecorator.php(69): GrowthExperiments\NewcomerTasks\TaskSuggester\CacheDecorator->suggest(User, array, array, integer, NULL, array)
#185 /vagrant/mediawiki/extensions/GrowthExperiments/includes/HomepageModules/SuggestedEdits.php(402): GrowthExperiments\NewcomerTasks\TaskSuggester\QualityGateDecorator->suggest(User, array, array, NULL, NULL, array)
#186 /vagrant/mediawiki/extensions/GrowthExperiments/includes/HomepageModules/SuggestedEdits.php(802): GrowthExperiments\HomepageModules\SuggestedEdits->getTaskSet()
#187 /vagrant/mediawiki/extensions/GrowthExperiments/includes/HomepageModules/BaseModule.php(165): GrowthExperiments\HomepageModules\SuggestedEdits->getActionData()
#188 /vagrant/mediawiki/extensions/GrowthExperiments/includes/DashboardModule/DashboardModule.php(184): GrowthExperiments\HomepageModules\BaseModule->outputDependencies()
#189 /vagrant/mediawiki/extensions/GrowthExperiments/includes/Specials/SpecialHomepage.php(325): GrowthExperiments\DashboardModule\DashboardModule->render(string)
#190 /vagrant/mediawiki/extensions/GrowthExperiments/includes/Specials/SpecialHomepage.php(267): GrowthExperiments\Specials\SpecialHomepage->getModuleRenderHtmlSafe(GrowthExperiments\HomepageModules\SuggestedEdits, string)
#191 /vagrant/mediawiki/extensions/GrowthExperiments/includes/Specials/SpecialHomepage.php(137): GrowthExperiments\Specials\SpecialHomepage->renderDesktop()
#192 /vagrant/mediawiki/includes/specialpage/SpecialPage.php(647): GrowthExperiments\Specials\SpecialHomepage->execute(NULL)
#193 /vagrant/mediawiki/includes/specialpage/SpecialPageFactory.php(1375): SpecialPage->run(NULL)
#194 /vagrant/mediawiki/includes/MediaWiki.php(314): MediaWiki\SpecialPage\SpecialPageFactory->executePath(string, RequestContext)
#195 /vagrant/mediawiki/includes/MediaWiki.php(910): MediaWiki->performRequest()
#196 /vagrant/mediawiki/includes/MediaWiki.php(554): MediaWiki->main()
#197 /vagrant/mediawiki/index.php(53): MediaWiki->run()
#198 /vagrant/mediawiki/index.php(46): wfIndexMain()
#199 /var/www/w/index.php(5): require(string)
#200 {main}

Apparently MediumSpecificBagOStuff->guessSerialValueSize tries to traverse the cached object, and gets into an infinite loop inside the Title contained in TaskSet (Title -> deprecatedPublicProperties -> mTextform -> Closure -> Title).

Not sure if this affects production (MediumSpecificBagOStuff->guessSerialValueSize has some logic to abort after 256 levels so depends on how much nesting is allowed) Also not sure why I'm only seeing this error now, none of the affected code is new. In any case, TaskSet should probably be JsonSerializable, so that a JSON-ish representation is serialized instead of the full object, per T161647: RFC: Deprecate using php serialization inside MediaWiki.

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

@Tgr are you still seeing this? I don't see it on enwiki beta. T296508: ERROR @wdio/sync: Error: socket hang up looks similar.

@Tgr are you still seeing this? I don't see it on enwiki beta. T296508: ERROR @wdio/sync: Error: socket hang up looks similar.

The hoomepage on my local setup is permanently broken because of this. I think production has some perversely high nesting limit because of something that Parsoid does, so it wouldn't be affected. It would be easy to configure around this locally, but

  1. seems not ideal for performance (I wonder if the loop condition was meant to be an OR instead of an AND, that would seem more natural to me)
  2. we should avoid serializing PHP objects anyway, so we might as well fix this.

Change 742591 had a related patch set uploaded (by Gergő Tisza; author: Gergő Tisza):

[mediawiki/extensions/GrowthExperiments@master] NewcomerTasks: Make value objects JSON-serializable

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

Change 742594 had a related patch set uploaded (by Gergő Tisza; author: Gergő Tisza):

[mediawiki/extensions/GrowthExperiments@master] [WIP] CacheDecorator: Use JSON serialization

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

I think production has some perversely high nesting limit because of something that Parsoid does

I had a vague recollection that fastcgi has nesting limits, but that doesn't seem to be the case, so the error would only happen with XDebug installed.

It would be easy to configure around this locally

FTR that's xdebug.max_nesting_level=300 in some PHP ini file.

Change 742591 merged by jenkins-bot:

[mediawiki/extensions/GrowthExperiments@master] NewcomerTasks: Make value objects JSON-serializable

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

Change 742594 merged by jenkins-bot:

[mediawiki/extensions/GrowthExperiments@master] CacheDecorator: Use JSON serialization

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

aaron triaged this task as Medium priority.Jan 7 2022, 1:42 AM

Change 754856 had a related patch set uploaded (by Aaron Schulz; author: Aaron Schulz):

[mediawiki/core@master] objectcache: fix guessSerialValueSize() for complex objects

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

Was any benchmarking done to support the introduction of this feature? There was no linked bug on https://gerrit.wikimedia.org/r/c/mediawiki/core/+/580607 . I'm having trouble trying to find a test case for which it is faster than json_encode().

> $prefixes = svc('InterwikiLookup')->getAllPrefixes();
> print count($prefixes);
67
> $t = microtime(true); for ( $i = 0; $i < 100000; $i++ ) { $cache->guessSerialValueSize($prefixes); } print microtime(true)-$t;
6.735631942749
> $t = microtime(true); for ( $i = 0; $i < 100000; $i++ ) { json_encode($prefixes); } print microtime(true)-$t;
3.0343141555786
> $s = str_repeat('x',1000);
> $t = microtime(true); for ( $i = 0; $i < 100000; $i++ ) { $cache->guessSerialValueSize($s); } print microtime(true)-$t;
0.088092088699341
> $t = microtime(true); for ( $i = 0; $i < 100000; $i++ ) { json_encode($s); } print microtime(true)-$t;
0.082571983337402
> $a = new stdClass;
> $a = [ &$a, &$a ];
> $t = microtime(true); for ( $i = 0; $i < 100000; $i++ ) { $cache->guessSerialValueSize($a); } print microtime(true)-$t;
0.36614179611206
> $t = microtime(true); for ( $i = 0; $i < 100000; $i++ ) { json_encode($a); } print microtime(true)-$t;
0.011312961578369

This is including the patch above.

Change 755517 had a related patch set uploaded (by Tim Starling; author: Tim Starling):

[mediawiki/core@master] Improve the performance of guessSerialValueSize()

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

Krinkle renamed this task from Special:Homepage throws "Maximum function nesting level of '200' reached, aborting!" to MediumSpecificBagOStuff->guessSerialValueSize infinite loop when storing Title object (Special:Homepage throws "Maximum function nesting reached").Jan 21 2022, 9:39 PM

Change 755517 merged by jenkins-bot:

[mediawiki/core@master] Improve the performance of guessSerialValueSize()

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