Path: blob/master/src/infrastructure/util/__tests__/PhabricatorGlobalLockTestCase.php
12242 views
<?php12final class PhabricatorGlobalLockTestCase3extends PhabricatorTestCase {45protected function getPhabricatorTestCaseConfiguration() {6return array(7self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,8);9}1011public function testConnectionPoolWithDefaultConnection() {12PhabricatorGlobalLock::clearConnectionPool();1314$this->assertEqual(150,16PhabricatorGlobalLock::getConnectionPoolSize(),17pht('Clear Connection Pool'));1819$lock_name = $this->newLockName();20$lock = PhabricatorGlobalLock::newLock($lock_name);21$lock->lock();2223$this->assertEqual(240,25PhabricatorGlobalLock::getConnectionPoolSize(),26pht('Connection Pool With Lock'));2728$lock->unlock();2930$this->assertEqual(311,32PhabricatorGlobalLock::getConnectionPoolSize(),33pht('Connection Pool With Lock Released'));3435PhabricatorGlobalLock::clearConnectionPool();36}3738public function testConnectionPoolWithSpecificConnection() {39$conn = PhabricatorGlobalLock::newConnection();4041PhabricatorGlobalLock::clearConnectionPool();4243$this->assertEqual(440,45PhabricatorGlobalLock::getConnectionPoolSize(),46pht('Clear Connection Pool'));4748$this->assertEqual(49false,50$conn->isHoldingAnyLock(),51pht('Specific Connection, No Lock'));5253$lock_name = $this->newLockName();54$lock = PhabricatorGlobalLock::newLock($lock_name);55$lock->setExternalConnection($conn);56$lock->lock();5758$this->assertEqual(590,60PhabricatorGlobalLock::getConnectionPoolSize(),61pht('Connection Pool + Specific, With Lock'));6263$this->assertEqual(64true,65$conn->isHoldingAnyLock(),66pht('Specific Connection, Holding Lock'));6768$lock->unlock();6970// The specific connection provided should NOT be returned to the71// connection pool.7273$this->assertEqual(740,75PhabricatorGlobalLock::getConnectionPoolSize(),76pht('Connection Pool + Specific, With Lock Released'));7778$this->assertEqual(79false,80$conn->isHoldingAnyLock(),81pht('Specific Connection, No Lock'));8283PhabricatorGlobalLock::clearConnectionPool();84}8586public function testExternalConnectionMutationScope() {87$conn = PhabricatorGlobalLock::newConnection();8889$lock_name = $this->newLockName();90$lock = PhabricatorGlobalLock::newLock($lock_name);91$lock->lock();9293$caught = null;94try {95$lock->setExternalConnection($conn);96} catch (Exception $ex) {97$caught = $ex;98} catch (Throwable $ex) {99$caught = $ex;100}101102$lock->unlock();103104$this->assertTrue(105($caught instanceof Exception),106pht('Changing connection while locked is forbidden.'));107}108109public function testMultipleLocks() {110$conn = PhabricatorGlobalLock::newConnection();111112PhabricatorGlobalLock::clearConnectionPool();113114$lock_name_a = $this->newLockName();115$lock_name_b = $this->newLockName();116117$lock_a = PhabricatorGlobalLock::newLock($lock_name_a);118$lock_a->setExternalConnection($conn);119120$lock_b = PhabricatorGlobalLock::newLock($lock_name_b);121$lock_b->setExternalConnection($conn);122123$lock_a->lock();124125$caught = null;126try {127$lock_b->lock();128} catch (Exception $ex) {129$caught = $ex;130} catch (Throwable $ex) {131$caught = $ex;132}133134// See T13627. The lock infrastructure must forbid this because it does135// not work in versions of MySQL older than 5.7.136137$this->assertTrue(138($caught instanceof Exception),139pht('Expect multiple locks on the same connection to fail.'));140}141142public function testPoolReleaseOnFailure() {143$conn = PhabricatorGlobalLock::newConnection();144$lock_name = $this->newLockName();145146PhabricatorGlobalLock::clearConnectionPool();147148$this->assertEqual(1490,150PhabricatorGlobalLock::getConnectionPoolSize(),151pht('Clear Connection Pool'));152153$lock = PhabricatorGlobalLock::newLock($lock_name);154155// NOTE: We're cheating here, since there's a global registry of locks156// for the process that we have to bypass. In the real world, this lock157// would have to be held by some external process. To simplify this158// test case, just use a raw "GET_LOCK()" call to hold the lock.159160$raw_conn = PhabricatorGlobalLock::newConnection();161$raw_name = $lock->getName();162163$row = queryfx_one(164$raw_conn,165'SELECT GET_LOCK(%s, %f)',166$raw_name,1670);168$this->assertTrue((bool)head($row), pht('Establish Raw Lock'));169170$this->assertEqual(1710,172PhabricatorGlobalLock::getConnectionPoolSize(),173pht('Connection Pool with Held Lock'));174175// We expect this sequence to establish a new connection, fail to acquire176// the lock, then put the connection in the connection pool. After the177// first cycle, the connection should be reused.178179for ($ii = 0; $ii < 3; $ii++) {180$this->tryHeldLock($lock_name);181$this->assertEqual(1821,183PhabricatorGlobalLock::getConnectionPoolSize(),184pht('Connection Pool After Lock Failure'));185}186187PhabricatorGlobalLock::clearConnectionPool();188189// Now, do the same thing with an external connection. This connection190// should not be put into the pool! See T13627.191192for ($ii = 0; $ii < 3; $ii++) {193$this->tryHeldLock($lock_name, $conn);194$this->assertEqual(1950,196PhabricatorGlobalLock::getConnectionPoolSize(),197pht('Connection Pool After External Lock Failure'));198}199}200201private function newLockName() {202return 'testlock-'.Filesystem::readRandomCharacters(16);203}204205private function tryHeldLock(206$lock_name,207AphrontDatabaseConnection $conn = null) {208209$lock = PhabricatorGlobalLock::newLock($lock_name);210211if ($conn) {212$lock->setExternalConnection($conn);213}214215$caught = null;216try {217$lock->lock(0);218} catch (PhutilLockException $ex) {219$caught = $ex;220}221222$this->assertTrue($caught instanceof PhutilLockException);223}224225226}227228229