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 = [];
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.
static getInstance()
Returns the global default instance of the top level service locator.
Track per-module dependency file paths that are expensive to mass compute.
array $updateBuffer
Map of (module-variant => buffered DependencyStore updates)
retrieve( $entity)
Get the currently tracked dependencies for an entity.
newEntityDependencies(array $paths=[], $asOf=null)
retrieveMulti(array $entities)
Get the currently tracked dependencies for a set of entities.
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73