mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-02 14:25:38 +08:00
fix: add SIGHUP/SIGTERM handling, modernize PHP support and CI
Signal handling fixes: - SignalListener: add SIGHUP/SIGTERM handling for both Swoole and Workerman drivers in master and worker processes - Prevent 100% CPU when IDE terminal is closed by ensuring graceful shutdown on terminal hangup PHP version support: - Widen PHP constraint to 8.3, 8.4, 8.5 - Bump doctrine/dbal from ^2.13.1 to ^4.4 - Bump php-cs-fixer to ^3.64, phpstan to ^1.12 - Bump swoole/ide-helper to ^5.0 - Drop phpunit ^8.5 (EOL), keep ^9.0 CI updates: - actions/checkout@v3 → @v4 (Node.js 20 deprecated) - Bump static analysis/code style PHP from 8.1 to 8.3
This commit is contained in:
@@ -12,14 +12,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.base_ref }}
|
ref: ${{ github.base_ref }}
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: sunxyw/workflows/setup-environment@main
|
uses: sunxyw/workflows/setup-environment@main
|
||||||
with:
|
with:
|
||||||
php-version: 8.1
|
php-version: 8.3
|
||||||
php-extensions: swoole, posix, json
|
php-extensions: swoole, posix, json
|
||||||
operating-system: ubuntu-latest
|
operating-system: ubuntu-latest
|
||||||
use-cache: true
|
use-cache: true
|
||||||
@@ -40,14 +40,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.base_ref }}
|
ref: ${{ github.base_ref }}
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: sunxyw/workflows/setup-environment@main
|
uses: sunxyw/workflows/setup-environment@main
|
||||||
with:
|
with:
|
||||||
php-version: 8.1
|
php-version: 8.3
|
||||||
php-extensions: swoole, posix, json
|
php-extensions: swoole, posix, json
|
||||||
operating-system: ubuntu-latest
|
operating-system: ubuntu-latest
|
||||||
use-cache: true
|
use-cache: true
|
||||||
|
|||||||
4
.github/workflows/coding-style.yml
vendored
4
.github/workflows/coding-style.yml
vendored
@@ -27,14 +27,14 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: sunxyw/workflows/setup-environment@main
|
uses: sunxyw/workflows/setup-environment@main
|
||||||
with:
|
with:
|
||||||
php-version: 8.1
|
php-version: 8.3
|
||||||
php-extensions: swoole, posix, json
|
php-extensions: swoole, posix, json
|
||||||
operating-system: ubuntu-latest
|
operating-system: ubuntu-latest
|
||||||
use-cache: true
|
use-cache: true
|
||||||
|
|||||||
4
.github/workflows/increment-build-number.yml
vendored
4
.github/workflows/increment-build-number.yml
vendored
@@ -16,14 +16,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.base_ref }}
|
ref: ${{ github.base_ref }}
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: sunxyw/workflows/setup-environment@main
|
uses: sunxyw/workflows/setup-environment@main
|
||||||
with:
|
with:
|
||||||
php-version: 8.1
|
php-version: 8.3
|
||||||
php-extensions: swoole, posix, json
|
php-extensions: swoole, posix, json
|
||||||
operating-system: ubuntu-latest
|
operating-system: ubuntu-latest
|
||||||
use-cache: true
|
use-cache: true
|
||||||
|
|||||||
4
.github/workflows/static-analysis.yml
vendored
4
.github/workflows/static-analysis.yml
vendored
@@ -27,14 +27,14 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: sunxyw/workflows/setup-environment@main
|
uses: sunxyw/workflows/setup-environment@main
|
||||||
with:
|
with:
|
||||||
php-version: 8.1
|
php-version: 8.3
|
||||||
php-extensions: swoole, posix, json
|
php-extensions: swoole, posix, json
|
||||||
operating-system: ubuntu-latest
|
operating-system: ubuntu-latest
|
||||||
use-cache: true
|
use-cache: true
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/vuepress-deploy.yml
vendored
2
.github/workflows/vuepress-deploy.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout master
|
- name: Checkout master
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Deploy docs to GitHub Pages
|
- name: Deploy docs to GitHub Pages
|
||||||
uses: jenkey2011/vuepress-deploy@master
|
uses: jenkey2011/vuepress-deploy@master
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0 || ^8.1 || ^8.2",
|
"php": "^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4 || ^8.5",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-tokenizer": "*",
|
"ext-tokenizer": "*",
|
||||||
"doctrine/dbal": "^2.13.1",
|
"doctrine/dbal": "^2.13.1",
|
||||||
@@ -35,18 +35,18 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"captainhook/captainhook": "^5.10",
|
"captainhook/captainhook": "^5.10",
|
||||||
"captainhook/plugin-composer": "^5.3",
|
"captainhook/plugin-composer": "^5.3",
|
||||||
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
|
"friendsofphp/php-cs-fixer": "^3.64",
|
||||||
"jangregor/phpstan-prophecy": "^1.0",
|
"jangregor/phpstan-prophecy": "^1.0",
|
||||||
"jetbrains/phpstorm-attributes": "^1.0",
|
"jetbrains/phpstorm-attributes": "^1.0",
|
||||||
"mikey179/vfsstream": "^1.6",
|
"mikey179/vfsstream": "^1.6",
|
||||||
"phpspec/prophecy-phpunit": "^2.3",
|
"phpspec/prophecy-phpunit": "^2.3",
|
||||||
"phpstan/extension-installer": "^1.1",
|
"phpstan/extension-installer": "^1.1",
|
||||||
"phpstan/phpstan": "^1.1",
|
"phpstan/phpstan": "^1.12",
|
||||||
"phpstan/phpstan-deprecation-rules": "^1.0",
|
"phpstan/phpstan-deprecation-rules": "^1.0",
|
||||||
"phpstan/phpstan-phpunit": "^1.1",
|
"phpstan/phpstan-phpunit": "^1.1",
|
||||||
"phpunit/phpunit": "^8.5 || ^9.0",
|
"phpunit/phpunit": "^9.0",
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"swoole/ide-helper": "^4.5"
|
"swoole/ide-helper": "^5.0"
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"symfony/polyfill-php80": "*"
|
"symfony/polyfill-php80": "*"
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ function redis(string $name = 'default'): RedisWrapper
|
|||||||
* @param null|mixed $default 默认值
|
* @param null|mixed $default 默认值
|
||||||
* @return mixed|void|ZMConfig
|
* @return mixed|void|ZMConfig
|
||||||
*/
|
*/
|
||||||
function config(null|array|string $key = null, mixed $default = null)
|
function config(array|string|null $key = null, mixed $default = null)
|
||||||
{
|
{
|
||||||
$config = ZMConfig::getInstance();
|
$config = ZMConfig::getInstance();
|
||||||
if (is_null($key)) {
|
if (is_null($key)) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class CommandArgument extends AnnotationBase implements ErgodicAnnotation
|
|||||||
string $type = 'string',
|
string $type = 'string',
|
||||||
public bool $required = false,
|
public bool $required = false,
|
||||||
public string $prompt = '',
|
public string $prompt = '',
|
||||||
public null|array|\Closure|float|int|string $default = '',
|
public array|\Closure|float|int|string|null $default = '',
|
||||||
public int $timeout = 60,
|
public int $timeout = 60,
|
||||||
public int $error_prompt_policy = 1
|
public int $error_prompt_policy = 1
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class BotContext implements ContextInterface
|
|||||||
* @noinspection PhpDocMissingThrowsInspection
|
* @noinspection PhpDocMissingThrowsInspection
|
||||||
* @noinspection PhpUnhandledExceptionInspection
|
* @noinspection PhpUnhandledExceptionInspection
|
||||||
*/
|
*/
|
||||||
public function prompt(array|MessageSegment|string|\Stringable $prompt = '', int $timeout = 600, array|MessageSegment|string|\Stringable $timeout_prompt = '', int $option = ZM_PROMPT_NONE): null|array|OneBotEvent|string
|
public function prompt(array|MessageSegment|string|\Stringable $prompt = '', int $timeout = 600, array|MessageSegment|string|\Stringable $timeout_prompt = '', int $option = ZM_PROMPT_NONE): array|OneBotEvent|string|null
|
||||||
{
|
{
|
||||||
if (!container()->has('bot.event')) {
|
if (!container()->has('bot.event')) {
|
||||||
throw new OneBot12Exception('bot()->prompt() can only be used in message event');
|
throw new OneBot12Exception('bot()->prompt() can only be used in message event');
|
||||||
@@ -268,7 +268,7 @@ class BotContext implements ContextInterface
|
|||||||
* @return null|array|OneBotEvent|string 根据不同匹配类型返回不同的东西
|
* @return null|array|OneBotEvent|string 根据不同匹配类型返回不同的东西
|
||||||
* @throws OneBot12Exception
|
* @throws OneBot12Exception
|
||||||
*/
|
*/
|
||||||
private function applyPromptReturn(mixed $result, int $option): null|array|OneBotEvent|string
|
private function applyPromptReturn(mixed $result, int $option): array|OneBotEvent|string|null
|
||||||
{
|
{
|
||||||
// 必须是 OneBotEvent 且是消息类型
|
// 必须是 OneBotEvent 且是消息类型
|
||||||
if (!$result instanceof OneBotEvent || $result->type !== 'message') {
|
if (!$result instanceof OneBotEvent || $result->type !== 'message') {
|
||||||
|
|||||||
@@ -30,10 +30,13 @@ class SignalListener
|
|||||||
switch (Framework::getInstance()->getDriver()->getName()) {
|
switch (Framework::getInstance()->getDriver()->getName()) {
|
||||||
case 'swoole':
|
case 'swoole':
|
||||||
Process::signal(SIGINT, [$this, 'onWorkerInt']);
|
Process::signal(SIGINT, [$this, 'onWorkerInt']);
|
||||||
|
Process::signal(SIGTERM, [$this, 'onWorkerInt']);
|
||||||
|
Process::signal(SIGHUP, [$this, 'onWorkerInt']);
|
||||||
break;
|
break;
|
||||||
case 'workerman':
|
case 'workerman':
|
||||||
Worker::$globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, [$this, 'onWorkerInt']);
|
Worker::$globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, [$this, 'onWorkerInt']);
|
||||||
Worker::$globalEvent->add(SIGTERM, EventInterface::EV_SIGNAL, fn () => Worker::stopAll(15));
|
Worker::$globalEvent->add(SIGTERM, EventInterface::EV_SIGNAL, fn () => Worker::stopAll(15));
|
||||||
|
Worker::$globalEvent->add(SIGHUP, EventInterface::EV_SIGNAL, fn () => Worker::stopAll(15));
|
||||||
if (function_exists('pcntl_signal')) {
|
if (function_exists('pcntl_signal')) {
|
||||||
pcntl_signal(SIGUSR1, SIG_IGN, false);
|
pcntl_signal(SIGUSR1, SIG_IGN, false);
|
||||||
}
|
}
|
||||||
@@ -51,10 +54,9 @@ class SignalListener
|
|||||||
{
|
{
|
||||||
$driver = Framework::getInstance()->getDriver()->getName();
|
$driver = Framework::getInstance()->getDriver()->getName();
|
||||||
if ($driver === 'swoole') {
|
if ($driver === 'swoole') {
|
||||||
Process::signal(SIGINT, function () {
|
$stopHandler = function () {
|
||||||
echo "\r";
|
echo "\r";
|
||||||
logger()->notice('Master 进程收到中断信号 SIGINT');
|
logger()->notice('Master 进程收到中断信号,正在停止服务器');
|
||||||
logger()->notice('正在停止服务器');
|
|
||||||
Framework::getInstance()->stop();
|
Framework::getInstance()->stop();
|
||||||
if (extension_loaded('posix')) {
|
if (extension_loaded('posix')) {
|
||||||
Process::kill(posix_getpid(), SIGTERM);
|
Process::kill(posix_getpid(), SIGTERM);
|
||||||
@@ -62,10 +64,13 @@ class SignalListener
|
|||||||
/* @phpstan-ignore-next-line */
|
/* @phpstan-ignore-next-line */
|
||||||
Process::kill(Framework::getInstance()->getDriver()->getSwooleServer()->master_pid, SIGTERM);
|
Process::kill(Framework::getInstance()->getDriver()->getSwooleServer()->master_pid, SIGTERM);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
Process::signal(SIGINT, $stopHandler);
|
||||||
|
Process::signal(SIGTERM, $stopHandler);
|
||||||
|
Process::signal(SIGHUP, $stopHandler);
|
||||||
} elseif ($driver === 'workerman') {
|
} elseif ($driver === 'workerman') {
|
||||||
if (!extension_loaded('pcntl') || !extension_loaded('posix')) {
|
if (!extension_loaded('pcntl') || !extension_loaded('posix')) {
|
||||||
logger()->error('请安装 pcntl 和 posix 扩展以支持 SIGINT 监听');
|
logger()->error('请安装 pcntl 和 posix 扩展以支持信号监听');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,15 +78,14 @@ class SignalListener
|
|||||||
logger()->warning('重启ing');
|
logger()->warning('重启ing');
|
||||||
Worker::reloadSelf();
|
Worker::reloadSelf();
|
||||||
}, false);
|
}, false);
|
||||||
pcntl_signal(SIGTERM, function () {
|
$stopMaster = function () {
|
||||||
Worker::stopAll();
|
|
||||||
}, false);
|
|
||||||
pcntl_signal(SIGINT, function () {
|
|
||||||
echo "\r";
|
echo "\r";
|
||||||
logger()->notice('Master 进程收到中断信号 SIGINT');
|
logger()->notice('Master 进程收到中断信号,正在停止服务器');
|
||||||
logger()->notice('正在停止服务器');
|
|
||||||
Worker::stopAll();
|
Worker::stopAll();
|
||||||
}, false);
|
};
|
||||||
|
pcntl_signal(SIGTERM, $stopMaster, false);
|
||||||
|
pcntl_signal(SIGINT, $stopMaster, false);
|
||||||
|
pcntl_signal(SIGHUP, $stopMaster, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class Framework
|
|||||||
public const VERSION_ID = 726;
|
public const VERSION_ID = 726;
|
||||||
|
|
||||||
/** @var string 版本名称 */
|
/** @var string 版本名称 */
|
||||||
public const VERSION = '3.2.6';
|
public const VERSION = '3.2.7';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var RuntimePreferences 运行时偏好(环境信息&参数)
|
* @var RuntimePreferences 运行时偏好(环境信息&参数)
|
||||||
@@ -61,7 +61,7 @@ class Framework
|
|||||||
protected array $argv;
|
protected array $argv;
|
||||||
|
|
||||||
/** @var null|Driver|SwooleDriver|WorkermanDriver OneBot驱动 */
|
/** @var null|Driver|SwooleDriver|WorkermanDriver OneBot驱动 */
|
||||||
protected null|Driver|SwooleDriver|WorkermanDriver $driver = null;
|
protected Driver|SwooleDriver|WorkermanDriver|null $driver = null;
|
||||||
|
|
||||||
/** @var array<array<string, string>> 启动注解列表 */
|
/** @var array<array<string, string>> 启动注解列表 */
|
||||||
protected array $setup_annotations = [];
|
protected array $setup_annotations = [];
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class ProcessStateManager
|
|||||||
* @throws ZMKnownException
|
* @throws ZMKnownException
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public static function removeProcessState(int $type, null|int|string $id_or_name = null): void
|
public static function removeProcessState(int $type, int|string|null $id_or_name = null): void
|
||||||
{
|
{
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case ZM_PROCESS_MASTER:
|
case ZM_PROCESS_MASTER:
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class LightCache implements KVInterface
|
|||||||
/**
|
/**
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function set(string $key, mixed $value, null|\DateInterval|int $ttl = null): bool
|
public function set(string $key, mixed $value, \DateInterval|int|null $ttl = null): bool
|
||||||
{
|
{
|
||||||
$this->validateKey($key);
|
$this->validateKey($key);
|
||||||
self::$caches[$this->name][$key] = $value;
|
self::$caches[$this->name][$key] = $value;
|
||||||
@@ -139,7 +139,7 @@ class LightCache implements KVInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMultiple(iterable $values, null|\DateInterval|int $ttl = null): bool
|
public function setMultiple(iterable $values, \DateInterval|int|null $ttl = null): bool
|
||||||
{
|
{
|
||||||
foreach ($values as $k => $v) {
|
foreach ($values as $k => $v) {
|
||||||
if (!$this->set($k, $v, $ttl)) {
|
if (!$this->set($k, $v, $ttl)) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class KVRedis implements KVInterface
|
|||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set(string $key, mixed $value, null|\DateInterval|int $ttl = null): bool
|
public function set(string $key, mixed $value, \DateInterval|int|null $ttl = null): bool
|
||||||
{
|
{
|
||||||
/** @var ZMRedis $redis */
|
/** @var ZMRedis $redis */
|
||||||
$redis = RedisPool::pool($this->pool_name)->get();
|
$redis = RedisPool::pool($this->pool_name)->get();
|
||||||
@@ -78,7 +78,7 @@ class KVRedis implements KVInterface
|
|||||||
RedisPool::pool($this->pool_name)->put($redis);
|
RedisPool::pool($this->pool_name)->put($redis);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMultiple(iterable $values, null|\DateInterval|int $ttl = null): bool
|
public function setMultiple(iterable $values, \DateInterval|int|null $ttl = null): bool
|
||||||
{
|
{
|
||||||
/** @var ZMRedis $redis */
|
/** @var ZMRedis $redis */
|
||||||
$redis = RedisPool::pool($this->pool_name)->get();
|
$redis = RedisPool::pool($this->pool_name)->get();
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class MessageUtil
|
|||||||
return $ls;
|
return $ls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAltMessage(null|array|MessageSegment|string $message): string
|
public static function getAltMessage(array|MessageSegment|string|null $message): string
|
||||||
{
|
{
|
||||||
if ($message === null) {
|
if ($message === null) {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
Reference in New Issue
Block a user