33 private $urlTuples = [];
35 private $pageTuples = [];
38 private const MAX_REBOUND_DELAY = 300;
46 public function __construct( array $targets, array $options = [] ) {
48 (
int)max( $options[
'reboundDelay'] ?? 0, 0 ),
49 self::MAX_REBOUND_DELAY
52 foreach ( $targets as $target ) {
54 $this->pageTuples[] = [ $target, $delay ];
56 $this->urlTuples[] = [ $target, $delay ];
63 Assert::parameterType( __CLASS__, $update,
'$update' );
64 '@phan-var self $update';
66 $this->urlTuples = array_merge( $this->urlTuples, $update->urlTuples );
67 $this->pageTuples = array_merge( $this->pageTuples, $update->pageTuples );
86 $reboundDelayByUrl = $this->resolveReboundDelayByUrl();
90 $immediatePurgeTimestamp = time();
93 $urlsWithReboundByDelay = [];
94 foreach ( $reboundDelayByUrl as $url => $delay ) {
96 $urlsWithReboundByDelay[$delay][] = $url;
101 foreach ( $urlsWithReboundByDelay as $delay => $urls ) {
104 'jobReleaseTimestamp' => $immediatePurgeTimestamp + $delay
107 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
117 public static function purge( array $urls ) {
118 $cdnServers = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::CdnServers );
119 $htcpRouting = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::HTCPRouting );
125 $urls = array_unique( $urls );
127 wfDebugLog(
'squid', __METHOD__ .
': ' . implode(
' ', $urls ) );
130 $ts = microtime(
true );
131 $relayerGroup = MediaWikiServices::getInstance()->getEventRelayerGroup();
132 $relayerGroup->getRelayer(
'cdn-url-purges' )->notifyMulti(
135 static function ( $url ) use ( $ts ) {
146 if ( $htcpRouting ) {
147 self::HTCPPurge( $urls );
152 self::naivePurge( $urls );
160 return array_keys( $this->resolveReboundDelayByUrl() );
166 private function resolveReboundDelayByUrl() {
167 $services = MediaWikiServices::getInstance();
171 $lb = $services->getLinkBatchFactory()->newLinkBatch();
172 foreach ( $this->pageTuples as list( $page, $delay ) ) {
173 $lb->addObj( $page );
177 $reboundDelayByUrl = [];
180 $htmlCacheUpdater = $services->getHtmlCacheUpdater();
181 foreach ( $this->pageTuples as list( $page, $delay ) ) {
182 foreach ( $htmlCacheUpdater->getUrls( $page ) as $url ) {
184 $reboundDelayByUrl[$url] = max( $reboundDelayByUrl[$url] ?? 0, $delay );
188 foreach ( $this->urlTuples as list( $url, $delay ) ) {
190 $reboundDelayByUrl[$url] = max( $reboundDelayByUrl[$url] ?? 0, $delay );
193 return $reboundDelayByUrl;
202 private static function HTCPPurge( array $urls ) {
203 $htcpRouting = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::HTCPRouting );
204 $htcpMulticastTTL = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::HTCPMulticastTTL );
209 if ( !defined(
"IPPROTO_IP" ) ) {
210 define(
"IPPROTO_IP", 0 );
211 define(
"IP_MULTICAST_LOOP", 34 );
212 define(
"IP_MULTICAST_TTL", 33 );
216 $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
218 $errstr = socket_strerror( socket_last_error() );
220 ": Error opening UDP socket: $errstr" );
226 socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
227 if ( $htcpMulticastTTL != 1 ) {
229 socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
234 $idGenerator = MediaWikiServices::getInstance()->getGlobalIdGenerator();
235 $ids = $idGenerator->newSequentialPerNodeIDs(
241 foreach ( $urls as $url ) {
242 if ( !is_string( $url ) ) {
245 $url = self::expand( $url );
246 $conf = self::getRuleForURL( $url, $htcpRouting );
249 "No HTCP rule configured for URL {$url} , skipping" );
253 if ( isset( $conf[
'host'] ) && isset( $conf[
'port'] ) ) {
257 foreach ( $conf as $subconf ) {
258 if ( !isset( $subconf[
'host'] ) || !isset( $subconf[
'port'] ) ) {
259 throw new MWException(
"Invalid HTCP rule for URL $url\n" );
266 $htcpTransID = current( $ids );
269 $htcpSpecifier = pack(
'na4na*na8n',
270 4,
'HEAD', strlen( $url ), $url,
273 $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
274 $htcpLen = 4 + $htcpDataLen + 2;
279 $htcpPacket = pack(
'nxxnCxNxxa*n',
280 $htcpLen, $htcpDataLen, $htcpOpCLR,
281 $htcpTransID, $htcpSpecifier, 2 );
284 "Purging URL $url via HTCP" );
285 foreach ( $conf as $subconf ) {
286 socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
287 $subconf[
'host'], $subconf[
'port'] );
298 private static function naivePurge( array $urls ) {
299 $cdnServers = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::CdnServers );
302 foreach ( $urls as $url ) {
303 $url = self::expand( $url );
305 $urlHost = strlen( $urlInfo[
'port'] ??
'' )
306 ? IPUtils::combineHostAndPort( $urlInfo[
'host'], (
int)$urlInfo[
'port'] )
313 'Connection' =>
'Keep-Alive',
314 'Proxy-Connection' =>
'Keep-Alive',
315 'User-Agent' =>
'MediaWiki/' .
MW_VERSION .
' ' . __CLASS__
318 foreach ( $cdnServers as $server ) {
319 $reqs[] = ( $baseReq + [
'proxy' => $server ] );
323 $http = MediaWikiServices::getInstance()->getHttpRequestFactory()
324 ->createMultiClient( [
'maxConnsPerHost' => 8,
'usePipelining' =>
true ] );
325 $http->runMulti( $reqs );
342 private static function expand( $url ) {
352 private static function getRuleForURL( $url, $rules ) {
353 foreach ( $rules as $regex => $routing ) {
354 if ( $regex ===
'' || preg_match( $regex, $url ) ) {
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.