diff --git a/app/code/Magento/Backend/Cron/CleanLocks.php b/app/code/Magento/Backend/Cron/CleanLocks.php new file mode 100644 index 0000000000000..565f4518b9fb1 --- /dev/null +++ b/app/code/Magento/Backend/Cron/CleanLocks.php @@ -0,0 +1,39 @@ +lockFactory->create(); + + if ($locker instanceof FileLock) { + $numberOfLockFilesDeleted = $locker->cleanupOldLocks(); + + $this->logger->info(sprintf('Deleted %d old lock files', $numberOfLockFilesDeleted)); + } + } +} diff --git a/app/code/Magento/Backend/etc/crontab.xml b/app/code/Magento/Backend/etc/crontab.xml index e555fbdcb394e..54b3f89b63559 100644 --- a/app/code/Magento/Backend/etc/crontab.xml +++ b/app/code/Magento/Backend/etc/crontab.xml @@ -1,8 +1,8 @@ @@ -10,5 +10,8 @@ 30 2 * * * + + 20 2 * * * + diff --git a/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php b/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php index c5e172614821c..b587696ae2bdb 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php @@ -1,7 +1,7 @@ lockPath = '/tmp/magento-test-locks'; $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->model = $this->objectManager->create( \Magento\Framework\Lock\Backend\FileLock::class, - ['path' => '/tmp'] + ['path' => $this->lockPath] ); } @@ -52,4 +56,28 @@ public function testUnlockWithoutExistingLock() $this->assertFalse($this->model->isLocked($name)); $this->assertFalse($this->model->unlock($name)); } + + public function testCleanupOldFile() + { + $name = 'test_lock'; + + $this->assertTrue($this->model->lock($name)); + $this->assertTrue($this->model->unlock($name)); + + touch(sprintf('%s/%s', $this->lockPath, $name), strtotime('30 hours ago')); + + $this->assertEquals(1, $this->model->cleanupOldLocks()); + } + + public function testDontCleanupNewFile() + { + $name = 'test_lock'; + + $this->assertTrue($this->model->lock($name)); + $this->assertTrue($this->model->unlock($name)); + + touch(sprintf('%s/%s', $this->lockPath, $name), strtotime('1 hour ago')); + + $this->assertEquals(0, $this->model->cleanupOldLocks()); + } } diff --git a/lib/internal/Magento/Framework/Lock/Backend/FileLock.php b/lib/internal/Magento/Framework/Lock/Backend/FileLock.php index dd8eda9621667..615c2a300269b 100644 --- a/lib/internal/Magento/Framework/Lock/Backend/FileLock.php +++ b/lib/internal/Magento/Framework/Lock/Backend/FileLock.php @@ -1,7 +1,7 @@ */ private $locks = []; @@ -105,6 +105,44 @@ public function lock(string $name, int $timeout = -1): bool return true; } + /** + * Find lock files that haven't been touched in the last 24 hours and are unlocked, and delete those + */ + public function cleanupOldLocks(): int + { + if (!$this->fileDriver->isExists($this->path)) { + return 0; + } + + $numberOfLocksDeleted = 0; + $timestamp24HoursAgo = strtotime('24 hours ago'); + + $lockFiles = $this->fileDriver->readDirectory($this->path); + foreach ($lockFiles as $lockFile) { + if (!$this->fileDriver->isFile($lockFile)) { + continue; + } + + $modifiedTimestamp = filemtime($lockFile); + if ($timestamp24HoursAgo < $modifiedTimestamp) { + continue; + } + + if ($this->isLocked(basename($lockFile))) { + continue; + } + + try { + $this->fileDriver->deleteFile($lockFile); + ++$numberOfLocksDeleted; + } catch (FileSystemException $exception) { // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch + // do nothing + } + } + + return $numberOfLocksDeleted; + } + /** * Checks if a lock exists by name *