Catrope has uploaded a new change for review.
https://gerrit.wikimedia.org/r/291676
Change subject: [WIP] Gracefully handle outdated echo_unread_wikis rows
......................................................................
[WIP] Gracefully handle outdated echo_unread_wikis rows
Adds $wgEchoUnreadWikisTransition. If this setting is enabled,
we will disbelieve the alert/message counts in the euw table
and obtain them using server-side cross-wiki API queries instead.
TODO:
* Do a full test that involves changing a notification type's
section and updating the cache version (because cached counts
will be inaccurate as well)
* This may cause problems with global badge counts, figure out
if we can get those to be accurate too.
Bug: T132954
Change-Id: Ibcc8ac102dac3cf06916d67427b42457fdb93db6
---
M Echo.php
M includes/ForeignNotifications.php
M includes/api/ApiCrossWikiBase.php
M includes/api/ApiEchoNotifications.php
4 files changed, 101 insertions(+), 16 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Echo
refs/changes/76/291676/1
diff --git a/Echo.php b/Echo.php
index ede0cab..ebe4134 100644
--- a/Echo.php
+++ b/Echo.php
@@ -150,6 +150,12 @@
// main one. Must be a key defined in $wgExternalServers
$wgEchoSharedTrackingCluster = false;
+// Enable this when you've changed the section (alert vs message) of a
notification
+// type, but haven't yet finished running backfillUnreadWikis.php. This setting
+// reduces performance but prevents glitchy and inaccurate information from
being
+// show to users while the unread_wikis table is being rebuilt.
+$wgEchoUnreadWikisTransition = false;
+
// The max number of notifications allowed for a user to do a live update,
// this is also the number of max notifications allowed for a user to have
// @FIXME - the name is not intuitive, probably change it when the deleteJob
patch
diff --git a/includes/ForeignNotifications.php
b/includes/ForeignNotifications.php
index 1bddc98..c3d14f8 100644
--- a/includes/ForeignNotifications.php
+++ b/includes/ForeignNotifications.php
@@ -40,6 +40,11 @@
protected $wikiTimestamps = array();
/**
+ * @var array [(str) wiki => [ (str) section => (int) count, ... ], ...
]
+ */
+ protected $wikiCounts = array();
+
+ /**
* @var bool
*/
protected $populated = false;
@@ -137,6 +142,17 @@
return isset( $this->wikiTimestamps[$wiki][$section] ) ?
$this->wikiTimestamps[$wiki][$section] : false;
}
+ public function getWikiCount( $wiki, $section =
EchoAttributeManager::ALL ) {
+ $this->populate();
+ if ( !isset( $this->wikiCounts[$wiki] ) ) {
+ return false;
+ }
+ if ( $section === EchoAttributeManager::ALL ) {
+ return array_sum( $this->wikiCounts[$wiki] );
+ }
+ return isset( $this->wikiCounts[$wiki][$section] ) ?
$this->wikiCounts[$wiki][$section] : false;
+ }
+
protected function populate() {
if ( $this->populated ) {
return;
@@ -163,8 +179,9 @@
foreach ( $sections as $section => $data ) {
if ( $data['count'] > 0 ) {
- $this->counts[$section] +=
$data['count'];
$this->wikis[$section][] = $wiki;
+ $this->wikiCounts[$wiki][$section] =
intval( $data['count'] );
+ $this->counts[$section] += intval(
$data['count'] );
$timestamp = new MWTimestamp(
$data['ts'] );
$this->wikiTimestamps[$wiki][$section]
= $timestamp;
diff --git a/includes/api/ApiCrossWikiBase.php
b/includes/api/ApiCrossWikiBase.php
index d1f96c3..2695d85 100644
--- a/includes/api/ApiCrossWikiBase.php
+++ b/includes/api/ApiCrossWikiBase.php
@@ -23,11 +23,13 @@
* This will turn the current API call (with all of it's params) and
execute
* it on all foreign wikis, returning an array of results per wiki.
*
+ * @param array $wikis List of wikis to query. Defaults to the result
of getForeignWikis().
+ * @param array $paramOverrides Request parameter overrides
* @return array
* @throws Exception
*/
- protected function getFromForeign() {
- $reqs = $this->getForeignRequestParams(
$this->getForeignWikis() );
+ protected function getFromForeign( $wikis = null, array $paramOverrides
= array() ) {
+ $reqs = $this->getForeignRequestParams( $wikis !== null ?
$wikis : $this->getForeignWikis(), $paramOverrides );
return $this->foreignRequests( $reqs );
}
@@ -69,9 +71,10 @@
/**
* @param array $wikis Wiki names
+ * @param array $paramOverrides Query parameter overrides
* @return array
*/
- protected function getForeignRequestParams( array $wikis ) {
+ protected function getForeignRequestParams( array $wikis, array
$paramOverrides = array() ) {
$apis = $this->foreignNotifications->getApiEndpoints( $wikis );
if ( !$apis ) {
return array();
@@ -82,7 +85,7 @@
$reqs[$wiki] = array(
'method' => 'GET',
'url' => $api['url'],
- 'query' => $this->getForeignQueryParams( $wiki
),
+ 'query' => $paramOverrides +
$this->getForeignQueryParams( $wiki ),
);
}
diff --git a/includes/api/ApiEchoNotifications.php
b/includes/api/ApiEchoNotifications.php
index 7556a10..1b292a1 100644
--- a/includes/api/ApiEchoNotifications.php
+++ b/includes/api/ApiEchoNotifications.php
@@ -86,9 +86,12 @@
$params[$section . 'continue'],
$params['format'], $params[$section . 'unreadfirst']
);
- if ( $this->crossWikiSummary &&
$this->foreignNotifications->getCount( $section ) > 0 ) {
+ if ( $this->crossWikiSummary ) {
// insert fake notification for
foreign notifications
- array_unshift(
$result[$section]['list'], $this->makeForeignNotification( $user,
$params['format'], $section ) );
+ $foreignNotification =
$this->makeForeignNotification( $user, $params['format'], $section );
+ if ( $foreignNotification ) {
+ array_unshift(
$result[$section]['list'], $foreignNotification );
+ }
}
}
} else {
@@ -102,8 +105,11 @@
// if exactly 1 section is specified, we
consider only that section, otherwise
// we pass ALL to consider all foreign
notifications
$section = count( $params['sections'] ) === 1 ?
reset( $params['sections'] ) : EchoAttributeManager::ALL;
- if ( $this->crossWikiSummary &&
$this->foreignNotifications->getCount( $section ) > 0 ) {
- array_unshift( $result['list'],
$this->makeForeignNotification( $user, $params['format'], $section ) );
+ if ( $this->crossWikiSummary ) {
+ $foreignNotification =
$this->makeForeignNotification( $user, $params['format'], $section );
+ if ( $foreignNotification ) {
+ array_unshift( $result['list'],
$foreignNotification );
+ }
}
}
}
@@ -274,15 +280,68 @@
return $result;
}
+ /**
+ * Build and format a "fake" notification to represent foreign
notifications.
+ * @param User $user
+ * @param string $format
+ * @param string $section
+ * @return array|false A formatted notification, or false if there are
no foreign notifications
+ */
protected function makeForeignNotification( User $user, $format,
$section = EchoAttributeManager::ALL ) {
- $wikis = $this->foreignNotifications->getWikis( $section );
- $count = $this->foreignNotifications->getCount( $section );
+ global $wgEchoUnreadWikisTransition;
+ if ( $wgEchoUnreadWikisTransition && $section !==
EchoAttributeManager::ALL ) {
+ // In transition mode we trust that echo_unread_wikis
is accurate for the total of alerts+messages,
+ // but not for each section individually (i.e. we don't
trust that notifications won't be misclassified).
+ // We get all wikis that have any notifications at all
according to the euw table,
+ // and query them to find out what's really there.
+ $potentialWikis =
$this->foreignNotifications->getWikis( EchoAttributeManager::ALL );
+ if ( !$potentialWikis ) {
+ return false;
+ }
+ $foreignResults = $this->getFromForeign(
$potentialWikis, array( $this->getModulePrefix() . 'filter' => '!read' ) );
+
+ $countsByWiki = array();
+ $timestampsByWiki = array();
+ foreach ( $foreignResults as $wiki => $result ) {
+ if ( isset(
$result['query']['notifications']['list'] ) ) {
+ $notifs =
$result['query']['notifications']['list'];
+ } elseif ( isset(
$result['query']['notifications'][$section]['list'] ) ) {
+ $notifs =
$result['query']['notifications'][$section]['list'];
+ } else {
+ $notifs = false;
+ }
+ if ( $notifs ) {
+ $countsByWiki[$wiki] = count( $notifs );
+ $timestampsByWiki[$wiki] = max(
array_map( function ( $n ) {
+ return $n['timestamp']['mw'];
+ } ) );
+ }
+ }
+
+ $wikis = array_keys( $countsByWiki );
+ $count = array_sum( $countsByWiki );
+ $maxTimestamp = new MWTimestamp( max( $timestampsByWiki
) );
+ $timestampsByWiki = array_map( function ( $ts ) {
+ return new MWTimestamp( $ts );
+ }, $timestampsByWiki );
+ } else {
+ // In non-transition mode, or when querying all
sections, we can trust the euw table
+ $wikis = $this->foreignNotifications->getWikis(
$section );
+ $count = $this->foreignNotifications->getCount(
$section );
+ $maxTimestamp =
$this->foreignNotifications->getTimestamp( $section );
+ $timestampsByWiki = array();
+ foreach ( $wikis as $wiki ) {
+ $timestampsByWiki[$wiki] =
$this->foreignNotifications->getWikiTimestamp( $wiki, $section );
+ }
+ }
+
+ if ( $count === 0 || count( $wikis ) === 0 ) {
+ return false;
+ }
// Sort wikis by timestamp, in descending order (newest first)
usort( $wikis, function ( $a, $b ) use ( $section ) {
- $aTimestamp =
$this->foreignNotifications->getWikiTimestamp( $a, $section ) ?: new
MWTimestamp( 0 );
- $bTimestamp =
$this->foreignNotifications->getWikiTimestamp( $b, $section ) ?: new
MWTimestamp( 0 );
- return $bTimestamp->getTimestamp( TS_UNIX ) -
$aTimestamp->getTimestamp( TS_UNIX );
+ return $timestampsByWiki[$b]->getTimestamp( TS_UNIX ) -
$timestampsByWiki[$a]->getTimestamp( TS_UNIX );
} );
$row = new StdClass;
@@ -301,7 +360,7 @@
) );
$row->notification_user = $user->getId();
- $row->notification_timestamp =
$this->foreignNotifications->getTimestamp( $section );
+ $row->notification_timestamp = $maxTimestamp;
$row->notification_read_timestamp = null;
$row->notification_bundle_base = 1;
$row->notification_bundle_hash = md5( 'bogus' );
@@ -317,7 +376,7 @@
$output['sources'] = EchoForeignNotifications::getApiEndpoints(
$wikis );
// Add timestamp information
foreach ( $output['sources'] as $wiki => &$data ) {
- $data['ts'] =
$this->foreignNotifications->getWikiTimestamp( $wiki, $section )->getTimestamp(
TS_MW );
+ $data['ts'] = $timestampsByWiki[$wiki]->getTimestamp(
TS_MW );
}
return $output;
}
--
To view, visit https://gerrit.wikimedia.org/r/291676
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibcc8ac102dac3cf06916d67427b42457fdb93db6
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits