mirror of
https://gitlab.com/hashborgir/d2tools.git
synced 2025-04-27 14:55:38 +00:00
315 lines
8.6 KiB
PHP
315 lines
8.6 KiB
PHP
<?php
|
|
|
|
namespace RedUNIT\Base;
|
|
|
|
use RedUNIT\Base as Base;
|
|
use RedBeanPHP\Facade as R;
|
|
use RedBeanPHP\RedException as RedException;
|
|
use RedBeanPHP\OODBBean as OODBBean;
|
|
use RedBeanPHP\QueryWriter\AQueryWriter;
|
|
|
|
/**
|
|
* Concurrency
|
|
*
|
|
* Tests whether we can lock beans.
|
|
*
|
|
* @file RedUNIT/Base/Concurrency.php
|
|
* @desc Tests concurrency scenarios
|
|
* @author Gabor de Mooij and the RedBeanPHP Community
|
|
* @license New BSD/GPLv2
|
|
*
|
|
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
|
|
* This source file is subject to the New BSD/GPLv2 License that is bundled
|
|
* with this source code in the file license.txt.
|
|
*/
|
|
class Concurrency extends Base
|
|
{
|
|
/**
|
|
* Returns the target drivers for this test.
|
|
* This test only works with Postgres and MySQL/MariaDB.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getTargetDrivers()
|
|
{
|
|
return array( 'pgsql','mysql' );
|
|
}
|
|
|
|
/**
|
|
* Prepares the database connection.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function prepare()
|
|
{
|
|
try{
|
|
R::close();
|
|
} catch( \Exception $e ) {}
|
|
}
|
|
|
|
/**
|
|
* This test has to be run manually.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function testLockException()
|
|
{
|
|
R::nuke();
|
|
$lock = R::dispense('lock');
|
|
$lock->value = 123;
|
|
R::store($lock);
|
|
$c = pcntl_fork();
|
|
if ($c == -1) exit(1);
|
|
if (!$c) {
|
|
R::selectDatabase($this->currentlyActiveDriverID . 'c2');
|
|
R::freeze(TRUE);
|
|
try { R::exec('SET SESSION innodb_lock_wait_timeout=5');}catch( \Exception $e ){}
|
|
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
|
|
R::begin();
|
|
$lock = R::loadForUpdate('lock', $lock->id);
|
|
$lock->value = 4;
|
|
sleep(10);
|
|
R::store($lock);
|
|
exit(0);
|
|
} else {
|
|
R::selectDatabase($this->currentlyActiveDriverID . 'c2');
|
|
sleep(1);
|
|
R::freeze(TRUE);
|
|
try{ R::exec('SET SESSION innodb_lock_wait_timeout=5');}catch( \Exception $e ){}
|
|
try{R::exec("SET lock_timeout = '1s';");}catch( \Exception $e ){}
|
|
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
|
|
R::begin();
|
|
$exception = NULL;
|
|
try {
|
|
$lock = R::loadForUpdate('lock', $lock->id);
|
|
} catch( \Exception $e ) {
|
|
$exception = $e;
|
|
}
|
|
if ( !$exception ) fail();
|
|
pass();
|
|
$details = $exception->getDriverDetails();
|
|
asrt( ($details[1]===1205 || $details[0]==='55P03'), TRUE );
|
|
var_dump($lock);
|
|
}
|
|
try { R::exec('SET autocommit = 1'); }catch( \Exception $e ){}
|
|
pcntl_wait($status);
|
|
try { R::exec('SET SESSION innodb_lock_wait_timeout=50');}catch( \Exception $e ){}
|
|
try{R::exec("SET lock_timeout = '50s';");}catch( \Exception $e ){}
|
|
}
|
|
|
|
/**
|
|
* Tests basic locking scenarios using fork().
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testConcurrency()
|
|
{
|
|
$c = pcntl_fork();
|
|
if ($c == -1) exit(1);
|
|
if (!$c) {
|
|
R::selectDatabase($this->currentlyActiveDriverID . 'c');
|
|
try{ R::exec('SET SESSION innodb_lock_wait_timeout=51');}catch( \Exception $e ){}
|
|
try{R::exec("SET lock_timeout = '51s';");}catch( \Exception $e ){}
|
|
R::exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
|
sleep(2);
|
|
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
|
|
R::freeze(TRUE);
|
|
R::begin();
|
|
echo "CHILD: SUBTRACTING 2 START\n";
|
|
$i = R::loadForUpdate('inventory', 1);
|
|
$i->apples -= 2;
|
|
sleep(4);
|
|
R::store($i);
|
|
R::commit();
|
|
echo "CHILD: SUBTRACTING 2 DONE\n";
|
|
echo (R::load('inventory', 1));
|
|
echo "\n";
|
|
exit(0);
|
|
} else {
|
|
R::selectDatabase($this->currentlyActiveDriverID . 'c');
|
|
try{ R::exec('SET SESSION innodb_lock_wait_timeout=51');}catch( \Exception $e ){}
|
|
try{R::exec("SET lock_timeout = '51s';");}catch( \Exception $e ){}
|
|
echo "PARENT: PREP START\n";
|
|
R::nuke();
|
|
$i = R::dispense('inventory');
|
|
$i->apples = 10;
|
|
R::store($i);
|
|
R::exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
|
echo "PARENT: PREP DONE\n";
|
|
sleep(3);
|
|
echo "PARENT: ADDING 5 START\n";
|
|
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
|
|
R::freeze( TRUE );
|
|
R::begin();
|
|
$i = R::findForUpdate('inventory', ' id = ? ', array(1));
|
|
$i = reset( $i );
|
|
print_r($i);
|
|
$i->apples += 5;
|
|
R::store($i);
|
|
R::commit();
|
|
echo "PARENT ADDING 5 DONE\n";
|
|
$i = R::getAll('select * from inventory where id = 1');
|
|
print_r($i);
|
|
asrt((int)$i[0]['apples'], 13);
|
|
R::freeze( FALSE );
|
|
try { R::exec('SET autocommit = 1'); }catch( \Exception $e ){}
|
|
pcntl_wait($status);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test whether we can use setSQLSnippet with find() and batch().
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testBatchAndFind()
|
|
{
|
|
/* baseline */
|
|
R::nuke();
|
|
$beans = R::dispenseAll('bean*10');
|
|
R::storeAll($beans[0]);
|
|
$ids = array();
|
|
foreach($beans[0] as $bean) $ids[] = $bean->id;
|
|
R::debug( TRUE, 1 );
|
|
$logs = R::getDatabaseAdapter()
|
|
->getDatabase()
|
|
->getLogger();
|
|
$beans = R::batch( 'bean', $ids );
|
|
asrt( count( $beans ), 10 );
|
|
$entries = $logs->grep('for update');
|
|
asrt( count( $entries ), 0 );
|
|
$logs->clear();
|
|
|
|
/* findOneForUpdate */
|
|
R::nuke();
|
|
$beans = R::dispenseAll('bean*10');
|
|
R::storeAll($beans[0]);
|
|
$ids = array();
|
|
foreach($beans[0] as $bean) $ids[] = $bean->id;
|
|
R::debug( TRUE, 1 );
|
|
$logs = R::getDatabaseAdapter()
|
|
->getDatabase()
|
|
->getLogger();
|
|
$beans = R::findOneForUpdate( 'bean' );
|
|
asrt( count( $beans ), 1 );
|
|
$entries = $logs->grep('for update');
|
|
asrt( count( $entries ), 0 );
|
|
$logs->clear();
|
|
|
|
/* findForUpdate */
|
|
R::nuke();
|
|
$beans = R::dispenseAll('bean*10');
|
|
R::storeAll($beans[0]);
|
|
$ids = array();
|
|
foreach($beans[0] as $bean) $ids[] = $bean->id;
|
|
R::debug( TRUE, 1 );
|
|
$logs = R::getDatabaseAdapter()
|
|
->getDatabase()
|
|
->getLogger();
|
|
$beans = R::findForUpdate( 'bean' );
|
|
asrt( count( $beans ), 10 );
|
|
$entries = $logs->grep('for update');
|
|
asrt( count( $entries ), 0 );
|
|
$logs->clear();
|
|
|
|
/* batch + snippet */
|
|
R::nuke();
|
|
$beans = R::dispenseAll('bean*10');
|
|
R::storeAll($beans[0]);
|
|
$ids = array();
|
|
R::getWriter()->setSQLSelectSnippet('for update');
|
|
foreach($beans[0] as $bean) $ids[] = $bean->id;
|
|
R::debug( TRUE, 1 );
|
|
$logs = R::getDatabaseAdapter()
|
|
->getDatabase()
|
|
->getLogger();
|
|
$beans = R::batch( 'bean', $ids );
|
|
asrt( count( $beans ), 10 );
|
|
$entries = $logs->grep('for update');
|
|
asrt( count( $entries ), 1 );
|
|
print_r( $entries );
|
|
$logs->clear();
|
|
|
|
/* baseline */
|
|
R::nuke();
|
|
$beans = R::dispenseAll('bean*10');
|
|
R::storeAll($beans[0]);
|
|
$ids = array();
|
|
foreach($beans[0] as $bean) $ids[] = $bean->id;
|
|
R::debug( TRUE, 1 );
|
|
$logs = R::getDatabaseAdapter()
|
|
->getDatabase()
|
|
->getLogger();
|
|
$beans = R::batch( 'bean', $ids );
|
|
asrt( count( $beans ), 10 );
|
|
$entries = $logs->grep('for update');
|
|
asrt( count( $entries ), 0 );
|
|
$logs->clear();
|
|
|
|
/* find + snippet */
|
|
R::nuke();
|
|
$beans = R::dispenseAll('bean*10');
|
|
R::storeAll($beans[0]);
|
|
$ids = array();
|
|
R::getWriter()->setSQLSelectSnippet('for update');
|
|
foreach($beans[0] as $bean) $ids[] = $bean->id;
|
|
R::debug( TRUE, 1 );
|
|
$logs = R::getDatabaseAdapter()
|
|
->getDatabase()
|
|
->getLogger();
|
|
$beans = R::find( 'bean' );
|
|
asrt( count( $beans ), 10 );
|
|
$entries = $logs->grep('for update');
|
|
asrt( count( $entries ), 1 );
|
|
print_r( $entries );
|
|
$logs->clear();
|
|
|
|
/* baseline */
|
|
R::nuke();
|
|
$beans = R::dispenseAll('bean*10');
|
|
R::storeAll($beans[0]);
|
|
$ids = array();
|
|
foreach($beans[0] as $bean) $ids[] = $bean->id;
|
|
R::debug( TRUE, 1 );
|
|
$logs = R::getDatabaseAdapter()
|
|
->getDatabase()
|
|
->getLogger();
|
|
$beans = R::batch( 'bean', $ids );
|
|
asrt( count( $beans ), 10 );
|
|
$entries = $logs->grep('for update');
|
|
asrt( count( $entries ), 0 );
|
|
$logs->clear();
|
|
|
|
R::debug( FALSE );
|
|
}
|
|
|
|
/**
|
|
* loadForUpdate/findForUpdate should be applied even if caching is on.
|
|
* Caching may not interfere with locking beans.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testLockAndCache()
|
|
{
|
|
R::nuke();
|
|
$bean = R::dispense('lock');
|
|
$bean->title = 'lock';
|
|
$id = R::store( $bean );
|
|
R::getWriter()->setUseCache( TRUE );
|
|
$lock = R::loadForUpdate( 'lock', $id );
|
|
R::debug( TRUE, 1 );
|
|
$lock = R::loadForUpdate( 'lock', $id );
|
|
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
|
|
asrt( count($logs), 1 ); //if no cache clear, then would have been 2
|
|
R::debug( FALSE );
|
|
$lock = R::findForUpdate( 'lock', 'id = ?', array( $id ) );
|
|
R::debug( TRUE, 1 );
|
|
$lock = R::findForUpdate( 'lock', 'id = ?', array( $id ) );
|
|
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
|
|
asrt( count($logs), 1 ); //if no cache clear, then would have been 2
|
|
R::getWriter()->setUseCache( FALSE );
|
|
R::debug( FALSE );
|
|
R::nuke();
|
|
}
|
|
}
|