From b8402c9563682b1e547e501378ff8b50292cf3eb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 2 May 2010 13:04:25 +0200 Subject: [PATCH] Added Gearman Lock Test and Worker, verified lockings indeed works on MySQL, PostgreSQL and Oracle --- .../Functional/Locking/GearmanLockTest.php | 177 ++++++++++++++++++ .../Functional/Locking/LockAgentWorker.php | 111 +++++++++++ tests/README.markdown | 25 +++ 3 files changed, 313 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Locking/GearmanLockTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php create mode 100644 tests/README.markdown diff --git a/tests/Doctrine/Tests/ORM/Functional/Locking/GearmanLockTest.php b/tests/Doctrine/Tests/ORM/Functional/Locking/GearmanLockTest.php new file mode 100644 index 000000000..ced552422 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Locking/GearmanLockTest.php @@ -0,0 +1,177 @@ +markTestSkipped('pecl/gearman is required for this test to run.'); + } + + $this->useModelSet('cms'); + parent::setUp(); + $this->tasks = array(); + + $this->gearman = new \GearmanClient(); + $this->gearman->addServer(); + $this->gearman->setCompleteCallback(array($this, "gearmanTaskCompleted")); + + $article = new CmsArticle(); + $article->text = "my article"; + $article->topic = "Hello"; + + $this->_em->persist($article); + $this->_em->flush(); + + $this->articleId = $article->id; + } + + public function gearmanTaskCompleted($task) + { + $this->maxRunTime = max($this->maxRunTime, $task->data()); + } + + public function testFindWithLock() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + + $this->assertLockWorked(); + } + + public function testFindWithWriteThenReadLock() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ); + + $this->assertLockWorked(); + } + + public function testFindWithReadThenWriteLock() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ); + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + + $this->assertLockWorked(); + } + + public function testFindWithOneLock() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::NONE); + + $this->assertLockDoesNotBlock(); + } + + public function testDqlWithLock() + { + $this->asyncDqlWithLock('SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a', array(), LockMode::PESSIMISTIC_WRITE); + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + + $this->assertLockWorked(); + } + + public function testLock() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + $this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + + $this->assertLockWorked(); + } + + public function testLock2() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + $this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ); + + $this->assertLockWorked(); + } + + public function testLock3() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_READ); + $this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + + $this->assertLockWorked(); + } + + public function testLock4() + { + $this->asyncFindWithLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::NONE); + $this->asyncLock('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId, LockMode::PESSIMISTIC_WRITE); + + $this->assertLockDoesNotBlock(); + } + + protected function assertLockDoesNotBlock() + { + $this->assertLockWorked($onlyForSeconds = 1); + } + + protected function assertLockWorked($forTime = 2, $notLongerThan = null) + { + if ($notLongerThan === null) { + $notLongerThan = $forTime + 1; + } + + $this->gearman->runTasks(); + + $this->assertTrue($this->maxRunTime > $forTime, + "Because of locking this tests should have run at least " . $forTime . " seconds, ". + "but only did for " . $this->maxRunTime . " seconds."); + $this->assertTrue($this->maxRunTime < $notLongerThan, + "The longest task should not run longer than " . $notLongerThan . " seconds, ". + "but did for " . $this->maxRunTime . " seconds." + ); + } + + protected function asyncFindWithLock($entityName, $entityId, $lockMode) + { + $this->startGearmanJob('findWithLock', array( + 'entityName' => $entityName, + 'entityId' => $entityId, + 'lockMode' => $lockMode, + )); + } + + protected function asyncDqlWithLock($dql, $params, $lockMode) + { + $this->startGearmanJob('dqlWithLock', array( + 'dql' => $dql, + 'dqlParams' => $params, + 'lockMode' => $lockMode, + )); + } + + protected function asyncLock($entityName, $entityId, $lockMode) + { + $this->startGearmanJob('lock', array( + 'entityName' => $entityName, + 'entityId' => $entityId, + 'lockMode' => $lockMode, + )); + } + + protected function startGearmanJob($fn, $fixture) + { + $this->gearman->addTask($fn, serialize(array( + 'conn' => $this->_em->getConnection()->getParams(), + 'fixture' => $fixture + ))); + + $this->assertEquals(GEARMAN_SUCCESS, $this->gearman->returnCode()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php b/tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php new file mode 100644 index 000000000..6b1e6d488 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php @@ -0,0 +1,111 @@ +addServer(); + $worker->addFunction("findWithLock", array($lockAgent, "findWithLock")); + $worker->addFunction("dqlWithLock", array($lockAgent, "dqlWithLock")); + $worker->addFunction('lock', array($lockAgent, 'lock')); + + while($worker->work()) { + if ($worker->returnCode() != GEARMAN_SUCCESS) { + echo "return_code: " . $worker->returnCode() . "\n"; + break; + } + } + } + + protected function process($job, \Closure $do) + { + $fixture = $this->processWorkload($job); + + $s = microtime(true); + $this->em->beginTransaction(); + $do($fixture, $this->em); + + sleep(1); + $this->em->rollback(); + $this->em->clear(); + $this->em->close(); + $this->em->getConnection()->close(); + + return (microtime(true) - $s); + } + + public function findWithLock($job) + { + return $this->process($job, function($fixture, $em) { + $entity = $em->find($fixture['entityName'], $fixture['entityId'], $fixture['lockMode']); + }); + } + + public function dqlWithLock($job) + { + return $this->process($job, function($fixture, $em) { + /* @var $query Doctrine\ORM\Query */ + $query = $em->createQuery($fixture['dql']); + $query->setLockMode($fixture['lockMode']); + $query->setParameters($fixture['dqlParams']); + $result = $query->getResult(); + }); + } + + public function lock($job) + { + return $this->process($job, function($fixture, $em) { + $entity = $em->find($fixture['entityName'], $fixture['entityId']); + $em->lock($entity, $fixture['lockMode']); + }); + } + + protected function processWorkload($job) + { + echo "Received job: " . $job->handle() . " for function " . $job->functionName() . "\n"; + + $workload = $job->workload(); + $workload = unserialize($workload); + + if (!isset($workload['conn']) || !is_array($workload['conn'])) { + throw new \InvalidArgumentException("Missing Database parameters"); + } + + $this->em = $this->createEntityManager($workload['conn']); + + if (!isset($workload['fixture'])) { + throw new \InvalidArgumentException("Missing Fixture parameters"); + } + return $workload['fixture']; + } + + protected function createEntityManager($conn) + { + $config = new \Doctrine\ORM\Configuration(); + $config->setProxyDir(__DIR__ . '/../../../Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + $config->setAutoGenerateProxyClasses(true); + + $annotDriver = $config->newDefaultAnnotationDriver(array(__DIR__ . '/../../../Models/')); + $config->setMetadataDriverImpl($annotDriver); + + $cache = new \Doctrine\Common\Cache\ArrayCache(); + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + $em = \Doctrine\ORM\EntityManager::create($conn, $config); + + return $em; + } +} + +LockAgentWorker::run(); \ No newline at end of file diff --git a/tests/README.markdown b/tests/README.markdown new file mode 100644 index 000000000..c1027aced --- /dev/null +++ b/tests/README.markdown @@ -0,0 +1,25 @@ +# Running the Doctrine 2 Testsuite + +## Setting up a PHPUnit Configuration XML + +.. + +## Testing Lock-Support + +The Lock support in Doctrine 2 is tested using Gearman, which allows to run concurrent tasks in parallel. +Install Gearman with PHP as follows: + +1. Go to http://www.gearman.org and download the latest Gearman Server +2. Compile it and then call ldconfig +3. Start it up "gearmand -vvvv" +4. Install pecl/gearman by calling "gearman-beta" + +You can then go into tests/ and start up two workers: + + php Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php + +Then run the locking test-suite: + + phpunit --configuration Doctrine/Tests/ORM/Functional/Locking/GearmanLockTest.php + +This can run considerable time, because it is using sleep() to test for the timing ranges of locks. \ No newline at end of file