1<?php 2 3use MediaWiki\Linker\LinkTarget; 4use MediaWiki\MediaWikiServices; 5use Psr\Log\LoggerInterface; 6use Wikimedia\Rdbms\IDatabase; 7 8/** 9 * Class for fixing stale WANObjectCache keys using a purge event source 10 * 11 * This is useful for expiring keys that missed fire-and-forget purges. This uses the 12 * recentchanges table as a reliable stream to make certain keys reach consistency 13 * as soon as the underlying replica database catches up. These means that critical 14 * keys will not escape getting purged simply due to brief hiccups in the network, 15 * which are more prone to happen across datacenters. 16 * 17 * ---- 18 * "I was trying to cheat death. I was only trying to surmount for a little while the 19 * darkness that all my life I surely knew was going to come rolling in on me some day 20 * and obliterate me. I was only to stay alive a little brief while longer, after I was 21 * already gone. To stay in the light, to be with the living, a little while past my time." 22 * -- Notes for "Blues of a Lifetime", by [[Cornell Woolrich]] 23 * 24 * @since 1.28 25 */ 26class WANCacheReapUpdate implements DeferrableUpdate { 27 /** @var IDatabase */ 28 private $db; 29 /** @var LoggerInterface */ 30 private $logger; 31 32 /** 33 * @param IDatabase $db 34 * @param LoggerInterface $logger 35 */ 36 public function __construct( IDatabase $db, LoggerInterface $logger ) { 37 $this->db = $db; 38 $this->logger = $logger; 39 } 40 41 public function doUpdate() { 42 $reaper = new WANObjectCacheReaper( 43 MediaWikiServices::getInstance()->getMainWANObjectCache(), 44 ObjectCache::getLocalClusterInstance(), 45 [ $this, 'getTitleChangeEvents' ], 46 [ $this, 'getEventAffectedKeys' ], 47 [ 48 'channel' => 'table:recentchanges:' . $this->db->getDomainID(), 49 'logger' => $this->logger 50 ] 51 ); 52 53 $reaper->invoke( 100 ); 54 } 55 56 /** 57 * @see WANObjectCacheRepear 58 * 59 * @param int $start 60 * @param int $id 61 * @param int $end 62 * @param int $limit 63 * @return TitleValue[] 64 */ 65 public function getTitleChangeEvents( $start, $id, $end, $limit ) { 66 $db = $this->db; 67 $encStart = $db->addQuotes( $db->timestamp( $start ) ); 68 $encEnd = $db->addQuotes( $db->timestamp( $end ) ); 69 $id = (int)$id; // cast NULL => 0 since rc_id is an integer 70 71 $res = $db->select( 72 'recentchanges', 73 [ 'rc_namespace', 'rc_title', 'rc_timestamp', 'rc_id' ], 74 [ 75 $db->makeList( [ 76 "rc_timestamp > $encStart", 77 "rc_timestamp = $encStart AND rc_id > " . $db->addQuotes( $id ) 78 ], LIST_OR ), 79 "rc_timestamp < $encEnd" 80 ], 81 __METHOD__, 82 [ 'ORDER BY' => [ 'rc_timestamp ASC', 'rc_id ASC' ], 'LIMIT' => $limit ] 83 ); 84 85 $events = []; 86 foreach ( $res as $row ) { 87 $events[] = [ 88 'id' => (int)$row->rc_id, 89 'pos' => (int)wfTimestamp( TS_UNIX, $row->rc_timestamp ), 90 'item' => new TitleValue( (int)$row->rc_namespace, $row->rc_title ) 91 ]; 92 } 93 94 return $events; 95 } 96 97 /** 98 * Gets a list of important cache keys associated with a title 99 * 100 * @see WANObjectCacheRepear 101 * @param WANObjectCache $cache 102 * @param LinkTarget $t 103 * @return string[] 104 */ 105 public function getEventAffectedKeys( WANObjectCache $cache, LinkTarget $t ) { 106 /** @var WikiPage[]|LocalFile[]|User[] $entities */ 107 $entities = []; 108 109 // You can't create a WikiPage for special pages (-1) or other virtual 110 // namespaces, but special pages do appear in RC sometimes, e.g. for logs 111 // of AbuseFilter filter changes. 112 if ( $t->getNamespace() >= 0 ) { 113 $entities[] = MediaWikiServices::getInstance()->getWikiPageFactory() 114 ->newFromLinkTarget( $t ); 115 } 116 117 if ( $t->inNamespace( NS_FILE ) ) { 118 $entities[] = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo() 119 ->newFile( $t->getText() ); 120 } 121 if ( $t->inNamespace( NS_USER ) ) { 122 $entities[] = User::newFromName( $t->getText(), false ); 123 } 124 125 $keys = []; 126 foreach ( $entities as $entity ) { 127 if ( $entity ) { 128 $keys = array_merge( $keys, $entity->getMutableCacheKeys( $cache ) ); 129 } 130 } 131 if ( $keys ) { 132 $this->logger->debug( __CLASS__ . ': got key(s) ' . implode( ', ', $keys ) ); 133 } 134 135 return $keys; 136 } 137} 138