Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.62% covered (warning)
84.62%
44 / 52
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ClearUserWatchlistJob
86.27% covered (warning)
86.27%
44 / 51
50.00% covered (danger)
50.00%
2 / 4
10.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 newForUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 run
86.36% covered (warning)
86.36%
38 / 44
0.00% covered (danger)
0.00%
0 / 1
7.12
 getDeduplicationInfo
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Watchlist;
4
5use GenericParameterJob;
6use Job;
7use MediaWiki\MainConfigNames;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\User\UserIdentity;
10
11/**
12 * Job to clear a users watchlist in batches.
13 *
14 * @since 1.31
15 * @ingroup JobQueue
16 * @author Addshore
17 */
18class ClearUserWatchlistJob extends Job implements GenericParameterJob {
19    /**
20     * @param array $params
21     *  - userId,         The ID for the user whose watchlist is being cleared.
22     *  - maxWatchlistId, The maximum wl_id at the time the job was first created,
23     */
24    public function __construct( array $params ) {
25        parent::__construct( 'clearUserWatchlist', $params );
26
27        $this->removeDuplicates = true;
28    }
29
30    /**
31     * @param UserIdentity $user User to clear the watchlist for.
32     * @param int $maxWatchlistId The maximum wl_id at the time the job was first created.
33     *
34     * @return ClearUserWatchlistJob
35     */
36    public static function newForUser( UserIdentity $user, $maxWatchlistId ) {
37        return new self( [ 'userId' => $user->getId(), 'maxWatchlistId' => $maxWatchlistId ] );
38    }
39
40    public function run() {
41        $updateRowsPerQuery = MediaWikiServices::getInstance()->getMainConfig()->get(
42            MainConfigNames::UpdateRowsPerQuery );
43        $userId = $this->params['userId'];
44        $maxWatchlistId = $this->params['maxWatchlistId'];
45        $batchSize = $updateRowsPerQuery;
46
47        $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
48        $dbw = $loadBalancer->getConnection( DB_PRIMARY );
49        $dbr = $loadBalancer->getConnection( DB_REPLICA );
50
51        // Wait before lock to try to reduce time waiting in the lock.
52        if ( !$loadBalancer->waitForPrimaryPos( $dbr ) ) {
53            $this->setLastError( 'Timed out waiting for replica to catch up before lock' );
54            return false;
55        }
56
57        // Use a named lock so that jobs for this user see each others' changes
58        $lockKey = "{{$dbw->getDomainID()}}:ClearUserWatchlist:$userId"; // per-wiki
59        $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 10 );
60        if ( !$scopedLock ) {
61            $this->setLastError( "Could not acquire lock '$lockKey'" );
62            return false;
63        }
64
65        if ( !$loadBalancer->waitForPrimaryPos( $dbr ) ) {
66            $this->setLastError( 'Timed out waiting for replica to catch up within lock' );
67            return false;
68        }
69
70        // Clear any stale REPEATABLE-READ snapshot
71        $dbr->flushSnapshot( __METHOD__ );
72
73        $watchlistIds = $dbr->newSelectQueryBuilder()
74            ->select( 'wl_id' )
75            ->from( 'watchlist' )
76            ->where( [ 'wl_user' => $userId ] )
77            ->andWhere( $dbr->expr( 'wl_id', '<=', $maxWatchlistId ) )
78            ->limit( $batchSize )
79            ->caller( __METHOD__ )->fetchFieldValues();
80        if ( count( $watchlistIds ) == 0 ) {
81            return true;
82        }
83
84        $dbw->newDeleteQueryBuilder()
85            ->deleteFrom( 'watchlist' )
86            ->where( [ 'wl_id' => $watchlistIds ] )
87            ->caller( __METHOD__ )->execute();
88        if ( MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::WatchlistExpiry ) ) {
89            $dbw->newDeleteQueryBuilder()
90                ->deleteFrom( 'watchlist_expiry' )
91                ->where( [ 'we_item' => $watchlistIds ] )
92                ->caller( __METHOD__ )->execute();
93        }
94
95        // Commit changes and remove lock before inserting next job.
96        $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
97        $lbf->commitPrimaryChanges( __METHOD__ );
98        unset( $scopedLock );
99
100        if ( count( $watchlistIds ) === (int)$batchSize ) {
101            // Until we get less results than the limit, recursively push
102            // the same job again.
103            MediaWikiServices::getInstance()->getJobQueueGroup()->push( new self( $this->getParams() ) );
104        }
105
106        return true;
107    }
108
109    public function getDeduplicationInfo() {
110        $info = parent::getDeduplicationInfo();
111        // This job never has a namespace or title so we can't use it for deduplication
112        unset( $info['namespace'] );
113        unset( $info['title'] );
114        return $info;
115    }
116
117}
118/** @deprecated class alias since 1.43 */
119class_alias( ClearUserWatchlistJob::class, 'ClearUserWatchlistJob' );