MediaWiki master
DependencyStore.php
Go to the documentation of this file.
1<?php
22
23use InvalidArgumentException;
27
34 protected const KEY_PATHS = 'paths';
35 protected const KEY_AS_OF = 'asOf';
36
38 private $stash;
39
41 private const RL_MODULE_DEP_TTL = BagOStuff::TTL_YEAR;
42
44 public $updateBuffer = [];
45
49 public function __construct( BagOStuff $stash ) {
50 $this->stash = $stash;
51 }
52
58 public function newEntityDependencies( array $paths = [], $asOf = null ) {
59 return [ self::KEY_PATHS => $paths, self::KEY_AS_OF => $asOf ];
60 }
61
75 final public function retrieve( $entity ) {
76 return $this->retrieveMulti( [ $entity ] )[$entity];
77 }
78
85 public function retrieveMulti( array $entities ) {
86 $entitiesByKey = [];
87 foreach ( $entities as $entity ) {
88 $entitiesByKey[$this->getStoreKey( $entity )] = $entity;
89 }
90
91 $blobsByKey = $this->stash->getMulti( array_keys( $entitiesByKey ) );
92
93 $results = [];
94 foreach ( $entitiesByKey as $key => $entity ) {
95 $blob = $blobsByKey[$key] ?? null;
96 $data = is_string( $blob ) ? json_decode( $blob, true ) : null;
97 $results[$entity] = $this->newEntityDependencies(
98 $data[self::KEY_PATHS] ?? [],
99 $data[self::KEY_AS_OF] ?? null
100 );
101 }
102
103 return $results;
104 }
105
106 public function storeMulti( array $pathByEntity ): void {
107 $hasPendingUpdate = (bool)$this->updateBuffer;
108
109 foreach ( $pathByEntity as $entity => $paths ) {
110 $this->updateBuffer[$entity] = $paths ? $this->newEntityDependencies( $paths, time() ) : null;
111 }
112
113 // If paths were unchanged, leave the dependency store unchanged also.
114 // The entry will eventually expire, after which we will briefly issue an incomplete
115 // version hash for a 5-min startup window, the module then recomputes and rediscovers
116 // the paths and arrive at the same module version hash once again. It will churn
117 // part of the browser cache once, for clients connecting during that window.
118
119 if ( !$hasPendingUpdate ) {
120 DeferredUpdates::addCallableUpdate( function () {
121 $updatesByEntity = $this->updateBuffer;
122 $this->updateBuffer = [];
123 $scopeLocks = [];
124 $depsByEntity = [];
125 $entitiesUnreg = [];
126 $blobsByKey = [];
127 $cache = MediaWikiServices::getInstance()
128 ->getObjectCacheFactory()->getLocalClusterInstance();
129 foreach ( $updatesByEntity as $entity => $update ) {
130 $lockKey = $cache->makeKey( 'rl-deps', $entity );
131 $scopeLocks[$entity] = $cache->getScopedLock( $lockKey, 0 );
132 if ( !$scopeLocks[$entity] ) {
133 // avoid duplicate write request slams (T124649)
134 // the lock must be specific to the current wiki (T247028)
135 continue;
136 }
137 if ( !$update ) {
138 $entitiesUnreg[] = $entity;
139 } else {
140 $depsByEntity[$entity] = $update;
141 }
142 }
143
144 foreach ( $depsByEntity as $entity => $data ) {
145 if ( !is_array( $data[self::KEY_PATHS] ) || !is_int( $data[self::KEY_AS_OF] ) ) {
146 throw new InvalidArgumentException( "Invalid entry for '$entity'" );
147 }
148
149 // Normalize the list by removing duplicates and sorting
150 $data[self::KEY_PATHS] = array_values( array_unique( $data[self::KEY_PATHS] ) );
151 sort( $data[self::KEY_PATHS], SORT_STRING );
152
153 $blob = json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
154
155 $blobsByKey[$this->getStoreKey( $entity )] = $blob;
156 }
157
158 if ( $blobsByKey ) {
159 $this->stash->setMulti( $blobsByKey, self::RL_MODULE_DEP_TTL, BagOStuff::WRITE_BACKGROUND );
160 }
161
162 $this->remove( $entitiesUnreg );
163 } );
164 }
165 }
166
172 public function remove( $entities ) {
173 $keys = [];
174 foreach ( (array)$entities as $entity ) {
175 $keys[] = $this->getStoreKey( $entity );
176 }
177
178 if ( $keys ) {
179 $this->stash->deleteMulti( $keys, BagOStuff::WRITE_BACKGROUND );
180 }
181 }
182
187 private function getStoreKey( $entity ) {
188 return $this->stash->makeKey( "ResourceLoaderModule-dependencies", $entity );
189 }
190}
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:88