Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/util/__tests__/PhabricatorGlobalLockTestCase.php
Show All 31 Lines | $this->assertEqual( | ||||
1, | 1, | ||||
PhabricatorGlobalLock::getConnectionPoolSize(), | PhabricatorGlobalLock::getConnectionPoolSize(), | ||||
pht('Connection Pool With Lock Released')); | pht('Connection Pool With Lock Released')); | ||||
PhabricatorGlobalLock::clearConnectionPool(); | PhabricatorGlobalLock::clearConnectionPool(); | ||||
} | } | ||||
public function testConnectionPoolWithSpecificConnection() { | public function testConnectionPoolWithSpecificConnection() { | ||||
$conn = id(new HarbormasterScratchTable()) | $conn = PhabricatorGlobalLock::newConnection(); | ||||
->establishConnection('w'); | |||||
PhabricatorGlobalLock::clearConnectionPool(); | PhabricatorGlobalLock::clearConnectionPool(); | ||||
$this->assertEqual( | $this->assertEqual( | ||||
0, | 0, | ||||
PhabricatorGlobalLock::getConnectionPoolSize(), | PhabricatorGlobalLock::getConnectionPoolSize(), | ||||
pht('Clear Connection Pool')); | pht('Clear Connection Pool')); | ||||
Show All 31 Lines | $this->assertEqual( | ||||
false, | false, | ||||
$conn->isHoldingAnyLock(), | $conn->isHoldingAnyLock(), | ||||
pht('Specific Connection, No Lock')); | pht('Specific Connection, No Lock')); | ||||
PhabricatorGlobalLock::clearConnectionPool(); | PhabricatorGlobalLock::clearConnectionPool(); | ||||
} | } | ||||
public function testExternalConnectionMutationScope() { | public function testExternalConnectionMutationScope() { | ||||
$conn = id(new HarbormasterScratchTable()) | $conn = PhabricatorGlobalLock::newConnection(); | ||||
->establishConnection('w'); | |||||
$lock_name = $this->newLockName(); | $lock_name = $this->newLockName(); | ||||
$lock = PhabricatorGlobalLock::newLock($lock_name); | $lock = PhabricatorGlobalLock::newLock($lock_name); | ||||
$lock->lock(); | $lock->lock(); | ||||
$caught = null; | $caught = null; | ||||
try { | try { | ||||
$lock->setExternalConnection($conn); | $lock->setExternalConnection($conn); | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
$caught = $ex; | $caught = $ex; | ||||
} catch (Throwable $ex) { | } catch (Throwable $ex) { | ||||
$caught = $ex; | $caught = $ex; | ||||
} | } | ||||
$lock->unlock(); | $lock->unlock(); | ||||
$this->assertTrue( | $this->assertTrue( | ||||
($caught instanceof Exception), | ($caught instanceof Exception), | ||||
pht('Changing connection while locked is forbidden.')); | pht('Changing connection while locked is forbidden.')); | ||||
} | } | ||||
public function testMultipleLocks() { | public function testMultipleLocks() { | ||||
$conn = id(new HarbormasterScratchTable()) | $conn = PhabricatorGlobalLock::newConnection(); | ||||
->establishConnection('w'); | |||||
PhabricatorGlobalLock::clearConnectionPool(); | PhabricatorGlobalLock::clearConnectionPool(); | ||||
$lock_name_a = $this->newLockName(); | $lock_name_a = $this->newLockName(); | ||||
$lock_name_b = $this->newLockName(); | $lock_name_b = $this->newLockName(); | ||||
$lock_a = PhabricatorGlobalLock::newLock($lock_name_a); | $lock_a = PhabricatorGlobalLock::newLock($lock_name_a); | ||||
$lock_a->setExternalConnection($conn); | $lock_a->setExternalConnection($conn); | ||||
Show All 15 Lines | public function testMultipleLocks() { | ||||
// See T13627. The lock infrastructure must forbid this because it does | // See T13627. The lock infrastructure must forbid this because it does | ||||
// not work in versions of MySQL older than 5.7. | // not work in versions of MySQL older than 5.7. | ||||
$this->assertTrue( | $this->assertTrue( | ||||
($caught instanceof Exception), | ($caught instanceof Exception), | ||||
pht('Expect multiple locks on the same connection to fail.')); | pht('Expect multiple locks on the same connection to fail.')); | ||||
} | } | ||||
public function testPoolReleaseOnFailure() { | |||||
$conn = PhabricatorGlobalLock::newConnection(); | |||||
$lock_name = $this->newLockName(); | |||||
PhabricatorGlobalLock::clearConnectionPool(); | |||||
$this->assertEqual( | |||||
0, | |||||
PhabricatorGlobalLock::getConnectionPoolSize(), | |||||
pht('Clear Connection Pool')); | |||||
$lock = PhabricatorGlobalLock::newLock($lock_name); | |||||
// NOTE: We're cheating here, since there's a global registry of locks | |||||
// for the process that we have to bypass. In the real world, this lock | |||||
// would have to be held by some external process. To simplify this | |||||
// test case, just use a raw "GET_LOCK()" call to hold the lock. | |||||
$raw_conn = PhabricatorGlobalLock::newConnection(); | |||||
$raw_name = $lock->getName(); | |||||
$row = queryfx_one( | |||||
$raw_conn, | |||||
'SELECT GET_LOCK(%s, %f)', | |||||
$raw_name, | |||||
0); | |||||
$this->assertTrue((bool)head($row), pht('Establish Raw Lock')); | |||||
$this->assertEqual( | |||||
0, | |||||
PhabricatorGlobalLock::getConnectionPoolSize(), | |||||
pht('Connection Pool with Held Lock')); | |||||
// We expect this sequence to establish a new connection, fail to acquire | |||||
// the lock, then put the connection in the connection pool. After the | |||||
// first cycle, the connection should be reused. | |||||
for ($ii = 0; $ii < 3; $ii++) { | |||||
$this->tryHeldLock($lock_name); | |||||
$this->assertEqual( | |||||
1, | |||||
PhabricatorGlobalLock::getConnectionPoolSize(), | |||||
pht('Connection Pool After Lock Failure')); | |||||
} | |||||
PhabricatorGlobalLock::clearConnectionPool(); | |||||
// Now, do the same thing with an external connection. This connection | |||||
// should not be put into the pool! See T13627. | |||||
for ($ii = 0; $ii < 3; $ii++) { | |||||
$this->tryHeldLock($lock_name, $conn); | |||||
$this->assertEqual( | |||||
0, | |||||
PhabricatorGlobalLock::getConnectionPoolSize(), | |||||
pht('Connection Pool After External Lock Failure')); | |||||
} | |||||
} | |||||
private function newLockName() { | private function newLockName() { | ||||
return 'testlock-'.Filesystem::readRandomCharacters(16); | return 'testlock-'.Filesystem::readRandomCharacters(16); | ||||
} | } | ||||
private function tryHeldLock( | |||||
$lock_name, | |||||
AphrontDatabaseConnection $conn = null) { | |||||
$lock = PhabricatorGlobalLock::newLock($lock_name); | |||||
if ($conn) { | |||||
$lock->setExternalConnection($conn); | |||||
} | |||||
$caught = null; | |||||
try { | |||||
$lock->lock(0); | |||||
} catch (PhutilLockException $ex) { | |||||
$caught = $ex; | |||||
} | |||||
$this->assertTrue($caught instanceof PhutilLockException); | |||||
} | |||||
} | } |