Skip to content

Commit 77ae7f3

Browse files
committed
Implemented locking on pruning to prevent concurrent pruning processes
1 parent 6dbafb1 commit 77ae7f3

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

src/Psr6Store.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,14 @@ public function prune()
395395
return;
396396
}
397397

398-
$this->cache->prune();
398+
// Make sure we do not have multiple pruning processes running
399+
$lock = $this->lockFactory->createLock('prune-lock');
400+
401+
if ($lock->acquire()) {
402+
$this->cache->prune();
403+
404+
$lock->release();
405+
}
399406
}
400407

401408
/**

tests/Psr6StoreTest.php

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,10 +506,27 @@ public function testAutoPruneExpiredEntries()
506506
->expects($this->exactly(3))
507507
->method('prune');
508508

509+
$lock = $this->createMock(LockInterface::class);
510+
$lock
511+
->expects($this->exactly(3))
512+
->method('acquire')
513+
->willReturn(true);
514+
$lock
515+
->expects($this->exactly(3))
516+
->method('release')
517+
->willReturn(true);
518+
519+
$lockFactory = $this->createMock(Factory::class);
520+
$lockFactory
521+
->expects($this->any())
522+
->method('createLock')
523+
->with('prune-lock')
524+
->willReturn($lock);
525+
509526
$store = new Psr6Store([
510-
'cache_directory' => sys_get_temp_dir(),
511527
'cache' => $cache,
512528
'prune_threshold' => 5,
529+
'lock_factory' => $lockFactory,
513530
]);
514531

515532
foreach (range(1, 21) as $entry) {
@@ -550,6 +567,47 @@ public function testAutoPruneIsSkippedIfThresholdDisabled()
550567
$store->cleanup();
551568
}
552569

570+
public function testAutoPruneIsSkippedIfPruningIsAlreadyInProgress()
571+
{
572+
$innerCache = new ArrayAdapter();
573+
$cache = $this->getMockBuilder(TagAwareAdapter::class)
574+
->setConstructorArgs([$innerCache])
575+
->setMethods(['prune'])
576+
->getMock();
577+
578+
$cache
579+
->expects($this->never())
580+
->method('prune');
581+
582+
$lock = $this->createMock(LockInterface::class);
583+
$lock
584+
->expects($this->exactly(3))
585+
->method('acquire')
586+
->willReturn(false);
587+
588+
$lockFactory = $this->createMock(Factory::class);
589+
$lockFactory
590+
->expects($this->any())
591+
->method('createLock')
592+
->with('prune-lock')
593+
->willReturn($lock);
594+
595+
$store = new Psr6Store([
596+
'cache' => $cache,
597+
'prune_threshold' => 5,
598+
'lock_factory' => $lockFactory,
599+
]);
600+
601+
foreach (range(1, 21) as $entry) {
602+
$request = Request::create('https://foobar.com/'.$entry);
603+
$response = new Response('hello world', 200);
604+
605+
$store->write($request, $response);
606+
}
607+
608+
$store->cleanup();
609+
}
610+
553611
public function testItFailsWithoutCacheDirectoryForCache()
554612
{
555613
$this->expectException(MissingOptionsException::class);

0 commit comments

Comments
 (0)