start(); register_shutdown_function( function ($pid, $process) { swoole_process::kill($pid); $process->wait(); }, $pid, $process ); return $process; } function swoole_unittest_fork($func) { $process = new swoole_process($func, false, false); $process->start(); return $process; } function swoole_unittest_wait() { return swoole_process::wait(); } function makeTcpClient($host, $port, callable $onConnect = null, callable $onReceive = null) { $cli = new \swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); assert($cli->set([ 'open_length_check' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ])); $cli->on("connect", function (\swoole_client $cli) use ($onConnect) { assert($cli->isConnected() === true); if ($onConnect) { $onConnect($cli); } }); $cli->on("receive", function (\swoole_client $cli, $recv) use ($onReceive) { if ($onReceive) { $onReceive($cli, $recv); } }); $cli->on("error", function (\swoole_client $cli) { swoole_event_exit(); }); $cli->on("close", function (\swoole_client $cli) { swoole_event_exit(); }); $cli->connect($host, $port); } function opcode_encode($op, $data) { $r = json_encode([$op, $data]); assert(json_last_error() === JSON_ERROR_NONE); return pack("N", strlen($r) + 4) . $r; } function opcode_decode($raw) { $json = substr($raw, 4); $r = json_decode($json, true); assert(json_last_error() === JSON_ERROR_NONE); assert(is_array($r) && count($r) === 2); return $r; } function kill_self_and_descendant($pid) { if (PHP_OS === "Darwin") { return; } $pids = findDescendantPids($pid); foreach($pids as $pid) { posix_kill($pid, SIGKILL); } posix_kill($pid, SIGKILL); } /** * fork 一个进程把父进程pid通过消息队列传给子进程,延时把父进程干掉 * @param int $after * @param int $sig */ function killself_in_syncmode($lifetime = 1000, $sig = SIGKILL) { $proc = new \swoole_process(function(\swoole_process $proc) use($lifetime, $sig) { $pid = $proc->pop(); $proc->freeQueue(); usleep($lifetime * 1000); \swoole_process::kill($pid, $sig); $proc->exit(); }, true); $proc->useQueue(); $proc->push(posix_getpid()); $proc->start(); } /** * 异步模式用定时器干掉自己 * @param int $lifetime * @param int $sig * @param callable $cb */ function suicide($lifetime, $sig = SIGKILL, callable $cb = null) { swoole_timer_after($lifetime, function() use($lifetime, $sig, $cb) { if ($cb) { $cb(); } echo "suicide after $lifetime ms\n"; posix_kill(posix_getpid(), $sig); }); } // 查找某pid的所有子孙pid function findDescendantPids($pid) { list($pinfo, ) = pstree(); $y = function($pid) use(&$y, $pinfo) { if (isset($pinfo[$pid])) { list(, $childs) = $pinfo[$pid]; $pids = $childs; foreach($childs as $child) { $pids = array_merge($pids, $y($child)); } return $pids; } else { return []; } }; return $y($pid); } /** * @return array [pinfo, tree] * tree [ * ppid * [...child pids] * ] * list(ppid, array childs) = tree[pid] */ function pstree() { $pinfo = []; $iter = new DirectoryIterator("/proc"); foreach($iter as $item) { $pid = $item->getFilename(); if ($item->isDir() && ctype_digit($pid)) { $stat = file_get_contents("/proc/$pid/stat"); $info = explode(" ", $stat); $pinfo[$pid] = [intval($info[3]), []/*, $info*/]; } } foreach($pinfo as $pid => $info) { list($ppid, ) = $info; $ppid = intval($ppid); $pinfo[$ppid][1][] = $pid; } $y = function($pid, $path = []) use(&$y, $pinfo) { if (isset($pinfo[$pid])) { list($ppid, ) = $pinfo[$pid]; $ppid = $ppid; $path[] = $pid; return $y($ppid, $path); } else { return array_reverse($path); } }; $tree = []; foreach($pinfo as $pid => $info) { $path = $y($pid); $node = &$tree; foreach($path as $id) { if (!isset($node[$id])) { $node[$id] = []; } $node = &$node[$id]; } } return [$pinfo, $tree]; } function debug_log($str, $handle = STDERR) { if ($handle === STDERR) { $tpl = "\033[31m[%d %s] %s\033[0m\n"; } else { $tpl = "[%d %s] %s\n"; } if (is_resource($handle)) { fprintf($handle, $tpl, posix_getpid(), date("Y-m-d H:i:s", time()), $str); } else { printf($tpl, posix_getpid(), date("Y-m-d H:i:s", time()), $str); } } function arrayEqual(array $a, array $b, $strict = true) { if (($a && !$b) || (!$a && $b)) { return false; } if ($strict) { foreach ($a as $k => $v) { if (!array_key_exists($k, $b)) { return false; } if (gettype($v) !== gettype($b[$k])) { return false; } if (is_array($v) && arrayEqual($v, $b[$k]) === false) { return false; } } return true; } else { $aks = array_keys($a); $bks = array_keys($b); sort($aks); sort($bks); return $aks === $bks; } } function get_one_free_port() { $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $ok = socket_bind($socket, "0.0.0.0", 0); if (!$ok) { return false; } $ok = socket_listen($socket); if (!$ok) { return false; } $ok = socket_getsockname($socket, $addr, $port); if (!$ok) { return false; } socket_close($socket); return $port; } function start_server($file, $host, $port, $redirect_file = "/dev/null", $ext1 = null, $ext2 = null, $debug = false) { $php_executable = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; $cmd_args = getenv('TEST_PHP_ARGS'); $fdSpec = [ 0 => STDIN, 1 => STDOUT, 2 => STDERR, ]; /*if (substr(PHP_OS, 0, 3) == 'WIN') { $cmd = "$php_executable $cmd_args $file"; $opts = ["bypass_shell" => true, "suppress_errors" => true]; $handle = proc_open(addslashes($cmd), $fdSpec, $pipes, null, null, $opts); } else { $cmd = "exec $php_executable $file > $redirect_file 2>&1"; $handle = proc_open($cmd, $fdSpec, $pipes); }*/ // 必须加exec, 否咋proc_terminate结束不了server进程 !!!!!! if ($debug) { $cmd = "exec $php_executable $file $host $port $ext1 $ext2"; echo "[SHELL_EXEC]".$cmd."\n"; } else { $cmd = "exec $php_executable $file $host $port $ext1 $ext2 > $redirect_file 2>&1"; } // $cmd = "exec $php_executable $file $host $port"; $handle = proc_open($cmd, $fdSpec, $pipes); if ($handle === false) { exit(__FUNCTION__ . " fail"); } make_sure_server_listen_success: { $i = 0; $fp = null; while (($i++ < 30) && !($fp = @fsockopen($host, $port))) { usleep(10000); } if ($fp) { fclose($fp); } } // linux上有问题,client端事件循环还没起起来就会先调用这个shutdown回调, 结束了子进程 // 第二个shutdown_function swoole才会把子进程的事件循环起来 // register_shutdown_function(function() use($handle, $redirect_file) { // proc_terminate($handle, SIGTERM); // @unlink($redirect_file); // }); return function() use($handle, $redirect_file) { // @unlink($redirect_file); proc_terminate($handle, SIGTERM); swoole_event_exit(); exit(); }; } function fork_exec(callable $fn, $f_stdout = "/dev/null", $f_stderr = null) { $pid = pcntl_fork(); if ($pid < 0) { exit("fork fail"); } if ($pid === 0) { fclose(STDOUT); $STDOUT = fopen($f_stdout, "w"); if ($f_stderr !== null) { fclose(STDERR); $STDERR = fopen($f_stderr, "w"); } $fn(); exit; } pcntl_waitpid($pid, $status); } /** * spawn_exec * @param null|string $cmd command * @param null|string $input code * @param null|int $tv_sec timeout sec * @param null|int $tv_usec timeout usec * @param null|string $cwd change work dir * @param array|null $env env * @return array [out, err] */ function spawn_exec($cmd, $input = null, $tv_sec = null, $tv_usec = null, $cwd = null, array $env = null) { $out = $err = null; $winOpt = ['suppress_errors' => true, 'binary_pipes' => true]; $proc = proc_open($cmd, [ 0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"], ], $pipes, $cwd, $env, $winOpt); assert($proc !== false); if ($input !== null) { $n = fwrite($pipes[0], $input); if (strlen($input) !== $n) { goto closePipes; } } // 必须关闭 assert(fclose($pipes[0])); unset($pipes[0]); // 防止select立即返回, 消耗cpu assert(!($tv_sec === 0 && $tv_usec === 0)); while (true) { $r = $pipes; $w = null; $e = null; /* 隐藏被信号或者其他系统调用打断 产生的错误*/ set_error_handler(function() {}); $n = @stream_select($r, $w, $e, $tv_sec, $tv_usec); restore_error_handler(); if ($n === false) { break; } else if ($n === 0) { // 超时kill -9 assert(proc_terminate($proc, SIGKILL)); throw new \RuntimeException("exec $cmd time out"); } else if ($n > 0) { foreach ($r as $handle) { if ($handle === $pipes[1]) { $_ = &$out; } else if ($handle === $pipes[2]) { $_ = &$err; } else { $_ = ""; } $line = fread($handle, 8192); $isEOF = $line === ""; if ($isEOF) { break 2; } else { $_ .= $line; } } } } closePipes: foreach ($pipes as $fd => $pipe) { if (is_resource($pipe)) { @fclose($pipe); } unset($pipes[$fd]); } return [$out, $err]; } function parent_child($parentFunc, $childFunc) { $pid = pcntl_fork(); if ($pid < 0) { echo "ERROR"; exit; } if ($pid === 0) { $childFunc(); exit; } else { $parentFunc($pid); } } /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2017 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ class ProcessManager { /** * @var swoole_atomic */ protected $atomic; protected $alone = false; public $parentFunc; public $childFunc; public $async = false; protected $childPid; protected $parentFirst = false; function __construct() { $this->atomic = new swoole_atomic(0); } function setParent(callable $func) { $this->parentFunc = $func; } function parentFirst() { $this->parentFirst = true; } function childFirst() { $this->parentFirst = false; } function setChild(callable $func) { $this->childFunc = $func; } //等待信息 function wait() { $this->atomic->wait(); } //唤醒等待的进程 function wakeup() { $this->atomic->wakeup(); } function runParentFunc($pid = 0) { return call_user_func($this->parentFunc, $pid); } function runChildFunc() { return call_user_func($this->childFunc); } function fork($func) { $pid = pcntl_fork(); if ($pid > 0) { return $pid; } elseif ($pid < 0) { return false; } else { call_user_func($func); exit; } } /** * 杀死子进程 */ function kill() { if (!$this->alone) { swoole_process::kill($this->childPid); } } function run() { global $argv, $argc; if ($argc > 1) { if ($argv[1] == 'child') { $this->alone = true; return $this->runChildFunc(); } elseif ($argv[1] == 'parent') { $this->alone = true; return $this->runParentFunc(); } } $pid = pcntl_fork(); if ($this->parentFirst) { $this->atomic->set(0); } if ($pid < 0) { echo "ERROR"; exit; } //子进程 elseif ($pid === 0) { //等待父进程 if ($this->parentFirst) { $this->wait(); } $this->runChildFunc(); exit; } //父进程 else { $this->childPid = $pid; //子进程优先运行,父进程进入等待状态 if (!$this->parentFirst) { $this->wait(); } $this->runParentFunc($pid); if ($this->async) { swoole_event::wait(); } pcntl_waitpid($pid, $status); } } } /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2017 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ class ServerManager { protected $host; protected $file; public $port; function __construct($file) { if (!is_file($file)) { throw new \Exception("server file [$file] not exists."); } $this->file = $file; } function listen($host = '127.0.0.1', $port = 0) { $this->port = $port == 0 ? get_one_free_port() : $port; $this->host = $host; } function run($debug = false) { return start_server($this->file, $this->host, $this->port, "/dev/null", null, null, $debug); } } function debug($var) { var_dump($var); exit; }