jenkins-bot has submitted this change and it was merged.
Change subject: Add caching & cache invalidation for Campaign pages
......................................................................
Add caching & cache invalidation for Campaign pages
Cache into memcached on first parse. Also calculate
the list of templates used in the campaign JSON,
and insert that into templatelinks. Then use
the JobQueue and LinksUpdate to invalidate the
cache on the Campaign whenever any of the templates
it depends on is changed.
Store an invalidation timestamp in a separate key,
and consider all cache items as valid only if they
have a timestamp value greater than or equal to the
invalidation timestamp
Change-Id: I79fb24997c20118e229585a4c97b45a173f466b1
---
M UploadWizard.php
M includes/CampaignHooks.php
M includes/UploadWizardCampaign.php
3 files changed, 134 insertions(+), 7 deletions(-)
Approvals:
Brion VIBBER: Looks good to me, approved
MaxSem: Looks good to me, but someone else must approve
jenkins-bot: Verified
diff --git a/UploadWizard.php b/UploadWizard.php
index 9ee1275..320974e 100644
--- a/UploadWizard.php
+++ b/UploadWizard.php
@@ -128,6 +128,7 @@
$wgHooks[ 'PageContentSaveComplete' ][] =
'CampaignHooks::onPageContentSaveComplete';
$wgHooks[ 'ArticleDeleteComplete' ][] =
'CampaignHooks::onArticleDeleteComplete';
$wgHooks[ 'TitleMoveComplete' ][] = 'CampaignHooks::onTitleMoveComplete';
+$wgHooks[ 'LinksUpdateComplete' ][] = 'CampaignHooks::onLinksUpdateComplete';
$wgAvailableRights[] = 'upwizcampaigns';
diff --git a/includes/CampaignHooks.php b/includes/CampaignHooks.php
index 6c29301..c309b0d 100644
--- a/includes/CampaignHooks.php
+++ b/includes/CampaignHooks.php
@@ -36,12 +36,54 @@
$insertData
);
+
+ $campaign = new UploadWizardCampaign( $article->getTitle(),
$content->getJsonData() );
+
+ // Track the templates being used in the wikitext in this
campaign, and add the campaign page
+ // as a dependency on those templates, via the templatelinks
table. This triggers an update
+ // for the Campaign page when LinksUpdate runs due to an edit
to any of the templates in
+ // the dependency chain - and hence we can invalidate caches
when any page that the
+ // Campaign depends on changes!
+ $templates = $campaign->getTemplates();
+
+ $insertions = array();
+
+ foreach ( $templates as $template ) {
+ $insertions[] = array(
+ 'tl_from' => $article->getId(),
+ 'tl_namespace' => $template[0],
+ 'tl_title' => $template[1]
+ );
+ }
+
+ $success = $success && $dbw->delete( 'templatelinks', array(
'tl_from' => $article->getId() ) );
+ $success = $success && $dbw->insert( 'templatelinks',
$insertions, __METHOD__, array( 'IGNORE' ) );
+
$dbw->commit();
+
+ $campaign->invalidateCache();
return $success;
}
/**
+ * Invalidates the cache for a campaign when any of its dependents are
edited. The 'dependents'
+ * are tracked by entries in the templatelinks table, which are
inserted by using the
+ * PageContentSaveComplete hook.
+ *
+ * This is usually run via the Job Queue mechanism.
+ */
+ public static function onLinksUpdateComplete( LinksUpdate
&$linksupdate) {
+ if( !$linksupdate->getTitle()->inNamespace( NS_CAMPAIGN ) ) {
+ return true;
+ }
+
+ $campaign = new UploadWizardCampaign( $linksupdate->getTitle()
);
+ $campaign->invalidateCache();
+
+ return true;
+ }
+ /**
* Deletes entries from uc_campaigns table when a Campaign is deleted
*/
public static function onArticleDeleteComplete( $article, $user,
$reason, $id, $content, $logEntry ) {
diff --git a/includes/UploadWizardCampaign.php
b/includes/UploadWizardCampaign.php
index 3f81bd9..195ce1f 100644
--- a/includes/UploadWizardCampaign.php
+++ b/includes/UploadWizardCampaign.php
@@ -35,6 +35,15 @@
*/
protected $parsedConfig = null;
+ /**
+ * Array of templates used in this campaign.
+ * Each item is an array with ( namespace, tempalte_title )
+ * Stored without deduplication
+ *
+ * @since 1.2
+ * @var array
+ */
+ protected $templates = array();
/**
* The Title representing the current campaign
@@ -179,21 +188,37 @@
}
/**
+ * Update internal list of templates used in parsing this campaign
+ *
+ * @param ParserOutput $parserOutput
+ */
+ private function updateTemplates( ParserOutput $parserOutput ) {
+ foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
+ foreach ( $templates as $dbk => $id ) {
+ $this->templates[ "$ns:$dbk" ] = array( $ns,
$dbk );
+ }
+ }
+ }
+
+ /**
* Wrapper around OutputPage::parseInline
*
* @param $value String Wikitext to parse
+ * @param $lang Language
*
* @since 1.3
*
* @return String parsed wikitext
*/
- private function parseValue( $value ) {
+ private function parseValue( $value, Language $lang ) {
global $wgParser;
wfProfileIn( __METHOD__ );
$parserOptions = ParserOptions::newFromContext( $this->context
);
$parserOptions->setEditSection( false );
$parserOptions->setInterfaceMessage( true );
+ $parserOptions->setUserLang( $lang );
+ $parserOptions->setTargetLanguage( $lang );
$output = $wgParser->parse( $value, $this->getTitle(),
$parserOptions );
@@ -204,6 +229,8 @@
if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
$parsed = $m[1];
}
+
+ $this->updateTemplates( $output );
wfProfileOut( __METHOD__ );
return $parsed;
@@ -219,17 +246,17 @@
*
* @return array
*/
- private function parseArrayValues( $array, $forKeys = null ) {
+ private function parseArrayValues( $array, $lang, $forKeys = null ) {
$parsed = array();
foreach ( $array as $key => $value ) {
if ( $forKeys !== null ) {
if( in_array( $key, $forKeys ) ) {
- $parsed[$key] = $this->parseValue(
$value );
+ $parsed[$key] = $this->parseValue(
$value, $lang );
} else {
$parsed[$key] = $value;
}
} else {
- $parsed[$key] = $this->parseValue( $value );
+ $parsed[$key] = $this->parseValue( $value,
$lang );
}
}
return $parsed;
@@ -242,7 +269,26 @@
*
* @return array
*/
- public function getParsedConfig() {
+ public function getParsedConfig( $lang = null ) {
+ global $wgMemc;
+
+ if ( $lang === null ) {
+ $lang = $this->context->getLanguage();
+ }
+
+ // We check if the parsed config for this campaign is cached.
If it is available in cache,
+ // we then check to make sure that it is the latest version -
by verifying that its
+ // timestamp is greater than or equal to the timestamp of the
last time an invalidate was
+ // issued.
+ $memKey = wfMemcKey( 'uploadwizard', 'campaign',
$this->getName(), 'parsed-config', $lang->getCode() );
+ $memValue = $wgMemc->get( $memKey );
+ if ( $memValue !== false ) {
+ $invalidateTimestamp = $wgMemc->get(
$this->makeInvalidateTimestampKey() );
+ if( $invalidateTimestamp === false ||
$memValue['timestamp'] >= $invalidateTimestamp ) {
+ $this->parsedConfig = $memValue['config'];
+ }
+ }
+
wfProfileIn( __METHOD__ );
if ( $this->parsedConfig === null ) {
$parsedConfig = array();
@@ -250,16 +296,17 @@
switch ( $key ) {
case "title":
case "description":
- $parsedConfig[$key] =
$this->parseValue( $value );
+ $parsedConfig[$key] =
$this->parseValue( $value, $lang );
break;
case "display":
- $parsedConfig['display'] =
$this->parseArrayValues( $value );
+ $parsedConfig['display'] =
$this->parseArrayValues( $value, $lang );
break;
case "fields":
$parsedConfig['fields'] = array();
foreach ( $value as $field ) {
$parsedConfig['fields'][] =
$this->parseArrayValues(
$field,
+ $lang,
array( 'label' )
);
}
@@ -270,8 +317,45 @@
}
}
$this->parsedConfig = $parsedConfig;
+ $wgMemc->set( $memKey, array( 'timestamp' => time(),
'config' => $parsedConfig ) );
}
wfProfileOut( __METHOD__ );
return $this->parsedConfig;
}
+
+ /**
+ * Returns the templates used in this Campaign's config
+ *
+ * @return array with items of form array( ns, title )
+ */
+ public function getTemplates() {
+ if ( $this->parsedConfig === null ) {
+ $this->getParsedConfig();
+ }
+ return array_values( $this->templates );
+ }
+
+
+ /**
+ * Invalidate the cache for this campaign, in all languages
+ *
+ * Does so by simply writing a new invalidate timestamp to memcached.
+ * Since this invalidate timestamp is checked on every read, the cached
entries
+ * for the campaign will be regenerated the next time there is a read.
+ */
+ public function invalidateCache() {
+ global $wgMemc;
+
+ $memKey = $this->makeInvalidateTimestampKey();
+ $wgMemc->set($memKey, time() );
+ }
+
+ /**
+ * Returns key used to store the last time the cache for a particular
campaign was invalidated
+ *
+ * @return String
+ */
+ private function makeInvalidateTimestampKey() {
+ return wfMemcKey( 'uploadwizard', 'campaign', $this->getName(),
'parsed-config', 'invalidate-timestamp' );
+ }
}
--
To view, visit https://gerrit.wikimedia.org/r/86960
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I79fb24997c20118e229585a4c97b45a173f466b1
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/UploadWizard
Gerrit-Branch: master
Gerrit-Owner: Yuvipanda <[email protected]>
Gerrit-Reviewer: Brian Wolff <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: MaxSem <[email protected]>
Gerrit-Reviewer: Yuvipanda <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits