Jackmcbarn has uploaded a new change for review. https://gerrit.wikimedia.org/r/178698
Change subject: Avoid unnecessary database queries ...................................................................... Avoid unnecessary database queries Currently, mw.title.new always results in a database query, which holds up the parse until it finishes. This changes it to not require a database query if it's not actually necessary. Bug: T68328 Change-Id: I62f347d4cd9176bd0440215dcbe804c1dc3d4c99 --- M engines/LuaCommon/TitleLibrary.php M engines/LuaCommon/lualib/mw.title.lua M tests/engines/LuaCommon/TitleLibraryTest.php M tests/engines/LuaCommon/TitleLibraryTests.lua 4 files changed, 101 insertions(+), 51 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Scribunto refs/changes/98/178698/1 diff --git a/engines/LuaCommon/TitleLibrary.php b/engines/LuaCommon/TitleLibrary.php index c3db982..9f3c5f1 100644 --- a/engines/LuaCommon/TitleLibrary.php +++ b/engines/LuaCommon/TitleLibrary.php @@ -12,6 +12,7 @@ $lib = array( 'newTitle' => array( $this, 'newTitle' ), 'makeTitle' => array( $this, 'makeTitle' ), + 'getExpensiveData' => array( $this, 'getExpensiveData' ), 'getUrl' => array( $this, 'getUrl' ), 'getContent' => array( $this, 'getContent' ), 'getFileInfo' => array( $this, 'getFileInfo' ), @@ -19,7 +20,7 @@ 'cascadingProtection' => array( $this, 'cascadingProtection' ), ); return $this->getEngine()->registerInterface( 'mw.title.lua', $lib, array( - 'thisTitle' => $this->returnTitleToLua( $this->getTitle() ), + 'thisTitle' => $this->getInexpensiveTitleData( $this->getTitle() ), 'NS_MEDIA' => NS_MEDIA, ) ); } @@ -50,58 +51,92 @@ } /** - * Extract information from a Title object for return to Lua - * - * This also records a link to this title in the current ParserOutput - * and caches the title for repeated lookups. The caller should call - * incrementExpensiveFunctionCount() if necessary. + * Extract inexpensive information from a Title object for return to Lua * * @param $title Title Title to return * @return array Lua data */ - private function returnTitleToLua( Title $title ) { - // Cache it - $this->titleCache[$title->getPrefixedDBkey()] = $title; - if ( $title->getArticleID() > 0 ) { - $this->idCache[$title->getArticleID()] = $title; - } - - // Record a link - if ( $this->getParser() && !$title->equals( $this->getTitle() ) ) { - $this->getParser()->getOutput()->addLink( $title ); - } - + private function getInexpensiveTitleData( Title $title ) { $ns = $title->getNamespace(); $ret = array( 'isLocal' => (bool)$title->isLocal(), - 'isRedirect' => (bool)$title->isRedirect(), 'interwiki' => $title->getInterwiki(), 'namespace' => $ns, 'nsText' => $title->getNsText(), 'text' => $title->getText(), - 'id' => $title->getArticleID(), 'fragment' => $title->getFragment(), - 'contentModel' => $title->getContentModel(), 'thePartialUrl' => $title->getPartialURL(), ); - if ( $ns === NS_SPECIAL ) { + switch( $ns ) { + case NS_SPECIAL: + $ret['exists'] = (bool)SpecialPageFactory::exists( $title->getDBkey() ); + // fall through + default: + $ret['file'] = false; + break; + case NS_FILE: + case NS_MEDIA: + // do nothing + } + return $ret; + } + + /** + * Extract expensive information from a Title object for return to Lua + * + * This records a link to this title in the current ParserOutput and caches the + * title for repeated lookups. It may call incrementExpensiveFunctionCount() if + * the title is not already cached. + * + * @param string $text Title text + * @return array Lua data + */ + public function getExpensiveData( $text ) { + $this->checkType( 'getExpensiveData', 1, $text, 'string' ); + $title = Title::newFromText( $text ); + if ( !$title ) { + return array( null ); + } + $dbKey = $title->getPrefixedDBkey(); + if ( isset( $this->titleCache[$dbKey] ) ) { + // It was already cached, so we already did the expensive work and added a link + $title = $this->titleCache[$dbKey]; + } else { + if ( !$title->equals( $this->getTitle() ) ) { + $this->incrementExpensiveFunctionCount(); + + // Record a link + if ( $this->getParser() ) { + $this->getParser()->getOutput()->addLink( $title ); + } + } + + // Cache it + $this->titleCache[$dbKey] = $title; + if ( $title->getArticleID() > 0 ) { + $this->idCache[$title->getArticleID()] = $title; + } + } + + $ret = array( + 'isRedirect' => (bool)$title->isRedirect(), + 'id' => $title->getArticleID(), + 'contentModel' => $title->getContentModel(), + ); + if ( $title->getNamespace() === NS_SPECIAL ) { $ret['exists'] = (bool)SpecialPageFactory::exists( $title->getDBkey() ); } else { // bug 70495: don't just check whether the ID != 0 $ret['exists'] = $title->exists(); } - if ( $ns !== NS_FILE && $ns !== NS_MEDIA ) { - $ret['file'] = false; - } - return $ret; + return array( $ret ); } /** * Handler for title.new * * Calls Title::newFromID or Title::newFromTitle as appropriate for the - * arguments, and may call incrementExpensiveFunctionCount() if the title - * is not already cached. + * arguments. * * @param $text_or_id string|int Title or page_id to fetch * @param $defaultNamespace string|int Namespace name or number to use if $text_or_id doesn't override @@ -117,7 +152,9 @@ $title = Title::newFromID( $text_or_id ); $this->idCache[$text_or_id] = $title; } - if ( !$title ) { + if ( $title ) { + $this->titleCache[$title->getPrefixedDBkey()] = $title; + } else { return array( null ); } } elseif ( $type === 'string' ) { @@ -129,25 +166,18 @@ if ( !$title ) { return array( null ); } - if ( isset( $this->titleCache[$title->getPrefixedDBkey()] ) ) { - // Use the cached version, because that has already been loaded from the database - $title = $this->titleCache[$title->getPrefixedDBkey()]; - } else { - $this->incrementExpensiveFunctionCount(); - } } else { // This will always fail $this->checkType( 'title.new', 1, $text_or_id, 'number or string' ); } - return array( $this->returnTitleToLua( $title ) ); + return array( $this->getInexpensiveTitleData( $title ) ); } /** * Handler for title.makeTitle * - * Calls Title::makeTitleSafe, and may call - * incrementExpensiveFunctionCount() if the title is not already cached. + * Calls Title::makeTitleSafe. * * @param $ns string|int Namespace * @param $text string Title text @@ -167,14 +197,8 @@ if ( !$title ) { return array( null ); } - if ( isset( $this->titleCache[$title->getPrefixedDBkey()] ) ) { - // Use the cached version, because that has already been loaded from the database - $title = $this->titleCache[$title->getPrefixedDBkey()]; - } else { - $this->incrementExpensiveFunctionCount(); - } - return array( $this->returnTitleToLua( $title ) ); + return array( $this->getInexpensiveTitleData( $title ) ); } // May call the following Title methods: diff --git a/engines/LuaCommon/lualib/mw.title.lua b/engines/LuaCommon/lualib/mw.title.lua index 8594e4a..feb6947 100644 --- a/engines/LuaCommon/lualib/mw.title.lua +++ b/engines/LuaCommon/lualib/mw.title.lua @@ -195,6 +195,12 @@ k = 'fileExists' end + if ( k == 'exists' or k == 'isRedirect' or k == 'contentModel' or k == 'id' ) and not data[k] then + for k,v in pairs( php.getExpensiveData( data.prefixedText ) ) do + data[k] = v + end + end + if k == 'fullText' then if data.fragment ~= '' then return data.prefixedText .. '#' .. data.fragment diff --git a/tests/engines/LuaCommon/TitleLibraryTest.php b/tests/engines/LuaCommon/TitleLibraryTest.php index 281cc28..dc78666 100644 --- a/tests/engines/LuaCommon/TitleLibraryTest.php +++ b/tests/engines/LuaCommon/TitleLibraryTest.php @@ -124,7 +124,7 @@ $this->assertFalse( isset( $links[NS_PROJECT]['Referenced_from_Lua'] ) ); $interpreter->callFunction( - $interpreter->loadString( 'mw.title.new( "Project:Referenced from Lua" )', 'reference title' ) + $interpreter->loadString( 'local _ = mw.title.new( "Project:Referenced from Lua" ).id', 'reference title' ) ); $links = $engine->getParser()->getOutput()->getLinks(); diff --git a/tests/engines/LuaCommon/TitleLibraryTests.lua b/tests/engines/LuaCommon/TitleLibraryTests.lua index 7619b9f..9126c01 100644 --- a/tests/engines/LuaCommon/TitleLibraryTests.lua +++ b/tests/engines/LuaCommon/TitleLibraryTests.lua @@ -42,16 +42,30 @@ return tostring( title ), tostring( title.fragment ) end -local function test_expensive() +local function test_expensive_10() for i = 1, 10 do - mw.title.new( tostring( i ) ) + local _ = mw.title.new( tostring( i ) ).id + end + return 'did not error' +end + +local function test_expensive_11() + for i = 1, 11 do + local _ = mw.title.new( tostring( i ) ).id end return 'did not error' end local function test_expensive_cached() for i = 1, 100 do - mw.title.new( 'Title' ) + local _ = mw.title.new( 'Title' ).id + end + return 'did not error' +end + +local function test_expensive_inexpensive() + for i = 1, 100 do + local _ = mw.title.new( 'Title' ).prefixedText end return 'did not error' end @@ -356,10 +370,16 @@ expect = { '{{int:mainpage}}<includeonly>...</includeonly><noinclude>...</noinclude>' } }, - { name = 'expensive functions', func = test_expensive, + { name = 'not quite too many expensive functions', func = test_expensive_10, + expect = { 'did not error' } + }, + { name = 'too many expensive functions', func = test_expensive_11, expect = 'too many expensive function calls' }, - { name = 'expensive cached', func = test_expensive_cached, + { name = "previously cached titles shouldn't count as expensive", func = test_expensive_cached, + expect = { 'did not error' } + }, + { name = "inexpensive actions shouldn't count as expensive", func = test_expensive_inexpensive, expect = { 'did not error' } }, } -- To view, visit https://gerrit.wikimedia.org/r/178698 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I62f347d4cd9176bd0440215dcbe804c1dc3d4c99 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Scribunto Gerrit-Branch: master Gerrit-Owner: Jackmcbarn <jackmcb...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits