/**
* 进程锁管理, DISCUZ 的进程管理, 进程管理, 在 DX 的计划任务里面使用!
* PHP 本身是触发型语言,需要触发才能执行某个操作,而不像 linux 那样可以到指定时间执行指定的计划任务。
* 所以上面提到的计划任务,实际上都是需要我们触发才能执行的。
*
* 执行的过程采用了加锁的方式,防止在任务被同时执行!
*
* 例子:
* 在实现数据调用模块的自动更新的时候调用了函数 block_updatecache (位于 function_block.php), 有这样的判断
* 如果 discuz_process::islocked('block_update_cache', 5) 为 true 的话 直接 return false; 中断脚本执行!
* 如果 discuz_process::islocked('block_update_cache', 5) 为 false 的话函数才允许继续执行, 并且在函数
* 的最后面调用 discuz_process::unlock('block_update_cache'); 对进程的解锁!
*
*/
class discuz_process
{
/**
* 检查进程是否已经上锁, 上锁的话, 当然这个进程就不能继续执行了
* @param <string> $process 进程的名称
* @param <num> $ttl 锁的过期时间, 以秒为单位 ! 如果进程没有被锁的话, 系统会自动把
* 这个进程加入到进程锁里面, 过期的参数会是 'expiry' => time() + $ttl ,
* 默认为 0 , 就是不把此进程进行加锁操作, 不进行时间限制, 必须调用 unlock 才能继续下面的进程!
* @return <bool> true 此动作已经被加锁, false 则未加锁
*/
function islocked($process, $ttl = 0) {
$ttl = $ttl < 1 ? 600 : intval($ttl);
if(discuz_process::_status('get', $process)) {
return true;
} else {
return discuz_process::_find($process, $ttl);
}
}
/**
* 删除某个进程或过期进程
* @param <string> $name 进程名
*/
function unlock($process) {
discuz_process::_status('rm', $process);
discuz_process::_cmd('rm', $process);
}
/**
* 用来优化的, 当一次 DX 请求里面需要执行某个进程多次, 设置一个 "static $plist"
* 是一个不错的选择! 不用每一次的去检查内存缓存或者数据库! 需要注意的是在每
* 一个进程执行完之后需要执行下 unlock 操作, 否则会导致同一个请求的剩余进程无法继续!
*
* @param <string> $action 为命令名称, 可以为 set, get, rm, clear
* @param <string> $process 进程的名称
*/
function _status($action, $process) {
static $plist = array();
switch ($action) {
case 'set' : $plist[$process] = true; break;
case 'get' : return !empty($plist[$process]); break;
case 'rm' : $plist[$process] = null; break;
case 'clear' : $plist = array(); break;
}
return true;
}
/**
* 检查进程是否存在, 内部函数
* @param <string> $process 进程的名称
* @param <num> $ttl 锁的过期时间, 以秒为单位 ! 如果进程没有被锁的话, 系统会自动把
* 这个进程加入到进程锁里面, 过期的参数会是 'expiry' => time() + $ttl ,
* 默认为 0 , 就是不把此进程进行加锁操作, 不进行时间限制, 必须调用 unlock 才能继续下面的进程!
* @return <bool> true 此动作已经被加锁, false 则未加锁
*/
function _find($name, $ttl) {
if(!discuz_process::_cmd('get', $name)) { /** 如果不存在获取过期了, 返回的为 FALSE */
discuz_process::_cmd('set', $name, $ttl); // 既然不存在就加上去呗
$ret = false;
} else {
$ret = true;
}
discuz_process::_status('set', $name); // 设置 static $plist[$process] = true;
return $ret;
}
/**
* 执行命令, 内部函数, _find 的帮助函数
* @param <string> $cmd 为命令名称, 可以为 set, get, rm
* @param <string> $process 进程的名称
* @param <num> $ttl 锁的过期时间, 以秒为单位 ! 如果进程没有被锁的话, 系统会自动把
* 这个进程加入到进程锁里面, 过期的参数会是 'expiry' => time() + $ttl ,
* 默认为 0 , 就是不把此进程进行加锁操作, 不进行时间限制, 必须调用 unlock 才能继续下面的进程!
* @return <bool> true 此动作已经被加锁, false 则未加锁
*/
function _cmd($cmd, $name, $ttl = 0) {
static $allowmem;
if($allowmem === null) {
$allowmem = memory('check') == 'memcache';
}
if($allowmem) {
return discuz_process::_process_cmd_memory($cmd, $name, $ttl);
} else {
return discuz_process::_process_cmd_db($cmd, $name, $ttl);
}
}
/**
* 从内存里面读进程信息
* @param <string> $cmd 为命令名称, 可以为 set, get, rm
* @param <string> $process 进程的名称
* @param <num> $ttl 锁的过期时间, 以秒为单位 !
*/
function _process_cmd_memory($cmd, $name, $ttl = 0) {
return memory($cmd, 'process_lock_'.$name, time(), $ttl);
}
/**
* 从数据库里面读的进程信息
* @param <string> $cmd 为命令名称, 可以为 set, get, rm
* @param <string> $process 进程的名称
* @param <num> $ttl 锁的过期时间, 以秒为单位 !
*/
function _process_cmd_db($cmd, $name, $ttl = 0) {
$ret = '';
switch ($cmd) {
case 'set':
$ret = DB::insert('common_process', array('processid' => $name, 'expiry' => time() + $ttl), false, true);
break;
case 'get':
$ret = DB::fetch_first("SELECT * FROM ".DB::table('common_process')." WHERE processid='$name'");
if(empty($ret) || $ret['expiry'] < time()) { // 如果为空或者是判断是否过期
$ret = false;
} else {
$ret = true;
}
break;
case 'rm':
$ret = DB::delete('common_process', "processid='$name' OR expiry<".time());
break;
}
return $ret;
}
}