1
0
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:
Andreas Fischer 2012-12-04 20:26:43 +01:00
commit 2fdd039e52
5 changed files with 266 additions and 72 deletions

View File

@ -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;
} }
/** /**

View File

@ -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();
} }
} }

View 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;
}
}
}

View File

@ -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
View 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'));
}
}
}