Compare commits

...

17 Commits

Author SHA1 Message Date
crazywhalecc
d01bd69aa5 update to 2.7.0-beta1 (build 435) 2022-03-13 22:50:32 +08:00
crazywhalecc
e6b9ae3ee1 add --watch function for no-installed-inotify users 2022-03-13 22:50:01 +08:00
crazywhalecc
73b6b8045d enhancement for process state 2022-03-13 22:47:11 +08:00
crazywhalecc
3c87abc6e8 enhancement for process state 2022-03-13 22:46:22 +08:00
crazywhalecc
e95925c129 add compatibility for PHP 8.1 2022-03-13 22:16:02 +08:00
crazywhalecc
82c44d6c40 enhancement for process state 2022-03-13 22:15:27 +08:00
crazywhalecc
e0a268e05e add autoload-dev auto scanner 2022-03-13 22:11:30 +08:00
crazywhalecc
487892e1d9 add force kill framework command --force 2022-03-13 22:05:53 +08:00
crazywhalecc
7ab4e88359 add KILLER PROMPT function 2022-03-13 22:05:23 +08:00
crazywhalecc
4702b6ee75 separate ProcessManager and WorkerManager 2022-03-13 22:04:52 +08:00
crazywhalecc
20cd3aa66d update docs 2022-03-13 22:03:52 +08:00
crazywhalecc
391114bdef update docs 2022-03-13 21:57:41 +08:00
crazywhalecc
ffe1052ecc add gitignore items 2022-03-13 21:54:43 +08:00
crazywhalecc
b4159152a7 add watcher 2022-01-08 20:19:10 +08:00
crazywhalecc
8fc6e4b0f7 update docs 2022-01-08 16:26:47 +08:00
crazywhalecc
c8938b7a4b update docs 2022-01-08 16:23:10 +08:00
crazywhalecc
34db1626a5 update to 2.6.6 (build 434) 2022-01-08 16:19:43 +08:00
36 changed files with 792 additions and 265 deletions

1
.gitignore vendored
View File

@@ -19,3 +19,4 @@ composer.lock
/ext/go-cqhttp/device.json
/ext/go-cqhttp/go-cqhttp
/ext/go-cqhttp/session.token
.zm_worker_*.pid

17
.run/Run watcher.run.xml Normal file
View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run watcher" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="bin/watcher" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

0
bin/start Normal file → Executable file
View File

118
bin/watcher Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
_red="\e[33m"
_green="\e[32m"
_reset="\e[0m"
unix_s=$(uname -s)
unix_release=$(
marked_release=""
if [ "$unix_s" = "Linux" ]; then
echo $HOME | grep com.termux >/dev/null
if [ $? == 0 ]; then
marked_release="termux"
elif [ -f "/etc/redhat-release" ]; then
if [ "$(cat /etc/redhat-release | awk '{print $1}' | grep -v '^$')" = "CentOS" ]; then
marked_release="CentOS"
else
marked_release="unknown"
fi
elif [ -f "/etc/os-release" ]; then
cat /etc/os-release | grep Alpine > /dev/null
if [ $? == 0 ]; then
marked_release="Alpine"
fi
fi
if [ "$marked_release" = "" ]; then
if [ -f "/etc/issue" ]; then
marked_release=$(cat /etc/issue | grep -v '^$' | awk '{print $1}')
else
marked_release="unknown"
fi
fi
elif [ "$unix_s" = "Darwin" ]; then
marked_release=$(sw_vers | grep ProductName | awk '{print $2" "$3" "$4}')
fi
echo $marked_release
)
unix_release=$(echo $unix_release | xargs)
function echo_error() {
echo -e "${_red}$1${_reset}"
}
function echo_info() {
echo -e "${_green}$1${_reset}"
}
function install_test() {
which fswatch >/dev/null
if [ $? != 0 ]; then
operate_confirm "fswatch还没有安装是否确认安装" && install_fswatch
fi
}
function install_fswatch() {
if [ "$unix_s" = "Linux" ]; then
case $unix_release in
"Kali" | "Ubuntu" | "Debian" | "Raspbian" | 'Pop!_OS')
sudo apt-get install fswatch -y ;;
#"termux") pkg install $1 -y ;;
"CentOS")
curl -o fswatch.tgz https://download.fastgit.org/emcrisostomo/fswatch/releases/download/1.16.0/fswatch-1.16.0.tar.gz && \
tar -xzf fswatch.tgz && \
cd fswatch-1.16.0 && \
./configure && \
make && \
sudo make install && \
cd .. && \
rm -rf fswatch.tgz fswatch-1.16.0
;;
#"Alpine") apk add $1 ;;
*)
echo_error "不支持的 Linux 发行版:$unix_release"
exit 1
;;
esac
elif [ "$unix_s" = "Darwin" ]; then
brew install fswatch
else
echo_error "不支持的操作系统:$unix_s"
exit 1
fi
}
function operate_confirm() {
echo -n $(echo_info "$1 [Y/n] ")
read operate
operate=$(echo $operate | tr A-Z a-z)
if [[ "$operate" = "y" || "$operate" = "" ]]; then
return 0
else
return 1
fi
}
echo_info "当前系统:$unix_release"
install_test
if [ $? -ne 0 ]; then
exit 1
else
echo_info "程序路径:$(which fswatch)"
fi
watch_dir="./src"
if [ ! -d "$watch_dir" ]; then
echo_error "src目录不存在"
exit 1
else
echo_info "监听目录:$watch_dir"
fi
_pid=$(cat .daemon_pid | awk -F"\"pid\": " '{print $2}' | grep -v ^$ | sed 's/,//g')
if [ "$_pid" = "" ]; then
echo_error "未检测到框架进程"
exit 1
fi
fswatch $watch_dir | while read file
do
echo "Detect file change: $file"
kill -USR1 $_pid
done

View File

