Я расширил понятие с zerkms , чтобы создать функцию, которую можно вызывать с начала cron.
Используя Cronlocker, вы указываете имя замка, а затем имяфункции обратного вызова, которая вызывается, если cron выключен.При желании вы можете указать массив параметров для передачи в функцию обратного вызова.Есть также дополнительная функция обратного вызова, если вам нужно сделать что-то другое, если блокировка включена.
В некоторых случаях я получил несколько исключений и хотел иметь возможность их перехватывать, и я добавил функцию для обработки фатальных ошибок.исключения, которые следует добавить.Я хотел иметь возможность получить доступ к файлу из браузера и обойти cronlock, так что он встроен.
Я обнаружил, что когда я использовал это много раз, были случаи, когда я хотел заблокировать запуск других крон, пока этоcron запущен, поэтому я добавил необязательный массив lockblocks, которые являются другими именами блокировок для блокировки.
Затем были случаи, когда я хотел, чтобы этот cron запускался после завершения других cron, поэтому есть необязательный массивожидания блокировки, которые являются другими именами блокировок, чтобы ждать, пока ни одно из них не будет запущено.
простой пример:
Cronlocker::CronLock('cron1', 'RunThis');
function RunThis() {
echo('I ran!');
}
параметры обратного вызова и функции сбоя:
Cronlocker::CronLock('cron2', 'RunThat', ['ran'], 'ImLocked');
function RunThat($x) {
echo('I also ran! ' . $x);
}
function ImLocked($x) {
echo('I am locked :-( ' . $x);
}
блокировкаи ожидание:
Cronlocker::CronLock('cron3', 'RunAgain', null, null, ['cron1'], ['cron2']);
function RunAgain() {
echo('I ran.<br />');
echo('I block cron1 while I am running.<br />')
echo('I wait for cron2 to finish if it is running.');
}
класс:
class Cronlocker {
private static $LockFile = null;
private static $LockFileBlocks = [];
private static $LockFileWait = null;
private static function GetLockfileName($lockname) {
return "/tmp/lock-" . $lockname . ".txt";
}
/**
* Locks a PHP script from being executed more than once at a time
* @param string $lockname Use a unique lock name for each lock that needs to be applied.
* @param string $callback The name of the function to call if the lock is OFF
* @param array $callbackParams Optional array of parameters to apply to the callback function when called
* @param string $callbackFail Optional name of the function to call if the lock is ON
* @param string[] $lockblocks Optional array of locknames for other crons to also block while this cron is running
* @param string[] $lockwaits Optional array of locknames for other crons to wait until they finish running before this cron will run
* @see /5317950/php-predotvraschenie-stolknovenii-v-cron-bezopasnaya-blokirovka-failov
*/
public static function CronLock($lockname, $callback, $callbackParams = null, $callbackFail = null, $lockblocks = [], $lockwaits = []) {
// check all the crons we are waiting for to finish running
if (!empty($lockwaits)) {
$waitingOnCron = true;
while ($waitingOnCron) {
$waitingOnCron = false;
foreach ($lockwaits as $lockwait) {
self::$LockFileWait = null;
$tempfile = self::GetLockfileName($lockwait);
try {
self::$LockFileWait = fopen($tempfile, "w+");
} catch (Exception $e) {
//ignore error
}
if (flock(self::$LockFileWait, LOCK_EX | LOCK_NB)) { // do an exclusive lock
// cron we're waiting on isn't running
flock(self::$LockFileWait, LOCK_UN); // release the lock
} else {
// we're wating on a cron
$waitingOnCron = true;
}
if (is_resource(self::$LockFileWait))
fclose(self::$LockFileWait);
if ($waitingOnCron) break; // no need to check any more
}
if ($waitingOnCron) sleep(15); // wait a few seconds
}
}
// block any additional crons from starting
if (!empty($lockblocks)) {
self::$LockFileBlocks = [];
foreach ($lockblocks as $lockblock) {
$tempfile = self::GetLockfileName($lockblock);
try {
$block = fopen($tempfile, "w+");
} catch (Exception $e) {
//ignore error
}
if (flock($block, LOCK_EX | LOCK_NB)) { // do an exclusive lock
// lock made
self::$LockFileBlocks[] = $block;
} else {
// couldn't lock it, we ignore and move on
}
}
}
// set the cronlock
self::$LockFile = null;
$tempfile = self::GetLockfileName($lockname);
$return = null;
try {
if (file_exists($tempfile) && !is_writable($tempfile)) {
//assume we're hitting this from a browser and execute it regardless of the cronlock
if (empty($callbackParams))
$return = $callback();
else
$return = call_user_func_array($callback, $callbackParams);
} else {
self::$LockFile = fopen($tempfile, "w+");
}
} catch (Exception $e) {
//ignore error
}
if (!empty(self::$LockFile)) {
if (flock(self::$LockFile, LOCK_EX | LOCK_NB)) { // do an exclusive lock
// do the work
if (empty($callbackParams))
$return = $callback();
else
$return = call_user_func_array($callback, $callbackParams);
flock(self::$LockFile, LOCK_UN); // release the lock
} else {
// call the failed function
if (!empty($callbackFail)) {
if (empty($callbackParams))
$return = $callbackFail();
else
$return = call_user_func_array($callbackFail, $callbackParams);
}
}
if (is_resource(self::$LockFile))
fclose(self::$LockFile);
}
// remove any lockblocks
if (!empty($lockblocks)) {
foreach (self::$LockFileBlocks as $LockFileBlock) {
flock($LockFileBlock, LOCK_UN); // release the lock
if (is_resource($LockFileBlock))
fclose($LockFileBlock);
}
}
return $return;
}
/**
* Releases the Cron Lock locking file, useful to specify on fatal errors
*/
public static function ReleaseCronLock() {
// release the cronlock
if (!empty(self::$LockFile) && is_resource(self::$LockFile)) {
var_dump('Cronlock released after error encountered: ' . self::$LockFile);
flock(self::$LockFile, LOCK_UN);
fclose(self::$LockFile);
}
// release any lockblocks too
foreach (self::$LockFileBlocks as $LockFileBlock) {
if (!empty($LockFileBlock) && is_resource($LockFileBlock)) {
flock($LockFileBlock, LOCK_UN);
fclose($LockFileBlock);
}
}
}
}
Также должно быть реализовано на общей странице или встроено в существующий обработчик фатальных ошибок:
function fatal_handler() {
// For cleaning up crons that fail
Cronlocker::ReleaseCronLock();
}
register_shutdown_function("fatal_handler");