MediaWiki master
DependencyStore.php
Go to the documentation of this file.
1<?php
8
9use InvalidArgumentException;
13
20 protected const KEY_PATHS = 'paths';
21 protected const KEY_AS_OF = 'asOf';
22
24 private $stash;
25
27 private const RL_MODULE_DEP_TTL = BagOStuff::TTL_YEAR;
28
30 public $updateBuffer = [];
31
35 public function __construct( BagOStuff $stash ) {
36 $this->stash = $stash;
37 }
38
44 public function newEntityDependencies( array $paths = [], $asOf = null ) {
45 return [ self::KEY_PATHS => $paths, self::KEY_AS_OF => $asOf ];
46 }
47
61 final public function retrieve( $entity ) {
62 return $this->retrieveMulti( [ $entity ] )[$entity];
63 }
64
71 public function retrieveMulti( array $entities ) {
72 $entitiesByKey = [];
73 foreach ( $entities as $entity ) {
74 $entitiesByKey[$this->getStoreKey( $entity )] = $entity;
75 }
76
77 $blobsByKey = $this->stash->getMulti( array_keys( $entitiesByKey ) );
78
79 $results = [];
80 foreach ( $entitiesByKey as $key => $entity ) {
81 $blob = $blobsByKey[$key] ?? null;
82 $data = is_string( $blob ) ? json_decode( $blob, true ) : null;
83 $results[$entity] = $this->newEntityDependencies(
84 $data[self::KEY_PATHS] ?? [],
85 $data[self::KEY_AS_OF] ?? null
86 );
87 }
88
89 return $results;
90 }
91
92 public function storeMulti( array $pathByEntity ): void {
93 $hasPendingUpdate = (bool)$this->updateBuffer;
94
95 foreach ( $pathByEntity as $entity => $paths ) {
96 $this->updateBuffer[$entity] = $paths ? $this->newEntityDependencies( $paths, time() ) : null;
97 }
98
99 // If paths were unchanged, leave the dependency store unchanged also.
100 // The entry will eventually expire, after which we will briefly issue an incomplete
101 // version hash for a 5-min startup window, the module then recomputes and rediscovers
102 // the paths and arrive at the same module version hash once again. It will churn
103 // part of the browser cache once, for clients connecting during that window.
104
105 if ( !$hasPendingUpdate ) {
106 DeferredUpdates::addCallableUpdate( function () {
107 $updatesByEntity = $this->updateBuffer;
108 $this->updateBuffer = [];
109 $scopeLocks = [];
110 $depsByEntity = [];
111 $entitiesUnreg = [];
112 $blobsByKey = [];
113 $cache = MediaWikiServices::getInstance()
114 ->getObjectCacheFactory()->getLocalClusterInstance();
115 foreach ( $updatesByEntity as $entity => $update ) {
116 $lockKey = $cache->makeKey( 'rl-deps', $entity );
117 $scopeLocks[$entity] = $cache->getScopedLock( $lockKey, 0 );
118 if ( !$scopeLocks[$entity] ) {
119 // avoid duplicate write request slams (T124649)
120 // the lock must be specific to the current wiki (T247028)
121 continue;
122 }
123 if ( !$update ) {
124 $entitiesUnreg[] = $entity;
125 } else {
126 $depsByEntity[$entity] = $update;
127 }
128 }
129
130 foreach ( $depsByEntity as $entity => $data ) {
131 if ( !is_array( $data[self::KEY_PATHS] ) || !is_int( $data[self::KEY_AS_OF] ) ) {
132 throw new InvalidArgumentException( "Invalid entry for '$entity'" );
133 }
134
135 // Normalize the list by removing duplicates and sorting
136 $data[self::KEY_PATHS] = array_values( array_unique( $data[self::KEY_PATHS] ) );
137 sort( $data[self::KEY_PATHS], SORT_STRING );
138
139 $blob = json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
140
141 $blobsByKey[$this->getStoreKey( $entity )] = $blob;
142 }
143
144 if ( $blobsByKey ) {
145 $this->stash->setMulti( $blobsByKey, self::RL_MODULE_DEP_TTL, BagOStuff::WRITE_BACKGROUND );
146 }
147
148 $this->remove( $entitiesUnreg );
149 } );
150 }
151 }
152
158 public function remove( $entities ) {
159 $keys = [];
160 foreach ( (array)$entities as $entity ) {
161 $keys[] = $this->getStoreKey( $entity );
162 }
163
164 if ( $keys ) {
165 $this->stash->deleteMulti( $keys, BagOStuff::WRITE_BACKGROUND );
166 }
167 }
168
173 private function getStoreKey( $entity ) {
174 return $this->stash->makeKey( "ResourceLoaderModule-dependencies", $entity );
175 }
176}
Defer callable updates to run later in the PHP process.
Service locator for MediaWiki core services.
Track per-module dependency file paths that are expensive to mass compute.
newEntityDependencies(array $paths=[], $asOf=null)
array $updateBuffer
Map of (module-variant => buffered DependencyStore updates)
retrieve( $entity)
Get the currently tracked dependencies for an entity.
retrieveMulti(array $entities)
Get the currently tracked dependencies for a set of entities.
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73