@@ -17,7 +17,8 @@
"prefer-stable": true,
"bin": [
"bin/start",
"bin/phpunit-swoole"
"bin/phpunit-swoole",
"bin/watcher"
],
"require": {
"php": ">=7.2",
@@ -52,6 +53,12 @@
"src/ZM/global_functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Module\\": "src/Module",
"Custom\\": "src/Custom"
}
},
"require-dev": {
"swoole/ide-helper": "@dev",
"phpunit/phpunit": "^8.5 || ^9.0"

View File

@@ -41,7 +41,8 @@ $config['runtime'] = [
'swoole_server_mode' => SWOOLE_PROCESS,
'middleware_error_policy' => 1,
'reload_delay_time' => 800,
'global_middleware_binding' => []
'global_middleware_binding' => [],
'save_console_log_file' => false, // 改为目标路径,则将 Console 输出的日志保存到文件
];
/** 轻量字符串缓存,默认开启 */

View File

@@ -2,7 +2,7 @@
此类管理的是 TaskWorker 相关工作。有关使用 TaskWorker 的教程,见 [进阶 - 使用 TaskWorker 进程处理密集运算](/advanced/task-worker)
类定义:`\ZM\Utils\TaskManager`
类定义:`\ZM\Utils\Manager\TaskManager`
使用 TaskWorker 需要先在 `global.php` 配置文件中开启!

View File

@@ -8,6 +8,10 @@ DataProvider 是框架内提供的一个简易的文件管理类。
`working_dir()`
## DataProvider::getSourceRootDir()
获取用户的源码根目录,除 Phar 模式外与 `getWorkingDir()` 相同。
## DataProvider::getFrameworkLink()
`ZMConfig::get("global", "http_reverse_link")`,获取反向代理的链接。
@@ -46,6 +50,45 @@ DataProvider::getDataFolder("TestModule"); // 例如返回 /root/zhamao-framewor
文件名同上 `saveToJson()` 的定义,解析后的返回值为原先的内容或 `null`(如果文件不存在或 json 解析失败)。
## DataProvider::scanDirFiles()
递归或非递归扫描目录,返回相对目录的文件列表或绝对目录的文件列表。(非常好用)
定义:`scanDirFiles($dir, $recursive = true, $relative = false)`
`$dir` 为要扫描的目录,`$recursive` 为是否递归,`$relative` 为是否返回相对目录的文件列表。
从给定的目录下开始遍历整个目录,如果将 `$recursive` 设置为 `true`,则会递归扫描子目录,否则将返回包含目录的文件列表。
如果将 `$relative` 设置为 `true`,则会返回文件列表的相对路径,否则返回绝对路径。
例如:假设目录 `/home/test/` 下有两个文件:`test1.txt``testdir/test2.txt`:如果将 `$recursive` 设置为 `true``$relative` 设置为 `false`,则返回的文件列表为:
```json
[
"/home/test/test1.txt",
"home/test/testdir/test2.txt"
]
```
相同条件下,如果将 `$relative` 设置为 `true`
```json
[
"test1.txt",
"testdir/test2.txt"
]
```
如果再把 `$recursive` 设置为 `false`
```json
[
"test1.txt",
"testdir"
]
```
## 其他文件读取
框架比较贴近原生的 PHP所以推荐直接使用原生的方法来读写文件`file_get_contents``file_put_contents`)。但有一点要注意,框架内最好使用**工作目录或者绝对路径**。

View File

