diff --git a/typo3/sysext/core/Classes/Locking/FileLockStrategy.php b/typo3/sysext/core/Classes/Locking/FileLockStrategy.php index cb2b58801bc0c5b912004e806e0a72066a909074..e59c4b86adb167fa2d6c2cabfb5d2a6151bae485 100644 --- a/typo3/sysext/core/Classes/Locking/FileLockStrategy.php +++ b/typo3/sysext/core/Classes/Locking/FileLockStrategy.php @@ -27,6 +27,8 @@ class FileLockStrategy implements LockingStrategyInterface { const FILE_LOCK_FOLDER = 'lock/'; + const DEFAULT_PRIORITY = 75; + /** * @var resource File pointer if using flock method */ @@ -49,11 +51,17 @@ class FileLockStrategy implements LockingStrategyInterface public function __construct($subject) { /* - * Tests if the directory for simple locks is available. + * Tests if the directory for file locks is available. * If not, the directory will be created. The lock path is usually * below typo3temp/var, typo3temp/var itself should exist already (or root-path/var/ respectively) */ - $path = Environment::getVarPath() . '/' . self::FILE_LOCK_FOLDER; + if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['lockFileDir'] ?? false) { + $path = Environment::getProjectPath() . '/' + . trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['lockFileDir'], ' /') + . '/'; + } else { + $path = Environment::getVarPath() . '/' . self::FILE_LOCK_FOLDER; + } if (!is_dir($path)) { // Not using mkdir_deep on purpose here, if typo3temp itself // does not exist, this issue should be solved on a different @@ -152,7 +160,8 @@ class FileLockStrategy implements LockingStrategyInterface */ public static function getPriority() { - return 75; + return $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['priority'] + ?? self::DEFAULT_PRIORITY; } /** diff --git a/typo3/sysext/core/Classes/Locking/SemaphoreLockStrategy.php b/typo3/sysext/core/Classes/Locking/SemaphoreLockStrategy.php index 016fd5c5104b269863daff8a723530178b7d9140..fbff61c1c742455ad9575f6d3454033e209185e5 100644 --- a/typo3/sysext/core/Classes/Locking/SemaphoreLockStrategy.php +++ b/typo3/sysext/core/Classes/Locking/SemaphoreLockStrategy.php @@ -26,6 +26,8 @@ class SemaphoreLockStrategy implements LockingStrategyInterface { const FILE_LOCK_FOLDER = 'lock/'; + const DEFAULT_PRIORITY = 25; + /** * @var mixed Identifier used for this lock */ @@ -52,7 +54,18 @@ class SemaphoreLockStrategy implements LockingStrategyInterface */ public function __construct($subject) { - $path = Environment::getVarPath() . '/' . self::FILE_LOCK_FOLDER; + /* + * Tests if the directory for semaphore locks is available. + * If not, the directory will be created. The lock path is usually + * below typo3temp/var, typo3temp/var itself should exist already (or root-path/var/ respectively) + */ + if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['lockFileDir'] ?? false) { + $path = Environment::getProjectPath() . '/' + . trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['lockFileDir'], ' /') + . '/'; + } else { + $path = Environment::getVarPath() . '/' . self::FILE_LOCK_FOLDER; + } if (!is_dir($path)) { // Not using mkdir_deep on purpose here, if typo3temp/var itself // does not exist, this issue should be solved on a different @@ -147,7 +160,8 @@ class SemaphoreLockStrategy implements LockingStrategyInterface */ public static function getPriority() { - return 25; + return $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['priority'] + ?? self::DEFAULT_PRIORITY; } /** diff --git a/typo3/sysext/core/Classes/Locking/SimpleLockStrategy.php b/typo3/sysext/core/Classes/Locking/SimpleLockStrategy.php index bd792c272cd3c14cacdfa27f4e4830ac3da7b9c2..a354485932f70becb023e0008c1840bd049e0e3d 100644 --- a/typo3/sysext/core/Classes/Locking/SimpleLockStrategy.php +++ b/typo3/sysext/core/Classes/Locking/SimpleLockStrategy.php @@ -26,6 +26,8 @@ class SimpleLockStrategy implements LockingStrategyInterface { const FILE_LOCK_FOLDER = 'lock/'; + const DEFAULT_PRIORITY = 50; + /** * @var string File path used for this lock */ @@ -55,7 +57,13 @@ class SimpleLockStrategy implements LockingStrategyInterface // Tests if the directory for simple locks is available. // If not, the directory will be created. The lock path is usually // below typo3temp/var, typo3temp/var itself should exist already (or getProjectPath . /var/ respectively) - $path = Environment::getVarPath() . '/' . self::FILE_LOCK_FOLDER; + if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['lockFileDir'] ?? false) { + $path = Environment::getProjectPath() . '/' + . trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['lockFileDir'], ' /') + . '/'; + } else { + $path = Environment::getVarPath() . '/' . self::FILE_LOCK_FOLDER; + } if (!is_dir($path)) { // Not using mkdir_deep on purpose here, if typo3temp/var itself // does not exist, this issue should be solved on a different @@ -183,7 +191,8 @@ class SimpleLockStrategy implements LockingStrategyInterface */ public static function getPriority() { - return 50; + return $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['priority'] + ?? self::DEFAULT_PRIORITY; } /** diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php index 7f91e1bccf183a3f3363b0728ac411349ca57228..ddd9b9b77983b0dc27af9e821d5e6a3dc85dfa40 100644 --- a/typo3/sysext/core/Configuration/DefaultConfiguration.php +++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php @@ -129,6 +129,34 @@ return [ 'StaticValueMapper' => \TYPO3\CMS\Core\Routing\Aspect\StaticValueMapper::class, ], ], + 'locking' => [ + 'strategies' => [ + \TYPO3\CMS\Core\Locking\FileLockStrategy::class => [ + // if not set: use default priority of FileLockStrategy + //'priority' => 75, + + // if not set: use default path of FileLockStrategy + // If you change this, directory must exist! + // 'lockFileDir' => 'typo3temp/var' + ], + \TYPO3\CMS\Core\Locking\SemaphoreLockStrategy::class => [ + // if not set: use default priority of SemaphoreLockStrategy + // 'priority' => 50 + + // empty: use default path of SemaphoreLockStrategy + // If you change this, directory must exist! + // 'lockFileDir' => 'typo3temp/var' + ], + \TYPO3\CMS\Core\Locking\SimpleLockStrategy::class => [ + // if not set: use default priority of SimpleLockStrategy + //'priority' => 25, + + // empty: use default path of SimpleLockStrategy + // If you change this, directory must exist! + // 'lockFileDir' => 'typo3temp/var' + ] + ] + ], 'caching' => [ 'cacheConfigurations' => [ // The cache_core cache is is for core php code only and must diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-87072-AddedConfigurationOptionsForLockingAddedConfigurationOptionsForLocking.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-87072-AddedConfigurationOptionsForLockingAddedConfigurationOptionsForLocking.rst new file mode 100644 index 0000000000000000000000000000000000000000..557eefc0f10364c2a81c92cb9b452208cefbe5ec --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-87072-AddedConfigurationOptionsForLockingAddedConfigurationOptionsForLocking.rst @@ -0,0 +1,73 @@ +.. include:: ../../Includes.txt + +========================================================= +Feature: #87072 - Added Configuration Options for Locking +========================================================= + +See :issue:`87072` + +Description +=========== + +With change `Feature: #47712 - New Locking API +<https://docs.typo3.org/typo3cms/extensions/core/latest/Changelog/7.2/Feature-47712-NewLockingAPI.html>`__ a new Locking API was introduced. +This API can be extended. It provides three locking strategies and an interface for adding your own locking strategy in an extension. +However, until now, the default behaviour could not be changed using only the TYPO3 core. + +The introduction of new options makes some of the default properties of the locking API configurable: + +* The priority of each locking strategy can be changed. +* The directory where the lock files are written can be configured. + +Configuration example +--------------------- + +typo3conf/AdditionalConfiguration.php:: + + <?php + + if (!defined('TYPO3_MODE')) { + die('Access denied.'); + } + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\FileLockStrategy::class]['priority'] = 10; + // The directory specified here must exist und must be a subdirectory of `Environment::getProjectPath()` + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\FileLockStrategy::class]['lockFileDir'] = 'mylockdir'; + + +This sets the priority of FileLockStrategy to 10, thus making it the locking strategy with the least priority, which +will be chosen last by the LockFactory. + +The directory for storing the locks is changed to `mylockdir`. + +Impact +====== + +For administrators +------------------ + +Nothing changes by default. The default values are used for the Locking API, same as before this change. + +If AdditionalConfiguration.php is used to change Global Configuration settings for Locking API, and not used with care, +it can seriously compromise the stability of the system. As usual, when overriding Global Configuration with +LocalConfiguration.php or AdditionalConfiguration.php, great caution must be used. + +Specifically, do the following: + +* Test this on a test system first +* If you change the priorities, make sure your system fully supports the locking strategy which will be chosen by default. +* If you change the directory, make sure the directory exists and will always exist in the future. + +For developers +-------------- + +If a locking strategy is added by an extension, the priority and possibly directory for storing locks should be made +configurable as well:: + + public static function getPriority() + { + return $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][self::class]['priority'] + ?? self::DEFAULT_PRIORITY; + } + +.. index:: ext:core diff --git a/typo3/sysext/core/Tests/Unit/Locking/FileLockStrategyTest.php b/typo3/sysext/core/Tests/Unit/Locking/FileLockStrategyTest.php index a83f4174062fd016423f439c0c77d979fcfad848..a5a5e7504e1e1ecc186153bad27c6aa57b8b1ac9 100644 --- a/typo3/sysext/core/Tests/Unit/Locking/FileLockStrategyTest.php +++ b/typo3/sysext/core/Tests/Unit/Locking/FileLockStrategyTest.php @@ -42,4 +42,23 @@ class FileLockStrategyTest extends UnitTestCase $lock = $this->getAccessibleMock(FileLockStrategy::class, ['dummy'], ['999999999']); self::assertSame(Environment::getVarPath() . '/' . FileLockStrategy::FILE_LOCK_FOLDER . 'flock_' . md5('999999999'), $lock->_get('filePath')); } + + /** + * @test + */ + public function getPriorityReturnsDefaultPriority() + { + self::assertEquals(FileLockStrategy::getPriority(), FileLockStrategy::DEFAULT_PRIORITY); + } + + /** + * @test + */ + public function setPriority() + { + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\FileLockStrategy::class]['priority'] = 10; + + self::assertEquals(10, FileLockStrategy::getPriority()); + unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\FileLockStrategy::class]['priority']); + } } diff --git a/typo3/sysext/core/Tests/Unit/Locking/LockFactoryTest.php b/typo3/sysext/core/Tests/Unit/Locking/LockFactoryTest.php index 95fbcf3c2f76e52ff058e169d6b203951b1edb7d..a3c69a8773ba258c3368765ee9a376e029e0a1b1 100644 --- a/typo3/sysext/core/Tests/Unit/Locking/LockFactoryTest.php +++ b/typo3/sysext/core/Tests/Unit/Locking/LockFactoryTest.php @@ -19,6 +19,8 @@ use TYPO3\CMS\Core\Locking\FileLockStrategy; use TYPO3\CMS\Core\Locking\LockFactory; use TYPO3\CMS\Core\Locking\LockingStrategyInterface; use TYPO3\CMS\Core\Locking\SemaphoreLockStrategy; +use TYPO3\CMS\Core\Locking\SimpleLockStrategy; +use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Tests\Unit\Locking\Fixtures\DummyLock; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; @@ -32,6 +34,11 @@ class LockFactoryTest extends UnitTestCase */ protected $mockFactory; + /** + * @var array + */ + protected $strategiesConfigBackup = []; + /** * Set up the tests */ @@ -39,6 +46,21 @@ class LockFactoryTest extends UnitTestCase { parent::setUp(); $this->mockFactory = $this->getAccessibleMock(LockFactory::class, ['dummy']); + + // backup global configuration + if (isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'])) { + $this->strategiesConfigBackup = $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies']; + } else { + $this->strategiesConfigBackup = []; + } + } + + protected function tearDown(): void + { + // restore global configuration + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'] = $this->strategiesConfigBackup; + + parent::tearDown(); } /** @@ -67,7 +89,10 @@ class LockFactoryTest extends UnitTestCase public function getLockerReturnsExpectedClass() { $this->mockFactory->_set('lockingStrategy', [FileLockStrategy::class => true, DummyLock::class => true]); - $locker = $this->mockFactory->createLocker('id', LockingStrategyInterface::LOCK_CAPABILITY_EXCLUSIVE | LockingStrategyInterface::LOCK_CAPABILITY_SHARED); + $locker = $this->mockFactory->createLocker( + 'id', + LockingStrategyInterface::LOCK_CAPABILITY_EXCLUSIVE | LockingStrategyInterface::LOCK_CAPABILITY_SHARED + ); self::assertInstanceOf(FileLockStrategy::class, $locker); } @@ -81,6 +106,25 @@ class LockFactoryTest extends UnitTestCase self::assertInstanceOf(DummyLock::class, $locker); } + /** + * @test + */ + public function setPriorityGetLockerReturnsClassWithHighestPriority() + { + $lowestValue = min([ + FileLockStrategy::DEFAULT_PRIORITY, + SimpleLockStrategy::DEFAULT_PRIORITY, + SemaphoreLockStrategy::DEFAULT_PRIORITY + ]) - 1; + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][FileLockStrategy::class]['priority'] = $lowestValue; + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][SemaphoreLockStrategy::class]['priority'] = $lowestValue; + $locker = $this->mockFactory->createLocker('id'); + self::assertInstanceOf(SimpleLockStrategy::class, $locker); + + unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][FileLockStrategy::class]['priority']); + unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][SemaphoreLockStrategy::class]['priority']); + } + /** * @test */ diff --git a/typo3/sysext/core/Tests/Unit/Locking/SemaphoreLockStrategyTest.php b/typo3/sysext/core/Tests/Unit/Locking/SemaphoreLockStrategyTest.php index b9c5a60c6d375e83111ee001177ac94141173afe..e0a2be90e6a26e4fb2d9695d8f7291ac8db33926 100644 --- a/typo3/sysext/core/Tests/Unit/Locking/SemaphoreLockStrategyTest.php +++ b/typo3/sysext/core/Tests/Unit/Locking/SemaphoreLockStrategyTest.php @@ -15,6 +15,7 @@ namespace TYPO3\CMS\Core\Tests\Unit\Locking; */ use TYPO3\CMS\Core\Locking\SemaphoreLockStrategy; +use TYPO3\CMS\Core\Locking\SimpleLockStrategy; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; /** @@ -34,4 +35,23 @@ class SemaphoreLockStrategyTest extends UnitTestCase $lock->release(); $lock->destroy(); } + + /** + * @test + */ + public function getPriorityReturnsDefaultPriority() + { + self::assertEquals(SimpleLockStrategy::getPriority(), SimpleLockStrategy::DEFAULT_PRIORITY); + } + + /** + * @test + */ + public function setPriority() + { + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\SemaphoreLockStrategy::class]['priority'] = 10; + + self::assertEquals(10, SemaphoreLockStrategy::getPriority()); + unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\SemaphoreLockStrategy::class]['priority']); + } } diff --git a/typo3/sysext/core/Tests/Unit/Locking/SimpleLockStrategyTest.php b/typo3/sysext/core/Tests/Unit/Locking/SimpleLockStrategyTest.php index 8c20bf9d7365be49d97ad97cbc03b7d06f3c7a57..2b0fd3b3d163a98b7a1f4fe5f34618ba13e7efdd 100644 --- a/typo3/sysext/core/Tests/Unit/Locking/SimpleLockStrategyTest.php +++ b/typo3/sysext/core/Tests/Unit/Locking/SimpleLockStrategyTest.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Core\Tests\Unit\Locking; use PHPUnit\Framework\SkippedTestError; use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Locking\SemaphoreLockStrategy; use TYPO3\CMS\Core\Locking\SimpleLockStrategy; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; @@ -124,4 +125,23 @@ class SimpleLockStrategyTest extends UnitTestCase } self::assertTrue($fileExists); } + + /** + * @test + */ + public function getPriorityReturnsDefaultPriority() + { + self::assertEquals(SemaphoreLockStrategy::getPriority(), SemaphoreLockStrategy::DEFAULT_PRIORITY); + } + + /** + * @test + */ + public function setPriority() + { + $GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\SimpleLockStrategy::class]['priority'] = 10; + + self::assertEquals(10, SimpleLockStrategy::getPriority()); + unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][\TYPO3\CMS\Core\Locking\SimpleLockStrategy::class]['priority']); + } }