/** * Stop profiling of a function * @param $functionname String: name of the function we have profiled */ function wfProfileOut($functionname = 'missing') { global $wgProfiler; if ($wgProfiler instanceof Profiler || isset($wgProfiler['class'])) { Profiler::instance()->profileOut($functionname); } }
/** * Override the default Symphony constructor to initialise the Log, Config * and Database objects for installation/update. This allows us to use the * normal accessors. */ protected function __construct() { self::$Profiler = Profiler::instance(); self::$Profiler->sample('Engine Initialisation'); if (get_magic_quotes_gpc()) { General::cleanArray($_SERVER); General::cleanArray($_COOKIE); General::cleanArray($_GET); General::cleanArray($_POST); } // Include the default Config for installation. include INSTALL . '/includes/config_default.php'; static::initialiseConfiguration($settings); // Initialize date/time define_safe('__SYM_DATE_FORMAT__', self::Configuration()->get('date_format', 'region')); define_safe('__SYM_TIME_FORMAT__', self::Configuration()->get('time_format', 'region')); define_safe('__SYM_DATETIME_FORMAT__', __SYM_DATE_FORMAT__ . self::Configuration()->get('datetime_separator', 'region') . __SYM_TIME_FORMAT__); DateTimeObj::setSettings(self::Configuration()->get('region')); // Initialize Language, Logs and Database static::initialiseLang(); static::initialiseLog(INSTALL_LOGS . '/install'); static::initialiseDatabase(); // Initialize error handlers GenericExceptionHandler::initialise(Symphony::Log()); GenericErrorHandler::initialise(Symphony::Log()); }
/** * purge is slightly weird because it can be either formed or formless depending * on user permissions */ public function show() { $this->setHeaders(); // This will throw exceptions if there's a problem $this->checkCanExecute($this->getUser()); $user = $this->getUser(); if ($user->pingLimiter('purge')) { // TODO: Display actionthrottledtext return; } if ($user->isAllowed('purge')) { // This will update the database immediately, even on HTTP GET. // Lots of uses may exist for this feature, so just ignore warnings. Profiler::instance()->getTransactionProfiler()->resetExpectations(); $this->redirectParams = wfArrayToCgi(array_diff_key($this->getRequest()->getQueryValues(), ['title' => null, 'action' => null])); if ($this->onSubmit([])) { $this->onSuccess(); } } else { $this->redirectParams = $this->getRequest()->getVal('redirectparams', ''); $form = $this->getForm(); if ($form->show()) { $this->onSuccess(); } } }
/** * Main execution point * * @param null|string $code Confirmation code passed to the page * @throws PermissionsError * @throws ReadOnlyError * @throws UserNotLoggedIn */ function execute($code) { // Ignore things like master queries/connections on GET requests. // It's very convenient to just allow formless link usage. $trxProfiler = Profiler::instance()->getTransactionProfiler(); $this->setHeaders(); $this->checkReadOnly(); $this->checkPermissions(); // This could also let someone check the current email address, so // require both permissions. if (!$this->getUser()->isAllowed('viewmyprivateinfo')) { throw new PermissionsError('viewmyprivateinfo'); } if ($code === null || $code === '') { $this->requireLogin('confirmemail_needlogin'); if (Sanitizer::validateEmail($this->getUser()->getEmail())) { $this->showRequestForm(); } else { $this->getOutput()->addWikiMsg('confirmemail_noemail'); } } else { $old = $trxProfiler->setSilenced(true); $this->attemptConfirm($code); $trxProfiler->setSilenced($old); } }
/** * Get singleton instance * @return Profiler */ public static function getInstance() { if (!isset(self::$instance)) { self::$instance = new Profiler(); } return self::$instance; }
/** * The Symphony constructor initialises the class variables of Symphony. * It will set the DateTime settings, define new date constants and initialise * the correct Language for the currently logged in Author. If magic quotes * are enabled, Symphony will sanitize the `$_SERVER`, `$_COOKIE`, * `$_GET` and `$_POST` arrays. The constructor loads in * the initial Configuration values from the `CONFIG` file */ protected function __construct() { self::$Profiler = Profiler::instance(); if (get_magic_quotes_gpc()) { General::cleanArray($_SERVER); General::cleanArray($_COOKIE); General::cleanArray($_GET); General::cleanArray($_POST); } // Set date format throughout the system define_safe('__SYM_DATE_FORMAT__', self::Configuration()->get('date_format', 'region')); define_safe('__SYM_TIME_FORMAT__', self::Configuration()->get('time_format', 'region')); define_safe('__SYM_DATETIME_FORMAT__', __SYM_DATE_FORMAT__ . self::Configuration()->get('datetime_separator', 'region') . __SYM_TIME_FORMAT__); DateTimeObj::setSettings(self::Configuration()->get('region')); self::initialiseErrorHandler(); // Initialize language management Lang::initialize(); Lang::set(self::$Configuration->get('lang', 'symphony')); self::initialiseCookie(); // If the user is not a logged in Author, turn off the verbose error messages. if (!self::isLoggedIn() && is_null(self::$Author)) { GenericExceptionHandler::$enabled = false; } // Engine is ready. self::$Profiler->sample('Engine Initialisation'); }
/** * Construct a factory based on a configuration array (typically from $wgLBFactoryConf) * @param array $conf */ public function __construct(array $conf) { if (isset($conf['readOnlyReason']) && is_string($conf['readOnlyReason'])) { $this->readOnlyReason = $conf['readOnlyReason']; } $this->chronProt = $this->newChronologyProtector(); $this->trxProfiler = Profiler::instance()->getTransactionProfiler(); }
/** * Begin profiling of a function and return an object that ends profiling of * the function when that object leaves scope. As long as the object is not * specifically linked to other objects, it will fall out of scope at the same * moment that the function to be profiled terminates. * * This is typically called like: * <code>$section = new ProfileSection( __METHOD__ );</code> * * @param string $name Name of the function to profile */ public function __construct( $name ) { $this->name = $name; if ( Profiler::$__instance === null ) { // use this directly to reduce overhead Profiler::instance(); } if ( !( Profiler::$__instance instanceof ProfilerStub ) ) { $this->enabled = true; Profiler::$__instance->profileIn( $this->name ); } }
function execute($code) { // Ignore things like master queries/connections on GET requests. // It's very convenient to just allow formless link usage. Profiler::instance()->getTransactionProfiler()->resetExpectations(); $this->setHeaders(); $this->checkReadOnly(); $this->checkPermissions(); $this->attemptInvalidate($code); }
/** * @param array $lbConf Config for LBFactory::__construct() * @param Config $mainConfig Main config object from MediaWikiServices * @return array */ public static function applyDefaultConfig(array $lbConf, Config $mainConfig) { global $wgCommandLineMode; $lbConf += ['localDomain' => new DatabaseDomain($mainConfig->get('DBname'), null, $mainConfig->get('DBprefix')), 'profiler' => Profiler::instance(), 'trxProfiler' => Profiler::instance()->getTransactionProfiler(), 'replLogger' => LoggerFactory::getInstance('DBReplication'), 'queryLogger' => LoggerFactory::getInstance('DBQuery'), 'connLogger' => LoggerFactory::getInstance('DBConnection'), 'perfLogger' => LoggerFactory::getInstance('DBPerformance'), 'errorLogger' => [MWExceptionHandler::class, 'logException'], 'cliMode' => $wgCommandLineMode, 'hostname' => wfHostname(), 'readOnlyReason' => wfConfiguredReadOnlyReason()]; if ($lbConf['class'] === 'LBFactorySimple') { if (isset($lbConf['servers'])) { // Server array is already explicitly configured; leave alone } elseif (is_array($mainConfig->get('DBservers'))) { foreach ($mainConfig->get('DBservers') as $i => $server) { if ($server['type'] === 'sqlite') { $server += ['dbDirectory' => $mainConfig->get('SQLiteDataDir')]; } elseif ($server['type'] === 'postgres') { $server += ['port' => $mainConfig->get('DBport')]; } $lbConf['servers'][$i] = $server + ['schema' => $mainConfig->get('DBmwschema'), 'tablePrefix' => $mainConfig->get('DBprefix'), 'flags' => DBO_DEFAULT, 'sqlMode' => $mainConfig->get('SQLMode'), 'utf8Mode' => $mainConfig->get('DBmysql5')]; } } else { $flags = DBO_DEFAULT; $flags |= $mainConfig->get('DebugDumpSql') ? DBO_DEBUG : 0; $flags |= $mainConfig->get('DBssl') ? DBO_SSL : 0; $flags |= $mainConfig->get('DBcompress') ? DBO_COMPRESS : 0; $server = ['host' => $mainConfig->get('DBserver'), 'user' => $mainConfig->get('DBuser'), 'password' => $mainConfig->get('DBpassword'), 'dbname' => $mainConfig->get('DBname'), 'schema' => $mainConfig->get('DBmwschema'), 'tablePrefix' => $mainConfig->get('DBprefix'), 'type' => $mainConfig->get('DBtype'), 'load' => 1, 'flags' => $flags, 'sqlMode' => $mainConfig->get('SQLMode'), 'utf8Mode' => $mainConfig->get('DBmysql5')]; if ($server['type'] === 'sqlite') { $server['dbDirectory'] = $mainConfig->get('SQLiteDataDir'); } elseif ($server['type'] === 'postgres') { $server['port'] = $mainConfig->get('DBport'); } $lbConf['servers'] = [$server]; } if (!isset($lbConf['externalClusters'])) { $lbConf['externalClusters'] = $mainConfig->get('ExternalServers'); } } elseif ($lbConf['class'] === 'LBFactoryMulti') { if (isset($lbConf['serverTemplate'])) { $lbConf['serverTemplate']['schema'] = $mainConfig->get('DBmwschema'); $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get('SQLMode'); $lbConf['serverTemplate']['utf8Mode'] = $mainConfig->get('DBmysql5'); } } // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804) $sCache = MediaWikiServices::getInstance()->getLocalServerObjectCache(); if ($sCache->getQoS($sCache::ATTR_EMULATION) > $sCache::QOS_EMULATION_SQL) { $lbConf['srvCache'] = $sCache; } $cCache = ObjectCache::getLocalClusterInstance(); if ($cCache->getQoS($cCache::ATTR_EMULATION) > $cCache::QOS_EMULATION_SQL) { $lbConf['memCache'] = $cCache; } $wCache = MediaWikiServices::getInstance()->getMainWANObjectCache(); if ($wCache->getQoS($wCache::ATTR_EMULATION) > $wCache::QOS_EMULATION_SQL) { $lbConf['wanCache'] = $wCache; } return $lbConf; }
public function execute() { $backend = FileBackendGroup::singleton()->get($this->getOption('b1')); $this->doPerfTest($backend); if ($this->getOption('b2')) { $backend = FileBackendGroup::singleton()->get($this->getOption('b2')); $this->doPerfTest($backend); } $profiler = Profiler::instance(); $profiler->setTemplated(true); //NOTE: as of MW1.21, $profiler->logData() is called implicitly by doMaintenance.php. }
/** * Stop profiling of a function * @param $functionname String: name of the function we have profiled */ function wfProfileOut($functionname = 'missing') { global $wgProfiler; // Wikia change - @author: wladek - 2x faster if ($wgProfiler instanceof Profiler) { if (!$wgProfiler instanceof ProfilerStub) { $wgProfiler->profileOut($functionname); } } elseif (isset($wgProfiler['class'])) { Profiler::instance()->profileOut($functionname); } }
/** * Return a \Profiler instance * * @note Profiler::$__instance only made public in 1.22 therefore * we use our own static to keep overhead at a minimum * * @see $wgProfiler * @see http://www.mediawiki.org/wiki/Profiling#Profiling * * @since 1.9 * * @return \Profiler|null */ public static function getInstance() { // Nothing we can do to avoid the global state here until we have // public access to Profiler::$__instance $profiler = isset($GLOBALS['wgProfiler']['class']); if (self::$instance === null && $profiler) { self::$instance = \Profiler::instance(); } if (!$profiler) { self::reset(); } return self::$instance; }
public function execute() { $backend = FileBackendGroup::singleton()->get($this->getOption('b1')); $this->doPerfTest($backend); if ($this->getOption('b2')) { $backend = FileBackendGroup::singleton()->get($this->getOption('b2')); $this->doPerfTest($backend); } $profiler = Profiler::instance(); $profiler->setTemplated(true); $profiler->logData(); // prints }
/** * Overridden to make changes to resource loader * * @param null|OutputPage $out */ function outputPage(OutputPage $out = null) { global $wgScript, $wgMobileFrontend2Logo; $out = $this->getOutput(); $request = $this->getRequest(); $user = $this->getUser(); $title = $this->getTitle(); // We need to disable all the default RL modules, do that like this $out->clearAllModules(); // Add the mobile js $out->addModules('ext.mobileFrontend2'); // TODO: Hook for adding modules Profiler::instance()->setTemplated(true); $this->initPage($out); $tpl = $this->setupTemplate($this->template, 'skins'); // Give the skin (us) to the template $tpl->setRef('skin', $this); // Language stuff $lang = $this->getLanguage(); $userlang = $lang->getHtmlCode(); $userdir = $lang->getDir(); $tpl->set('lang', $userlang); $tpl->set('dir', $userdir); // Title $tpl->set('title', $out->getPageTitle()); $tpl->set('pagetitle', $out->getHTMLTitle()); // Scriptpath (Used for search and forms) $tpl->setRef('wgScript', $wgScript); // Mobile stuff $tpl->setRef('mobilelogopath', $wgMobileFrontend2Logo); # Add a <div class="mw-content-ltr/rtl"> around the body text # not for special pages or file pages AND only when viewing AND if the page exists # (or is in MW namespace, because that has default content) if (!in_array($title->getNamespace(), array(NS_SPECIAL, NS_FILE)) && in_array($request->getVal('action', 'view'), array('view', 'historysubmit')) && ($title->exists() || $title->getNamespace() == NS_MEDIAWIKI)) { $pageLang = $title->getPageLanguage(); $realBodyAttribs = array('lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(), 'class' => 'mw-content-' . $pageLang->getDir()); $out->mBodytext = Html::rawElement('div', $realBodyAttribs, $out->mBodytext); } $tpl->setRef('bodycontent', MobileFrontend2_PostParse::mangle($out->mBodytext)); // CSS & JS // Make these last $tpl->set('headscripts', $this->getHeadScripts($out)); $tpl->set('csslinks', $out->buildCssLinks()); $tpl->set('bottomscripts', $this->bottomScripts()); // Debug comments and stuff $tpl->set('debughtml', $this->generateDebugHTML()); // Output $res = $tpl->execute(); // result may be an error $this->printOrError($res); }
/** * The Symphony constructor initialises the class variables of Symphony. At present * constructor has a couple of responsibilities: * - Start a profiler instance * - If magic quotes are enabled, clean `$_SERVER`, `$_COOKIE`, `$_GET` and `$_POST` arrays * - Initialise the correct Language for the currently logged in Author. * - Start the session and adjust the error handling if the user is logged in */ protected function __construct() { self::$Profiler = Profiler::instance(); if (get_magic_quotes_gpc()) { General::cleanArray($_SERVER); General::cleanArray($_COOKIE); General::cleanArray($_GET); General::cleanArray($_POST); } // Initialize language management Lang::initialize(); Lang::set(self::$Configuration->get('lang', 'symphony')); self::initialiseCookie(); // If the user is not a logged in Author, turn off the verbose error messages. if (!self::isLoggedIn() && is_null(self::$Author)) { GenericExceptionHandler::$enabled = false; } // Engine is ready. self::$Profiler->sample('Engine Initialisation'); }
/** * Ends this task peacefully * @param string $mode Use 'fast' to always skip job running */ public function restInPeace($mode = 'fast') { // Assure deferred updates are not in the main transaction wfGetLBFactory()->commitMasterChanges(__METHOD__); // Ignore things like master queries/connections on GET requests // as long as they are in deferred updates (which catch errors). Profiler::instance()->getTransactionProfiler()->resetExpectations(); // Do any deferred jobs DeferredUpdates::doUpdates('enqueue'); // Make sure any lazy jobs are pushed JobQueueGroup::pushLazyJobs(); // Now that everything specific to this request is done, // try to occasionally run jobs (if enabled) from the queues if ($mode === 'normal') { $this->triggerJobs(); } // Log profiling data, e.g. in the database or UDP wfLogProfilingData(); // Commit and close up! $factory = wfGetLBFactory(); $factory->commitMasterChanges(__METHOD__); $factory->shutdown(LBFactory::SHUTDOWN_NO_CHRONPROT); wfDebug("Request ended normally\n"); }
/** * Return the text of a template, after recursively * replacing any variables or templates within the template. * * @param array $piece The parts of the template * $piece['title']: the title, i.e. the part before the | * $piece['parts']: the parameter array * $piece['lineStart']: whether the brace was at the start of a line * @param PPFrame $frame The current frame, contains template arguments * @throws MWException * @return string The text of the template * @private */ function braceSubstitution($piece, $frame) { wfProfileIn(__METHOD__); wfProfileIn(__METHOD__ . '-setup'); # Flags $found = false; # $text has been filled $nowiki = false; # wiki markup in $text should be escaped $isHTML = false; # $text is HTML, armour it against wikitext transformation $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered $isChildObj = false; # $text is a DOM node needing expansion in a child frame $isLocalObj = false; # $text is a DOM node needing expansion in the current frame # Title object, where $text came from $title = false; # $part1 is the bit before the first |, and must contain only title characters. # Various prefixes will be stripped from it later. $titleWithSpaces = $frame->expand($piece['title']); $part1 = trim($titleWithSpaces); $titleText = false; # Original title text preserved for various purposes $originalTitle = $part1; # $args is a list of argument nodes, starting from index 0, not including $part1 # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object $args = null == $piece['parts'] ? array() : $piece['parts']; wfProfileOut(__METHOD__ . '-setup'); $titleProfileIn = null; // profile templates # SUBST wfProfileIn(__METHOD__ . '-modifiers'); if (!$found) { $substMatch = $this->mSubstWords->matchStartAndRemove($part1); # Possibilities for substMatch: "subst", "safesubst" or FALSE # Decide whether to expand template or keep wikitext as-is. if ($this->ot['wiki']) { if ($substMatch === false) { $literal = true; # literal when in PST with no prefix } else { $literal = false; # expand when in PST with subst: or safesubst: } } else { if ($substMatch == 'subst') { $literal = true; # literal when not in PST with plain subst: } else { $literal = false; # expand when not in PST with safesubst: or no prefix } } if ($literal) { $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args); $isLocalObj = true; $found = true; } } # Variables if (!$found && $args->getLength() == 0) { $id = $this->mVariables->matchStartToEnd($part1); if ($id !== false) { $text = $this->getVariableValue($id, $frame); if (MagicWord::getCacheTTL($id) > -1) { $this->mOutput->updateCacheExpiry(MagicWord::getCacheTTL($id)); } $found = true; } } # MSG, MSGNW and RAW if (!$found) { # Check for MSGNW: $mwMsgnw = MagicWord::get('msgnw'); if ($mwMsgnw->matchStartAndRemove($part1)) { $nowiki = true; } else { # Remove obsolete MSG: $mwMsg = MagicWord::get('msg'); $mwMsg->matchStartAndRemove($part1); } # Check for RAW: $mwRaw = MagicWord::get('raw'); if ($mwRaw->matchStartAndRemove($part1)) { $forceRawInterwiki = true; } } wfProfileOut(__METHOD__ . '-modifiers'); # Parser functions if (!$found) { wfProfileIn(__METHOD__ . '-pfunc'); $colonPos = strpos($part1, ':'); if ($colonPos !== false) { $func = substr($part1, 0, $colonPos); $funcArgs = array(trim(substr($part1, $colonPos + 1))); for ($i = 0; $i < $args->getLength(); $i++) { $funcArgs[] = $args->item($i); } try { $result = $this->callParserFunction($frame, $func, $funcArgs); } catch (Exception $ex) { wfProfileOut(__METHOD__ . '-pfunc'); wfProfileOut(__METHOD__); throw $ex; } # The interface for parser functions allows for extracting # flags into the local scope. Extract any forwarded flags # here. extract($result); } wfProfileOut(__METHOD__ . '-pfunc'); } # Finish mangling title and then check for loops. # Set $title to a Title object and $titleText to the PDBK if (!$found) { $ns = NS_TEMPLATE; # Split the title into page and subpage $subpage = ''; $relative = $this->maybeDoSubpageLink($part1, $subpage); if ($part1 !== $relative) { $part1 = $relative; $ns = $this->mTitle->getNamespace(); } $title = Title::newFromText($part1, $ns); if ($title) { $titleText = $title->getPrefixedText(); # Check for language variants if the template is not found if ($this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0) { $this->getConverterLanguage()->findVariantLink($part1, $title, true); } # Do recursion depth check $limit = $this->mOptions->getMaxTemplateDepth(); if ($frame->depth >= $limit) { $found = true; $text = '<span class="error">' . wfMessage('parser-template-recursion-depth-warning')->numParams($limit)->inContentLanguage()->text() . '</span>'; } } } # Load from database if (!$found && $title) { if (!Profiler::instance()->isPersistent()) { # Too many unique items can kill profiling DBs/collectors $titleProfileIn = __METHOD__ . "-title-" . $title->getPrefixedDBkey(); wfProfileIn($titleProfileIn); // template in } wfProfileIn(__METHOD__ . '-loadtpl'); if (!$title->isExternal()) { if ($title->isSpecialPage() && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html']) { // Pass the template arguments as URL parameters. // "uselang" will have no effect since the Language object // is forced to the one defined in ParserOptions. $pageArgs = array(); for ($i = 0; $i < $args->getLength(); $i++) { $bits = $args->item($i)->splitArg(); if (strval($bits['index']) === '') { $name = trim($frame->expand($bits['name'], PPFrame::STRIP_COMMENTS)); $value = trim($frame->expand($bits['value'])); $pageArgs[$name] = $value; } } // Create a new context to execute the special page $context = new RequestContext(); $context->setTitle($title); $context->setRequest(new FauxRequest($pageArgs)); $context->setUser($this->getUser()); $context->setLanguage($this->mOptions->getUserLangObj()); $ret = SpecialPageFactory::capturePath($title, $context); if ($ret) { $text = $context->getOutput()->getHTML(); $this->mOutput->addOutputPageMetadata($context->getOutput()); $found = true; $isHTML = true; $this->disableCache(); } } elseif (MWNamespace::isNonincludable($title->getNamespace())) { $found = false; # access denied wfDebug(__METHOD__ . ": template inclusion denied for " . $title->getPrefixedDBkey() . "\n"); } else { list($text, $title) = $this->getTemplateDom($title); if ($text !== false) { $found = true; $isChildObj = true; } } # If the title is valid but undisplayable, make a link to it if (!$found && ($this->ot['html'] || $this->ot['pre'])) { $text = "[[:{$titleText}]]"; $found = true; } } elseif ($title->isTrans()) { # Interwiki transclusion if ($this->ot['html'] && !$forceRawInterwiki) { $text = $this->interwikiTransclude($title, 'render'); $isHTML = true; } else { $text = $this->interwikiTransclude($title, 'raw'); # Preprocess it like a template $text = $this->preprocessToDom($text, self::PTD_FOR_INCLUSION); $isChildObj = true; } $found = true; } # Do infinite loop check # This has to be done after redirect resolution to avoid infinite loops via redirects if (!$frame->loopCheck($title)) { $found = true; $text = '<span class="error">' . wfMessage('parser-template-loop-warning', $titleText)->inContentLanguage()->text() . '</span>'; wfDebug(__METHOD__ . ": template loop broken at '{$titleText}'\n"); } wfProfileOut(__METHOD__ . '-loadtpl'); } # If we haven't found text to substitute by now, we're done # Recover the source wikitext and return it if (!$found) { $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args); if ($titleProfileIn) { wfProfileOut($titleProfileIn); // template out } wfProfileOut(__METHOD__); return array('object' => $text); } # Expand DOM-style return values in a child frame if ($isChildObj) { # Clean up argument array $newFrame = $frame->newChild($args, $title); if ($nowiki) { $text = $newFrame->expand($text, PPFrame::RECOVER_ORIG); } elseif ($titleText !== false && $newFrame->isEmpty()) { # Expansion is eligible for the empty-frame cache if (isset($this->mTplExpandCache[$titleText])) { $text = $this->mTplExpandCache[$titleText]; } else { $text = $newFrame->expand($text); $this->mTplExpandCache[$titleText] = $text; } } else { # Uncached expansion $text = $newFrame->expand($text); } } if ($isLocalObj && $nowiki) { $text = $frame->expand($text, PPFrame::RECOVER_ORIG); $isLocalObj = false; } if ($titleProfileIn) { wfProfileOut($titleProfileIn); // template out } # Replace raw HTML by a placeholder if ($isHTML) { $text = $this->insertStripItem($text); } elseif ($nowiki && ($this->ot['html'] || $this->ot['pre'])) { # Escape nowiki-style return values $text = wfEscapeWikiText($text); } elseif (is_string($text) && !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\\*)/', $text)) { # Bug 529: if the template begins with a table or block-level # element, it should be treated as beginning a new line. # This behavior is somewhat controversial. $text = "\n" . $text; } if (is_string($text) && !$this->incrementIncludeSize('post-expand', strlen($text))) { # Error, oversize inclusion if ($titleText !== false) { # Make a working, properly escaped link if possible (bug 23588) $text = "[[:{$titleText}]]"; } else { # This will probably not be a working link, but at least it may # provide some hint of where the problem is preg_replace('/^:/', '', $originalTitle); $text = "[[:{$originalTitle}]]"; } $text .= $this->insertStripItem('<!-- WARNING: template omitted, post-expand include size too large -->'); $this->limitationWarn('post-expand-template-inclusion'); } if ($isLocalObj) { $ret = array('object' => $text); } else { $ret = array('text' => $text); } wfProfileOut(__METHOD__); return $ret; }
/** * Get a connection by index * This is the main entry point for this class. * * @param int $i Server index * @param array|string|bool $groups Query group(s), or false for the generic reader * @param string|bool $wiki Wiki ID, or false for the current wiki * * @throws MWException * @return DatabaseBase */ public function getConnection($i, $groups = array(), $wiki = false) { if ($i === null || $i === false) { throw new MWException('Attempt to call ' . __METHOD__ . ' with invalid server index'); } if ($wiki === wfWikiID()) { $wiki = false; } $groups = $groups === false || $groups === array() ? array(false) : (array) $groups; $masterOnly = $i == DB_MASTER || $i == $this->getWriterIndex(); $oldConnsOpened = $this->connsOpened; // connections open now if ($i == DB_MASTER) { $i = $this->getWriterIndex(); } else { # Try to find an available server in any the query groups (in order) foreach ($groups as $group) { $groupIndex = $this->getReaderIndex($group, $wiki); if ($groupIndex !== false) { $i = $groupIndex; break; } } } # Operation-based index if ($i == DB_SLAVE) { $this->mLastError = 'Unknown error'; // reset error string # Try the general server pool if $groups are unavailable. $i = in_array(false, $groups, true) ? false : $this->getReaderIndex(false, $wiki); # Couldn't find a working server in getReaderIndex()? if ($i === false) { $this->mLastError = 'No working slave server: ' . $this->mLastError; return $this->reportConnectionError(); } } # Now we have an explicit index into the servers array $conn = $this->openConnection($i, $wiki); if (!$conn) { return $this->reportConnectionError(); } # Profile any new connections that happen if ($this->connsOpened > $oldConnsOpened) { $host = $conn->getServer(); $dbname = $conn->getDBname(); $trxProf = Profiler::instance()->getTransactionProfiler(); $trxProf->recordConnection($host, $dbname, $masterOnly); } return $conn; }
/** * Execute a shell command, with time and memory limits mirrored from the PHP * configuration if supported. * * @param string|string[] $cmd If string, a properly shell-escaped command line, * or an array of unescaped arguments, in which case each value will be escaped * Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'" * @param null|mixed &$retval Optional, will receive the program's exit code. * (non-zero is usually failure). If there is an error from * read, select, or proc_open(), this will be set to -1. * @param array $environ Optional environment variables which should be * added to the executed command environment. * @param array $limits Optional array with limits(filesize, memory, time, walltime) * this overwrites the global wgMaxShell* limits. * @param array $options Array of options: * - duplicateStderr: Set this to true to duplicate stderr to stdout, * including errors from limit.sh * - profileMethod: By default this function will profile based on the calling * method. Set this to a string for an alternative method to profile from * * @return string Collected stdout as a string */ function wfShellExec($cmd, &$retval = null, $environ = array(), $limits = array(), $options = array()) { global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime, $wgMaxShellWallClockTime, $wgShellCgroup; $disabled = wfShellExecDisabled(); if ($disabled) { $retval = 1; return $disabled == 'safemode' ? 'Unable to run external programs in safe mode.' : 'Unable to run external programs, proc_open() is disabled.'; } $includeStderr = isset($options['duplicateStderr']) && $options['duplicateStderr']; $profileMethod = isset($options['profileMethod']) ? $options['profileMethod'] : wfGetCaller(); wfInitShellLocale(); $envcmd = ''; foreach ($environ as $k => $v) { if (wfIsWindows()) { /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves * appear in the environment variable, so we must use carat escaping as documented in * http://technet.microsoft.com/en-us/library/cc723564.aspx * Note however that the quote isn't listed there, but is needed, and the parentheses * are listed there but doesn't appear to need it. */ $envcmd .= "set {$k}=" . preg_replace('/([&|()<>^"])/', '^\\1', $v) . '&& '; } else { /* Assume this is a POSIX shell, thus required to accept variable assignments before the command * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01 */ $envcmd .= "{$k}=" . escapeshellarg($v) . ' '; } } if (is_array($cmd)) { $cmd = wfEscapeShellArg($cmd); } $cmd = $envcmd . $cmd; $useLogPipe = false; if (is_executable('/bin/bash')) { $time = intval(isset($limits['time']) ? $limits['time'] : $wgMaxShellTime); if (isset($limits['walltime'])) { $wallTime = intval($limits['walltime']); } elseif (isset($limits['time'])) { $wallTime = $time; } else { $wallTime = intval($wgMaxShellWallClockTime); } $mem = intval(isset($limits['memory']) ? $limits['memory'] : $wgMaxShellMemory); $filesize = intval(isset($limits['filesize']) ? $limits['filesize'] : $wgMaxShellFileSize); if ($time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0) { $cmd = '/bin/bash ' . escapeshellarg("{$IP}/includes/limit.sh") . ' ' . escapeshellarg($cmd) . ' ' . escapeshellarg("MW_INCLUDE_STDERR=" . ($includeStderr ? '1' : '') . ';' . "MW_CPU_LIMIT={$time}; " . 'MW_CGROUP=' . escapeshellarg($wgShellCgroup) . '; ' . "MW_MEM_LIMIT={$mem}; " . "MW_FILE_SIZE_LIMIT={$filesize}; " . "MW_WALL_CLOCK_LIMIT={$wallTime}; " . "MW_USE_LOG_PIPE=yes"); $useLogPipe = true; } elseif ($includeStderr) { $cmd .= ' 2>&1'; } } elseif ($includeStderr) { $cmd .= ' 2>&1'; } wfDebug("wfShellExec: {$cmd}\n"); $desc = array(0 => array('file', 'php://stdin', 'r'), 1 => array('pipe', 'w'), 2 => array('file', 'php://stderr', 'w')); if ($useLogPipe) { $desc[3] = array('pipe', 'w'); } $pipes = null; $scoped = Profiler::instance()->scopedProfileIn(__FUNCTION__ . '-' . $profileMethod); $proc = proc_open($cmd, $desc, $pipes); if (!$proc) { wfDebugLog('exec', "proc_open() failed: {$cmd}"); $retval = -1; return ''; } $outBuffer = $logBuffer = ''; $emptyArray = array(); $status = false; $logMsg = false; // According to the documentation, it is possible for stream_select() // to fail due to EINTR. I haven't managed to induce this in testing // despite sending various signals. If it did happen, the error // message would take the form: // // stream_select(): unable to select [4]: Interrupted system call (max_fd=5) // // where [4] is the value of the macro EINTR and "Interrupted system // call" is string which according to the Linux manual is "possibly" // localised according to LC_MESSAGES. $eintr = defined('SOCKET_EINTR') ? SOCKET_EINTR : 4; $eintrMessage = "stream_select(): unable to select [{$eintr}]"; // Build a table mapping resource IDs to pipe FDs to work around a // PHP 5.3 issue in which stream_select() does not preserve array keys // <https://bugs.php.net/bug.php?id=53427>. $fds = array(); foreach ($pipes as $fd => $pipe) { $fds[(int) $pipe] = $fd; } $running = true; $timeout = null; $numReadyPipes = 0; while ($running === true || $numReadyPipes !== 0) { if ($running) { $status = proc_get_status($proc); // If the process has terminated, switch to nonblocking selects // for getting any data still waiting to be read. if (!$status['running']) { $running = false; $timeout = 0; } } $readyPipes = $pipes; // Clear last error // @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged @trigger_error(''); $numReadyPipes = @stream_select($readyPipes, $emptyArray, $emptyArray, $timeout); if ($numReadyPipes === false) { // @codingStandardsIgnoreEnd $error = error_get_last(); if (strncmp($error['message'], $eintrMessage, strlen($eintrMessage)) == 0) { continue; } else { trigger_error($error['message'], E_USER_WARNING); $logMsg = $error['message']; break; } } foreach ($readyPipes as $pipe) { $block = fread($pipe, 65536); $fd = $fds[(int) $pipe]; if ($block === '') { // End of file fclose($pipes[$fd]); unset($pipes[$fd]); if (!$pipes) { break 2; } } elseif ($block === false) { // Read error $logMsg = "Error reading from pipe"; break 2; } elseif ($fd == 1) { // From stdout $outBuffer .= $block; } elseif ($fd == 3) { // From log FD $logBuffer .= $block; if (strpos($block, "\n") !== false) { $lines = explode("\n", $logBuffer); $logBuffer = array_pop($lines); foreach ($lines as $line) { wfDebugLog('exec', $line); } } } } } foreach ($pipes as $pipe) { fclose($pipe); } // Use the status previously collected if possible, since proc_get_status() // just calls waitpid() which will not return anything useful the second time. if ($running) { $status = proc_get_status($proc); } if ($logMsg !== false) { // Read/select error $retval = -1; proc_close($proc); } elseif ($status['signaled']) { $logMsg = "Exited with signal {$status['termsig']}"; $retval = 128 + $status['termsig']; proc_close($proc); } else { if ($status['running']) { $retval = proc_close($proc); } else { $retval = $status['exitcode']; proc_close($proc); } if ($retval == 127) { $logMsg = "Possibly missing executable file"; } elseif ($retval >= 129 && $retval <= 192) { $logMsg = "Probably exited with signal " . ($retval - 128); } } if ($logMsg !== false) { wfDebugLog('exec', "{$logMsg}: {$cmd}"); } return $outBuffer; }
} else { $profName = $fname . '-extensions-' . implode('::', $func); } } else { $profName = $fname . '-extensions-' . strval($func); } $ps_ext_func = Profiler::instance()->scopedProfileIn($profName); call_user_func($func); Profiler::instance()->scopedProfileOut($ps_ext_func); } // If the session user has a 0 id but a valid name, that means we need to // autocreate it. if (!defined('MW_NO_SESSION') && !$wgCommandLineMode) { $sessionUser = MediaWiki\Session\SessionManager::getGlobalSession()->getUser(); if ($sessionUser->getId() === 0 && User::isValidUserName($sessionUser->getName())) { $ps_autocreate = Profiler::instance()->scopedProfileIn($fname . '-autocreate'); $res = MediaWiki\Auth\AuthManager::singleton()->autoCreateUser($sessionUser, MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_SESSION, true); Profiler::instance()->scopedProfileOut($ps_autocreate); \MediaWiki\Logger\LoggerFactory::getInstance('authevents')->info('Autocreation attempt', ['event' => 'autocreate', 'status' => $res]); unset($res); } unset($sessionUser); } if (!$wgCommandLineMode) { Pingback::schedulePingback(); } wfDebug("Fully initialised\n"); $wgFullyInitialised = true; Profiler::instance()->scopedProfileOut($ps_extensions); Profiler::instance()->scopedProfileOut($ps_setup);
function execute($sql) { if (defined("LIMB_APP_MODE") && LIMB_APP_MODE == "devel") { Profiler::instance()->addHit("Query"); Profiler::instance()->startIncrementCheckpoint("sql_time"); } $sql = mb_convert_encoding($sql, 'Windows-1251', 'UTF-8'); $result = mssql_query($sql, $this->getConnectionId()); if (defined("LIMB_APP_MODE") && LIMB_APP_MODE == "devel") { error_log($sql . "\n\n\n", 3, LIMB_VAR_DIR . '/log/query.log'); Profiler::instance()->stopIncrementCheckpoint("sql_time"); } if ($result === false) { $this->_raiseError($sql); } return $result; }
/** * initialize various variables and generate the template * * @param OutputPage $out */ function outputPage(OutputPage $out = null) { Profiler::instance()->setTemplated(true); $oldContext = null; if ($out !== null) { // Deprecated since 1.20, note added in 1.25 wfDeprecated(__METHOD__, '1.25'); $oldContext = $this->getContext(); $this->setContext($out->getContext()); } $out = $this->getOutput(); $this->initPage($out); $tpl = $this->prepareQuickTemplate($out); // execute template $res = $tpl->execute(); // result may be an error $this->printOrError($res); if ($oldContext) { $this->setContext($oldContext); } }
/** * Rollback a transaction previously started using begin(). * If no transaction is in progress, a warning is issued. * * No-op on non-transactional databases. * * @param $fname string */ final public function rollback( $fname = __METHOD__ ) { if ( !$this->mTrxLevel ) { wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); } $this->doRollback( $fname ); $this->mTrxIdleCallbacks = array(); // cancel $this->mTrxPreCommitCallbacks = array(); // cancel if ( $this->mTrxDoneWrites ) { Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); } $this->mTrxDoneWrites = false; }
/** * Run jobs of the specified number/type for the specified time * * The response map has a 'job' field that lists status of each job, including: * - type : the job type * - status : ok/failed * - error : any error message string * - time : the job run time in ms * The response map also has: * - backoffs : the (job type => seconds) map of backoff times * - elapsed : the total time spent running tasks in ms * - reached : the reason the script finished, one of (none-ready, job-limit, time-limit) * * This method outputs status information only if a debug handler was set. * Any exceptions are caught and logged, but are not reported as output. * * @param array $options Map of parameters: * - type : the job type (or false for the default types) * - maxJobs : maximum number of jobs to run * - maxTime : maximum time in seconds before stopping * - throttle : whether to respect job backoff configuration * @return array Summary response that can easily be JSON serialized */ public function run(array $options) { global $wgJobClasses, $wgTrxProfilerLimits; $response = array('jobs' => array(), 'reached' => 'none-ready'); $type = isset($options['type']) ? $options['type'] : false; $maxJobs = isset($options['maxJobs']) ? $options['maxJobs'] : false; $maxTime = isset($options['maxTime']) ? $options['maxTime'] : false; $noThrottle = isset($options['throttle']) && !$options['throttle']; if ($type !== false && !isset($wgJobClasses[$type])) { $response['reached'] = 'none-possible'; return $response; } // Bail out if in read-only mode if (wfReadOnly()) { $response['reached'] = 'read-only'; return $response; } // Catch huge single updates that lead to slave lag $trxProfiler = Profiler::instance()->getTransactionProfiler(); $trxProfiler->setLogger(LoggerFactory::getInstance('DBPerformance')); $trxProfiler->setExpectations($wgTrxProfilerLimits['JobRunner'], __METHOD__); // Bail out if there is too much DB lag. // This check should not block as we want to try other wiki queues. $maxAllowedLag = 3; list(, $maxLag) = wfGetLB(wfWikiID())->getMaxLag(); if ($maxLag >= $maxAllowedLag) { $response['reached'] = 'slave-lag-limit'; return $response; } $group = JobQueueGroup::singleton(); // Flush any pending DB writes for sanity wfGetLBFactory()->commitAll(); // Some jobs types should not run until a certain timestamp $backoffs = array(); // map of (type => UNIX expiry) $backoffDeltas = array(); // map of (type => seconds) $wait = 'wait'; // block to read backoffs the first time $stats = RequestContext::getMain()->getStats(); $jobsPopped = 0; $timeMsTotal = 0; $flags = JobQueueGroup::USE_CACHE; $startTime = microtime(true); // time since jobs started running $checkLagPeriod = 1.0; // check slave lag this many seconds $lastCheckTime = 1; // timestamp of last slave check do { // Sync the persistent backoffs with concurrent runners $backoffs = $this->syncBackoffDeltas($backoffs, $backoffDeltas, $wait); $blacklist = $noThrottle ? array() : array_keys($backoffs); $wait = 'nowait'; // less important now if ($type === false) { $job = $group->pop(JobQueueGroup::TYPE_DEFAULT, $flags, $blacklist); } elseif (in_array($type, $blacklist)) { $job = false; // requested queue in backoff state } else { $job = $group->pop($type); // job from a single queue } if ($job) { // found a job $popTime = time(); $jType = $job->getType(); // Back off of certain jobs for a while (for throttling and for errors) $ttw = $this->getBackoffTimeToWait($job); if ($ttw > 0) { // Always add the delta for other runners in case the time running the // job negated the backoff for each individually but not collectively. $backoffDeltas[$jType] = isset($backoffDeltas[$jType]) ? $backoffDeltas[$jType] + $ttw : $ttw; $backoffs = $this->syncBackoffDeltas($backoffs, $backoffDeltas, $wait); } $msg = $job->toString() . " STARTING"; $this->logger->debug($msg); $this->debugCallback($msg); // Run the job... $jobStartTime = microtime(true); try { ++$jobsPopped; $status = $job->run(); $error = $job->getLastError(); $this->commitMasterChanges($job); DeferredUpdates::doUpdates(); $this->commitMasterChanges($job); } catch (Exception $e) { MWExceptionHandler::rollbackMasterChangesAndLog($e); $status = false; $error = get_class($e) . ': ' . $e->getMessage(); MWExceptionHandler::logException($e); } // Commit all outstanding connections that are in a transaction // to get a fresh repeatable read snapshot on every connection. // Note that jobs are still responsible for handling slave lag. wfGetLBFactory()->commitAll(); // Clear out title cache data from prior snapshots LinkCache::singleton()->clear(); $timeMs = intval((microtime(true) - $jobStartTime) * 1000); $timeMsTotal += $timeMs; // Record how long jobs wait before getting popped $readyTs = $job->getReadyTimestamp(); if ($readyTs) { $pickupDelay = $popTime - $readyTs; $stats->timing('jobqueue.pickup_delay.all', 1000 * $pickupDelay); $stats->timing("jobqueue.pickup_delay.{$jType}", 1000 * $pickupDelay); } // Record root job age for jobs being run $root = $job->getRootJobParams(); if ($root['rootJobTimestamp']) { $age = $popTime - wfTimestamp(TS_UNIX, $root['rootJobTimestamp']); $stats->timing("jobqueue.pickup_root_age.{$jType}", 1000 * $age); } // Track the execution time for jobs $stats->timing("jobqueue.run.{$jType}", $timeMs); // Mark the job as done on success or when the job cannot be retried if ($status !== false || !$job->allowRetries()) { $group->ack($job); // done } // Back off of certain jobs for a while (for throttling and for errors) if ($status === false && mt_rand(0, 49) == 0) { $ttw = max($ttw, 30); // too many errors $backoffDeltas[$jType] = isset($backoffDeltas[$jType]) ? $backoffDeltas[$jType] + $ttw : $ttw; } if ($status === false) { $msg = $job->toString() . " t={$timeMs} error={$error}"; $this->logger->error($msg); $this->debugCallback($msg); } else { $msg = $job->toString() . " t={$timeMs} good"; $this->logger->info($msg); $this->debugCallback($msg); } $response['jobs'][] = array('type' => $jType, 'status' => $status === false ? 'failed' : 'ok', 'error' => $error, 'time' => $timeMs); // Break out if we hit the job count or wall time limits... if ($maxJobs && $jobsPopped >= $maxJobs) { $response['reached'] = 'job-limit'; break; } elseif ($maxTime && microtime(true) - $startTime > $maxTime) { $response['reached'] = 'time-limit'; break; } // Don't let any of the main DB slaves get backed up. // This only waits for so long before exiting and letting // other wikis in the farm (on different masters) get a chance. $timePassed = microtime(true) - $lastCheckTime; if ($timePassed >= $checkLagPeriod || $timePassed < 0) { if (!wfWaitForSlaves($lastCheckTime, false, '*', $maxAllowedLag)) { $response['reached'] = 'slave-lag-limit'; break; } $lastCheckTime = microtime(true); } // Don't let any queue slaves/backups fall behind if ($jobsPopped > 0 && $jobsPopped % 100 == 0) { $group->waitForBackups(); } // Bail if near-OOM instead of in a job if (!$this->checkMemoryOK()) { $response['reached'] = 'memory-limit'; break; } } } while ($job); // stop when there are no jobs // Sync the persistent backoffs for the next runJobs.php pass if ($backoffDeltas) { $this->syncBackoffDeltas($backoffs, $backoffDeltas, 'wait'); } $response['backoffs'] = $backoffs; $response['elapsed'] = $timeMsTotal; return $response; }
/** * Get a Swift container stat array, possibly from process cache. * Use $reCache if the file count or byte count is needed. * * @param string $container Container name * @param bool $bypassCache Bypass all caches and load from Swift * @return array|bool|null False on 404, null on failure */ protected function getContainerStat($container, $bypassCache = false) { $ps = Profiler::instance()->scopedProfileIn(__METHOD__ . "-{$this->name}"); if ($bypassCache) { // purge cache $this->containerStatCache->clear($container); } elseif (!$this->containerStatCache->has($container, 'stat')) { $this->primeContainerCache(array($container)); // check persistent cache } if (!$this->containerStatCache->has($container, 'stat')) { $auth = $this->getAuthentication(); if (!$auth) { return null; } list($rcode, $rdesc, $rhdrs, $rbody, $rerr) = $this->http->run(array('method' => 'HEAD', 'url' => $this->storageUrl($auth, $container), 'headers' => $this->authTokenHeaders($auth))); if ($rcode === 204) { $stat = array('count' => $rhdrs['x-container-object-count'], 'bytes' => $rhdrs['x-container-bytes-used']); if ($bypassCache) { return $stat; } else { $this->containerStatCache->set($container, 'stat', $stat); // cache it $this->setContainerCache($container, $stat); // update persistent cache } } elseif ($rcode === 404) { return false; } else { $this->onError(null, __METHOD__, array('cont' => $container), $rerr, $rcode, $rdesc); return null; } } return $this->containerStatCache->get($container, 'stat'); }
/** * Execute a special page path. * The path may contain parameters, e.g. Special:Name/Params * Extracts the special page name and call the execute method, passing the parameters * * Returns a title object if the page is redirected, false if there was no such special * page, and true if it was successful. * * @param Title $title * @param IContextSource $context * @param bool $including Bool output is being captured for use in {{special:whatever}} * * @return bool */ public static function executePath(Title &$title, IContextSource &$context, $including = false) { // @todo FIXME: Redirects broken due to this call $bits = explode('/', $title->getDBkey(), 2); $name = $bits[0]; if (!isset($bits[1])) { // bug 2087 $par = null; } else { $par = $bits[1]; } $page = self::getPage($name); if (!$page) { $context->getOutput()->setArticleRelated(false); $context->getOutput()->setRobotPolicy('noindex,nofollow'); global $wgSend404Code; if ($wgSend404Code) { $context->getOutput()->setStatusCode(404); } $context->getOutput()->showErrorPage('nosuchspecialpage', 'nospecialpagetext'); return false; } if (!$including) { // Narrow DB query expectations for this HTTP request $trxLimits = $context->getConfig()->get('TrxProfilerLimits'); $trxProfiler = Profiler::instance()->getTransactionProfiler(); if ($context->getRequest()->wasPosted() && !$page->doesWrites()) { $trxProfiler->setExpectations($trxLimits['POST-nonwrite'], __METHOD__); $context->getRequest()->markAsSafeRequest(); } } // Page exists, set the context $page->setContext($context); if (!$including) { // Redirect to canonical alias for GET commands // Not for POST, we'd lose the post data, so it's best to just distribute // the request. Such POST requests are possible for old extensions that // generate self-links without being aware that their default name has // changed. if ($name != $page->getLocalName() && !$context->getRequest()->wasPosted()) { $query = $context->getRequest()->getQueryValues(); unset($query['title']); $title = $page->getPageTitle($par); $url = $title->getFullURL($query); $context->getOutput()->redirect($url); return $title; } else { $context->setTitle($page->getPageTitle($par)); } } elseif (!$page->isIncludable()) { return false; } $page->including($including); // Execute special page $page->run($par); return true; }
/** * Run an SQL query and return the result. Normally throws a DBQueryError * on failure. If errors are ignored, returns false instead. * * In new code, the query wrappers select(), insert(), update(), delete(), * etc. should be used where possible, since they give much better DBMS * independence and automatically quote or validate user input in a variety * of contexts. This function is generally only useful for queries which are * explicitly DBMS-dependent and are unsupported by the query wrappers, such * as CREATE TABLE. * * However, the query wrappers themselves should call this function. * * @param string $sql SQL query * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST * comment (you can use __METHOD__ or add some extra info) * @param bool $tempIgnore Whether to avoid throwing an exception on errors... * maybe best to catch the exception instead? * @throws MWException * @return bool|ResultWrapper True for a successful write query, ResultWrapper object * for a successful read query, or false on failure if $tempIgnore set */ public function query($sql, $fname = __METHOD__, $tempIgnore = false) { global $wgUser; $this->mLastQuery = $sql; $isWriteQuery = $this->isWriteQuery($sql); if ($isWriteQuery) { $reason = $this->getReadOnlyReason(); if ($reason !== false) { throw new DBReadOnlyError($this, "Database is read-only: {$reason}"); } # Set a flag indicating that writes have been done $this->mDoneWrites = microtime(true); } # Add a comment for easy SHOW PROCESSLIST interpretation if (is_object($wgUser) && $wgUser->isItemLoaded('name')) { $userName = $wgUser->getName(); if (mb_strlen($userName) > 15) { $userName = mb_substr($userName, 0, 15) . '...'; } $userName = str_replace('/', '', $userName); } else { $userName = ''; } // Add trace comment to the begin of the sql string, right after the operator. // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) $commentedSql = preg_replace('/\\s|$/', " /* {$fname} {$userName} */ ", $sql, 1); if (!$this->mTrxLevel && $this->getFlag(DBO_TRX) && $this->isTransactableQuery($sql)) { $this->begin(__METHOD__ . " ({$fname})"); $this->mTrxAutomatic = true; } # Keep track of whether the transaction has write queries pending if ($this->mTrxLevel && !$this->mTrxDoneWrites && $isWriteQuery) { $this->mTrxDoneWrites = true; $this->getTransactionProfiler()->transactionWritingIn($this->mServer, $this->mDBname, $this->mTrxShortId); } $isMaster = !is_null($this->getLBInfo('master')); # generalizeSQL will probably cut down the query to reasonable # logging size most of the time. The substr is really just a sanity check. if ($isMaster) { $queryProf = 'query-m: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query-master'; } else { $queryProf = 'query: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query'; } # Include query transaction state $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : ""; $profiler = Profiler::instance(); if (!$profiler instanceof ProfilerStub) { $totalProfSection = $profiler->scopedProfileIn($totalProf); $queryProfSection = $profiler->scopedProfileIn($queryProf); } if ($this->debug()) { wfDebugLog('queries', sprintf("%s: %s", $this->mDBname, $sql)); } $queryId = MWDebug::query($sql, $fname, $isMaster); # Avoid fatals if close() was called $this->assertOpen(); # Do the query and handle errors $startTime = microtime(true); $ret = $this->doQuery($commentedSql); $queryRuntime = microtime(true) - $startTime; # Log the query time and feed it into the DB trx profiler $this->getTransactionProfiler()->recordQueryCompletion($queryProf, $startTime, $isWriteQuery, $this->affectedRows()); MWDebug::queryTime($queryId); # Try reconnecting if the connection was lost if (false === $ret && $this->wasErrorReissuable()) { # Transaction is gone, like it or not $hadTrx = $this->mTrxLevel; // possible lost transaction $this->mTrxLevel = 0; $this->mTrxIdleCallbacks = array(); // bug 65263 $this->mTrxPreCommitCallbacks = array(); // bug 65263 wfDebug("Connection lost, reconnecting...\n"); # Stash the last error values since ping() might clear them $lastError = $this->lastError(); $lastErrno = $this->lastErrno(); if ($this->ping()) { wfDebug("Reconnected\n"); $server = $this->getServer(); $msg = __METHOD__ . ": lost connection to {$server}; reconnected"; wfDebugLog('DBPerformance', "{$msg}:\n" . wfBacktrace(true)); if ($hadTrx) { # Leave $ret as false and let an error be reported. # Callers may catch the exception and continue to use the DB. $this->reportQueryError($lastError, $lastErrno, $sql, $fname, $tempIgnore); } else { # Should be safe to silently retry (no trx and thus no callbacks) $startTime = microtime(true); $ret = $this->doQuery($commentedSql); $queryRuntime = microtime(true) - $startTime; # Log the query time and feed it into the DB trx profiler $this->getTransactionProfiler()->recordQueryCompletion($queryProf, $startTime, $isWriteQuery, $this->affectedRows()); } } else { wfDebug("Failed\n"); } } if (false === $ret) { $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore); } $res = $this->resultObject($ret); // Destroy profile sections in the opposite order to their creation ScopedCallback::consume($queryProfSection); ScopedCallback::consume($totalProfSection); if ($isWriteQuery && $this->mTrxLevel) { $this->mTrxWriteDuration += $queryRuntime; } return $res; }
/** * Auto-create the given user, if necessary * @private Don't call this yourself. Let Setup.php do it for you at the right time. * @note This more properly belongs in AuthManager, but we need it now. * When AuthManager comes, this will be deprecated and will pass-through * to the corresponding AuthManager method. * @param User $user User to auto-create * @return bool Success */ public static function autoCreateUser(User $user) { global $wgAuth; $logger = self::singleton()->logger; // Much of this code is based on that in CentralAuth // Try the local user from the slave DB $localId = User::idFromName($user->getName()); // Fetch the user ID from the master, so that we don't try to create the user // when they already exist, due to replication lag // @codeCoverageIgnoreStart if (!$localId && wfGetLB()->getReaderIndex() != 0) { $localId = User::idFromName($user->getName(), User::READ_LATEST); } // @codeCoverageIgnoreEnd if ($localId) { // User exists after all. $user->setId($localId); $user->loadFromId(); return false; } // Denied by AuthPlugin? But ignore AuthPlugin itself. if (get_class($wgAuth) !== 'AuthPlugin' && !$wgAuth->autoCreate()) { $logger->debug(__METHOD__ . ': denied by AuthPlugin'); $user->setId(0); $user->loadFromId(); return false; } // Wiki is read-only? if (wfReadOnly()) { $logger->debug(__METHOD__ . ': denied by wfReadOnly()'); $user->setId(0); $user->loadFromId(); return false; } $userName = $user->getName(); // Check the session, if we tried to create this user already there's // no point in retrying. $session = self::getGlobalSession(); $reason = $session->get('MWSession::AutoCreateBlacklist'); if ($reason) { $logger->debug(__METHOD__ . ": blacklisted in session ({$reason})"); $user->setId(0); $user->loadFromId(); return false; } // Is the IP user able to create accounts? $anon = new User(); if (!$anon->isAllowedAny('createaccount', 'autocreateaccount') || $anon->isBlockedFromCreateAccount()) { // Blacklist the user to avoid repeated DB queries subsequently $logger->debug(__METHOD__ . ': user is blocked from this wiki, blacklisting'); $session->set('MWSession::AutoCreateBlacklist', 'blocked', 600); $session->persist(); $user->setId(0); $user->loadFromId(); return false; } // Check for validity of username if (!User::isCreatableName($userName)) { $logger->debug(__METHOD__ . ': Invalid username, blacklisting'); $session->set('MWSession::AutoCreateBlacklist', 'invalid username', 600); $session->persist(); $user->setId(0); $user->loadFromId(); return false; } // Give other extensions a chance to stop auto creation. $user->loadDefaults($userName); $abortMessage = ''; if (!\Hooks::run('AbortAutoAccount', array($user, &$abortMessage))) { // In this case we have no way to return the message to the user, // but we can log it. $logger->debug(__METHOD__ . ": denied by hook: {$abortMessage}"); $session->set('MWSession::AutoCreateBlacklist', "hook aborted: {$abortMessage}", 600); $session->persist(); $user->setId(0); $user->loadFromId(); return false; } // Make sure the name has not been changed if ($user->getName() !== $userName) { $user->setId(0); $user->loadFromId(); throw new \UnexpectedValueException('AbortAutoAccount hook tried to change the user name'); } // Ignore warnings about master connections/writes...hard to avoid here \Profiler::instance()->getTransactionProfiler()->resetExpectations(); $cache = \ObjectCache::getLocalClusterInstance(); $backoffKey = wfMemcKey('MWSession', 'autocreate-failed', md5($userName)); if ($cache->get($backoffKey)) { $logger->debug(__METHOD__ . ': denied by prior creation attempt failures'); $user->setId(0); $user->loadFromId(); return false; } // Checks passed, create the user... $from = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'CLI'; $logger->info(__METHOD__ . ": creating new user ({$userName}) - from: {$from}"); try { // Insert the user into the local DB master $status = $user->addToDatabase(); if (!$status->isOK()) { // @codeCoverageIgnoreStart $logger->error(__METHOD__ . ': failed with message ' . $status->getWikiText()); $user->setId(0); $user->loadFromId(); return false; // @codeCoverageIgnoreEnd } } catch (\Exception $ex) { // @codeCoverageIgnoreStart $logger->error(__METHOD__ . ': failed with exception ' . $ex->getMessage()); // Do not keep throwing errors for a while $cache->set($backoffKey, 1, 600); // Bubble up error; which should normally trigger DB rollbacks throw $ex; // @codeCoverageIgnoreEnd } # Notify hooks (e.g. Newuserlog) \Hooks::run('AuthPluginAutoCreate', array($user)); \Hooks::run('LocalUserCreated', array($user, true)); # Notify AuthPlugin too $tmpUser = $user; $wgAuth->initUser($tmpUser, true); if ($tmpUser !== $user) { $logger->warning(__METHOD__ . ': ' . get_class($wgAuth) . '::initUser() replaced the user object'); } $user->saveSettings(); # Update user count \DeferredUpdates::addUpdate(new \SiteStatsUpdate(0, 0, 0, 0, 1)); # Watch user's userpage and talk page $user->addWatch($user->getUserPage(), \WatchedItem::IGNORE_USER_RIGHTS); return true; }
/** * Ends this task peacefully * @param string $mode Use 'fast' to always skip job running */ public function restInPeace($mode = 'fast') { $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); // Assure deferred updates are not in the main transaction $lbFactory->commitMasterChanges(__METHOD__); // Loosen DB query expectations since the HTTP client is unblocked $trxProfiler = Profiler::instance()->getTransactionProfiler(); $trxProfiler->resetExpectations(); $trxProfiler->setExpectations($this->config->get('TrxProfilerLimits')['PostSend'], __METHOD__); // Do any deferred jobs DeferredUpdates::doUpdates('enqueue'); DeferredUpdates::setImmediateMode(true); // Make sure any lazy jobs are pushed JobQueueGroup::pushLazyJobs(); // Now that everything specific to this request is done, // try to occasionally run jobs (if enabled) from the queues if ($mode === 'normal') { $this->triggerJobs(); } // Log profiling data, e.g. in the database or UDP wfLogProfilingData(); // Commit and close up! $lbFactory->commitMasterChanges(__METHOD__); $lbFactory->shutdown(LBFactory::SHUTDOWN_NO_CHRONPROT); wfDebug("Request ended normally\n"); }