ClearUserWatchlistJob.php 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. <?php
  2. use MediaWiki\MediaWikiServices;
  3. use MediaWiki\User\UserIdentity;
  4. /**
  5. * Job to clear a users watchlist in batches.
  6. *
  7. * @author Addshore
  8. *
  9. * @ingroup JobQueue
  10. * @since 1.31
  11. */
  12. class ClearUserWatchlistJob extends Job implements GenericParameterJob {
  13. /**
  14. * @param array $params
  15. * - userId, The ID for the user whose watchlist is being cleared.
  16. * - maxWatchlistId, The maximum wl_id at the time the job was first created,
  17. */
  18. public function __construct( array $params ) {
  19. parent::__construct( 'clearUserWatchlist', $params );
  20. $this->removeDuplicates = true;
  21. }
  22. /**
  23. * @param UserIdentity $user User to clear the watchlist for.
  24. * @param int $maxWatchlistId The maximum wl_id at the time the job was first created.
  25. *
  26. * @return ClearUserWatchlistJob
  27. */
  28. public static function newForUser( UserIdentity $user, $maxWatchlistId ) {
  29. return new self( [ 'userId' => $user->getId(), 'maxWatchlistId' => $maxWatchlistId ] );
  30. }
  31. public function run() {
  32. global $wgUpdateRowsPerQuery;
  33. $userId = $this->params['userId'];
  34. $maxWatchlistId = $this->params['maxWatchlistId'];
  35. $batchSize = $wgUpdateRowsPerQuery;
  36. $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
  37. $dbw = $loadBalancer->getConnectionRef( DB_MASTER );
  38. $dbr = $loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
  39. // Wait before lock to try to reduce time waiting in the lock.
  40. if ( !$loadBalancer->waitForMasterPos( $dbr ) ) {
  41. $this->setLastError( 'Timed out waiting for replica to catch up before lock' );
  42. return false;
  43. }
  44. // Use a named lock so that jobs for this user see each others' changes
  45. $lockKey = "{{$dbw->getDomainID()}}:ClearUserWatchlist:$userId"; // per-wiki
  46. $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 10 );
  47. if ( !$scopedLock ) {
  48. $this->setLastError( "Could not acquire lock '$lockKey'" );
  49. return false;
  50. }
  51. if ( !$loadBalancer->waitForMasterPos( $dbr ) ) {
  52. $this->setLastError( 'Timed out waiting for replica to catch up within lock' );
  53. return false;
  54. }
  55. // Clear any stale REPEATABLE-READ snapshot
  56. $dbr->flushSnapshot( __METHOD__ );
  57. $watchlistIds = $dbr->selectFieldValues(
  58. 'watchlist',
  59. 'wl_id',
  60. [
  61. 'wl_user' => $userId,
  62. 'wl_id <= ' . $maxWatchlistId
  63. ],
  64. __METHOD__,
  65. [
  66. 'ORDER BY' => 'wl_id ASC',
  67. 'LIMIT' => $batchSize,
  68. ]
  69. );
  70. if ( count( $watchlistIds ) == 0 ) {
  71. return true;
  72. }
  73. $dbw->delete( 'watchlist', [ 'wl_id' => $watchlistIds ], __METHOD__ );
  74. // Commit changes and remove lock before inserting next job.
  75. $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
  76. $lbf->commitMasterChanges( __METHOD__ );
  77. unset( $scopedLock );
  78. if ( count( $watchlistIds ) === (int)$batchSize ) {
  79. // Until we get less results than the limit, recursively push
  80. // the same job again.
  81. JobQueueGroup::singleton()->push( new self( $this->getParams() ) );
  82. }
  83. return true;
  84. }
  85. public function getDeduplicationInfo() {
  86. $info = parent::getDeduplicationInfo();
  87. // This job never has a namespace or title so we can't use it for deduplication
  88. unset( $info['namespace'] );
  89. unset( $info['title'] );
  90. return $info;
  91. }
  92. }