Skip to content

Commit 6b43288

Browse files
committed
Adding a lock mechanism on cache rebuild
When a cache expires, it needs to be recomputed. If the website is under heavy load, the recompute will be started by many processes at once and will slow down the server. Adding a lock allows us to perform this recompute only once.
1 parent 02a7b65 commit 6b43288

File tree

3 files changed

+67
-15
lines changed

3 files changed

+67
-15
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"phpdocumentor/reflection-docblock": "^4.3",
2121
"phpdocumentor/type-resolver": "^0.4",
2222
"psr/http-message": "^1",
23-
"ecodev/graphql-upload": "^4.0"
23+
"ecodev/graphql-upload": "^4.0",
24+
"symfony/lock": "^3 || ^4"
2425
},
2526
"require-dev": {
2627
"phpunit/phpunit": "^6.1",

src/GlobControllerQueryProvider.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
use Mouf\Composer\ClassNameMapper;
77
use Psr\Container\ContainerInterface;
88
use Psr\SimpleCache\CacheInterface;
9+
use Symfony\Component\Lock\Store\SemaphoreStore;
910
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
1011
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
12+
use Symfony\Component\Lock\Factory as LockFactory;
1113

1214
/**
1315
* Scans all the classes in a given namespace of the main project (not the vendor directory).
@@ -53,6 +55,10 @@ final class GlobControllerQueryProvider implements QueryProviderInterface
5355
* @var bool
5456
*/
5557
private $recursive;
58+
/**
59+
* @var LockFactory
60+
*/
61+
private $lockFactory;
5662

5763
/**
5864
* @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
@@ -72,6 +78,9 @@ public function __construct(string $namespace, FieldsBuilderFactory $fieldsBuild
7278
$this->fieldsBuilderFactory = $fieldsBuilderFactory;
7379
$this->recursiveTypeMapper = $recursiveTypeMapper;
7480
$this->recursive = $recursive;
81+
$store = new SemaphoreStore();
82+
$this->lockFactory = new LockFactory($store);
83+
7584
}
7685

7786
private function getAggregateControllerQueryProvider(): AggregateControllerQueryProvider
@@ -93,13 +102,24 @@ private function getInstancesList(): array
93102
$key = 'globQueryProvider_'.str_replace('\\', '_', $this->namespace);
94103
$this->instancesList = $this->cache->get($key);
95104
if ($this->instancesList === null) {
96-
$this->instancesList = $this->buildInstancesList();
105+
$this->instancesList = $this->lockAndBuildInstanceList();
97106
$this->cache->set($key, $this->instancesList, $this->cacheTtl);
98107
}
99108
}
100109
return $this->instancesList;
101110
}
102111

112+
private function lockAndBuildInstanceList(): array
113+
{
114+
$lock = $this->lockFactory->createLock('buildInstanceList_'.$this->namespace, 5);
115+
$lock->acquire(true);
116+
try {
117+
return $this->buildInstancesList();
118+
} finally {
119+
$lock->release();
120+
}
121+
}
122+
103123
/**
104124
* @return string[]
105125
*/

src/Mappers/GlobTypeMapper.php

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Psr\SimpleCache\CacheInterface;
1313
use ReflectionClass;
1414
use ReflectionMethod;
15+
use Symfony\Component\Lock\Factory as LockFactory;
16+
use Symfony\Component\Lock\Store\SemaphoreStore;
1517
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
1618
use TheCodingMachine\GraphQLite\AnnotationReader;
1719
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
@@ -119,6 +121,10 @@ final class GlobTypeMapper implements TypeMapperInterface
119121
* @var bool
120122
*/
121123
private $recursive;
124+
/**
125+
* @var LockFactory
126+
*/
127+
private $lockFactory;
122128

123129
/**
124130
* @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
@@ -136,6 +142,8 @@ public function __construct(string $namespace, TypeGenerator $typeGenerator, Inp
136142
$this->inputTypeGenerator = $inputTypeGenerator;
137143
$this->inputTypeUtils = $inputTypeUtils;
138144
$this->recursive = $recursive;
145+
$store = new SemaphoreStore();
146+
$this->lockFactory = new LockFactory($store);
139147
}
140148

141149
/**
@@ -160,7 +168,7 @@ private function getMaps(): array
160168
$this->mapClassToFactory === null ||
161169
$this->mapInputNameToFactory
162170
) {
163-
$this->buildMap();
171+
$this->lockAndBuildMap();
164172
// This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
165173
// Defaults to 2 seconds.
166174
$this->cache->set($keyClassCache, $this->mapClassToTypeArray, $this->globTtl);
@@ -258,6 +266,17 @@ private function getClassList(): array
258266
return $this->classes;
259267
}
260268

269+
private function lockAndBuildMap(): void
270+
{
271+
$lock = $this->lockFactory->createLock('buildmap_'.$this->namespace, 5);
272+
$lock->acquire(true);
273+
try {
274+
$this->buildMap();
275+
} finally {
276+
$lock->release();
277+
}
278+
}
279+
261280
private function buildMap(): void
262281
{
263282
$this->mapClassToTypeArray = [];
@@ -303,27 +322,39 @@ private function buildMap(): void
303322

304323
private function buildMapClassToExtendTypeArray(): void
305324
{
306-
$this->mapClassToExtendTypeArray = [];
307-
$classes = $this->getClassList();
308-
foreach ($classes as $className => $refClass) {
309-
$extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
325+
$lock = $this->lockFactory->createLock('buildmapclassextend_'.$this->namespace, 5);
326+
$lock->acquire(true);
327+
try {
328+
$this->mapClassToExtendTypeArray = [];
329+
$classes = $this->getClassList();
330+
foreach ($classes as $className => $refClass) {
331+
$extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
310332

311-
if ($extendType !== null) {
312-
$this->storeExtendTypeMapperByClassInCache($className, $extendType, $refClass->getFileName());
333+
if ($extendType !== null) {
334+
$this->storeExtendTypeMapperByClassInCache($className, $extendType, $refClass->getFileName());
335+
}
313336
}
337+
} finally {
338+
$lock->release();
314339
}
315340
}
316341

317342
private function buildMapNameToExtendTypeArray(RecursiveTypeMapperInterface $recursiveTypeMapper): void
318343
{
319-
$this->mapNameToExtendType = [];
320-
$classes = $this->getClassList();
321-
foreach ($classes as $className => $refClass) {
322-
$extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
344+
$lock = $this->lockFactory->createLock('buildmapnameextend_'.$this->namespace, 5);
345+
$lock->acquire(true);
346+
try {
347+
$this->mapNameToExtendType = [];
348+
$classes = $this->getClassList();
349+
foreach ($classes as $className => $refClass) {
350+
$extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
323351

324-
if ($extendType !== null) {
325-
$this->storeExtendTypeMapperByNameInCache($className, $extendType, $refClass->getFileName(), $recursiveTypeMapper);
352+
if ($extendType !== null) {
353+
$this->storeExtendTypeMapperByNameInCache($className, $extendType, $refClass->getFileName(), $recursiveTypeMapper);
354+
}
326355
}
356+
} finally {
357+
$lock->release();
327358
}
328359
}
329360

0 commit comments

Comments
 (0)