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

Reply via email to