信号处理

  • 需要linux环境并开启pcntl

<?php

declare(ticks=1);

namespace app\common\library;

/**
 * 信号处理, 比如重新队列文件的while (true)增加信号管理
Signal::init();
while (true) {
    Signal::checkExit();
    // code ...
}
 * @throws \Exception
 * @copyright lxx
 * @author lxx
 */
class Signal
{
    // 强制停止时间
    public const TIMEOUT = 60 * 3;
    /**
     * 注册信号
     * 请在while(true)中运行 pcntl_signal_dispatch();
     * @return void
     * @throws \Exception
     * @copyright lxx
     * @author lxx
     */
    public static function init()
    {
        if (PHP_OS == 'Linux' && IS_CLI) {
            // go on
        } else {
            return;
        }
        $GLOBALS['signal_stop'] = 0;
        pcntl_signal(SIGINT,  function ($s) {
            // CTRL+c
            (new Signal())->tagStop();
        });
        pcntl_signal(SIGTERM, function ($s) {
            // docker默认发送这个终止SIGTERM
            // https://docs.docker.com/compose/compose-file/05-services/#stop_signal
            // 超时时间 https://docs.docker.com/compose/compose-file/05-services/#stop_grace_period
            (new Signal())->tagStop();
        });
        pcntl_signal(SIGHUP,  function ($s) {
            // 重启信号
            (new Signal())->tagStop();
        });
        pcntl_signal(SIGALRM,  function ($s) {
            // pcntl_alarm信号
            echo 'TIMEOUT EXIT.';
            exit();
        });
    }
    /**
     * 标记全进程的stop, 超时时候直接发送停止信号
     * ! 闭包限制,虽然是public也仅限当前类使用
     * @return void
     * @throws \Exception
     * @copyright lxx
     * @author lxx
     */
    public function tagStop()
    {
        $GLOBALS['signal_stop'] = 1;
        // 使用这个不会阻塞进程
        echo '收到停止信号';
        pcntl_alarm(self::TIMEOUT);
    }
    /**
     * 判断是否存在停止信号
     *
     * @return boolean
     * @throws \Exception
     * @copyright lxx
     * @author lxx
     */
    private static function isSignalStop()
    {
        return !empty($GLOBALS['signal_stop']);
    }
    /**
     * 检查是否需要退出
     * while中,且在代码之前
     * @return void
     * @throws \Exception
     * @copyright lxx
     * @author lxx
     */
    public static function checkExit()
    {
        if (PHP_OS == 'Linux' && IS_CLI) {
            // go on
        } else {
            return;
        }
        pcntl_signal_dispatch();
        if (self::isSignalStop()) {
            // 直接停止
            echo '不再运行,直接停止' . PHP_EOL;
            exit();
        }
    }
}