@@ -6,9 +6,18 @@
但是如果因为用户的误操作,导致炸毛框架其中的一个或多个进程阻塞,或者比如将框架挂在 screen 等守护但是守护服务进程被杀掉,总之就是无法使用 Ctrl+C 的方式正常关闭框架,这时就需要正确地杀掉所有框架进程(这固然可能会造成内存的缓存数据丢失)。
### v2.7.0 及以上版本教程
- 安全关框架指令:`./zhamao server:stop`
- 万能杀死所有框架进程指令:`./zhamao server:stop --force`
- 监视框架是否在运行:`./zhamao server:status`
- Worker 进程卡死:连续按 5 次 Ctrl+C 即可强行杀掉所有进程SIGKILL
### v2.6.6 及以下版本教程
!!! warning "注意"
下方涉及 `ps` 命令后使用 `grep` 过滤的框架进程方式,如果你的服务器同时有其他使用 PHP 启动的服务,命令行刚好有 `server` 字样,可能会导致误杀,如果有影响的话,建议将 `grep server` 换成你启动时命令行的特殊参数或手动排除!
下方涉及 `ps` 命令后使用 `grep` 过滤的框架进程方式,如果你的服务器同时有其他使用 PHP 启动的服务,命令行刚好有 `server` 字样,可能会导致误杀,如果有影响的话,建议将 `grep server` 换成你启动时命令行的特殊参数或手动排除!
**一、**首先,使用 `ps``htop``netstat -nlp` 等命令确定框架的入口进程(也就是 Master 进程的 pid

View File

@@ -58,6 +58,7 @@
| `middleware_error_policy` | 中间件错误处理策略,见 [中间件 - 错误处理策略](../../event/middleware/#_6) | 1 |
| `reload_delay_time` | 框架 reload 重载命令接收后延迟的时间毫秒0 为不等待) | 800 |
| `global_middleware_binding` | 给注解事件绑定全局中间件,见 [中间件 - 全局中间件](../../event/middleware/#_6) | `[]` |
| `save_console_log_file` | 当这里输入字符串路径时,所有 `Console::xxx()` 输出的日志都将保存到目标文件 | false |
### 子表 **light_cache**

View File

@@ -1,7 +1,5 @@
# 介绍
> 本文档为炸毛框架 v2 版本,如需查看 v1 版本,[点我](https://docs-v1.zhamao.xin/)。
!!! tip "提示"
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档

View File

@@ -4,6 +4,11 @@
同时此处将只使用 build 版本号进行区分。
## build 434 (2022-1-8)
- 修复框架在 PHP 8.1 下运行时的一些问题
- 新增 Console 日志输出到文件的功能
## build 433 (2021-12-28)
- 修复 OneBotV11 因 IDE 自动优化导致 API 接口发生变化的问题

View File

@@ -2,6 +2,10 @@
这里将会记录各个主版本的框架升级后,涉及 `global.php` 的更新日志,你可以根据这里描述的内容与你的旧配置文件进行合并。
## v2.6.6 (build 434)
- 新增 `$config['runtime']` 下的 `save_console_log_file` 项。
## v2.6.0 (build 427)
- 新增 `$config['runtime']` 下的 `reload_delay_time``global_middleware_binding` 项。

View File

@@ -1,5 +1,12 @@
# 更新日志v2 版本)
## v2.6.6build 434
> 更新时间2022.1.8
- 修复框架在 PHP 8.1 下运行时的一些问题
- 新增 Console 日志输出到文件的功能
## v2.6.5build 433
> 更新时间2021.12.28

View File

@@ -106,6 +106,7 @@ nav:
- Console 终端: component/common/console.md
- TaskWorker 管理: component/common/task-worker.md
- Terminal 终端: component/common/remote-terminal.md
- EventTracer 事件追踪器: component/comon/event-tracer.md
- HTTP 服务器工具类:
- HTTP 和 WebSocket 客户端: component/http/zmrequest.md
- HTTP 路由管理: component/http/route-manager.md

View File

@@ -7,22 +7,17 @@ namespace ZM\Command\Daemon;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
use ZM\Framework;
abstract class DaemonCommand extends Command
{
protected $daemon_file = null;
protected function execute(InputInterface $input, OutputInterface $output): int {
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
if (!file_exists($pid_path)) {
$output->writeln("<comment>没有检测到正在运行的守护进程或框架进程!</comment>");
die();
}
$file = json_decode(file_get_contents($pid_path), true);
if ($file === null || posix_getsid(intval($file["pid"])) === false) {
$file = Framework::getProcessState(ZM_PROCESS_MASTER);
if ($file === false || posix_getsid(intval($file["pid"])) === false) {
$output->writeln("<comment>未检测到正在运行的守护进程或框架进程!</comment>");
unlink($pid_path);
Framework::removeProcessState(ZM_PROCESS_MASTER);
die();
}
$this->daemon_file = $file;

View File

@@ -6,7 +6,7 @@ namespace ZM\Command\Daemon;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
use ZM\Framework;
class DaemonStopCommand extends DaemonCommand
{
@@ -20,7 +20,7 @@ class DaemonStopCommand extends DaemonCommand
parent::execute($input, $output);
Process::kill(intval($this->daemon_file["pid"]), SIGTERM);
$i = 10;
while (file_exists(DataProvider::getWorkingDir() . "/.daemon_pid") && $i > 0) {
while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) {
sleep(1);
--$i;
}

View File

@@ -36,7 +36,8 @@ class RunServerCommand extends Command
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
new InputOption("disable-safe-exit", null, null, "关闭安全退出关闭后按CtrlC时直接杀死进程"),
new InputOption("preview", null, null, "只显示参数,不启动服务器"),
new InputOption("force-load-module", null, InputOption::VALUE_OPTIONAL, "强制打包状态下加载模块(使用英文逗号分割多个)")
new InputOption("force-load-module", null, InputOption::VALUE_OPTIONAL, "强制打包状态下加载模块(使用英文逗号分割多个)"),
new InputOption("polling-watch", null, null, "强制启用轮询模式监听"),
]);
$this->setDescription("Run zhamao-framework | 启动框架");
$this->setHelp("直接运行可以启动");
@@ -49,16 +50,11 @@ class RunServerCommand extends Command
return 1;
}
}
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
if (file_exists($pid_path)) {
$pid = json_decode(file_get_contents($pid_path), true)["pid"] ?? null;
if ($pid !== null && posix_getsid($pid) !== false) {
$output->writeln("<error>检测到已经在 pid: $pid 进程启动了框架!</error>");
$output->writeln("<error>不可以同时启动两个框架!</error>");
return 1;
} else {
unlink($pid_path);
}
$state = Framework::getProcessState(ZM_PROCESS_MASTER);
if (is_array($state) && posix_getsid($state['pid'] ?? -1) !== false) {
$output->writeln("<error>检测到已经在 pid: {$state['pid']} 进程启动了框架!</error>");
$output->writeln("<error>不可以同时启动两个框架!</error>");
return 1;
}
(new Framework($input->getOptions()))->start();
return 0;

View File

@@ -5,8 +5,10 @@ namespace ZM\Command\Server;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Command\Daemon\DaemonCommand;
use ZM\Framework;
use ZM\Utils\DataProvider;
class ServerStopCommand extends DaemonCommand
@@ -15,18 +17,38 @@ class ServerStopCommand extends DaemonCommand
protected function configure() {
$this->setDescription("停止运行的框架");
$this->setDefinition([
new InputOption('force', 'f', InputOption::VALUE_NONE, '强制停止'),
]);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output);
if ($input->getOption('force') !== false) {
$file_path = _zm_pid_dir();
$list = DataProvider::scanDirFiles($file_path, false, true);
foreach($list as $file) {
$name = explode('.', $file);
if (end($name) == 'pid') {
$pid = file_get_contents($file_path.'/'.$file);
Process::kill($pid, SIGKILL);
} elseif ($file === 'master.json') {
$json = json_decode(file_get_contents($file_path.'/'.$file), true);
Process::kill($json['pid'], SIGKILL);
}
unlink($file_path.'/'.$file);
}
} else {
parent::execute($input, $output);
}
Process::kill(intval($this->daemon_file["pid"]), SIGTERM);
$i = 10;
while (file_exists(DataProvider::getWorkingDir() . "/.daemon_pid") && $i > 0) {
while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) {
sleep(1);
--$i;
}
if ($i === 0) {
$output->writeln("<error>停止失败请检查进程pid #" . $this->daemon_file["pid"] . " 是否响应!</error>");
$output->writeln("<error>或者可以尝试使用参数 --force 来强行杀死所有进程</error>");
} else {
$output->writeln("<info>成功停止!</info>");
}

View File

@@ -30,8 +30,8 @@ class ConsoleApplication extends Application
{
private static $obj = null;
const VERSION_ID = 433;
const VERSION = "2.6.5";
const VERSION_ID = 435;
const VERSION = "2.7.0-beta1";
/**
* @throws InitException
@@ -54,7 +54,20 @@ class ConsoleApplication extends Application
_zm_env_check();
// 定义多进程的全局变量
define('ZM_PROCESS_MASTER', 1);
define('ZM_PROCESS_MANAGER', 2);
define('ZM_PROCESS_WORKER', 4);
define('ZM_PROCESS_USER', 8);
define('ZM_PROCESS_TASKWORKER', 16);
define("WORKING_DIR", getcwd());
if (!is_dir(_zm_pid_dir())) {
@mkdir(_zm_pid_dir());
}
if (Phar::running() !== "") {
echo "* Running in phar mode.\n";
define("SOURCE_ROOT_DIR", Phar::running());
@@ -65,28 +78,6 @@ class ConsoleApplication extends Application
define("LOAD_MODE", is_dir(SOURCE_ROOT_DIR . "/src/ZM") ? 0 : 1);
define("FRAMEWORK_ROOT_DIR", realpath(__DIR__ . "/../../"));
}
if (LOAD_MODE == 0) {
$composer = json_decode(file_get_contents(SOURCE_ROOT_DIR . "/composer.json"), true);
if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
echo "框架源码模式需要在autoload文件中添加Module目录为自动加载是否添加[Y/n] ";
$r = strtolower(trim(fgets(STDIN)));
if ($r === "" || $r === "y") {
$composer["autoload"]["psr-4"]["Module\\"] = "src/Module";
$composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom";
$r = file_put_contents(WORKING_DIR . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
if ($r !== false) {
echo "成功添加!请运行 'composer dump-autoload'\n";
exit(0);
} else {
echo zm_internal_errcode("E00006") . "添加失败!请按任意键继续!";
fgets(STDIN);
exit(1);
}
} else {
exit(1);
}
}
}
$this->addCommands([
new DaemonStatusCommand(),

View File

@@ -17,6 +17,7 @@ use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider;
use ZM\Utils\Manager\ProcessManager;
use ZM\Utils\SignalListener;
use ZM\Utils\Terminal;
use ZM\Utils\ZMUtil;
@@ -28,15 +29,20 @@ use ZM\Utils\ZMUtil;
*/
class OnManagerStart implements SwooleEvent
{
/** @var null|Process */
public static $process = null;
private static $last_hash = "";
private static $watch = -1;
public function onCall(Server $server) {
Console::debug("Calling onManagerStart event(1)");
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalManager();
}
self::$process = new Process(function() {
Framework::saveProcessState(ZM_PROCESS_MANAGER, $server->manager_pid);
ProcessManager::createUserProcess('monitor', function() use ($server){
Process::signal(SIGINT, function() {
Console::success("用户进程检测到了Ctrl+C");
});
if (Framework::$argv["watch"]) {
if (extension_loaded('inotify')) {
Console::info("Enabled File watcher, framework will reload automatically.");
@@ -48,10 +54,27 @@ class OnManagerStart implements SwooleEvent
Console::verbose("File updated: " . $r[0]["name"]);
ZMUtil::reload();
});
Framework::$argv["polling-watch"] = false; // 如果开启了inotify则关闭轮询热更新
} else {
Console::warning(zm_internal_errcode("E00024") . "You have not loaded \"inotify\" extension, please install first.");
Console::warning(zm_internal_errcode("E00024") . "你还没有安装或启用 inotify 扩展,将默认使用轮询检测模式开启热更新!");
Framework::$argv["polling-watch"] = true;
}
}
if (Framework::$argv["polling-watch"]) {
self::$watch = swoole_timer_tick(3000, function () use ($server) {
$data = (DataProvider::scanDirFiles(DataProvider::getSourceRootDir() . '/src/'));
$hash = md5("");
foreach ($data as $file) {
$hash = md5($hash . md5_file($file));
}
if (self::$last_hash == "") {
self::$last_hash = $hash;
} elseif (self::$last_hash !== $hash) {
self::$last_hash = $hash;
$server->reload();
}
});
}
if (Framework::$argv["interact"]) {
Console::info("Interact mode");
ZMBuf::$terminal = $r = STDIN;
@@ -73,8 +96,16 @@ class OnManagerStart implements SwooleEvent
});
}
});
self::$process->set(['enable_coroutine' => true]);
self::$process->start();
ProcessManager::getUserProcess('monitor')->set(['enable_coroutine' => true]);
ProcessManager::getUserProcess('monitor')->start();
/*$dispatcher = new EventDispatcher(OnManagerStartEvent::class);
$dispatcher->setRuleFunction(function($v) {
return eval("return " . $v->getRule() . ";");
});
$dispatcher->dispatchEvents($server);
*/
Console::verbose("进程 Manager 已启动");
}

View File

@@ -8,7 +8,8 @@ use Swoole\Process;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Utils\DataProvider;
use ZM\Framework;
use ZM\Utils\Manager\ProcessManager;
/**
* Class OnManagerStop
@@ -17,15 +18,12 @@ use ZM\Utils\DataProvider;
*/
class OnManagerStop implements SwooleEvent
{
public function onCall() {
if (OnManagerStart::$process !== null) {
if (Process::kill(OnManagerStart::$process->pid, 0)) {
Process::kill(OnManagerStart::$process->pid, SIGTERM);
}
public function onCall()
{
foreach (ProcessManager::$user_process as $v) {
if (posix_getsid($v->pid) !== false) Process::kill($v->pid, SIGTERM);
}
Console::verbose("进程 Manager 已停止!");
if (file_exists(DataProvider::getWorkingDir()."/.daemon_pid")) {
unlink(DataProvider::getWorkingDir()."/.daemon_pid");
}
Framework::removeProcessState(ZM_PROCESS_MANAGER);
}
}

View File

@@ -25,9 +25,6 @@ use ZM\Event\SwooleEvent;
*/
class OnMessage implements SwooleEvent
{
/**
* @noinspection PhpUnreachableStatementInspection
*/
public function onCall($server, Frame $frame) {
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd . ": " . TermColor::ITALIC . $frame->data . TermColor::RESET);
unset(Context::$context[Coroutine::getCid()]);

View File

@@ -10,7 +10,7 @@ use Swoole\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Utils\Manager\ProcessManager;
use ZM\Utils\Manager\WorkerManager;
/**
* Class OnPipeMessage
@@ -22,7 +22,7 @@ class OnPipeMessage implements SwooleEvent
public function onCall(Server $server, $src_worker_id, $data) {
$data = json_decode($data, true);
try {
ProcessManager::workerAction($src_worker_id, $data);
WorkerManager::workerAction($src_worker_id, $data);
} catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error(zm_internal_errcode("E00021") . "Uncaught exception " . get_class($e) . " when calling \"pipeMessage\": " . $error_msg);

View File

@@ -8,6 +8,7 @@ use Swoole\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Utils\DataProvider;
/**
@@ -19,9 +20,9 @@ class OnShutdown implements SwooleEvent
{
public function onCall(Server $server) {
Console::verbose("正在关闭 Master 进程pid=" . posix_getpid());
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
if (file_exists($pid_path)) {
unlink($pid_path);
Framework::removeProcessState(ZM_PROCESS_MASTER);
if (DataProvider::scanDirFiles(_zm_pid_dir()) == []) {
rmdir(_zm_pid_dir());
}
}
}

View File

@@ -25,13 +25,9 @@ class OnStart implements SwooleEvent
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalMaster($server);
}
$daemon_data = json_encode([
"pid" => $server->master_pid,
"stdout" => ZMConfig::get("global")["swoole"]["log_file"],
"daemon" => (bool)Framework::$argv["daemon"]
], 128 | 256);
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
Framework::saveProcessState(ZM_PROCESS_MASTER, $server->master_pid, [
'stdout' => ZMConfig::get("global")["swoole"]["log_file"],
'daemon' => (bool)Framework::$argv["daemon"]
]);
}
}

View File

@@ -43,13 +43,15 @@ use ZM\Utils\SignalListener;
*/
class OnWorkerStart implements SwooleEvent
{
public function onCall(Server $server, $worker_id) {
public function onCall(Server $server, $worker_id)
{
Console::debug("Calling onWorkerStart event(1)");
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalWorker($server, $worker_id);
}
unset(Context::$context[Coroutine::getCid()]);
if ($server->taskworker === false) {
Framework::saveProcessState(ZM_PROCESS_WORKER, $server->worker_pid, ['worker_id' => $worker_id]);
zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid);
if (LightCacheInside::get("wait_api", "wait_api") !== null) {
LightCacheInside::unset("wait_api", "wait_api");
@@ -105,10 +107,13 @@ class OnWorkerStart implements SwooleEvent
Console::error(zm_internal_errcode("E00030") . "PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
Console::error("Maybe it caused by your own code if in your own Module directory.");
Console::log($e->getTraceAsString(), 'gray');
Process::kill($server->master_pid, SIGTERM);
if (!Framework::$argv["watch"]) { // 在热更新模式下不能退出
Process::kill($server->master_pid, SIGTERM);
}
}
} else {
// 这里是TaskWorker初始化的内容部分
Framework::saveProcessState(ZM_PROCESS_TASKWORKER, $server->worker_pid, ['worker_id' => $worker_id]);
try {
Framework::$server = $server;
$this->loadAnnotations();
@@ -131,13 +136,15 @@ class OnWorkerStart implements SwooleEvent
* @throws ReflectionException
* @throws Exception
*/
private function loadAnnotations() {
private function loadAnnotations()
{
if (Framework::$instant_mode) goto skip;
//加载各个模块的注解类,以及反射
Console::debug("Mapping annotations");
$parser = new AnnotationParser();
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true);
foreach ($composer["autoload"]["psr-4"] as $k => $v) {
$merge = array_merge($composer["autoload"]["psr-4"] ?? [], $composer["autoload-dev"]["psr-4"] ?? []);
foreach ($merge as $k => $v) {
if (is_dir(DataProvider::getSourceRootDir() . "/" . $v)) {
if (in_array(trim($k, "\\") . "\\", $composer["extra"]["exclude_annotate"] ?? [])) continue;
if (trim($k, "\\") == "ZM") continue;
@@ -150,12 +157,12 @@ class OnWorkerStart implements SwooleEvent
$plugin_enable_hotload = ZMConfig::get("global", "module_loader")["enable_hotload"] ?? false;
if ($plugin_enable_hotload) {
$list = ModuleManager::getPackedModules();
foreach($list as $k => $v) {
if (\server()->worker_id === 0) Console::info("Loading packed module: ".$k);
foreach ($list as $k => $v) {
if (\server()->worker_id === 0) Console::info("Loading packed module: " . $k);
require_once $v["phar-path"];
$func = "loader".$v["generated-id"];
$func = "loader" . $v["generated-id"];
$func();
$parser->addRegisterPath("phar://".$v["phar-path"]."/".$v["module-root-path"], $v["namespace"]);
$parser->addRegisterPath("phar://" . $v["phar-path"] . "/" . $v["module-root-path"], $v["namespace"]);
}
}
@@ -189,7 +196,8 @@ class OnWorkerStart implements SwooleEvent
}
}
private function initMySQLPool() {
private function initMySQLPool()
{
if (SqlPoolStorage::$sql_pool !== null) {
SqlPoolStorage::$sql_pool->close();
SqlPoolStorage::$sql_pool = null;

View File

@@ -9,7 +9,9 @@ use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Store\LightCache;
use ZM\Utils\DataProvider;
/**
* Class OnWorkerStop
@@ -22,6 +24,7 @@ class OnWorkerStop implements SwooleEvent
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
LightCache::savePersistence();
}
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止: ".$server->getWorkerStatus($worker_id));
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止 (Worker 状态码: ".$server->getWorkerStatus($worker_id).")");
Framework::removeProcessState($server->taskworker ? ZM_PROCESS_TASKWORKER : ZM_PROCESS_WORKER, $worker_id);
}
}

View File

@@ -12,6 +12,7 @@ use Throwable;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\TermColor;
use ZM\Exception\ZMKnownException;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
@@ -78,7 +79,8 @@ class Framework
public static $instant_mode = false;
public function __construct($args = [], $instant_mode = false) {
public function __construct($args = [], $instant_mode = false)
{
$tty_width = $this->getTtyWidth();
self::$instant_mode = $instant_mode;
self::$argv = $args;
@@ -118,6 +120,9 @@ class Framework
$args["log-theme"] ?? "default",
($o = ZMConfig::get("console_color")) === false ? [] : $o
);
if ((ZMConfig::get("global", "runtime")["save_console_log_file"] ?? false) !== false) {
Console::setOutputFile(ZMConfig::get("global", "runtime")["save_console_log_file"]);
}
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
date_default_timezone_set($timezone);
@@ -126,9 +131,9 @@ class Framework
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
if ($instant_mode) {
$this->loadServerEvents();
}
//if ($instant_mode) {
$this->loadServerEvents();
//}
$this->parseCliArgs(self::$argv, $add_port);
@@ -329,7 +334,8 @@ class Framework
}
}
private static function printMotd($tty_width) {
private static function printMotd($tty_width)
{
if (file_exists(DataProvider::getSourceRootDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getSourceRootDir() . "/config/motd.txt");
} else {
@@ -341,7 +347,118 @@ class Framework
echo $motd;
}
public function start() {
/**
* 将各进程的pid写入文件以备后续崩溃及僵尸进程处理使用
* @param int $type
* @param int|string $pid
* @param array $data
* @internal
*/
public static function saveProcessState(int $type, $pid, array $data = [])
{
switch ($type) {
case ZM_PROCESS_MASTER:
$file = _zm_pid_dir() . '/master.json';
$json = [
'pid' => intval($pid),
'stdout' => $data['stdout'],
'daemon' => $data['daemon']
];
file_put_contents($file, json_encode($json, JSON_UNESCAPED_UNICODE));
return;
case ZM_PROCESS_MANAGER:
$file = _zm_pid_dir() . '/manager.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_WORKER:
$file = _zm_pid_dir() . '/worker.' . $data['worker_id'] . '.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_USER:
$file = _zm_pid_dir() . '/user.' . $data['process_name'] . '.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_TASKWORKER:
$file = _zm_pid_dir() . '/taskworker.' . $data['worker_id'] . '.pid';
file_put_contents($file, strval($pid));
return;
}
}
/**
* 用于框架内部获取多进程运行状态的函数
* @param int $type
* @param null $id_or_name
* @return false|int|mixed
* @throws ZMKnownException
* @internal
*/
public static function getProcessState(int $type, $id_or_name = null)
{
$file = _zm_pid_dir();
switch ($type) {
case ZM_PROCESS_MASTER:
if (!file_exists($file . '/master.json')) return false;
$json = json_decode(file_get_contents($file . '/master.json'), true);
if ($json !== null) return $json;
else return false;
case ZM_PROCESS_MANAGER:
if (!file_exists($file . '/manager.pid')) return false;
return intval(file_get_contents($file . '/manager.pid'));
case ZM_PROCESS_WORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
if (!file_exists($file . '/worker.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/worker.' . $id_or_name . '.pid'));
case ZM_PROCESS_USER:
if (!is_string($id_or_name)) throw new ZMKnownException('E99999', 'process_name必须为字符串');
if (!file_exists($file . '/user.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/user.' . $id_or_name . '.pid'));
case ZM_PROCESS_TASKWORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
if (!file_exists($file . '/taskworker.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/taskworker.' . $id_or_name . '.pid'));
default:
return false;
}
}
/**
* @param int $type
* @param null $id_or_name
* @throws ZMKnownException
* @internal
*/
public static function removeProcessState(int $type, $id_or_name = null)
{
switch ($type) {
case ZM_PROCESS_MASTER:
$file = _zm_pid_dir() . '/master.json';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_MANAGER:
$file = _zm_pid_dir() . '/manager.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_WORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
$file = _zm_pid_dir() . '/worker.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_USER:
if (!is_string($id_or_name)) throw new ZMKnownException('E99999', 'process_name必须为字符串');
$file = _zm_pid_dir() . '/user.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_TASKWORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
$file = _zm_pid_dir() . '/taskworker.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
}
}
public function start()
{
try {
self::$loaded_files = get_included_files();
self::$server->start();
@@ -355,7 +472,8 @@ class Framework
/**
* @noinspection PhpIncludeInspection
*/
private function loadServerEvents() {
private function loadServerEvents()
{
if (Phar::running() !== "") {
ob_start();
include_once DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php";
@@ -380,7 +498,8 @@ class Framework
* @throws ReflectionException
* @throws ReflectionException
*/
private function registerServerEvents() {
private function registerServerEvents()
{
$reader = new AnnotationReader();
$all = ZMUtil::getClassesPsr4(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent");
foreach ($all as $v) {
@@ -397,6 +516,7 @@ class Framework
}
foreach (($this->setup_events["setup"] ?? []) as $v) {
Console::debug('Calling @OnSetup: ' . $v["class"]);
$c = ZMUtil::getModInstance($v["class"]);
$method = $v["method"];
$c->$method();
@@ -414,7 +534,8 @@ class Framework
* @param $args
* @param $add_port
*/
private function parseCliArgs($args, &$add_port) {
private function parseCliArgs($args, &$add_port)
{
$coroutine_mode = true;
global $terminal_id;
$terminal_id = uuidgen();
@@ -491,7 +612,8 @@ class Framework
else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL);
}
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border)
{
$tmp_line = $k . ": " . $v;
//Console::info("写入[".$tmp_line."]");
if (strlen($tmp_line) > $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
@@ -535,7 +657,8 @@ class Framework
}
}
public static function printProps($out, $tty_width, $colorful = true) {
public static function printProps($out, $tty_width, $colorful = true)
{
$max_border = $tty_width < 65 ? $tty_width : 65;
if (LOAD_MODE == 0) echo Console::setColor("* Framework started with source mode.\n", $colorful ? "yellow" : "");
echo str_pad("", $max_border, "=") . PHP_EOL;
@@ -577,20 +700,23 @@ class Framework
echo str_pad("", $max_border, "=") . PHP_EOL;
}
public static function getTtyWidth(): string {
public static function getTtyWidth(): string
{
$size = exec("stty size");
if (empty($size)) return 65;
return explode(" ", trim($size))[1];
}
public static function loadFrameworkState() {
public static function loadFrameworkState()
{
if (!file_exists(DataProvider::getDataFolder() . ".state.json")) return [];
$r = json_decode(file_get_contents(DataProvider::getDataFolder() . ".state.json"), true);
if ($r === null) $r = [];
return $r;
}
public static function saveFrameworkState($data) {
public static function saveFrameworkState($data)
{
return file_put_contents(DataProvider::getDataFolder() . ".state.json", json_encode($data, 64 | 128 | 256));
}
}

View File

@@ -119,7 +119,7 @@ class Response
* @return mixed
*/
public function header($key, $value, $ucwords = null) {
if (!$this->is_end) return $this->response->header($key, $value, $ucwords);
if (!$this->is_end) return $ucwords === null ? $this->response->header($key, $value) : $this->response->header($key, $value, $ucwords);
else return false;
}
@@ -130,7 +130,8 @@ class Response
* @return mixed
*/
public function setHeader($key, $value, $ucwords = null) {
return !$this->is_end ? $this->response->setHeader($key, $value, $ucwords) : false;
if (!$this->is_end) return $ucwords === null ? $this->response->setHeader($key, $value) : $this->response->setHeader($key, $value, $ucwords);
else return false;
}
/**

View File

@@ -94,7 +94,7 @@ class MySQLStatement implements IteratorAggregate, Statement
return $this->statement->rowCount();
}
public function getIterator()
public function getIterator(): StatementIterator
{
return new StatementIterator($this);
}

View File

@@ -4,120 +4,29 @@
namespace ZM\Utils\Manager;
use Swoole\Coroutine;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnPipeMessageEvent;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\WorkerCache;
use Swoole\Process;
class ProcessManager
{
public static function workerAction($src_worker_id, $data) {
$server = server();
switch ($data["action"] ?? '') {
case 'add_short_command':
Console::verbose("Adding short command " . $data["data"][0]);
$obj = new CQCommand();
$obj->method = quick_reply_closure($data["data"][1]);
$obj->match = $data["data"][0];
$obj->class = "";
EventManager::addEvent(CQCommand::class, $obj);
break;
case "eval":
eval($data["data"]);
break;
case "call_static":
call_user_func_array([$data["data"]["class"], $data["data"]["method"]], $data["data"]["params"]);
break;
case "save_persistence":
LightCache::savePersistence();
break;
case "resume_ws_message":
$obj = $data["data"];
Coroutine::resume($obj["coroutine"]);
break;
case "getWorkerCache":
$r = WorkerCache::get($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "setWorkerCache":
$r = WorkerCache::set($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "unsetWorkerCache":
$r = WorkerCache::unset($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "hasKeyWorkerCache":
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "asyncAddWorkerCache":
WorkerCache::add($data["key"], $data["value"], true);
break;
case "asyncSubWorkerCache":
WorkerCache::sub($data["key"], $data["value"], true);
break;
case "asyncSetWorkerCache":
WorkerCache::set($data["key"], $data["value"], true);
break;
case "asyncUnsetWorkerCache":
WorkerCache::unset($data["key"], true);
break;
case "addWorkerCache":
$r = WorkerCache::add($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "subWorkerCache":
$r = WorkerCache::sub($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "returnWorkerCache":
WorkerCache::$transfer[$data["cid"]] = $data["value"];
zm_resume($data["cid"]);
break;
default:
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
return $v->action == $data["action"];
});
$dispatcher->dispatchEvents($data);
break;
}
/** @var Process[] */
public static $user_process = [];
/**
* @param string $name
* @param callable $callable
* @return Process
*/
public static function createUserProcess(string $name, callable $callable): Process
{
return self::$user_process[$name] = new Process($callable);
}
public static function sendActionToWorker($worker_id, $action, $data) {
$obj = ["action" => $action, "data" => $data];
if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) {
Console::warning(zm_internal_errcode("E00022") . "Cannot send worker action from master or manager process!");
return;
}
if (server()->worker_id == $worker_id) {
self::workerAction($worker_id, $obj);
} else {
server()->sendMessage(json_encode($obj), $worker_id);
}
}
public static function resumeAllWorkerCoroutines() {
if (server()->worker_id === -1) {
Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!");
return;
}
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $v) {
if (isset($v["coroutine"], $v["worker_id"])) {
if (server()->worker_id == $v["worker_id"]) Coroutine::resume($v["coroutine"]);
}
}
/**
* @param string $string
* @return Process|null
*/
public static function getUserProcess(string $string): ?Process
{
return self::$user_process[$string] ?? null;
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace ZM\Utils\Manager;
use Exception;
use Swoole\Coroutine;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnPipeMessageEvent;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\WorkerCache;
class WorkerManager
{
/**
* Worker 进程间通信触发的动作类型函数
* @param $src_worker_id
* @param $data
* @throws Exception
*/
public static function workerAction($src_worker_id, $data)
{
$server = server();
switch ($data["action"] ?? '') {
case 'add_short_command':
Console::verbose("Adding short command " . $data["data"][0]);
$obj = new CQCommand();
$obj->method = quick_reply_closure($data["data"][1]);
$obj->match = $data["data"][0];
$obj->class = "";
EventManager::addEvent(CQCommand::class, $obj);
break;
case "eval":
eval($data["data"]);
break;
case "call_static":
call_user_func_array([$data["data"]["class"], $data["data"]["method"]], $data["data"]["params"]);
break;
case "save_persistence":
LightCache::savePersistence();
break;
case "resume_ws_message":
$obj = $data["data"];
Coroutine::resume($obj["coroutine"]);
break;
case "getWorkerCache":
$r = WorkerCache::get($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "setWorkerCache":
$r = WorkerCache::set($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "unsetWorkerCache":
$r = WorkerCache::unset($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "hasKeyWorkerCache":
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "asyncAddWorkerCache":
WorkerCache::add($data["key"], $data["value"], true);
break;
case "asyncSubWorkerCache":
WorkerCache::sub($data["key"], $data["value"], true);
break;
case "asyncSetWorkerCache":
WorkerCache::set($data["key"], $data["value"], true);
break;
case "asyncUnsetWorkerCache":
WorkerCache::unset($data["key"], true);
break;
case "addWorkerCache":
$r = WorkerCache::add($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "subWorkerCache":
$r = WorkerCache::sub($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "returnWorkerCache":
WorkerCache::$transfer[$data["cid"]] = $data["value"];
zm_resume($data["cid"]);
break;
default:
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
return $v->action == $data["action"];
});
$dispatcher->dispatchEvents($data);
break;
}
}
/**
* 给 Worker 进程发送动作指令(包括自身,自身将直接执行)
* @param $worker_id
* @param $action
* @param $data
* @throws Exception
*/
public static function sendActionToWorker($worker_id, $action, $data)
{
$obj = ["action" => $action, "data" => $data];
if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) {
Console::warning(zm_internal_errcode("E00022") . "Cannot send worker action from master or manager process!");
return;
}
if (server()->worker_id == $worker_id) {
self::workerAction($worker_id, $obj);
} else {
server()->sendMessage(json_encode($obj), $worker_id);
}
}
/**
* 向所有 Worker 进程发送动作指令
*/
public static function resumeAllWorkerCoroutines()
{
if (server()->worker_id === -1) {
Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!");
return;
}
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $v) {
if (isset($v["coroutine"], $v["worker_id"])) {
if (server()->worker_id == $v["worker_id"]) Coroutine::resume($v["coroutine"]);
}
}
}
}

View File

@@ -16,6 +16,7 @@ use ZM\Console\Console;
*/
class SignalListener
{
private static $manager_kill_time = 0;
/**
* 监听Master进程的Ctrl+C
* @param Server $server
@@ -29,6 +30,7 @@ class SignalListener
} else {
echo "\r";
Console::warning("Server interrupted(SIGINT) on Master.");
Console::warning("Server will be shutdown.");
Process::kill($server->master_pid, SIGTERM);
}
});
@@ -48,6 +50,7 @@ class SignalListener
} else {
Console::verbose("Interrupted in manager!");
}
self::processKillerPrompt();
};
Console::debug("Listening Manager SIGINT");
if (version_compare(SWOOLE_VERSION, "4.6.7") >= 0) {
@@ -65,15 +68,40 @@ class SignalListener
public static function signalWorker(Server $server, $worker_id) {
Console::debug("Listening Worker #".$worker_id." SIGINT");
Process::signal(SIGINT, function () use ($worker_id, $server) {
if ($server->master_pid == $server->worker_pid) {
if ($server->master_pid == $server->worker_pid) { // 当Swoole以单进程模型运行的时候Worker需要监听杀死的信号
echo "\r";
Console::warning("Server interrupted(SIGINT) on Worker.");
swoole_timer_after(2, function() {
Process::kill(posix_getpid(), SIGTERM);
});
self::processKillerPrompt();
}
//Console::verbose("Interrupted in worker");
// do nothing
});
}
/**
* 按5次Ctrl+C后强行杀死框架的处理函数
*/
private static function processKillerPrompt() {
if (self::$manager_kill_time > 0) {
if (self::$manager_kill_time >= 5) {
$file_path = _zm_pid_dir();
$flist = DataProvider::scanDirFiles($file_path, false, true);
foreach($flist as $file) {
$name = explode('.', $file);
if (end($name) == 'pid' && $name[0] !== 'manager') {
$pid = file_get_contents($file_path.'/'.$file);
Process::kill($pid, SIGKILL);
}
unlink($file_path.'/'.$file);
}
} else {
echo "\r";
Console::log("再按" . (5 - self::$manager_kill_time) . "次Ctrl+C所有Worker进程就会被强制杀死", 'red');
}
}
self::$manager_kill_time++;
}
}

View File

@@ -21,7 +21,8 @@ use Swoole\Coroutine\System;
use ZM\Context\ContextInterface;
function getClassPath($class_name) {
function getClassPath($class_name)
{
$dir = str_replace("\\", "/", $class_name);
$dir2 = DataProvider::getSourceRootDir() . "/src/" . $dir . ".php";
//echo "@@@".$dir2.PHP_EOL;
@@ -29,11 +30,13 @@ function getClassPath($class_name) {
if (file_exists($dir2)) return $dir2;
else return null;
}
/**
* 检查炸毛框架运行的环境
* @internal
*/
function _zm_env_check() {
function _zm_env_check()
{
if (!extension_loaded("swoole")) die(zm_internal_errcode("E00001") . "Can not find swoole extension.\n");
if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die(zm_internal_errcode("E00002") . "You must install swoole version >= 4.5.0 !");
if (version_compare(PHP_VERSION, "7.2") == -1) die(zm_internal_errcode("E00003") . "PHP >= 7.2 required.");
@@ -49,7 +52,8 @@ function _zm_env_check() {
* @param bool $ban_comma
* @return array
*/
function explodeMsg($msg, $ban_comma = false): array {
function explodeMsg($msg, $ban_comma = false): array
{
$msg = str_replace(" ", "\n", trim($msg));
if (!$ban_comma) {
//$msg = str_replace("", "\n", $msg);
@@ -65,13 +69,15 @@ function explodeMsg($msg, $ban_comma = false): array {
}
/** @noinspection PhpUnused */
function unicode_decode($str) {
function unicode_decode($str)
{
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) {
return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE");
}, $str);
}
function matchPattern($pattern, $context): bool {
function matchPattern($pattern, $context): bool
{
if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "")
return true;
if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1))
@@ -83,7 +89,8 @@ function matchPattern($pattern, $context): bool {
return false;
}
function split_explode($del, $str, $divide_en = false): array {
function split_explode($del, $str, $divide_en = false): array
{
$str = explode($del, $str);
for ($i = 0; $i < mb_strlen($str[0]); $i++) {
if (
@@ -115,7 +122,8 @@ function split_explode($del, $str, $divide_en = false): array {
return $ls == [] ? [""] : $ls;
}
function matchArgs($pattern, $context) {
function matchArgs($pattern, $context)
{
$result = [];
if (matchPattern($pattern, $context)) {
if (mb_strpos($pattern, "*") === false) return [];
@@ -145,19 +153,23 @@ function matchArgs($pattern, $context) {
} else return false;
}
function connectIsQQ(): bool {
function connectIsQQ(): bool
{
return ctx()->getConnection()->getName() == 'qq';
}
function connectIsDefault(): bool {
function connectIsDefault(): bool
{
return ctx()->getConnection()->getName() == 'default';
}
function connectIs($type): bool {
function connectIs($type): bool
{
return ctx()->getConnection()->getName() == $type;
}
function getAnnotations(): array {
function getAnnotations(): array
{
$s = debug_backtrace()[1];
//echo json_encode($s, 128|256);
$list = [];
@@ -172,9 +184,10 @@ function getAnnotations(): array {
return $list;
}
function set_coroutine_params($array) {
function set_coroutine_params($array)
{
$cid = Co::getCid();
if ($cid == -1) die(zm_internal_errcode("E00061")."Cannot set coroutine params at none coroutine mode.");
if ($cid == -1) die(zm_internal_errcode("E00061") . "Cannot set coroutine params at none coroutine mode.");
if (isset(Context::$context[$cid])) Context::$context[$cid] = array_merge(Context::$context[$cid], $array);
else Context::$context[$cid] = $array;
foreach (Context::$context as $c => $v) {
@@ -185,14 +198,16 @@ function set_coroutine_params($array) {
/**
* @return ContextInterface|null
*/
function context(): ?ContextInterface {
function context(): ?ContextInterface
{
return ctx();
}
/**
* @return ContextInterface|null
*/
function ctx(): ?ContextInterface {
function ctx(): ?ContextInterface
{
$cid = Co::getCid();
$c_class = ZMConfig::get("global", "context_class");
if (isset(Context::$context[$cid])) {
@@ -207,39 +222,47 @@ function ctx(): ?ContextInterface {
}
}
function onebot_target_id_name($message_type): string {
function onebot_target_id_name($message_type): string
{
return ($message_type == "group" ? "group_id" : "user_id");
}
function zm_sleep($s = 1): bool {
function zm_sleep($s = 1): bool
{
if (Coroutine::getCid() != -1) System::sleep($s);
else usleep($s * 1000 * 1000);
return true;
}
function zm_exec($cmd): array {
function zm_exec($cmd): array
{
return System::exec($cmd);
}
function zm_cid() {
function zm_cid()
{
return Co::getCid();
}
function zm_yield() {
function zm_yield()
{
Co::yield();
}
function zm_resume(int $cid) {
function zm_resume(int $cid)
{
Co::resume($cid);
}
function zm_timer_after($ms, callable $callable) {
function zm_timer_after($ms, callable $callable)
{
Swoole\Timer::after($ms, function () use ($callable) {
call_with_catch($callable);
});
}
function call_with_catch($callable) {
function call_with_catch($callable)
{
try {
$callable();
} catch (Exception $e) {
@@ -253,7 +276,8 @@ function call_with_catch($callable) {
}
}
function zm_timer_tick($ms, callable $callable) {
function zm_timer_tick($ms, callable $callable)
{
if (zm_cid() === -1) {
return go(function () use ($ms, $callable) {
Console::debug("Adding extra timer tick of " . $ms . " ms");
@@ -268,24 +292,39 @@ function zm_timer_tick($ms, callable $callable) {
}
}
function zm_go(callable $callable) {
function zm_go(callable $callable)
{
call_with_catch($callable);
}
function zm_data_hash($v): string {
function zm_data_hash($v): string
{
return md5($v["user_id"] . "^" . $v["self_id"] . "^" . $v["message_type"] . "^" . ($v[$v["message_type"] . "_id"] ?? $v["user_id"]));
}
function server(): ?Server {
function server(): ?Server
{
return Framework::$server;
}
/**
* 获取缓存当前框架pid的临时目录
* @return string
*/
function _zm_pid_dir(): string
{
global $_ZM_HASH;
if (!isset($_ZM_HASH)) $_ZM_HASH = md5(DataProvider::getWorkingDir());
return '/tmp/.zm_' . $_ZM_HASH;
}
/**
* @return OneBotV11
* @throws RobotNotFoundException
* @throws ZMException
*/
function bot() {
function bot()
{
if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) {
return OneBotV11::getRandom();
} elseif ($conn != -1) {
@@ -302,7 +341,8 @@ function bot() {
* @return array
* @author 854854321
*/
function getAllFdByConnectType(string $type = 'default'): array {
function getAllFdByConnectType(string $type = 'default'): array
{
$fds = [];
foreach (ManagerGM::getAllByName($type) as $obj) {
$fds[] = $obj->getFd();
@@ -310,11 +350,13 @@ function getAllFdByConnectType(string $type = 'default'): array {
return $fds;
}
function zm_atomic($name): ?Atomic {
function zm_atomic($name): ?Atomic
{
return ZMAtomic::get($name);
}
function uuidgen($uppercase = false): string {
function uuidgen($uppercase = false): string
{
try {
$data = random_bytes(16);
} catch (Exception $e) {
@@ -326,11 +368,13 @@ function uuidgen($uppercase = false): string {
vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
function working_dir() {
function working_dir()
{
return WORKING_DIR;
}
function zm_dump($var, ...$moreVars) {
function zm_dump($var, ...$moreVars)
{
VarDumper::dump($var);
foreach ($moreVars as $v) {
@@ -344,40 +388,49 @@ function zm_dump($var, ...$moreVars) {
return $var;
}
function zm_info($obj) {
function zm_info($obj)
{
Console::info($obj);
}
function zm_warning($obj) {
function zm_warning($obj)
{
Console::warning($obj);
}
function zm_success($obj) {
function zm_success($obj)
{
Console::success($obj);
}
function zm_debug($obj) {
function zm_debug($obj)
{
Console::debug($obj);
}
function zm_verbose($obj) {
function zm_verbose($obj)
{
Console::verbose($obj);
}
function zm_error($obj) {
function zm_error($obj)
{
Console::error($obj);
}
function zm_config($name, $key = null) {
function zm_config($name, $key = null)
{
return ZMConfig::get($name, $key);
}
function quick_reply_closure($reply) {
function quick_reply_closure($reply)
{
return function () use ($reply) {
return $reply;
};
}
function zm_internal_errcode($code): string {
function zm_internal_errcode($code): string
{
return "[ErrCode:$code] ";
}

View File

@@ -24,6 +24,11 @@ try {
$scan_paths[trim($k, "\\")] = $base_path . "/" . $v;
}
}
foreach (($composer["autoload-dev"]["psr-4"] ?? []) as $k => $v) {
if (is_dir($base_path . "/" . $v) && !in_array($v, $composer["extra"]["exclude_annotate"] ?? [])) {
$scan_paths[trim($k, "\\")] = $base_path . "/" . $v;
}
}
$all_event_class = [];
foreach ($scan_paths as $namespace => $autoload_path) {
$all_event_class = array_merge($all_event_class, ZMUtil::getClassesPsr4($autoload_path, $namespace));
@@ -61,3 +66,17 @@ try {
fclose($stderr);
exit(1);
}
/*
记迷惑,这里的代码是不是可以放到一个单独的文件里面,这样就不会出现每次都要重新加载的问题了?
然后这个文件就实现了,就是这个。
但是还有个什么问题呢?为了 reload 牺牲了太多太多,但是关键时刻好像又不是很能用到。
但又不能没有。
所以我很纠结很纠结。
如何让用户的代码能像 php-fpm 那样随时重置呢?
我不知道诶。
那这段代码干了个啥?
在最开始单独启动进程,加载一遍所有类,获取需要在启动前就执行的类,然后在启动的时候执行。
这样就可以不在爷进程里面加载所有类,在爹进程里面 Fork 的子进程再加载所有类,每次 reload 时可以重新加载了。
以上均为乱写的,请勿完全当真,本人对待框架代码还是比较认真的。
*/