mirror of
https://github.com/phpbb/phpbb.git
synced 2025-02-24 12:03:21 +01:00
Merge remote-tracking branch 'p/ticket/10103' into develop
* p/ticket/10103: [ticket/10103] New and improved wording. [ticket/10103] Assert with messages. [ticket/10103] assertLessThan/assertGreaterThan. [ticket/10103] Inline assignment is bad? [ticket/10103] $rv had too few characters. [ticket/10103] Correct flock class documentation. [ticket/10103] Try a longer sleep for travis. [ticket/10103] Convert the rest of the tree to flock class. [ticket/10103] Test for flock lock class, with concurrency no less. [ticket/10103] Use flock lock class in messenger. [ticket/10103] Factor out flock lock class.
This commit is contained in:
commit
2fdd039e52
16
phpBB/includes/cache/driver/file.php
vendored
16
phpBB/includes/cache/driver/file.php
vendored
@ -653,10 +653,11 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base
|
|||||||
|
|
||||||
$file = "{$this->cache_dir}$filename.$phpEx";
|
$file = "{$this->cache_dir}$filename.$phpEx";
|
||||||
|
|
||||||
|
$lock = new phpbb_lock_flock($file);
|
||||||
|
$lock->acquire();
|
||||||
|
|
||||||
if ($handle = @fopen($file, 'wb'))
|
if ($handle = @fopen($file, 'wb'))
|
||||||
{
|
{
|
||||||
@flock($handle, LOCK_EX);
|
|
||||||
|
|
||||||
// File header
|
// File header
|
||||||
fwrite($handle, '<' . '?php exit; ?' . '>');
|
fwrite($handle, '<' . '?php exit; ?' . '>');
|
||||||
|
|
||||||
@ -697,7 +698,6 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base
|
|||||||
fwrite($handle, $data);
|
fwrite($handle, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@flock($handle, LOCK_UN);
|
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
|
||||||
if (!function_exists('phpbb_chmod'))
|
if (!function_exists('phpbb_chmod'))
|
||||||
@ -708,10 +708,16 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base
|
|||||||
|
|
||||||
phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE);
|
phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE);
|
||||||
|
|
||||||
return true;
|
$return_value = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$return_value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
$lock->release();
|
||||||
|
|
||||||
|
return $return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -650,64 +650,6 @@ class queue
|
|||||||
$this->data[$object]['data'][] = $scope;
|
$this->data[$object]['data'][] = $scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains exclusive lock on queue cache file.
|
|
||||||
* Returns resource representing the lock
|
|
||||||
*/
|
|
||||||
function lock()
|
|
||||||
{
|
|
||||||
// For systems that can't have two processes opening
|
|
||||||
// one file for writing simultaneously
|
|
||||||
if (file_exists($this->cache_file . '.lock'))
|
|
||||||
{
|
|
||||||
$mode = 'rb';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$mode = 'wb';
|
|
||||||
}
|
|
||||||
|
|
||||||
$lock_fp = @fopen($this->cache_file . '.lock', $mode);
|
|
||||||
|
|
||||||
if ($mode == 'wb')
|
|
||||||
{
|
|
||||||
if (!$lock_fp)
|
|
||||||
{
|
|
||||||
// Two processes may attempt to create lock file at the same time.
|
|
||||||
// Have the losing process try opening the lock file again for reading
|
|
||||||
// on the assumption that the winning process created it
|
|
||||||
$mode = 'rb';
|
|
||||||
$lock_fp = @fopen($this->cache_file . '.lock', $mode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Only need to set mode when the lock file is written
|
|
||||||
@chmod($this->cache_file . '.lock', 0666);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($lock_fp)
|
|
||||||
{
|
|
||||||
@flock($lock_fp, LOCK_EX);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $lock_fp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases lock on queue cache file, using resource obtained from lock()
|
|
||||||
*/
|
|
||||||
function unlock($lock_fp)
|
|
||||||
{
|
|
||||||
// lock() will return null if opening lock file, and thus locking, failed.
|
|
||||||
// Accept null values here so that client code does not need to check them
|
|
||||||
if ($lock_fp)
|
|
||||||
{
|
|
||||||
@flock($lock_fp, LOCK_UN);
|
|
||||||
fclose($lock_fp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process queue
|
* Process queue
|
||||||
* Using lock file
|
* Using lock file
|
||||||
@ -716,13 +658,14 @@ class queue
|
|||||||
{
|
{
|
||||||
global $db, $config, $phpEx, $phpbb_root_path, $user;
|
global $db, $config, $phpEx, $phpbb_root_path, $user;
|
||||||
|
|
||||||
$lock_fp = $this->lock();
|
$lock = new phpbb_lock_flock($this->cache_file);
|
||||||
|
$lock->acquire();
|
||||||
|
|
||||||
set_config('last_queue_run', time(), true);
|
set_config('last_queue_run', time(), true);
|
||||||
|
|
||||||
if (!file_exists($this->cache_file) || filemtime($this->cache_file) > time() - $config['queue_interval'])
|
if (!file_exists($this->cache_file) || filemtime($this->cache_file) > time() - $config['queue_interval'])
|
||||||
{
|
{
|
||||||
$this->unlock($lock_fp);
|
$lock->release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,7 +732,7 @@ class queue
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$this->unlock($lock_fp);
|
$lock->release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -865,7 +808,7 @@ class queue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->unlock($lock_fp);
|
$lock->release();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -878,7 +821,8 @@ class queue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$lock_fp = $this->lock();
|
$lock = new phpbb_lock_flock($this->cache_file);
|
||||||
|
$lock->acquire();
|
||||||
|
|
||||||
if (file_exists($this->cache_file))
|
if (file_exists($this->cache_file))
|
||||||
{
|
{
|
||||||
@ -905,7 +849,7 @@ class queue
|
|||||||
phpbb_chmod($this->cache_file, CHMOD_READ | CHMOD_WRITE);
|
phpbb_chmod($this->cache_file, CHMOD_READ | CHMOD_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->unlock($lock_fp);
|
$lock->release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
133
phpBB/includes/lock/flock.php
Normal file
133
phpBB/includes/lock/flock.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @package phpBB3
|
||||||
|
* @copyright (c) 2012 phpBB Group
|
||||||
|
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
if (!defined('IN_PHPBB'))
|
||||||
|
{
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File locking class
|
||||||
|
* @package phpBB3
|
||||||
|
*/
|
||||||
|
class phpbb_lock_flock
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Path to the file to which access is controlled
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File pointer for the lock file
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $lock_fp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* You have to call acquire() to actually acquire the lock.
|
||||||
|
*
|
||||||
|
* @param string $path Path to the file to which access is controlled
|
||||||
|
*/
|
||||||
|
public function __construct($path)
|
||||||
|
{
|
||||||
|
$this->path = $path;
|
||||||
|
$this->lock_fp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to acquire the lock.
|
||||||
|
*
|
||||||
|
* If the lock is already held by another process, this call will block
|
||||||
|
* until the other process releases the lock. If a lock is acquired and
|
||||||
|
* is not released before script finishes but the process continues to
|
||||||
|
* live (apache/fastcgi) then subsequent processes trying to acquire
|
||||||
|
* the same lock will be blocked forever.
|
||||||
|
*
|
||||||
|
* If the lock is already held by the same process via another instance
|
||||||
|
* of this class, this call will block forever.
|
||||||
|
*
|
||||||
|
* If flock function is disabled in php or fails to work, lock
|
||||||
|
* acquisition will fail and false will be returned.
|
||||||
|
*
|
||||||
|
* @return bool true if lock was acquired
|
||||||
|
* false otherwise
|
||||||
|
*/
|
||||||
|
public function acquire()
|
||||||
|
{
|
||||||
|
if ($this->locked)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For systems that can't have two processes opening
|
||||||
|
// one file for writing simultaneously
|
||||||
|
if (file_exists($this->path . '.lock'))
|
||||||
|
{
|
||||||
|
$mode = 'rb';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$mode = 'wb';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lock_fp = @fopen($this->path . '.lock', $mode);
|
||||||
|
|
||||||
|
if ($mode == 'wb')
|
||||||
|
{
|
||||||
|
if (!$this->lock_fp)
|
||||||
|
{
|
||||||
|
// Two processes may attempt to create lock file at the same time.
|
||||||
|
// Have the losing process try opening the lock file again for reading
|
||||||
|
// on the assumption that the winning process created it
|
||||||
|
$mode = 'rb';
|
||||||
|
$this->lock_fp = @fopen($this->path . '.lock', $mode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Only need to set mode when the lock file is written
|
||||||
|
@chmod($this->path . '.lock', 0666);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lock_fp)
|
||||||
|
{
|
||||||
|
@flock($this->lock_fp, LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) $this->lock_fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the lock.
|
||||||
|
*
|
||||||
|
* The lock must have been previously obtained, that is, acquire() call
|
||||||
|
* was issued and returned true.
|
||||||
|
*
|
||||||
|
* Note: Attempting to release a lock that is already released,
|
||||||
|
* that is, calling release() multiple times, is harmless.
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function release()
|
||||||
|
{
|
||||||
|
if ($this->lock_fp)
|
||||||
|
{
|
||||||
|
@flock($this->lock_fp, LOCK_UN);
|
||||||
|
fclose($this->lock_fp);
|
||||||
|
$this->lock_fp = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,9 @@ class phpbb_template_compile
|
|||||||
*/
|
*/
|
||||||
public function compile_file_to_file($source_file, $compiled_file)
|
public function compile_file_to_file($source_file, $compiled_file)
|
||||||
{
|
{
|
||||||
|
$lock = new phpbb_lock_flock($compiled_file);
|
||||||
|
$lock->acquire();
|
||||||
|
|
||||||
$source_handle = @fopen($source_file, 'rb');
|
$source_handle = @fopen($source_file, 'rb');
|
||||||
$destination_handle = @fopen($compiled_file, 'wb');
|
$destination_handle = @fopen($compiled_file, 'wb');
|
||||||
|
|
||||||
@ -66,16 +69,15 @@ class phpbb_template_compile
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@flock($destination_handle, LOCK_EX);
|
|
||||||
|
|
||||||
$this->compile_stream_to_stream($source_handle, $destination_handle);
|
$this->compile_stream_to_stream($source_handle, $destination_handle);
|
||||||
|
|
||||||
@fclose($source_handle);
|
@fclose($source_handle);
|
||||||
@flock($destination_handle, LOCK_UN);
|
|
||||||
@fclose($destination_handle);
|
@fclose($destination_handle);
|
||||||
|
|
||||||
phpbb_chmod($compiled_file, CHMOD_READ | CHMOD_WRITE);
|
phpbb_chmod($compiled_file, CHMOD_READ | CHMOD_WRITE);
|
||||||
|
|
||||||
|
$lock->release();
|
||||||
|
|
||||||
clearstatcache();
|
clearstatcache();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
109
tests/lock/flock_test.php
Normal file
109
tests/lock/flock_test.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @package testing
|
||||||
|
* @copyright (c) 2012 phpBB Group
|
||||||
|
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class phpbb_lock_flock_test extends phpbb_test_case
|
||||||
|
{
|
||||||
|
public function test_lock()
|
||||||
|
{
|
||||||
|
$path = __DIR__ . '/../tmp/precious';
|
||||||
|
|
||||||
|
$lock = new phpbb_lock_flock($path);
|
||||||
|
$ok = $lock->acquire();
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
$lock->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_consecutive_locking()
|
||||||
|
{
|
||||||
|
$path = __DIR__ . '/../tmp/precious';
|
||||||
|
|
||||||
|
$lock = new phpbb_lock_flock($path);
|
||||||
|
$ok = $lock->acquire();
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
$lock->release();
|
||||||
|
|
||||||
|
$ok = $lock->acquire();
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
$lock->release();
|
||||||
|
|
||||||
|
$ok = $lock->acquire();
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
$lock->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This hangs the process.
|
||||||
|
public function test_concurrent_locking_fail()
|
||||||
|
{
|
||||||
|
$path = __DIR__ . '/../tmp/precious';
|
||||||
|
|
||||||
|
$lock1 = new phpbb_lock_flock($path);
|
||||||
|
$ok = $lock1->acquire();
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
|
||||||
|
$lock2 = new phpbb_lock_flock($path);
|
||||||
|
$ok = $lock2->acquire();
|
||||||
|
$this->assertFalse($ok);
|
||||||
|
|
||||||
|
$lock->release();
|
||||||
|
$ok = $lock2->acquire();
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function test_concurrent_locking()
|
||||||
|
{
|
||||||
|
if (!function_exists('pcntl_fork'))
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('pcntl extension and pcntl_fork are required for this test');
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = __DIR__ . '/../tmp/precious';
|
||||||
|
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
if ($pid)
|
||||||
|
{
|
||||||
|
// parent
|
||||||
|
// wait 0.5 s, acquire the lock, note how long it took
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
$lock = new phpbb_lock_flock($path);
|
||||||
|
$start = time();
|
||||||
|
$ok = $lock->acquire();
|
||||||
|
$delta = time() - $start;
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
$this->assertGreaterThan(0.5, $delta, 'First lock acquired too soon');
|
||||||
|
|
||||||
|
$lock->release();
|
||||||
|
|
||||||
|
// acquire again, this should be instantaneous
|
||||||
|
$start = time();
|
||||||
|
$ok = $lock->acquire();
|
||||||
|
$delta = time() - $start;
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
$this->assertLessThan(0.1, $delta, 'Second lock not acquired instantaneously');
|
||||||
|
|
||||||
|
// reap the child
|
||||||
|
$status = null;
|
||||||
|
pcntl_waitpid($pid, $status);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// child
|
||||||
|
// immediately acquire the lock and sleep for 2 s
|
||||||
|
$lock = new phpbb_lock_flock($path);
|
||||||
|
$ok = $lock->acquire();
|
||||||
|
$this->assertTrue($ok);
|
||||||
|
sleep(2);
|
||||||
|
$lock->release();
|
||||||
|
|
||||||
|
// and go away silently
|
||||||
|
pcntl_exec('/usr/bin/env', array('true'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user