From d21f67dd2fa4830dc4cc39db7a93430b978e05aa Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 21 Nov 2018 23:43:43 +0800
Subject: [PATCH 001/146] 0.12.0

---
 CMakeLists.txt       |  67 +++++++++++
 README.md            |  56 +++++++++
 src/command.h        |  24 ++++
 src/controller.cpp   | 265 +++++++++++++++++++++++++++++++++++++++++++
 src/controller.h     |  63 ++++++++++
 src/core.cpp         |  34 ++++++
 src/core.h           |   9 ++
 src/coroutine.cpp    |  82 +++++++++++++
 src/coroutine.h      |  76 +++++++++++++
 src/flame.cpp        |  43 +++++++
 src/time/time.cpp    |  24 ++++
 src/time/time.h      |   7 ++
 src/vendor.h         |  46 ++++++++
 test/exception_1.php |  10 ++
 test/sleep_1.php     |  24 ++++
 15 files changed, 830 insertions(+)
 create mode 100644 CMakeLists.txt
 create mode 100644 README.md
 create mode 100644 src/command.h
 create mode 100644 src/controller.cpp
 create mode 100644 src/controller.h
 create mode 100644 src/core.cpp
 create mode 100644 src/core.h
 create mode 100644 src/coroutine.cpp
 create mode 100644 src/coroutine.h
 create mode 100644 src/flame.cpp
 create mode 100644 src/time/time.cpp
 create mode 100644 src/time/time.h
 create mode 100644 src/vendor.h
 create mode 100644 test/exception_1.php
 create mode 100644 test/sleep_1.php

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c5ccafb
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,67 @@
+cmake_minimum_required(VERSION 3.10)
+
+# 依赖项目录
+set(VENDOR_GCC /data/vendor/gcc-8.2.0)
+set(VENDOR_LLVM /data/vendor/llvm-7.0.0)
+set(VENDOR_PHP /data/vendor/php-7.2.11)
+execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
+    COMMAND sed "s/ *-I/;/g"
+    OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
+    OUTPUT_STRIP_TRAILING_WHITESPACE)
+set(VENDOR_PHPEXT /data/vendor/phpext-1.1.0)
+set(VENDOR_PARSER /data/vendor/parser-1.0.0)
+set(VENDOR_BOOST /data/vendor/boost-1.68.0)
+set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
+set(VENDOR_MONGODB /data/vendor/mongoc-1.13.0)
+set(VENDOR_AMQP /data/vendor/amqpcpp-4.0.0)
+set(VENDOR_RDKAFKA /data/vendor/rdkafka-0.11.6)
+# 基本参数
+set(CMAKE_BUILD_TYPE "Debug")
+# set(CMAKE_C_COMPILER /data/vendor/llvm-7.0.0/bin/clang)
+# set(CMAKE_CXX_COMPILER /data/vendor/llvm-7.0.0/bin/clang++)
+set(CMAKE_C_COMPILER /data/vendor/gcc-8.2.0/bin/gcc)
+set(CMAKE_CXX_COMPILER /data/vendor/gcc-8.2.0/bin/g++)
+set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
+# 项目
+project(FLAME)
+execute_process(COMMAND find ${CMAKE_SOURCE_DIR}/src -name *.cpp
+    COMMAND tr "\n" ";"
+    OUTPUT_VARIABLE SOURCES)
+add_library(FLAME SHARED ${SOURCES})
+set_target_properties(FLAME PROPERTIES
+#   COMPILE_FLAGS "--gcc-toolchain=${VENDOR_GCC} -std=c++17"
+   COMPILE_FLAGS "-std=c++17"
+   LINK_FLAGS "-static-libstdc++"
+   PREFIX ""
+   OUTPUT_NAME "flame")
+# 包含路径
+target_include_directories(FLAME SYSTEM PRIVATE
+    ${VENDOR_PHP_INCLUDES}
+    ${VENDOR_BOOST}/include
+    ${VENDOR_PHPEXT}/include
+    ${VENDOR_PARSER}/include
+    ${VENDOR_MYSQL}/include
+    ${VENDOR_MONGODB}/include/libmongoc-1.0
+    ${VENDOR_MONGODB}/include/libbson-1.0
+    ${VENDOR_AMQP}/include
+    ${VENDOR_RDKAFKA}/include
+)
+# 链接库
+target_link_libraries(FLAME
+    ${VENDOR_PHPEXT}/lib/libphpext.a
+    ${VENDOR_MYSQL}/lib/libmysqlclient.a
+    ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
+    ${VENDOR_MONGODB}/lib/libbson-static-1.0.a
+    ${VENDOR_AMQP}/lib/libamqpcpp.a
+    ${VENDOR_RDKAFKA}/lib/librdkafka.a
+    ${VENDOR_BOOST}/lib/libboost_program_options.a
+    ${VENDOR_BOOST}/lib/libboost_context.a
+    ${VENDOR_BOOST}/lib/libboost_system.a
+    ${VENDOR_BOOST}/lib/libboost_thread.a
+    ${VENDOR_BOOST}/lib/libboost_filesystem.a
+    sasl2
+    ssl
+    crypto
+    pthread
+    rt
+)
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..03d5d51
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+### 依赖库
+
+#### PHP
+``` Bash
+./configure  --prefix=/data/vendor/php-7.2.11 --with-config-file-path=/data/vendor/php-7.0.32/etc --disable-fpm --disable-phar --disable-dom --disable-libxml --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-openssl --with-readline --enable-mbstring --without-pear
+```
+#### Boost
+
+``` Bash
+./bootstrap.sh --prefix=/data/vendor/boost-1.68.0
+./b2 --prefix=/data/vendor/boost-1.68.0 cxxflags=-fPIC variant=release link=static threading=multi install
+```
+
+#### libphpext
+``` Bash
+CXXFLAGS="-O2" make -j4
+make install
+```
+
+#### cpp-parser
+``` Bash
+make install
+```
+
+#### mysql-connector-c v6.1.11
+``` Bash
+mv mysql-connector-c-6.1.11-src/ /data/vendor/mysqlc-6.1.11
+rm /data/vendor/mysqlc-6.1.11/lib/*.so*
+```
+
+#### mongoc-driver
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.13.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+make
+make install
+rm /data/vendor/mongoc-1.13.0/lib/*.so*
+```
+
+#### AMQP-CPP
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.0.0 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release ../
+make
+make install
+rm /data/vendor/amqpcpp-4.0.0/lib/*.so*
+```
+
+#### Rdkafka
+``` Bash
+./configure --prefix=/data/vendor/rdkafka-0.11.6
+make
+make install
+rm /data/vendor/rdkafka-0.11.6/lib/*.so*
+```
+
diff --git a/src/command.h b/src/command.h
new file mode 100644
index 0000000..e32242c
--- /dev/null
+++ b/src/command.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "coroutine.h"
+
+namespace flame {
+    class command {
+    public:
+        command(coroutine_handler &ch)
+        : ch_(ch) {}
+        // 1. boost::asio::post(gcontroller->context_y, .....);
+        // 2. ch_.suspend();
+        virtual void execute() = 0;
+
+        virtual void finish() {
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch_));
+        }
+    protected:
+        coroutine_handler& ch_;
+    };
+}
+
+
+auto cmd = std::make_shared<command_xxx>(ch, xxxx);
+cmd->execute();
+php::object = .,..... cmd->xxxxx 
diff --git a/src/controller.cpp b/src/controller.cpp
new file mode 100644
index 0000000..efe35b7
--- /dev/null
+++ b/src/controller.cpp
@@ -0,0 +1,265 @@
+#include "controller.h"
+#include "coroutine.h"
+
+namespace flame {
+// 全局控制器
+std::unique_ptr<controller> gcontroller;
+// 当前协程
+// std::shared_ptr<coroutine> coroutine::current;
+coroutine* coroutine::current;
+// 统一协程上下文
+coroutine::php_context_t coroutine::php_context;
+
+controller::controller()
+	: type(process_type::UNKNOWN)
+	, env(boost::this_process::environment())
+	, status(controller_status::UNKNOWN) {
+
+}
+
+void controller::initialize() {
+	// 使用此环境变量区分父子进程
+	// if (env.count("FLAME_PROCESS_WORKER") > 0)
+	// {
+		type = process_type::WORKER;
+		worker_.reset(new controller_worker());
+	// }
+	// else
+	// {
+	// 	type = process_type::MASTER;
+	// 	master_.reset(new controller_master());
+	// }
+	for (auto fn : init_cb) {
+		fn();
+	}
+}
+
+void controller::run() {
+	// if(type == process_type::WORKER) {
+		worker_->run();
+	// }else{
+		// master_->run();
+	// }
+}
+
+controller_master::controller_master()
+	: signal_(gcontroller->context_x/*, SIGINT, SIGTERM*/)
+{
+}
+
+void controller_master::run() {
+	// 主进程的启动过程:
+	// 1. 新增环境变量及命令行参数, 启动子进程;
+	// 2. 侦听子进程退出动作，按状态标志进行拉起；
+	// 3. 等待所有子进程输出, 并进行日志数据重定向(日志配置);
+	// 4. 监听信号进行日志重载或停止
+	// signal_.async_wait([this](const boost::system::error_code &error, int signal) {
+
+	// });
+	// 5. 启动 context_x 运行
+	gcontroller->context_x.run();
+	std::cout << "master: done\n";
+}
+
+controller_worker::controller_worker()
+	: signal_(gcontroller->context_x/*, SIGINT, SIGTERM*/)
+{
+	
+}
+
+void controller_worker::run() {
+	// 子进程的启动过程:
+	// 1. 监听信号进行日志重载或停止
+	// signal_.async_wait([this] (const boost::system::error_code& error, int signal) {
+		
+	// });
+	// 2. 启动线程池, 并使用线程池运行 context_y
+	thread_.resize(4);
+	for(int i=0;i<4;++i) {
+		thread_[i] = std::thread([this] {
+			gcontroller->context_y.run();
+		});
+	}
+	// 3. 启动 context_x 运行
+	gcontroller->context_x.run();
+}
+
+
+	// void controller::init(const std::string& title) {
+	// 	environ["FLAME_PROCESS_TITLE"] = title;
+	// 	if(type == MASTER) {
+	// 		php::callable("cli_set_process_title").call({ title + " (flame/m)" });
+	// 		int count = 1;
+	// 		if(options.exists("worker")) {
+	// 			count = std::min(256, std::max((int)options.get("worker").to_integer(), 1));
+	// 		}
+	// 		mworker_.resize(count);
+	// 	}else{
+	// 		php::callable("cli_set_process_title").call({ title + " (flame/" + environ["FLAME_PROCESS_WORKER"].to_string() + ")" });
+	// 	}
+	// 	php::value debug = options.get("debug");
+	// 	if(!debug.typeof(php::TYPE::UNDEFINED) && debug.empty()) {
+	// 		status |= STATUS_AUTORESTART;
+	// 	}
+	// 	status |= STATUS_INITIALIZED;
+	// }
+	// controller* controller::before(std::function<void ()> fn) {
+	// 	before_.push_back(fn);
+	// 	return this;
+	// }
+	// controller* controller::after(std::function<void ()> fn) {
+	// 	after_.push_back(fn);
+	// 	return this;
+	// }
+	// void controller::before() {
+	// 	for(auto fn: before_) {
+	// 		fn();
+	// 	}
+	// }
+	// void controller::after() {
+	// 	for(auto fn: after_) {
+	// 		fn();
+	// 	}
+	// }
+	// void controller::run() {
+	// 	status |= STATUS_STARTED;
+	// 	switch(type) {
+	// 	case MASTER:
+	// 		master_run();
+	// 		break;
+	// 	case WORKER:
+	// 		worker_run();
+	// 		break;
+	// 	default:
+	// 		assert(0 && "未知进程类型");
+	// 	}
+	// }
+	// static std::string build_command_line() {
+	// 	php::stream_buffer sb;
+	// 	std::ostream os(&sb);
+	// 	os << php::constant("PHP_BINARY");
+	// 	php::array argv = php::server("argv");
+	// 	for(auto i=argv.begin(); i!=argv.end(); ++i) {
+	// 		os << " " << i->second;
+	// 	}
+	// 	return std::string(sb.data(), sb.size());
+	// }
+	// void controller::spawn(int i) {
+	// 	std::string cmd = build_command_line();
+
+	// 	boost::process::environment env = environ;
+	// 	env["FLAME_PROCESS_WORKER"] = std::to_string(i+1);
+	// 	mworker_[i] = boost::process::child(cmd, env, mg_, context,
+	// 		boost::process::std_out > stdout,
+	// 		boost::process::std_err > stdout,
+	// 		boost::process::on_exit = [this, i] (int exit_code, const std::error_code& error) {
+	// 			if(error.value() == static_cast<int>(std::errc::no_child_process)) return;
+	// 			if(exit_code != 0 && (status & STATUS_AUTORESTART)) {
+	// 				// 日志记录
+	// 				log::logger_->write(boost::format(" %2% [%1%] (WARN) worker unexpected exit, restart in 3s ...") % time::datetime());
+	// 				auto tm = std::make_shared<boost::asio::steady_timer>(context, std::chrono::seconds(3));
+	// 				tm->async_wait([this, tm, i] (const boost::system::error_code& error) {
+	// 					if(!error) spawn(i);
+	// 				});
+	// 			}else{
+	// 				for(int i=0;i<mworker_.size();++i) {
+	// 					if(mworker_[i].running()) return;
+	// 				}
+	// 				context.stop();
+	// 			}
+	// 		});
+	// }
+	// void controller::master_run() {
+	// 	before();
+	// 	for(int i=0; i<mworker_.size(); ++i) {
+	// 		spawn(i);
+	// 	}
+	// 	// 主进程负责进程监控, 转发信号
+	// 	ms_.async_wait([this] (const boost::system::error_code& error, int sig) {
+	// 		// 日志记录
+	// 		log::logger_->write(boost::format(" %3% [%1%] (INFO) master receives signal '%2%', exiting ...") % time::datetime() % sig);
+	// 		signal_ = sig;
+	// 		for(int i=0; i<mworker_.size(); ++i) {
+	// 			::kill(mworker_[i].id(), sig);
+	// 		}
+	// 		context.stop();
+	// 	});
+	// 	context.run();
+	// 	master_shutdown();
+	// }
+	// void controller::worker_run() {
+	// 	before();
+	// 	// boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work(context_ex.get_executor());
+	// 	ws_.async_wait([this] (const boost::system::error_code& error, int sig) {
+	// 		signal_ = sig;
+	// 		context.stop();
+	// 	});
+	// 	// 辅助工作线程
+	// 	wworker_.resize(4);
+	// 	for(int i=0;i<4;++i) {
+	// 		wworker_[i] = std::thread([this] {
+	// 			context_ex.run();
+	// 		});
+	// 	}
+	// 	// 工作进程负责执行
+	// 	try{
+	// 		context.run();
+
+	// 		php::exception::rethrow();
+	// 	}catch(...) {
+	// 		exception = std::current_exception();
+	// 	}
+	// 	worker_shutdown();
+	// }
+	// void controller::shutdown() {
+	// 	switch(type) {
+	// 	case MASTER:
+	// 		master_shutdown();
+	// 		break;
+	// 	case WORKER:
+	// 		worker_shutdown();
+	// 		break;
+	// 	default:
+	// 		assert(0 && "未知进程类型");
+	// 	}
+	// }
+	// void controller::master_shutdown() {
+	// 	if(status & STATUS_SHUTDOWN) return;
+	// 	status |= STATUS_SHUTDOWN;
+
+	// 	options = nullptr;
+	// 	context.stop();
+	// 	context_ex.stop();
+	// 	// 这里有两种情况:
+	// 	// 1. 所有子进程自主退出;
+	// 	// 2. 子进程还未退出, 一段时间后强制结束;
+	// 	std::error_code error;
+	// 	if(!mg_.wait_for(std::chrono::seconds(10), error)) {
+	// 		mg_.terminate(); // 10s 后还未结束, 强制杀死
+	// 	}
+	// 	after();
+	// 	exit(0);
+	// }
+	// void controller::worker_shutdown() {
+	// 	if(status & STATUS_SHUTDOWN) return;
+	// 	status |= STATUS_SHUTDOWN;
+
+	// 	options = nullptr;
+	// 	context.stop();
+	// 	context_ex.stop();
+	// 	ws_.cancel();
+	// 	for(int i=0;i<wworker_.size();++i) {
+	// 		if(wworker_[i].joinable()) {
+	// 			wworker_[i].join();
+	// 		}
+	// 	}
+	// 	after();
+	// 	// 停止所有还在运行的协程
+	// 	coroutine::shutdown();
+	// 	// !!! 发生异常退出, 防止 PHP 引擎将还存活的对象内存提前释放
+	// 	if(exception) exit(-99);
+	// 	if(signal_ == SIGINT) exit(0);
+	// 	exit(0);
+	// }
+
+}
diff --git a/src/controller.h b/src/controller.h
new file mode 100644
index 0000000..faf65f6
--- /dev/null
+++ b/src/controller.h
@@ -0,0 +1,63 @@
+#pragma once
+#include "vendor.h"
+
+namespace flame {
+	class controller_master {
+	public:
+		controller_master();
+		void run();
+
+	  private:
+		std::vector<boost::process::child> worker_;
+		boost::process::group               group_;
+		boost::asio::signal_set            signal_;
+
+	};
+	class controller_worker {
+	public:
+		controller_worker();
+		void run();
+
+	  private:
+		std::vector<std::thread>           thread_;
+		boost::asio::signal_set            signal_;
+
+		
+	};
+	class controller {
+	public:
+	  boost::asio::io_context context_x;
+	  boost::asio::io_context context_y;
+
+	  enum class process_type
+	  {
+		  UNKNOWN = 0,
+		  MASTER = 1,
+		  WORKER = 2,
+	  } type;
+	  boost::process::environment env;
+	  boost::program_options::variables_map options;
+	  enum class controller_status
+	  {
+		  UNKNOWN = 0,
+	  } status;
+	private:
+		// 核心进程对象
+		std::unique_ptr<controller_master> master_;
+		// 工作进程对象
+		std::unique_ptr<controller_worker> worker_;
+		
+		// 公共
+		std::list<std::function<void ()>>  init_cb;
+		std::list<std::function<void ()>>  stop_cb;
+	public:
+		controller();
+		controller(const controller& c) = delete;
+		void initialize();
+		void run();
+		controller* on_init(std::function<void ()> fn);
+		controller* on_stop(std::function<void ()> fn);
+	};
+
+	extern std::unique_ptr<controller> gcontroller;
+}
diff --git a/src/core.cpp b/src/core.cpp
new file mode 100644
index 0000000..1b894f5
--- /dev/null
+++ b/src/core.cpp
@@ -0,0 +1,34 @@
+#include "controller.h"
+#include "coroutine.h"
+#include "core.h"
+
+namespace flame {
+    void declare(php::extension_entry &ext) {
+        ext
+            .on_module_startup([](php::extension_entry &ext) -> bool {
+                gcontroller.reset(new controller());
+                return true;
+            })
+            .function<init>("flame\\init", {
+                {"process_name", php::TYPE::STRING},
+            })
+            .function<go>("flame\\go", {
+                {"coroutine", php::TYPE::CALLABLE},
+            })
+            .function<run>("flame\\run");
+    }
+    php::value init(php::parameters& params) {
+        gcontroller->initialize();
+        return nullptr;
+    }
+    php::value go(php::parameters& params) {
+        php::callable fn = params[0];
+        coroutine::start(fn);
+        return nullptr;
+    }
+    
+    php::value run(php::parameters& params) {
+        gcontroller->context_x.run();
+        return nullptr;
+    }
+}
\ No newline at end of file
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..a374078
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "vendor.h"
+
+namespace flame {
+    void declare(php::extension_entry& ext);
+    php::value init(php::parameters& params);
+    php::value go(php::parameters& params);
+    php::value run(php::parameters& params);
+}
\ No newline at end of file
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
new file mode 100644
index 0000000..37cdc36
--- /dev/null
+++ b/src/coroutine.cpp
@@ -0,0 +1,82 @@
+#include "controller.h"
+#include "coroutine.h"
+
+namespace flame
+{
+    void coroutine::save_context(php_context_t &ctx)
+    {
+        ctx.vm_stack = EG(vm_stack);
+        ctx.vm_stack_top = EG(vm_stack_top);
+        ctx.vm_stack_end = EG(vm_stack_end);
+        //   ctx.scope = EG(fake_scope);
+        ctx.current_execute_data = EG(current_execute_data);
+    }
+    void coroutine::restore_context(php_context_t &ctx)
+    {
+        EG(vm_stack) = ctx.vm_stack;
+        EG(vm_stack_top) = ctx.vm_stack_top;
+        EG(vm_stack_end) = ctx.vm_stack_end;
+        // EG(fake_scope) = ctx.scope;
+        EG(current_execute_data) = ctx.current_execute_data;
+    }
+    void coroutine::start(php::callable fn)
+    {
+        auto co = std::make_shared<coroutine>(std::move(fn));
+        boost::asio::post(gcontroller->context_x, [co] {
+            // co->c1_ = boost::context::callcc([co] (auto &&cc) {
+            co->c1_ = boost::context::fiber([co](boost::context::fiber &&cc) {
+                // 启动进入协程
+                coroutine::current = co.get();
+                zend_vm_stack_init();
+                co->c2_ = std::move(cc);
+                // 协程运行
+                co->fn_.call();
+                // 协程运行完毕
+                zend_vm_stack_destroy();
+                coroutine::current = nullptr;
+                return std::move(co->c2_);
+            });
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+    coroutine::coroutine(php::callable &&fn)
+        : fn_(std::move(fn)), c1_(), c2_() {}
+
+    void coroutine::suspend()
+    {
+        // 保存 PHP 堆栈
+        coroutine::save_context(php_);
+        // 离开协程
+        coroutine::current = nullptr;
+        c2_ = std::move(c2_).resume();
+    }
+
+    void coroutine::resume()
+    {
+        // 恢复 PHP 堆栈
+        coroutine::restore_context(php_);
+        // 恢复进入协程
+        coroutine::current = this;
+        c1_ = std::move(c1_).resume();
+    }
+
+    coroutine_handler::coroutine_handler(coroutine *co)
+        : co_(co)
+    {
+    }
+    coroutine_handler::~coroutine_handler()
+    {
+        // std::cout << "~coroutine_handler\n";
+    }
+    void coroutine_handler::operator()(const boost::system::error_code &e, std::size_t n)
+    {
+        error = e;
+        nsize = n;
+        co_->resume();
+        // boost::asio::post(co_->sq_, std::bind(&coroutine::resume, co_));
+    }
+    void coroutine_handler::suspend()
+    {
+        co_->suspend();
+    }
+}
\ No newline at end of file
diff --git a/src/coroutine.h b/src/coroutine.h
new file mode 100644
index 0000000..dc3ee2b
--- /dev/null
+++ b/src/coroutine.h
@@ -0,0 +1,76 @@
+#pragma once
+#include "vendor.h"
+
+namespace flame {
+	class coroutine : public std::enable_shared_from_this<coroutine> {
+	public:
+		struct php_context_t
+		{
+			zend_vm_stack vm_stack;
+			zval *vm_stack_top;
+			zval *vm_stack_end;
+			zend_class_entry *scope;
+			zend_execute_data *current_execute_data;
+		};
+		static coroutine::php_context_t   php_context;
+		// 当前协程
+		static coroutine* current;
+		static void save_context(php_context_t &ctx);
+		static void restore_context(php_context_t& ctx);
+		static void start(php::callable fn);
+		coroutine(php::callable&& fn);
+
+		void suspend();
+		void resume();
+		php::callable fn_;
+		// boost::context::continuation c1_;
+		// boost::context::continuation c2_;
+		boost::context::fiber c1_;
+		boost::context::fiber c2_;
+
+
+		php_context_t php_;
+	};
+
+	struct coroutine_handler
+	{
+	public:
+		// coroutine_handler(std::shared_ptr<coroutine> co)
+		coroutine_handler(coroutine *co);
+		~coroutine_handler();
+		void operator()(const boost::system::error_code& e, std::size_t n = 0);
+		void suspend();
+		boost::system::error_code error;
+		std::size_t 	          nsize;
+		coroutine *co_;
+
+		friend bool asio_handler_is_continuation(coroutine_handler *ch)
+		{
+			std::cout << "continuation\n";
+			return true;
+		}
+
+	  private:
+		
+	};
+} // namespace flame
+
+namespace boost::asio
+{
+
+	template <>
+	class async_result<::flame::coroutine_handler>
+	{
+	public:
+		explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
+			
+		}
+		using type = void;
+		void get()
+		{
+			ch_.suspend();
+		}
+	private:
+		::flame::coroutine_handler &ch_;
+	};
+} // namespace boost::asio
\ No newline at end of file
diff --git a/src/flame.cpp b/src/flame.cpp
new file mode 100644
index 0000000..a4b0668
--- /dev/null
+++ b/src/flame.cpp
@@ -0,0 +1,43 @@
+#include "vendor.h"
+#include "core.h"
+#include "time/time.h"
+
+extern "C"
+{
+    ZEND_DLEXPORT zend_module_entry *get_module()
+    {
+        static php::extension_entry ext(EXTENSION_NAME, EXTENSION_VERSION);
+        std::string sapi = php::constant("PHP_SAPI");
+        if (sapi != "cli")
+        {
+            std::cerr << "Flame can only be using in SAPI='cli' mode\n";
+            return ext;
+        }
+
+        php::class_entry<php::closure> class_closure("flame\\closure");
+        class_closure.method<&php::closure::__invoke>("__invoke");
+        ext.add(std::move(class_closure));
+
+        ext
+            .desc({"vendor/boost", BOOST_LIB_VERSION})
+            .desc({"vendor/libphpext", PHPEXT_LIB_VERSION})
+            .desc({"vendor/amqpcpp", "4.0.0"})
+            .desc({"vendor/mysqlc", PACKAGE_VERSION})
+            .desc({"vendor/librdkafka", rd_kafka_version_str()})
+            .desc({"vendor/mongoc", MONGOC_VERSION_S});
+
+        flame::declare(ext);
+        flame::time::declare(ext);
+        // flame::os::declare(ext);
+        // flame::log::declare(ext);
+        // flame::udp::declare(ext);
+        // flame::tcp::declare(ext);
+        // flame::http::declare(ext);
+        // flame::redis::declare(ext);
+        // flame::rabbitmq::declare(ext);
+        // flame::mysql::declare(ext);
+        // flame::mongodb::declare(ext);
+        // flame::kafka::declare(ext);
+        return ext;
+    }
+};
\ No newline at end of file
diff --git a/src/time/time.cpp b/src/time/time.cpp
new file mode 100644
index 0000000..772cc0d
--- /dev/null
+++ b/src/time/time.cpp
@@ -0,0 +1,24 @@
+#include "time.h"
+#include "../controller.h"
+#include "../coroutine.h"
+
+namespace flame::time {
+
+
+static php::value sleep(php::parameters& params) {
+    coroutine_handler yield {coroutine::current};
+    boost::asio::steady_timer tm(gcontroller->context_x);
+    int ms = static_cast<int>(params[0]);
+    // std::cout << "sleep: " << ms << std::endl;
+    tm.expires_from_now(std::chrono::milliseconds(ms));
+    tm.async_wait(yield);
+    return nullptr;
+}
+
+void declare(php::extension_entry& ext) {
+    ext.function<sleep>("flame\\time\\sleep", {
+        {"duration", php::TYPE::INTEGER},
+    });
+}
+
+}
\ No newline at end of file
diff --git a/src/time/time.h b/src/time/time.h
new file mode 100644
index 0000000..a464dd1
--- /dev/null
+++ b/src/time/time.h
@@ -0,0 +1,7 @@
+#include "../vendor.h"
+
+namespace flame::time {
+
+void declare(php::extension_entry &ext);
+
+}
\ No newline at end of file
diff --git a/src/vendor.h b/src/vendor.h
new file mode 100644
index 0000000..86bfe8f
--- /dev/null
+++ b/src/vendor.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#define EXTENSION_NAME "flame"
+#define EXTENSION_VERSION "0.12.0"
+
+#include <cstdio>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+
+#include <limits>
+#include <algorithm>
+#include <functional> // for std::functional
+#include <cctype>
+#include <utility>
+#include <memory>
+#include <stack>
+#include <unordered_set>
+#include <phpext/phpext.h>
+#include <parser/separator_parser.hpp>
+#include <parser/multipart_parser.hpp>
+#include <boost/version.hpp>
+#include <boost/core/null_deleter.hpp>
+#include <boost/context/continuation.hpp>
+#include <boost/context/fiber.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/format.hpp>
+#include <boost/program_options.hpp>
+#include <boost/process.hpp>
+#include <boost/interprocess/ipc/message_queue.hpp>
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+#include <boost/asio/coroutine.hpp>
+using boost::asio::ip::tcp;
+using boost::asio::ip::udp;
+namespace ssl = boost::asio::ssl;
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <amqpcpp.h>
+#include <amqpcpp/libboostasio.h>
+#include <mysql.h>
+#include <my_sys.h>
+#include <mongoc.h>
+#include <librdkafka/rdkafka.h>
diff --git a/test/exception_1.php b/test/exception_1.php
new file mode 100644
index 0000000..0be768e
--- /dev/null
+++ b/test/exception_1.php
@@ -0,0 +1,10 @@
+<?php
+flame\init("exception_1");
+
+flame\go(function() {
+    flame\time\sleep(1000);
+    throw new Exception("Aaaaaaa");
+});
+
+
+flame\run();
diff --git a/test/sleep_1.php b/test/sleep_1.php
new file mode 100644
index 0000000..ebe30c3
--- /dev/null
+++ b/test/sleep_1.php
@@ -0,0 +1,24 @@
+<?php
+flame\init("sleep_1");
+
+function mySleep1() {
+    mySleep2(rand(10, 30));
+}
+
+function mySleep2($ms) {
+    flame\time\sleep($ms);
+}
+
+for($i=0;$i<200;++$i) {
+    flame\go(function() {
+        $i = 0;
+        for($x=0;$x<200;++$x) {
+            echo ++$i, "\n";
+            mySleep1();
+            // flame\time\sleep(10);
+        }
+    });
+}
+
+
+flame\run();

From fe50b2987e3093b22c213aef95fc6860c44e9982 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 22 Nov 2018 16:26:38 +0800
Subject: [PATCH 002/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=20?=
 =?UTF-8?q?MySQL=20=E7=BA=BF=E7=A8=8B=E6=A8=A1=E5=9E=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                 |   3 +
 README.md                      |   9 +
 doc/core.php                   |  21 ++
 doc/time.php                   |  11 +
 src/command.h                  |   6 +-
 src/controller.cpp             |   7 +-
 src/core.cpp                   |  26 +-
 src/core.h                     |   3 -
 src/coroutine.cpp              |   7 +-
 src/coroutine.h                |  15 +-
 src/flame.cpp                  |   3 +-
 src/mysql/_connection_base.cpp |  82 +++++
 src/mysql/_connection_base.h   |  17 +
 src/mysql/_connection_lock.cpp |  22 ++
 src/mysql/_connection_lock.h   |  16 +
 src/mysql/_connection_pool.cpp | 115 +++++++
 src/mysql/_connection_pool.h   |  40 +++
 src/mysql/client.cpp           | 158 +++++++++
 src/mysql/client.h             |  29 ++
 src/mysql/mysql.cpp            | 591 +++++++++++++++++++++++++++++++++
 src/mysql/mysql.h              |  17 +
 src/time/time.cpp              |  82 ++++-
 src/time/time.h                |   8 +-
 src/url.cpp                    |  54 +++
 src/url.h                      |  17 +
 src/vendor.h                   |   1 +
 test/mysql_1.php               |  14 +
 test/time_1.php                |   9 +
 28 files changed, 1325 insertions(+), 58 deletions(-)
 create mode 100644 doc/core.php
 create mode 100644 doc/time.php
 create mode 100644 src/mysql/_connection_base.cpp
 create mode 100644 src/mysql/_connection_base.h
 create mode 100644 src/mysql/_connection_lock.cpp
 create mode 100644 src/mysql/_connection_lock.h
 create mode 100644 src/mysql/_connection_pool.cpp
 create mode 100644 src/mysql/_connection_pool.h
 create mode 100644 src/mysql/client.cpp
 create mode 100644 src/mysql/client.h
 create mode 100644 src/mysql/mysql.cpp
 create mode 100644 src/mysql/mysql.h
 create mode 100644 src/url.cpp
 create mode 100644 src/url.h
 create mode 100644 test/mysql_1.php
 create mode 100644 test/time_1.php

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c5ccafb..c571f5e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,6 +15,7 @@ set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.13.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.0.0)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-0.11.6)
+set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.8.1)
 # 基本参数
 set(CMAKE_BUILD_TYPE "Debug")
 # set(CMAKE_C_COMPILER /data/vendor/llvm-7.0.0/bin/clang)
@@ -45,9 +46,11 @@ target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_MONGODB}/include/libbson-1.0
     ${VENDOR_AMQP}/include
     ${VENDOR_RDKAFKA}/include
+    ${VENDOR_HTTPPARSER}/include
 )
 # 链接库
 target_link_libraries(FLAME
+    ${VENDOR_HTTPPARSER}/lib/libhttp_parser.o
     ${VENDOR_PHPEXT}/lib/libphpext.a
     ${VENDOR_MYSQL}/lib/libmysqlclient.a
     ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
diff --git a/README.md b/README.md
index 03d5d51..e210ff2 100644
--- a/README.md
+++ b/README.md
@@ -54,3 +54,12 @@ make install
 rm /data/vendor/rdkafka-0.11.6/lib/*.so*
 ```
 
+#### HttpParser
+``` Bash
+mkdir -p /data/vendor/http-parser-2.8.1/lib
+mkdir -p /data/vendor/http-parser-2.8.1/include
+CFLAGS=-fPIC make libhttp_parser.o
+cp libhttp_parser.o /data/vendor/http-parser-2.8.1/lib
+cp http_parser.h /data/vendor/http-parser-2.8.1/include
+```
+
diff --git a/doc/core.php b/doc/core.php
new file mode 100644
index 0000000..5500c9d
--- /dev/null
+++ b/doc/core.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * 基本框架流程函数, 用于框架初始化, 启动协程等;
+ */
+namespace flame;
+/**
+ * 初始化框架, 设置进程名称及相关配置;
+ * @param string $process_name 进程名称
+ * @param array  $options 选项配置, 目前可用如下:
+ *  * `worker` - 工作进程仓库
+ *  * `logger` - 日志输出重定向目标文件(完整路径);
+ */
+function init($process_name, $options = []) {}
+/**
+ *  启动协程(运行对应回调函数)
+ */
+function go(callable $cb) {}
+/**
+ * 框架调度, 上述协程会在框架开始调度运行后启动
+ */
+function run() {}
diff --git a/doc/time.php b/doc/time.php
new file mode 100644
index 0000000..918bdf4
--- /dev/null
+++ b/doc/time.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace flame\time;
+
+// 当前协程休眠 $ms 毫秒
+function sleep(Integer $ms) {}
+// 获取毫秒时间戳
+function now() {}
+// 获取标准时间 YYYY-mm-dd HH:ii:ss
+function iso() {}
+
diff --git a/src/command.h b/src/command.h
index e32242c..a7c2add 100644
--- a/src/command.h
+++ b/src/command.h
@@ -1,4 +1,5 @@
 #pragma once
+#include "controller.h"
 #include "coroutine.h"
 
 namespace flame {
@@ -17,8 +18,3 @@ namespace flame {
         coroutine_handler& ch_;
     };
 }
-
-
-auto cmd = std::make_shared<command_xxx>(ch, xxxx);
-cmd->execute();
-php::object = .,..... cmd->xxxxx 
diff --git a/src/controller.cpp b/src/controller.cpp
index efe35b7..2ac0e12 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -58,7 +58,6 @@ void controller_master::run() {
 	// });
 	// 5. 启动 context_x 运行
 	gcontroller->context_x.run();
-	std::cout << "master: done\n";
 }
 
 controller_worker::controller_worker()
@@ -68,6 +67,7 @@ controller_worker::controller_worker()
 }
 
 void controller_worker::run() {
+	auto work = boost::asio::make_work_guard(gcontroller->context_y);
 	// 子进程的启动过程:
 	// 1. 监听信号进行日志重载或停止
 	// signal_.async_wait([this] (const boost::system::error_code& error, int signal) {
@@ -82,6 +82,11 @@ void controller_worker::run() {
 	}
 	// 3. 启动 context_x 运行
 	gcontroller->context_x.run();
+	work.reset();
+	for (int i = 0; i < 4; ++i)
+	{
+		thread_[i].join();
+	}
 }
 
 
diff --git a/src/core.cpp b/src/core.cpp
index 1b894f5..c12da9e 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -3,6 +3,19 @@
 #include "core.h"
 
 namespace flame {
+    static php::value init(php::parameters& params) {
+        gcontroller->initialize();
+        return nullptr;
+    }
+    static php::value go(php::parameters& params) {
+        php::callable fn = params[0];
+        coroutine::start(fn);
+        return nullptr;
+    }
+    static php::value run(php::parameters& params) {
+        gcontroller->run();
+        return nullptr;
+    }
     void declare(php::extension_entry &ext) {
         ext
             .on_module_startup([](php::extension_entry &ext) -> bool {
@@ -17,18 +30,5 @@ namespace flame {
             })
             .function<run>("flame\\run");
     }
-    php::value init(php::parameters& params) {
-        gcontroller->initialize();
-        return nullptr;
-    }
-    php::value go(php::parameters& params) {
-        php::callable fn = params[0];
-        coroutine::start(fn);
-        return nullptr;
-    }
     
-    php::value run(php::parameters& params) {
-        gcontroller->context_x.run();
-        return nullptr;
-    }
 }
\ No newline at end of file
diff --git a/src/core.h b/src/core.h
index a374078..377765d 100644
--- a/src/core.h
+++ b/src/core.h
@@ -3,7 +3,4 @@
 
 namespace flame {
     void declare(php::extension_entry& ext);
-    php::value init(php::parameters& params);
-    php::value go(php::parameters& params);
-    php::value run(php::parameters& params);
 }
\ No newline at end of file
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 37cdc36..0e5f3a0 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -24,7 +24,8 @@ namespace flame
         auto co = std::make_shared<coroutine>(std::move(fn));
         boost::asio::post(gcontroller->context_x, [co] {
             // co->c1_ = boost::context::callcc([co] (auto &&cc) {
-            co->c1_ = boost::context::fiber([co](boost::context::fiber &&cc) {
+            co->c1_ = boost::context::fiber([co] (boost::context::fiber &&cc) {
+                auto work = boost::asio::make_work_guard(gcontroller->context_x);
                 // 启动进入协程
                 coroutine::current = co.get();
                 zend_vm_stack_init();
@@ -73,7 +74,9 @@ namespace flame
         error = e;
         nsize = n;
         co_->resume();
-        // boost::asio::post(co_->sq_, std::bind(&coroutine::resume, co_));
+    }
+    void coroutine_handler::resume() {
+        co_->resume();
     }
     void coroutine_handler::suspend()
     {
diff --git a/src/coroutine.h b/src/coroutine.h
index dc3ee2b..67c09f9 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -2,7 +2,8 @@
 #include "vendor.h"
 
 namespace flame {
-	class coroutine : public std::enable_shared_from_this<coroutine> {
+	class coroutine : public std::enable_shared_from_this<coroutine>
+	{
 	public:
 		struct php_context_t
 		{
@@ -27,8 +28,6 @@ namespace flame {
 		// boost::context::continuation c2_;
 		boost::context::fiber c1_;
 		boost::context::fiber c2_;
-
-
 		php_context_t php_;
 	};
 
@@ -39,18 +38,12 @@ namespace flame {
 		coroutine_handler(coroutine *co);
 		~coroutine_handler();
 		void operator()(const boost::system::error_code& e, std::size_t n = 0);
+		void resume();
 		void suspend();
 		boost::system::error_code error;
 		std::size_t 	          nsize;
 		coroutine *co_;
-
-		friend bool asio_handler_is_continuation(coroutine_handler *ch)
-		{
-			std::cout << "continuation\n";
-			return true;
-		}
-
-	  private:
+	private:
 		
 	};
 } // namespace flame
diff --git a/src/flame.cpp b/src/flame.cpp
index a4b0668..b84beb8 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -1,6 +1,7 @@
 #include "vendor.h"
 #include "core.h"
 #include "time/time.h"
+#include "mysql/mysql.h"
 
 extern "C"
 {
@@ -35,7 +36,7 @@ extern "C"
         // flame::http::declare(ext);
         // flame::redis::declare(ext);
         // flame::rabbitmq::declare(ext);
-        // flame::mysql::declare(ext);
+        flame::mysql::declare(ext);
         // flame::mongodb::declare(ext);
         // flame::kafka::declare(ext);
         return ext;
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
new file mode 100644
index 0000000..9093513
--- /dev/null
+++ b/src/mysql/_connection_base.cpp
@@ -0,0 +1,82 @@
+#include "_connection_base.h"
+
+namespace flame::mysql
+{
+    void _connection_base::escape(std::shared_ptr<MYSQL> conn, php::buffer &b, const php::value &v, char quote)
+    {
+        switch (Z_TYPE_P(static_cast<zval *>(v)))
+        {
+        case IS_NULL:
+            b.append("NULL", 4);
+            break;
+        case IS_TRUE:
+            b.append("TRUE", 4);
+            break;
+        case IS_FALSE:
+            b.append("FALSE", 5);
+            break;
+        // case IS_LONG:
+        // case IS_DOUBLE: {
+        // 	php::string str = v;
+        // 	str.to_string();
+        // 	b.append(str);
+        // 	break;
+        // }
+        case IS_STRING:
+        {
+            php::string str = v;
+            // 支持字段名 aaa.bbb 进行 ESCAPE 变为 `aaa`.`bbb`
+            if (quote == '`')
+            {
+                const char *s = str.c_str(), *c, *e = str.c_str() + str.size();
+                for (c = s; c < e; ++c)
+                {
+                    if (*c == '.')
+                    {
+                        escape(conn, b, php::string(s, c - s), quote);
+                        b.push_back('.');
+                        escape(conn, b, php::string(c + 1, e - c - 1), quote);
+                        goto ESCAPE_FINISHED;
+                    }
+                }
+            }
+            char *to = b.prepare(str.size() * 2 + 2);
+            std::size_t n = 0;
+            to[n++] = quote;
+            // 摘自 mysql_real_escape_string_quote() @ libmysql.c:1228 相关流程
+            if (conn->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
+            {
+                n += escape_quotes_for_mysql(conn->charset, to + 1, str.size() * 2 + 1, str.c_str(), str.size(), quote);
+            }
+            else
+            {
+                n += escape_string_for_mysql(conn->charset, to + 1, str.size() * 2 + 1, str.c_str(), str.size());
+            }
+            to[n++] = quote;
+            b.commit(n);
+            break;
+        }
+        case IS_ARRAY:
+        {
+            php::array arr = v;
+            int index = 0;
+            b.push_back('(');
+            for (auto i = arr.begin(); i != arr.end(); ++i)
+            {
+                if (++index > 1)
+                    b.push_back(',');
+                escape(conn, b, i->second, quote);
+            }
+            b.push_back(')');
+            break;
+        }
+        default:
+        {
+            php::string str = v;
+            str.to_string();
+            escape(conn, b, str, quote);
+        }
+        }
+ESCAPE_FINISHED:;
+    }
+} // namespace flame::mysql
diff --git a/src/mysql/_connection_base.h b/src/mysql/_connection_base.h
new file mode 100644
index 0000000..16ba426
--- /dev/null
+++ b/src/mysql/_connection_base.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::mysql
+{
+
+    class _connection_base
+    {
+    public:
+        // 此函数仅允许 escape 主线程调用
+        static void escape(std::shared_ptr<MYSQL> c, php::buffer &b, const php::value &v, char quote = '\''); // 方便使用
+        virtual std::shared_ptr<MYSQL> acquire(coroutine_handler& ch) = 0;
+
+      protected:
+    };
+} // namespace flame::mysql
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
new file mode 100644
index 0000000..f0bbe07
--- /dev/null
+++ b/src/mysql/_connection_lock.cpp
@@ -0,0 +1,22 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "_connection_base.h"
+#include "_connection_lock.h"
+
+namespace flame::mysql
+{
+
+    _connection_lock::_connection_lock(std::shared_ptr<MYSQL> c)
+        : conn_(c)
+    {
+        
+    }
+    _connection_lock::~_connection_lock()
+    {
+    }
+    std::shared_ptr<MYSQL> _connection_lock::acquire(coroutine_handler &ch)
+    {
+        return conn_;
+    }
+
+} // namespace flame
diff --git a/src/mysql/_connection_lock.h b/src/mysql/_connection_lock.h
new file mode 100644
index 0000000..6fe8467
--- /dev/null
+++ b/src/mysql/_connection_lock.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "../vendor.h"
+#include "_connection_base.h"
+
+namespace flame::mysql
+{
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock>
+    {
+    public:
+        _connection_lock(std::shared_ptr<MYSQL> c);
+        ~_connection_lock();
+        std::shared_ptr<MYSQL> acquire(coroutine_handler& ch) override;
+      private:
+        std::shared_ptr<MYSQL> conn_;
+    };
+} // namespace flame::mysql
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
new file mode 100644
index 0000000..281c12f
--- /dev/null
+++ b/src/mysql/_connection_pool.cpp
@@ -0,0 +1,115 @@
+#include "../controller.h"
+#include "_connection_pool.h"
+
+namespace flame::mysql
+{
+        
+    _connection_pool::_connection_pool(url u, std::string charset)
+        : url_(std::move(u)), charset_(charset), min_(2), max_(8), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+    {
+        
+    }
+    _connection_pool::~_connection_pool()
+    {
+        while (!conn_.empty())
+        {
+            mysql_close(conn_.front().conn);
+            conn_.pop_front();
+        }
+    }
+    
+    std::shared_ptr<MYSQL> _connection_pool::acquire(coroutine_handler& ch)
+    {
+        std::shared_ptr<MYSQL> conn;
+        // 提交异步任务
+        boost::asio::post(guard_, [this, &conn, &ch] () {
+            // 设置对应的回调, 在获取连接后恢复协程
+            await_.push_back([&conn, &ch] (std::shared_ptr<MYSQL> c) {
+                conn = c;
+                // RESUME 需要在主线程进行
+                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            });
+            while (!conn_.empty())
+            {
+                if (mysql_ping(conn_.front().conn) == 0)
+                { // 可用连接
+                    MYSQL *c = conn_.front().conn;
+                    conn_.pop_front();
+                    release(c);
+                    return;
+                }
+                else
+                { // 连接已丢失，回收资源
+                    mysql_close(conn_.front().conn);
+                    conn_.pop_front();
+                    --size_;
+                }
+            }
+            if (size_ >= max_) return; // 已建立了足够多的连接, 需要等待已分配连接释放
+
+            MYSQL* c = create();
+            ++size_; // 当前还存在的连接数量
+            release(c);
+        });
+        // 暂停, 等待连接获取(异步任务)
+        ch.suspend();
+        // 恢复, 已经填充连接
+        return conn;
+    }
+    void _connection_pool::sweep() {
+        auto self = shared_from_this();
+        tm_.expires_from_now(std::chrono::seconds(300));
+        // 注意, 实际的清理流程需要保证 guard_ 串行流程
+        tm_.async_wait(boost::asio::bind_executor(guard_, [this, self] (const boost::system::error_code &error) {
+            if(error) return;
+            auto now = std::chrono::steady_clock::now();
+            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)
+            {
+                // 超低水位，关闭不活跃连接
+                auto duration = now - (*i).ttl;
+                if (duration > std::chrono::seconds(60))
+                {
+                    mysql_close((*i).conn);
+                    --size_;
+                    i = conn_.erase(i);
+                }
+                else
+                {
+                    ++i;
+                } // 所有连接还活跃（即使超过低水位）
+            }
+            // 再次启动
+            sweep();
+        }));
+    }
+    MYSQL* _connection_pool::create() {
+        MYSQL* c = mysql_init(nullptr);
+        mysql_options(c, MYSQL_SET_CHARSET_NAME, charset_.c_str());
+        unsigned int timeout = 5; // 连接超时
+        mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
+        if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0))
+        {
+            throw std::runtime_error("failed to connect mysql server");
+        }
+        return c;
+    }
+    void _connection_pool::release(MYSQL *c)
+    {
+        if (await_.empty())
+        { // 无等待分配的请求
+            conn_.push_back({c, std::chrono::steady_clock::now()});
+        }
+        else
+        { // 立刻分配使用
+            std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
+            await_.pop_front();
+            auto ptr = this->shared_from_this();
+            cb(
+                std::shared_ptr<MYSQL>(c, [this, ptr] (MYSQL *c) {
+                    boost::asio::post(guard_, std::bind(&_connection_pool::release, ptr, c));
+                })
+            );
+        }
+    }
+
+} // namespace flame::mysql
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
new file mode 100644
index 0000000..c3390f6
--- /dev/null
+++ b/src/mysql/_connection_pool.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "../url.h"
+#include "_connection_base.h"
+
+
+namespace flame::mysql
+{
+    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool>
+    {
+    public:
+        _connection_pool(url u, std::string charset);
+        ~_connection_pool();
+        std::shared_ptr<MYSQL> acquire(coroutine_handler &ch) override;
+        void sweep();
+
+      private:
+        url                               url_;
+        std::string                   charset_;
+        const std::uint16_t               min_;
+        const std::uint16_t               max_;
+        std::uint16_t                    size_;
+
+        boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题;
+        std::list<std::function<void(std::shared_ptr<MYSQL>)>> await_;
+        struct connection_t
+        {
+            MYSQL *conn;
+            std::chrono::time_point<std::chrono::steady_clock> ttl;
+        };
+        std::list<connection_t> conn_;
+        boost::asio::steady_timer tm_;
+
+        
+        MYSQL* create();
+        void release(MYSQL *c);
+    };
+
+} // namespace flame::mysql
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
new file mode 100644
index 0000000..8d19d3b
--- /dev/null
+++ b/src/mysql/client.cpp
@@ -0,0 +1,158 @@
+#include "../coroutine.h"
+// #include "transaction.h"
+#include "client.h"
+#include "_connection_pool.h"
+#include "mysql.h"
+// #include "result.h"
+
+namespace flame::mysql
+{
+    void client::declare(php::extension_entry &ext)
+    {
+        php::class_entry<client> class_client("flame\\mysql\\client");
+        class_client
+            .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::escape>("escape",
+            {
+                {"data", php::TYPE::UNDEFINED},
+            })
+            .method<&client::begin_tx>("begin_tx")
+            .method<&client::query>("query", {
+                {"sql", php::TYPE::STRING},
+            })
+            .method<&client::insert>("insert", {
+                {"table", php::TYPE::STRING},
+                {"rows", php::TYPE::ARRAY},
+            })
+            .method<&client::delete_>("delete", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::update>("update", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"set", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::select>("select", {
+                {"table", php::TYPE::STRING},
+                {"fields", php::TYPE::UNDEFINED},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::one>("one", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::get>("get", {
+                {"table", php::TYPE::STRING},
+                {"field", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            });
+        ext.add(std::move(class_client));
+    }
+    php::value client::escape(php::parameters &params)
+    {
+        coroutine_handler ch {coroutine::current};
+        std::shared_ptr<MYSQL> conn = cp_->acquire(ch);
+        
+        php::buffer buffer;
+        char quote = '\'';
+        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING))
+        {
+            php::string q = params[1];
+            if (q.data()[0] == '`') quote = '`';
+        }
+        _connection_base::escape(conn, buffer, params[0], quote);
+        return std::move(buffer);
+    }
+    php::value client::begin_tx(php::parameters &params)
+    {
+        // std::shared_ptr<coroutine> co = coroutine::current;
+        // c_->exec([](std::shared_ptr<MYSQL> c, int &error) -> MYSQL_RES*
+        // { // 工作线程
+        //     MYSQL *conn = c.get();
+        //     error = mysql_real_query(conn, "START TRANSACTION", 17);
+        //     assert(mysql_field_count(conn) == 0);
+        //     return nullptr;
+        // }, [co](std::shared_ptr<MYSQL> c, MYSQL_RES *r, int error) { // 主线程
+        //     MYSQL *conn = c.get();
+        //     if (error)
+        //     {
+        //         co->fail(mysql_error(conn), mysql_errno(conn));
+        //     }
+        //     else
+        //     {
+        //         php::object tx(php::class_entry<transaction>::entry());
+        //         transaction *tx_ = static_cast<transaction *>(php::native(tx));
+        //         tx_->c_.reset(new _connection_lock(c)); // 继续持有当前连接
+        //         co->resume(std::move(tx));
+        //     }
+        // });
+        // return coroutine::async();
+        return nullptr;
+    }
+    php::value client::query(php::parameters &params)
+    {
+        // c_->query(coroutine::current, php::object(this), params[0]);
+        // return coroutine::async();
+        return nullptr;
+    }
+    php::value client::insert(php::parameters &params)
+    {
+        // php::buffer buf;
+        // build_insert(c_, buf, params);
+        // c_->query(coroutine::current, php::object(this), std::move(buf));
+        // return coroutine::async();
+        return nullptr;
+    }
+    php::value client::delete_(php::parameters &params)
+    {
+        // php::buffer buf;
+        // build_delete(c_, buf, params);
+        // c_->query(coroutine::current, php::object(this), std::move(buf));
+        // return coroutine::async();
+        return nullptr;
+    }
+    php::value client::update(php::parameters &params)
+    {
+        // php::buffer buf;
+        // build_update(c_, buf, params);
+        // c_->query(coroutine::current, php::object(this), std::move(buf));
+        // return coroutine::async();
+        return nullptr;
+    }
+    php::value client::select(php::parameters &params)
+    {
+        // php::buffer buf;
+        // build_select(c_, buf, params);
+        // c_->query(coroutine::current, php::object(this), std::move(buf));
+        // return coroutine::async();
+        return nullptr;
+    }
+    php::value client::one(php::parameters &params)
+    {
+        // php::buffer buf;
+        // build_one(c_, buf, params);
+        // result::stack_fetch(coroutine::current, php::object(this), php::string(nullptr));
+        // c_->query(coroutine::current, php::object(this), std::move(buf));
+        // return coroutine::async();
+        return nullptr;
+    }
+    php::value client::get(php::parameters &params)
+    {
+        // php::buffer buf;
+        // build_get(c_, buf, params);
+        // result::stack_fetch(coroutine::current, php::object(this), params[1]);
+        // c_->query(coroutine::current, php::object(this), std::move(buf));
+        // return coroutine::async();
+        return nullptr;
+    }
+
+} // namespace flame::mysql
diff --git a/src/mysql/client.h b/src/mysql/client.h
new file mode 100644
index 0000000..977857c
--- /dev/null
+++ b/src/mysql/client.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::mysql
+{
+    class _connection_pool;
+    class client : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params);
+        php::value escape(php::parameters &params);
+        php::value begin_tx(php::parameters &params);
+        // php::value where(php::parameters &params);
+        // php::value order(php::parameters &params);
+        // php::value limit(php::parameters &params);
+        php::value query(php::parameters &params);
+        php::value insert(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value update(php::parameters &params);
+        php::value select(php::parameters &params);
+        php::value one(php::parameters &params);
+        php::value get(php::parameters &params);
+
+    protected:
+        std::shared_ptr<_connection_pool> cp_;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::mysql
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
new file mode 100644
index 0000000..854516e
--- /dev/null
+++ b/src/mysql/mysql.cpp
@@ -0,0 +1,591 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "mysql.h"
+#include "_connection_base.h"
+#include "_connection_pool.h"
+#include "client.h"
+
+namespace flame::mysql {
+    php::value connect(php::parameters& params) {
+		php::object obj(php::class_entry<client>::entry());
+
+        client *ptr = static_cast<client *>(php::native(obj));
+        std::string charset {"utf8"};
+		if(params.size() > 1) {
+			php::array opts = params[1];
+			if(opts.exists("charset")) {
+				charset = opts.get("charset").to_string();
+			}
+		}
+        url u(params[0]);
+
+        ptr->cp_.reset(new _connection_pool(u, charset));
+        ptr->cp_->sweep(); // 启动自动清理扫描
+        // TODO 确认第一个连接建立
+        return std::move(obj);
+	}
+    void declare(php::extension_entry &ext) {
+        ext
+            .on_module_startup([](php::extension_entry &ext) -> bool {
+                return mysql_library_init(0, nullptr, nullptr) == 0;
+            })
+            .on_module_shutdown([](php::extension_entry &ext) -> bool {
+                mysql_library_end();
+                return true;
+            })
+            .function<connect>("flame\\mysql\\connect", {
+                {"url", php::TYPE::STRING},
+            });
+        client::declare(ext);
+    }
+
+    // 相等
+    static void where_eq(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        if (cond.typeof(php::TYPE::NULLABLE))
+        {
+            buf.append(" IS NULL", 8);
+        }
+        else
+        {
+            php::string str = cond;
+            str.to_string();
+            buf.push_back('=');
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 不等 {!=}
+    static void where_ne(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        if (cond.typeof(php::TYPE::NULLABLE))
+        {
+            buf.append(" IS NOT NULL", 12);
+        }
+        else
+        {
+            php::string str = cond;
+            str.to_string();
+            buf.append("!=", 2);
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 大于 {>}
+    static void where_gt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        if (cond.typeof(php::TYPE::NULLABLE))
+        {
+            buf.append(">0", 2);
+        }
+        else
+        {
+            php::string str = cond;
+            str.to_string();
+            buf.push_back('>');
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 小于 {<}
+    static void where_lt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        if (cond.typeof(php::TYPE::NULLABLE))
+        {
+            buf.append("<0", 2);
+        }
+        else
+        {
+            php::string str = cond;
+            str.to_string();
+            buf.push_back('<');
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 大于等于 {>=}
+    static void where_gte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        if (cond.typeof(php::TYPE::NULLABLE))
+        {
+            buf.append(">=0", 3);
+        }
+        else
+        {
+            php::string str = cond;
+            str.to_string();
+            buf.append(">=", 2);
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 小于等于 {<=}
+    static void where_lte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        if (cond.typeof(php::TYPE::NULLABLE))
+        {
+            buf.append("<=0", 3);
+        }
+        else
+        {
+            php::string str = cond;
+            str.to_string();
+            buf.append("<=", 2);
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 某个 IN 无对应符号
+    static void where_in(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond)
+    {
+        assert(cond.typeof(php::TYPE::ARRAY) && "目标格式错误");
+
+        buf.append(" IN (", 5);
+        for (auto i = cond.begin(); i != cond.end(); ++i)
+        {
+            if (static_cast<int>(i->first) > 0)
+            {
+                buf.push_back(',');
+            }
+            php::string v = i->second;
+            v.to_string();
+            _connection_base::escape(cc, buf, v);
+        }
+        buf.push_back(')');
+    }
+    // WITH IN RANGE
+    static void where_wir(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond)
+    {
+        assert(cond.typeof(php::TYPE::ARRAY) && "目标格式错误");
+
+        buf.append(" BETWEEN ", 9);
+        std::int64_t min = std::numeric_limits<std::int64_t>::max(), max = std::numeric_limits<std::int64_t>::min();
+        for (auto i = cond.begin(); i != cond.end(); ++i)
+        {
+            std::int64_t x = i->second.to_integer();
+            if (x > max)
+                max = x;
+            if (x < min)
+                min = x;
+        }
+        buf.append(std::to_string(min));
+        buf.append(" AND ", 5);
+        buf.append(std::to_string(max));
+    }
+    // OUT OF RANGE
+    static void where_oor(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond)
+    {
+        buf.append(" NOT", 4);
+        where_wir(cc, buf, cond);
+    }
+    // LINK
+    static void where_lk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        buf.append(" LIKE ", 6);
+        _connection_base::escape(cc, buf, cond);
+    }
+    // NOT LIKE
+    static void where_nlk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
+    {
+        buf.append(" NOT LIKE ", 10);
+        _connection_base::escape(cc, buf, cond);
+    }
+    static void where_ex(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond, const php::string &field, const php::string &separator)
+    {
+        if (cond.size() > 1)
+            buf.push_back('(');
+
+        int j = -1;
+        for (auto i = cond.begin(); i != cond.end(); ++i)
+        {
+            if (++j > 0)
+                buf.append(separator);
+
+            if (i->first.typeof(php::TYPE::INTEGER))
+            {
+                if (i->second.typeof(php::TYPE::ARRAY))
+                {
+                    where_ex(cc, buf, i->second, i->first, " && ");
+                }
+                else
+                {
+                    php::string str = i->second;
+                    str.to_string();
+                    buf.append(str);
+                }
+            }
+            else
+            {
+                php::string key = i->first;
+                if (key.c_str()[0] == '{')
+                { // OPERATOR (php::TYPE::STRING)
+                    if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0) || (key.size() == 3 && strncasecmp(key.c_str(), "{!}", 3) == 0))
+                    {
+                        assert(i->second.typeof(php::TYPE::ARRAY));
+                        buf.append(" NOT ", 5);
+                        where_ex(cc, buf, i->second, php::string(nullptr), " && ");
+                    }
+                    else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0 || strncasecmp(key.c_str(), "{||}", 4) == 0))
+                    {
+                        assert(i->second.typeof(php::TYPE::ARRAY));
+                        where_ex(cc, buf, i->second, php::string(nullptr), " || ");
+                    }
+                    else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0) || (key.size() == 4 && strncasecmp(key.c_str(), "{&&}", 4) == 0))
+                    {
+                        assert(i->second.typeof(php::TYPE::ARRAY));
+                        where_ex(cc, buf, i->second, php::string(nullptr), " && ");
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_ne(cc, buf, i->second);
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{>}", 3) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_gt(cc, buf, i->second);
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{<}", 2) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_lt(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{>=}", 4) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_gte(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<=}", 4) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_lte(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<>}", 4) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_wir(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{><}", 4) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_oor(cc, buf, i->second);
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{~}", 3) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_lk(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!~}", 4) == 0)
+                    {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_nlk(cc, buf, i->second);
+                    }
+                }
+                else
+                { // php::TYPE::STRING
+                    if (i->second.typeof(php::TYPE::ARRAY))
+                    {
+                        php::array cond = i->second;
+                        if (cond.exists(0))
+                        {
+                            _connection_base::escape(cc, buf, key, '`');
+                            buf.push_back(' ');
+                            where_in(cc, buf, i->second);
+                        }
+                        else
+                        {
+                            where_ex(cc, buf, cond, key, " && ");
+                        }
+                    }
+                    else
+                    {
+                        _connection_base::escape(cc, buf, key, '`');
+                        where_eq(cc, buf, i->second);
+                    }
+                }
+            }
+        }
+        if (cond.size() > 1)
+            buf.push_back(')');
+    }
+    void build_where(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data)
+    {
+        buf.append(" WHERE ", 7);
+        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER))
+        {
+            buf.push_back(' ');
+            buf.append(data);
+        }
+        else if (data.typeof(php::TYPE::ARRAY))
+        {
+            where_ex(cc, buf, data, php::string(0), " && ");
+        }
+        else
+        {
+            buf.push_back('1');
+        }
+    }
+    void build_order(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data)
+    {
+        if (data.typeof(php::TYPE::STRING))
+        {
+            buf.append(" ORDER BY ", 10);
+            buf.append(data);
+        }
+        else if (data.typeof(php::TYPE::ARRAY))
+        {
+            buf.append(" ORDER BY", 9);
+            php::array order = data;
+            int j = -1;
+            for (auto i = order.begin(); i != order.end(); ++i)
+            {
+                if (++j > 0)
+                    buf.push_back(',');
+                if (i->first.typeof(php::TYPE::INTEGER))
+                {
+                    if (i->second.typeof(php::TYPE::STRING))
+                    {
+                        buf.push_back(' ');
+                        buf.append(i->second);
+                    }
+                }
+                else
+                {
+                    buf.push_back(' ');
+                    _connection_base::escape(cc, buf, i->first, '`');
+                    if (i->second.typeof(php::TYPE::STRING))
+                    {
+                        buf.push_back(' ');
+                        buf.append(i->second);
+                    }
+                    else if (i->second.typeof(php::TYPE::YES) || (i->second.typeof(php::TYPE::INTEGER) &&static_cast<int>(i->second) >= 0))
+                    {
+                        buf.append(" ASC", 4);
+                    }
+                    else
+                    {
+                        buf.append(" DESC", 5);
+                    }
+                }
+            }
+        }
+        else
+        {
+            // throw php::exception(zend_ce_type_error, "failed to build 'ORDER BY' clause: unsupported type");
+        }
+    }
+    void build_limit(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data)
+    {
+        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER))
+        {
+            buf.append(" LIMIT ", 7);
+            buf.append(data);
+        }
+        else if (data.typeof(php::TYPE::ARRAY))
+        {
+            buf.append(" LIMIT", 6);
+            php::array limit = data;
+            int j = -1;
+            for (auto i = limit.begin(); i != limit.end(); ++i)
+            {
+                if (++j > 0)
+                    buf.push_back(',');
+                buf.push_back(' ');
+                buf.append(i->second);
+                if (j > 0)
+                    break;
+            }
+        }
+        else
+        {
+            throw php::exception(zend_ce_type_error, "failed to build 'LIMIT' clause: unsupported type");
+        }
+    }
+    static void insert_row(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &row)
+    {
+        int j = -1;
+        for (auto i = row.begin(); i != row.end(); ++i)
+        {
+            if (++j > 0)
+                buf.append(", ", 2);
+            _connection_base::escape(cc, buf, i->second);
+        }
+    }
+    void build_insert(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
+    {
+        buf.append("INSERT INTO ", 12);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 字段
+        buf.push_back('(');
+        php::array rows = params[1], row;
+        if (rows.exists(0))
+        {
+            row = rows.get(0);
+            assert(!row.exists(0) && "二级行无字段");
+        }
+        else
+        {
+            row = rows;
+            rows = nullptr;
+        }
+        assert(row.typeof(php::TYPE::ARRAY) && "行非数组");
+        int j = -1;
+        for (auto i = row.begin(); i != row.end(); ++i)
+        {
+            if (++j > 0)
+                buf.append(", ", 2);
+            _connection_base::escape(cc, buf, i->first, '`');
+        }
+        // 数据
+        buf.append(") VALUES", 8);
+        if (rows.typeof(php::TYPE::ARRAY))
+        {
+            buf.push_back('(');
+            int j = -1;
+            for (auto i = rows.begin(); i != rows.end(); ++i)
+            {
+                if (++j > 0)
+                    buf.append("), (", 4);
+                insert_row(cc, buf, i->second);
+            }
+            buf.push_back(')');
+        }
+        else
+        {
+            buf.push_back('(');
+            insert_row(cc, buf, row);
+            buf.push_back(')');
+        }
+    }
+    void build_delete(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
+    {
+        buf.append("DELETE FROM ", 12);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        build_where(cc, buf, params[1]);
+        // 排序
+        if (params.size() > 2)
+            build_order(cc, buf, params[2]);
+        // 限制
+        if (params.size() > 3)
+            build_limit(cc, buf, params[3]);
+    }
+    void build_update(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
+    {
+        buf.append("UPDATE ", 7);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 数据
+        buf.append(" SET", 4);
+        php::array data = params[2];
+        if (data.typeof(php::TYPE::STRING))
+        {
+            buf.push_back(' ');
+            buf.append(data);
+        }
+        else if (data.typeof(php::TYPE::ARRAY))
+        {
+            int j = -1;
+            for (auto i = data.begin(); i != data.end(); ++i)
+            {
+                if (++j > 0)
+                    buf.push_back(',');
+                buf.push_back(' ');
+                _connection_base::escape(cc, buf, i->first, '`');
+                buf.push_back('=');
+                _connection_base::escape(cc, buf, i->second);
+            }
+        }
+        else
+        {
+            throw php::exception(zend_ce_type_error, "failed to build 'UPDATE' clause: unsupported type");
+        }
+        // 条件
+        build_where(cc, buf, params[1]);
+        // 排序
+        if (params.size() > 3)
+            build_order(cc, buf, params[3]);
+        // 限制
+        if (params.size() > 4)
+            build_limit(cc, buf, params[4]);
+    }
+    void build_select(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
+    {
+        buf.append("SELECT ", 7);
+        // 字段
+        php::value fields = params[1];
+        if (fields.typeof(php::TYPE::STRING))
+        {
+            buf.append(fields);
+        }
+        else if (fields.typeof(php::TYPE::ARRAY))
+        {
+            php::array a = fields;
+            int j = -1;
+            for (auto i = a.begin(); i != a.end(); ++i)
+            {
+                if (++j > 0)
+                    buf.append(", ", 2);
+                if (i->first.typeof(php::TYPE::INTEGER))
+                {
+                    _connection_base::escape(cc, buf, i->second, '`');
+                }
+                else
+                {
+                    buf.append(i->first);
+                    buf.push_back('(');
+                    _connection_base::escape(cc, buf, i->second, '`');
+                    buf.push_back(')');
+                }
+            }
+        }
+        else
+        {
+            throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type");
+        }
+        // 表名
+        buf.append(" FROM ", 6);
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        if (params.size() > 2)
+            build_where(cc, buf, params[2]);
+        // 排序
+        if (params.size() > 3)
+            build_order(cc, buf, params[3]);
+        // 限制
+        if (params.size() > 4)
+            build_limit(cc, buf, params[4]);
+    }
+    void build_one(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
+    {
+        buf.append("SELECT *  FROM ", 15);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        if (params.size() > 1)
+            build_where(cc, buf, params[1]);
+        // 排序
+        if (params.size() > 2)
+            build_order(cc, buf, params[2]);
+        buf.append(" LIMIT 1", 8);
+    }
+    void build_get(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
+    {
+        buf.append("SELECT ", 7);
+        // 字段
+        if (params[1].typeof(php::TYPE::STRING))
+        {
+            _connection_base::escape(cc, buf, params[1], '`');
+        }
+        else
+        {
+            throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type");
+        }
+        // 表名
+        buf.append(" FROM ", 6);
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        if (params.size() > 2)
+            build_where(cc, buf, params[2]);
+        // 排序
+        if (params.size() > 3)
+            build_order(cc, buf, params[3]);
+        buf.append(" LIMIT 1", 8);
+    }
+}
\ No newline at end of file
diff --git a/src/mysql/mysql.h b/src/mysql/mysql.h
new file mode 100644
index 0000000..7b6f623
--- /dev/null
+++ b/src/mysql/mysql.h
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace flame::mysql
+{
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters& params);
+
+    void build_where(std::shared_ptr<MYSQL> cm, php::buffer &buf, const php::value &data);
+    void build_order(std::shared_ptr<MYSQL> cm, php::buffer &buf, const php::value &data);
+    void build_limit(std::shared_ptr<MYSQL> cm, php::buffer &buf, const php::value &data);
+    void build_insert(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_delete(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_update(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_select(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_one(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_get(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+}
\ No newline at end of file
diff --git a/src/time/time.cpp b/src/time/time.cpp
index 772cc0d..87b5c8d 100644
--- a/src/time/time.cpp
+++ b/src/time/time.cpp
@@ -2,23 +2,69 @@
 #include "../controller.h"
 #include "../coroutine.h"
 
-namespace flame::time {
-
-
-static php::value sleep(php::parameters& params) {
-    coroutine_handler yield {coroutine::current};
-    boost::asio::steady_timer tm(gcontroller->context_x);
-    int ms = static_cast<int>(params[0]);
-    // std::cout << "sleep: " << ms << std::endl;
-    tm.expires_from_now(std::chrono::milliseconds(ms));
-    tm.async_wait(yield);
-    return nullptr;
-}
-
-void declare(php::extension_entry& ext) {
-    ext.function<sleep>("flame\\time\\sleep", {
-        {"duration", php::TYPE::INTEGER},
-    });
-}
+namespace flame::time
+{
+
+    static std::chrono::time_point<std::chrono::system_clock> time_system;
+	static std::chrono::time_point<std::chrono::steady_clock> time_steady;
+
+    static php::value sleep(php::parameters& params)
+    {
+        coroutine_handler ch {coroutine::current};
+        boost::asio::steady_timer tm(gcontroller->context_x);
+        int ms = static_cast<int>(params[0]);
+        if(ms < 0) ms = 1;
+        tm.expires_from_now(std::chrono::milliseconds(ms));
+        tm.async_wait(ch);
+        return nullptr;
+    }
+
+
+    std::chrono::time_point<std::chrono::system_clock> now()
+    {
+        std::chrono::milliseconds diff = std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now() - time_steady);
+        return time_system + diff;
+    }
+    std::string iso()
+    {
+        std::string data(19, '\0');
+        std::time_t t = std::chrono::system_clock::to_time_t(now());
+        struct tm *m = std::localtime(&t);
+        sprintf(data.data(), "%04d-%02d-%02d %02d:%02d:%02d",
+                1900 + m->tm_year,
+                1 + m->tm_mon,
+                m->tm_mday,
+                m->tm_hour,
+                m->tm_min,
+                m->tm_sec);
+        return data;
+    }
+
+    static php::value now(php::parameters &params)
+    {
+        return std::chrono::duration_cast<std::chrono::milliseconds>(now().time_since_epoch()).count();
+    }
+    static php::value iso(php::parameters &params)
+    {
+        return iso();
+    }
+
+    void declare(php::extension_entry& ext)
+    {
+        time_steady = std::chrono::steady_clock::now();
+		time_system = std::chrono::system_clock::now();
+
+        ext
+        .function<sleep>("flame\\time\\sleep",
+        {
+            {"duration", php::TYPE::INTEGER},
+        })
+        // 毫秒时间戳
+        .function<now>("flame\\time\\now")
+        // 标准时间 YYYY-mm-dd HH:ii:ss
+        .function<iso>("flame\\time\\iso");
+
+    }
 
 }
\ No newline at end of file
diff --git a/src/time/time.h b/src/time/time.h
index a464dd1..c427354 100644
--- a/src/time/time.h
+++ b/src/time/time.h
@@ -1,7 +1,7 @@
 #include "../vendor.h"
 
-namespace flame::time {
-
-void declare(php::extension_entry &ext);
-
+namespace flame::time
+{
+    void declare(php::extension_entry &ext);
+    std::chrono::time_point<std::chrono::system_clock> now();
 }
\ No newline at end of file
diff --git a/src/url.cpp b/src/url.cpp
new file mode 100644
index 0000000..ad7e6c3
--- /dev/null
+++ b/src/url.cpp
@@ -0,0 +1,54 @@
+#include "vendor.h"
+#include "url.h"
+
+namespace flame
+{
+
+    typedef ::parser::separator_parser<std::string, std::string> parser_t;
+    url::url(const php::string &str) {
+        const char* s = str.c_str();
+
+        struct http_parser_url x;
+        http_parser_parse_url(s, str.size(), 0, &x);
+        if(x.field_set & (1 << UF_SCHEMA)) {
+            auto y = x.field_data[UF_SCHEMA];
+            schema.assign(s + y.off, y.len);
+        }
+        if(x.field_set & (1 << UF_USERINFO)) {
+            auto y = x.field_data[UF_USERINFO];
+            int i = 0;
+            for(;i<y.len;++i) {
+                if(s[y.off + i] == ':') {
+                    break;
+                }else{
+                    user.push_back(s[y.off + i]);
+                }
+            }
+            ++i;
+            for(;i<y.len;++i) {
+                pass.push_back(s[y.off + i]);
+            }
+        }
+        if(x.field_set & (1 << UF_HOST)) {
+            auto y = x.field_data[UF_HOST];
+            host.assign(s + y.off, y.len);
+        }
+        port = x.port;
+        if (x.field_set & (1 << UF_PATH))
+        {
+            auto y = x.field_data[UF_PATH];
+            path.assign(s + y.off, y.len);
+        }
+        if (x.field_set & (1 << UF_QUERY)) {
+            auto y = x.field_data[UF_QUERY];
+            parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
+                std::string val = et.second;
+                val.resize(php::url_decode_inplace(val.data(), val.size()));
+                query[et.first] = val;
+            });
+            p.parse(s + y.off, y.len);
+            p.end();
+        }
+    }
+
+} // namespace flame
\ No newline at end of file
diff --git a/src/url.h b/src/url.h
new file mode 100644
index 0000000..18940a9
--- /dev/null
+++ b/src/url.h
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace flame {
+    class url {
+    public:
+        url(const php::string& str);
+        std::string schema;
+        std::string   host;
+        std::uint16_t port;
+        std::string   path;
+        std::map<std::string, std::string> query;
+        std::string   user;
+        std::string   pass;
+    private:
+        
+    };
+}
\ No newline at end of file
diff --git a/src/vendor.h b/src/vendor.h
index 86bfe8f..1cdd2d1 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -44,3 +44,4 @@ namespace ssl = boost::asio::ssl;
 #include <my_sys.h>
 #include <mongoc.h>
 #include <librdkafka/rdkafka.h>
+#include <http_parser.h>
diff --git a/test/mysql_1.php b/test/mysql_1.php
new file mode 100644
index 0000000..91b2f8b
--- /dev/null
+++ b/test/mysql_1.php
@@ -0,0 +1,14 @@
+<?php
+
+flame\init("mysql_1");
+
+flame\go(function() {
+    echo "1\n";
+    $cli = flame\mysql\connect("mysql://ucenter_rw:eVrFSRa2NLdoIPbITvPe@rm-2ze8xu255t0523w07.mysql.rds.aliyuncs.com:3306/ucenter");
+    echo "2\n";
+    var_dump( $cli->escape('a.b') );
+    echo "3\n";
+});
+
+
+flame\run();
diff --git a/test/time_1.php b/test/time_1.php
new file mode 100644
index 0000000..675a04f
--- /dev/null
+++ b/test/time_1.php
@@ -0,0 +1,9 @@
+<?php
+flame\init("time_1");
+
+flame\go(function() {
+    echo flame\time\now(), "\n";
+    echo flame\time\iso(), "\n";
+});
+
+flame\run();

From 72b31f8fd8401aed94284bc080a43b0f685a2e97 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 22 Nov 2018 22:00:57 +0800
Subject: [PATCH 003/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=20?=
 =?UTF-8?q?MySQL=20=E6=8E=A5=E5=8F=A3;=20=E4=BF=AE=E6=AD=A3:=20=E5=90=AF?=
 =?UTF-8?q?=E5=8A=A8=E5=8D=8F=E7=A8=8B=E9=A1=BB=E4=BF=9D=E5=AD=98=E5=BD=93?=
 =?UTF-8?q?=E6=97=B6=E5=A0=86=E6=A0=88;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mysql.php                  | 197 +++++++++++++++++++++++++++++++++
 src/controller.cpp             |   1 +
 src/controller.h               |  32 +++---
 src/coroutine.cpp              |  12 +-
 src/mysql/_connection_base.cpp | 100 +++++++++++++++++
 src/mysql/_connection_base.h   |   2 +
 src/mysql/_connection_lock.cpp |  54 ++++++++-
 src/mysql/_connection_lock.h   |   4 +
 src/mysql/_connection_pool.cpp |  15 +--
 src/mysql/_connection_pool.h   |   6 +-
 src/mysql/client.cpp           | 114 +++++++++----------
 src/mysql/mysql.cpp            |  45 ++++----
 src/mysql/result.cpp           | 126 +++++++++++++++++++++
 src/mysql/result.h             |  20 ++++
 src/mysql/tx.cpp               | 164 +++++++++++++++++++++++++++
 src/mysql/tx.h                 |  31 ++++++
 test/mysql_1.php               |  30 +++--
 17 files changed, 834 insertions(+), 119 deletions(-)
 create mode 100644 doc/mysql.php
 create mode 100644 src/mysql/result.cpp
 create mode 100644 src/mysql/result.h
 create mode 100644 src/mysql/tx.cpp
 create mode 100644 src/mysql/tx.h

diff --git a/doc/mysql.php b/doc/mysql.php
new file mode 100644
index 0000000..f3619f7
--- /dev/null
+++ b/doc/mysql.php
@@ -0,0 +1,197 @@
+<?php
+/**
+ * 
+ */
+namespace flame\mysql;
+/**
+ * 连接 MySQL 服务器
+ * @param string $url 服务端地址, 形如:
+ *  mysql://{user}:{pass}@{host}:{port}/{database}?opt1=val1
+ *  目前可用的选项如下:
+ *      * "charset" => 字符集
+ * 
+ * @return client 客户端对象
+ */
+function connect($url): client {}
+
+/**
+ * WHERE 字句语法规则:
+ * 
+ * @example
+ *  $where = ["a"=>"1", "b"=>[2,"3","4"], "c"=>null, "d"=>["{!=}"=>5]];
+ *  // " WHERE (`a`='1' && `b`  IN ('2','3','4') && `c` IS NULL && `d`!='5')"
+ * @example
+ *  $where = ["{OR}"=>["a"=>["{!=}"=>1], "b"=>["{><}"=>[1, 10, 3]], "c"=>["{~}"=>"aaa%"]]];
+ *  // $sql == " WHERE (`a`!='1' || `b` NOT BETWEEN 1 AND 10 || `c` LIKE 'aaa%')"
+ * @example
+ *  $where = "`a`=1";
+ *  // $sql == " WHERE `a`=1";
+ * 
+ * @param 特殊的符号均以 "{}" 进行包裹, 存在以下可用项:
+ *  * `{NOT}` / `{!}` - 逻辑非, 对逻辑子句取反, 生成形式: `NOT (.........)`;
+ *  * `{OR}` / `{||}` - 逻辑或, 对逻辑子句进行逻辑或拼接, 生成形式: `... || ... || ...`;
+ *  * `{AND}` / `{||}` - 逻辑与, 对逻辑子句进行逻辑与拼接 (默认拼接方式), 生成形式: `... && ... && ...`;
+ *  * `{!=}` - 不等, 生成形式: `...!='...'` / ` ... IS NOT NULL`;
+ *  * `{>}` - 大于, 生成形式: `...>...`;
+ *  * `{<}` - 小于, 生成形式: `...<...`;
+ *  * `{>=}` - 大于等于, 生成形式: `...>=...`;
+ *  * `{<=}` - 小于等于, 生成形式: `...<=...`;
+ *  * `{<>}` - 区间内, 生成形式: `... BETWEEN ... AND ...`, 目标数组至少存在两个数值;
+ *  * `{><}` - 区间外, 生成形式: `... NOT BETWEEN ... AND ...`, 目标数组至少存在两个数值;
+ *  * `{~}` - 模糊正匹配, 生成形式: `... LIKE ...`;
+ *  * `{!~}` - 模糊非匹配, 生成形式: `... NOT LIKE ...`;
+ */
+
+/**
+ * ORDER 子句语法规则:
+ * @example
+ *  $order = "`a` ASC, `b` DESC";
+ *  // $sql == " ORDER BY `a` ASC, `b` DESC");
+ * @example
+ *  $order = ["a"=>1, "b"=>-1, "c"=>true, "d"=>false, "e"=>"ASC", "f"=>"DESC"];
+ *  // $sql == " ORDER BY `a` ASC, `b` DESC, `c` ASC, `d` DESC, `e` ASC, `f` DESC"
+ * 
+ * @param
+ *  * 当 value 位正数或 `true` 时, 生成 `ASC`; 否则生成 `DESC`;
+ *  * 当 value 为文本时, 直接拼接;
+ */
+
+/**
+ * LIMIT 子句语法规则:
+ * @example
+ *  $limit = 10;
+ *  // $sql == " LIMIT 10"
+ * @example
+ *  $limit = [10,10];
+ *  // $sql == " LIMIT 10, 10"
+ * @example
+ *  $limit = "20, 300";
+ *  // $sql == " LIMIT 20, 300"
+ *
+ * @param
+ *  * 当 $limit 值位文本或数值时, 直接拼接;
+ *  * 当 $limit 为数组时, 拼接前两个值;
+ */
+
+
+/**
+ * MySQL 客户端
+ */
+class client {
+    /**
+     * 返回事务对象 (绑定在一个连接上)
+     */
+    function begin_tx(): ?tx {}
+    /**
+     * 执行制定的 SQL 查询, 并返回结果
+     * @param string $sql 
+     * @return result 结果对象
+     */
+    function query(string $sql): result {}
+    /**
+     * 向指定表插入一行或多行数据
+     * @param string $table 表名
+     * @param array $data 待插入数据, 多行关联数组插入多行数据(以首行 KEY 做字段名); 普通关联数组插入一行数据;
+     */
+    function insert(string $table, array $data): result {}
+    /**
+     * 从指定表格删除数
+     * @param string $table 表明
+     * @param mixed $where WHERE 子句, 请参考上文;
+     * @param mixed $order ORDER 子句, 请参考上文;
+     * @param mixed $limit LIMIT 子句, 请参考上文;
+     */
+    function delete(string $table, mixed $where, mixed $order, mixed $limit): result {}
+    /**
+     * 更新指定表
+     * @param string $table 表名
+     * @param mixed $where WHERE 子句, 请参考上文;
+     * @param mixed $modify 当 $modify 为字符串时, 直接拼接 "UPDATE ... SET $modify", 否则按惯例数组进行 KEY = VAL 拼接;
+     * @param mixed $order ORDER 子句, 请参考上文;
+     * @param mixed $limit LIMIT 子句, 请参考上文;
+     */
+    function update(string $table, mixed $where, mixed $modify, mixed $order, mixed $limit): result {}
+    /**
+     * 从指定表筛选获取数据
+     * @param string $table 表名
+     * @param mixed $fields 待选取字段, 为数组时其元素表示各个字段; 文本时直接拼接在 "SELECT $fields FROM $table"; 
+     *  关联数组的 KEY 表示对应函数, 仅可用于简单函数调用, 例如:
+     *      "SUM" => "a"
+     *  表示:
+     *      SUM(`a`)
+     * @param mixed $where WHERE 子句, 请参考上文;
+     * @param mixed $order ORDER 子句, 请参考上文;
+     * @param mixed $limit LIMIT 子句, 请参考上文;
+     */
+    function select(string $table, mixed $fields, array $where, mixed $order, mixed $limit): result {}
+    /**
+     * 从指定表筛选获取一条数据, 并立即返回该行数据
+     */
+    function one(string $table, mixed $where, mixed $order): array {}
+    /**
+     * 从指定表获取一行数据的指定字段值
+     */
+    function get(string $table, string $field, array $where, mixed $order): mixed {}
+    
+};
+
+/**
+ * 事务对象, 与 client 对象接口基本相同
+ */
+class tx {
+    /**
+     * 提交当前事务
+     */
+    function commit() {}
+    /**
+     * 回滚当前事务
+     */
+    function rollback() {}
+    /**
+     * @see client::query()
+     */
+    function query(string $sql): result {}
+    /**
+     * @see client::insert()
+     */
+    function insert(string $table, array $data): result {}
+    /**
+     * @see client::update()
+     */
+    function update(string $table, array $where, mixed $modify, mixed $order = null, mixed $limit = null): result {}
+    /**
+     * @see client::select()
+     */
+    function select(string $table, mixed $fields, array $where, mixed $order = null, mixed $limit = null): result {}
+    /**
+     * @see client::one()
+     */
+    function one(string $table, array $where, mixed $order = null): array {}
+    /**
+     * @see client::get()
+     */
+    function get(string $table, string $field, array $where, mixed $order = null): mixed {}
+};
+
+class result {
+    /**
+     * @property Integer
+     */
+    public $affected_rows;
+    /**
+     * @property Integer
+     */
+    public $insert_id;
+    /**
+     * 取出下一行
+     * @return 下一行数据关联数组;
+     *  若不存在下一行, 返回 NULL
+     */
+    function fetch_row():?array {}
+    /**
+     * 取出(剩余)全部行
+     * @return 二维数组, 可能为空数组; 
+     *  若当前对象不包含结果集(更新型查询), 返回 NULL
+     */
+    function fetch_all():?array {}
+};
\ No newline at end of file
diff --git a/src/controller.cpp b/src/controller.cpp
index 2ac0e12..e6cd7a5 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -15,6 +15,7 @@ controller::controller()
 	, env(boost::this_process::environment())
 	, status(controller_status::UNKNOWN) {
 
+	mthread_id = std::this_thread::get_id();
 }
 
 void controller::initialize() {
diff --git a/src/controller.h b/src/controller.h
index faf65f6..cf215e3 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -26,21 +26,23 @@ namespace flame {
 	};
 	class controller {
 	public:
-	  boost::asio::io_context context_x;
-	  boost::asio::io_context context_y;
-
-	  enum class process_type
-	  {
-		  UNKNOWN = 0,
-		  MASTER = 1,
-		  WORKER = 2,
-	  } type;
-	  boost::process::environment env;
-	  boost::program_options::variables_map options;
-	  enum class controller_status
-	  {
-		  UNKNOWN = 0,
-	  } status;
+		boost::asio::io_context context_x;
+		boost::asio::io_context context_y;
+
+		enum class process_type
+		{
+			UNKNOWN = 0,
+			MASTER = 1,
+			WORKER = 2,
+		} type;
+		boost::process::environment env;
+		boost::program_options::variables_map options;
+		enum class controller_status
+		{
+			UNKNOWN = 0,
+		} status;
+
+		std::thread::id mthread_id;
 	private:
 		// 核心进程对象
 		std::unique_ptr<controller_master> master_;
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 0e5f3a0..8b6bef8 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -22,6 +22,9 @@ namespace flame
     void coroutine::start(php::callable fn)
     {
         auto co = std::make_shared<coroutine>(std::move(fn));
+        // 需要即时保存 PHP 堆栈
+        coroutine::save_context(co->php_);
+        // 事实上的协程启动会稍后
         boost::asio::post(gcontroller->context_x, [co] {
             // co->c1_ = boost::context::callcc([co] (auto &&cc) {
             co->c1_ = boost::context::fiber([co] (boost::context::fiber &&cc) {
@@ -37,7 +40,7 @@ namespace flame
                 coroutine::current = nullptr;
                 return std::move(co->c2_);
             });
-            co->c1_ = std::move(co->c1_).resume();
+            co->resume();
         });
     }
     coroutine::coroutine(php::callable &&fn)
@@ -73,13 +76,18 @@ namespace flame
     {
         error = e;
         nsize = n;
+
+        assert(std::this_thread::get_id() == gcontroller->mthread_id);
         co_->resume();
     }
-    void coroutine_handler::resume() {
+    void coroutine_handler::resume()
+    {
+        assert(std::this_thread::get_id() == gcontroller->mthread_id);
         co_->resume();
     }
     void coroutine_handler::suspend()
     {
+        assert(std::this_thread::get_id() == gcontroller->mthread_id);
         co_->suspend();
     }
 }
\ No newline at end of file
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 9093513..82bb8e6 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -1,4 +1,7 @@
+#include "../controller.h"
 #include "_connection_base.h"
+#include "_connection_lock.h"
+#include "result.h"
 
 namespace flame::mysql
 {
@@ -79,4 +82,101 @@ namespace flame::mysql
         }
 ESCAPE_FINISHED:;
     }
+
+    php::object _connection_base::query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch) {
+        MYSQL_RES* rst = nullptr;
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [&err, &conn, &ch, query = std::move(sql), &rst] ()
+        {
+            // 在工作线程执行查询
+            err = mysql_real_query(conn.get(), query.c_str(), query.size());
+            if (err == 0)
+            {
+                // 防止锁表, 均使用 store 方式
+                rst = mysql_store_result(conn.get());
+                if (!rst && mysql_field_count(conn.get()) > 0)
+                {
+                    err = -1;
+                }
+            }
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(err != 0)
+        {
+            int err = mysql_errno(conn.get());
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to query MySQL server: (%1%) %2%") % err % mysql_error(conn.get())).str(),
+                                 err);
+        }
+        php::object obj(php::class_entry<result>::entry());
+        result* ptr = static_cast<result*>(php::native(obj));
+        if(rst) // 存在结果集
+        {
+            ptr->cl_.reset(new _connection_lock(conn));
+            ptr->rs_.reset(rst, mysql_free_result);
+            ptr->f_ = mysql_fetch_fields(rst);
+            ptr->n_ = mysql_num_fields(rst);
+        }
+        else // 更新型操作
+        {
+            obj.set("affect_rows", static_cast<std::int64_t>(mysql_affected_rows(conn.get())));
+            obj.set("insert_id", static_cast<std::int64_t>(mysql_insert_id(conn.get())));
+        }
+        return std::move(obj);
+    }
+
+    php::array _connection_base::fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch)
+    {
+        MYSQL_ROW row;
+        unsigned long* len;
+        boost::asio::post(gcontroller->context_y, [&rst, &ch, &row, &len] () {
+            row = mysql_fetch_row(rst.get());
+            if(row) {
+                len = mysql_fetch_lengths(rst.get());
+            }
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(!row) {
+            int err = mysql_errno(conn.get());
+            if(err != 0) {
+                throw php::exception(zend_ce_exception,
+                                     (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn.get())).str(),
+                                     err);
+            }else{
+                return nullptr;
+            }
+        }
+        php::array php_row {std::size_t(n)};
+        for(int i=0;i<n;++i) {
+            php::string field(f[i].name, f[i].name_length);
+            php::value  value;
+            switch(f[i].type) {
+                case MYSQL_TYPE_DOUBLE:
+                case MYSQL_TYPE_FLOAT:
+                    value = std::strtod(row[i], nullptr);
+                    break;
+                case MYSQL_TYPE_TINY:
+                case MYSQL_TYPE_SHORT:
+                case MYSQL_TYPE_LONG:
+                case MYSQL_TYPE_INT24:
+                    value = std::strtol(row[i], nullptr, 10);
+                    break;
+                case MYSQL_TYPE_LONGLONG:
+                    value = static_cast<std::int64_t>(std::strtoll(row[i], nullptr, 10));
+                    break;
+                case MYSQL_TYPE_DATETIME: {
+                    php::object obj( php::CLASS{php_date_get_date_ce()} );
+                    obj.call("__construct", {php::string(row[i], len[i])});
+                    value = std::move(obj);
+                    break;
+                }
+                default:
+                    value = php::string(row[i], len[i]);
+            }
+            php_row.set(field, value);
+        }
+        return php_row;
+    }
 } // namespace flame::mysql
diff --git a/src/mysql/_connection_base.h b/src/mysql/_connection_base.h
index 16ba426..75240dc 100644
--- a/src/mysql/_connection_base.h
+++ b/src/mysql/_connection_base.h
@@ -11,6 +11,8 @@ namespace flame::mysql
         // 此函数仅允许 escape 主线程调用
         static void escape(std::shared_ptr<MYSQL> c, php::buffer &b, const php::value &v, char quote = '\''); // 方便使用
         virtual std::shared_ptr<MYSQL> acquire(coroutine_handler& ch) = 0;
+        php::object query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch);
+        php::array fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch);
 
       protected:
     };
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
index f0bbe07..da3c5ed 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/mysql/_connection_lock.cpp
@@ -11,12 +11,64 @@ namespace flame::mysql
     {
         
     }
+
     _connection_lock::~_connection_lock()
     {
     }
+
     std::shared_ptr<MYSQL> _connection_lock::acquire(coroutine_handler &ch)
     {
         return conn_;
     }
 
-} // namespace flame
+    void _connection_lock::begin_tx(coroutine_handler &ch)
+    {
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
+            err = mysql_real_query(conn_.get(), "START TRANSACTION", 17);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(err != 0) {
+            err = mysql_errno(conn_.get());
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
+                                 err);
+        }
+        
+    }
+
+    void _connection_lock::commit(coroutine_handler &ch)
+    {
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
+            err = mysql_real_query(conn_.get(), "COMMIT", 6);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if (err != 0)
+        {
+            err = mysql_errno(conn_.get());
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
+                                 err);
+        }
+    }
+    void _connection_lock::rollback(coroutine_handler &ch) 
+    {
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
+            err = mysql_real_query(conn_.get(), "ROLLBACK", 8);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if (err != 0)
+        {
+            err = mysql_errno(conn_.get());
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
+                                 err);
+        }
+    }
+
+} // namespace flame::mysql
diff --git a/src/mysql/_connection_lock.h b/src/mysql/_connection_lock.h
index 6fe8467..2a04ff3 100644
--- a/src/mysql/_connection_lock.h
+++ b/src/mysql/_connection_lock.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "../coroutine.h"
 #include "_connection_base.h"
 
 namespace flame::mysql
@@ -10,6 +11,9 @@ namespace flame::mysql
         _connection_lock(std::shared_ptr<MYSQL> c);
         ~_connection_lock();
         std::shared_ptr<MYSQL> acquire(coroutine_handler& ch) override;
+        void begin_tx(coroutine_handler& ch);
+        void commit(coroutine_handler& ch);
+        void rollback(coroutine_handler& ch);
       private:
         std::shared_ptr<MYSQL> conn_;
     };
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 281c12f..5663601 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -4,10 +4,12 @@
 namespace flame::mysql
 {
         
-    _connection_pool::_connection_pool(url u, std::string charset)
-        : url_(std::move(u)), charset_(charset), min_(2), max_(8), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+    _connection_pool::_connection_pool(url u)
+        : url_(std::move(u)), min_(2), max_(8), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     {
-        
+        if(!url_.query.count("charset")) {
+            url_.query["charset"] = "utf8";
+        }
     }
     _connection_pool::~_connection_pool()
     {
@@ -57,11 +59,10 @@ namespace flame::mysql
         return conn;
     }
     void _connection_pool::sweep() {
-        auto self = shared_from_this();
         tm_.expires_from_now(std::chrono::seconds(300));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
-        tm_.async_wait(boost::asio::bind_executor(guard_, [this, self] (const boost::system::error_code &error) {
-            if(error) return;
+        tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
+            if(error) return; // 当前对象销毁时会发生对应的 abort 错误
             auto now = std::chrono::steady_clock::now();
             for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)
             {
@@ -84,7 +85,7 @@ namespace flame::mysql
     }
     MYSQL* _connection_pool::create() {
         MYSQL* c = mysql_init(nullptr);
-        mysql_options(c, MYSQL_SET_CHARSET_NAME, charset_.c_str());
+        mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
         unsigned int timeout = 5; // 连接超时
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
         if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0))
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index c3390f6..f535ca6 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -10,14 +10,12 @@ namespace flame::mysql
     class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool>
     {
     public:
-        _connection_pool(url u, std::string charset);
+        _connection_pool(url u);
         ~_connection_pool();
         std::shared_ptr<MYSQL> acquire(coroutine_handler &ch) override;
         void sweep();
-
-      private:
+    private:
         url                               url_;
-        std::string                   charset_;
         const std::uint16_t               min_;
         const std::uint16_t               max_;
         std::uint16_t                    size_;
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 8d19d3b..312edcc 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -1,9 +1,9 @@
 #include "../coroutine.h"
-// #include "transaction.h"
 #include "client.h"
 #include "_connection_pool.h"
+#include "_connection_lock.h"
 #include "mysql.h"
-// #include "result.h"
+#include "tx.h"
 
 namespace flame::mysql
 {
@@ -11,6 +11,7 @@ namespace flame::mysql
     {
         php::class_entry<client> class_client("flame\\mysql\\client");
         class_client
+            .constant({"AAAA",123})
             .method<&client::__construct>("__construct", {}, php::PRIVATE)
             .method<&client::escape>("escape",
             {
@@ -57,6 +58,9 @@ namespace flame::mysql
             });
         ext.add(std::move(class_client));
     }
+    php::value client::__construct(php::parameters& params) {
+        return nullptr;
+    }
     php::value client::escape(php::parameters &params)
     {
         coroutine_handler ch {coroutine::current};
@@ -74,85 +78,75 @@ namespace flame::mysql
     }
     php::value client::begin_tx(php::parameters &params)
     {
-        // std::shared_ptr<coroutine> co = coroutine::current;
-        // c_->exec([](std::shared_ptr<MYSQL> c, int &error) -> MYSQL_RES*
-        // { // 工作线程
-        //     MYSQL *conn = c.get();
-        //     error = mysql_real_query(conn, "START TRANSACTION", 17);
-        //     assert(mysql_field_count(conn) == 0);
-        //     return nullptr;
-        // }, [co](std::shared_ptr<MYSQL> c, MYSQL_RES *r, int error) { // 主线程
-        //     MYSQL *conn = c.get();
-        //     if (error)
-        //     {
-        //         co->fail(mysql_error(conn), mysql_errno(conn));
-        //     }
-        //     else
-        //     {
-        //         php::object tx(php::class_entry<transaction>::entry());
-        //         transaction *tx_ = static_cast<transaction *>(php::native(tx));
-        //         tx_->c_.reset(new _connection_lock(c)); // 继续持有当前连接
-        //         co->resume(std::move(tx));
-        //     }
-        // });
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        auto cl = std::make_shared<_connection_lock>(conn);
+        cl->begin_tx(ch);
+        // 构建事务对象
+        php::object obj(php::class_entry<tx>::entry());
+        tx *ptr = static_cast<tx *>(php::native(obj));
+        ptr->cl_ = cl; // 继续持有当前连接
+        return std::move(obj);
     }
     php::value client::query(php::parameters &params)
     {
-        // c_->query(coroutine::current, php::object(this), params[0]);
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        return cp_->query(cp_->acquire(ch), params[0], ch);
     }
     php::value client::insert(php::parameters &params)
     {
-        // php::buffer buf;
-        // build_insert(c_, buf, params);
-        // c_->query(coroutine::current, php::object(this), std::move(buf));
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_insert(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
     php::value client::delete_(php::parameters &params)
     {
-        // php::buffer buf;
-        // build_delete(c_, buf, params);
-        // c_->query(coroutine::current, php::object(this), std::move(buf));
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_delete(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
     php::value client::update(php::parameters &params)
     {
-        // php::buffer buf;
-        // build_update(c_, buf, params);
-        // c_->query(coroutine::current, php::object(this), std::move(buf));
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_update(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
     php::value client::select(php::parameters &params)
     {
-        // php::buffer buf;
-        // build_select(c_, buf, params);
-        // c_->query(coroutine::current, php::object(this), std::move(buf));
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_select(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
     php::value client::one(php::parameters &params)
     {
-        // php::buffer buf;
-        // build_one(c_, buf, params);
-        // result::stack_fetch(coroutine::current, php::object(this), php::string(nullptr));
-        // c_->query(coroutine::current, php::object(this), std::move(buf));
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_one(conn, buf, params);
+        php::object rst = cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+        return rst.call("fetch_row");
     }
     php::value client::get(php::parameters &params)
     {
-        // php::buffer buf;
-        // build_get(c_, buf, params);
-        // result::stack_fetch(coroutine::current, php::object(this), params[1]);
-        // c_->query(coroutine::current, php::object(this), std::move(buf));
-        // return coroutine::async();
-        return nullptr;
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_get(conn, buf, params);
+        php::object rst = cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+        php::array row = rst.call("fetch_row");
+        if(!row.empty()) {
+            return row.get( static_cast<php::string>(params[1]) );
+        }else{
+            return nullptr;
+        }
     }
 
 } // namespace flame::mysql
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 854516e..25705d9 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -4,41 +4,42 @@
 #include "_connection_base.h"
 #include "_connection_pool.h"
 #include "client.h"
+#include "result.h"
+#include "tx.h"
 
 namespace flame::mysql {
-    php::value connect(php::parameters& params) {
-		php::object obj(php::class_entry<client>::entry());
-
-        client *ptr = static_cast<client *>(php::native(obj));
-        std::string charset {"utf8"};
-		if(params.size() > 1) {
-			php::array opts = params[1];
-			if(opts.exists("charset")) {
-				charset = opts.get("charset").to_string();
-			}
-		}
-        url u(params[0]);
-
-        ptr->cp_.reset(new _connection_pool(u, charset));
-        ptr->cp_->sweep(); // 启动自动清理扫描
-        // TODO 确认第一个连接建立
-        return std::move(obj);
-	}
-    void declare(php::extension_entry &ext) {
+    
+    void declare(php::extension_entry &ext)
+    {
         ext
-            .on_module_startup([](php::extension_entry &ext) -> bool {
+            .on_module_startup([](php::extension_entry &ext) -> bool
+            {
                 return mysql_library_init(0, nullptr, nullptr) == 0;
             })
-            .on_module_shutdown([](php::extension_entry &ext) -> bool {
+            .on_module_shutdown([](php::extension_entry &ext) -> bool
+            {
                 mysql_library_end();
                 return true;
             })
-            .function<connect>("flame\\mysql\\connect", {
+            .function<connect>("flame\\mysql\\connect",
+            {
                 {"url", php::TYPE::STRING},
             });
         client::declare(ext);
+        result::declare(ext);
+        tx::declare(ext);
     }
 
+    php::value connect(php::parameters& params)
+    {
+        url u(params[0]);
+        php::object obj(php::class_entry<client>::entry());
+        client *ptr = static_cast<client *>(php::native(obj));
+        ptr->cp_.reset(new _connection_pool(u));
+        ptr->cp_->sweep(); // 启动自动清理扫描
+        // TODO 确认第一个连接建立
+        return std::move(obj);
+	}
     // 相等
     static void where_eq(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
     {
diff --git a/src/mysql/result.cpp b/src/mysql/result.cpp
new file mode 100644
index 0000000..1409810
--- /dev/null
+++ b/src/mysql/result.cpp
@@ -0,0 +1,126 @@
+#include "../coroutine.h"
+#include "result.h"
+#include "_connection_lock.h"
+
+namespace flame::mysql
+{
+    void result::declare(php::extension_entry &ext)
+    {
+        php::class_entry<result> class_result("flame\\mysql\\result");
+        class_result
+            .property({"affected_rows", 0})
+            .property({"insert_id", 0})
+            .method<&result::fetch_row>("fetch_row")
+            .method<&result::fetch_all>("fetch_all");
+
+        ext.add(std::move(class_result));
+    }
+    // void result::stack_fetch(std::shared_ptr<coroutine> co, const php::object &ref, const php::string &field)
+    // {
+    //     co->stack(php::value([field](php::parameters &params) -> php::value {
+    //         php::array row = params[0];
+    //         if (!row.typeof(php::TYPE::ARRAY))
+    //             return nullptr;
+    //         if (field.typeof(php::TYPE::STRING))
+    //             return row.get(field);
+    //         else
+    //             return std::move(row);
+    //     }))
+    //         ->stack(php::value([field](php::parameters &params) -> php::value {
+    //                     php::object rs = params[0];
+    //                     result *rs_ = static_cast<result *>(php::native(rs));
+
+    //                     assert(rs.instanceof (php::class_entry<result>::entry()));
+    //                     if (rs_->n_ > 0)
+    //                     {
+    //                         return rs.call("fetch_row");
+    //                     }
+    //                     else
+    //                     {
+    //                         return nullptr;
+    //                     }
+    //                 }),
+    //                 ref);
+    // }
+    // result::~result()
+    // {
+    //     if (r_)
+    //         release();
+    // }
+    // struct fetch_row_t
+    // {
+    //     MYSQL_ROW data;
+    //     unsigned long *size;
+    // };
+    // void result::fetch_next(std::shared_ptr<coroutine> co, const php::object &ref, bool fetch_all, php::array &data_all)
+    // {
+    //     c_->exec([this](std::shared_ptr<MYSQL> c, int &error) -> MYSQL_RES * {
+    //             fetch_row_t* row = new fetch_row_t();
+    //             assert(row->data == nullptr);
+    //             if ((row->data = mysql_fetch_row(r_))) {
+    //                 row->size = mysql_fetch_lengths(r_);
+    //             }
+    //             return reinterpret_cast<MYSQL_RES*>(row); }, [this, co, ref, fetch_all, data_all](std::shared_ptr<MYSQL> c, MYSQL_RES *r, int error) mutable {
+    //             MYSQL* conn = c.get();
+    //             fetch_row_t* row = reinterpret_cast<fetch_row_t*>(r);
+    //             if(row->data == nullptr && mysql_errno(conn) != 0) {
+    //                 co->fail(mysql_error(conn), mysql_errno(conn));
+    //                 release(); // fetch 过程终止终止清理
+    //             } else if(row->data == nullptr) {
+    //                 if(fetch_all) co->resume(data_all);
+    //                 else co->resume(nullptr);
+    //                 release(); // fetch 过程终止清理
+    //             } else {
+    //                 php::array data_row((int)n_);
+    //                 for(int i = 0; i < n_; i++) {
+    //                     data_row.set(php::string(f_[i].name), php::string(row->data[i], row->size[i]));
+    //                 }
+    //                 if(fetch_all) {
+    //                     data_all.set(data_all.size(), data_row);
+    //                     fetch_next(co, ref, fetch_all, data_all);
+    //                 } else {
+    //                     co->resume(std::move(data_row));
+    //                 }
+    //             }
+    //             delete row; });
+    // }
+    php::value result::fetch_row(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        return cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch);
+    }
+    php::value result::fetch_all(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::array data {4}, row;
+        
+        if(!rs_) return nullptr;
+        for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) {
+            data.set(data.size(), row);
+        }
+        // row = cl_->fetch(conn, rs_, f_, n_, ch);
+        // data.set(data.size(), row);
+
+        // row = cl_->fetch(conn, rs_, f_, n_, ch);
+        // data.set(data.size(), row);
+
+        // row = cl_->fetch(conn, rs_, f_, n_, ch);
+        // data.set(data.size(), row);
+
+        return data;
+    }
+    // void result::release()
+    // {
+    //     std::shared_ptr<_connection_lock> cl = c_;
+    //     MYSQL_RES *r = r_;
+    //     c_.reset();
+    //     r_ = nullptr;
+    //     f_ = nullptr;
+    //     n_ = 0;
+    //     cl->exec([r](std::shared_ptr<MYSQL> c, int &error) -> MYSQL_RES * {
+    //             mysql_free_result(r);
+    //             return nullptr; }, [cl](std::shared_ptr<MYSQL> c, MYSQL_RES *r, int error) {
+    //             // cl.reset() });
+    // }
+} // namespace flame::mysql
diff --git a/src/mysql/result.h b/src/mysql/result.h
new file mode 100644
index 0000000..906da0f
--- /dev/null
+++ b/src/mysql/result.h
@@ -0,0 +1,20 @@
+#pragma once
+
+namespace flame::mysql
+{
+    class _connection_lock;
+    class result: public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value fetch_row(php::parameters &params);
+        php::value fetch_all(php::parameters &params);
+
+    private:
+        std::shared_ptr<_connection_lock> cl_;
+        std::shared_ptr<MYSQL_RES>        rs_;
+        MYSQL_FIELD                       *f_;
+        unsigned int                       n_; // Number Of Fields
+        friend class _connection_base;
+    };
+} // namespace flame::mysql
diff --git a/src/mysql/tx.cpp b/src/mysql/tx.cpp
new file mode 100644
index 0000000..2a96b50
--- /dev/null
+++ b/src/mysql/tx.cpp
@@ -0,0 +1,164 @@
+#include "../coroutine.h"
+#include "tx.h"
+#include "_connection_lock.h"
+#include "mysql.h"
+
+namespace flame::mysql
+{
+    void tx::declare(php::extension_entry &ext)
+    {
+        php::class_entry<tx> class_tx("flame\\mysql\\tx");
+        class_tx
+            .constant({"AAAA", 123})
+            .method<&tx::__construct>("__construct", {}, php::PRIVATE)
+            .method<&tx::escape>("escape",
+            {
+                {"data", php::TYPE::UNDEFINED},
+            })
+            .method<&tx::commit>("commit")
+            .method<&tx::rollback>("rollback")
+            .method<&tx::query>("query",
+            {
+                {"sql", php::TYPE::STRING},
+            })
+            .method<&tx::insert>("insert",
+            {
+                {"table", php::TYPE::STRING},
+                {"rows", php::TYPE::ARRAY},
+            })
+            .method<&tx::delete_>("delete",
+            {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::update>("update",
+            {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"set", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::select>("select",
+            {
+                {"table", php::TYPE::STRING},
+                {"fields", php::TYPE::UNDEFINED},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::one>("one",
+            {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::get>("get",
+            {
+                {"table", php::TYPE::STRING},
+                {"field", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            });
+        ext.add(std::move(class_tx));
+    }
+    php::value tx::__construct(php::parameters &params)
+    {
+        return nullptr;
+    }
+    php::value tx::escape(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        std::shared_ptr<MYSQL> conn = cl_->acquire(ch);
+
+        php::buffer buffer;
+        char quote = '\'';
+        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING))
+        {
+            php::string q = params[1];
+            if (q.data()[0] == '`')
+                quote = '`';
+        }
+        _connection_base::escape(conn, buffer, params[0], quote);
+        return std::move(buffer);
+    }
+    php::value tx::commit(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        cl_->commit(ch);
+        return nullptr;
+    }
+    php::value tx::rollback(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        cl_->rollback(ch);
+        return nullptr;
+    }
+    php::value tx::query(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        std::shared_ptr<MYSQL> conn = cl_->acquire(ch);
+        return cl_->query(conn, params[0], ch);
+    }
+    php::value tx::insert(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_insert(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+    php::value tx::delete_(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_delete(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+    php::value tx::update(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_update(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+    php::value tx::select(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_select(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+    php::value tx::one(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_one(conn, buf, params);
+        php::object rst = cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+        return rst.call("fetch_row");
+    }
+    php::value tx::get(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_get(conn, buf, params);
+        php::object rst = cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+        php::array row = rst.call("fetch_row");
+        if (!row.empty())
+        {
+            return row.get( static_cast<php::string>(params[1]) );
+        }
+        else
+        {
+            return nullptr;
+        }
+    }
+
+} // namespace flame::mysql
diff --git a/src/mysql/tx.h b/src/mysql/tx.h
new file mode 100644
index 0000000..2c70952
--- /dev/null
+++ b/src/mysql/tx.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::mysql
+{
+
+    class _connection_lock;
+    class tx : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value commit(php::parameters &params);
+        php::value rollback(php::parameters &params);
+
+        php::value escape(php::parameters &params);
+        php::value query(php::parameters &params);
+
+        php::value insert(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value update(php::parameters &params);
+        php::value select(php::parameters &params);
+        php::value one(php::parameters &params);
+        php::value get(php::parameters &params);
+
+    protected:
+        std::shared_ptr<_connection_lock> cl_;
+        friend class client;
+    };
+
+} // namespace flame::mysql
diff --git a/test/mysql_1.php b/test/mysql_1.php
index 91b2f8b..f09f052 100644
--- a/test/mysql_1.php
+++ b/test/mysql_1.php
@@ -2,13 +2,27 @@
 
 flame\init("mysql_1");
 
-flame\go(function() {
-    echo "1\n";
-    $cli = flame\mysql\connect("mysql://ucenter_rw:eVrFSRa2NLdoIPbITvPe@rm-2ze8xu255t0523w07.mysql.rds.aliyuncs.com:3306/ucenter");
-    echo "2\n";
-    var_dump( $cli->escape('a.b') );
-    echo "3\n";
-});
-
+for($i=0;$i<5;++$i) {
+    flame\go(function() {
+        $cli = flame\mysql\connect("mysql://user:password@host:port/database");
+        var_dump( $cli->escape('a.b', '`') );
+        $rs = $cli->query("SELECT * FROM `test_0` LIMIT 3");
+        $row = $rs->fetch_row();
+        var_dump($row);
+        $row = $rs->fetch_row();
+        var_dump($row);
+        $data = $rs->fetch_all();
+        var_dump($data);
+        $tx = $cli->begin_tx();
+        try{
+            $tx->insert("test_0", ["key"=>123, "val"=>"456"]);
+            $tx->update("test_0", ["key"=>123], ["val"=>"567"]);
+            $tx->commit();
+        }catch(Exception $ex) {
+            $tx->rollback();
+            return;
+        }    
+    });
+}
 
 flame\run();

From 3bc2cfaa3c8e78000ca536b46bcdad8acde94246 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 23 Nov 2018 15:06:23 +0800
Subject: [PATCH 004/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=20?=
 =?UTF-8?q?redis=20=E6=8E=A5=E5=8F=A3=E9=87=8D=E6=9E=84;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                 |   3 +
 README.md                      |   8 ++
 src/coroutine.cpp              |   4 +-
 src/coroutine.h                |   2 +-
 src/flame.cpp                  |   7 +-
 src/mysql/_connection_pool.cpp |  68 +++++++-----
 src/mysql/mysql.cpp            |   2 +-
 src/redis/_connection_base.cpp |  95 +++++++++++++++++
 src/redis/_connection_base.h   |  24 +++++
 src/redis/_connection_lock.cpp |  59 +++++++++++
 src/redis/_connection_lock.h   |  28 +++++
 src/redis/_connection_pool.cpp | 182 +++++++++++++++++++++++++++++++++
 src/redis/_connection_pool.h   |  40 ++++++++
 src/redis/client.cpp           | 176 +++++++++++++++++++++++++++++++
 src/redis/client.h             |  34 ++++++
 src/redis/redis.cpp            |  29 ++++++
 src/redis/redis.h              |   8 ++
 src/vendor.h                   |   3 +-
 test/redis_1.php               |  19 ++++
 19 files changed, 756 insertions(+), 35 deletions(-)
 create mode 100644 src/redis/_connection_base.cpp
 create mode 100644 src/redis/_connection_base.h
 create mode 100644 src/redis/_connection_lock.cpp
 create mode 100644 src/redis/_connection_lock.h
 create mode 100644 src/redis/_connection_pool.cpp
 create mode 100644 src/redis/_connection_pool.h
 create mode 100644 src/redis/client.cpp
 create mode 100644 src/redis/client.h
 create mode 100644 src/redis/redis.cpp
 create mode 100644 src/redis/redis.h
 create mode 100644 test/redis_1.php

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c571f5e..64325a0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,7 @@ set(VENDOR_PHPEXT /data/vendor/phpext-1.1.0)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.68.0)
 set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
+set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.13.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.0.0)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-0.11.6)
@@ -42,6 +43,7 @@ target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_PHPEXT}/include
     ${VENDOR_PARSER}/include
     ${VENDOR_MYSQL}/include
+    ${VENDOR_HIREDIS}/include
     ${VENDOR_MONGODB}/include/libmongoc-1.0
     ${VENDOR_MONGODB}/include/libbson-1.0
     ${VENDOR_AMQP}/include
@@ -53,6 +55,7 @@ target_link_libraries(FLAME
     ${VENDOR_HTTPPARSER}/lib/libhttp_parser.o
     ${VENDOR_PHPEXT}/lib/libphpext.a
     ${VENDOR_MYSQL}/lib/libmysqlclient.a
+    ${VENDOR_HIREDIS}/lib/libhiredis.a
     ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
     ${VENDOR_MONGODB}/lib/libbson-static-1.0.a
     ${VENDOR_AMQP}/lib/libamqpcpp.a
diff --git a/README.md b/README.md
index e210ff2..1843895 100644
--- a/README.md
+++ b/README.md
@@ -63,3 +63,11 @@ cp libhttp_parser.o /data/vendor/http-parser-2.8.1/lib
 cp http_parser.h /data/vendor/http-parser-2.8.1/include
 ```
 
+#### HiRedis
+``` Bash
+make
+PREFIX=/data/vendor/hiredis-0.14.0 make install
+rm /data/vendor/hiredis-0.14.0/lib/*.so*
+```
+
+
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 8b6bef8..9cdc67c 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -19,11 +19,11 @@ namespace flame
         // EG(fake_scope) = ctx.scope;
         EG(current_execute_data) = ctx.current_execute_data;
     }
-    void coroutine::start(php::callable fn)
+    void coroutine::start(php::callable fn, zend_execute_data* execute_data)
     {
         auto co = std::make_shared<coroutine>(std::move(fn));
         // 需要即时保存 PHP 堆栈
-        coroutine::save_context(co->php_);
+        co->php_.current_execute_data = execute_data == nullptr ? EG(current_execute_data) : execute_data;
         // 事实上的协程启动会稍后
         boost::asio::post(gcontroller->context_x, [co] {
             // co->c1_ = boost::context::callcc([co] (auto &&cc) {
diff --git a/src/coroutine.h b/src/coroutine.h
index 67c09f9..5f6cb14 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -18,7 +18,7 @@ namespace flame {
 		static coroutine* current;
 		static void save_context(php_context_t &ctx);
 		static void restore_context(php_context_t& ctx);
-		static void start(php::callable fn);
+		static void start(php::callable fn, zend_execute_data* execute_data = nullptr);
 		coroutine(php::callable&& fn);
 
 		void suspend();
diff --git a/src/flame.cpp b/src/flame.cpp
index b84beb8..a753fd3 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -2,6 +2,7 @@
 #include "core.h"
 #include "time/time.h"
 #include "mysql/mysql.h"
+#include "redis/redis.h"
 
 extern "C"
 {
@@ -29,14 +30,16 @@ extern "C"
 
         flame::declare(ext);
         flame::time::declare(ext);
+        flame::mysql::declare(ext);
+        flame::redis::declare(ext);
         // flame::os::declare(ext);
         // flame::log::declare(ext);
         // flame::udp::declare(ext);
         // flame::tcp::declare(ext);
         // flame::http::declare(ext);
-        // flame::redis::declare(ext);
+        
         // flame::rabbitmq::declare(ext);
-        flame::mysql::declare(ext);
+        
         // flame::mongodb::declare(ext);
         // flame::kafka::declare(ext);
         return ext;
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 5663601..e29d9be 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -11,6 +11,7 @@ namespace flame::mysql
             url_.query["charset"] = "utf8";
         }
     }
+    
     _connection_pool::~_connection_pool()
     {
         while (!conn_.empty())
@@ -50,39 +51,23 @@ namespace flame::mysql
             if (size_ >= max_) return; // 已建立了足够多的连接, 需要等待已分配连接释放
 
             MYSQL* c = create();
-            ++size_; // 当前还存在的连接数量
-            release(c);
+            if(c == nullptr) {
+                await_.pop_back();
+                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            }else{
+                ++size_; // 当前还存在的连接数量
+                release(c);
+            }
         });
         // 暂停, 等待连接获取(异步任务)
         ch.suspend();
+        if(!conn) {
+            throw php::exception(zend_ce_exception, "failed to connect to MySQL server", -1);
+        }
         // 恢复, 已经填充连接
         return conn;
     }
-    void _connection_pool::sweep() {
-        tm_.expires_from_now(std::chrono::seconds(300));
-        // 注意, 实际的清理流程需要保证 guard_ 串行流程
-        tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
-            if(error) return; // 当前对象销毁时会发生对应的 abort 错误
-            auto now = std::chrono::steady_clock::now();
-            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)
-            {
-                // 超低水位，关闭不活跃连接
-                auto duration = now - (*i).ttl;
-                if (duration > std::chrono::seconds(60))
-                {
-                    mysql_close((*i).conn);
-                    --size_;
-                    i = conn_.erase(i);
-                }
-                else
-                {
-                    ++i;
-                } // 所有连接还活跃（即使超过低水位）
-            }
-            // 再次启动
-            sweep();
-        }));
-    }
+
     MYSQL* _connection_pool::create() {
         MYSQL* c = mysql_init(nullptr);
         mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
@@ -90,10 +75,11 @@ namespace flame::mysql
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
         if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0))
         {
-            throw std::runtime_error("failed to connect mysql server");
+            return nullptr;
         }
         return c;
     }
+
     void _connection_pool::release(MYSQL *c)
     {
         if (await_.empty())
@@ -113,4 +99,30 @@ namespace flame::mysql
         }
     }
 
+    void _connection_pool::sweep() {
+        tm_.expires_from_now(std::chrono::seconds(300));
+        // 注意, 实际的清理流程需要保证 guard_ 串行流程
+        tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
+            if(error) return; // 当前对象销毁时会发生对应的 abort 错误
+            auto now = std::chrono::steady_clock::now();
+            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)
+            {
+                // 超低水位，关闭不活跃或已丢失的连接
+                auto duration = now - (*i).ttl;
+                if (duration > std::chrono::seconds(60) || mysql_ping(conn_.front().conn) != 0)
+                {
+                    mysql_close((*i).conn);
+                    --size_;
+                    i = conn_.erase(i);
+                }
+                else
+                {
+                    ++i;
+                } // 所有连接还活跃（即使超过低水位）
+            }
+            // 再次启动
+            sweep();
+        }));
+    }
+
 } // namespace flame::mysql
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 25705d9..cc87cdb 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -37,7 +37,7 @@ namespace flame::mysql {
         client *ptr = static_cast<client *>(php::native(obj));
         ptr->cp_.reset(new _connection_pool(u));
         ptr->cp_->sweep(); // 启动自动清理扫描
-        // TODO 确认第一个连接建立
+        // TODO 优化: 确认第一个连接建立 ?
         return std::move(obj);
 	}
     // 相等
diff --git a/src/redis/_connection_base.cpp b/src/redis/_connection_base.cpp
new file mode 100644
index 0000000..1613a52
--- /dev/null
+++ b/src/redis/_connection_base.cpp
@@ -0,0 +1,95 @@
+#include "_connection_base.h"
+
+namespace flame::redis {
+    static php::value simple2value(redisReply* rp) {
+        if(!rp) {
+            return nullptr;
+        }
+        switch (rp->type)
+        {
+        case REDIS_REPLY_STATUS:
+        case REDIS_REPLY_ERROR:
+        case REDIS_REPLY_STRING:
+            return php::string(rp->str, rp->len);
+        case REDIS_REPLY_INTEGER:
+            return static_cast<std::int64_t>(rp->integer);
+        case REDIS_REPLY_ARRAY: {
+            php::array data(rp->elements);
+            for(int i=0;i<rp->elements;++i) {
+                data.set(data.size(), simple2value(rp->element[i]));
+            }
+            return data;
+        }
+        case REDIS_REPLY_NIL:
+        default:
+            return nullptr;
+        }
+    }
+    php::value _connection_base::reply2value(redisReply* rp, php::array &argv, reply_type rt)
+    {
+        switch(rt)
+        {
+            case reply_type::ASSOC_ARRAY_1:
+            {
+                php::array data(rp->elements/2 + 1);
+                for(int i = 0; i < rp->elements; i += 2) {
+                    php::string key = simple2value(rp->element[i]).to_string();
+                    data.set(key, simple2value(rp->element[i+1]));
+                }
+                return data;
+            }
+            case reply_type::ASSOC_ARRAY_2:
+            {
+                assert(rp->elements == 2 && rp->element[0]->type == REDIS_REPLY_STRING && rp->element[1]->type == REDIS_REPLY_ARRAY);
+                php::array wrap(2);
+                php::array data(rp->element[1]->elements/2 + 1);
+                wrap.set(0, simple2value(rp->element[0]));
+                for(int i = 0; i < rp->element[1]->elements; i += 2) {
+                    php::string key = simple2value(rp->element[1]->element[i]).to_string();
+                    data.set(key, simple2value(rp->element[1]->element[i+1]));
+                }
+                wrap.set(1, data); 
+                return wrap;
+            }
+            case reply_type::COMBINE_1:
+            {
+                php::array data(rp->elements);
+                for(int i = 0; i < rp->elements; ++i)
+                {
+                    php::string key = argv[i].to_string();
+                    data.set(key, simple2value(rp->element[i]));
+                }
+                return data;
+            }
+            case reply_type::COMBINE_2:
+            {
+                php::array data(rp->elements);
+                for (int i = 0; i < rp->elements; ++i)
+                {
+                    php::string key = argv[i+1].to_string();
+                    data.set(key, simple2value(rp->element[i]));
+                }
+                return data;
+            }
+            case reply_type::SIMPLE:
+            default:
+                return simple2value(rp);
+        }
+    }
+
+    std::string _connection_base::format(php::string& name, php::array& argv)
+    {
+        std::ostringstream ss;
+        ss << "*" << 1 + argv.size() << "\r\n$" << name.size() << "\r\n" << name << "\r\n";
+        for (auto i = argv.begin(); i != argv.end(); ++i)
+        {
+            php::string v = i->second.to_string();
+            if(v.size() > 0) {
+                ss << "$" << v.size() << "\r\n" << v << "\r\n";;
+            }else{
+                ss << "$0\r\n\r\n";
+            }
+        }
+        return ss.str();
+    }
+}
\ No newline at end of file
diff --git a/src/redis/_connection_base.h b/src/redis/_connection_base.h
new file mode 100644
index 0000000..d2b018e
--- /dev/null
+++ b/src/redis/_connection_base.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::redis
+{
+    enum class reply_type
+    {
+        SIMPLE        = 0x00,  // 默认返回处理
+        ASSOC_ARRAY_1 = 0x01,  // 返回数据多项按 KEY VAL 生成关联数组
+        ASSOC_ARRAY_2 = 0x02,  // 第一层普通数组, 第二层关联数组 (HSCAN/ZSCAN)
+        COMBINE_1     = 0x04,  // 与参数结合生成关联数组
+        COMBINE_2     = 0x08,  // 同上, 但偏移错位 1 个参数
+        EXEC          = 0x100, // 事务特殊处理方式（需要结合上面几种方式）
+        PIPE          = 0x200, // 与事务形式相同
+    };
+    class _connection_base
+    {
+    public:
+        virtual std::shared_ptr<redisContext> acquire(coroutine_handler &ch) = 0;
+        php::value reply2value(redisReply *rp, php::array &argv, reply_type rt);
+        std::string format(php::string& name, php::array& argv);
+    };
+} // namespace flame::mysql
diff --git a/src/redis/_connection_lock.cpp b/src/redis/_connection_lock.cpp
new file mode 100644
index 0000000..b08ea41
--- /dev/null
+++ b/src/redis/_connection_lock.cpp
@@ -0,0 +1,59 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "_connection_base.h"
+#include "_connection_lock.h"
+
+namespace flame::redis
+{
+
+    _connection_lock::_connection_lock(std::shared_ptr<redisContext> c)
+        : conn_(c)
+    {
+    }
+
+    _connection_lock::~_connection_lock()
+    {
+    }
+
+    std::shared_ptr<redisContext> _connection_lock::acquire(coroutine_handler &ch)
+    {
+        return conn_;
+    }
+
+    void _connection_lock::push(php::string &name, php::array &argv, reply_type type)
+    {
+        // 暂未提交, 仅记录
+        // TODO 优化: 是否可以在提交前在进行 format 操作 (减少内存占用的持续时间) ?
+        cmds_.push_back({name, argv, type, format(name, argv)});
+    }
+    void _connection_lock::push(php::string &name, php::parameters& params, reply_type type) {
+        php::array argv {params};
+        push(name, argv, type);
+    }
+    php::array _connection_lock::exec(coroutine_handler &ch) 
+    {
+        boost::asio::post(gcontroller->context_x, [this, &ch] ()
+        {
+            // 在工作线程中, 提交所有待执行命令
+            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) {
+                redisAppendFormattedCommand(conn_.get(), i->strs.c_str(), i->strs.size());
+            }
+            // 读取对应的返回值
+            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) {
+                redisGetReply(conn_.get(), (void**)&i->reply);
+            }
+            // 回到主线程
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        // 整合个命令返回值
+        php::array data(cmds_.size());
+        for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) {
+            data.set(data.size(), reply2value(i->reply, i->argv, i->type));
+        }
+        // 命令执行完毕 (所有返回值处理完成)
+        cmds_.clear();
+        return std::move(data);
+    }
+
+} // namespace flame::redis
diff --git a/src/redis/_connection_lock.h b/src/redis/_connection_lock.h
new file mode 100644
index 0000000..f71c8f9
--- /dev/null
+++ b/src/redis/_connection_lock.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "_connection_base.h"
+
+namespace flame::redis
+{
+class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock>
+{
+  public:
+    _connection_lock(std::shared_ptr<redisContext> c);
+    ~_connection_lock();
+    std::shared_ptr<redisContext> acquire(coroutine_handler &ch) override;
+    void push(php::string &name, php::array &argv, reply_type rt);
+    void push(php::string &name, php::parameters &argv, reply_type rt);
+    php::array exec(coroutine_handler& ch);
+    struct command_t {
+        php::string name;
+        php::array  argv;
+        reply_type  type;
+        std::string strs;
+        redisReply* reply;
+    };
+  private:
+    std::shared_ptr<redisContext> conn_;
+    std::list<command_t> cmds_;
+};
+} // namespace flame::redis
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
new file mode 100644
index 0000000..ffec4ab
--- /dev/null
+++ b/src/redis/_connection_pool.cpp
@@ -0,0 +1,182 @@
+#include "../controller.h"
+#include "_connection_pool.h"
+
+namespace flame::redis
+{
+
+    _connection_pool::_connection_pool(url u)
+        : url_(std::move(u)), min_(1), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+    {
+        
+    }
+    _connection_pool::~_connection_pool()
+    {
+        while (!conn_.empty())
+        {
+            redisFree(conn_.front().conn);
+            conn_.pop_front();
+        }
+    }
+
+    std::shared_ptr<redisContext> _connection_pool::acquire(coroutine_handler &ch)
+    {
+        std::shared_ptr<redisContext> conn;
+        // 提交异步任务
+        boost::asio::post(guard_, [this, &conn, &ch]() {
+            // 设置对应的回调, 在获取连接后恢复协程
+            await_.push_back([&conn, &ch](std::shared_ptr<redisContext> c) {
+                conn = c;
+                // RESUME 需要在主线程进行
+                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            });
+            while (!conn_.empty())
+            {
+                if(ping(conn_.front().conn))
+                {
+                    // 可用连接
+                    redisContext *c = conn_.front().conn;
+                    conn_.pop_front();
+                    release(c);
+                    return;
+                }
+                else
+                { // 连接已丢失，回收资源
+                    redisFree(conn_.front().conn);
+                    conn_.pop_front();
+                    --size_;
+                }
+            }
+            if (size_ >= max_)
+                return; // 已建立了足够多的连接, 需要等待已分配连接释放
+
+            redisContext *c = create();
+            if (c == nullptr)
+            {
+                await_.pop_back();
+                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            }
+            else
+            {
+                ++size_; // 当前还存在的连接数量
+                release(c);
+            }
+        });
+        // 暂停, 等待连接获取(异步任务)
+        ch.suspend();
+        if (!conn)
+        {
+            throw php::exception(zend_ce_exception, "failed to connect to MySQL server", -1);
+        }
+        // 恢复, 已经填充连接
+        return conn;
+    }
+    
+    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler& ch)
+    {
+        std::string ss = format(name, argv);
+        redisReply *rp = nullptr;
+        boost::asio::post(gcontroller->context_y, [&rc, &ss, &ch, &rp]() {
+            redisAppendFormattedCommand(rc.get(), ss.c_str(), ss.size());
+            redisGetReply(rc.get(), (void **)&rp);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        php::value rv = reply2value(rp, argv, rt);
+        freeReplyObject(rp);
+        return std::move(rv);
+    }
+    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler& ch)
+    {
+        php::array data {argv};
+        return exec(rc, name, data, rt, ch);
+    }
+
+    redisContext *_connection_pool::create()
+    {
+        struct timeval tv {5, 0};
+        redisContext* c = redisConnectWithTimeout(url_.host.c_str(), url_.port, tv);
+        if(!c || c->err) {
+            return nullptr;
+        }
+        if(!url_.pass.empty()) {
+            // 认证
+            redisReply* rp = (redisReply*)redisCommand(c, "AUTH %s", url_.pass.c_str());
+            if(!rp || rp->type == REDIS_REPLY_ERROR) {
+                if(rp) {
+                    redisFree(c);
+                    freeReplyObject(rp);
+                }
+                return nullptr;
+            }
+        }
+        if(url_.path.length() > 1) {
+            // 指定数据库
+            redisReply *rp = (redisReply *)redisCommand(c, "SELECT %d", std::atoi(url_.path.c_str() + 1));
+            if(!rp || rp->type == REDIS_REPLY_ERROR) {
+                if(rp) {
+                    redisFree(c);
+                    freeReplyObject(rp);
+                }
+                return nullptr;
+            }
+        }
+        return c;
+    }
+    void _connection_pool::release(redisContext *c)
+    {
+        if (await_.empty())
+        { // 无等待分配的请求
+            conn_.push_back({c, std::chrono::steady_clock::now()});
+        }
+        else
+        { // 立刻分配使用
+            std::function<void(std::shared_ptr<redisContext> c)> cb = await_.front();
+            await_.pop_front();
+            auto ptr = this->shared_from_this();
+            cb(
+                std::shared_ptr<redisContext>(c, [this, ptr] (redisContext *c)
+                {
+                    boost::asio::post(guard_, std::bind(&_connection_pool::release, ptr, c));
+                })
+            );
+        }
+    }
+    bool _connection_pool::ping(redisContext* c) {
+        redisReply *rp = (redisReply *)redisCommand(c, "PING");
+        if (!rp || rp->type == REDIS_REPLY_ERROR)
+        {
+            if (rp) freeReplyObject(rp);
+            return false;
+        }
+        return true;
+    }
+
+    void _connection_pool::sweep()
+    {
+        tm_.expires_from_now(std::chrono::seconds(300));
+        // 注意, 实际的清理流程需要保证 guard_ 串行流程
+        tm_.async_wait(boost::asio::bind_executor(guard_, [this](const boost::system::error_code &error) {
+            if (error)
+                return; // 当前对象销毁时会发生对应的 abort 错误
+            auto now = std::chrono::steady_clock::now();
+            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)
+            {
+                // 超低水位，关闭不活跃连接
+                auto duration = now - (*i).ttl;
+                if (duration > std::chrono::seconds(60) || !ping((*i).conn))
+                {
+                    redisFree((*i).conn);
+                    --size_;
+                    i = conn_.erase(i);
+                }
+                else
+                {
+                    ++i;
+                } // 所有连接还活跃（即使超过低水位）
+            }
+            // 再次启动
+            sweep();
+        }));
+    }
+
+} // namespace flame::redis
diff --git a/src/redis/_connection_pool.h b/src/redis/_connection_pool.h
new file mode 100644
index 0000000..b270e63
--- /dev/null
+++ b/src/redis/_connection_pool.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "../url.h"
+#include "_connection_base.h"
+
+
+namespace flame::redis
+{
+    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool>
+    {
+    public:
+        _connection_pool(url u);
+        ~_connection_pool();
+        std::shared_ptr<redisContext> acquire(coroutine_handler &ch) override;
+        php::value exec(std::shared_ptr<redisContext> rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler &ch);
+        php::value exec(std::shared_ptr<redisContext> rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler &ch);
+        void sweep();
+    private:
+        url                               url_;
+        const std::uint16_t               min_;
+        const std::uint16_t               max_;
+        std::uint16_t                    size_;
+
+        boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题;
+        std::list<std::function<void(std::shared_ptr<redisContext>)>> await_;
+        struct connection_t
+        {
+            redisContext *conn;
+            std::chrono::time_point<std::chrono::steady_clock> ttl;
+        };
+        std::list<connection_t> conn_;
+        boost::asio::steady_timer tm_;
+
+        bool ping(redisContext* c);
+        redisContext *create();
+        void release(redisContext *c);
+    };
+
+} // namespace flame::mysql
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
new file mode 100644
index 0000000..b839d8d
--- /dev/null
+++ b/src/redis/client.cpp
@@ -0,0 +1,176 @@
+#include "../coroutine.h"
+#include "client.h"
+#include "_connection_pool.h"
+#include "_connection_lock.h"
+
+namespace flame::redis {
+	void client::declare(php::extension_entry& ext) {
+		php::class_entry<client> class_client("flame\\redis\\client");
+		class_client
+			.method<&client::__construct>("__construct", {}, php::PRIVATE)
+			.method<&client::__call>("__call", {
+				{"cmd", php::TYPE::STRING},
+				{"arg", php::TYPE::ARRAY},
+			})
+			.method<&client::mget>("mget", {
+				{"key", php::TYPE::STRING},
+			})
+			.method<&client::hmget>("hmget", {
+				{"hash", php::TYPE::STRING},
+			})
+			.method<&client::hgetall>("hgetall", {
+				{"hash", php::TYPE::STRING},
+			})
+			.method<&client::hscan>("hscan", {
+				{"hash", php::TYPE::STRING},
+				{"cursor", php::TYPE::INTEGER},
+			})
+			.method<&client::zscan>("zscan", {
+				{"zset", php::TYPE::STRING},
+				{"cursor", php::TYPE::INTEGER},
+			})
+			.method<&client::zrange>("zrange", {
+				{"zset", php::TYPE::STRING},
+				{"start", php::TYPE::INTEGER},
+				{"stop", php::TYPE::INTEGER},
+			})
+			.method<&client::zrevrange>("zrevrange", {
+				{"zset", php::TYPE::STRING},
+				{"start", php::TYPE::INTEGER},
+				{"stop", php::TYPE::INTEGER},
+			})
+			.method<&client::zrangebyscore>("zrangebyscore", {
+				{"zset", php::TYPE::STRING},
+				{"min", php::TYPE::STRING},
+				{"max", php::TYPE::STRING},
+			})
+			.method<&client::zrevrangebyscore>("zrevrangebyscore", {
+				{"zset", php::TYPE::STRING},
+				{"min", php::TYPE::STRING},
+				{"max", php::TYPE::STRING},
+			})
+			.method<&client::multi>("multi")
+			// 以下暂未实现
+			.method<&client::unimplement>("subscribe")
+			.method<&client::unimplement>("psubscribe")
+            .method<&client::unimplement>("unsubscribe")
+			.method<&client::unimplement>("punsubscribe");
+        
+		ext.add(std::move(class_client));
+	}
+
+	php::value client::__construct(php::parameters& params)
+	{
+		return nullptr;
+	}
+	php::value client::__call(php::parameters& params)
+	{
+        coroutine_handler ch {coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name = params[0];
+        php::array  argv = params[1];
+        return cp_->exec(rc, name, argv, reply_type::SIMPLE, ch);
+	}
+	php::value client::mget(php::parameters& params)
+	{
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("MGET", 4);
+		return cp_->exec(rc, name, params, reply_type::COMBINE_1, ch);
+	}
+	php::value client::hmget(php::parameters& params)
+	{
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("HMGET", 5);
+		return cp_->exec(rc, name, params, reply_type::COMBINE_2, ch);
+	}
+	php::value client::hgetall(php::parameters& params)
+	{
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("HGETALL", 7);
+		return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+	}
+	php::value client::hscan(php::parameters& params) {
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("HSCAN", 5);
+		return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
+	}
+	php::value client::zscan(php::parameters& params) {
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("ZSCAN", 5);
+		return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
+	}
+	php::value client::zrange(php::parameters& params) {
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("ZRANGE", 6);
+		php::string last = params[params.size()-1];
+		if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+		{
+			return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+		}
+		else
+		{
+			return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+		}
+	} 
+	php::value client::zrevrange(php::parameters& params) {
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("ZREVRANGE", 9);
+		php::string last = params[params.size() - 1];
+		if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+		{
+			return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+		}
+		else
+		{
+			return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+		}
+	}
+	php::value client::zrangebyscore(php::parameters& params) {
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("ZRANGEBYSCORE", 13);
+
+		for(int i=3; i<params.size(); ++i) {
+			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+				php::string arg = params[i];
+				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+					return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+				}
+			}
+		}
+		return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+	}
+	php::value client::zrevrangebyscore(php::parameters& params) {
+		coroutine_handler ch{coroutine::current};
+		auto rc = cp_->acquire(ch);
+		php::string name("ZREVRANGEBYSCORE", 16);
+
+		for(int i=3; i<params.size(); ++i) {
+			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+				php::string arg = params[i];
+				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+					return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+				}
+			}
+		}
+		return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+	}
+	php::value client::multi(php::parameters& params) {
+        // coroutine_handler ch{coroutine::current};
+        // auto conn_ = cp_->acquire(ch);
+		// php::object obj(php::class_entry<tx>::entry());
+		// tx* ptr = static_cast<tx*>(php::native(tx));
+		// ptr->cl_ = _connection_lock(conn_);
+		// return std::move(tx);
+	}
+	php::value client::unimplement(php::parameters& params) {
+        throw php::exception(zend_ce_error, "This redis command is NOT yet implemented");
+    }
+} // namespace flame::redis
diff --git a/src/redis/client.h b/src/redis/client.h
new file mode 100644
index 0000000..9aaef19
--- /dev/null
+++ b/src/redis/client.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::redis
+{
+    class _connection_pool;
+    class client : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value __call(php::parameters &params);
+        // 处理特殊情况的命令
+        php::value mget(php::parameters &params);
+        php::value hmget(php::parameters &params);
+        php::value hgetall(php::parameters &params);
+        php::value hscan(php::parameters &params);
+        php::value sscan(php::parameters &params);
+        php::value zscan(php::parameters &params);
+        php::value zrange(php::parameters &params);
+        php::value zrevrange(php::parameters &params);
+        php::value zrangebyscore(php::parameters &params);
+        php::value zrevrangebyscore(php::parameters &params);
+        php::value unsubscribe(php::parameters &params);
+        php::value punsubscribe(php::parameters &params);
+        // 批量
+        php::value multi(php::parameters &params);
+        // 用于标记不实现的功能
+        php::value unimplement(php::parameters &params);
+    private:
+        std::shared_ptr<_connection_pool> cp_;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::redis
\ No newline at end of file
diff --git a/src/redis/redis.cpp b/src/redis/redis.cpp
new file mode 100644
index 0000000..37882e8
--- /dev/null
+++ b/src/redis/redis.cpp
@@ -0,0 +1,29 @@
+#include "redis.h"
+#include "_connection_pool.h"
+#include "client.h"
+
+namespace flame::redis
+{
+    void declare(php::extension_entry& ext)
+    {
+		ext.function<connect>("flame\\redis\\connect",
+        {
+			{"uri", php::TYPE::STRING}
+		});
+		client::declare(ext);
+		// tx::declare(ext);
+	}
+
+    php::value connect(php::parameters &params)
+    {
+        url u(params[0]);
+        php::object obj {php::class_entry<client>::entry()};
+        client *ptr = static_cast<client *>(php::native(obj));
+        ptr->cp_.reset(new _connection_pool(u));
+        ptr->cp_->sweep(); // 启动自动清理扫描
+        // TODO 优化: 确认第一个连接建立 ?
+        return std::move(obj);
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/redis/redis.h b/src/redis/redis.h
new file mode 100644
index 0000000..3369ee9
--- /dev/null
+++ b/src/redis/redis.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::redis
+{
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters &params);
+} // namespace flame::redis
\ No newline at end of file
diff --git a/src/vendor.h b/src/vendor.h
index 1cdd2d1..afa18a4 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -18,6 +18,8 @@
 #include <stack>
 #include <unordered_set>
 #include <phpext/phpext.h>
+#include <http_parser.h>
+#include <hiredis/hiredis.h>
 #include <parser/separator_parser.hpp>
 #include <parser/multipart_parser.hpp>
 #include <boost/version.hpp>
@@ -44,4 +46,3 @@ namespace ssl = boost::asio::ssl;
 #include <my_sys.h>
 #include <mongoc.h>
 #include <librdkafka/rdkafka.h>
-#include <http_parser.h>
diff --git a/test/redis_1.php b/test/redis_1.php
new file mode 100644
index 0000000..bd56d95
--- /dev/null
+++ b/test/redis_1.php
@@ -0,0 +1,19 @@
+<?php
+flame\init("redis_1");
+
+flame\go(function() {
+    $cli = flame\redis\connect("redis://auth:password@host:port/database");
+    var_dump( $cli->set("test1", "this is a text") );
+    var_dump( $cli->get("test1") );
+    var_dump( $cli->del("test1") );
+    var_dump( $cli->get("test1") );
+    var_dump( $cli->set("test2", 123456) );
+    var_dump( $cli->get("test2") );
+    var_dump( $cli->incr("test2") );
+    var_dump( $cli->del("test2") );
+
+    var_dump( $cli->zrange("rank:relay_20181122", 0, 30, "WITHSCORES") );
+    var_dump( $cli->hgetall("rank:relay_201811") );
+});
+
+flame\run();

From 19caadb41ac56e45def08a5864c8bf854ec19b09 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 23 Nov 2018 16:08:38 +0800
Subject: [PATCH 005/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20Redis=20=E6=94=AF?=
 =?UTF-8?q?=E6=8C=81=20multi=20=E6=89=B9=E9=87=8F=E6=93=8D=E4=BD=9C;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mysql/_connection_pool.cpp |   2 +-
 src/redis/_connection_pool.cpp |   2 +-
 src/redis/client.cpp           |  13 +--
 src/redis/redis.cpp            |   3 +-
 src/redis/tx.cpp               | 175 +++++++++++++++++++++++++++++++++
 src/redis/tx.h                 |  36 +++++++
 test/redis_1.php               |  10 +-
 7 files changed, 230 insertions(+), 11 deletions(-)
 create mode 100644 src/redis/tx.cpp
 create mode 100644 src/redis/tx.h

diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index e29d9be..2b1f5ee 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -100,7 +100,7 @@ namespace flame::mysql
     }
 
     void _connection_pool::sweep() {
-        tm_.expires_from_now(std::chrono::seconds(300));
+        tm_.expires_from_now(std::chrono::seconds(60));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
         tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
             if(error) return; // 当前对象销毁时会发生对应的 abort 错误
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index ffec4ab..6d20747 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -153,7 +153,7 @@ namespace flame::redis
 
     void _connection_pool::sweep()
     {
-        tm_.expires_from_now(std::chrono::seconds(300));
+        tm_.expires_from_now(std::chrono::seconds(60));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
         tm_.async_wait(boost::asio::bind_executor(guard_, [this](const boost::system::error_code &error) {
             if (error)
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index b839d8d..bd642b5 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -1,6 +1,7 @@
 #include "../coroutine.h"
 #include "client.h"
 #include "_connection_pool.h"
+#include "tx.h"
 #include "_connection_lock.h"
 
 namespace flame::redis {
@@ -163,12 +164,12 @@ namespace flame::redis {
 		return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
 	}
 	php::value client::multi(php::parameters& params) {
-        // coroutine_handler ch{coroutine::current};
-        // auto conn_ = cp_->acquire(ch);
-		// php::object obj(php::class_entry<tx>::entry());
-		// tx* ptr = static_cast<tx*>(php::native(tx));
-		// ptr->cl_ = _connection_lock(conn_);
-		// return std::move(tx);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+		php::object obj(php::class_entry<tx>::entry());
+		tx *ptr = static_cast<tx *>(php::native(obj));
+		ptr->cl_.reset(new _connection_lock(conn_));
+		return std::move(obj);
 	}
 	php::value client::unimplement(php::parameters& params) {
         throw php::exception(zend_ce_error, "This redis command is NOT yet implemented");
diff --git a/src/redis/redis.cpp b/src/redis/redis.cpp
index 37882e8..21e0010 100644
--- a/src/redis/redis.cpp
+++ b/src/redis/redis.cpp
@@ -1,6 +1,7 @@
 #include "redis.h"
 #include "_connection_pool.h"
 #include "client.h"
+#include "tx.h"
 
 namespace flame::redis
 {
@@ -11,7 +12,7 @@ namespace flame::redis
 			{"uri", php::TYPE::STRING}
 		});
 		client::declare(ext);
-		// tx::declare(ext);
+		tx::declare(ext);
 	}
 
     php::value connect(php::parameters &params)
diff --git a/src/redis/tx.cpp b/src/redis/tx.cpp
new file mode 100644
index 0000000..c8aa1c1
--- /dev/null
+++ b/src/redis/tx.cpp
@@ -0,0 +1,175 @@
+#include "../coroutine.h"
+#include "tx.h"
+#include "_connection_lock.h"
+
+namespace flame::redis {
+	void tx::declare(php::extension_entry& ext) {
+		php::class_entry<tx> class_tx("flame\\redis\\tx");
+		class_tx
+			.method<&tx::__construct>("__construct", {}, php::PRIVATE)
+            .method<&tx::exec>("exec")
+			.method<&tx::__call>("__call",
+            {
+				{"cmd", php::TYPE::STRING},
+				{"arg", php::TYPE::ARRAY},
+			})
+			.method<&tx::mget>("mget",
+            {
+				{"key", php::TYPE::STRING},
+			})
+			.method<&tx::hmget>("hmget",
+            {
+				{"hash", php::TYPE::STRING},
+			})
+			.method<&tx::hgetall>("hgetall",
+            {
+				{"hash", php::TYPE::STRING},
+			})
+			.method<&tx::hscan>("hscan",
+            {
+				{"hash", php::TYPE::STRING},
+				{"cursor", php::TYPE::INTEGER},
+			})
+			.method<&tx::zscan>("zscan",
+            {
+				{"zset", php::TYPE::STRING},
+				{"cursor", php::TYPE::INTEGER},
+			})
+			.method<&tx::zrange>("zrange",
+            {
+				{"zset", php::TYPE::STRING},
+				{"start", php::TYPE::INTEGER},
+				{"stop", php::TYPE::INTEGER},
+			})
+			.method<&tx::zrevrange>("zrevrange",
+            {
+				{"zset", php::TYPE::STRING},
+				{"start", php::TYPE::INTEGER},
+				{"stop", php::TYPE::INTEGER},
+			})
+			.method<&tx::zrangebyscore>("zrangebyscore",
+            {
+				{"zset", php::TYPE::STRING},
+				{"min", php::TYPE::STRING},
+				{"max", php::TYPE::STRING},
+			})
+			.method<&tx::zrevrangebyscore>("zrevrangebyscore",
+            {
+				{"zset", php::TYPE::STRING},
+				{"min", php::TYPE::STRING},
+				{"max", php::TYPE::STRING},
+			})
+			.method<&tx::unimplement>("multi")
+			// 以下暂未实现
+			.method<&tx::unimplement>("subscribe")
+			.method<&tx::unimplement>("psubscribe")
+            .method<&tx::unimplement>("unsubscribe")
+			.method<&tx::unimplement>("punsubscribe");
+        
+		ext.add(std::move(class_tx));
+	}
+
+	php::value tx::__construct(php::parameters& params)
+	{
+		return nullptr;
+	}
+    php::value tx::exec(php::parameters& params)
+    {
+        coroutine_handler ch {coroutine::current};
+        return cl_->exec(ch);
+    }
+	php::value tx::__call(php::parameters& params)
+	{
+        php::string name = params[0];
+        php::array  argv = params[1];
+        cl_->push(name, argv, reply_type::SIMPLE);
+        return this;
+	}
+	php::value tx::mget(php::parameters& params)
+	{
+		php::string name("MGET", 4);
+		cl_->push(name, params, reply_type::COMBINE_1);
+        return this;
+    }
+	php::value tx::hmget(php::parameters& params)
+	{
+		php::string name("HMGET", 5);
+        cl_->push(name, params, reply_type::COMBINE_2);
+        return this;
+    }
+	php::value tx::hgetall(php::parameters& params)
+	{
+		php::string name("HGETALL", 7);
+        cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+        return this;
+    }
+	php::value tx::hscan(php::parameters& params) {
+		php::string name("HSCAN", 5);
+		cl_->push(name, params, reply_type::ASSOC_ARRAY_2);
+        return this;
+    }
+	php::value tx::zscan(php::parameters& params) {
+		php::string name("ZSCAN", 5);
+        cl_->push(name, params, reply_type::ASSOC_ARRAY_2);
+        return this;
+    }
+	php::value tx::zrange(php::parameters& params) {
+		php::string name("ZRANGE", 6);
+		php::string last = params[params.size()-1];
+		if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+		{
+            cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+        }
+		else
+		{
+            cl_->push(name, params, reply_type::SIMPLE);
+        }
+        return this;
+    } 
+	php::value tx::zrevrange(php::parameters& params) {
+		php::string name("ZREVRANGE", 9);
+		php::string last = params[params.size() - 1];
+		if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+		{
+            cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+        }
+		else
+		{
+            cl_->push(name, params, reply_type::SIMPLE);
+        }
+        return this;
+    }
+	php::value tx::zrangebyscore(php::parameters& params) {
+		php::string name("ZRANGEBYSCORE", 13);
+
+		for(int i=3; i<params.size(); ++i) {
+			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+				php::string arg = params[i];
+				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+                    cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+                    return this;
+                }
+			}
+		}
+        cl_->push(name, params, reply_type::SIMPLE);
+        return this;
+    }
+	php::value tx::zrevrangebyscore(php::parameters& params) {
+		php::string name("ZREVRANGEBYSCORE", 16);
+
+		for(int i=3; i<params.size(); ++i) {
+			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+				php::string arg = params[i];
+				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+                    cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+                    return this;
+                }
+			}
+		}
+        cl_->push(name, params, reply_type::SIMPLE);
+        return this;
+    }
+	php::value tx::unimplement(php::parameters& params) {
+        throw php::exception(zend_ce_error, "This redis command is NOT yet implemented");
+    }
+} // namespace flame::redis
diff --git a/src/redis/tx.h b/src/redis/tx.h
new file mode 100644
index 0000000..5f09c6a
--- /dev/null
+++ b/src/redis/tx.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::redis
+{
+    class _connection_lock;
+    class tx : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value exec(php::parameters& params);
+        php::value __call(php::parameters &params);
+        // 处理特殊情况的命令
+        php::value mget(php::parameters &params);
+        php::value hmget(php::parameters &params);
+        php::value hgetall(php::parameters &params);
+        php::value hscan(php::parameters &params);
+        php::value sscan(php::parameters &params);
+        php::value zscan(php::parameters &params);
+        php::value zrange(php::parameters &params);
+        php::value zrevrange(php::parameters &params);
+        php::value zrangebyscore(php::parameters &params);
+        php::value zrevrangebyscore(php::parameters &params);
+        php::value unsubscribe(php::parameters &params);
+        php::value punsubscribe(php::parameters &params);
+        // 批量
+        php::value multi(php::parameters &params);
+        // 用于标记不实现的功能
+        php::value unimplement(php::parameters &params);
+
+    private:
+        std::shared_ptr<_connection_lock> cl_;
+        friend class client;
+    };
+} // namespace flame::redis
\ No newline at end of file
diff --git a/test/redis_1.php b/test/redis_1.php
index bd56d95..c2cff4d 100644
--- a/test/redis_1.php
+++ b/test/redis_1.php
@@ -5,15 +5,21 @@
     $cli = flame\redis\connect("redis://auth:password@host:port/database");
     var_dump( $cli->set("test1", "this is a text") );
     var_dump( $cli->get("test1") );
-    var_dump( $cli->del("test1") );
     var_dump( $cli->get("test1") );
     var_dump( $cli->set("test2", 123456) );
     var_dump( $cli->get("test2") );
     var_dump( $cli->incr("test2") );
-    var_dump( $cli->del("test2") );
 
     var_dump( $cli->zrange("rank:relay_20181122", 0, 30, "WITHSCORES") );
     var_dump( $cli->hgetall("rank:relay_201811") );
+
+    $rv = $cli->multi()
+        ->get("test1")
+        ->get("test2")
+        ->del("test1")
+        ->del("test2")
+        ->exec();
+    var_dump( $rv );
 });
 
 flame\run();

From 309b920f6d71b614edaf324faa06642b7f849d2e Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 23 Nov 2018 20:55:50 +0800
Subject: [PATCH 006/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E5=88=9D?=
 =?UTF-8?q?=E6=AD=A5=E5=AE=8C=E6=88=90=20MongoDB=20=E9=87=8D=E6=9E=84;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mongodb.php                  |  49 ++++++
 doc/mysql.php                    |  23 +--
 doc/redis.php                    |  72 +++++++++
 src/controller.cpp               |   7 +-
 src/flame.cpp                    |   4 +-
 src/mongodb/_connection_base.cpp |  54 +++++++
 src/mongodb/_connection_base.h   |  14 ++
 src/mongodb/_connection_lock.cpp |  38 +++++
 src/mongodb/_connection_lock.h   |  16 ++
 src/mongodb/_connection_pool.cpp |  38 +++++
 src/mongodb/_connection_pool.h   |  16 ++
 src/mongodb/client.cpp           |  55 +++++++
 src/mongodb/client.h             |  19 +++
 src/mongodb/collection.cpp       | 248 +++++++++++++++++++++++++++++++
 src/mongodb/collection.h         |  28 ++++
 src/mongodb/cursor.cpp           |  31 ++++
 src/mongodb/cursor.h             |  20 +++
 src/mongodb/date_time.cpp        |  54 +++++++
 src/mongodb/date_time.h          |  20 +++
 src/mongodb/mongodb.cpp          | 190 +++++++++++++++++++++++
 src/mongodb/mongodb.h            |  12 ++
 src/mongodb/object_id.cpp        |  78 ++++++++++
 src/mongodb/object_id.h          |  20 +++
 src/mysql/_connection_base.cpp   |  14 +-
 src/mysql/_connection_pool.cpp   |  11 +-
 src/mysql/result.cpp             |  94 +-----------
 src/redis/_connection_pool.cpp   |  11 +-
 src/url.cpp                      |  15 +-
 src/url.h                        |   5 +-
 test/mongodb_1.php               |  19 +++
 30 files changed, 1150 insertions(+), 125 deletions(-)
 create mode 100644 doc/mongodb.php
 create mode 100644 doc/redis.php
 create mode 100644 src/mongodb/_connection_base.cpp
 create mode 100644 src/mongodb/_connection_base.h
 create mode 100644 src/mongodb/_connection_lock.cpp
 create mode 100644 src/mongodb/_connection_lock.h
 create mode 100644 src/mongodb/_connection_pool.cpp
 create mode 100644 src/mongodb/_connection_pool.h
 create mode 100644 src/mongodb/client.cpp
 create mode 100644 src/mongodb/client.h
 create mode 100644 src/mongodb/collection.cpp
 create mode 100644 src/mongodb/collection.h
 create mode 100644 src/mongodb/cursor.cpp
 create mode 100644 src/mongodb/cursor.h
 create mode 100644 src/mongodb/date_time.cpp
 create mode 100644 src/mongodb/date_time.h
 create mode 100644 src/mongodb/mongodb.cpp
 create mode 100644 src/mongodb/mongodb.h
 create mode 100644 src/mongodb/object_id.cpp
 create mode 100644 src/mongodb/object_id.h
 create mode 100644 test/mongodb_1.php

diff --git a/doc/mongodb.php b/doc/mongodb.php
new file mode 100644
index 0000000..34b4637
--- /dev/null
+++ b/doc/mongodb.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * MongoDB 基本客户端
+ */
+namespace flame\mongodb;
+
+function connect(string $url):client {}
+
+class client {
+    function execute(array $command, boolean $write = false): mixed {}
+    function collection(): collection {}
+    function __get(): collection {}
+    /**
+     * @return boolean 永远为 true ;
+     */
+    function __isset(): boolean {}
+}
+class collection {
+    function insert(array $data, boolean $ordered = true):array {}
+    function delete(array $query, integer $limit = 0):array {}
+    function update(array $query, array $update, $upsert = false):array {}
+    function find(array $query, array $projection = null, array $sort = null, mixed $limit):cursor {}
+    function one(array $query, array $sort = null): array {}
+    function get(array $query, string $field, array $sort = null): mixed {}
+    function count(array $query): integer {}
+    function aggregate(array $pipeline): cursor {}
+}
+class cursor {
+    function fetch_row() {}
+    function fetch_all() {}
+}
+class date_time implements JsonSerializable {
+    function __construct(integer $milliseconds = null) {}
+    function __toString() {}
+    function __toDateTime() {}
+    function __debugInfo() {}
+    function jsonSerialize(): array {}
+    function unix(): integer {}
+    function unix_ms(): integer {}
+}
+class object_id implements JsonSerializable {
+    function __construct(string $object_id = null) {}
+    function __toString() {}
+    function __toDateTime() {}
+    function __debugInfo() {}
+    function jsonSerialize(): array {}
+    function unix(): integer {}
+    function equal(object_id $oid): boolean {}
+}
diff --git a/doc/mysql.php b/doc/mysql.php
index f3619f7..dba29a2 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -85,13 +85,14 @@ function begin_tx(): ?tx {}
     /**
      * 执行制定的 SQL 查询, 并返回结果
      * @param string $sql 
-     * @return result 结果对象
+     * @return mixed SELECT 型语句返回结果集对象 `result`; 否则返回关联数组, 一般包含 affected_rows / insert_id 两项;
      */
     function query(string $sql): result {}
     /**
      * 向指定表插入一行或多行数据
      * @param string $table 表名
      * @param array $data 待插入数据, 多行关联数组插入多行数据(以首行 KEY 做字段名); 普通关联数组插入一行数据;
+     * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
     function insert(string $table, array $data): result {}
     /**
@@ -100,6 +101,7 @@ function insert(string $table, array $data): result {}
      * @param mixed $where WHERE 子句, 请参考上文;
      * @param mixed $order ORDER 子句, 请参考上文;
      * @param mixed $limit LIMIT 子句, 请参考上文;
+     * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
     function delete(string $table, mixed $where, mixed $order, mixed $limit): result {}
     /**
@@ -109,6 +111,7 @@ function delete(string $table, mixed $where, mixed $order, mixed $limit): result
      * @param mixed $modify 当 $modify 为字符串时, 直接拼接 "UPDATE ... SET $modify", 否则按惯例数组进行 KEY = VAL 拼接;
      * @param mixed $order ORDER 子句, 请参考上文;
      * @param mixed $limit LIMIT 子句, 请参考上文;
+     * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
     function update(string $table, mixed $where, mixed $modify, mixed $order, mixed $limit): result {}
     /**
@@ -150,15 +153,19 @@ function rollback() {}
     /**
      * @see client::query()
      */
-    function query(string $sql): result {}
+    function query(string $sql): mixed {}
     /**
      * @see client::insert()
      */
-    function insert(string $table, array $data): result {}
+    function insert(string $table, array $data): array {}
+    /**
+     * @see client::delete()
+     */
+    function delete(string $table, mixed $where, mixed $order, mixed $limit): array {}
     /**
      * @see client::update()
      */
-    function update(string $table, array $where, mixed $modify, mixed $order = null, mixed $limit = null): result {}
+    function update(string $table, array $where, mixed $modify, mixed $order = null, mixed $limit = null): array {}
     /**
      * @see client::select()
      */
@@ -175,13 +182,9 @@ function get(string $table, string $field, array $where, mixed $order = null): m
 
 class result {
     /**
-     * @property Integer
-     */
-    public $affected_rows;
-    /**
-     * @property Integer
+     * @property 结果集包含的记录行数
      */
-    public $insert_id;
+    public $fetched_rows;
     /**
      * 取出下一行
      * @return 下一行数据关联数组;
diff --git a/doc/redis.php b/doc/redis.php
new file mode 100644
index 0000000..279307f
--- /dev/null
+++ b/doc/redis.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * MongoDB 基本客户端
+ */
+namespace flame\redis;
+
+function connect(string $url):client {}
+/**
+ * 
+ */
+class client {
+    /**
+     * 用于通用的实现可用 Redis 命令
+     */
+    function __call($name, $argv) {}
+    /**
+     * 支持 Redis 大部分指令:
+     * 
+     * @method get()
+     * @method set()
+     * @method incr()
+     * @method zincr()   
+     * @method mset()
+     * @method mget()
+     * @method hmset()
+     * @method hmget()
+     * @method hgetall()
+     * @method zrange()
+     * @method zrevrange()
+     * @method zrangebyscore()
+     * @method zrevrangebyscore()
+     * ...
+     * 这里不再一一列举
+     */
+    /**
+     * 批量执行
+     */
+    function multi(): tx {}
+    /**
+     * @uimplement 暂未实现
+     */
+    function subscribe() {}
+    /**
+     * @uimplement 暂未实现
+     */
+    function unsubscribe() {}
+    /**
+     * @uimplement 暂未实现
+     */
+    function psubscribe() {}
+    /**
+     * @uimplement 暂未实现
+     */
+    function punsubscribe() {}
+}
+/**
+ * 
+ */
+class tx {
+    /**
+     * 所有 client 支持的命令, 但不会立即执行; 返回对象自身, 可连续串写, 例如:
+     *  @example
+     *      $r = $tx->get("xxx")->set("xxx","yyy")->exec();
+     */
+    function __call($name, $argv): tx {
+        return $this;
+    }
+    /**
+     * 执行目前提交的命令并带回所有响应返回
+     */
+    function exec():array {}
+}
\ No newline at end of file
diff --git a/src/controller.cpp b/src/controller.cpp
index e6cd7a5..4ce62b9 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -75,8 +75,9 @@ void controller_worker::run() {
 		
 	// });
 	// 2. 启动线程池, 并使用线程池运行 context_y
-	thread_.resize(4);
-	for(int i=0;i<4;++i) {
+	thread_.resize(3);
+	for(int i=0;i<thread_.size();++i) {
+		std::cout << "worker: " << i << std::endl;
 		thread_[i] = std::thread([this] {
 			gcontroller->context_y.run();
 		});
@@ -84,7 +85,7 @@ void controller_worker::run() {
 	// 3. 启动 context_x 运行
 	gcontroller->context_x.run();
 	work.reset();
-	for (int i = 0; i < 4; ++i)
+	for (int i=0; i<thread_.size(); ++i)
 	{
 		thread_[i].join();
 	}
diff --git a/src/flame.cpp b/src/flame.cpp
index a753fd3..451d101 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -3,6 +3,7 @@
 #include "time/time.h"
 #include "mysql/mysql.h"
 #include "redis/redis.h"
+#include "mongodb/mongodb.h"
 
 extern "C"
 {
@@ -32,6 +33,7 @@ extern "C"
         flame::time::declare(ext);
         flame::mysql::declare(ext);
         flame::redis::declare(ext);
+        flame::mongodb::declare(ext);
         // flame::os::declare(ext);
         // flame::log::declare(ext);
         // flame::udp::declare(ext);
@@ -40,7 +42,7 @@ extern "C"
         
         // flame::rabbitmq::declare(ext);
         
-        // flame::mongodb::declare(ext);
+        
         // flame::kafka::declare(ext);
         return ext;
     }
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
new file mode 100644
index 0000000..2610e2b
--- /dev/null
+++ b/src/mongodb/_connection_base.cpp
@@ -0,0 +1,54 @@
+#include "../controller.h"
+#include "_connection_base.h"
+#include "_connection_lock.h"
+#include "mongodb.h"
+#include "cursor.h"
+
+namespace flame::mongodb
+{
+    void _connection_base::fake_deleter(bson_t *doc) {}
+    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, bool write, coroutine_handler &ch)
+    {
+        auto cmd = array2bson(pcmd);
+        std::shared_ptr<bson_t> rep(bson_new(), bson_destroy);
+        auto err = std::make_shared<bson_error_t>();
+        int  rok = 0;
+        boost::asio::post(gcontroller->context_y, [conn, write, &cmd, &rep, &err, &rok, &ch] () {
+            const mongoc_uri_t *uri = mongoc_client_get_uri(conn.get());
+            if(write)
+            {
+                rok = mongoc_client_write_command_with_opts(conn.get(), mongoc_uri_get_database(uri), cmd.get(), nullptr, rep.get(), err.get());
+            }
+            else
+            {
+                rok = mongoc_client_read_command_with_opts(conn.get(), mongoc_uri_get_database(uri), cmd.get(), mongoc_uri_get_read_prefs_t(uri), nullptr, rep.get(), err.get());
+            }
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(!rok)
+        {
+            throw php::exception(zend_ce_exception,
+                    (boost::format("failed to execute MongoDB command: (%1%) %1%") % err->message % err->code).str(),
+                    err->code);
+        }
+        else if(bson_has_field(rep.get(), "cursor"))
+        {
+            php::object obj{php::class_entry<cursor>::entry()};
+            auto ptr = static_cast<cursor *>(php::native(obj));
+            ptr->cl_.reset(new _connection_lock(conn));
+            ptr->cs_.reset(
+                mongoc_cursor_new_from_command_reply_with_opts(conn.get(), rep.get(), nullptr),
+                mongoc_cursor_destroy);
+            // cursor 实际会窃取 rep 对应的 bson_t 结构; 
+            // 按文档说会被上面函数 "销毁"
+            // 参考: http://mongoc.org/libmongoc/current/mongoc_cursor_new_from_command_reply_with_opts.html
+            *std::get_deleter<void (*)(bson_t*)>(rep) = fake_deleter;
+            return std::move(obj);
+        }
+        else
+        {
+            return bson2array(rep);
+        }
+    }
+} // namespace flame::mongodb
\ No newline at end of file
diff --git a/src/mongodb/_connection_base.h b/src/mongodb/_connection_base.h
new file mode 100644
index 0000000..07bc2ea
--- /dev/null
+++ b/src/mongodb/_connection_base.h
@@ -0,0 +1,14 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::mongodb {
+    class _connection_base
+    {
+    public:
+        virtual std::shared_ptr<mongoc_client_t> acquire(coroutine_handler& ch) = 0;
+        php::value exec(std::shared_ptr<mongoc_client_t> conn, php::array& cmd, bool write, coroutine_handler& ch);
+
+        static void fake_deleter(bson_t *doc);
+    };
+}
\ No newline at end of file
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
new file mode 100644
index 0000000..fab3861
--- /dev/null
+++ b/src/mongodb/_connection_lock.cpp
@@ -0,0 +1,38 @@
+#include "../controller.h"
+#include "_connection_lock.h"
+#include "mongodb.h"
+
+namespace flame::mongodb
+{
+    _connection_lock::_connection_lock(std::shared_ptr<mongoc_client_t> c)
+        : conn_(c)
+    {
+        
+    }
+    std::shared_ptr<mongoc_client_t> _connection_lock::acquire(coroutine_handler &ch)
+    {
+        return conn_;
+    }
+    php::array _connection_lock::fetch(std::shared_ptr<mongoc_cursor_t> cs, coroutine_handler &ch)
+    {
+        bool has = false;
+        auto err = std::make_shared<bson_error_t>();
+        const bson_t *doc;
+        boost::asio::post(gcontroller->context_y, [this, &cs, &ch, &has, &err, &doc]() {
+            if(!mongoc_cursor_next(cs.get(), &doc))
+            {
+                has = mongoc_cursor_error(cs.get(), err.get());
+            }
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if (has) // 发生了错误
+        {
+            throw php::exception(zend_ce_exception,
+                    (boost::format("failed to fetch document: (%1%) %2%") % err->code % err->message).str(),
+                    err->code);
+        }
+        // 文档 doc 仅为 "引用" 流程
+        return bson2array(const_cast<bson_t*>(doc));
+    }
+} // namespace flame::mongodb
\ No newline at end of file
diff --git a/src/mongodb/_connection_lock.h b/src/mongodb/_connection_lock.h
new file mode 100644
index 0000000..72b2b46
--- /dev/null
+++ b/src/mongodb/_connection_lock.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "../vendor.h"
+#include "_connection_base.h"
+
+namespace flame::mongodb
+{
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock>
+    {
+    public:
+        _connection_lock(std::shared_ptr<mongoc_client_t> c);
+        std::shared_ptr<mongoc_client_t> acquire(coroutine_handler &ch) override;
+        php::array fetch(std::shared_ptr<mongoc_cursor_t> cs, coroutine_handler &ch);
+    private:
+        std::shared_ptr<mongoc_client_t> conn_;
+    };
+} // namespace flame::mongodb
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
new file mode 100644
index 0000000..f02457d
--- /dev/null
+++ b/src/mongodb/_connection_pool.cpp
@@ -0,0 +1,38 @@
+#include "../controller.h"
+#include "_connection_pool.h"
+
+namespace flame::mongodb 
+{
+    _connection_pool::_connection_pool(const std::string& url)
+    {
+        std::unique_ptr<mongoc_uri_t, void (*)(mongoc_uri_t *)> uri(mongoc_uri_new(url.c_str()), mongoc_uri_destroy);
+        const bson_t* options = mongoc_uri_get_options(uri.get());
+        if (!bson_has_field(options, MONGOC_URI_READPREFERENCE))
+        {
+            mongoc_uri_set_option_as_utf8(uri.get(), MONGOC_URI_READPREFERENCE, "secondaryPreferred");
+        }
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 16);
+
+        p_ = mongoc_client_pool_new(uri.get());
+    }
+    _connection_pool::~_connection_pool() {
+        mongoc_client_pool_destroy(p_);
+    }
+    std::shared_ptr<mongoc_client_t> _connection_pool::acquire(coroutine_handler &ch)
+    {
+        std::shared_ptr<mongoc_client_t> conn;
+        auto self = shared_from_this();
+        boost::asio::post(gcontroller->context_y, [this, self, &conn, &ch] () {
+            // 对应释放(归还)连接过程, 须持有当前对象的引用 self 
+            // (否则当前对象可能先于连接释放被销毁)
+            conn.reset(mongoc_client_pool_pop(p_), [this, self] (mongoc_client_t* c)
+            {
+                mongoc_client_pool_push(p_, c);
+            });
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        return conn;
+    }
+}
\ No newline at end of file
diff --git a/src/mongodb/_connection_pool.h b/src/mongodb/_connection_pool.h
new file mode 100644
index 0000000..1674a55
--- /dev/null
+++ b/src/mongodb/_connection_pool.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "../vendor.h"
+#include "_connection_base.h"
+
+namespace flame::mongodb {
+
+    class _connection_pool: public _connection_base, public std::enable_shared_from_this<_connection_pool>
+    {
+    public:
+        _connection_pool(const std::string& url);
+        ~_connection_pool();
+        std::shared_ptr<mongoc_client_t> acquire(coroutine_handler &ch) override;
+    private:
+        mongoc_client_pool_t* p_;
+    };
+}
diff --git a/src/mongodb/client.cpp b/src/mongodb/client.cpp
new file mode 100644
index 0000000..75285a6
--- /dev/null
+++ b/src/mongodb/client.cpp
@@ -0,0 +1,55 @@
+#include "../coroutine.h"
+#include "_connection_pool.h"
+#include "client.h"
+#include "collection.h"
+
+namespace flame::mongodb
+{
+    void client::declare(php::extension_entry &ext)
+    {
+        php::class_entry<client> class_client("flame\\mongodb\\client");
+        class_client
+            .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::execute>("execute",
+            {
+                {"command", php::TYPE::ARRAY},
+                {"write", php::TYPE::BOOLEAN, false, true},
+            })
+            .method<&client::__get>("collection",
+            {
+                {"name", php::TYPE::STRING}
+            })
+            .method<&client::__get>("__get",
+            {
+                {"name", php::TYPE::STRING}
+            })
+            .method<&client::__isset>("__isset", 
+            {
+                {"name", php::TYPE::STRING}
+            });
+        ext.add(std::move(class_client));
+    }
+    php::value client::execute(php::parameters &params)
+    {
+        bool write = false;
+        if (params.size() > 1) write = params[1].to_boolean();
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        
+        php::array cmd = params[0];
+        return cp_->exec(conn_, cmd, write, ch);
+    }
+    php::value client::__get(php::parameters &params)
+    {
+        php::object obj(php::class_entry<collection>::entry());
+        collection *ptr = static_cast<collection *>(php::native(obj));
+        ptr->cp_ = cp_;
+        ptr->name_ = params[0];
+        obj.set("name", params[0]);
+        return std::move(obj);
+    }
+    php::value client::__isset(php::parameters &params)
+    {
+        return true;
+    }
+} // namespace flame::mongodb
\ No newline at end of file
diff --git a/src/mongodb/client.h b/src/mongodb/client.h
new file mode 100644
index 0000000..5f8b98a
--- /dev/null
+++ b/src/mongodb/client.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::mongodb
+{
+    class _connection_pool;
+    class client : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params);
+        php::value execute(php::parameters &params);
+        php::value __get(php::parameters &params);
+        php::value __isset(php::parameters &params);
+    private:
+        std::shared_ptr<_connection_pool> cp_;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::mongodb
\ No newline at end of file
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
new file mode 100644
index 0000000..a0495b6
--- /dev/null
+++ b/src/mongodb/collection.cpp
@@ -0,0 +1,248 @@
+#include "../coroutine.h"
+#include "_connection_pool.h"
+#include "collection.h"
+#include "cursor.h"
+
+namespace flame::mongodb
+{
+    void collection::declare(php::extension_entry &ext)
+    {
+        php::class_entry<collection> class_collection("flame\\mongodb\\collection");
+        class_collection
+            .property({"name", ""})
+            .method<&collection::__construct>("__construct", {}, php::PRIVATE)
+            .method<&collection::insert>("insert",
+            {
+                {"data", php::TYPE::ARRAY},
+                {"ordered", php::TYPE::BOOLEAN, false, true} // true
+            })
+            .method<&collection::delete_>("delete",
+            {
+                {"query", php::TYPE::ARRAY},
+                {"limit", php::TYPE::INTEGER, false, true}, // 0
+            })
+            .method<&collection::update>("update",
+            {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"upsert", php::TYPE::BOOLEAN, false, true}, // false
+            })
+            .method<&collection::find>("find",
+            {
+                {"filter", php::TYPE::ARRAY},
+                {"projection", php::TYPE::ARRAY, false, true},
+                {"sort", php::TYPE::ARRAY, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&collection::one>("one",
+            {
+                {"filter", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::get>("get",
+            {
+                {"filter", php::TYPE::ARRAY},
+                {"field", php::TYPE::STRING},
+                {"sort", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::count>("count",
+            {
+                {"query", php::TYPE::ARRAY},
+            })
+            .method<&collection::aggregate>("aggregate",
+            {
+                {"pipeline", php::TYPE::ARRAY},
+            });
+        ext.add(std::move(class_collection));
+    }
+    php::value collection::insert(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("insert", name_);
+        php::array docs = params[0];
+        if (!docs.exists(0))
+        { // 单项插入的情况
+            docs = php::array(2);
+            docs.set(0, params[0]);
+        }
+        cmd.set("documents", docs);
+        if (params.size() > 1)
+        {
+            cmd.set("ordered", params[1].to_boolean());
+        }
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, true, ch);
+    }
+    php::value collection::delete_(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("delete", name_);
+        php::array deletes(2), deleteo(2);
+        deleteo.set("q", params[0]);
+        if (params.size() > 1 && params[1].typeof(php::TYPE::INTEGER))
+        {
+            deleteo.set("limit", params[1].to_integer());
+        }
+        else
+        {
+            deleteo.set("limit", 0);
+        }
+        deletes.set(0, deleteo);
+        cmd.set("deletes", deletes);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, true, ch);
+    }
+    php::value collection::update(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("update", name_);
+        php::array updates(2), updateo(4);
+        updateo.set("q", params[0]);
+        updateo.set("u", params[1]);
+        if (params.size() > 2)
+        {
+            updateo.set("upsert", params[2].to_boolean());
+        }
+        updateo.set("multi", true);
+        updates.set(0, updateo);
+        cmd.set("updates", updates);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, true, ch);
+    }
+    php::value collection::find(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("find", name_);
+        if (params.size() > 0)
+        {
+            cmd.set("filter", params[0]);
+        }
+        if (params.size() > 1)
+        {
+            php::array project;
+            if (params[1].typeof(php::TYPE::ARRAY))
+            {
+                php::array fields = params[1];
+                if (fields.exists(0))
+                { // 将纯字段列表形式转换为K/V形式
+                    project = php::array(8);
+                    for (auto i = fields.begin(); i != fields.end(); ++i)
+                    {
+                        project.set(php::string(i->second), 1);
+                    }
+                }
+                else
+                {
+                    project = fields;
+                }
+            }
+            else if (params[1].typeof(php::TYPE::STRING))
+            { // 单个字段
+                php::array project(2);
+                project.set(php::string(params[1]), 1);
+            }
+            else
+            {
+                goto PROJECTION_ALL;
+            }
+            if (!project.exists("_id"))
+            {
+                project.set("_id", 0);
+            }
+            cmd.set("projection", project);
+        }
+    PROJECTION_ALL:
+        if (params.size() > 2 && params[2].typeof(php::TYPE::ARRAY))
+        {
+            cmd.set("sort", params[2]);
+        }
+        if (params.size() > 3)
+        {
+            if (params[3].typeof(php::TYPE::INTEGER))
+            {
+                cmd.set("limit", params[3]);
+            }
+            else if (params[3].typeof(php::TYPE::ARRAY))
+            {
+                php::array limits = params[3];
+                if (limits.size() > 0)
+                    cmd.set("skip", limits[0].to_integer());
+                if (limits.size() > 1)
+                    cmd.set("limit", limits[1].to_integer());
+            }
+        }
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, false, ch);
+    }
+    php::value collection::one(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("find", name_);
+        cmd.set("filter", params[0]);
+        if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY))
+        {
+            cmd.set("sort", params[1]);
+        }
+        cmd.set("limit", 1);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        php::object cs = cp_->exec(conn_, cmd, true, ch);
+        assert(cs.instanceof(php::class_entry<cursor>::entry()));
+        return cs.call("fetch_row");
+    }
+    php::value collection::get(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("find", name_);
+        php::array project(2);
+        php::string field = params[1];
+        project.set(field, 1);
+        cmd.set("projection", project);
+        cmd.set("filter", params[0]);
+        if (params.size() > 2 && params[2].typeof(php::TYPE::ARRAY))
+        {
+            cmd.set("sort", params[2]);
+        }
+        cmd.set("limit", 1);
+
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        php::object cs = cp_->exec(conn_, cmd, true, ch);
+        assert(cs.instanceof (php::class_entry<cursor>::entry()));
+        php::array row = cs.call("fetch_row");
+        if(row.empty()) {
+            return nullptr;
+        }else{
+            return row.get(field);
+        }
+    }
+    php::value collection::count(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("count", name_);
+        cmd.set("query", params[0]);
+
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        php::array cs = cp_->exec(conn_, cmd, true, ch);
+        return cs.get("n");
+    }
+    php::value collection::aggregate(php::parameters &params)
+    {
+        php::array cmd(8);
+        cmd.set("aggregate", name_);
+        php::array pipeline = params[0];
+        if (!pipeline.exists(0))
+            throw php::exception(zend_ce_type_error, "aggregate pipeline must be a array of stages (array)");
+        cmd.set("pipeline", params[0]);
+        cmd.set("cursor", php::array(0));
+
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, false, ch);
+    }
+} // namespace flame::mongodb
diff --git a/src/mongodb/collection.h b/src/mongodb/collection.h
new file mode 100644
index 0000000..e0a346f
--- /dev/null
+++ b/src/mongodb/collection.h
@@ -0,0 +1,28 @@
+#pragma once
+
+namespace flame::mongodb
+{
+    class _connection_pool;
+    class collection : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params)
+        { // 私有
+            return nullptr;
+        }
+        php::value insert(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value update(php::parameters &params);
+        php::value find(php::parameters &params);
+        php::value one(php::parameters &params);
+        php::value get(php::parameters &params);
+        php::value count(php::parameters &params);
+        php::value aggregate(php::parameters &params);
+
+    private:
+        std::shared_ptr<_connection_pool> cp_;
+        php::string name_;
+        friend class client;
+    };
+} // namespace flame::mongodb
\ No newline at end of file
diff --git a/src/mongodb/cursor.cpp b/src/mongodb/cursor.cpp
new file mode 100644
index 0000000..4b5846e
--- /dev/null
+++ b/src/mongodb/cursor.cpp
@@ -0,0 +1,31 @@
+#include "../coroutine.h"
+#include "cursor.h"
+#include "_connection_lock.h"
+
+namespace flame::mongodb
+{
+    void cursor::declare(php::extension_entry &ext)
+    {
+        php::class_entry<cursor> class_cursor("flame\\mongodb\\cursor");
+        class_cursor
+            .method<&cursor::__construct>("__construct", {}, php::PRIVATE)
+            .method<&cursor::fetch_row>("fetch_row")
+            .method<&cursor::fetch_all>("fetch_all");
+        ext.add(std::move(class_cursor));
+    }
+    php::value cursor::fetch_row(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        return cl_->fetch(cs_, ch);
+    }
+    php::value cursor::fetch_all(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        php::array data(8);
+        for(php::array row = cl_->fetch(cs_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
+        {
+            data.set(data.size(), row);
+        }
+        return data;
+    }
+} // namespace flame::mongodb
diff --git a/src/mongodb/cursor.h b/src/mongodb/cursor.h
new file mode 100644
index 0000000..32cf8d8
--- /dev/null
+++ b/src/mongodb/cursor.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::mongodb {
+	class _connection_lock;
+	class cursor: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value __construct(php::parameters& params) { // 私有
+			return nullptr;
+		}
+		php::value fetch_row(php::parameters& params);
+		php::value fetch_all(php::parameters& params);
+	private:
+        // 须先销毁 指针 后归还 连接
+        std::shared_ptr<_connection_lock> cl_;
+        std::shared_ptr<mongoc_cursor_t>  cs_;
+        friend class _connection_base;
+	};
+} // namespace flame::mongodb
diff --git a/src/mongodb/date_time.cpp b/src/mongodb/date_time.cpp
new file mode 100644
index 0000000..88eb077
--- /dev/null
+++ b/src/mongodb/date_time.cpp
@@ -0,0 +1,54 @@
+#include "date_time.h"
+#include "../time/time.h"
+
+namespace flame {
+namespace mongodb {
+	void date_time::declare(php::extension_entry& ext) {
+		php::class_entry<date_time> class_date_time("flame\\mongodb\\date_time");
+		class_date_time
+			.implements(&php_json_serializable_ce)
+			.method<&date_time::__construct>("__construct", {
+				{"milliseconds", php::TYPE::INTEGER, false, true}
+			})
+			.method<&date_time::to_string>("__toString")
+			.method<&date_time::to_json>("__toJSON")
+			.method<&date_time::to_datetime>("__toDateTime")
+			.method<&date_time::unix>("unix")
+			.method<&date_time::unix_ms>("unix_ms")
+			.method<&date_time::to_json>("jsonSerialize")
+			.method<&date_time::to_json>("__debugInfo");
+		ext.add(std::move(class_date_time));
+	}
+	php::value date_time::__construct(php::parameters& params) {
+		if(params.size() > 0)
+        {
+			tm_ = params[0].to_integer();
+		}
+        else
+        {
+            // 默认以当前时间建立
+			tm_ = std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count();
+		}
+		return nullptr;
+	}
+	php::value date_time::to_string(php::parameters& params) {
+		return std::to_string(tm_);
+	}
+	php::value date_time::unix(php::parameters& params) {
+		return std::int64_t(tm_ / 1000);
+	}
+	php::value date_time::unix_ms(php::parameters& params) {
+		return tm_;
+	}
+	php::value date_time::to_datetime(php::parameters& params) {
+		return php::datetime(tm_);
+	}
+	php::value date_time::to_json(php::parameters& params) {
+        // 自定义 JSON 形式 (保持和官方 MongoDB 驱动形式一致)
+		php::array ret(1), num(1);
+		num.set("$numberLong", std::to_string(tm_));
+		ret.set("$date", num);
+		return std::move(ret);
+	}
+}
+}
\ No newline at end of file
diff --git a/src/mongodb/date_time.h b/src/mongodb/date_time.h
new file mode 100644
index 0000000..906e711
--- /dev/null
+++ b/src/mongodb/date_time.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::mongodb {
+	class date_time: public php::class_base
+    {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value __construct(php::parameters& params);
+		php::value to_string(php::parameters& params);
+		php::value unix(php::parameters& params);
+		php::value unix_ms(php::parameters& params);
+		php::value to_datetime(php::parameters& params);
+		php::value to_json(php::parameters& params);
+	private:
+		std::int64_t tm_;
+		friend php::value iter2value(bson_iter_t *i);
+		friend std::shared_ptr<bson_t> array2bson(const php::array &v);
+	};
+}
\ No newline at end of file
diff --git a/src/mongodb/mongodb.cpp b/src/mongodb/mongodb.cpp
new file mode 100644
index 0000000..71ea6dd
--- /dev/null
+++ b/src/mongodb/mongodb.cpp
@@ -0,0 +1,190 @@
+#include "../coroutine.h"
+#include "mongodb.h"
+#include "_connection_pool.h"
+#include "client.h"
+#include "cursor.h"
+#include "collection.h"
+#include "object_id.h"
+#include "date_time.h"
+
+
+namespace flame::mongodb
+{
+    void declare(php::extension_entry &ext)
+    {
+        ext
+            .on_module_startup([](php::extension_entry &ext) -> bool {
+                mongoc_init();
+                return true;
+            })
+            .on_module_shutdown([](php::extension_entry &ext) -> bool {
+                mongoc_cleanup();
+                return true;
+            });
+        ext
+            .function<connect>("flame\\mongodb\\connect");
+        client::declare(ext);
+        object_id::declare(ext);
+        date_time::declare(ext);
+        cursor::declare(ext);
+        collection::declare(ext);
+    }
+    php::value connect(php::parameters &params)
+    {
+        php::object obj(php::class_entry<client>::entry());
+        client *ptr = static_cast<client *>(php::native(obj));
+        std::string url = params[0];
+        ptr->cp_.reset(new _connection_pool(url));
+
+        // TODO 优化: 实际确认建立第一个连接?
+        return std::move(obj);
+    }
+    php::value iter2value(bson_iter_t *i)
+    {
+        switch (bson_iter_type(i))
+        {
+        case BSON_TYPE_DOUBLE:
+            return bson_iter_double(i);
+        case BSON_TYPE_UTF8:
+        {
+            std::uint32_t size = 0;
+            const char *data = bson_iter_utf8(i, &size);
+            return php::string(data, size);
+        }
+        case BSON_TYPE_DOCUMENT:
+        case BSON_TYPE_ARRAY:
+        {
+            bson_iter_t j;
+            bson_iter_recurse(i, &j);
+            php::array a(4);
+            while (bson_iter_next(&j))
+            {
+                a.set(bson_iter_key(&j), iter2value(&j));
+            }
+            return std::move(a);
+        }
+        case BSON_TYPE_BINARY:
+        {
+            std::uint32_t size = 0;
+            const unsigned char *data;
+            bson_iter_binary(i, nullptr, &size, &data);
+            return php::string((const char *)data, size);
+        }
+        case BSON_TYPE_OID:
+        {
+            php::object o(php::class_entry<object_id>::entry());
+            object_id *o_ = static_cast<object_id *>(php::native(o));
+            bson_oid_copy(bson_iter_oid(i), &o_->oid_);
+            return std::move(o);
+        }
+        case BSON_TYPE_BOOL:
+            return bson_iter_bool(i);
+        case BSON_TYPE_DATE_TIME:
+        {
+            php::object o(php::class_entry<date_time>::entry());
+            date_time *o_ = static_cast<date_time *>(php::native(o));
+            o_->tm_ = bson_iter_date_time(i);
+            return o;
+        }
+        case BSON_TYPE_INT32:
+            return bson_iter_int32(i);
+        case BSON_TYPE_INT64:
+            return bson_iter_int64(i);
+        default:
+            return nullptr;
+        }
+    }
+    php::array bson2array(std::shared_ptr<bson_t> v)
+    {
+        return bson2array(v.get());
+    }
+    php::array bson2array(bson_t* v)
+    {
+        if(v == nullptr) return nullptr;
+        
+        php::array doc(4);
+
+        bson_iter_t i;
+        bson_oid_t oid;
+        bson_iter_init(&i, v);
+        while (bson_iter_next(&i))
+        {
+            doc.set(bson_iter_key(&i), iter2value(&i));
+        }
+        return std::move(doc);
+    }
+
+    std::shared_ptr<bson_t> array2bson(const php::array &v)
+    {
+        assert(v.typeof(php::TYPE::ARRAY));
+        bson_t *doc = bson_new();
+        for (auto i = v.begin(); i != v.end(); ++i)
+        {
+            php::string key = i->first;
+            key.to_string();
+            php::value val = i->second;
+            switch (Z_TYPE_P(static_cast<zval *>(val)))
+            {
+                case IS_UNDEF:
+                    break;
+                case IS_NULL:
+                    bson_append_null(doc, key.c_str(), key.size());
+                    break;
+                case IS_TRUE:
+                    bson_append_bool(doc, key.c_str(), key.size(), true);
+                    break;
+                case IS_FALSE:
+                    bson_append_bool(doc, key.c_str(), key.size(), false);
+                    break;
+                case IS_LONG:
+                    bson_append_int64(doc, key.c_str(), key.size(), static_cast<std::int64_t>(val));
+                    break;
+                case IS_DOUBLE:
+                    bson_append_double(doc, key.c_str(), key.size(), static_cast<double>(val));
+                    break;
+                case IS_STRING:
+                {
+                    php::string str = val;
+                    bson_append_utf8(doc, key.c_str(), key.size(), str.c_str(), str.size());
+                    break;
+                }
+                case IS_ARRAY:
+                {
+                    auto a = array2bson(val);
+                    if (bson_has_field(a.get(), "0"))
+                    {
+                        bson_append_array(doc, key.c_str(), key.size(), a.get());
+                    }
+                    else
+                    {
+                        bson_append_document(doc, key.c_str(), key.size(), a.get());
+                    }
+                    break;
+                }
+                case IS_OBJECT: 
+                {
+                    php::object o = val;
+                    if (o.instanceof (php_date_get_date_ce()))
+                    { // PHP 内置的 DateTime 类型
+                        bson_append_date_time(doc, key.c_str(), key.size(), static_cast<std::int64_t>(o.call("getTimestamp")) * 1000);
+                    }
+                    else if (o.instanceof (php::class_entry<date_time>::entry()))
+                    {
+                        date_time *o_ = static_cast<date_time *>(php::native(o));
+                        bson_append_date_time(doc, key.c_str(), key.size(), o_->tm_);
+                    }
+                    else if (o.instanceof (php::class_entry<object_id>::entry()))
+                    {
+                        object_id *o_ = static_cast<object_id *>(php::native(o));
+                        bson_append_oid(doc, key.c_str(), key.size(), &o_->oid_);
+                    }
+                    else
+                    {
+                        bson_append_null(doc, key.c_str(), key.size());
+                    }
+                }
+            }
+        }
+        return std::shared_ptr<bson_t>(doc, bson_destroy);
+    }
+} // namespace flame::mongodb
diff --git a/src/mongodb/mongodb.h b/src/mongodb/mongodb.h
new file mode 100644
index 0000000..06a2f6a
--- /dev/null
+++ b/src/mongodb/mongodb.h
@@ -0,0 +1,12 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::mongodb
+{
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters &params);
+    php::value iter2value(bson_iter_t *i);
+    php::array bson2array(std::shared_ptr<bson_t> v);
+    php::array bson2array(bson_t* v);
+    std::shared_ptr<bson_t> array2bson(const php::array &v);
+} // namespace flame::mongodb
diff --git a/src/mongodb/object_id.cpp b/src/mongodb/object_id.cpp
new file mode 100644
index 0000000..578db40
--- /dev/null
+++ b/src/mongodb/object_id.cpp
@@ -0,0 +1,78 @@
+#include "object_id.h"
+
+namespace flame::mongodb
+{
+    void object_id::declare(php::extension_entry &ext)
+    {
+        php::class_entry<object_id> class_object_id("flame\\mongodb\\object_id");
+        class_object_id
+            .method<&object_id::__construct>("__construct")
+            .method<&object_id::to_string>("__toString")
+            .method<&object_id::to_json>("__toJSON")
+            .method<&object_id::to_datetime>("__toDateTime")
+            .method<&object_id::unix>("unix")
+            .method<&object_id::to_json>("jsonSerialize")
+            .method<&object_id::to_json>("__debugInfo")
+            .method<&object_id::equal>("equal",
+            {
+                {"object_id", "?flame\\mongodb\\object_id"}
+            });
+        ext.add(std::move(class_object_id));
+    }
+    php::value object_id::__construct(php::parameters &params)
+    {
+        if (params.size() > 0)
+        {
+            php::string oid = params[0].to_string();
+            bson_oid_init_from_string(&oid_, oid.c_str());
+        }
+        else
+        {
+            bson_oid_init(&oid_, nullptr);
+        }
+        return nullptr;
+    }
+    php::value object_id::to_string(php::parameters &params)
+    {
+        php::string str(24);
+        bson_oid_to_string(&oid_, str.data());
+        return std::move(str);
+    }
+    php::value object_id::unix(php::parameters &params)
+    {
+        return bson_oid_get_time_t(&oid_);
+    }
+    php::value object_id::to_datetime(php::parameters &params)
+    {
+        return php::datetime(bson_oid_get_time_t(&oid_) * 1000);
+    }
+    php::value object_id::to_json(php::parameters &params)
+    {
+        // 定制 JSON 输出形式 (为何官方 MongoDB 驱动保持一致)
+        php::array oid(1);
+        php::string str(24);
+        bson_oid_to_string(&oid_, str.data());
+        oid.set("$oid", str);
+        return std::move(oid);
+    }
+    php::value object_id::equal(php::parameters &params)
+    {
+        if (params[0].typeof(php::TYPE::STRING))
+        {
+            php::string data = params[0];
+            bson_oid_t oid;
+            bson_oid_init_from_string(&oid, data.c_str());
+            return bson_oid_equal(&oid_, &oid);
+        }
+        else if (params[0].instanceof (php::class_entry<object_id>::entry()))
+        {
+            php::object obj = params[0];
+            object_id *ptr = static_cast<object_id *>(php::native(obj));
+            return bson_oid_equal(&oid_, &ptr->oid_);
+        }
+        else
+        {
+            return false;
+        }
+    }
+} // namespace flame::mongodb
diff --git a/src/mongodb/object_id.h b/src/mongodb/object_id.h
new file mode 100644
index 0000000..459ac64
--- /dev/null
+++ b/src/mongodb/object_id.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::mongodb {
+	class object_id: public php::class_base
+    {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value __construct(php::parameters& params);
+		php::value to_string(php::parameters& params);
+		php::value unix(php::parameters& params);
+		php::value to_datetime(php::parameters& params);
+		php::value to_json(php::parameters& params);
+		php::value equal(php::parameters& params);
+	private:
+		bson_oid_t oid_;
+		friend php::value iter2value(bson_iter_t *i);
+		friend std::shared_ptr<bson_t> array2bson(const php::array &v);
+	};
+}
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 82bb8e6..7a69279 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -109,21 +109,25 @@ ESCAPE_FINISHED:;
                                  (boost::format("failed to query MySQL server: (%1%) %2%") % err % mysql_error(conn.get())).str(),
                                  err);
         }
-        php::object obj(php::class_entry<result>::entry());
-        result* ptr = static_cast<result*>(php::native(obj));
+        
         if(rst) // 存在结果集
         {
+            php::object obj(php::class_entry<result>::entry());
+            result* ptr = static_cast<result*>(php::native(obj));
             ptr->cl_.reset(new _connection_lock(conn));
             ptr->rs_.reset(rst, mysql_free_result);
             ptr->f_ = mysql_fetch_fields(rst);
             ptr->n_ = mysql_num_fields(rst);
+            obj.set("stored_rows", static_cast<std::int64_t>(mysql_num_rows(rst)));
+            return std::move(obj);
         }
         else // 更新型操作
         {
-            obj.set("affect_rows", static_cast<std::int64_t>(mysql_affected_rows(conn.get())));
-            obj.set("insert_id", static_cast<std::int64_t>(mysql_insert_id(conn.get())));
+            php::array data(2);
+            data.set(php::string("affected_rows", 11), static_cast<std::int64_t>(mysql_affected_rows(conn.get())));
+            data.set(php::string("insert_id", 9), static_cast<std::int64_t>(mysql_insert_id(conn.get())));
+            return std::move(data);
         }
-        return std::move(obj);
     }
 
     php::array _connection_base::fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch)
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 2b1f5ee..340c244 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -90,12 +90,13 @@ namespace flame::mysql
         { // 立刻分配使用
             std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
             await_.pop_front();
-            auto ptr = this->shared_from_this();
+            auto self = this->shared_from_this();
             cb(
-                std::shared_ptr<MYSQL>(c, [this, ptr] (MYSQL *c) {
-                    boost::asio::post(guard_, std::bind(&_connection_pool::release, ptr, c));
-                })
-            );
+                // 释放回调函数须持有当前对象引用 self
+                // (否则连接池可能先于连接归还被销毁)
+                std::shared_ptr<MYSQL>(c, [this, self](MYSQL *c) {
+                    boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+                }));
         }
     }
 
diff --git a/src/mysql/result.cpp b/src/mysql/result.cpp
index 1409810..7b20112 100644
--- a/src/mysql/result.cpp
+++ b/src/mysql/result.cpp
@@ -8,82 +8,12 @@ namespace flame::mysql
     {
         php::class_entry<result> class_result("flame\\mysql\\result");
         class_result
-            .property({"affected_rows", 0})
-            .property({"insert_id", 0})
+            .property({"stored_rows", 0})
             .method<&result::fetch_row>("fetch_row")
             .method<&result::fetch_all>("fetch_all");
 
         ext.add(std::move(class_result));
     }
-    // void result::stack_fetch(std::shared_ptr<coroutine> co, const php::object &ref, const php::string &field)
-    // {
-    //     co->stack(php::value([field](php::parameters &params) -> php::value {
-    //         php::array row = params[0];
-    //         if (!row.typeof(php::TYPE::ARRAY))
-    //             return nullptr;
-    //         if (field.typeof(php::TYPE::STRING))
-    //             return row.get(field);
-    //         else
-    //             return std::move(row);
-    //     }))
-    //         ->stack(php::value([field](php::parameters &params) -> php::value {
-    //                     php::object rs = params[0];
-    //                     result *rs_ = static_cast<result *>(php::native(rs));
-
-    //                     assert(rs.instanceof (php::class_entry<result>::entry()));
-    //                     if (rs_->n_ > 0)
-    //                     {
-    //                         return rs.call("fetch_row");
-    //                     }
-    //                     else
-    //                     {
-    //                         return nullptr;
-    //                     }
-    //                 }),
-    //                 ref);
-    // }
-    // result::~result()
-    // {
-    //     if (r_)
-    //         release();
-    // }
-    // struct fetch_row_t
-    // {
-    //     MYSQL_ROW data;
-    //     unsigned long *size;
-    // };
-    // void result::fetch_next(std::shared_ptr<coroutine> co, const php::object &ref, bool fetch_all, php::array &data_all)
-    // {
-    //     c_->exec([this](std::shared_ptr<MYSQL> c, int &error) -> MYSQL_RES * {
-    //             fetch_row_t* row = new fetch_row_t();
-    //             assert(row->data == nullptr);
-    //             if ((row->data = mysql_fetch_row(r_))) {
-    //                 row->size = mysql_fetch_lengths(r_);
-    //             }
-    //             return reinterpret_cast<MYSQL_RES*>(row); }, [this, co, ref, fetch_all, data_all](std::shared_ptr<MYSQL> c, MYSQL_RES *r, int error) mutable {
-    //             MYSQL* conn = c.get();
-    //             fetch_row_t* row = reinterpret_cast<fetch_row_t*>(r);
-    //             if(row->data == nullptr && mysql_errno(conn) != 0) {
-    //                 co->fail(mysql_error(conn), mysql_errno(conn));
-    //                 release(); // fetch 过程终止终止清理
-    //             } else if(row->data == nullptr) {
-    //                 if(fetch_all) co->resume(data_all);
-    //                 else co->resume(nullptr);
-    //                 release(); // fetch 过程终止清理
-    //             } else {
-    //                 php::array data_row((int)n_);
-    //                 for(int i = 0; i < n_; i++) {
-    //                     data_row.set(php::string(f_[i].name), php::string(row->data[i], row->size[i]));
-    //                 }
-    //                 if(fetch_all) {
-    //                     data_all.set(data_all.size(), data_row);
-    //                     fetch_next(co, ref, fetch_all, data_all);
-    //                 } else {
-    //                     co->resume(std::move(data_row));
-    //                 }
-    //             }
-    //             delete row; });
-    // }
     php::value result::fetch_row(php::parameters &params)
     {
         coroutine_handler ch{coroutine::current};
@@ -99,28 +29,6 @@ namespace flame::mysql
         for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) {
             data.set(data.size(), row);
         }
-        // row = cl_->fetch(conn, rs_, f_, n_, ch);
-        // data.set(data.size(), row);
-
-        // row = cl_->fetch(conn, rs_, f_, n_, ch);
-        // data.set(data.size(), row);
-
-        // row = cl_->fetch(conn, rs_, f_, n_, ch);
-        // data.set(data.size(), row);
-
         return data;
     }
-    // void result::release()
-    // {
-    //     std::shared_ptr<_connection_lock> cl = c_;
-    //     MYSQL_RES *r = r_;
-    //     c_.reset();
-    //     r_ = nullptr;
-    //     f_ = nullptr;
-    //     n_ = 0;
-    //     cl->exec([r](std::shared_ptr<MYSQL> c, int &error) -> MYSQL_RES * {
-    //             mysql_free_result(r);
-    //             return nullptr; }, [cl](std::shared_ptr<MYSQL> c, MYSQL_RES *r, int error) {
-    //             // cl.reset() });
-    // }
 } // namespace flame::mysql
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 6d20747..57b5f5d 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -132,13 +132,14 @@ namespace flame::redis
         { // 立刻分配使用
             std::function<void(std::shared_ptr<redisContext> c)> cb = await_.front();
             await_.pop_front();
-            auto ptr = this->shared_from_this();
+            auto self = this->shared_from_this();
             cb(
-                std::shared_ptr<redisContext>(c, [this, ptr] (redisContext *c)
+                // 释放回调函数须持有当前对象引用 self
+                // (否则连接池可能先于连接归还被销毁)
+                std::shared_ptr<redisContext>(c, [this, self](redisContext *c)
                 {
-                    boost::asio::post(guard_, std::bind(&_connection_pool::release, ptr, c));
-                })
-            );
+                    boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+                }));
         }
     }
     bool _connection_pool::ping(redisContext* c) {
diff --git a/src/url.cpp b/src/url.cpp
index ad7e6c3..83d24a9 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -42,13 +42,26 @@ namespace flame
         if (x.field_set & (1 << UF_QUERY)) {
             auto y = x.field_data[UF_QUERY];
             parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
+                std::string key = et.first;
                 std::string val = et.second;
+                // 一律小写 KEY
+                php::uppercase_inplace(key.data(), key.size());
                 val.resize(php::url_decode_inplace(val.data(), val.size()));
-                query[et.first] = val;
+                query[key] = val;
             });
             p.parse(s + y.off, y.len);
             p.end();
         }
     }
 
+    std::string url::str() {
+        std::ostringstream ss;
+        ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path << "?";
+        for(auto i=query.begin();i!=query.end();++i)
+        {
+            ss << i->first << "=" << i->second << "&";
+        }
+        return ss.str();
+    }
+
 } // namespace flame
\ No newline at end of file
diff --git a/src/url.h b/src/url.h
index 18940a9..bc6c6ed 100644
--- a/src/url.h
+++ b/src/url.h
@@ -11,7 +11,8 @@ namespace flame {
         std::map<std::string, std::string> query;
         std::string   user;
         std::string   pass;
-    private:
-        
+
+        std::string   str();
+   
     };
 }
\ No newline at end of file
diff --git a/test/mongodb_1.php b/test/mongodb_1.php
new file mode 100644
index 0000000..e761fc9
--- /dev/null
+++ b/test/mongodb_1.php
@@ -0,0 +1,19 @@
+<?php
+flame\init("mongodb_1");
+
+flame\go(function() {
+    $cli = flame\mongodb\connect("mongodb://username:password@host1:port1,host2:port2/database?replicaSet=rs1");
+    $cs = $cli->execute([
+        "find"   => "photo",
+        "filter" => ["uid" => 25865119921602568],
+        "limit"  => 10,
+    ]);
+    var_dump( $cs->fetch_row() );
+    var_dump( $cs->fetch_all() );
+
+    $cs = $cli->photo->find(["uid" => 25865119921602568]);
+    var_dump( $cs->fetch_all() );
+});
+
+
+flame\run();

From cd070133f91e3ac1d54d26f22ba018f5e00c0e79 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 23 Nov 2018 21:59:07 +0800
Subject: [PATCH 007/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E7=B3=BB?=
 =?UTF-8?q?=E7=BB=9F=E5=B8=B8=E7=94=A8=E5=8A=9F=E8=83=BD=E5=87=BD=E6=95=B0?=
 =?UTF-8?q?;=20=E4=BC=98=E5=8C=96:=20=E5=90=8E=E7=BD=AE=E5=8D=8F=E7=A8=8B?=
 =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E5=A4=84=E7=90=86;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp               |   1 -
 src/coroutine.cpp                |  14 +++-
 src/coroutine.h                  |   5 +-
 src/flame.cpp                    |   3 +-
 src/mongodb/_connection_lock.cpp |   2 +-
 src/mysql/_connection_lock.cpp   |   8 +-
 src/os/os.cpp                    | 137 +++++++++++++++++++++++++++++++
 src/os/os.h                      |  11 +++
 src/os/process.cpp               |  62 ++++++++++++++
 src/os/process.h                 |  23 ++++++
 10 files changed, 257 insertions(+), 9 deletions(-)
 create mode 100644 src/os/os.cpp
 create mode 100644 src/os/os.h
 create mode 100644 src/os/process.cpp
 create mode 100644 src/os/process.h

diff --git a/src/controller.cpp b/src/controller.cpp
index 4ce62b9..dd17e86 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -77,7 +77,6 @@ void controller_worker::run() {
 	// 2. 启动线程池, 并使用线程池运行 context_y
 	thread_.resize(3);
 	for(int i=0;i<thread_.size();++i) {
-		std::cout << "worker: " << i << std::endl;
 		thread_[i] = std::thread([this] {
 			gcontroller->context_y.run();
 		});
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 9cdc67c..c00aa9c 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -63,7 +63,10 @@ namespace flame
         coroutine::current = this;
         c1_ = std::move(c1_).resume();
     }
-
+    coroutine_handler::coroutine_handler()
+        : co_(nullptr) 
+    {
+    }
     coroutine_handler::coroutine_handler(coroutine *co)
         : co_(co)
     {
@@ -72,6 +75,15 @@ namespace flame
     {
         // std::cout << "~coroutine_handler\n";
     }
+    void coroutine_handler::reset(coroutine* co)
+    {
+        assert(co_ == nullptr);
+        co_ = co;
+    }
+    coroutine_handler::operator bool() const
+    {
+        return co_ != nullptr;
+    }
     void coroutine_handler::operator()(const boost::system::error_code &e, std::size_t n)
     {
         error = e;
diff --git a/src/coroutine.h b/src/coroutine.h
index 5f6cb14..f2d5e9f 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -34,9 +34,12 @@ namespace flame {
 	struct coroutine_handler
 	{
 	public:
-		// coroutine_handler(std::shared_ptr<coroutine> co)
+		coroutine_handler();
 		coroutine_handler(coroutine *co);
 		~coroutine_handler();
+		// !!! 慎用: 目前仅用于空构造后指定协程
+		void reset(coroutine* co);
+		operator bool() const;
 		void operator()(const boost::system::error_code& e, std::size_t n = 0);
 		void resume();
 		void suspend();
diff --git a/src/flame.cpp b/src/flame.cpp
index 451d101..be0a00c 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -1,5 +1,6 @@
 #include "vendor.h"
 #include "core.h"
+#include "os/os.h"
 #include "time/time.h"
 #include "mysql/mysql.h"
 #include "redis/redis.h"
@@ -30,11 +31,11 @@ extern "C"
             .desc({"vendor/mongoc", MONGOC_VERSION_S});
 
         flame::declare(ext);
+        flame::os::declare(ext);
         flame::time::declare(ext);
         flame::mysql::declare(ext);
         flame::redis::declare(ext);
         flame::mongodb::declare(ext);
-        // flame::os::declare(ext);
         // flame::log::declare(ext);
         // flame::udp::declare(ext);
         // flame::tcp::declare(ext);
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
index fab3861..ef65dac 100644
--- a/src/mongodb/_connection_lock.cpp
+++ b/src/mongodb/_connection_lock.cpp
@@ -28,7 +28,7 @@ namespace flame::mongodb
         ch.suspend();
         if (has) // 发生了错误
         {
-            throw php::exception(zend_ce_exception,
+            throw php::exception(zend_ce_error,
                     (boost::format("failed to fetch document: (%1%) %2%") % err->code % err->message).str(),
                     err->code);
         }
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
index da3c5ed..b64f9d2 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/mysql/_connection_lock.cpp
@@ -31,8 +31,8 @@ namespace flame::mysql
         ch.suspend();
         if(err != 0) {
             err = mysql_errno(conn_.get());
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
+            throw php::exception(zend_ce_error,
+                                 (boost::format("failed to begin transaction: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
                                  err);
         }
         
@@ -50,7 +50,7 @@ namespace flame::mysql
         {
             err = mysql_errno(conn_.get());
             throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
+                                 (boost::format("failed to commit MySQL tx: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
                                  err);
         }
     }
@@ -66,7 +66,7 @@ namespace flame::mysql
         {
             err = mysql_errno(conn_.get());
             throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
+                                 (boost::format("failed to rollback MySQL tx: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
                                  err);
         }
     }
diff --git a/src/os/os.cpp b/src/os/os.cpp
new file mode 100644
index 0000000..253a171
--- /dev/null
+++ b/src/os/os.cpp
@@ -0,0 +1,137 @@
+#include "../coroutine.h"
+#include "../controller.h"
+#include "os.h"
+#include "process.h"
+
+namespace flame::os
+{
+    void declare(php::extension_entry &ext)
+    {
+        ext
+            .function<interfaces>("flame\\os\\interfaces")
+            .function<spawn>("flame\\os\\spawn",
+            {
+                {"executable", php::TYPE::STRING},
+                {"arguments", php::TYPE::ARRAY, false, true},
+                {"options", php::TYPE::ARRAY, false, true},
+            })
+            .function<exec>("flame\\os\\exec",
+            {
+                {"executable", php::TYPE::STRING},
+                {"arguments", php::TYPE::ARRAY, false, true},
+                {"options", php::TYPE::ARRAY, false, true},
+            });
+
+        process::declare(ext);
+    }
+    static void interface(php::array& data, const struct ifaddrs *addr)
+    {
+        php::string name(addr->ifa_name);
+        php::array info(2);
+        info.set("family", addr->ifa_addr->sa_family == AF_INET ? "IPv4" : "IPv6");
+        char address[NI_MAXHOST];
+        if (getnameinfo(addr->ifa_addr,
+                        addr->ifa_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
+                        address, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST) != 0)
+        {
+            throw php::exception(zend_ce_error_exception, gai_strerror(errno));
+        }
+        info.set("address", address);
+        if (data.exists(name))
+        {
+            php::array iface = data.get(name);
+            iface.set(iface.size(), info);
+        }
+        else
+        {
+            php::array iface(2);
+            iface.set(iface.size(), info);
+            data.set(name, iface);
+        }
+    }
+    php::value interfaces(php::parameters &params)
+    {
+        struct ifaddrs *addr;
+        if (getifaddrs(&addr) != 0)
+        {
+            throw php::exception(zend_ce_error_exception, std::strerror(errno));
+        }
+        else if (!addr)
+        {
+            return php::array(0);
+        }
+        php::array data(4);
+        // 用 shared_ptr 作自动释放保证
+        std::shared_ptr<struct ifaddrs> autofree(addr, freeifaddrs);
+        do
+        {
+            if (addr->ifa_addr->sa_family != AF_INET && addr->ifa_addr->sa_family != AF_INET6)
+                continue;
+            interface(data, addr);
+        } while ((addr = addr->ifa_next) != nullptr);
+
+        return std::move(data);
+    }
+    php::value spawn(php::parameters &params)
+    {
+        php::object proc(php::class_entry<process>::entry());
+        process *proc_ = static_cast<process *>(php::native(proc));
+        proc_->exit_ = false;
+
+        auto env = boost::this_process::environment();
+        std::string exec = params[0].to_string();
+        if (exec[0] != '.' && exec[0] != '/')
+        {
+            exec = boost::process::search_path(exec).native();
+        }
+        std::vector<std::string> argv;
+        std::string cwdv = boost::filesystem::current_path().native();
+        if (params.size() > 1)
+        {
+            php::array args = params[1];
+            for (auto i = args.begin(); i != args.end(); ++i)
+            {
+                argv.push_back(i->second.to_string());
+            }
+        }
+        if (params.size() > 2)
+        {
+            php::array opts = params[2];
+            php::array envs = opts.get("env");
+            if (envs.typeof(php::TYPE::ARRAY))
+            {
+                for (auto i = envs.begin(); i != envs.end(); ++i)
+                {
+                    env[i->first.to_string()] = i->second.to_string();
+                }
+            }
+            php::string cwds = opts.get("cwd");
+            if (cwds.typeof(php::TYPE::STRING))
+            {
+                cwdv = cwds.to_string();
+            }
+        }
+
+        boost::process::child c(exec, boost::process::args = argv, env, gcontroller->context_x,
+                boost::process::start_dir = cwdv,
+                boost::process::std_out > proc_->out_,
+                boost::process::std_err > proc_->err_,
+                boost::process::on_exit = [proc, proc_] (int exit_code, const std::error_code &) {
+                    proc_->exit_ = true;
+                    if (proc_->ch_)
+                    {
+                        proc_->ch_.resume();
+                    }
+                });
+        proc.set("pid", c.id());
+        proc_->c_ = std::move(c);
+        return proc;
+    }
+    php::value exec(php::parameters &params)
+    {
+        php::object proc = spawn(params);
+        process *proc_ = static_cast<process *>(php::native(proc));
+        proc.call("wait");
+        return proc_->out_.get();
+    }
+} // namespace flame::os
diff --git a/src/os/os.h b/src/os/os.h
new file mode 100644
index 0000000..aaf41ce
--- /dev/null
+++ b/src/os/os.h
@@ -0,0 +1,11 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::os
+{
+    void declare(php::extension_entry &ext);
+    php::value interfaces(php::parameters &params);
+    php::value spawn(php::parameters &params);
+    php::value exec(php::parameters &params);
+
+} // namespace flame::os
diff --git a/src/os/process.cpp b/src/os/process.cpp
new file mode 100644
index 0000000..a5f75e3
--- /dev/null
+++ b/src/os/process.cpp
@@ -0,0 +1,62 @@
+#include "../coroutine.h"
+#include "process.h"
+
+namespace flame::os
+{
+    void process::declare(php::extension_entry &ext)
+    {
+        ext
+            .constant({"flame\\os\\SIGTERM", SIGTERM})
+            .constant({"flame\\os\\SIGKILL", SIGKILL})
+            .constant({"flame\\os\\SIGINT", SIGINT})
+            .constant({"flame\\os\\SIGUSR1", SIGUSR1})
+            .constant({"flame\\os\\SIGUSR2", SIGUSR2});
+
+        php::class_entry<process> class_process("flame\\os\\process");
+        class_process
+            .property({"pid", 0})
+            .method<&process::__construct>("__construct", {}, php::PRIVATE)
+            .method<&process::kill>("kill",
+            {
+                {"signal", php::TYPE::INTEGER, false, true}
+            })
+            .method<&process::wait>("wait")
+            .method<&process::stdout>("stdout")
+            .method<&process::stderr>("stderr");
+        ext.add(std::move(class_process));
+    }
+    php::value process::__construct(php::parameters& params)
+    {
+        return nullptr;
+    }
+    php::value process::kill(php::parameters &params)
+    {
+        if (params.size() > 0)
+        {
+            ::kill(get("pid"), params[0].to_integer());
+        }
+        else
+        {
+            ::kill(get("pid"), SIGTERM);
+        }
+        return nullptr;
+    }
+    php::value process::wait(php::parameters &params)
+    {
+        if (!exit_) {
+            ch_.reset(coroutine::current);
+            ch_.suspend();
+        }
+        return nullptr;
+    }
+    php::value process::stdout(php::parameters &params)
+    {
+        wait(params);
+        return out_.get();
+    }
+    php::value process::stderr(php::parameters &params)
+    {
+        wait(params);
+        return out_.get();
+    }
+} // namespace flame::os
diff --git a/src/os/process.h b/src/os/process.h
new file mode 100644
index 0000000..a34e1bf
--- /dev/null
+++ b/src/os/process.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::os {
+	class process: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value __construct(php::parameters& params);
+		php::value kill(php::parameters& params);
+		php::value wait(php::parameters& params);
+		php::value stdout(php::parameters& params);
+		php::value stderr(php::parameters& params);
+	private:
+		boost::process::child      c_;
+        coroutine_handler         ch_;
+		std::future<std::string> out_;
+		std::future<std::string> err_;
+		bool exit_;
+		friend class php::value spawn(php::parameters& params);
+		friend class php::value exec(php::parameters& params);
+	};
+}

From 9d548183f5fe7874e804813b7a304328856807b4 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 25 Nov 2018 16:28:52 +0800
Subject: [PATCH 008/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=20?=
 =?UTF-8?q?Kafka=20=E7=94=9F=E4=BA=A7=E6=B6=88=E8=B4=B9=E6=94=AF=E6=8C=81;?=
 =?UTF-8?q?=20=E4=BC=98=E5=8C=96:=20=E5=90=AF=E5=8A=A8=E5=8D=8F=E7=A8=8B?=
 =?UTF-8?q?=E6=97=B6=E8=BF=94=E5=9B=9E=E6=9E=84=E5=BB=BA=E7=9A=84=E5=AF=B9?=
 =?UTF-8?q?=E8=B1=A1;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/kafka.php           | 107 +++++++++++++++++++++++++++++
 doc/os.php              |  60 ++++++++++++++++
 src/coroutine.cpp       |   3 +-
 src/coroutine.h         |   2 +-
 src/flame.cpp           |   8 +--
 src/kafka/_consumer.cpp | 149 ++++++++++++++++++++++++++++++++++++++++
 src/kafka/_consumer.h   |  22 ++++++
 src/kafka/_producer.cpp |  63 +++++++++++++++++
 src/kafka/_producer.h   |  17 +++++
 src/kafka/consumer.cpp  |  89 ++++++++++++++++++++++++
 src/kafka/consumer.h    |  27 ++++++++
 src/kafka/kafka.cpp     | 108 +++++++++++++++++++++++++++++
 src/kafka/kafka.h       |  13 ++++
 src/kafka/message.cpp   |  77 +++++++++++++++++++++
 src/kafka/message.h     |  21 ++++++
 src/kafka/producer.cpp  |  80 +++++++++++++++++++++
 src/kafka/producer.h    |  20 ++++++
 src/time/time.h         |   1 +
 test/kafka_1.php        |  39 +++++++++++
 test/os_1.php           |  13 ++++
 20 files changed, 912 insertions(+), 7 deletions(-)
 create mode 100644 doc/kafka.php
 create mode 100644 doc/os.php
 create mode 100644 src/kafka/_consumer.cpp
 create mode 100644 src/kafka/_consumer.h
 create mode 100644 src/kafka/_producer.cpp
 create mode 100644 src/kafka/_producer.h
 create mode 100644 src/kafka/consumer.cpp
 create mode 100644 src/kafka/consumer.h
 create mode 100644 src/kafka/kafka.cpp
 create mode 100644 src/kafka/kafka.h
 create mode 100644 src/kafka/message.cpp
 create mode 100644 src/kafka/message.h
 create mode 100644 src/kafka/producer.cpp
 create mode 100644 src/kafka/producer.h
 create mode 100644 test/kafka_1.php
 create mode 100644 test/os_1.php

diff --git a/doc/kafka.php b/doc/kafka.php
new file mode 100644
index 0000000..463044a
--- /dev/null
+++ b/doc/kafka.php
@@ -0,0 +1,107 @@
+<?php
+namespace flame\kafka;
+
+/**
+ * @param array $config 基本 Kafka 配置, 一下两个选项必要:
+ *  * "bootstrap.servers" - string 服务器地址(9092);
+ *  * "group.id" - string 消费组名称(同一消费组共享消费进度);
+ * 可以使用一个自定义的的参数:
+ *  * "concurrent" - 控制并行消费协程数量, 默认为 8 (数值类型);
+ * @see 其他可配置选项均为字符串类型(非字符串会被强制转换), 请参考: 
+ *  https://github.com/edenhill/librdkafka/blob/v0.11.6/CONFIGURATION.md
+ * @param array $topics 待消费的数据源 TOPIC 名称列表
+ */
+function consume(array $config, array $topics) {}
+
+/**
+ * @param array $config 基本 Kafka 配置, 一下两个选项必要:
+ *  * "bootstrap.servers" - 服务器地址(9092);
+ *  * "group.id" - 消费组名称(同一消费组共享消费进度);
+ * @see 其他可配置选项请参考: 
+ *  https://github.com/edenhill/librdkafka/blob/v0.11.6/CONFIGURATION.md
+ * @param array $topics 待生产的目标 TOPIC 名称列表
+ */
+function produce(array $config, array $topics) {}
+
+class consumer {
+    /**
+     * 执行消费, 实际的回调函数会被对应的协程回调执行
+     * 注意: 由于 Kafka 消费组的平衡调度, 消费可能会被延迟执行 (15s+);
+     * @param callable $cb 消费回调函数
+     */
+    function run(callable $cb) {}
+    /**
+     * 手动提交消息(的偏移), 一般需要将 "enable.auto.commit" 设为 "false" 
+     * @see consume();
+     */
+    function commit(message $msg) {}
+    /**
+     * 关闭消费者(停止消费)
+     * 注意: 为保证消费消息不丢失, 消费者的实际关闭可能会持续一段时间
+     */
+    function close() {}
+}
+
+class producer {
+    /**
+     * 生产消息
+     * 注意: 此函数会将消息放入"生产队列", 并通过后台线程传输; 
+     * (为保证完成, 请使用 `flush()`)
+     * @see producer::flush()
+     * @param string $topic 生成目标 TOPIC 名称, 必须是 待生产 $topics 数组中的一个;
+     * @see produce()
+     * @param mixed $mssage 若为 message 类型的对象, 则后续参数无效; 
+     *  否则为现场 message 对象的 payload 数据;
+     * @see class message;
+     * @param string $key 
+     * @param array $header
+     */
+    function publish(string $topic, mixed $message, string $key = null, array $header = []) {}
+    /**
+     * 清空"生产队列", 若当前队列中还有消息等待其传输完毕;
+     */
+    function flush() {}
+}
+
+class message implements JsonSerializable {
+    /**
+     * @property string $topic
+     */
+    public $topic;
+    /**
+     * @property integer $partition
+     */
+    public $partition;
+    /**
+     * @property string $key
+     */
+    public $key;
+    /**
+     * @property integer $offset
+     */
+    public $offset;
+    /**
+     * @property array $header
+     */
+    public $header;
+    /**
+     * @property string $payload 消息内容
+     */
+    public $payload;
+    /**
+     * @property $timestamp 消息时间戳(一般为毫秒)
+     */
+    public $timestamp;
+    /**
+     * 构造一个新消息, 将其 $timestamp 填充为当前时间
+     */
+    function __construct($payload = "", $key = null) {}
+    /**
+     * 返回当前消息内容 ($payload)
+     */
+    function __toString() {}
+    /**
+     * Json 序列化, 返回 topic + key + payload 关联数组
+     */
+    function jsonSerialize() {}
+}
diff --git a/doc/os.php b/doc/os.php
new file mode 100644
index 0000000..376f4a6
--- /dev/null
+++ b/doc/os.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace flame\os;
+
+const SIGTERM = 15;
+const SIGKILL = 9;
+const SIGINT  = 2;
+const SIGUSR1 = 10;
+const SIGUSR2 = 12;
+
+/**
+ * 获取当前系统网卡绑定地址
+ * @example
+ *  array(2) {
+ *      ["lo"]=>
+ *      array(1) {
+ *          [0]=>
+ *          array(2) {
+ *          ["family"]=>
+ *          string(4) "IPv4"
+ *          ["address"]=>
+ *          string(9) "127.0.0.1"
+ *          }
+ *      }
+ *      ["eth0"]=>
+ *      array(1) {
+ *          [0]=>
+ *          array(2) {
+ *          ["family"]=>
+ *          string(4) "IPv4"
+ *          ["address"]=>
+ *          string(13) "10.110.16.197"
+ *          }
+ *      }
+ *      }
+ */
+function interfaces():array {}
+/**
+ * 异步启动进程
+ * @return 进程对象
+ */
+function spawn(string $command, array $argv = [], array $options = []):process {}
+/**
+ * 调用上述 spawn() 异步启动进程, 并等待其结束, 返回进程标准输出
+ * @return string 进程标准输出内容
+ */
+function exec(string $command, array $argv, array $options = []):string {}
+
+/**
+ * 进程对象
+ */
+class process {
+    function kill(integer $signal = SIGTERM) {}
+    /**
+     * 等待进程结束
+     */
+    function wait() {}
+    function stdout():string {}
+    function stderr():string {}
+}
\ No newline at end of file
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index c00aa9c..fce7172 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -19,7 +19,7 @@ namespace flame
         // EG(fake_scope) = ctx.scope;
         EG(current_execute_data) = ctx.current_execute_data;
     }
-    void coroutine::start(php::callable fn, zend_execute_data* execute_data)
+    std::shared_ptr<coroutine> coroutine::start(php::callable fn, zend_execute_data* execute_data)
     {
         auto co = std::make_shared<coroutine>(std::move(fn));
         // 需要即时保存 PHP 堆栈
@@ -42,6 +42,7 @@ namespace flame
             });
             co->resume();
         });
+        return co;
     }
     coroutine::coroutine(php::callable &&fn)
         : fn_(std::move(fn)), c1_(), c2_() {}
diff --git a/src/coroutine.h b/src/coroutine.h
index f2d5e9f..ad978b5 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -18,7 +18,7 @@ namespace flame {
 		static coroutine* current;
 		static void save_context(php_context_t &ctx);
 		static void restore_context(php_context_t& ctx);
-		static void start(php::callable fn, zend_execute_data* execute_data = nullptr);
+		static std::shared_ptr<coroutine> start(php::callable fn, zend_execute_data* execute_data = nullptr);
 		coroutine(php::callable&& fn);
 
 		void suspend();
diff --git a/src/flame.cpp b/src/flame.cpp
index be0a00c..dde8d93 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -5,6 +5,7 @@
 #include "mysql/mysql.h"
 #include "redis/redis.h"
 #include "mongodb/mongodb.h"
+#include "kafka/kafka.h"
 
 extern "C"
 {
@@ -36,15 +37,12 @@ extern "C"
         flame::mysql::declare(ext);
         flame::redis::declare(ext);
         flame::mongodb::declare(ext);
+        flame::kafka::declare(ext);
+        // flame::rabbitmq::declare(ext);
         // flame::log::declare(ext);
         // flame::udp::declare(ext);
         // flame::tcp::declare(ext);
         // flame::http::declare(ext);
-        
-        // flame::rabbitmq::declare(ext);
-        
-        
-        // flame::kafka::declare(ext);
         return ext;
     }
 };
\ No newline at end of file
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
new file mode 100644
index 0000000..7edcf47
--- /dev/null
+++ b/src/kafka/_consumer.cpp
@@ -0,0 +1,149 @@
+#include "../controller.h"
+#include "_consumer.h"
+#include "kafka.h"
+#include "message.h"
+
+namespace flame::kafka
+{
+    static rd_kafka_topic_partition_list_t *array2topics(const php::array &topics)
+    { // 目标订阅的 TOPIC
+        rd_kafka_topic_partition_list_t *t = rd_kafka_topic_partition_list_new(topics.size());
+        for (auto i = topics.begin(); i != topics.end(); ++i)
+        {
+            rd_kafka_topic_partition_list_add(t, i->second.to_string().c_str(), RD_KAFKA_PARTITION_UA);
+        }
+        return t;
+    }
+    _consumer::_consumer(php::array &config, php::array &topics)
+    : strd_(gcontroller->context_y)
+    {
+        if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
+        {
+            throw php::exception(zend_ce_type_error, "kafka conf 'bootstrap.servers' is required");
+        }
+        if (!config.exists("group.id"))
+        {
+            throw php::exception(zend_ce_type_error, "kafka conf 'group.id' is required");
+        }
+        rd_kafka_conf_t *conf = array2conf(config);
+        char err[256];
+
+        conn_ = rd_kafka_new(RD_KAFKA_CONSUMER, conf, err, sizeof(err));
+        if (!conn_)
+        {
+            throw php::exception(zend_ce_type_error,
+                                (boost::format("failed to create Kafka Consumer: %1%") % err).str(), -1);
+        }
+        auto r = rd_kafka_poll_set_consumer(conn_);
+        if (r != RD_KAFKA_RESP_ERR_NO_ERROR)
+        {
+            throw php::exception(zend_ce_type_error,
+                                (boost::format("failed to create Kafka Consumer: (%1%) %2%") % r % rd_kafka_err2str(r)).str(), r);
+        }
+        if (topics.size() == 0)
+        {
+            throw php::exception(zend_ce_type_error, "failed to create Kafka Consumer: target topics missing", -1);
+        }
+        tops_ = array2topics(topics);
+    }
+    _consumer::~_consumer()
+    {
+        // rd_kafka_consumer_close(conn_);
+        rd_kafka_topic_partition_list_destroy(tops_);
+        rd_kafka_destroy(conn_);
+    }
+    void _consumer::subscribe(coroutine_handler &ch)
+    {
+        rd_kafka_resp_err_t err;
+        boost::asio::post(gcontroller->context_y, [this, &err, &ch] ()
+        {
+            err = rd_kafka_subscribe(conn_, tops_);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
+        {
+            throw php::exception(zend_ce_exception,
+                (boost::format() % err % rd_kafka_err2str(err)).str(),
+                err);
+        }
+    }
+    php::object _consumer::consume(coroutine_handler& ch)
+    {
+        rd_kafka_resp_err_t err;
+        rd_kafka_message_t* msg = nullptr;
+        // 由于 poll 动作本身阻塞, 为防止所有工作线被占用, 这里绑定到 strd_ 执行
+        // (理论上仅占用一个协程)
+        boost::asio::post(strd_, [this, &err, &msg, &ch]()
+        {
+            msg = rd_kafka_consumer_poll(conn_, 200);
+            if(!msg)
+            {
+                // 这里考虑关闭 consumer 的即时型, 需要在 200ms 超时后进行关闭判定
+                // 考虑到可能并行协程数量, 最长需要 concurrent * 200ms 才能完全关闭
+                err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
+            }
+            else if (msg->err)
+            {
+                err = msg->err;
+                rd_kafka_message_destroy(msg);
+            }
+            else
+            {
+                err = RD_KAFKA_RESP_ERR_NO_ERROR;
+            }
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(err == RD_KAFKA_RESP_ERR__PARTITION_EOF)
+        {
+            return nullptr;
+        }
+        else if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
+        {
+            throw php::exception(zend_ce_error,
+                (boost::format("failed to consume Kafka message: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
+                err);
+        }
+        else
+        {
+            php::object obj(php::class_entry<message>::entry());
+            message* ptr = static_cast<message*>(php::native(obj));
+            ptr->build_ex(msg); // msg 交由 message 对象管理
+            return std::move(obj);
+        }
+    }
+    void _consumer::commit(const php::object& obj, coroutine_handler& ch)
+    {
+        rd_kafka_resp_err_t err;
+        rd_kafka_message_t *msg = static_cast<message*>(php::native(obj))->msg_;
+        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]()
+        {
+            err = rd_kafka_commit_message(conn_, msg, 0);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
+        {
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to commit Kafka message: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
+                                 err);
+        }
+    }
+    void _consumer::close(coroutine_handler& ch)
+    {
+        rd_kafka_resp_err_t err;
+        boost::asio::post(gcontroller->context_y, [this, &err, &ch] () {
+            err = rd_kafka_consumer_close(conn_);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
+        {
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to close Kafka consumer: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
+                                 err);
+        }
+    }
+
+} // namespace flame::kafka
diff --git a/src/kafka/_consumer.h b/src/kafka/_consumer.h
new file mode 100644
index 0000000..1961855
--- /dev/null
+++ b/src/kafka/_consumer.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::kafka
+{
+    class _consumer
+    {
+    public:
+        _consumer(php::array& config, php::array& topics);
+        ~_consumer();
+        void subscribe(coroutine_handler& ch);
+        php::object consume(coroutine_handler& ch);
+        void commit(const php::object& msg, coroutine_handler& ch);
+        void close(coroutine_handler& ch);
+    private:
+        rd_kafka_t* conn_;
+        rd_kafka_topic_partition_list_t* tops_;
+        // 此 strand 用于限制消费动作, 防止其堵塞所有工作线程
+        boost::asio::io_context::strand  strd_;
+    };
+} // namespace flame::kafka
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
new file mode 100644
index 0000000..2273e95
--- /dev/null
+++ b/src/kafka/_producer.cpp
@@ -0,0 +1,63 @@
+#include "../controller.h"
+#include "_producer.h"
+#include "kafka.h"
+
+namespace flame::kafka
+{
+    _producer::_producer(php::array& config, php::array& topics)
+    {
+        if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
+        {
+            throw php::exception(zend_ce_type_error, "kafka conf 'bootstrap.servers' is required");
+        }
+        rd_kafka_conf_t *conf = array2conf(config);
+        char err[256];
+
+        conn_ = rd_kafka_new(RD_KAFKA_PRODUCER, conf, err, sizeof(err));
+        if (!conn_)
+        {
+            throw php::exception(zend_ce_type_error,
+                                 (boost::format("failed to create Kafka Consumer: %1%") % err).str(), -1);
+        }
+        for (auto i = topics.begin(); i != topics.end(); ++i)
+        {
+            php::string topic = i->second.to_string();
+            tops_[topic] = rd_kafka_topic_new(conn_, topic.c_str(), nullptr);
+        }
+    }
+    _producer::~_producer()
+    {
+        for(auto i=tops_.begin(); i!=tops_.end(); ++i) {
+            rd_kafka_topic_destroy(i->second);
+        }
+        rd_kafka_destroy(conn_);
+    }
+    void _producer::publish(const php::string &topic, const php::string &key, const php::string &payload, const php::array &headers, coroutine_handler &ch)
+    {
+        rd_kafka_resp_err_t err;
+        rd_kafka_headers_t* hdrs = array2hdrs(headers);
+        boost::asio::post(gcontroller->context_y, [this, &err, &topic, &key, &payload, &hdrs, &ch]() {
+            err = rd_kafka_producev(conn_,
+                              RD_KAFKA_V_TOPIC(topic.c_str()),
+                              RD_KAFKA_V_KEY(key.c_str(), key.size()),
+                              RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
+                              RD_KAFKA_V_HEADERS(hdrs),
+                              RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
+                              RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
+                              RD_KAFKA_V_END);
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+        });
+        ch.suspend();
+        if(err != RD_KAFKA_RESP_ERR_NO_ERROR) {
+            // 发生错误时, 需要手动销毁
+            if(hdrs) rd_kafka_headers_destroy(hdrs);
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to publish Kafka message: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
+                                 err);
+        }
+    }
+    void _producer::flush(coroutine_handler& ch)
+    {
+        rd_kafka_flush(conn_, 10000);
+    }
+} // namespace flame::kafka
diff --git a/src/kafka/_producer.h b/src/kafka/_producer.h
new file mode 100644
index 0000000..ab98aad
--- /dev/null
+++ b/src/kafka/_producer.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::kafka
+{
+    class _producer {
+    public:
+        _producer(php::array& config, php::array& topics);
+        ~_producer();
+        void publish(const php::string& topic, const php::string& key, const php::string& payload, const php::array& headers, coroutine_handler& ch);
+        void flush(coroutine_handler& ch);
+    private:
+        rd_kafka_t* conn_;
+        std::map<php::string, rd_kafka_topic_t*> tops_;
+    };
+} // namespace flame::kafka
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
new file mode 100644
index 0000000..d1c2498
--- /dev/null
+++ b/src/kafka/consumer.cpp
@@ -0,0 +1,89 @@
+#include "../coroutine.h"
+#include "../time/time.h"
+#include "consumer.h"
+#include "_consumer.h"
+#include "kafka.h"
+#include "message.h"
+
+namespace flame::kafka {
+
+    void consumer::declare(php::extension_entry& ext) {
+		php::class_entry<consumer> class_consumer("flame\\kafka\\consumer");
+		class_consumer
+			.method<&consumer::__construct>("__construct", {}, php::PRIVATE)
+			.method<&consumer::run>("run",
+            {
+				{"callable", php::TYPE::CALLABLE},
+			})
+            .method<&consumer::commit>("commit",
+            {
+                {"message", "flame\\kafka\\message"}
+            })
+			.method<&consumer::close>("close");
+		ext.add(std::move(class_consumer));
+	}
+
+	php::value consumer::__construct(php::parameters& params)
+    {
+		return nullptr;
+	}
+	php::value consumer::run(php::parameters& params)
+    {
+        close_ = false;
+        ex_ = EG(current_execute_data);
+        cb_ = params[0];
+        
+        // 启动若干协程, 然后进行"并行"消费
+        std::vector<std::shared_ptr<coroutine>> cos(cc_);
+        int run = 0;
+        for(int i=0;i<cos.size();++i)
+        {
+            // 启动协程开始消费
+            ++run;
+            cos[i] = coroutine::start(php::value([this, &run] (php::parameters& params) -> php::value
+            {
+                coroutine_handler ch {coroutine::current};
+                while(!close_)
+                {
+                    php::object msg = cs_->consume(ch);
+                        // 可能出现为 NULL 的情况
+                    if (msg.typeof(php::TYPE::NULLABLE))
+                    {
+                        continue;
+                    }
+                    try {
+                        cb_.call({msg});
+                    }
+                    catch(const std::exception& ex)
+                    {
+                        std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                        close_ = true;
+                    }
+                }
+                if (--run == 0) // 当所有并行协程结束后, 停止消费
+                {
+                    ch_.resume();
+                }
+                return nullptr;
+            }));
+        }
+        // 暂停当前消费协程 (等待消费停止)
+        ch_.reset(coroutine::current);
+        ch_.suspend();
+        return nullptr;
+	}
+    php::value consumer::commit(php::parameters& params)
+    {
+        php::object obj = params[0];
+        coroutine_handler ch {coroutine::current};
+        
+        cs_->commit(obj, ch);
+        return nullptr;
+    }
+	php::value consumer::close(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
+        cs_->close(ch);
+        close_ = true;
+        return nullptr;
+	}
+} // namespace 
diff --git a/src/kafka/consumer.h b/src/kafka/consumer.h
new file mode 100644
index 0000000..bc42d37
--- /dev/null
+++ b/src/kafka/consumer.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::kafka
+{
+    class _consumer;
+    class consumer : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value run(php::parameters &params);
+        php::value commit(php::parameters &params);
+        php::value close(php::parameters &params);
+
+    private:
+        std::shared_ptr<_consumer> cs_;
+        coroutine_handler          ch_;
+        int                        cc_ = 8;
+        php::callable              cb_;
+        zend_execute_data*         ex_;
+        bool close_ = false;
+
+        friend php::value consume(php::parameters &params);
+    };
+} // namespace flame::kafka
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
new file mode 100644
index 0000000..02321b1
--- /dev/null
+++ b/src/kafka/kafka.cpp
@@ -0,0 +1,108 @@
+#include "kafka.h"
+#include "message.h"
+#include "consumer.h"
+#include "_consumer.h"
+#include "producer.h"
+#include "_producer.h"
+
+namespace flame::kafka
+{
+    void declare(php::extension_entry &ext)
+    {
+        ext
+			.function<consume>("flame\\kafka\\consume", {
+				{"options", php::TYPE::ARRAY},
+                {"topics", php::TYPE::ARRAY},
+			})
+            .function<produce>("flame\\kafka\\produce", {
+				{"options", php::TYPE::ARRAY},
+                {"topics", php::TYPE::ARRAY},
+			});;
+		message::declare(ext);
+        consumer::declare(ext);
+		producer::declare(ext);
+    }
+    php::value consume(php::parameters& params)
+    {
+        php::array config = params[0];
+        php::array topics = params[1];
+        
+        php::object obj(php::class_entry<consumer>::entry());
+        consumer* ptr = static_cast<consumer*>(php::native(obj));
+        ptr->cs_.reset(new _consumer(config, topics));
+        if (config.exists("concurrent"))
+        {
+            ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrenty")), 1), 8), 256);
+            config.erase("concurrent");
+        }
+        coroutine_handler ch {coroutine::current};
+        // 订阅
+        ptr->cs_->subscribe(ch);
+        return std::move(obj);
+    }
+    php::value produce(php::parameters& params)
+    {
+        php::array config = params[0];
+        php::array topics = params[1];
+        
+        php::object obj(php::class_entry<producer>::entry());
+        producer* ptr = static_cast<producer*>(php::native(obj));
+        ptr->pd_.reset(new _producer(config, topics));
+
+        // TODO 优化: 确认首次连接已建立
+        return std::move(obj);
+    }
+
+
+    rd_kafka_conf_t* array2conf(const php::array &config)
+    {
+        if(config.empty()) return nullptr;
+        char err[256];
+        rd_kafka_conf_t *conf = rd_kafka_conf_new();
+        for (auto i = config.begin(); i != config.end(); ++i)
+        {
+            php::string key = i->first.to_string();
+            php::string val = i->second.to_string();
+
+            if(RD_KAFKA_CONF_OK != rd_kafka_conf_set(conf, key.data(), val.data(), err, sizeof(err)))
+            {
+                throw php::exception(zend_ce_type_error,
+                        (boost::format("unable to set Kafka config: %1%") % err).str(), -1);
+            }
+        }
+        return conf;
+    }
+    rd_kafka_headers_t* array2hdrs(const php::array& data)
+    {
+        rd_kafka_headers_t *hdrs = nullptr;
+        if (!data.empty() && data.typeof(php::TYPE::ARRAY) && data.size() > 0)
+        {
+            hdrs = rd_kafka_headers_new(data.size());
+            for (auto i = data.begin(); i != data.end(); ++i)
+            {
+                php::string key = i->first.to_string(),
+                            val = i->second.to_string();
+                rd_kafka_header_add(hdrs,
+                                    key.c_str(), key.size(),
+                                    val.c_str(), val.size());
+            }
+        }
+        return hdrs;
+    }
+    php::array hdrs2array(rd_kafka_headers_t* hdrs)
+    {   
+        if(hdrs == nullptr)
+        {
+            return php::array(0);
+        }
+        php::array header(rd_kafka_header_cnt(hdrs));
+        const char *key;
+        const void *val;
+        std::size_t len;
+        for (std::size_t i = 0; rd_kafka_header_get_all(hdrs, i, &key, &val, &len) == RD_KAFKA_RESP_ERR_NO_ERROR; ++i)
+        {
+            header.set(key, php::string((const char *)val, len));
+        }
+        return header;
+    }
+}
\ No newline at end of file
diff --git a/src/kafka/kafka.h b/src/kafka/kafka.h
new file mode 100644
index 0000000..3d868ab
--- /dev/null
+++ b/src/kafka/kafka.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::kafka
+{
+    void declare(php::extension_entry &ext);
+    php::value consume(php::parameters& params);
+    php::value produce(php::parameters& params);
+    
+    rd_kafka_conf_t*    array2conf(const php::array& data);
+    rd_kafka_headers_t* array2hdrs(const php::array& data);
+    php::array          hdrs2array(rd_kafka_headers_t* hdrs);
+} // namespace flame::kafka
diff --git a/src/kafka/message.cpp b/src/kafka/message.cpp
new file mode 100644
index 0000000..1efc91a
--- /dev/null
+++ b/src/kafka/message.cpp
@@ -0,0 +1,77 @@
+#include "message.h"
+#include "kafka.h"
+#include "../time/time.h"
+
+namespace flame::kafka {
+    void message::declare(php::extension_entry& ext)
+    {
+        php::class_entry<message> class_message("flame\\kafka\\message");
+		class_message
+            .implements(&php_json_serializable_ce)
+			.property({"topic", ""})
+			.property({"partition", -1})
+			.property({"key", ""})
+            .property({"offset", -1})
+            .property({"header", nullptr})
+			.property({"payload", nullptr})
+			.property({"timestamp", 0})
+			.method<&message::__construct>("__construct",
+            {
+				{"payload", php::TYPE::STRING, false, true},
+                {"key", php::TYPE::STRING, false, true},
+			})
+			.method<&message::to_string>("__toString")
+            .method<&message::to_json>("jsonSerialize");
+
+		ext.add(std::move(class_message));
+    }
+    message::~message()
+    {
+        if(msg_) rd_kafka_message_destroy(msg_);
+    }
+    void message::build_ex(rd_kafka_message_t* msg)
+    {
+        // 用于单挑 message 的提交
+        msg_ = msg;
+        // ！！！ 是否必须复制出 PHP 的内容？
+        set("topic", php::string(rd_kafka_topic_name(msg->rkt)));
+        set("partition", msg->partition);
+        set("key", php::string((const char*)msg->key, msg->key_len));
+        set("offset", msg->offset);
+        rd_kafka_headers_t* hdrs;
+        if(rd_kafka_message_headers(msg, &hdrs) == RD_KAFKA_RESP_ERR_NO_ERROR)
+        {
+            set("header", hdrs2array(hdrs));
+        }
+        // header
+        set("payload", php::string((const char*)msg->payload, msg->len));
+        set("timestamp", rd_kafka_message_timestamp(msg, nullptr));
+    }
+    php::value message::__construct(php::parameters& params)
+    {
+        if(params.size() > 0)
+        {
+            set("payload", params[0].to_string());
+        }
+        if(params.size() > 1)
+        {
+            set("key", params[1].to_string());
+        }
+        set("header", php::array(4));
+        set("timestamp", 
+            std::chrono::duration_cast<std::chrono::milliseconds>(flame::time::now().time_since_epoch()).count());
+        return nullptr;
+    }
+    php::value message::to_json(php::parameters& params)
+    {
+        php::array json(4);
+        json.set("topic",  get("topic"));
+        json.set("key",    get("key"));
+        json.set("payload",get("payload"));
+        return json;
+    }
+    php::value message::to_string(php::parameters& params)
+    {
+        return get("payload");
+    }
+} // namespace flame::kafka
diff --git a/src/kafka/message.h b/src/kafka/message.h
new file mode 100644
index 0000000..a1d7604
--- /dev/null
+++ b/src/kafka/message.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::kafka
+{
+	class _consumer;
+	class message: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		~message();
+		void build_ex(rd_kafka_message_t* msg);
+		php::value __construct(php::parameters& params); // 私有
+		php::value to_json(php::parameters& params);
+		php::value to_string(php::parameters& params);
+	private:
+	  rd_kafka_message_t *msg_ = nullptr;
+
+	  friend class _consumer;
+	//   friend class _producer;
+	};
+}
diff --git a/src/kafka/producer.cpp b/src/kafka/producer.cpp
new file mode 100644
index 0000000..540e5d3
--- /dev/null
+++ b/src/kafka/producer.cpp
@@ -0,0 +1,80 @@
+#include "../coroutine.h"
+#include "kafka.h"
+#include "message.h"
+#include "_producer.h"
+#include "producer.h"
+
+namespace flame::kafka
+{
+    void producer::declare(php::extension_entry &ext)
+    {
+        php::class_entry<producer> class_producer("flame\\kafka\\producer");
+        class_producer
+            .method<&producer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&producer::__destruct>("__destruct")
+            .method<&producer::publish>("publish",
+            {
+                {"topic", php::TYPE::STRING},
+            })
+            .method<&producer::flush>("flush");
+        ext.add(std::move(class_producer));
+    }
+    php::value producer::__construct(php::parameters &params)
+    {
+        return nullptr;
+    }
+    php::value producer::__destruct(php::parameters &params)
+    {
+        coroutine_handler ch {coroutine::current};
+        pd_->flush(ch);
+        return nullptr;
+    }
+    php::value producer::publish(php::parameters &params)
+    {
+        php::string topic = params[0].to_string(), key, payload;
+        php::array header;
+        if (params[1].instanceof (php::class_entry<message>::entry()))
+        {
+            php::object msg = params[1];
+            key = msg.get("key").to_string();
+            payload = msg.get("payload").to_string();
+            header = msg.get("header");
+            if (!header.typeof(php::TYPE::ARRAY))
+            {
+                header = php::array(0);
+            }
+        }
+        else
+        {
+            payload = params[1].to_string();
+            if (params.size() > 2)
+            {
+                key = params[2].to_string();
+            }
+            else
+            {
+                key = php::string(0);
+            }
+            if (params.size() > 3)
+            {
+                if (params[3].typeof(php::TYPE::ARRAY))
+                {
+                    header = params[4];
+                }else
+                {
+                    header = php::array(0);
+                }
+            }
+        }
+
+        coroutine_handler ch {coroutine::current};
+        pd_->publish(topic, key, payload, header, ch);
+        return nullptr;
+    }
+    php::value producer::flush(php::parameters &params)
+    {
+        coroutine_handler ch {coroutine::current};
+        pd_->flush(ch);
+        return nullptr;
+    }
+} // namespace flame::kafka
diff --git a/src/kafka/producer.h b/src/kafka/producer.h
new file mode 100644
index 0000000..476f420
--- /dev/null
+++ b/src/kafka/producer.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::kafka
+{
+    class _producer;
+    class producer : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value __destruct(php::parameters &params);
+        php::value publish(php::parameters &params);
+        php::value flush(php::parameters &params);
+
+    private:
+        std::shared_ptr<_producer> pd_;
+        friend php::value produce(php::parameters &params);
+    };
+} // namespace flame::kafka
diff --git a/src/time/time.h b/src/time/time.h
index c427354..d8a8a77 100644
--- a/src/time/time.h
+++ b/src/time/time.h
@@ -4,4 +4,5 @@ namespace flame::time
 {
     void declare(php::extension_entry &ext);
     std::chrono::time_point<std::chrono::system_clock> now();
+    std::string iso();
 }
\ No newline at end of file
diff --git a/test/kafka_1.php b/test/kafka_1.php
new file mode 100644
index 0000000..c5772fa
--- /dev/null
+++ b/test/kafka_1.php
@@ -0,0 +1,39 @@
+<?php
+flame\init("kafka_1");
+
+flame\go(function() {
+    $consumer = flame\kafka\consume([
+        "bootstrap.servers" => "host1:port1,host2:port2",
+        "group.id" => "flame-test-consumer",
+        "auto.offset.reset" => "smallest",
+    ], ["test"]);
+    flame\go(function() use($consumer) {
+        // 60 秒后关闭消费者
+        flame\time\sleep(60000);
+        $consumer->close();
+    });
+    $consumer->run(function($msg) {
+        var_dump($msg);
+        flame\time\sleep(30);
+    });
+});
+
+
+flame\go(function() {
+    $producer = flame\kafka\produce([
+        "bootstrap.servers" => "host1:port1,host2:port2",
+    ], ["test"]);
+    
+    for($i=0;$i<100;++$i) {
+        echo $i, "\n";
+        $message = new flame\kafka\message("this is the payload", "this is the key");
+        $message->header["key"] = "value";
+        $producer->publish("test", $message);
+        flame\time\sleep(50);
+        $producer->publish("test", "this is the payload", "this is the key");
+        flame\time\sleep(50);
+    }
+    $producer->flush();
+});
+
+flame\run();
diff --git a/test/os_1.php b/test/os_1.php
new file mode 100644
index 0000000..855f941
--- /dev/null
+++ b/test/os_1.php
@@ -0,0 +1,13 @@
+<?php
+flame\init("os_1");
+flame\go(function() {
+	$info = flame\os\interfaces();
+	var_dump($info);
+	$proc = flame\os\spawn("ls", ["-l"], [
+		"cwd" => "/data/htdocs",
+	]);
+	$proc->wait();
+	var_dump( $proc->stdout() );
+	var_dump( flame\os\exec("ping", ["-c", 3, "www.baidu.com"]) );
+});
+flame\run();

From 296c2e2dbb532e77abfe37fa279e0cae50e85354 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 26 Nov 2018 21:33:21 +0800
Subject: [PATCH 009/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=20?=
 =?UTF-8?q?RabbitMQ=20=E9=87=8D=E6=9E=84;=20=E6=96=B0=E5=A2=9E:=20flame\qu?=
 =?UTF-8?q?eue=20=E5=AE=9E=E7=8E=B0=E5=8D=8F=E7=A8=8B=E9=80=9A=E8=AE=AF;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                        |   2 +-
 doc/core.php                     |  27 +++++
 doc/rabbitmq.php                 |  76 ++++++++++++++
 src/command.h                    |  20 ----
 src/controller.cpp               |   5 -
 src/controller.h                 |   2 +-
 src/core.cpp                     |  44 +++++++-
 src/core.h                       |   1 +
 src/coroutine.cpp                |  24 +++--
 src/coroutine.h                  |  14 ++-
 src/coroutine_queue.h            | 115 +++++++++++++++++++++
 src/flame.cpp                    |   3 +-
 src/kafka/_consumer.cpp          |   8 +-
 src/kafka/_producer.cpp          |   2 +-
 src/mongodb/_connection_base.cpp |   2 +-
 src/mongodb/_connection_lock.cpp |   2 +-
 src/mongodb/_connection_pool.cpp |   2 +-
 src/mysql/_connection_base.cpp   |   4 +-
 src/mysql/_connection_lock.cpp   |   6 +-
 src/mysql/_connection_pool.cpp   |   4 +-
 src/queue.cpp                    |  56 ++++++++++
 src/queue.h                      |  20 ++++
 src/rabbitmq/_client.cpp         | 171 +++++++++++++++++++++++++++++++
 src/rabbitmq/_client.h           |  34 ++++++
 src/rabbitmq/consumer.cpp        |  94 +++++++++++++++++
 src/rabbitmq/consumer.h          |  27 +++++
 src/rabbitmq/message.cpp         | 143 ++++++++++++++++++++++++++
 src/rabbitmq/message.h           |  19 ++++
 src/rabbitmq/producer.cpp        |  57 +++++++++++
 src/rabbitmq/producer.h          |  18 ++++
 src/rabbitmq/rabbitmq.cpp        | 127 +++++++++++++++++++++++
 src/rabbitmq/rabbitmq.h          |  12 +++
 src/redis/_connection_lock.cpp   |   2 +-
 src/redis/_connection_pool.cpp   |   6 +-
 src/url.cpp                      |  35 +++++--
 src/url.h                        |   6 +-
 src/vendor.h                     |   2 +
 test/queue_1.php                 |  59 +++++++++++
 test/rabbitmq_1.php              |  34 ++++++
 39 files changed, 1214 insertions(+), 71 deletions(-)
 create mode 100644 doc/rabbitmq.php
 delete mode 100644 src/command.h
 create mode 100644 src/coroutine_queue.h
 create mode 100644 src/queue.cpp
 create mode 100644 src/queue.h
 create mode 100644 src/rabbitmq/_client.cpp
 create mode 100644 src/rabbitmq/_client.h
 create mode 100644 src/rabbitmq/consumer.cpp
 create mode 100644 src/rabbitmq/consumer.h
 create mode 100644 src/rabbitmq/message.cpp
 create mode 100644 src/rabbitmq/message.h
 create mode 100644 src/rabbitmq/producer.cpp
 create mode 100644 src/rabbitmq/producer.h
 create mode 100644 src/rabbitmq/rabbitmq.cpp
 create mode 100644 src/rabbitmq/rabbitmq.h
 create mode 100644 test/queue_1.php
 create mode 100644 test/rabbitmq_1.php

diff --git a/README.md b/README.md
index 1843895..2d89890 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ rm /data/vendor/mongoc-1.13.0/lib/*.so*
 #### AMQP-CPP
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.0.0 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release ../
+CC=gcc CXX=g++ cmake3 -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.0.0 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
 make
 make install
 rm /data/vendor/amqpcpp-4.0.0/lib/*.so*
diff --git a/doc/core.php b/doc/core.php
index 5500c9d..cd72731 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -19,3 +19,30 @@ function go(callable $cb) {}
  * 框架调度, 上述协程会在框架开始调度运行后启动
  */
 function run() {}
+/**
+ * 从若干个队列中选择(等待)一个有数据队列
+ * @return 若所有通道已关闭, 返回 null; 否则返回一个有数据的通道, 即: 可以无等待 pop()
+ */
+function select(channel $q1, $q2, ...): {}
+/**
+ * 协程型队列
+ */
+class queue {
+    /**
+     * @param integer $max 队列容量, 若已放入数据达到此数量, push() 将"阻塞"(等待消费);
+     */
+    function __construct($max = 1) {}
+    /**
+     * 放入; 若向已关闭的队列放入, 将抛出异常;
+     */
+    function push($v) {}
+    /**
+     * 取出
+     */
+    function pop() {}
+    /**
+     * 关闭 (将唤醒阻塞在取出 pop() 的协程);
+     * 原则上仅能在生产者方向关闭队列;
+     */
+    function close() {}
+}
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
new file mode 100644
index 0000000..b2d88a6
--- /dev/null
+++ b/doc/rabbitmq.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace flame\rabbitmq;
+
+/**
+ * 通过 RabbitMQ 连接地址连接并准备消费
+ * @param string $url 连接地址, 形如:
+ *  amqp://{username}:{password}@{host}:{port}/{vhost}
+ */
+function consume(string $url, string $queue): consumer {}
+/**
+ * 通过 RabbitMQ 连接地址连接并启动生产
+ */
+function produce(string $url): producer {}
+
+/**
+ * 生产者
+ */
+class consumer {
+    /**
+     * 开始消费, 框架将按照一定数量启动并行消费协程, 并使用消息对象调用 $cb 回调函数;
+     * @param callable $cb 消费回调, 形如:
+     *  function callback($message) {}
+     */
+    function run(callable $cb) {}
+    /**
+     * 确认消息
+     */
+    function confirm(message $msg) {}
+    /**
+     * 拒绝消息(可选的返回队列, 重复消费)
+     */
+    function reject(message $msg, boolean $requeue = false) {}
+    /**
+     * 关闭消费者 (结束 run() 执行流程)
+     * 注意: 由于消费速度较快, 关闭消费者可能需要一段时间, 以保证完整消费;
+     */
+    function close() {}
+}
+class producer {
+    /**
+     * @param mixed $message 为 string 时表示生产的消息的包体; 也可以为 message 类型的对象;
+     * @param string $routing_key 路由KEY, 可以覆盖 $message 对象内部的 routing_key 属性
+     */
+    function publish(string $exchange, mixed $message, string $routing_key = null) {}
+}
+class message implements JsonSerializable {
+    public $routing_key;
+    public $body;
+    public $expiration;
+    public $reply_to;
+    public $correlation_id;
+    public $priority;
+    public $delivery_mode;
+    /**
+     * @property array
+     */
+    public $header;
+    public $content_encoding;
+    public $content_type;
+    public $cluster_id;
+    public $app_id;
+    public $user_id;
+    public $type_name;
+    public $timestamp;
+    public $message_id;
+    /**
+     * 构建一条新的 message 消息
+     */
+    function __construct($body = "", $routing_key = "") {}
+    function __toString() {}
+    /**
+     * 自定义 JSON 序列化, 返回 routing_key / body / timestamp 字段;
+     */
+    function jsonSerialize() {}
+}
diff --git a/src/command.h b/src/command.h
deleted file mode 100644
index a7c2add..0000000
--- a/src/command.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-#include "controller.h"
-#include "coroutine.h"
-
-namespace flame {
-    class command {
-    public:
-        command(coroutine_handler &ch)
-        : ch_(ch) {}
-        // 1. boost::asio::post(gcontroller->context_y, .....);
-        // 2. ch_.suspend();
-        virtual void execute() = 0;
-
-        virtual void finish() {
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch_));
-        }
-    protected:
-        coroutine_handler& ch_;
-    };
-}
diff --git a/src/controller.cpp b/src/controller.cpp
index dd17e86..e1b3e60 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -4,11 +4,6 @@
 namespace flame {
 // 全局控制器
 std::unique_ptr<controller> gcontroller;
-// 当前协程
-// std::shared_ptr<coroutine> coroutine::current;
-coroutine* coroutine::current;
-// 统一协程上下文
-coroutine::php_context_t coroutine::php_context;
 
 controller::controller()
 	: type(process_type::UNKNOWN)
diff --git a/src/controller.h b/src/controller.h
index cf215e3..bcd473e 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -28,7 +28,7 @@ namespace flame {
 	public:
 		boost::asio::io_context context_x;
 		boost::asio::io_context context_y;
-
+		zend_execute_data* core_execute_data;
 		enum class process_type
 		{
 			UNKNOWN = 0,
diff --git a/src/core.cpp b/src/core.cpp
index c12da9e..6da4558 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -1,6 +1,8 @@
 #include "controller.h"
 #include "coroutine.h"
 #include "core.h"
+#include "coroutine_queue.h"
+#include "queue.h"
 
 namespace flame {
     static php::value init(php::parameters& params) {
@@ -13,9 +15,46 @@ namespace flame {
         return nullptr;
     }
     static php::value run(php::parameters& params) {
+        gcontroller->core_execute_data = EG(current_execute_data);
         gcontroller->run();
         return nullptr;
     }
+    // static coroutine_queue<int> q;
+    // static php::value produce(php::parameters& params)
+    // {
+    //     coroutine_handler ch{coroutine::current};
+    //     q.push(static_cast<int>(params[0]), ch);
+    //     return nullptr;
+    // }
+    // static php::value consume(php::parameters& params)
+    // {
+    //     coroutine_handler ch{coroutine::current};
+    //     auto x = q.pop(ch);
+    //     if(x) {
+    //         return x.value();
+    //     }else{
+    //         return nullptr;
+    //     }
+    // }
+    php::value select(php::parameters& params)
+    {
+        std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
+        std::map< std::shared_ptr<coroutine_queue<php::value>>, php::object > mm;
+        for (auto i = 0; i < params.size(); ++i)
+        {
+            if(!params[i].instanceof(php::class_entry<queue>::entry()))
+            {
+                throw php::exception(zend_ce_type_error, "only flame\\queue can be selected", -1);
+            }
+            php::object obj = params[i];
+            queue* ptr = static_cast<queue*>(php::native(obj));
+            qs.push_back( ptr->q_ );
+            mm.insert({ptr->q_, obj});
+        }
+        coroutine_handler ch{coroutine::current};
+        std::shared_ptr<coroutine_queue<php::value>> q = select_queue(qs, ch);
+        return mm[q];
+    }
     void declare(php::extension_entry &ext) {
         ext
             .on_module_startup([](php::extension_entry &ext) -> bool {
@@ -28,7 +67,10 @@ namespace flame {
             .function<go>("flame\\go", {
                 {"coroutine", php::TYPE::CALLABLE},
             })
-            .function<run>("flame\\run");
+            .function<run>("flame\\run")
+            .function<select>("flame\\select");
+
+        queue::declare(ext);
     }
     
 }
\ No newline at end of file
diff --git a/src/core.h b/src/core.h
index 377765d..e761a97 100644
--- a/src/core.h
+++ b/src/core.h
@@ -3,4 +3,5 @@
 
 namespace flame {
     void declare(php::extension_entry& ext);
+    php::value select(php::parameters &params);
 }
\ No newline at end of file
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index fce7172..52ec01c 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -3,6 +3,8 @@
 
 namespace flame
 {
+    // 当前协程
+    std::shared_ptr<coroutine> coroutine::current;
     void coroutine::save_context(php_context_t &ctx)
     {
         ctx.vm_stack = EG(vm_stack);
@@ -23,14 +25,14 @@ namespace flame
     {
         auto co = std::make_shared<coroutine>(std::move(fn));
         // 需要即时保存 PHP 堆栈
-        co->php_.current_execute_data = execute_data == nullptr ? EG(current_execute_data) : execute_data;
+        co->php_.current_execute_data = execute_data == nullptr ? gcontroller->core_execute_data : execute_data;
         // 事实上的协程启动会稍后
         boost::asio::post(gcontroller->context_x, [co] {
             // co->c1_ = boost::context::callcc([co] (auto &&cc) {
             co->c1_ = boost::context::fiber([co] (boost::context::fiber &&cc) {
                 auto work = boost::asio::make_work_guard(gcontroller->context_x);
                 // 启动进入协程
-                coroutine::current = co.get();
+                coroutine::current = co;
                 zend_vm_stack_init();
                 co->c2_ = std::move(cc);
                 // 协程运行
@@ -61,14 +63,14 @@ namespace flame
         // 恢复 PHP 堆栈
         coroutine::restore_context(php_);
         // 恢复进入协程
-        coroutine::current = this;
+        coroutine::current = shared_from_this();
         c1_ = std::move(c1_).resume();
     }
     coroutine_handler::coroutine_handler()
         : co_(nullptr) 
     {
     }
-    coroutine_handler::coroutine_handler(coroutine *co)
+    coroutine_handler::coroutine_handler(std::shared_ptr<coroutine> co)
         : co_(co)
     {
     }
@@ -76,7 +78,11 @@ namespace flame
     {
         // std::cout << "~coroutine_handler\n";
     }
-    void coroutine_handler::reset(coroutine* co)
+    void coroutine_handler::reset()
+    {
+        co_.reset();
+    }
+    void coroutine_handler::reset(std::shared_ptr<coroutine> co)
     {
         assert(co_ == nullptr);
         co_ = co;
@@ -91,16 +97,20 @@ namespace flame
         nsize = n;
 
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
-        co_->resume();
+        boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
     void coroutine_handler::resume()
     {
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
-        co_->resume();
+        boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
     void coroutine_handler::suspend()
     {
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
         co_->suspend();
     }
+    bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2)
+    {
+        return ch1.co_ < ch2.co_;
+    }
 }
\ No newline at end of file
diff --git a/src/coroutine.h b/src/coroutine.h
index ad978b5..135c51f 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -15,7 +15,7 @@ namespace flame {
 		};
 		static coroutine::php_context_t   php_context;
 		// 当前协程
-		static coroutine* current;
+		static std::shared_ptr<coroutine> current;
 		static void save_context(php_context_t &ctx);
 		static void restore_context(php_context_t& ctx);
 		static std::shared_ptr<coroutine> start(php::callable fn, zend_execute_data* execute_data = nullptr);
@@ -35,18 +35,22 @@ namespace flame {
 	{
 	public:
 		coroutine_handler();
-		coroutine_handler(coroutine *co);
+		coroutine_handler(std::shared_ptr<coroutine> co);
 		~coroutine_handler();
 		// !!! 慎用: 目前仅用于空构造后指定协程
-		void reset(coroutine* co);
+		void reset(std::shared_ptr<coroutine> co);
+		void reset();
 		operator bool() const;
 		void operator()(const boost::system::error_code& e, std::size_t n = 0);
 		void resume();
 		void suspend();
 		boost::system::error_code error;
 		std::size_t 	          nsize;
-		coroutine *co_;
-	private:
+		std::shared_ptr<coroutine> co_;
+
+		friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
+
+	  private:
 		
 	};
 } // namespace flame
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
new file mode 100644
index 0000000..782e284
--- /dev/null
+++ b/src/coroutine_queue.h
@@ -0,0 +1,115 @@
+#pragma once
+#include "coroutine.h"
+
+namespace flame
+{
+    template <class T>
+    class coroutine_queue: private boost::noncopyable
+    {
+    public:
+        coroutine_queue(std::size_t n = 1)
+        : n_(n)
+        , closed_(false)
+        {
+        }
+        // !!!! 生产者进行关闭 !!!!
+        void close()
+        {
+            closed_ = true;
+            while (!c_.empty())
+            {
+                auto ch = c_.extract(c_.begin()).value();
+                ch.resume();
+            }
+        }
+        void push(const T& t, coroutine_handler& ch)
+        {
+            if(closed_)
+            {
+                throw php::exception(zend_ce_error, "queue already closed", -1);
+            }
+            if(q_.size() >= n_)
+            {
+                p_.insert(ch);
+                ch.suspend();
+            }
+            q_.push_back(t);
+            while(!q_.empty() && !c_.empty()) {
+                auto ch = c_.extract(c_.begin()).value();
+                ch.resume();
+            }
+        }
+        std::optional<T> pop(coroutine_handler& ch)
+        {
+            for(;;)
+            {
+                if(!q_.empty())
+                {
+                    // 有数据消费
+                    break;
+                }
+                else if(closed_) 
+                {
+                    // 无数据关闭
+                    return std::optional<T>();
+                }
+                else
+                {
+                    // 无数据等待
+                    c_.insert(ch);
+                    ch.suspend();
+                }
+            }
+            T t = q_.front();
+            q_.pop_front();
+
+            while (!p_.empty())
+            {
+                auto ch = p_.extract(p_.begin()).value();
+                ch.resume();
+            }
+
+            return std::optional<T>(t);
+        }
+    
+    /* private: */
+        std::size_t                 n_;
+        std::list<T>                q_;
+        std::set<coroutine_handler> c_; // 消费者
+        std::set<coroutine_handler> p_; // 生产者
+        bool                   closed_;
+    };
+
+    template <class T>
+    std::shared_ptr < coroutine_queue<T> > select_queue(std::vector < std::shared_ptr<coroutine_queue<T>> > queues, coroutine_handler &ch)
+    {
+        TRY_ALL:
+        bool all_closed = true;
+        for(auto i=queues.begin();i!=queues.end();++i)
+        {
+            if(!(*i)->q_.empty())
+            {
+                return *i;
+            }
+            else if (!(*i)->closed_)
+            {
+                all_closed = false;
+            }
+        }
+        if(all_closed)
+        {
+            return std::shared_ptr< coroutine_queue<T> >(nullptr);
+        }
+        for(auto i=queues.begin();i!=queues.end();++i)
+        {
+            (*i)->c_.insert(ch);
+        }
+        ch.suspend();
+        for (auto i = queues.begin(); i != queues.end(); ++i)
+        {
+            (*i)->c_.erase(ch);
+        }
+        goto TRY_ALL;
+    }
+
+}
\ No newline at end of file
diff --git a/src/flame.cpp b/src/flame.cpp
index dde8d93..d86c78d 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -6,6 +6,7 @@
 #include "redis/redis.h"
 #include "mongodb/mongodb.h"
 #include "kafka/kafka.h"
+#include "rabbitmq/rabbitmq.h"
 
 extern "C"
 {
@@ -38,7 +39,7 @@ extern "C"
         flame::redis::declare(ext);
         flame::mongodb::declare(ext);
         flame::kafka::declare(ext);
-        // flame::rabbitmq::declare(ext);
+        flame::rabbitmq::declare(ext);
         // flame::log::declare(ext);
         // flame::udp::declare(ext);
         // flame::tcp::declare(ext);
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index 7edcf47..4125c6e 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -58,7 +58,7 @@ namespace flame::kafka
         boost::asio::post(gcontroller->context_y, [this, &err, &ch] ()
         {
             err = rd_kafka_subscribe(conn_, tops_);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
@@ -92,7 +92,7 @@ namespace flame::kafka
             {
                 err = RD_KAFKA_RESP_ERR_NO_ERROR;
             }
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(err == RD_KAFKA_RESP_ERR__PARTITION_EOF)
@@ -120,7 +120,7 @@ namespace flame::kafka
         boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]()
         {
             err = rd_kafka_commit_message(conn_, msg, 0);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
@@ -135,7 +135,7 @@ namespace flame::kafka
         rd_kafka_resp_err_t err;
         boost::asio::post(gcontroller->context_y, [this, &err, &ch] () {
             err = rd_kafka_consumer_close(conn_);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index 2273e95..2356070 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -45,7 +45,7 @@ namespace flame::kafka
                               RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
                               RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
                               RD_KAFKA_V_END);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(err != RD_KAFKA_RESP_ERR_NO_ERROR) {
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index 2610e2b..eeac3e2 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -23,7 +23,7 @@ namespace flame::mongodb
             {
                 rok = mongoc_client_read_command_with_opts(conn.get(), mongoc_uri_get_database(uri), cmd.get(), mongoc_uri_get_read_prefs_t(uri), nullptr, rep.get(), err.get());
             }
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(!rok)
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
index ef65dac..0e86f1b 100644
--- a/src/mongodb/_connection_lock.cpp
+++ b/src/mongodb/_connection_lock.cpp
@@ -23,7 +23,7 @@ namespace flame::mongodb
             {
                 has = mongoc_cursor_error(cs.get(), err.get());
             }
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if (has) // 发生了错误
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index f02457d..cb43e7c 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -30,7 +30,7 @@ namespace flame::mongodb
             {
                 mongoc_client_pool_push(p_, c);
             });
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         return conn;
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 7a69279..0163398 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -99,7 +99,7 @@ ESCAPE_FINISHED:;
                     err = -1;
                 }
             }
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(err != 0)
@@ -139,7 +139,7 @@ ESCAPE_FINISHED:;
             if(row) {
                 len = mysql_fetch_lengths(rst.get());
             }
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(!row) {
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
index b64f9d2..40c847f 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/mysql/_connection_lock.cpp
@@ -26,7 +26,7 @@ namespace flame::mysql
         int err = 0;
         boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
             err = mysql_real_query(conn_.get(), "START TRANSACTION", 17);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if(err != 0) {
@@ -43,7 +43,7 @@ namespace flame::mysql
         int err = 0;
         boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
             err = mysql_real_query(conn_.get(), "COMMIT", 6);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if (err != 0)
@@ -59,7 +59,7 @@ namespace flame::mysql
         int err = 0;
         boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
             err = mysql_real_query(conn_.get(), "ROLLBACK", 8);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         if (err != 0)
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 340c244..8736cb4 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -30,7 +30,7 @@ namespace flame::mysql
             await_.push_back([&conn, &ch] (std::shared_ptr<MYSQL> c) {
                 conn = c;
                 // RESUME 需要在主线程进行
-                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+                ch.resume();
             });
             while (!conn_.empty())
             {
@@ -53,7 +53,7 @@ namespace flame::mysql
             MYSQL* c = create();
             if(c == nullptr) {
                 await_.pop_back();
-                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+                ch.resume();
             }else{
                 ++size_; // 当前还存在的连接数量
                 release(c);
diff --git a/src/queue.cpp b/src/queue.cpp
new file mode 100644
index 0000000..02c7d2c
--- /dev/null
+++ b/src/queue.cpp
@@ -0,0 +1,56 @@
+#include "coroutine.h"
+#include "queue.h"
+
+namespace flame
+{
+    void queue::declare(php::extension_entry &ext)
+    {
+        php::class_entry<queue> class_queue("flame\\queue");
+        class_queue
+            .method<&queue::__construct>("__construct",
+            {
+                {"n", php::TYPE::INTEGER, false, true},
+            })
+            .method<&queue::push>("push",
+            {
+                {"value", php::TYPE::UNDEFINED}
+            })
+            .method<&queue::pop>("pop")
+            .method<&queue::close>("close");
+        ext.add(std::move(class_queue));
+    }
+    php::value queue::__construct(php::parameters& params)
+    {   
+        if(params.size() > 0)
+        {
+            q_.reset(new coroutine_queue<php::value>(static_cast<int>(params[0])));
+        }else{
+            q_.reset(new coroutine_queue<php::value>(1));
+        }
+        return nullptr;
+    }
+    php::value queue::push(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        q_->push(params[0], ch);
+        return nullptr;
+    }
+    php::value queue::pop(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto x = q_->pop(ch);
+        if(x)
+        {
+            return x.value();
+        }
+        else
+        {
+            return nullptr;
+        }
+    }
+    php::value queue::close(php::parameters &params)
+    {
+        q_->close();
+        return nullptr;
+    }
+}
\ No newline at end of file
diff --git a/src/queue.h b/src/queue.h
new file mode 100644
index 0000000..625e824
--- /dev/null
+++ b/src/queue.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "vendor.h"
+#include "coroutine_queue.h"
+
+namespace flame
+{
+    class queue: public php::class_base 
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters& params);
+        php::value push(php::parameters &params);
+        php::value pop(php::parameters &params);
+        php::value close(php::parameters &params);
+    private:
+        std::shared_ptr<coroutine_queue<php::value>> q_;
+
+        friend php::value select(php::parameters &params);
+    };
+}
\ No newline at end of file
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
new file mode 100644
index 0000000..ce92aa9
--- /dev/null
+++ b/src/rabbitmq/_client.cpp
@@ -0,0 +1,171 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "_client.h"
+#include "message.h"
+
+namespace flame::rabbitmq
+{
+    static bool str2bool(const char *str){
+        return strncasecmp(str, "1", 1) == 0 || strncasecmp(str, "yes", 3) == 0 ||
+            strncasecmp(str, "yes", 3) == 0 || strncasecmp(str, "true", 4);
+    }
+
+#define CHECK_AND_SET_FLAG(xflag, yflag)                  \
+    for (auto i = u.query.find(#xflag); i != u.query.end();)    \
+    {                                                           \
+        const char *str = i->second.c_str();                    \
+        if (str2bool(str))                                      \
+        {                                                       \
+            fl_ |= yflag;                                      \
+        }                                                       \
+    }
+
+    _client::_client(url u, coroutine_handler& ch)
+        : hd_(gcontroller->context_x), cc_(&hd_, AMQP::Address(u.str(true, false).c_str(), u.str().size())), ch_(&cc_), consumer_closed_(false)
+    {
+        const char *err = nullptr;
+        ch_.onReady([this, &ch]() {
+            ch_.onError(nullptr);
+            ch.resume();
+        });
+        ch_.onError([this, &err, &ch](const char *message) {
+            err = message;
+            ch_.onReady(nullptr);
+            ch.resume();
+        });
+        ch.suspend();
+
+        auto i = u.query.find("prefetch");
+        if(i == u.query.end()) {
+            pf_ = 8;
+        }else{
+            pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 1024);
+        }
+        ch_.setQos(pf_);
+
+        CHECK_AND_SET_FLAG(nolocal, AMQP::nolocal);
+        CHECK_AND_SET_FLAG(noack, AMQP::noack);
+        CHECK_AND_SET_FLAG(exclusive, AMQP::noack);
+        CHECK_AND_SET_FLAG(mandatory, AMQP::mandatory);
+        CHECK_AND_SET_FLAG(immediate, AMQP::immediate);
+        
+        if(err) {
+            throw php::exception(zend_ce_error,
+                (boost::format("failed to connect RabbitMQ server: %1%") % err).str(), -1);
+        }
+    }
+
+    void _client::connect(coroutine_handler& ch)
+    {
+        
+    }
+    void _client::consume(const std::string &queue, coroutine_queue<php::object>& q, coroutine_handler &ch)
+    {
+        php::object obj = nullptr;
+        const char* err = nullptr;
+        ch_.consume(queue)
+            .onReceived([this, &q, &obj, &ch](const AMQP::Message &m, std::uint64_t tag, bool redelivered)
+            {
+                obj = php::object(php::class_entry<message>::entry());
+                message* ptr = static_cast<message*>(php::native(obj));
+                ptr->build_ex(m, tag);
+                ch.resume();
+            })
+            .onSuccess([this, &ch](const std::string &t)
+            {
+                tag_ = t;
+                ch.resume();
+            })
+            .onError([&err, &ch](const char *message)
+            {
+                err = message;
+                ch.resume();
+            });
+        ch.suspend();
+        if(err) {
+            throw php::exception(zend_ce_error,
+                (boost::format("failed to consume RabbitMQ message: %1%") % err).str(), -1);
+        }
+        consumer_ch_ = ch;
+        ch.suspend();
+        // 非关闭恢复执行, 消息对象一定存在
+        while(!consumer_closed_/* && !obj.empty()*/)
+        {
+            q.push(std::move(obj), ch);
+            ch.suspend();
+        }
+        q.close();
+        consumer_ch_.reset();
+    }
+    void _client::confirm(std::uint64_t tag, coroutine_handler &ch)
+    {
+        ch_.ack(tag, 0);
+    }
+    void _client::reject(std::uint64_t tag, int flags, coroutine_handler &ch)
+    {
+        ch_.reject(tag, flags);
+    }
+    void _client::close_consumer(coroutine_handler& ch)
+    {
+        const char* err = nullptr;
+        ch_.cancel(tag_)
+            .onSuccess([&ch] () {
+                ch.resume();
+            })
+            .onError([&err, &ch] (const char* message)
+            {
+                err = message;
+                ch.resume();
+            });
+        ch.suspend();
+        // TODO 关闭消费流程队列
+        if (err)
+        {
+            throw php::exception(zend_ce_error,
+                                 (boost::format("failed to close RabbitMQ consumer: %1%") % err).str(), -1);
+        }
+        consumer_closed_ = true;
+        if(consumer_ch_) consumer_ch_.resume();
+    }
+    void _client::publish(const std::string &ex, const std::string &rk, const AMQP::Envelope &env, coroutine_handler& ch)
+    {
+        const char* err = nullptr;
+        ch_.publish(ex, rk, env, fl_);
+        // TODO 目前的流程, 暂时未实现消息发送的确认逻辑
+        //     .onSuccess([&ch] ()
+        //     {
+        //         ch.resume();
+        //     })
+        //     .onError([&err, &ch] (const char* message)
+        //     {
+        //         err = message;
+        //         ch.resume();
+        //     });
+        // ch.suspend();
+        // if(err) {
+        //     throw php::exception(zend_ce_error,
+        //                          (boost::format("failed to publish RabbitMQ message: %1%") % err).str(), -1);
+        // }
+    }
+    void _client::publish(const std::string &ex, const std::string &rk, const char *msg, size_t len, coroutine_handler& ch)
+    {
+        const char *err = nullptr;
+        ch_.publish(ex, rk, msg, len, fl_);
+        // TODO 目前的流程, 暂时未实现消息发送的确认逻辑
+            // .onSuccess([&ch]() {
+            //     std::cout << "success\n";
+            //     ch.resume();
+            // })
+            // .onError([&err, &ch](const char *message) {
+            //     std::cout << "error\n";
+            //     err = message;
+            //     ch.resume();
+            // });
+        // ch.suspend();
+        // if (err)
+        // {
+        //     throw php::exception(zend_ce_error,
+        //                          (boost::format("failed to publish RabbitMQ message: %1%") % err).str(), -1);
+        // }
+    }
+}
diff --git a/src/rabbitmq/_client.h b/src/rabbitmq/_client.h
new file mode 100644
index 0000000..ecde057
--- /dev/null
+++ b/src/rabbitmq/_client.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "../url.h"
+#include "../coroutine_queue.h"
+
+namespace flame::rabbitmq
+{
+    class _client
+    {
+    public:
+        _client(url u, coroutine_handler& ch);
+        
+        void connect(coroutine_handler &ch);
+        void consume(const std::string& queue, coroutine_queue<php::object>& q, coroutine_handler& ch);
+        void confirm(std::uint64_t tag, coroutine_handler& ch);
+        void reject(std::uint64_t tag, int flags, coroutine_handler& ch);
+        void close_consumer(coroutine_handler& ch);
+        void publish(const std::string& ex, const std::string& rk, const AMQP::Envelope& env, coroutine_handler& ch);
+        void publish(const std::string& ex, const std::string& rk, const char* msg, size_t len, coroutine_handler& ch);
+    private:
+        AMQP::LibBoostAsioHandler hd_;
+        AMQP::TcpConnection       cc_;
+        AMQP::TcpChannel          ch_;
+        int                       pf_;
+        int                       fl_;
+        std::string              tag_;
+
+        coroutine_handler consumer_ch_;
+        bool          consumer_closed_;
+
+        friend class consumer;
+    };
+}
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
new file mode 100644
index 0000000..308d09f
--- /dev/null
+++ b/src/rabbitmq/consumer.cpp
@@ -0,0 +1,94 @@
+#include "../coroutine.h"
+#include "consumer.h"
+#include "_client.h"
+#include "message.h"
+
+namespace flame::rabbitmq
+{
+    void consumer::declare(php::extension_entry &ext)
+    {
+        php::class_entry<consumer> class_consumer("flame\\rabbitmq\\consumer");
+        class_consumer
+            .method<&consumer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&consumer::__destruct>("__destruct")
+            .method<&consumer::run>("run",
+            {
+                {"callable", php::TYPE::CALLABLE},
+            })
+            .method<&consumer::confirm>("confirm",
+            {
+                {"message", "flame\\rabbitmq\\message"}
+            })
+            .method<&consumer::reject>("reject",
+            {
+                {"message", "flame\\rabbitmq\\message"},
+                {"requeue", php::TYPE::BOOLEAN, false, true},
+            })
+            .method<&consumer::close>("close");
+        ext.add(std::move(class_consumer));
+    }
+    php::value consumer::__construct(php::parameters &params)
+    {
+        return nullptr;
+    }
+    php::value consumer::__destruct(php::parameters& params)
+    {
+        return nullptr;
+    }
+    php::value consumer::run(php::parameters &params)
+    {
+        cb_ = params[0];
+        
+        coroutine_handler ch {coroutine::current};
+        // 下述 q 需要在堆分配, 否则当 cc_->consume() 结束后可能与被提前销毁 (协程销毁稍晚)
+        coroutine_queue<php::object> q(cc_->pf_);
+        // 启动若干协程, 然后进行"并行>"消费
+        int count = (int)std::ceil(cc_->pf_ / 2.0);
+        std::vector<std::shared_ptr<coroutine>> cos(count);
+        for (int i = 0; i < count; ++i)
+        {
+            // 启动协程开始消费
+            cos[i] = coroutine::start(php::value([this, &q, &count, &ch, i] (php::parameters &params) -> php::value {
+                coroutine_handler cch {coroutine::current};
+                while(auto x = q.pop(cch))
+                {
+                    cb_.call( {x.value()} );
+                }
+                if(--count == 0)
+                {
+                    ch.resume();
+                }
+                return nullptr;
+            }));
+        }
+        cc_->consume(cq_, q, ch);
+        ch.suspend();
+        cb_ = nullptr;
+        return nullptr;
+    }
+    php::value consumer::confirm(php::parameters &params)
+    {   
+        php::object obj = params[0];
+        message *ptr = static_cast<message *>(php::native(obj));
+        coroutine_handler ch {coroutine::current};
+        cc_->confirm(ptr->tag_, ch);
+        return nullptr;
+    }
+    php::value consumer::reject(php::parameters &params)
+    {
+        php::object obj = params[0];
+        message *ptr = static_cast<message *>(php::native(obj));
+        int flags = 0;
+        if (params.size() > 1 && params[1].to_boolean()) flags |= AMQP::requeue;
+        coroutine_handler ch {coroutine::current};
+        cc_->reject(ptr->tag_, flags, ch);
+        return nullptr;
+    }
+    php::value consumer::close(php::parameters &params)
+    {
+        coroutine_handler ch {coroutine::current};
+        cc_->close_consumer(ch);
+        return nullptr;
+    }
+
+} // namespace flame::rabbitmq
diff --git a/src/rabbitmq/consumer.h b/src/rabbitmq/consumer.h
new file mode 100644
index 0000000..ddcd423
--- /dev/null
+++ b/src/rabbitmq/consumer.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::rabbitmq
+{
+    class _client;
+    class consumer : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value __destruct(php::parameters& params);
+        php::value run(php::parameters &params);
+        php::value confirm(php::parameters &params);
+        php::value reject(php::parameters &params);
+        php::value close(php::parameters &params);
+
+    private:
+        // 实际的客户端对象可能超过当前对象的生存期
+        std::shared_ptr<_client> cc_;
+        php::callable            cb_;
+        std::string              cq_;
+        int              concurrent_;
+
+        friend php::value consume(php::parameters &params);
+    };
+} // namespace flame::rabbitmq
diff --git a/src/rabbitmq/message.cpp b/src/rabbitmq/message.cpp
new file mode 100644
index 0000000..e925098
--- /dev/null
+++ b/src/rabbitmq/message.cpp
@@ -0,0 +1,143 @@
+#include "../coroutine.h"
+#include "rabbitmq.h"
+#include "message.h"
+
+namespace flame::rabbitmq
+{
+    void message::declare(php::extension_entry &ext)
+    {
+        php::class_entry<message> class_message("flame\\rabbitmq\\message");
+        class_message
+            .implements(&php_json_serializable_ce)
+            .property({"routing_key", ""})
+            .property({"body", ""})
+            .property({"expiration", ""})
+            .property({"reply_to", ""})
+            .property({"correlation_id", ""})
+            .property({"priority", 0})
+            .property({"delivery_mode", 0})
+            .property({"header", nullptr})
+            .property({"content_encoding", ""})
+            .property({"content_type", ""})
+            .property({"cluster_id", ""})
+            .property({"app_id", ""})
+            .property({"user_id", ""})
+            .property({"type_name", ""})
+            .property({"timestamp", 0})
+            .property({"message_id", ""})
+            .method<&message::__construct>("__construct",
+            {
+                {"body", php::TYPE::STRING, false, true},
+            })
+            .method<&message::to_string>("__toString")
+            .method<&message::to_json>("jsonSerialize");
+
+        ext.add(std::move(class_message));
+    }
+    php::value message::__construct(php::parameters &params)
+    {
+        if (params.size() > 0)
+        {
+            set("body", params[0]);
+        }
+        if (params.size() > 1)
+        {
+            set("routing_key", params[1]);
+        }
+        return nullptr;
+    }
+    php::value message::to_json(php::parameters &params)
+    {
+        php::array data(16);
+        data.set("routing_key", get("routing_key"));
+        data.set("body", get("body"));
+        data.set("timestamp", get("timestamp"));
+        return std::move(data);
+    }
+    php::value message::to_string(php::parameters &params)
+    {
+        return get("body");
+    }
+    void message::build_ex(const AMQP::Message &msg, std::uint64_t tag)
+    {
+        tag_ = tag;
+        set("routing_key", msg.routingkey());
+        set("body", php::string(msg.body(), msg.bodySize()));
+        if (msg.hasExpiration())
+            set("expiration", msg.expiration());
+        if (msg.hasReplyTo())
+            set("reply_to", msg.replyTo());
+        if (msg.hasCorrelationID())
+            set("correlation_id", msg.correlationID());
+        if (msg.hasPriority())
+            set("priority", msg.priority());
+        if (msg.hasDeliveryMode())
+            set("delivery_mode", msg.deliveryMode());
+        if (msg.hasHeaders())
+            set("header", table2array(msg.headers()));
+        if (msg.hasContentEncoding())
+            set("content_encoding", msg.contentEncoding());
+        if (msg.hasContentType())
+            set("content_type", msg.contentType());
+        if (msg.hasClusterID())
+            set("cluster_id", msg.clusterID());
+        if (msg.hasAppID())
+            set("app_id", msg.appID());
+        if (msg.hasUserID())
+            set("user_id", msg.userID());
+        if (msg.hasTypeName())
+            set("type_name", msg.typeName());
+        if (msg.hasTimestamp())
+            set("timestamp", msg.timestamp());
+        if (msg.hasMessageID())
+            set("message_id", msg.messageID());
+    }
+    void message::build_ex(AMQP::Envelope &env)
+    {
+        php::string v;
+
+        v = get("expiration");
+        if (!v.empty())
+            env.setExpiration(v.to_string());
+        v = get("reply_to");
+        if (!v.empty())
+            env.setReplyTo(v.to_string());
+        v = get("correlation_id");
+        if (!v.empty())
+            env.setCorrelationID(v.to_string());
+        v = get("priority");
+        if (!v.empty())
+            env.setPriority(v.to_integer());
+        v = get("delivery_mode");
+        if (!v.empty())
+            env.setDeliveryMode(v.to_integer());
+        v = get("header");
+        if (!v.empty())
+            env.setHeaders(array2table(v));
+        v = get("content_encoding");
+        if (!v.empty())
+            env.setContentEncoding(v.to_string());
+        v = get("content_type");
+        if (!v.empty())
+            env.setContentType(v.to_string());
+        v = get("cluster_id");
+        if (!v.empty())
+            env.setClusterID(v.to_string());
+        v = get("app_id");
+        if (!v.empty())
+            env.setAppID(v.to_string());
+        v = get("user_id");
+        if (!v.empty())
+            env.setUserID(v.to_string());
+        v = get("type_name");
+        if (!v.empty())
+            env.setTypeName(v.to_string());
+        v = get("timestamp");
+        if (!v.empty())
+            env.setTimestamp(v.to_integer());
+        v = get("message_id");
+        if (!v.empty())
+            env.setMessageID(v.to_string());
+    }
+
+} // namespace flame::rabbitmq
diff --git a/src/rabbitmq/message.h b/src/rabbitmq/message.h
new file mode 100644
index 0000000..ad7e222
--- /dev/null
+++ b/src/rabbitmq/message.h
@@ -0,0 +1,19 @@
+#pragma once
+
+namespace flame::rabbitmq
+{
+
+    class message : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value to_json(php::parameters &params);
+        php::value to_string(php::parameters &params);
+
+        void build_ex(const AMQP::Message &msg, std::uint64_t tag);
+        void build_ex(AMQP::Envelope &env);
+        std::uint64_t tag_;
+    };
+    
+} // namespace flame::rabbitmq
diff --git a/src/rabbitmq/producer.cpp b/src/rabbitmq/producer.cpp
new file mode 100644
index 0000000..24fd158
--- /dev/null
+++ b/src/rabbitmq/producer.cpp
@@ -0,0 +1,57 @@
+#include "../coroutine.h"
+#include "producer.h"
+#include "_client.h"
+#include "message.h"
+
+namespace flame::rabbitmq
+{
+    void producer::declare(php::extension_entry &ext)
+    {
+        php::class_entry<producer> class_producer("flame\\rabbitmq\\producer");
+        class_producer
+            .method<&producer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&producer::publish>("publish",
+            {
+                {"exchange", php::TYPE::STRING},
+                {"message", php::TYPE::UNDEFINED},
+            });
+        ext.add(std::move(class_producer));
+    }
+    php::value producer::__construct(php::parameters &params)
+    {
+        return nullptr;
+    }
+    php::value producer::publish(php::parameters &params)
+    {
+        std::string exch = params[0];
+        std::string routing_key;
+        if (params.size() > 2)
+        {
+            routing_key = params[2].to_string();
+        }
+
+        coroutine_handler ch{coroutine::current};
+        if (params[1].instanceof(php::class_entry<message>::entry()))
+        {
+            php::object msg = params[1];
+            message *msg_ = static_cast<message *>(php::native(msg));
+            php::string body = msg.get("body");
+            body.to_string();
+            AMQP::Envelope env(body.c_str(), body.size());
+            msg_->build_ex(env);
+            if (routing_key.empty())
+            { // 未指定时使用 message 默认的 routing_key
+                routing_key = msg.get("routing_key").to_string();
+            }
+            coroutine_handler ch {coroutine::current};
+            cc_->publish(exch, routing_key, env, ch);
+        }
+        else
+        {
+            php::string msg = params[1];
+            msg.to_string();
+            cc_->publish(exch, routing_key, msg.c_str(), msg.size(), ch);
+        }
+        return nullptr;
+    }
+} // namespace flame::rabbitmq
diff --git a/src/rabbitmq/producer.h b/src/rabbitmq/producer.h
new file mode 100644
index 0000000..975b665
--- /dev/null
+++ b/src/rabbitmq/producer.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::rabbitmq {
+    class _client;
+	class producer: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value __construct(php::parameters& params); // 私有
+		php::value publish(php::parameters& params);
+	private:
+		// 实际的客户端对象可能超过当前对象的生存期
+		std::shared_ptr<_client> cc_;
+		
+		friend php::value produce(php::parameters& params);
+		friend class _client;
+	};
+} // namespace flame::rabbitmq
diff --git a/src/rabbitmq/rabbitmq.cpp b/src/rabbitmq/rabbitmq.cpp
new file mode 100644
index 0000000..3d1148a
--- /dev/null
+++ b/src/rabbitmq/rabbitmq.cpp
@@ -0,0 +1,127 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "../url.h"
+#include "_client.h"
+#include "rabbitmq.h"
+#include "consumer.h"
+#include "producer.h"
+#include "message.h"
+
+namespace flame::rabbitmq
+{
+    void declare(php::extension_entry &ext)
+    {
+        ext
+            .function<consume>("flame\\rabbitmq\\consume",
+            {
+                {"address", php::TYPE::STRING},
+                {"queue", php::TYPE::STRING},
+            })
+            .function<produce>("flame\\rabbitmq\\produce",
+            {
+                {"address", php::TYPE::STRING},
+            });
+
+        consumer::declare(ext);
+        producer::declare(ext);
+        message::declare(ext);
+    }
+
+    php::value consume(php::parameters &params)
+    {
+        coroutine_handler ch {coroutine::current};
+        url u(params[0]);
+        //  u.query.clear();
+        std::shared_ptr<_client> cc = std::make_shared<_client>(u, ch);
+
+        // cc->connect(ch);
+
+        php::object obj(php::class_entry<consumer>::entry());
+        consumer* ptr = static_cast<consumer*>(php::native(obj));
+        ptr->cc_ = cc;
+        ptr->cq_ = static_cast<std::string>(params[1]);
+        return std::move(obj);
+    }
+    php::value produce(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        url u(params[0]);
+        // u.query.clear();
+        std::shared_ptr<_client> cc = std::make_shared<_client>(u, ch);
+        
+        // cc->connect(ch);
+
+        php::object obj(php::class_entry<producer>::entry());
+        producer *ptr = static_cast<producer*>(php::native(obj));
+        ptr->cc_ = cc;
+        return std::move(obj);
+    }
+    // 仅支持一维数组
+    php::array table2array(const AMQP::Table& table)
+    {
+        php::array data(table.size());
+        for (auto key : table.keys())
+        {
+            const AMQP::Field &field = table.get(key);
+            if (field.isBoolean())
+            {
+                data.set(key, static_cast<uint8_t>(field) ? true : false);
+            }
+            else if (field.isInteger())
+            {
+                data.set(key, static_cast<int64_t>(field));
+            }
+            else if (field.isDecimal())
+            {
+                data.set(key, static_cast<double>(field));
+            }
+            else if (field.isString())
+            {
+                data.set(key, (const std::string &)field);
+            }
+            else if (field.typeID() == 'd')
+            {
+                data.set(key, static_cast<double>(field));
+            }
+            else if (field.typeID() == 'f')
+            {
+                data.set(key, static_cast<float>(field));
+            }
+            // TODO 记录警告信息?
+        }
+        return std::move(data);
+    }
+    // 仅支持一维数组
+    AMQP::Table array2table(const php::array& table)
+    {
+        AMQP::Table data;
+        if (!table.typeof(php::TYPE::ARRAY) || table.empty())
+        {
+            return std::move(data);
+        }
+        for (auto i = table.begin(); i != table.end(); ++i)
+        {
+            if (i->second.typeof(php::TYPE::BOOLEAN))
+            {
+                data.set(i->first.to_string(), i->second.to_boolean());
+            }
+            else if (i->second.typeof(php::TYPE::INTEGER))
+            {
+                data.set(i->first.to_string(), i->second.to_integer());
+            }
+            else if (i->second.typeof(php::TYPE::STRING))
+            {
+                data.set(i->first.to_string(), i->second.to_string());
+            }
+            else if (i->second.typeof(php::TYPE::FLOAT))
+            {
+                data.set(i->first.to_string(), AMQP::Double{i->second.to_float()});
+            }
+            else
+            {
+                throw php::exception(zend_ce_type_error, "table conversion failed: unsupported type");
+            }
+        }
+        return std::move(data);
+    }
+} // namespace flame::rabbitmq
diff --git a/src/rabbitmq/rabbitmq.h b/src/rabbitmq/rabbitmq.h
new file mode 100644
index 0000000..5ab1147
--- /dev/null
+++ b/src/rabbitmq/rabbitmq.h
@@ -0,0 +1,12 @@
+#pragma once
+
+namespace flame::rabbitmq
+{
+    void declare(php::extension_entry &ext);
+    php::value consume(php::parameters &params);
+    php::value produce(php::parameters &params);
+
+    php::array  table2array(const AMQP::Table &table);
+    AMQP::Table array2table(const php::array &table);
+    
+} // namespace flame::rabbitmq
\ No newline at end of file
diff --git a/src/redis/_connection_lock.cpp b/src/redis/_connection_lock.cpp
index b08ea41..682e5bc 100644
--- a/src/redis/_connection_lock.cpp
+++ b/src/redis/_connection_lock.cpp
@@ -43,7 +43,7 @@ namespace flame::redis
                 redisGetReply(conn_.get(), (void**)&i->reply);
             }
             // 回到主线程
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         // 整合个命令返回值
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 57b5f5d..586d959 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -27,7 +27,7 @@ namespace flame::redis
             await_.push_back([&conn, &ch](std::shared_ptr<redisContext> c) {
                 conn = c;
                 // RESUME 需要在主线程进行
-                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+                ch.resume();
             });
             while (!conn_.empty())
             {
@@ -53,7 +53,7 @@ namespace flame::redis
             if (c == nullptr)
             {
                 await_.pop_back();
-                boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+                ch.resume();
             }
             else
             {
@@ -78,7 +78,7 @@ namespace flame::redis
         boost::asio::post(gcontroller->context_y, [&rc, &ss, &ch, &rp]() {
             redisAppendFormattedCommand(rc.get(), ss.c_str(), ss.size());
             redisGetReply(rc.get(), (void **)&rp);
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine_handler::resume, ch));
+            ch.resume();
         });
         ch.suspend();
         php::value rv = reply2value(rp, argv, rt);
diff --git a/src/url.cpp b/src/url.cpp
index 83d24a9..16a18e1 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -5,7 +5,9 @@ namespace flame
 {
 
     typedef ::parser::separator_parser<std::string, std::string> parser_t;
-    url::url(const php::string &str) {
+    url::url(const php::string &str)
+        : raw_(str)
+    {
         const char* s = str.c_str();
 
         struct http_parser_url x;
@@ -45,7 +47,7 @@ namespace flame
                 std::string key = et.first;
                 std::string val = et.second;
                 // 一律小写 KEY
-                php::uppercase_inplace(key.data(), key.size());
+                php::lowercase_inplace(key.data(), key.size());
                 val.resize(php::url_decode_inplace(val.data(), val.size()));
                 query[key] = val;
             });
@@ -54,14 +56,29 @@ namespace flame
         }
     }
 
-    std::string url::str() {
-        std::ostringstream ss;
-        ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path << "?";
-        for(auto i=query.begin();i!=query.end();++i)
-        {
-            ss << i->first << "=" << i->second << "&";
+    std::string url::str(bool with_query, bool update)
+    {
+        if(update) {
+            std::ostringstream ss;
+            ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path;
+            if(with_query)
+            {
+                int x = 0;
+                for(auto i=query.begin();i!=query.end();++i)
+                {
+                    if(++x == 1)
+                    {
+                        ss.put('?');
+                    }else
+                    {
+                        ss.put('&');
+                    }
+                    ss << i->first << "=" << i->second;
+                }
+            }
+            raw_ = ss.str();
         }
-        return ss.str();
+        return raw_;
     }
 
 } // namespace flame
\ No newline at end of file
diff --git a/src/url.h b/src/url.h
index bc6c6ed..02d63e1 100644
--- a/src/url.h
+++ b/src/url.h
@@ -12,7 +12,9 @@ namespace flame {
         std::string   user;
         std::string   pass;
 
-        std::string   str();
-   
+        std::string   str(bool update = false, bool with_query = true);
+    private:
+        std::string raw_;
+
     };
 }
\ No newline at end of file
diff --git a/src/vendor.h b/src/vendor.h
index afa18a4..6104835 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -16,6 +16,7 @@
 #include <utility>
 #include <memory>
 #include <stack>
+#include <optional>
 #include <unordered_set>
 #include <phpext/phpext.h>
 #include <http_parser.h>
@@ -30,6 +31,7 @@
 #include <boost/format.hpp>
 #include <boost/program_options.hpp>
 #include <boost/process.hpp>
+#include <boost/logic/tribool.hpp>
 #include <boost/interprocess/ipc/message_queue.hpp>
 #include <boost/asio.hpp>
 #include <boost/asio/ssl.hpp>
diff --git a/test/queue_1.php b/test/queue_1.php
new file mode 100644
index 0000000..c7c5931
--- /dev/null
+++ b/test/queue_1.php
@@ -0,0 +1,59 @@
+<?php
+flame\init("queue_1");
+
+flame\go(function() {
+    $q1 = new flame\queue();
+    $q2 = new flame\queue();
+    $q3 = new flame\queue();
+    flame\go(function() use($q1) {
+        for($i=1;$i<10;++$i) {
+            $q1->push($i);
+            flame\time\sleep(rand(50, 100));
+        }
+        $q1->close();
+    });
+    flame\go(function() use($q2) {
+        for($i=1;$i<10;++$i) {
+            $q2->push($i + 10);
+            flame\time\sleep(rand(50, 150));
+        }
+        $q2->close();
+    });
+    flame\go(function() use($q3) {
+        for($i=1;$i<10;++$i) {
+            $q3->push($i + 100);
+            flame\time\sleep(rand(150, 250));
+        }
+        $q3->close();
+    });
+    flame\go(function() use($q1) {
+        while($x = $q1->pop()) {
+            echo "(1) q1: ", $x, "\n";
+            flame\time\sleep(rand(100, 200));
+        }
+        echo "(1) q1: consume done\n";
+    });
+    flame\go(function() use($q1) {
+        while($x = $q1->pop()) {
+            echo "(2) q1: ", $x, "\n";
+            flame\time\sleep(rand(100, 200));
+        }
+        echo "(2) q1: consume done\n";
+    });
+    flame\go(function() use($q2, $q3) {
+        while($q = flame\select($q2, $q3)) {
+            // 必须使用 全等 符号比较对象
+            if($q === $q2) {
+                echo "q2: ", $q->pop(), "\n";
+            }else if($q === $q3) {
+                echo "q3: ", $q->pop(), "\n";
+            }else{
+                // 
+            }
+        }
+        echo "q2: consume done\n";
+        echo "q3: consume done\n";
+    });
+});
+
+flame\run();
diff --git a/test/rabbitmq_1.php b/test/rabbitmq_1.php
new file mode 100644
index 0000000..f706ec1
--- /dev/null
+++ b/test/rabbitmq_1.php
@@ -0,0 +1,34 @@
+<?php
+flame\init("rabbitmq_1");
+
+flame\go(function() {
+    $consumer = flame\rabbitmq\consume("amqp://username:password@host:port/vhost", "test_q1");
+    flame\go(function() use($consumer) {
+        flame\time\sleep(5000);
+        echo "close\n";
+        $consumer->close();
+    });
+    $consumer->run(function($msg) use($consumer) {
+        var_dump($msg);
+        $consumer->confirm($msg);
+    });
+    echo "consumer done\n";
+});
+
+flame\go(function() {
+    $producer = flame\rabbitmq\produce("amqp://username:password@host:port/vhost");
+    for($i=0;$i<20;++$i) {
+        flame\time\sleep(20);
+        echo $i * 2, "\n";
+        $producer->publish("", "this is the data", "test_q1");
+        flame\time\sleep(20);
+        $message = new flame\rabbitmq\message("abcdefg", "test_q1");
+        $message->header["a"] = 1234567890123;
+        $message->header["b"] = "Asd";
+        $message->header["c"] = 123.456;
+        echo $i * 2 +1, "\n";
+        $producer->publish("", $message);
+    }
+});
+
+flame\run();

From 504078d47759c3aeac33d1e135b3cdff456f4b63 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 27 Nov 2018 18:29:31 +0800
Subject: [PATCH 010/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20mutex=20/=20guard?=
 =?UTF-8?q?=20=E5=8D=8F=E7=A8=8B=E9=94=81;=20=E5=88=9D=E6=AD=A5=E5=AE=8C?=
 =?UTF-8?q?=E6=88=90=20tcp=20=E9=87=8D=E6=9E=84;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php                     |  31 +++++++-
 doc/kafka.php                    |   4 +-
 doc/mongodb.php                  |  28 ++++----
 doc/os.php                       |   2 +-
 doc/rabbitmq.php                 |   2 +-
 doc/tcp.php                      |  47 +++++++++++++
 doc/time.php                     |   2 +-
 src/controller.cpp               |  18 ++++-
 src/core.cpp                     |   8 +--
 src/coroutine.cpp                |  17 +++--
 src/coroutine.h                  |  13 ++--
 src/coroutine_mutex.h            |  58 +++++++++++++++
 src/coroutine_queue.h            |   2 +-
 src/flame.cpp                    |   5 +-
 src/kafka/_consumer.cpp          |   8 +--
 src/kafka/consumer.cpp           |  17 ++---
 src/mongodb/_connection_lock.cpp |   2 +-
 src/mongodb/mongodb.cpp          |   8 +--
 src/mutex.cpp                    |  59 ++++++++++++++++
 src/mutex.h                      |  29 ++++++++
 src/mysql/_connection_base.cpp   |   2 +-
 src/mysql/_connection_lock.cpp   |   2 +-
 src/mysql/mysql.cpp              |  12 ++--
 src/os/os.cpp                    |   4 +-
 src/rabbitmq/_client.cpp         |   4 +-
 src/rabbitmq/consumer.cpp        |  10 ++-
 src/redis/client.cpp             |   2 +-
 src/redis/tx.cpp                 |   2 +-
 src/tcp/server.cpp               | 117 +++++++++++++++++++++++++++++++
 src/tcp/server.h                 |  20 ++++++
 src/tcp/socket.cpp               | 105 +++++++++++++++++++++++++++
 src/tcp/socket.h                 |  26 +++++++
 src/tcp/tcp.cpp                  |  86 +++++++++++++++++++++++
 src/tcp/tcp.h                    |   9 +++
 src/vendor.h                     |   1 +
 test/mutex_1.php                 |  32 +++++++++
 test/tcp_1.php                   |  34 +++++++++
 37 files changed, 754 insertions(+), 74 deletions(-)
 create mode 100644 doc/tcp.php
 create mode 100644 src/coroutine_mutex.h
 create mode 100644 src/mutex.cpp
 create mode 100644 src/mutex.h
 create mode 100644 src/tcp/server.cpp
 create mode 100644 src/tcp/server.h
 create mode 100644 src/tcp/socket.cpp
 create mode 100644 src/tcp/socket.h
 create mode 100644 src/tcp/tcp.cpp
 create mode 100644 src/tcp/tcp.h
 create mode 100644 test/mutex_1.php
 create mode 100644 test/tcp_1.php

diff --git a/doc/core.php b/doc/core.php
index cd72731..23cc4af 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -23,13 +23,13 @@ function run() {}
  * 从若干个队列中选择(等待)一个有数据队列
  * @return 若所有通道已关闭, 返回 null; 否则返回一个有数据的通道, 即: 可以无等待 pop()
  */
-function select(channel $q1, $q2, ...): {}
+function select(channel $q1, $q2/*, ...*/):channel {}
 /**
  * 协程型队列
  */
 class queue {
     /**
-     * @param integer $max 队列容量, 若已放入数据达到此数量, push() 将"阻塞"(等待消费);
+     * @param int $max 队列容量, 若已放入数据达到此数量, push() 将"阻塞"(等待消费);
      */
     function __construct($max = 1) {}
     /**
@@ -46,3 +46,30 @@ function pop() {}
      */
     function close() {}
 }
+
+class mutex {
+    /**
+     * 构建一个协程式 mutex 对象
+     */
+    function __construct() {}
+    /**
+     * 加锁
+     */
+    function lock() {}
+    /**
+     * 解锁
+     */
+    function unlock() {}
+}
+
+class guard {
+    /**
+     * 构建守护并锁定 mutex 
+     * @param mutex $mutex 实际保护使用的 mutex 对象
+     */
+    function __construct(mutex $mutex) {}
+    /**
+     * 解锁 mutex 并销毁守护
+     */
+    function __destruct() {}
+}
\ No newline at end of file
diff --git a/doc/kafka.php b/doc/kafka.php
index 463044a..d6a75d6 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -69,7 +69,7 @@ class message implements JsonSerializable {
      */
     public $topic;
     /**
-     * @property integer $partition
+     * @property int $partition
      */
     public $partition;
     /**
@@ -77,7 +77,7 @@ class message implements JsonSerializable {
      */
     public $key;
     /**
-     * @property integer $offset
+     * @property int $offset
      */
     public $offset;
     /**
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 34b4637..3b62096 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -7,22 +7,24 @@
 function connect(string $url):client {}
 
 class client {
-    function execute(array $command, boolean $write = false): mixed {}
-    function collection(): collection {}
-    function __get(): collection {}
+    function execute(array $command, bool $write = false):mixed {}
+    function collection():collection {}
+    function __get($name):collection {}
     /**
-     * @return boolean 永远为 true ;
+     * @return bool 永远为 true ;
      */
-    function __isset(): boolean {}
+    function __isset($name):bool {
+        return true;
+    }
 }
 class collection {
-    function insert(array $data, boolean $ordered = true):array {}
-    function delete(array $query, integer $limit = 0):array {}
+    function insert(array $data, bool $ordered = true):array {}
+    function delete(array $query, int $limit = 0):array {}
     function update(array $query, array $update, $upsert = false):array {}
     function find(array $query, array $projection = null, array $sort = null, mixed $limit):cursor {}
     function one(array $query, array $sort = null): array {}
     function get(array $query, string $field, array $sort = null): mixed {}
-    function count(array $query): integer {}
+    function count(array $query): int {}
     function aggregate(array $pipeline): cursor {}
 }
 class cursor {
@@ -30,13 +32,13 @@ function fetch_row() {}
     function fetch_all() {}
 }
 class date_time implements JsonSerializable {
-    function __construct(integer $milliseconds = null) {}
+    function __construct(int $milliseconds = null) {}
     function __toString() {}
     function __toDateTime() {}
     function __debugInfo() {}
     function jsonSerialize(): array {}
-    function unix(): integer {}
-    function unix_ms(): integer {}
+    function unix(): int {}
+    function unix_ms(): int {}
 }
 class object_id implements JsonSerializable {
     function __construct(string $object_id = null) {}
@@ -44,6 +46,6 @@ function __toString() {}
     function __toDateTime() {}
     function __debugInfo() {}
     function jsonSerialize(): array {}
-    function unix(): integer {}
-    function equal(object_id $oid): boolean {}
+    function unix(): int {}
+    function equal(object_id $oid): bool {}
 }
diff --git a/doc/os.php b/doc/os.php
index 376f4a6..16bff6a 100644
--- a/doc/os.php
+++ b/doc/os.php
@@ -50,7 +50,7 @@ function exec(string $command, array $argv, array $options = []):string {}
  * 进程对象
  */
 class process {
-    function kill(integer $signal = SIGTERM) {}
+    function kill(int $signal = SIGTERM) {}
     /**
      * 等待进程结束
      */
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
index b2d88a6..185da44 100644
--- a/doc/rabbitmq.php
+++ b/doc/rabbitmq.php
@@ -30,7 +30,7 @@ function confirm(message $msg) {}
     /**
      * 拒绝消息(可选的返回队列, 重复消费)
      */
-    function reject(message $msg, boolean $requeue = false) {}
+    function reject(message $msg, bool $requeue = false) {}
     /**
      * 关闭消费者 (结束 run() 执行流程)
      * 注意: 由于消费速度较快, 关闭消费者可能需要一段时间, 以保证完整消费;
diff --git a/doc/tcp.php b/doc/tcp.php
new file mode 100644
index 0000000..fdea99e
--- /dev/null
+++ b/doc/tcp.php
@@ -0,0 +1,47 @@
+<?php
+namespace flame\tcp;
+
+/**
+ * 连接指定地址, 并返回 socket 对象
+ * @param string $address 连接地址, 例如: "127.0.0.1:8687"
+ */
+function connect(string $address):socket {}
+
+class socket {
+    public $local_address;
+    public $remote_address;
+    /**
+     * 读取一定量的数据, 实际读取方式根据 $completion 参数决定;
+     * @param mixed $completion 
+     *  * 参数为 null 时, 表示随机读取一定量数据 (不会超过 8192 字节);
+     *  * 参数为 int 时, 表示读取指定量的数据, 例如 $completion = 1024 表示读取 1024 字节;
+     *  * 参数为 string 时, 表示读取到指定的结束符, 例如 $completion = "\r\n" 读取到 "abc\r\n" 字符串;
+     */
+    function read(mixed $completion = null) {}
+    /**
+     * 发送数据
+     * @param string $data
+     */
+    function write(string $data) {}
+    /**
+     * 关闭连接
+     */
+    function close() {}
+}
+class server {
+    /**
+     * 创建一个服务器, 绑定在指定的地址上, 准备服务
+     * @param string $address 绑定地址, 例如: "0.0.0.0:8888" 或 ":::8888"
+     */
+    function __construct(string $address) {}
+    /**
+     * 启动服务器, 当有连接建立时, 启用协程执行回调函数;
+     * @param callable $cb 回调函数, 形如:
+     *  function($socket) {}
+     */
+    function run(callable $cb) {}
+    /**
+     * 关闭服务器
+     */
+    function close() {}
+}
\ No newline at end of file
diff --git a/doc/time.php b/doc/time.php
index 918bdf4..02c5765 100644
--- a/doc/time.php
+++ b/doc/time.php
@@ -3,7 +3,7 @@
 namespace flame\time;
 
 // 当前协程休眠 $ms 毫秒
-function sleep(Integer $ms) {}
+function sleep(int $ms) {}
 // 获取毫秒时间戳
 function now() {}
 // 获取标准时间 YYYY-mm-dd HH:ii:ss
diff --git a/src/controller.cpp b/src/controller.cpp
index e1b3e60..987564d 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -13,6 +13,17 @@ controller::controller()
 	mthread_id = std::this_thread::get_id();
 }
 
+controller* controller::on_init(std::function<void ()> fn)
+{
+	init_cb.push_back(fn);
+	return this;
+}
+controller* controller::on_stop(std::function<void ()> fn)
+{
+	stop_cb.push_back(fn);
+	return this;
+}
+
 void controller::initialize() {
 	// 使用此环境变量区分父子进程
 	// if (env.count("FLAME_PROCESS_WORKER") > 0)
@@ -32,10 +43,14 @@ void controller::initialize() {
 
 void controller::run() {
 	// if(type == process_type::WORKER) {
-		worker_->run();
+	worker_->run();
 	// }else{
 		// master_->run();
 	// }
+	for (auto fn : stop_cb)
+	{
+		fn();
+	}
 }
 
 controller_master::controller_master()
@@ -83,6 +98,7 @@ void controller_worker::run() {
 	{
 		thread_[i].join();
 	}
+	
 }
 
 
diff --git a/src/core.cpp b/src/core.cpp
index 6da4558..4f1374f 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -3,6 +3,7 @@
 #include "core.h"
 #include "coroutine_queue.h"
 #include "queue.h"
+#include "mutex.h"
 
 namespace flame {
     static php::value init(php::parameters& params) {
@@ -56,11 +57,8 @@ namespace flame {
         return mm[q];
     }
     void declare(php::extension_entry &ext) {
+        gcontroller.reset(new controller());
         ext
-            .on_module_startup([](php::extension_entry &ext) -> bool {
-                gcontroller.reset(new controller());
-                return true;
-            })
             .function<init>("flame\\init", {
                 {"process_name", php::TYPE::STRING},
             })
@@ -71,6 +69,8 @@ namespace flame {
             .function<select>("flame\\select");
 
         queue::declare(ext);
+        mutex::declare(ext);
+        guard::declare(ext);
     }
     
 }
\ No newline at end of file
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 52ec01c..8dfaa81 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -21,22 +21,22 @@ namespace flame
         // EG(fake_scope) = ctx.scope;
         EG(current_execute_data) = ctx.current_execute_data;
     }
-    std::shared_ptr<coroutine> coroutine::start(php::callable fn, zend_execute_data* execute_data)
+    std::shared_ptr<coroutine> coroutine::start(php::callable fn, /*std::vector<php::value> ag,*/ zend_execute_data *execute_data)
     {
         auto co = std::make_shared<coroutine>(std::move(fn));
         // 需要即时保存 PHP 堆栈
         co->php_.current_execute_data = execute_data == nullptr ? gcontroller->core_execute_data : execute_data;
         // 事实上的协程启动会稍后
-        boost::asio::post(gcontroller->context_x, [co] {
+        boost::asio::post(gcontroller->context_x, [co/*, ag = std::move(ag)*/] {
             // co->c1_ = boost::context::callcc([co] (auto &&cc) {
-            co->c1_ = boost::context::fiber([co] (boost::context::fiber &&cc) {
+            co->c1_ = boost::context::fiber([co/*, ag = std::move(ag)*/] (boost::context::fiber &&cc) {
                 auto work = boost::asio::make_work_guard(gcontroller->context_x);
                 // 启动进入协程
                 coroutine::current = co;
                 zend_vm_stack_init();
                 co->c2_ = std::move(cc);
                 // 协程运行
-                co->fn_.call();
+                co->fn_.call(/*ag*/);
                 // 协程运行完毕
                 zend_vm_stack_destroy();
                 coroutine::current = nullptr;
@@ -93,12 +93,17 @@ namespace flame
     }
     void coroutine_handler::operator()(const boost::system::error_code &e, std::size_t n)
     {
-        error = e;
-        nsize = n;
+        if(error) *error = e;
+        co_->len_ = n;
 
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
         boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
+    coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e)
+    {
+        error = &e;
+        return *this;
+    }
     void coroutine_handler::resume()
     {
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
diff --git a/src/coroutine.h b/src/coroutine.h
index 135c51f..b91127c 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -18,7 +18,7 @@ namespace flame {
 		static std::shared_ptr<coroutine> current;
 		static void save_context(php_context_t &ctx);
 		static void restore_context(php_context_t& ctx);
-		static std::shared_ptr<coroutine> start(php::callable fn, zend_execute_data* execute_data = nullptr);
+		static std::shared_ptr<coroutine> start(php::callable fn, /*std::vector<php::value> ag = std::vector<php::value>(0),*/zend_execute_data* execute_data = nullptr);
 		coroutine(php::callable&& fn);
 
 		void suspend();
@@ -29,6 +29,8 @@ namespace flame {
 		boost::context::fiber c1_;
 		boost::context::fiber c2_;
 		php_context_t php_;
+		// 用于部分异步请求返回长度信息
+		std::size_t len_;
 	};
 
 	struct coroutine_handler
@@ -42,10 +44,10 @@ namespace flame {
 		void reset();
 		operator bool() const;
 		void operator()(const boost::system::error_code& e, std::size_t n = 0);
+		coroutine_handler& operator [](boost::system::error_code& e);
 		void resume();
 		void suspend();
-		boost::system::error_code error;
-		std::size_t 	          nsize;
+		boost::system::error_code* error;
 		std::shared_ptr<coroutine> co_;
 
 		friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
@@ -65,10 +67,11 @@ namespace boost::asio
 		explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
 			
 		}
-		using type = void;
-		void get()
+		using type = std::size_t;
+		type get()
 		{
 			ch_.suspend();
+			return ch_.co_->len_;
 		}
 	private:
 		::flame::coroutine_handler &ch_;
diff --git a/src/coroutine_mutex.h b/src/coroutine_mutex.h
new file mode 100644
index 0000000..8b5a499
--- /dev/null
+++ b/src/coroutine_mutex.h
@@ -0,0 +1,58 @@
+#pragma once
+#include "coroutine.h"
+
+namespace flame 
+{
+    class coroutine_mutex
+    {
+    public:
+        coroutine_mutex()
+        : mt_(false)
+        {
+
+        }
+        void lock(coroutine_handler& ch)
+        {  
+            while(mt_)
+            {
+                cm_.insert(ch);
+                ch.suspend();
+            }
+            mt_ = true;
+        }
+        bool try_lock()
+        {
+            if(!mt_)
+            {
+                mt_ = true;
+                return true;
+            }
+            return false;
+        }
+        void unlock()
+        {
+            while(!cm_.empty())
+            {
+                auto ch = cm_.extract(cm_.begin()).value();
+                ch.resume();
+            }
+            mt_ = false;
+        }
+    private:
+        std::set<coroutine_handler> cm_;
+        bool mt_;
+    };
+    class coroutine_guard {
+    public:
+        coroutine_guard(coroutine_mutex& cm, coroutine_handler& ch)
+        : cm_(cm)
+        {
+            cm_.lock(ch);
+        }
+        ~coroutine_guard() {
+            cm_.unlock();
+        }
+    private:
+        coroutine_mutex   cm_;
+    };
+}
\ No newline at end of file
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
index 782e284..1ece214 100644
--- a/src/coroutine_queue.h
+++ b/src/coroutine_queue.h
@@ -26,7 +26,7 @@ namespace flame
         {
             if(closed_)
             {
-                throw php::exception(zend_ce_error, "queue already closed", -1);
+                throw php::exception(zend_ce_exception, "queue already closed", -1);
             }
             if(q_.size() >= n_)
             {
diff --git a/src/flame.cpp b/src/flame.cpp
index d86c78d..cdc19b9 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -7,6 +7,7 @@
 #include "mongodb/mongodb.h"
 #include "kafka/kafka.h"
 #include "rabbitmq/rabbitmq.h"
+#include "tcp/tcp.h"
 
 extern "C"
 {
@@ -40,10 +41,10 @@ extern "C"
         flame::mongodb::declare(ext);
         flame::kafka::declare(ext);
         flame::rabbitmq::declare(ext);
-        // flame::log::declare(ext);
+        flame::tcp::declare(ext);
         // flame::udp::declare(ext);
-        // flame::tcp::declare(ext);
         // flame::http::declare(ext);
+        // flame::log::declare(ext);
         return ext;
     }
 };
\ No newline at end of file
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index 4125c6e..6d16633 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -31,14 +31,14 @@ namespace flame::kafka
         conn_ = rd_kafka_new(RD_KAFKA_CONSUMER, conf, err, sizeof(err));
         if (!conn_)
         {
-            throw php::exception(zend_ce_type_error,
+            throw php::exception(zend_ce_exception,
                                 (boost::format("failed to create Kafka Consumer: %1%") % err).str(), -1);
         }
         auto r = rd_kafka_poll_set_consumer(conn_);
         if (r != RD_KAFKA_RESP_ERR_NO_ERROR)
         {
-            throw php::exception(zend_ce_type_error,
-                                (boost::format("failed to create Kafka Consumer: (%1%) %2%") % r % rd_kafka_err2str(r)).str(), r);
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to create Kafka Consumer: (%1%) %2%") % r % rd_kafka_err2str(r)).str(), r);
         }
         if (topics.size() == 0)
         {
@@ -101,7 +101,7 @@ namespace flame::kafka
         }
         else if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
         {
-            throw php::exception(zend_ce_error,
+            throw php::exception(zend_ce_exception,
                 (boost::format("failed to consume Kafka message: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
                 err);
         }
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index d1c2498..8e6c6ca 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -34,33 +34,30 @@ namespace flame::kafka {
         cb_ = params[0];
         
         // 启动若干协程, 然后进行"并行"消费
-        std::vector<std::shared_ptr<coroutine>> cos(cc_);
-        int run = 0;
-        for(int i=0;i<cos.size();++i)
+        int count = cc_;
+        for (int i = 0; i < count; ++i)
         {
             // 启动协程开始消费
-            ++run;
-            cos[i] = coroutine::start(php::value([this, &run] (php::parameters& params) -> php::value
-            {
+            coroutine::start(php::value([this, &count](php::parameters &params) -> php::value {
                 coroutine_handler ch {coroutine::current};
                 while(!close_)
                 {
                     php::object msg = cs_->consume(ch);
-                        // 可能出现为 NULL 的情况
+                    // 可能出现为 NULL 的情况
                     if (msg.typeof(php::TYPE::NULLABLE))
                     {
                         continue;
                     }
-                    try {
+                    try
+                    {
                         cb_.call({msg});
                     }
                     catch(const std::exception& ex)
                     {
                         std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
-                        close_ = true;
                     }
                 }
-                if (--run == 0) // 当所有并行协程结束后, 停止消费
+                if (--count == 0) // 当所有并行协程结束后, 停止消费
                 {
                     ch_.resume();
                 }
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
index 0e86f1b..b079fcf 100644
--- a/src/mongodb/_connection_lock.cpp
+++ b/src/mongodb/_connection_lock.cpp
@@ -28,7 +28,7 @@ namespace flame::mongodb
         ch.suspend();
         if (has) // 发生了错误
         {
-            throw php::exception(zend_ce_error,
+            throw php::exception(zend_ce_exception,
                     (boost::format("failed to fetch document: (%1%) %2%") % err->code % err->message).str(),
                     err->code);
         }
diff --git a/src/mongodb/mongodb.cpp b/src/mongodb/mongodb.cpp
index 71ea6dd..84311d9 100644
--- a/src/mongodb/mongodb.cpp
+++ b/src/mongodb/mongodb.cpp
@@ -1,3 +1,4 @@
+#include "../controller.h"
 #include "../coroutine.h"
 #include "mongodb.h"
 #include "_connection_pool.h"
@@ -12,14 +13,11 @@ namespace flame::mongodb
 {
     void declare(php::extension_entry &ext)
     {
-        ext
-            .on_module_startup([](php::extension_entry &ext) -> bool {
+        gcontroller->on_init([]() {
                 mongoc_init();
-                return true;
             })
-            .on_module_shutdown([](php::extension_entry &ext) -> bool {
+            ->on_stop([]() {
                 mongoc_cleanup();
-                return true;
             });
         ext
             .function<connect>("flame\\mongodb\\connect");
diff --git a/src/mutex.cpp b/src/mutex.cpp
new file mode 100644
index 0000000..996df58
--- /dev/null
+++ b/src/mutex.cpp
@@ -0,0 +1,59 @@
+#include "coroutine.h"
+#include "coroutine_mutex.h"
+#include "mutex.h"
+
+namespace flame
+{
+    void mutex::declare(php::extension_entry &ext)
+    {
+        php::class_entry<mutex> class_mutex("flame\\mutex");
+        class_mutex
+            .method<&mutex::__construct>("__construct",
+            {
+                {"n", php::TYPE::INTEGER, false, true},
+            })
+            .method<&mutex::lock>("lock")
+            .method<&mutex::unlock>("unlock");
+        ext.add(std::move(class_mutex));
+    }
+    php::value mutex::__construct(php::parameters& params)
+    {
+        mutex_.reset(new coroutine_mutex());
+        return nullptr;
+    }
+    php::value mutex::lock(php::parameters& params)
+    {
+        coroutine_handler ch {coroutine::current};
+        mutex_->lock(ch);
+        return nullptr;
+    }
+    php::value mutex::unlock(php::parameters& params)
+    {
+        mutex_->unlock();
+        return nullptr;
+    }
+    void guard::declare(php::extension_entry &ext)
+    {
+        php::class_entry<guard> class_guard("flame\\guard");
+        class_guard
+            .method<&guard::__construct>("__construct",
+            {
+                {"mutex", "flame\\mutex"},
+            })
+            .method<&guard::__destruct>("__destruct");
+        ext.add(std::move(class_guard));
+    }
+    php::value guard::__construct(php::parameters & params)
+    {
+        php::object obj = params[0];
+        mutex_ = static_cast<mutex*>(php::native(obj))->mutex_;
+        coroutine_handler ch {coroutine::current};
+        mutex_->lock(ch);
+        return nullptr;
+    }
+    php::value guard::__destruct(php::parameters & params)
+    {
+        mutex_->unlock();
+        return nullptr;
+    }
+}
\ No newline at end of file
diff --git a/src/mutex.h b/src/mutex.h
new file mode 100644
index 0000000..66969d6
--- /dev/null
+++ b/src/mutex.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "vendor.h"
+
+namespace flame
+{
+    class coroutine_mutex;
+    class mutex: public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value lock(php::parameters& params);
+        php::value unlock(php::parameters& params);
+
+    private:
+        std::shared_ptr<coroutine_mutex> mutex_;
+        friend class guard;
+    };
+
+    class guard: public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value __destruct(php::parameters &params);
+    private:
+        std::shared_ptr<coroutine_mutex> mutex_;
+    };
+}
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 0163398..f22e8f7 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -145,7 +145,7 @@ ESCAPE_FINISHED:;
         if(!row) {
             int err = mysql_errno(conn.get());
             if(err != 0) {
-                throw php::exception(zend_ce_exception,
+                throw php::exception(zend_ce_error,
                                      (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn.get())).str(),
                                      err);
             }else{
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
index 40c847f..88c4ff4 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/mysql/_connection_lock.cpp
@@ -65,7 +65,7 @@ namespace flame::mysql
         if (err != 0)
         {
             err = mysql_errno(conn_.get());
-            throw php::exception(zend_ce_exception,
+            throw php::exception(zend_ce_error,
                                  (boost::format("failed to rollback MySQL tx: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
                                  err);
         }
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index cc87cdb..934ad8d 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -11,16 +11,16 @@ namespace flame::mysql {
     
     void declare(php::extension_entry &ext)
     {
-        ext
-            .on_module_startup([](php::extension_entry &ext) -> bool
+        gcontroller
+            ->on_init([]()
             {
-                return mysql_library_init(0, nullptr, nullptr) == 0;
+                mysql_library_init(0, nullptr, nullptr);
             })
-            .on_module_shutdown([](php::extension_entry &ext) -> bool
+            ->on_stop([] ()
             {
                 mysql_library_end();
-                return true;
-            })
+            });
+        ext
             .function<connect>("flame\\mysql\\connect",
             {
                 {"url", php::TYPE::STRING},
diff --git a/src/os/os.cpp b/src/os/os.cpp
index 253a171..4badf99 100644
--- a/src/os/os.cpp
+++ b/src/os/os.cpp
@@ -34,7 +34,7 @@ namespace flame::os
                         addr->ifa_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
                         address, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST) != 0)
         {
-            throw php::exception(zend_ce_error_exception, gai_strerror(errno));
+            throw php::exception(zend_ce_error, gai_strerror(errno));
         }
         info.set("address", address);
         if (data.exists(name))
@@ -54,7 +54,7 @@ namespace flame::os
         struct ifaddrs *addr;
         if (getifaddrs(&addr) != 0)
         {
-            throw php::exception(zend_ce_error_exception, std::strerror(errno));
+            throw php::exception(zend_ce_error, std::strerror(errno));
         }
         else if (!addr)
         {
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
index ce92aa9..80363a0 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/rabbitmq/_client.cpp
@@ -50,7 +50,7 @@ namespace flame::rabbitmq
         CHECK_AND_SET_FLAG(immediate, AMQP::immediate);
         
         if(err) {
-            throw php::exception(zend_ce_error,
+            throw php::exception(zend_ce_exception,
                 (boost::format("failed to connect RabbitMQ server: %1%") % err).str(), -1);
         }
     }
@@ -84,7 +84,7 @@ namespace flame::rabbitmq
         ch.suspend();
         if(err) {
             throw php::exception(zend_ce_error,
-                (boost::format("failed to consume RabbitMQ message: %1%") % err).str(), -1);
+                (boost::format("failed to consume RabbitMQ queue: %1%") % err).str(), -1);
         }
         consumer_ch_ = ch;
         ch.suspend();
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index 308d09f..46d26bb 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -2,6 +2,7 @@
 #include "consumer.h"
 #include "_client.h"
 #include "message.h"
+#include "../time/time.h"
 
 namespace flame::rabbitmq
 {
@@ -52,7 +53,14 @@ namespace flame::rabbitmq
                 coroutine_handler cch {coroutine::current};
                 while(auto x = q.pop(cch))
                 {
-                    cb_.call( {x.value()} );
+                    try
+                    {
+                        cb_.call( {x.value()} );
+                    }
+                    catch(const php::exception& ex)
+                    {
+                        std::clog << "[" << time::iso() << "] (ERROR) " << ex << std::endl;
+                    }
                 }
                 if(--count == 0)
                 {
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index bd642b5..0d82e62 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -172,6 +172,6 @@ namespace flame::redis {
 		return std::move(obj);
 	}
 	php::value client::unimplement(php::parameters& params) {
-        throw php::exception(zend_ce_error, "This redis command is NOT yet implemented");
+        throw php::exception(zend_ce_type_error, "This redis command is NOT yet implemented");
     }
 } // namespace flame::redis
diff --git a/src/redis/tx.cpp b/src/redis/tx.cpp
index c8aa1c1..2a984bb 100644
--- a/src/redis/tx.cpp
+++ b/src/redis/tx.cpp
@@ -170,6 +170,6 @@ namespace flame::redis {
         return this;
     }
 	php::value tx::unimplement(php::parameters& params) {
-        throw php::exception(zend_ce_error, "This redis command is NOT yet implemented");
+        throw php::exception(zend_ce_type_error, "This redis command is NOT yet implemented");
     }
 } // namespace flame::redis
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
new file mode 100644
index 0000000..ecb63cf
--- /dev/null
+++ b/src/tcp/server.cpp
@@ -0,0 +1,117 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "server.h"
+#include "tcp.h"
+#include "socket.h"
+#include "../time/time.h"
+
+namespace flame::tcp
+{
+    void server::declare(php::extension_entry &ext)
+    {
+        php::class_entry<server> class_server("flame\\tcp\\server");
+		class_server
+			.method<&server::__construct>("__construct",
+            {
+				{"bind", php::TYPE::STRING},
+			})
+			.method<&server::run>("run",
+            {
+				{"cb", php::TYPE::CALLABLE},
+			})
+			.method<&server::close>("close");
+		ext.add(std::move(class_server));
+    }
+    server::server()
+    : acceptor_(gcontroller->context_x)
+    , socket_(gcontroller->context_x)
+    {
+
+    }
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+
+    php::value server::__construct(php::parameters &params)
+    {
+        std::string str_addr = params[0];
+        auto pair = addr2pair(str_addr);
+        if(pair.second.empty())
+        {
+            throw php::exception(zend_ce_type_error, "failed to bind tcp socket: missing port");
+        }
+        boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
+        addr_.address(addr);
+        addr_.port(std::atoi(pair.second.c_str()));
+
+        set("address", str_addr);
+        acceptor_.open(addr_.protocol());
+        boost::asio::socket_base::reuse_address opt1(true);
+        acceptor_.set_option(opt1);
+        reuse_port opt2(true);
+        acceptor_.set_option(opt2);
+
+        boost::system::error_code err;
+        acceptor_.bind(addr_, err);
+        if(err)
+        {
+            throw php::exception(zend_ce_error, 
+                (boost::format("failed to bind tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        }
+        acceptor_.listen(boost::asio::socket_base::max_listen_connections, err);
+        if (err)
+        {
+            throw php::exception(zend_ce_error,
+                (boost::format("failed to listen tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        }
+        return nullptr;
+    }
+    php::value server::run(php::parameters &params)
+    {
+        cb_ = params[0];
+        coroutine_handler ch {coroutine::current};
+        boost::system::error_code err;
+        while(!closed_)
+        {
+            acceptor_.async_accept(socket_, ch[err]);
+            if(err == boost::asio::error::operation_aborted)
+            {
+                break;
+            }
+            else if(err)
+            {
+                throw php::exception(zend_ce_error,
+                                     (boost::format("failed to accept tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+            }
+            else{
+                php::object obj(php::class_entry<socket>::entry());
+                socket* ptr = static_cast<socket*>(php::native(obj));
+                ptr->socket_ = std::move(socket_);
+                obj.set("local_address",
+                    (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
+                obj.set("remote_address",
+                    (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+
+                coroutine::start(php::callable([obj, cb = cb_] (php::parameters& params) -> php::value {
+                    try
+                    {
+                        cb.call({obj});
+                    }
+                    catch(const php::exception& ex)
+                    {
+                        std::cout << "[" << time::iso << "] (ERROR) " << ex << std::endl;
+                    }
+                    return nullptr;
+                }));
+            }
+        }
+        cb_ = nullptr;
+        return nullptr;
+    }
+    php::value server::close(php::parameters &params)
+    {
+        if(!closed_) {
+            closed_ = true;
+            acceptor_.cancel();
+        }
+        return nullptr;
+    }
+}
\ No newline at end of file
diff --git a/src/tcp/server.h b/src/tcp/server.h
new file mode 100644
index 0000000..865b324
--- /dev/null
+++ b/src/tcp/server.h
@@ -0,0 +1,20 @@
+#pragma once
+
+namespace flame::tcp
+{
+    class server : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        server();
+        php::value __construct(php::parameters &params);
+        php::value run(php::parameters &params);
+        php::value close(php::parameters &params);
+    private:
+        boost::asio::ip::tcp::acceptor acceptor_;
+		boost::asio::ip::tcp::socket     socket_;
+        boost::asio::ip::tcp::endpoint     addr_;
+        php::callable                        cb_;
+        bool                             closed_;
+    };
+} // namespace flame::tcp
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
new file mode 100644
index 0000000..ee9aef5
--- /dev/null
+++ b/src/tcp/socket.cpp
@@ -0,0 +1,105 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "socket.h"
+
+namespace flame::tcp {
+	void socket::declare(php::extension_entry& ext)
+    {
+		php::class_entry<socket> class_socket("flame\\tcp\\socket");
+		class_socket
+			.property({"local_address", ""})
+			.property({"remote_address", ""})
+			.method<&socket::read>("read",
+            {
+				{"completion", php::TYPE::UNDEFINED, false, true}
+			})
+			.method<&socket::write>("write",
+            {
+				{"data", php::TYPE::STRING}
+			})
+			.method<&socket::close>("close");
+		ext.add(std::move(class_socket));
+	}
+	socket::socket()
+	: socket_(gcontroller->context_x) {
+
+	}
+    
+	php::value socket::read(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时进行读取
+        coroutine_guard guard(rmutex_, ch);
+
+		boost::system::error_code err;
+        std::size_t len;
+        if (params.size() == 0) // 1. 随意读取一段数据
+        {
+            len = socket_.async_read_some(boost::asio::buffer(buffer_.prepare(8192), 8192), ch[err]);
+            buffer_.commit(len);
+        }
+        else if (params[0].typeof(php::TYPE::STRING)) // 2. 读取到指定的结束符
+        {
+			std::string delim = params[0];
+            len = boost::asio::async_read_until(socket_, buffer_, delim, ch[err]);
+            std::cout << "len: " << len << std::endl;
+        }
+        else if (params[0].typeof(php::TYPE::INTEGER)) // 3. 读取指定长度
+        {
+            std::cout << "INTEGER\n";
+			std::size_t want = params[0].to_integer();
+			if(buffer_.size() >= want) {
+                len = want;
+                goto RETURN_DATA;
+			}
+            // 读取指定长度 (剩余的缓存数据也算在长度中)
+            len = boost::asio::async_read(socket_, buffer_, boost::asio::transfer_exactly(want - buffer_.size()), ch[err]);
+            len = want;
+		}
+        else // 未知读取方式
+        {
+            throw php::exception(zend_ce_error, "failed to read tcp socket: unknown completion");
+        }
+RETURN_DATA:
+        // 数据返回
+        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof)
+        {
+            return nullptr;
+        }
+        else if (err)
+        {
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to read tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        }
+        else
+        {
+            php::string data(len);
+            boost::asio::buffer_copy(boost::asio::buffer(data.data(), len), buffer_.data(), len);
+            buffer_.consume(len);
+            return std::move(data);
+        }
+    }
+	php::value socket::write(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时写入
+        coroutine_guard guard(wmutex_, ch);
+        
+        boost::system::error_code err;
+		std::string data = params[0];
+        boost::asio::async_write(socket_, boost::asio::buffer(data), ch);
+
+        if (!err || err == boost::asio::error::operation_aborted)
+        {
+            return nullptr;
+        }
+        else
+        {
+            throw php::exception(zend_ce_exception,
+                (boost::format("failed to write tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        }
+	}
+	php::value socket::close(php::parameters& params) {
+		socket_.shutdown(boost::asio::socket_base::shutdown_both);
+		return nullptr;
+	}
+}
+  
\ No newline at end of file
diff --git a/src/tcp/socket.h b/src/tcp/socket.h
new file mode 100644
index 0000000..11d3783
--- /dev/null
+++ b/src/tcp/socket.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "../coroutine_mutex.h"
+
+namespace flame::tcp
+{
+    class socket : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        socket();
+        php::value read(php::parameters &param);
+        php::value write(php::parameters &params);
+        php::value close(php::parameters &params);
+
+    private:
+        boost::asio::ip::tcp::socket socket_;
+        boost::asio::streambuf       buffer_;
+        coroutine_mutex              rmutex_;
+        coroutine_mutex              wmutex_;
+
+        friend class server;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::tcp
\ No newline at end of file
diff --git a/src/tcp/tcp.cpp b/src/tcp/tcp.cpp
new file mode 100644
index 0000000..4735c00
--- /dev/null
+++ b/src/tcp/tcp.cpp
@@ -0,0 +1,86 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "tcp.h"
+#include "socket.h"
+#include "server.h"
+
+namespace flame::tcp {
+	static std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
+	void declare(php::extension_entry& ext)
+    {
+		gcontroller->on_init([] ()
+        {
+            resolver_.reset(new boost::asio::ip::tcp::resolver(gcontroller->context_x));
+        })->on_stop([] ()
+        {
+            resolver_.reset();
+        });
+		ext
+			.function<connect>("flame\\tcp\\connect",
+            {
+				{"address", php::TYPE::STRING},
+			});
+		socket::declare(ext);
+		server::declare(ext);
+	}
+
+    std::pair<std::string, std::string> addr2pair(const std::string &addr)
+    {
+        char *s = const_cast<char*>(addr.data()), *p, *e = s + addr.size();
+        for (p = e - 1; p > s; --p)
+        {
+            if (*p == ':')
+                break; // 分离 地址与端口
+        }
+        if (*p != ':')
+        {
+            return std::make_pair<std::string, std::string>(std::string(addr), "");
+        }
+        else
+        {
+            return std::make_pair<std::string, std::string>(
+                std::string(s, p - s), std::string(p+1, e-p-1));
+        }
+    }
+    php::value connect(php::parameters& params) {
+		php::object obj(php::class_entry<socket>::entry());
+        socket *ptr = static_cast<socket *>(php::native(obj));
+
+        std::string str = params[0];
+        auto pair = addr2pair(str);
+        if(pair.second.empty()) {
+            throw php::exception(zend_ce_type_error, "failed to connect tcp socket: missing port");
+        }
+
+		coroutine_handler ch{coroutine::current};
+        boost::system::error_code err;
+		// DNS 地址解析
+		resolver_->async_resolve(pair.first, pair.second, [&err, &obj, &ptr, &ch] (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type edps) {
+			if(error) 
+            {
+                err = error;
+                ch.resume();
+                return;
+            }
+			// 连接
+            boost::asio::async_connect(ptr->socket_, edps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::tcp::endpoint &edp) {
+                if (error)
+                {
+                    err = error;
+                    ch.resume();
+                    return;
+                }
+                obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
+				obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+                ch.resume();
+            });
+		});
+        ch.suspend();
+        if(err) 
+        {
+            throw php::exception(zend_ce_exception,
+                (boost::format("failed to connect tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        }
+        return std::move(obj);
+	}
+} // namespace flame::tcp
diff --git a/src/tcp/tcp.h b/src/tcp/tcp.h
new file mode 100644
index 0000000..57fa815
--- /dev/null
+++ b/src/tcp/tcp.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::tcp
+{
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters &params);
+    std::pair<std::string, std::string> addr2pair(const std::string& addr);
+} // namespace flame::tcp
\ No newline at end of file
diff --git a/src/vendor.h b/src/vendor.h
index 6104835..c7549c3 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -18,6 +18,7 @@
 #include <stack>
 #include <optional>
 #include <unordered_set>
+#include <tuple>
 #include <phpext/phpext.h>
 #include <http_parser.h>
 #include <hiredis/hiredis.h>
diff --git a/test/mutex_1.php b/test/mutex_1.php
new file mode 100644
index 0000000..b3e3d0b
--- /dev/null
+++ b/test/mutex_1.php
@@ -0,0 +1,32 @@
+<?php
+flame\init("mutex_1");
+
+flame\go(function() {
+    $mutex = new flame\mutex();
+    flame\go(function() use($mutex) {
+        echo "(2) awaiting lock\n";
+        $guard = new flame\guard($mutex);
+        echo "(2) lock acquired\n";
+        flame\time\sleep(5000);
+        echo "(2) protected echo\n";
+        echo "(2) lock releasing\n";
+    });
+    flame\go(function() use($mutex) {
+        echo "(3) awaiting lock\n";
+        $mutex->lock();
+        echo "(3) lock acquired\n";
+        flame\time\sleep(1000);
+        echo "(3) protected echo\n";
+        $mutex->unlock();
+        echo "(3) lock released\n";
+    });
+    echo "(1) awaiting lock\n";
+    $mutex->lock();
+    echo "(1) lock acquired\n";
+    flame\time\sleep(2000);
+    echo "(1) protected echo\n";
+    $mutex->unlock();
+    echo "(1) lock released\n";
+});
+
+flame\run();
diff --git a/test/tcp_1.php b/test/tcp_1.php
new file mode 100644
index 0000000..bb7c15a
--- /dev/null
+++ b/test/tcp_1.php
@@ -0,0 +1,34 @@
+<?php
+flame\init("tcp_1");
+
+flame\go(function() {
+    $server = new flame\tcp\server("127.0.0.1:8687");
+    flame\go(function() use($server) {
+        flame\time\sleep(10000);
+        echo "closing\n";
+        $server->close();
+    });
+    $server->run(function($socket) {
+        echo "connection accepted\n";
+        while($line = $socket->read("\n")) {
+            var_dump($line);
+            $socket->write($line);
+        }
+        echo "disconnected\n";
+    });
+    echo "server closed\n";
+});
+flame\go(function() {
+    flame\time\sleep(1000);
+    $socket = flame\tcp\connect("127.0.0.1:8687");
+    
+    for($i=0;$i<10;++$i) {
+        $socket->write("aaaaaa\nbbbbbb\n");
+        $socket->read("\n");
+        flame\time\sleep(100);
+        $socket->read("\n");
+        flame\time\sleep(100);
+    }
+});
+
+flame\run();

From d62862641c12013b6bdfe44efc0a11b0429e793b Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 27 Nov 2018 18:30:42 +0800
Subject: [PATCH 011/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=20MySQL=20=E8=BF=9E?=
 =?UTF-8?q?=E6=8E=A5=E6=89=AB=E6=8F=8F=E5=BA=94=E8=AE=BF=E9=97=AE=E5=AF=B9?=
 =?UTF-8?q?=E5=BA=94=E9=A1=B9;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mysql/_connection_pool.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 8736cb4..89afee2 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -110,7 +110,7 @@ namespace flame::mysql
             {
                 // 超低水位，关闭不活跃或已丢失的连接
                 auto duration = now - (*i).ttl;
-                if (duration > std::chrono::seconds(60) || mysql_ping(conn_.front().conn) != 0)
+                if (duration > std::chrono::seconds(60) || mysql_ping((*i).conn) != 0)
                 {
                     mysql_close((*i).conn);
                     --size_;

From 4574ad2c83d617ac572019bc5cf1d0012b146596 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 27 Nov 2018 22:29:45 +0800
Subject: [PATCH 012/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0?=
 =?UTF-8?q?=E5=A4=9A=E8=BF=9B=E7=A8=8B=E5=8F=8A=E6=97=A5=E5=BF=97;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/log.php               |  39 +++++
 src/controller.cpp        | 332 ++++++++------------------------------
 src/controller.h          |  42 ++---
 src/controller_master.cpp | 152 +++++++++++++++++
 src/controller_master.h   |  28 ++++
 src/controller_worker.cpp |  36 +++++
 src/controller_worker.h   |  15 ++
 src/core.cpp              |  33 ++--
 src/coroutine.cpp         |  18 ++-
 src/coroutine.h           |   3 +-
 src/log/log.cpp           |  91 +++++++++++
 src/log/log.h             |   8 +
 src/mongodb/mongodb.cpp   |   2 +-
 src/mysql/mysql.cpp       |   2 +-
 src/tcp/tcp.cpp           |   2 +-
 test/exception_1.php      |   1 -
 16 files changed, 473 insertions(+), 331 deletions(-)
 create mode 100644 doc/log.php
 create mode 100644 src/controller_master.cpp
 create mode 100644 src/controller_master.h
 create mode 100644 src/controller_worker.cpp
 create mode 100644 src/controller_worker.h
 create mode 100644 src/log/log.cpp
 create mode 100644 src/log/log.h

diff --git a/doc/log.php b/doc/log.php
new file mode 100644
index 0000000..774b841
--- /dev/null
+++ b/doc/log.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * 日志模块, 可通过框架配置设置日志等级;
+ */
+namespace flame\log;
+/**
+ * 记录 TRACE 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function trace($x, $y, $z /* ... */) {}
+/**
+ * 记录 DEBUG 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function debug($x, $y, $z /* ... */) {}
+/**
+ * 记录 INFO 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function info($x, $y, $z /* ... */) {}
+/**
+ * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function warn($x, $y, $z /* ... */) {}
+/**
+ * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function warning($x, $y, $z /* ... */) {}
+/**
+ * 记录 ERROR 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function error($x, $y, $z /* ... */) {}
+/**
+ * 同 fatal()
+ * @see fatal()
+ */
+function fail($x, $y, $z /* ... */) {}
+/**
+ * 记录 FATAL 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function fatal($x, $y, $z /* ... */) {}
\ No newline at end of file
diff --git a/src/controller.cpp b/src/controller.cpp
index 987564d..2a72257 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -1,282 +1,76 @@
 #include "controller.h"
 #include "coroutine.h"
+#include "controller_master.h"
+#include "controller_worker.h"
 
 namespace flame {
-// 全局控制器
-std::unique_ptr<controller> gcontroller;
+	// 全局控制器
+	std::unique_ptr<controller> gcontroller;
 
-controller::controller()
-	: type(process_type::UNKNOWN)
-	, env(boost::this_process::environment())
-	, status(controller_status::UNKNOWN) {
+	controller::controller()
+		: type(process_type::UNKNOWN)
+		, env(boost::this_process::environment())
+		, status(STATUS_UNKNOWN) {
 
-	mthread_id = std::this_thread::get_id();
-}
-
-controller* controller::on_init(std::function<void ()> fn)
-{
-	init_cb.push_back(fn);
-	return this;
-}
-controller* controller::on_stop(std::function<void ()> fn)
-{
-	stop_cb.push_back(fn);
-	return this;
-}
-
-void controller::initialize() {
-	// 使用此环境变量区分父子进程
-	// if (env.count("FLAME_PROCESS_WORKER") > 0)
-	// {
-		type = process_type::WORKER;
-		worker_.reset(new controller_worker());
-	// }
-	// else
-	// {
-	// 	type = process_type::MASTER;
-	// 	master_.reset(new controller_master());
-	// }
-	for (auto fn : init_cb) {
-		fn();
+		mthread_id = std::this_thread::get_id();
 	}
-}
-
-void controller::run() {
-	// if(type == process_type::WORKER) {
-	worker_->run();
-	// }else{
-		// master_->run();
-	// }
-	for (auto fn : stop_cb)
+	controller *controller::on_init(std::function<void (const php::array &options)> fn)
 	{
-		fn();
-	}
-}
-
-controller_master::controller_master()
-	: signal_(gcontroller->context_x/*, SIGINT, SIGTERM*/)
-{
-}
-
-void controller_master::run() {
-	// 主进程的启动过程:
-	// 1. 新增环境变量及命令行参数, 启动子进程;
-	// 2. 侦听子进程退出动作，按状态标志进行拉起；
-	// 3. 等待所有子进程输出, 并进行日志数据重定向(日志配置);
-	// 4. 监听信号进行日志重载或停止
-	// signal_.async_wait([this](const boost::system::error_code &error, int signal) {
-
-	// });
-	// 5. 启动 context_x 运行
-	gcontroller->context_x.run();
-}
-
-controller_worker::controller_worker()
-	: signal_(gcontroller->context_x/*, SIGINT, SIGTERM*/)
-{
-	
-}
-
-void controller_worker::run() {
-	auto work = boost::asio::make_work_guard(gcontroller->context_y);
-	// 子进程的启动过程:
-	// 1. 监听信号进行日志重载或停止
-	// signal_.async_wait([this] (const boost::system::error_code& error, int signal) {
-		
-	// });
-	// 2. 启动线程池, 并使用线程池运行 context_y
-	thread_.resize(3);
-	for(int i=0;i<thread_.size();++i) {
-		thread_[i] = std::thread([this] {
-			gcontroller->context_y.run();
-		});
+		init_cb.push_back(fn);
+		return this;
 	}
-	// 3. 启动 context_x 运行
-	gcontroller->context_x.run();
-	work.reset();
-	for (int i=0; i<thread_.size(); ++i)
+	controller* controller::on_stop(std::function<void ()> fn)
 	{
-		thread_[i].join();
+		stop_cb.push_back(fn);
+		return this;
+	}
+	void controller::initialize(const std::string& title, const php::array& options) {
+		status |= STATUS_INITIALIZED;
+		for (auto fn : init_cb)
+		{
+			fn(options);
+		}
+		if(options.exists("worker"))
+		{
+			worker_size = std::min( std::max( (int)options.get("worker").to_integer(), 1), 256 );
+			// 使用此环境变量区分父子进程
+			if (env.count("FLAME_PROCESS_WORKER") > 0)
+			{
+				php::callable("cli_set_process_title").call({title + " (flame/" + env["FLAME_PROCESS_WORKER"].to_string() + ")"});
+				type = process_type::WORKER;
+				worker_.reset(new controller_worker());
+				worker_->initialize(options);
+			}
+			else
+			{
+				php::callable("cli_set_process_title").call({title + " (flame/m)"});
+				type = process_type::MASTER;
+				master_.reset(new controller_master());
+				master_->initialize(options);
+			}
+		}
+		else
+		{
+			php::callable("cli_set_process_title").call({title + " (flame/w)"});
+			type = process_type::WORKER;
+			worker_.reset(new controller_worker());
+			worker_->initialize(options);
+		}
 	}
-	
-}
-
-
-	// void controller::init(const std::string& title) {
-	// 	environ["FLAME_PROCESS_TITLE"] = title;
-	// 	if(type == MASTER) {
-	// 		php::callable("cli_set_process_title").call({ title + " (flame/m)" });
-	// 		int count = 1;
-	// 		if(options.exists("worker")) {
-	// 			count = std::min(256, std::max((int)options.get("worker").to_integer(), 1));
-	// 		}
-	// 		mworker_.resize(count);
-	// 	}else{
-	// 		php::callable("cli_set_process_title").call({ title + " (flame/" + environ["FLAME_PROCESS_WORKER"].to_string() + ")" });
-	// 	}
-	// 	php::value debug = options.get("debug");
-	// 	if(!debug.typeof(php::TYPE::UNDEFINED) && debug.empty()) {
-	// 		status |= STATUS_AUTORESTART;
-	// 	}
-	// 	status |= STATUS_INITIALIZED;
-	// }
-	// controller* controller::before(std::function<void ()> fn) {
-	// 	before_.push_back(fn);
-	// 	return this;
-	// }
-	// controller* controller::after(std::function<void ()> fn) {
-	// 	after_.push_back(fn);
-	// 	return this;
-	// }
-	// void controller::before() {
-	// 	for(auto fn: before_) {
-	// 		fn();
-	// 	}
-	// }
-	// void controller::after() {
-	// 	for(auto fn: after_) {
-	// 		fn();
-	// 	}
-	// }
-	// void controller::run() {
-	// 	status |= STATUS_STARTED;
-	// 	switch(type) {
-	// 	case MASTER:
-	// 		master_run();
-	// 		break;
-	// 	case WORKER:
-	// 		worker_run();
-	// 		break;
-	// 	default:
-	// 		assert(0 && "未知进程类型");
-	// 	}
-	// }
-	// static std::string build_command_line() {
-	// 	php::stream_buffer sb;
-	// 	std::ostream os(&sb);
-	// 	os << php::constant("PHP_BINARY");
-	// 	php::array argv = php::server("argv");
-	// 	for(auto i=argv.begin(); i!=argv.end(); ++i) {
-	// 		os << " " << i->second;
-	// 	}
-	// 	return std::string(sb.data(), sb.size());
-	// }
-	// void controller::spawn(int i) {
-	// 	std::string cmd = build_command_line();
-
-	// 	boost::process::environment env = environ;
-	// 	env["FLAME_PROCESS_WORKER"] = std::to_string(i+1);
-	// 	mworker_[i] = boost::process::child(cmd, env, mg_, context,
-	// 		boost::process::std_out > stdout,
-	// 		boost::process::std_err > stdout,
-	// 		boost::process::on_exit = [this, i] (int exit_code, const std::error_code& error) {
-	// 			if(error.value() == static_cast<int>(std::errc::no_child_process)) return;
-	// 			if(exit_code != 0 && (status & STATUS_AUTORESTART)) {
-	// 				// 日志记录
-	// 				log::logger_->write(boost::format(" %2% [%1%] (WARN) worker unexpected exit, restart in 3s ...") % time::datetime());
-	// 				auto tm = std::make_shared<boost::asio::steady_timer>(context, std::chrono::seconds(3));
-	// 				tm->async_wait([this, tm, i] (const boost::system::error_code& error) {
-	// 					if(!error) spawn(i);
-	// 				});
-	// 			}else{
-	// 				for(int i=0;i<mworker_.size();++i) {
-	// 					if(mworker_[i].running()) return;
-	// 				}
-	// 				context.stop();
-	// 			}
-	// 		});
-	// }
-	// void controller::master_run() {
-	// 	before();
-	// 	for(int i=0; i<mworker_.size(); ++i) {
-	// 		spawn(i);
-	// 	}
-	// 	// 主进程负责进程监控, 转发信号
-	// 	ms_.async_wait([this] (const boost::system::error_code& error, int sig) {
-	// 		// 日志记录
-	// 		log::logger_->write(boost::format(" %3% [%1%] (INFO) master receives signal '%2%', exiting ...") % time::datetime() % sig);
-	// 		signal_ = sig;
-	// 		for(int i=0; i<mworker_.size(); ++i) {
-	// 			::kill(mworker_[i].id(), sig);
-	// 		}
-	// 		context.stop();
-	// 	});
-	// 	context.run();
-	// 	master_shutdown();
-	// }
-	// void controller::worker_run() {
-	// 	before();
-	// 	// boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work(context_ex.get_executor());
-	// 	ws_.async_wait([this] (const boost::system::error_code& error, int sig) {
-	// 		signal_ = sig;
-	// 		context.stop();
-	// 	});
-	// 	// 辅助工作线程
-	// 	wworker_.resize(4);
-	// 	for(int i=0;i<4;++i) {
-	// 		wworker_[i] = std::thread([this] {
-	// 			context_ex.run();
-	// 		});
-	// 	}
-	// 	// 工作进程负责执行
-	// 	try{
-	// 		context.run();
-
-	// 		php::exception::rethrow();
-	// 	}catch(...) {
-	// 		exception = std::current_exception();
-	// 	}
-	// 	worker_shutdown();
-	// }
-	// void controller::shutdown() {
-	// 	switch(type) {
-	// 	case MASTER:
-	// 		master_shutdown();
-	// 		break;
-	// 	case WORKER:
-	// 		worker_shutdown();
-	// 		break;
-	// 	default:
-	// 		assert(0 && "未知进程类型");
-	// 	}
-	// }
-	// void controller::master_shutdown() {
-	// 	if(status & STATUS_SHUTDOWN) return;
-	// 	status |= STATUS_SHUTDOWN;
-
-	// 	options = nullptr;
-	// 	context.stop();
-	// 	context_ex.stop();
-	// 	// 这里有两种情况:
-	// 	// 1. 所有子进程自主退出;
-	// 	// 2. 子进程还未退出, 一段时间后强制结束;
-	// 	std::error_code error;
-	// 	if(!mg_.wait_for(std::chrono::seconds(10), error)) {
-	// 		mg_.terminate(); // 10s 后还未结束, 强制杀死
-	// 	}
-	// 	after();
-	// 	exit(0);
-	// }
-	// void controller::worker_shutdown() {
-	// 	if(status & STATUS_SHUTDOWN) return;
-	// 	status |= STATUS_SHUTDOWN;
-
-	// 	options = nullptr;
-	// 	context.stop();
-	// 	context_ex.stop();
-	// 	ws_.cancel();
-	// 	for(int i=0;i<wworker_.size();++i) {
-	// 		if(wworker_[i].joinable()) {
-	// 			wworker_[i].join();
-	// 		}
-	// 	}
-	// 	after();
-	// 	// 停止所有还在运行的协程
-	// 	coroutine::shutdown();
-	// 	// !!! 发生异常退出, 防止 PHP 引擎将还存活的对象内存提前释放
-	// 	if(exception) exit(-99);
-	// 	if(signal_ == SIGINT) exit(0);
-	// 	exit(0);
-	// }
 
+	void controller::run() {
+		if(type == process_type::WORKER)
+		{
+			worker_->run();
+		}
+		else
+		{
+			master_->run();
+		}
+		// 运行完毕
+		for (auto fn : stop_cb)
+		{
+			fn();
+		}
+	}
 }
diff --git a/src/controller.h b/src/controller.h
index bcd473e..c45a742 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -1,29 +1,9 @@
 #pragma once
 #include "vendor.h"
+#include "controller_master.h"
+#include "controller_worker.h"
 
 namespace flame {
-	class controller_master {
-	public:
-		controller_master();
-		void run();
-
-	  private:
-		std::vector<boost::process::child> worker_;
-		boost::process::group               group_;
-		boost::asio::signal_set            signal_;
-
-	};
-	class controller_worker {
-	public:
-		controller_worker();
-		void run();
-
-	  private:
-		std::vector<std::thread>           thread_;
-		boost::asio::signal_set            signal_;
-
-		
-	};
 	class controller {
 	public:
 		boost::asio::io_context context_x;
@@ -36,28 +16,28 @@ namespace flame {
 			WORKER = 2,
 		} type;
 		boost::process::environment env;
-		boost::program_options::variables_map options;
-		enum class controller_status
+		enum controller_status
 		{
-			UNKNOWN = 0,
-		} status;
-
+			STATUS_UNKNOWN = 0,
+			STATUS_INITIALIZED = 1,
+		};
+		int status;
+		std::size_t    worker_size;
 		std::thread::id mthread_id;
 	private:
 		// 核心进程对象
 		std::unique_ptr<controller_master> master_;
 		// 工作进程对象
 		std::unique_ptr<controller_worker> worker_;
-		
 		// 公共
-		std::list<std::function<void ()>>  init_cb;
+		std::list<std::function<void (const php::array& options)>>  init_cb;
 		std::list<std::function<void ()>>  stop_cb;
 	public:
 		controller();
 		controller(const controller& c) = delete;
-		void initialize();
+		void initialize(const std::string& title, const php::array &options);
 		void run();
-		controller* on_init(std::function<void ()> fn);
+		controller *on_init(std::function<void(const php::array &options)> fn);
 		controller* on_stop(std::function<void ()> fn);
 	};
 
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
new file mode 100644
index 0000000..22896d3
--- /dev/null
+++ b/src/controller_master.cpp
@@ -0,0 +1,152 @@
+#include "controller_master.h"
+#include "controller.h"
+
+namespace flame
+{
+    controller_master::controller_master()
+        : signal_(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2)
+    {
+
+    }
+    void controller_master::initialize(const php::array& options)
+    {
+        if(options.exists("logger"))
+        {
+            ofpath_ = options.get("logger").to_string();
+        }
+        else
+        {
+            ofpath_ = "stdout";
+        }
+        reload_output();
+        // 主进程实际上直接运行
+        run();
+        // 直接退出
+        exit(0);
+    }
+    static std::string start_command_line()
+    {
+        std::ostringstream ss;
+        ss << php::constant("PHP_BINARY");
+        php::array argv = php::server("argv");
+        for (auto i = argv.begin(); i != argv.end(); ++i)
+        {
+            ss << " " << i->second;
+        }
+        return ss.str();
+    }
+    void controller_master::spawn_worker(int i)
+    {
+        ++count_;
+        std::string cmd = start_command_line();
+
+        boost::process::environment env = gcontroller->env;
+        env["FLAME_PROCESS_WORKER"] = std::to_string(i + 1);
+        worker_[i] = boost::process::child(cmd, env, group_, gcontroller->context_x,
+            boost::process::std_out > pipe_[i],
+            boost::process::std_err > pipe_[i],
+            boost::process::on_exit = [this, i](int exit_code, const std::error_code &error) {
+                if (error.value() == static_cast<int>(std::errc::no_child_process))
+                    return;
+                if (exit_code != 0)
+                {
+                    // 日志记录
+                    int sec = std::rand() % 3 + 2;
+                    boost::asio::async_write(pipe_[i],
+                        boost::asio::buffer((boost::format("(FATAL) worker unexpected exit, restart in %2%s ...") % sec).str()),
+                        [this, i, sec](const boost::system::error_code &error, std::size_t nsent) {
+                            auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
+                            tm->async_wait([this, tm, i](const boost::system::error_code &error) {
+                                if (error)
+                                {
+                                    std::cerr << "(FATAL) failed to restart worker: (" << error.value() << ") " << error.message() << std::endl;
+                                }
+                                else
+                                {
+                                    spawn_worker(i);
+                                }
+                            });
+                        });
+                }
+                else
+                {
+                    pipe_[i].close();
+                }
+            });
+        
+    }
+    void controller_master::redirect_output(int i)
+    {
+        boost::asio::async_read_until(pipe_[i], buff_[i], '\n', [this, i] (const boost::system::error_code& error, std::size_t nread)
+        {
+            if(error == boost::asio::error::operation_aborted) 
+            {
+
+            }
+            else if(error)
+            {
+                std::cerr << "(ERROR) failed to read from worker process\n";
+            }
+            else
+            {
+                for (auto x = boost::asio::buffer_sequence_begin(buff_[i].data()); x != boost::asio::buffer_sequence_end(buff_[i].data()); ++x)
+                {
+                    offile_->write((char*)x->data(), x->size());
+                }
+                buff_[i].consume(nread);
+                redirect_output(i);
+            }
+        });
+    }
+    // !!! run 实际上是在 initialize 中执行 (防止实际启用了功能)
+    void controller_master::run()
+    {
+        // 主进程的启动过程:
+        // 1. 新增环境变量及命令行参数, 启动子进程;
+        for(int i=0;i<gcontroller->worker_size;++i)
+        {
+            pipe_.emplace_back(gcontroller->context_x);
+            spawn_worker(i);
+            redirect_output(i);
+        }
+        // 2. 监听信号进行日志重载或停止
+        signal_.async_wait([this] (const boost::system::error_code &error, int sig) {
+            if(sig == SIGUSR2)
+            {
+                reload_output();
+            }
+            else
+            {
+                signal_.clear();
+            }
+        });
+        // 3. 启动运行
+        thread_ = std::thread([this] {
+            gcontroller->context_y.run();
+        });
+        gcontroller->context_x.run();
+        // 4. 等待工作线程结束
+        signal_.clear();
+        thread_.join();
+        // 5. 等待工作进程结束
+        std::error_code error;
+        if(!group_.wait_for(std::chrono::seconds(10), error))
+        {
+            group_.terminate();
+        }
+    }
+    void controller_master::reload_output()
+    {
+        if(ofpath_[0] == '/') {
+            offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
+            // 文件打开失败时不会抛出异常，需要额外的状态检查
+			if(!(*offile_)) {
+                std::clog << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
+            }else{
+                return;
+            }
+		}
+        
+        offile_.reset(&std::clog, boost::null_deleter());
+    }
+}
\ No newline at end of file
diff --git a/src/controller_master.h b/src/controller_master.h
new file mode 100644
index 0000000..796b424
--- /dev/null
+++ b/src/controller_master.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "vendor.h"
+
+namespace flame
+{
+    class controller_master {
+	public:
+		controller_master();
+        void initialize(const php::array& options);
+		void run();
+
+    private:
+		std::vector<boost::process::child> worker_;
+        std::vector<boost::process::async_pipe> pipe_;
+        std::vector<boost::asio::streambuf> buff_;
+		boost::process::group               group_;
+		boost::asio::signal_set            signal_;
+		std::thread                        thread_;
+        std::size_t                        count_;
+
+        std::string                        ofpath_;
+        std::shared_ptr<std::ostream>      offile_;
+
+        void spawn_worker(int i);
+        void reload_output();
+        void redirect_output(int i);
+    };
+}
\ No newline at end of file
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
new file mode 100644
index 0000000..c831428
--- /dev/null
+++ b/src/controller_worker.cpp
@@ -0,0 +1,36 @@
+#include "controller_worker.h"
+#include "controller.h"
+
+namespace flame
+{
+    controller_worker::controller_worker()
+    {
+
+    }
+    void controller_worker::initialize(const php::array &options)
+    {
+        
+    }
+    void controller_worker::run()
+    {
+        // 工作线程的使用时随机的, 需要保持其一直存在
+        auto work = boost::asio::make_work_guard(gcontroller->context_y);
+        // 子进程的启动过程:
+        // 1. 启动线程池, 并使用线程池运行 context_y
+        thread_.resize(3);
+        for (int i = 0; i < thread_.size(); ++i)
+        {
+            thread_[i] = std::thread([this] {
+                gcontroller->context_y.run();
+            });
+        }
+        // 2. 启动 context_x 运行
+        gcontroller->context_x.run();
+        // 3. 确认工作线程停止
+        work.reset();
+        for (int i = 0; i < thread_.size(); ++i)
+        {
+            thread_[i].join();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/controller_worker.h b/src/controller_worker.h
new file mode 100644
index 0000000..b717696
--- /dev/null
+++ b/src/controller_worker.h
@@ -0,0 +1,15 @@
+#pragma once
+#include "vendor.h"
+
+namespace flame
+{
+    class controller_worker {
+	public:
+		controller_worker();
+        void initialize(const php::array& options);
+		void run();
+
+	private:
+		std::vector<std::thread> thread_;
+	};
+}
\ No newline at end of file
diff --git a/src/core.cpp b/src/core.cpp
index 4f1374f..fdc45a8 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -7,7 +7,12 @@
 
 namespace flame {
     static php::value init(php::parameters& params) {
-        gcontroller->initialize();
+        php::array options(0);
+        if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
+            options = params[1];
+        }
+        gcontroller->core_execute_data = EG(current_execute_data);
+        gcontroller->initialize(params[0], options);
         return nullptr;
     }
     static php::value go(php::parameters& params) {
@@ -16,27 +21,14 @@ namespace flame {
         return nullptr;
     }
     static php::value run(php::parameters& params) {
-        gcontroller->core_execute_data = EG(current_execute_data);
-        gcontroller->run();
+        if(gcontroller->status & controller::STATUS_INITIALIZED) {
+            gcontroller->core_execute_data = EG(current_execute_data);
+            gcontroller->run();
+        }else{
+            throw php::exception(zend_ce_type_error, "failed to run flame: not yet initialized (forget to call 'flame\\init()' ?)");
+        }
         return nullptr;
     }
-    // static coroutine_queue<int> q;
-    // static php::value produce(php::parameters& params)
-    // {
-    //     coroutine_handler ch{coroutine::current};
-    //     q.push(static_cast<int>(params[0]), ch);
-    //     return nullptr;
-    // }
-    // static php::value consume(php::parameters& params)
-    // {
-    //     coroutine_handler ch{coroutine::current};
-    //     auto x = q.pop(ch);
-    //     if(x) {
-    //         return x.value();
-    //     }else{
-    //         return nullptr;
-    //     }
-    // }
     php::value select(php::parameters& params)
     {
         std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
@@ -61,6 +53,7 @@ namespace flame {
         ext
             .function<init>("flame\\init", {
                 {"process_name", php::TYPE::STRING},
+                {"options", php::TYPE::ARRAY, false, true},
             })
             .function<go>("flame\\go", {
                 {"coroutine", php::TYPE::CALLABLE},
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 8dfaa81..bd609d9 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -25,7 +25,7 @@ namespace flame
     {
         auto co = std::make_shared<coroutine>(std::move(fn));
         // 需要即时保存 PHP 堆栈
-        co->php_.current_execute_data = execute_data == nullptr ? gcontroller->core_execute_data : execute_data;
+        co->php_.current_execute_data = execute_data;
         // 事实上的协程启动会稍后
         boost::asio::post(gcontroller->context_x, [co/*, ag = std::move(ag)*/] {
             // co->c1_ = boost::context::callcc([co] (auto &&cc) {
@@ -33,6 +33,12 @@ namespace flame
                 auto work = boost::asio::make_work_guard(gcontroller->context_x);
                 // 启动进入协程
                 coroutine::current = co;
+                if(co->php_.current_execute_data == nullptr)
+                {
+                    // 若 run 还未执行时, core_execute_data 还没有值;
+                    // 故赋值延迟到此处进行
+                    co->php_.current_execute_data = gcontroller->core_execute_data;
+                }
                 zend_vm_stack_init();
                 co->c2_ = std::move(cc);
                 // 协程运行
@@ -67,11 +73,13 @@ namespace flame
         c1_ = std::move(c1_).resume();
     }
     coroutine_handler::coroutine_handler()
-        : co_(nullptr) 
+        : co_(nullptr)
+        , er_(nullptr)
     {
     }
     coroutine_handler::coroutine_handler(std::shared_ptr<coroutine> co)
         : co_(co)
+        , er_(nullptr)
     {
     }
     coroutine_handler::~coroutine_handler()
@@ -91,9 +99,9 @@ namespace flame
     {
         return co_ != nullptr;
     }
-    void coroutine_handler::operator()(const boost::system::error_code &e, std::size_t n)
+    void coroutine_handler::operator() (const boost::system::error_code &e, std::size_t n)
     {
-        if(error) *error = e;
+        if(er_) *er_ = e;
         co_->len_ = n;
 
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
@@ -101,7 +109,7 @@ namespace flame
     }
     coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e)
     {
-        error = &e;
+        er_ = &e;
         return *this;
     }
     void coroutine_handler::resume()
diff --git a/src/coroutine.h b/src/coroutine.h
index b91127c..4de9e88 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -13,7 +13,6 @@ namespace flame {
 			zend_class_entry *scope;
 			zend_execute_data *current_execute_data;
 		};
-		static coroutine::php_context_t   php_context;
 		// 当前协程
 		static std::shared_ptr<coroutine> current;
 		static void save_context(php_context_t &ctx);
@@ -47,7 +46,7 @@ namespace flame {
 		coroutine_handler& operator [](boost::system::error_code& e);
 		void resume();
 		void suspend();
-		boost::system::error_code* error;
+		boost::system::error_code* er_;
 		std::shared_ptr<coroutine> co_;
 
 		friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
diff --git a/src/log/log.cpp b/src/log/log.cpp
new file mode 100644
index 0000000..6ced8c2
--- /dev/null
+++ b/src/log/log.cpp
@@ -0,0 +1,91 @@
+#include "../controller.h"
+#include "log.h"
+#include "../time/time.h"
+
+namespace flame::log
+{
+    static std::string LEVEL_S[] = {
+        "TRACE",
+        "DEBUG",
+        "INFO",
+        "WARNING",
+        "ERROR",
+        "FATAL",
+    };
+    static std::map<std::string, int> LEVEL_I {};
+    enum {
+        LEVEL_TRACE,
+        LEVEL_DEBUG,
+        LEVEL_INFO,
+        LEVEL_WARNING,
+        LEVEL_ERROR,
+        LEVEL_FATAL,
+    };
+    static int level = 0;
+    
+    
+    static void write_ex(int lv, php::parameters& params)
+    {
+        if(lv < level) return;
+        std::cout << '[' << time::iso() << "] (";
+        std::cout << LEVEL_S[lv];
+        std::cout << "] ";
+        for (int i = 0; i < params.size(); ++i)
+        {
+            std::cout << ' ' << params[i].ptr();
+        }
+    }
+    static php::value trace(php::parameters &params)
+    {
+        write_ex(LEVEL_TRACE, params);
+        return nullptr;
+    }
+    static php::value debug(php::parameters &params)
+    {
+        write_ex(LEVEL_DEBUG, params);
+        return nullptr;
+    }
+    static php::value info(php::parameters &params)
+    {
+        write_ex(LEVEL_INFO, params);
+        return nullptr;
+    }
+    static php::value warning(php::parameters &params)
+    {
+        write_ex(LEVEL_WARNING, params);
+        return nullptr;
+    }
+    static php::value error(php::parameters &params)
+    {
+        write_ex(LEVEL_ERROR, params);
+        return nullptr;
+    }
+    static php::value fatal(php::parameters &params)
+    {
+        write_ex(LEVEL_FATAL, params);
+        return nullptr;
+    }
+
+    void declare(php::extension_entry &ext)
+    {
+        gcontroller->on_init([] (const php::array& options) {
+            if(options.exists("level"))
+            {
+                level = options.get("level").to_integer();
+            }
+            else
+            {
+                level = 0;
+            }
+        });
+        ext
+            .function<trace>("flame\\log\\trace")
+            .function<debug>("flame\\log\\debug")
+            .function<info>("flame\\log\\info")
+            .function<warning>("flame\\log\\warn")
+            .function<warning>("flame\\log\\warning")
+            .function<error>("flame\\log\\error")
+            .function<fatal>("flame\\log\\fail")
+            .function<fatal>("flame\\log\\fatal");
+    }
+}
\ No newline at end of file
diff --git a/src/log/log.h b/src/log/log.h
new file mode 100644
index 0000000..8801bc2
--- /dev/null
+++ b/src/log/log.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::log
+{
+    void declare(php::extension_entry &ext);
+} // namespace flame::log
+
diff --git a/src/mongodb/mongodb.cpp b/src/mongodb/mongodb.cpp
index 84311d9..16b99e0 100644
--- a/src/mongodb/mongodb.cpp
+++ b/src/mongodb/mongodb.cpp
@@ -13,7 +13,7 @@ namespace flame::mongodb
 {
     void declare(php::extension_entry &ext)
     {
-        gcontroller->on_init([]() {
+        gcontroller->on_init([] (const php::array& options) {
                 mongoc_init();
             })
             ->on_stop([]() {
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 934ad8d..cc1c184 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -12,7 +12,7 @@ namespace flame::mysql {
     void declare(php::extension_entry &ext)
     {
         gcontroller
-            ->on_init([]()
+            ->on_init([] (const php::array& options)
             {
                 mysql_library_init(0, nullptr, nullptr);
             })
diff --git a/src/tcp/tcp.cpp b/src/tcp/tcp.cpp
index 4735c00..222de2f 100644
--- a/src/tcp/tcp.cpp
+++ b/src/tcp/tcp.cpp
@@ -8,7 +8,7 @@ namespace flame::tcp {
 	static std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
 	void declare(php::extension_entry& ext)
     {
-		gcontroller->on_init([] ()
+		gcontroller->on_init([] (const php::array& options)
         {
             resolver_.reset(new boost::asio::ip::tcp::resolver(gcontroller->context_x));
         })->on_stop([] ()
diff --git a/test/exception_1.php b/test/exception_1.php
index 0be768e..1f97980 100644
--- a/test/exception_1.php
+++ b/test/exception_1.php
@@ -6,5 +6,4 @@
     throw new Exception("Aaaaaaa");
 });
 
-
 flame\run();

From dede09417aed5c966248bf352ba116701efcb258 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 28 Nov 2018 17:33:21 +0800
Subject: [PATCH 013/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?=
 =?UTF-8?q?=E5=A4=9A=E8=BF=9B=E7=A8=8B,=E6=97=A5=E5=BF=97=E9=87=8D?=
 =?UTF-8?q?=E5=AE=9A=E5=90=91=E9=87=8D=E6=9E=84;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp               |  49 ++++++-------
 src/controller.h                 |   4 +-
 src/controller_master.cpp        | 120 ++++++++++++++++++++-----------
 src/controller_master.h          |  15 ++--
 src/core.cpp                     |  34 +--------
 src/core.h                       |   2 +-
 src/flame.cpp                    |  85 ++++++++++++++++++----
 src/kafka/_consumer.cpp          |   2 +-
 src/mongodb/_connection_base.cpp |   2 +-
 src/queue.h                      |   6 +-
 src/tcp/socket.cpp               |   2 -
 src/vendor.h                     |   2 +-
 test/worker_1.php                |  12 ++++
 13 files changed, 208 insertions(+), 127 deletions(-)
 create mode 100644 test/worker_1.php

diff --git a/src/controller.cpp b/src/controller.cpp
index 2a72257..03b7ca1 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -11,7 +11,21 @@ namespace flame {
 		: type(process_type::UNKNOWN)
 		, env(boost::this_process::environment())
 		, status(STATUS_UNKNOWN) {
-
+		// FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
+		if (env.count("FLAME_CUR_WORKER") > 0)
+		{
+			type = process_type::WORKER;
+		}
+		else if (env.count("FLAME_MAX_WORKERS") > 0)
+		{
+			worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
+			worker_size = std::min(std::max((int)worker_size, 1), 512);
+			type = process_type::MASTER;
+		} 
+		else
+		{
+			type = process_type::WORKER;
+		}
 		mthread_id = std::this_thread::get_id();
 	}
 	controller *controller::on_init(std::function<void (const php::array &options)> fn)
@@ -30,31 +44,18 @@ namespace flame {
 		{
 			fn(options);
 		}
-		if(options.exists("worker"))
-		{
-			worker_size = std::min( std::max( (int)options.get("worker").to_integer(), 1), 256 );
-			// 使用此环境变量区分父子进程
-			if (env.count("FLAME_PROCESS_WORKER") > 0)
-			{
-				php::callable("cli_set_process_title").call({title + " (flame/" + env["FLAME_PROCESS_WORKER"].to_string() + ")"});
-				type = process_type::WORKER;
-				worker_.reset(new controller_worker());
-				worker_->initialize(options);
-			}
-			else
-			{
-				php::callable("cli_set_process_title").call({title + " (flame/m)"});
-				type = process_type::MASTER;
-				master_.reset(new controller_master());
-				master_->initialize(options);
-			}
-		}
-		else
+		if(type == process_type::WORKER)
 		{
-			php::callable("cli_set_process_title").call({title + " (flame/w)"});
-			type = process_type::WORKER;
+			std::string index = env["FLAME_CUR_WORKER"].to_string();
+			if(index.empty()) index = "w";
+			php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
 			worker_.reset(new controller_worker());
 			worker_->initialize(options);
+		}else/* if(type == process_type::MASTER)*/
+		{
+			php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
+			master_.reset(new controller_master());
+			master_->initialize(options);
 		}
 	}
 
@@ -63,7 +64,7 @@ namespace flame {
 		{
 			worker_->run();
 		}
-		else
+		else /* if(type == process_type::MASTER)*/
 		{
 			master_->run();
 		}
diff --git a/src/controller.h b/src/controller.h
index c45a742..f9c239d 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -1,9 +1,9 @@
 #pragma once
 #include "vendor.h"
-#include "controller_master.h"
-#include "controller_worker.h"
 
 namespace flame {
+	class controller_master;
+	class controller_worker;
 	class controller {
 	public:
 		boost::asio::io_context context_x;
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 22896d3..e13447c 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -4,7 +4,10 @@
 namespace flame
 {
     controller_master::controller_master()
-        : signal_(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2)
+        : signal_(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2))
+        , worker_(gcontroller->worker_size)
+        , sbuf_(gcontroller->worker_size)
+        , ebuf_(gcontroller->worker_size)
     {
 
     }
@@ -19,10 +22,6 @@ namespace flame
             ofpath_ = "stdout";
         }
         reload_output();
-        // 主进程实际上直接运行
-        run();
-        // 直接退出
-        exit(0);
     }
     static std::string start_command_line()
     {
@@ -39,62 +38,91 @@ namespace flame
     {
         ++count_;
         std::string cmd = start_command_line();
-
         boost::process::environment env = gcontroller->env;
-        env["FLAME_PROCESS_WORKER"] = std::to_string(i + 1);
-        worker_[i] = boost::process::child(cmd, env, group_, gcontroller->context_x,
-            boost::process::std_out > pipe_[i],
-            boost::process::std_err > pipe_[i],
+        env["FLAME_CUR_WORKER"] = std::to_string(i + 1);
+
+        worker_[i].reset( new boost::process::child(cmd, env, group_, gcontroller->context_x,
+            boost::process::std_out > *sout_[i],
+            boost::process::std_err > *eout_[i],
+            // boost::process::std_out > boost::process::null,
             boost::process::on_exit = [this, i](int exit_code, const std::error_code &error) {
                 if (error.value() == static_cast<int>(std::errc::no_child_process))
+                {
                     return;
+                }
                 if (exit_code != 0)
                 {
                     // 日志记录
                     int sec = std::rand() % 3 + 2;
-                    boost::asio::async_write(pipe_[i],
-                        boost::asio::buffer((boost::format("(FATAL) worker unexpected exit, restart in %2%s ...") % sec).str()),
-                        [this, i, sec](const boost::system::error_code &error, std::size_t nsent) {
-                            auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
-                            tm->async_wait([this, tm, i](const boost::system::error_code &error) {
-                                if (error)
-                                {
-                                    std::cerr << "(FATAL) failed to restart worker: (" << error.value() << ") " << error.message() << std::endl;
-                                }
-                                else
-                                {
-                                    spawn_worker(i);
-                                }
-                            });
-                        });
+                    std::cerr << "(FATAL) worker unexpected exit, restart in " << sec << " ...\n";
+
+                    auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
+                    tm->async_wait([this, tm, i](const boost::system::error_code &error) {
+                        if (error)
+                        {
+                            std::cerr << "(FATAL) failed to restart worker: (" << error.value() << ") " << error.message() << std::endl;
+                        }
+                        else
+                        {
+                            spawn_worker(i);
+                        }
+                    });
                 }
                 else
                 {
-                    pipe_[i].close();
+                    sout_[i]->close();
+                    eout_[i]->close();
                 }
-            });
+            }) );
         
     }
-    void controller_master::redirect_output(int i)
+    void controller_master::redirect_sout(int i)
     {
-        boost::asio::async_read_until(pipe_[i], buff_[i], '\n', [this, i] (const boost::system::error_code& error, std::size_t nread)
-        {
-            if(error == boost::asio::error::operation_aborted) 
+        boost::asio::async_read_until(*sout_[i], sbuf_[i], '\n', [this, i](const boost::system::error_code &error, std::size_t nread) {
+            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
             {
-
+                // std::cerr << "(INFO) IGNORE sout\n";
             }
             else if(error)
             {
-                std::cerr << "(ERROR) failed to read from worker process\n";
+                std::cerr << "(ERROR) failed to read from worker process: (" << error.value() << ") " << error.message() << std::endl;
             }
             else
             {
-                for (auto x = boost::asio::buffer_sequence_begin(buff_[i].data()); x != boost::asio::buffer_sequence_end(buff_[i].data()); ++x)
+                auto y = sbuf_[i].data();
+                for (auto x = boost::asio::buffer_sequence_begin(y); x != boost::asio::buffer_sequence_end(y); ++x)
                 {
                     offile_->write((char*)x->data(), x->size());
                 }
-                buff_[i].consume(nread);
-                redirect_output(i);
+                offile_->flush();
+                sbuf_[i].consume(nread);
+
+                redirect_sout(i);
+            }
+        });
+    }
+    void controller_master::redirect_eout(int i)
+    {
+        boost::asio::async_read_until(*eout_[i], ebuf_[i], '\n', [this, i](const boost::system::error_code &error, std::size_t nread) {
+            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
+            {
+                // std::cerr << "(INFO) IGNORE eout\n";
+            }
+            else if (error)
+            {
+                std::cerr << "(ERROR) failed to read from worker process: (" << error.value() << ") " << error.message() << std::endl;
+            }
+            else
+            {
+                auto y = ebuf_[i].data();
+                for (auto x = boost::asio::buffer_sequence_begin(y); x != boost::asio::buffer_sequence_end(y); ++x)
+                {
+                    offile_->write((char *)x->data(), x->size());
+                }
+                offile_->flush();
+                ebuf_[i].consume(nread);
+
+                redirect_eout(i);
             }
         });
     }
@@ -105,19 +133,25 @@ namespace flame
         // 1. 新增环境变量及命令行参数, 启动子进程;
         for(int i=0;i<gcontroller->worker_size;++i)
         {
-            pipe_.emplace_back(gcontroller->context_x);
+            sout_.push_back(std::make_unique<boost::process::async_pipe>(gcontroller->context_x));
+            eout_.push_back(std::make_unique<boost::process::async_pipe>(gcontroller->context_x));
             spawn_worker(i);
-            redirect_output(i);
+            redirect_sout(i);
+            redirect_eout(i);
         }
         // 2. 监听信号进行日志重载或停止
-        signal_.async_wait([this] (const boost::system::error_code &error, int sig) {
+        signal_->async_wait([this] (const boost::system::error_code &error, int sig) {
             if(sig == SIGUSR2)
             {
                 reload_output();
             }
             else
             {
-                signal_.clear();
+                // 信号的默认处理: 转给所有子进程
+                for(auto i=worker_.begin(); i!=worker_.end(); ++i)
+                {
+                    ::kill( (*i)->id(), sig );
+                }
             }
         });
         // 3. 启动运行
@@ -126,7 +160,7 @@ namespace flame
         });
         gcontroller->context_x.run();
         // 4. 等待工作线程结束
-        signal_.clear();
+        signal_.reset();
         thread_.join();
         // 5. 等待工作进程结束
         std::error_code error;
@@ -141,12 +175,12 @@ namespace flame
             offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
             // 文件打开失败时不会抛出异常，需要额外的状态检查
 			if(!(*offile_)) {
-                std::clog << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
+                std::cout << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
             }else{
                 return;
             }
 		}
         
-        offile_.reset(&std::clog, boost::null_deleter());
+        offile_.reset(&std::cout, boost::null_deleter());
     }
 }
\ No newline at end of file
diff --git a/src/controller_master.h b/src/controller_master.h
index 796b424..727709e 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -10,11 +10,13 @@ namespace flame
 		void run();
 
     private:
-		std::vector<boost::process::child> worker_;
-        std::vector<boost::process::async_pipe> pipe_;
-        std::vector<boost::asio::streambuf> buff_;
-		boost::process::group               group_;
-		boost::asio::signal_set            signal_;
+		std::vector<std::unique_ptr<boost::process::child>>    worker_;
+        std::vector<std::unique_ptr<boost::process::async_pipe>> sout_;
+        std::vector<std::unique_ptr<boost::process::async_pipe>> eout_;
+        std::vector<boost::asio::streambuf> sbuf_;
+        std::vector<boost::asio::streambuf> ebuf_;
+        boost::process::group               group_;
+		std::unique_ptr<boost::asio::signal_set> signal_;
 		std::thread                        thread_;
         std::size_t                        count_;
 
@@ -23,6 +25,7 @@ namespace flame
 
         void spawn_worker(int i);
         void reload_output();
-        void redirect_output(int i);
+        void redirect_sout(int i);
+        void redirect_eout(int i);
     };
 }
\ No newline at end of file
diff --git a/src/core.cpp b/src/core.cpp
index fdc45a8..79d18a1 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -5,30 +5,7 @@
 #include "queue.h"
 #include "mutex.h"
 
-namespace flame {
-    static php::value init(php::parameters& params) {
-        php::array options(0);
-        if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
-            options = params[1];
-        }
-        gcontroller->core_execute_data = EG(current_execute_data);
-        gcontroller->initialize(params[0], options);
-        return nullptr;
-    }
-    static php::value go(php::parameters& params) {
-        php::callable fn = params[0];
-        coroutine::start(fn);
-        return nullptr;
-    }
-    static php::value run(php::parameters& params) {
-        if(gcontroller->status & controller::STATUS_INITIALIZED) {
-            gcontroller->core_execute_data = EG(current_execute_data);
-            gcontroller->run();
-        }else{
-            throw php::exception(zend_ce_type_error, "failed to run flame: not yet initialized (forget to call 'flame\\init()' ?)");
-        }
-        return nullptr;
-    }
+namespace flame::core {
     php::value select(php::parameters& params)
     {
         std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
@@ -49,16 +26,7 @@ namespace flame {
         return mm[q];
     }
     void declare(php::extension_entry &ext) {
-        gcontroller.reset(new controller());
         ext
-            .function<init>("flame\\init", {
-                {"process_name", php::TYPE::STRING},
-                {"options", php::TYPE::ARRAY, false, true},
-            })
-            .function<go>("flame\\go", {
-                {"coroutine", php::TYPE::CALLABLE},
-            })
-            .function<run>("flame\\run")
             .function<select>("flame\\select");
 
         queue::declare(ext);
diff --git a/src/core.h b/src/core.h
index e761a97..deb06bc 100644
--- a/src/core.h
+++ b/src/core.h
@@ -1,7 +1,7 @@
 #pragma once
 #include "vendor.h"
 
-namespace flame {
+namespace flame::core {
     void declare(php::extension_entry& ext);
     php::value select(php::parameters &params);
 }
\ No newline at end of file
diff --git a/src/flame.cpp b/src/flame.cpp
index cdc19b9..904a704 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -1,4 +1,8 @@
 #include "vendor.h"
+#include "controller.h"
+#include "controller_master.h"
+#include "controller_worker.h"
+#include "coroutine.h"
 #include "core.h"
 #include "os/os.h"
 #include "time/time.h"
@@ -9,6 +13,41 @@
 #include "rabbitmq/rabbitmq.h"
 #include "tcp/tcp.h"
 
+namespace flame 
+{
+    static php::value init(php::parameters& params)
+    {
+        php::array options(0);
+        if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY))
+        {
+            options = params[1];
+        }
+        gcontroller->core_execute_data = EG(current_execute_data);
+        gcontroller->initialize(params[0], options);
+        return nullptr;
+    }
+    static php::value go(php::parameters& params)
+    {
+        php::callable fn = params[0];
+        coroutine::start(fn);
+        return nullptr;
+    }
+    static php::value fake_go(php::parameters& params)
+    {
+        return nullptr;
+    }
+    static php::value run(php::parameters& params) {
+        if(gcontroller->status & controller::STATUS_INITIALIZED)
+        {
+            gcontroller->core_execute_data = EG(current_execute_data);
+            gcontroller->run();
+        }else{
+            throw php::exception(zend_ce_type_error, "failed to run flame: not yet initialized (forget to call 'flame\\init()' ?)");
+        }
+        return nullptr;
+    }
+}
+
 extern "C"
 {
     ZEND_DLEXPORT zend_module_entry *get_module()
@@ -33,18 +72,40 @@ extern "C"
             .desc({"vendor/librdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S});
 
-        flame::declare(ext);
-        flame::os::declare(ext);
-        flame::time::declare(ext);
-        flame::mysql::declare(ext);
-        flame::redis::declare(ext);
-        flame::mongodb::declare(ext);
-        flame::kafka::declare(ext);
-        flame::rabbitmq::declare(ext);
-        flame::tcp::declare(ext);
-        // flame::udp::declare(ext);
-        // flame::http::declare(ext);
-        // flame::log::declare(ext);
+        flame::gcontroller.reset(new flame::controller());
+        ext
+            .function<flame::init>("flame\\init",
+            {
+                {"process_name", php::TYPE::STRING},
+                {"options", php::TYPE::ARRAY, false, true},
+            })
+            .function<flame::run>("flame\\run");
+        if(flame::gcontroller->type == flame::controller::process_type::WORKER)
+        {
+            ext.function<flame::go>("flame\\go",
+            {
+                {"coroutine", php::TYPE::CALLABLE},
+            });
+            flame::core::declare(ext);
+            flame::os::declare(ext);
+            flame::time::declare(ext);
+            flame::mysql::declare(ext);
+            flame::redis::declare(ext);
+            flame::mongodb::declare(ext);
+            flame::kafka::declare(ext);
+            flame::rabbitmq::declare(ext);
+            flame::tcp::declare(ext);
+            // flame::udp::declare(ext);
+            // flame::http::declare(ext);
+            // flame::log::declare(ext);
+        }
+        else
+        {
+            ext.function<flame::fake_go>("flame\\go",
+            {
+                {"coroutine", php::TYPE::CALLABLE},
+            });
+        }
         return ext;
     }
 };
\ No newline at end of file
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index 6d16633..d73e3c0 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -64,7 +64,7 @@ namespace flame::kafka
         if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
         {
             throw php::exception(zend_ce_exception,
-                (boost::format() % err % rd_kafka_err2str(err)).str(),
+                (boost::format("failed to subcribe Kafka topics: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
                 err);
         }
     }
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index eeac3e2..741dcc6 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -29,7 +29,7 @@ namespace flame::mongodb
         if(!rok)
         {
             throw php::exception(zend_ce_exception,
-                    (boost::format("failed to execute MongoDB command: (%1%) %1%") % err->message % err->code).str(),
+                    (boost::format("failed to execute MongoDB command: (%1%) %1%") % err->code % err->message).str(),
                     err->code);
         }
         else if(bson_has_field(rep.get(), "cursor"))
diff --git a/src/queue.h b/src/queue.h
index 625e824..680da61 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -4,6 +4,10 @@
 
 namespace flame
 {
+    namespace core
+    {
+        php::value select(php::parameters &params);
+    }
     class queue: public php::class_base 
     {
     public:
@@ -15,6 +19,6 @@ namespace flame
     private:
         std::shared_ptr<coroutine_queue<php::value>> q_;
 
-        friend php::value select(php::parameters &params);
+        friend php::value core::select(php::parameters &params);
     };
 }
\ No newline at end of file
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
index ee9aef5..1b6748a 100644
--- a/src/tcp/socket.cpp
+++ b/src/tcp/socket.cpp
@@ -41,11 +41,9 @@ namespace flame::tcp {
         {
 			std::string delim = params[0];
             len = boost::asio::async_read_until(socket_, buffer_, delim, ch[err]);
-            std::cout << "len: " << len << std::endl;
         }
         else if (params[0].typeof(php::TYPE::INTEGER)) // 3. 读取指定长度
         {
-            std::cout << "INTEGER\n";
 			std::size_t want = params[0].to_integer();
 			if(buffer_.size() >= want) {
                 len = want;
diff --git a/src/vendor.h b/src/vendor.h
index c7549c3..e5d3c1b 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -32,8 +32,8 @@
 #include <boost/format.hpp>
 #include <boost/program_options.hpp>
 #include <boost/process.hpp>
+#include <boost/process/async.hpp>
 #include <boost/logic/tribool.hpp>
-#include <boost/interprocess/ipc/message_queue.hpp>
 #include <boost/asio.hpp>
 #include <boost/asio/ssl.hpp>
 #include <boost/asio/coroutine.hpp>
diff --git a/test/worker_1.php b/test/worker_1.php
new file mode 100644
index 0000000..f6b9916
--- /dev/null
+++ b/test/worker_1.php
@@ -0,0 +1,12 @@
+<?php
+flame\init("worker_1", [
+    // "logger" => "/tmp/output.txt",
+]);
+
+flame\go(function() {
+    flame\time\sleep(1000);
+    echo getenv("FLAME_CUR_WORKER").": abc\n";
+    flame\time\sleep(1000);
+});
+
+flame\run();

From aea7be24b63c0824b6e61c8627c73f9afffdaca8 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 29 Nov 2018 12:56:07 +0800
Subject: [PATCH 014/146] =?UTF-8?q?=E5=A4=9A=E8=BF=9B=E7=A8=8B=E7=9A=84?=
 =?UTF-8?q?=E5=B9=B3=E6=BB=91=E5=90=AF=E5=81=9C=E5=8F=AF=E8=83=BD=E5=8F=8A?=
 =?UTF-8?q?=E9=80=9A=E7=9F=A5;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php              | 16 ++++++++--
 src/controller.cpp        |  5 ++-
 src/controller.h          |  2 ++
 src/controller_master.cpp | 34 +++++++++-----------
 src/controller_worker.cpp | 22 ++++++++++---
 src/controller_worker.h   |  3 +-
 src/coroutine.cpp         |  3 +-
 src/flame.cpp             | 65 +++++++++++++++++++++++++++++++++------
 src/log/log.cpp           |  8 ++---
 src/rabbitmq/consumer.cpp |  5 ++-
 test/exception_1.php      | 19 ++++++++++--
 11 files changed, 132 insertions(+), 50 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index 23cc4af..46ffa01 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -7,8 +7,8 @@
  * 初始化框架, 设置进程名称及相关配置;
  * @param string $process_name 进程名称
  * @param array  $options 选项配置, 目前可用如下:
- *  * `worker` - 工作进程仓库
- *  * `logger` - 日志输出重定向目标文件(完整路径);
+ *  * `logger` - 日志输出重定向目标文件(完整路径, 若不提供使用标准输出); 
+ *      向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
  */
 function init($process_name, $options = []) {}
 /**
@@ -24,6 +24,18 @@ function run() {}
  * @return 若所有通道已关闭, 返回 null; 否则返回一个有数据的通道, 即: 可以无等待 pop()
  */
 function select(channel $q1, $q2/*, ...*/):channel {}
+/**
+ * 监听框架的通知
+ * @param string $event 目前消息存在以下两种:
+ *  * "exception" - 未捕获的异常通知;
+ *  * "exit" - 退出消息, 一般用于平滑停止各种服务;
+ * @param callable 回调函数
+ */
+function on(string $event, callable $cb) {}
+/**
+ * 用于在用户异常处理流程中退出整个框架
+ */
+function quit() {}
 /**
  * 协程型队列
  */
diff --git a/src/controller.cpp b/src/controller.cpp
index 03b7ca1..01d71e0 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -10,7 +10,9 @@ namespace flame {
 	controller::controller()
 		: type(process_type::UNKNOWN)
 		, env(boost::this_process::environment())
-		, status(STATUS_UNKNOWN) {
+		, status(STATUS_UNKNOWN)
+		, cbmap(new std::multimap<std::string, php::callable>())
+	{
 		// FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
 		if (env.count("FLAME_CUR_WORKER") > 0)
 		{
@@ -68,6 +70,7 @@ namespace flame {
 		{
 			master_->run();
 		}
+		delete cbmap;
 		// 运行完毕
 		for (auto fn : stop_cb)
 		{
diff --git a/src/controller.h b/src/controller.h
index f9c239d..b39f6e3 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -24,6 +24,8 @@ namespace flame {
 		int status;
 		std::size_t    worker_size;
 		std::thread::id mthread_id;
+		// 防止 PHP 提前回收, 使用堆容器
+		std::multimap<std::string, php::callable>* cbmap;
 	private:
 		// 核心进程对象
 		std::unique_ptr<controller_master> master_;
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index e13447c..a9dd293 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -6,7 +6,9 @@ namespace flame
     controller_master::controller_master()
         : signal_(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2))
         , worker_(gcontroller->worker_size)
+        , sout_(gcontroller->worker_size)
         , sbuf_(gcontroller->worker_size)
+        , eout_(gcontroller->worker_size)
         , ebuf_(gcontroller->worker_size)
     {
 
@@ -36,6 +38,9 @@ namespace flame
     }
     void controller_master::spawn_worker(int i)
     {
+        sout_[i].reset( new boost::process::async_pipe(gcontroller->context_x) );
+        eout_[i].reset( new boost::process::async_pipe(gcontroller->context_x) );
+
         ++count_;
         std::string cmd = start_command_line();
         boost::process::environment env = gcontroller->env;
@@ -68,11 +73,8 @@ namespace flame
                         }
                     });
                 }
-                else
-                {
-                    sout_[i]->close();
-                    eout_[i]->close();
-                }
+                sout_[i]->close();
+                eout_[i]->close();
             }) );
         
     }
@@ -90,12 +92,9 @@ namespace flame
             else
             {
                 auto y = sbuf_[i].data();
-                for (auto x = boost::asio::buffer_sequence_begin(y); x != boost::asio::buffer_sequence_end(y); ++x)
-                {
-                    offile_->write((char*)x->data(), x->size());
-                }
-                offile_->flush();
+                *offile_ << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
                 sbuf_[i].consume(nread);
+                offile_->flush();
 
                 redirect_sout(i);
             }
@@ -115,13 +114,10 @@ namespace flame
             else
             {
                 auto y = ebuf_[i].data();
-                for (auto x = boost::asio::buffer_sequence_begin(y); x != boost::asio::buffer_sequence_end(y); ++x)
-                {
-                    offile_->write((char *)x->data(), x->size());
-                }
-                offile_->flush();
+                *offile_ << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
                 ebuf_[i].consume(nread);
-
+                offile_->flush();
+                
                 redirect_eout(i);
             }
         });
@@ -133,8 +129,6 @@ namespace flame
         // 1. 新增环境变量及命令行参数, 启动子进程;
         for(int i=0;i<gcontroller->worker_size;++i)
         {
-            sout_.push_back(std::make_unique<boost::process::async_pipe>(gcontroller->context_x));
-            eout_.push_back(std::make_unique<boost::process::async_pipe>(gcontroller->context_x));
             spawn_worker(i);
             redirect_sout(i);
             redirect_eout(i);
@@ -175,12 +169,12 @@ namespace flame
             offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
             // 文件打开失败时不会抛出异常，需要额外的状态检查
 			if(!(*offile_)) {
-                std::cout << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
+                std::clog << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
             }else{
                 return;
             }
 		}
         
-        offile_.reset(&std::cout, boost::null_deleter());
+        offile_.reset(&std::clog, boost::null_deleter());
     }
 }
\ No newline at end of file
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index c831428..c4b7592 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -4,6 +4,7 @@
 namespace flame
 {
     controller_worker::controller_worker()
+        : signal_(new boost::asio::signal_set(gcontroller->context_x, SIGTERM, SIGINT))
     {
 
     }
@@ -13,24 +14,35 @@ namespace flame
     }
     void controller_worker::run()
     {
+        // 1. 停止信号调用退出通知
+        signal_->async_wait([this] (const boost::system::error_code& error, int sig)
+        {
+            auto ft = gcontroller->cbmap->equal_range("exit");
+            for(auto i=ft.first; i!=ft.second; ++i)
+            {
+                i->second.call();
+            }
+            signal_.reset();
+        });
         // 工作线程的使用时随机的, 需要保持其一直存在
         auto work = boost::asio::make_work_guard(gcontroller->context_y);
         // 子进程的启动过程:
-        // 1. 启动线程池, 并使用线程池运行 context_y
+        // 2. 启动线程池, 并使用线程池运行 context_y
         thread_.resize(3);
         for (int i = 0; i < thread_.size(); ++i)
         {
-            thread_[i] = std::thread([this] {
+            thread_[i] = new std::thread([this] {
                 gcontroller->context_y.run();
             });
         }
-        // 2. 启动 context_x 运行
+        // 3. 启动 context_x 运行
         gcontroller->context_x.run();
-        // 3. 确认工作线程停止
+        // 4. 确认工作线程停止
         work.reset();
         for (int i = 0; i < thread_.size(); ++i)
         {
-            thread_[i].join();
+            thread_[i]->join();
+            delete thread_[i];
         }
     }
 }
\ No newline at end of file
diff --git a/src/controller_worker.h b/src/controller_worker.h
index b717696..438c70c 100644
--- a/src/controller_worker.h
+++ b/src/controller_worker.h
@@ -10,6 +10,7 @@ namespace flame
 		void run();
 
 	private:
-		std::vector<std::thread> thread_;
+		std::vector<std::thread*> thread_;
+		std::unique_ptr<boost::asio::signal_set> signal_;
 	};
 }
\ No newline at end of file
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index bd609d9..ab52e8d 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -33,8 +33,7 @@ namespace flame
                 auto work = boost::asio::make_work_guard(gcontroller->context_x);
                 // 启动进入协程
                 coroutine::current = co;
-                if(co->php_.current_execute_data == nullptr)
-                {
+                if(co->php_.current_execute_data == nullptr) {
                     // 若 run 还未执行时, core_execute_data 还没有值;
                     // 故赋值延迟到此处进行
                     co->php_.current_execute_data = gcontroller->core_execute_data;
diff --git a/src/flame.cpp b/src/flame.cpp
index 904a704..0572339 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -29,14 +29,50 @@ namespace flame
     static php::value go(php::parameters& params)
     {
         php::callable fn = params[0];
-        coroutine::start(fn);
+        coroutine::start(php::callable([fn] (php::parameters& params) -> php::value
+        {
+            php::value rv;
+            try{
+                rv = fn.call();
+            }
+            catch (const php::exception &ex)
+            {
+                int x = 0;
+                auto ft = gcontroller->cbmap->equal_range("exception");
+                for(auto i=ft.first; i!=ft.second; ++i)
+                {
+                    ++x;
+                    i->second.call({ex});
+                }
+                if(x==0)
+                {
+                    std::cerr << "[" << time::iso() << "] (FATAL) " << ex.what() << "\n";
+                    exit(-1);
+                }else
+                {
+                    std::cerr << "[" << time::iso() << "] (ERROR) " << ex.what() << "\n";
+                }
+            }
+            return rv;
+        }));
+        return nullptr;
+    }
+    static php::value fake_fn(php::parameters& params)
+    {
         return nullptr;
     }
-    static php::value fake_go(php::parameters& params)
+    static php::value on(php::parameters &params)
     {
+        std::string event = params[0].to_string();
+        if (!params[1].typeof(php::TYPE::CALLABLE))
+        {
+            throw php::exception(zend_ce_type_error, "callable is required", -1);
+        }
+        gcontroller->cbmap->insert({event, params[1]});
         return nullptr;
     }
-    static php::value run(php::parameters& params) {
+    static php::value run(php::parameters& params)
+    {
         if(gcontroller->status & controller::STATUS_INITIALIZED)
         {
             gcontroller->core_execute_data = EG(current_execute_data);
@@ -46,6 +82,11 @@ namespace flame
         }
         return nullptr;
     }
+    static php::value quit(php::parameters& params)
+    {
+        gcontroller->context_x.stop();
+        return nullptr;
+    }
 }
 
 extern "C"
@@ -82,10 +123,17 @@ extern "C"
             .function<flame::run>("flame\\run");
         if(flame::gcontroller->type == flame::controller::process_type::WORKER)
         {
-            ext.function<flame::go>("flame\\go",
+            ext
+            .function<flame::go>("flame\\go",
             {
                 {"coroutine", php::TYPE::CALLABLE},
-            });
+            })
+            .function<flame::on>("flame\\on",
+            {
+                {"event", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .function<flame::quit>("flame\\quit");
             flame::core::declare(ext);
             flame::os::declare(ext);
             flame::time::declare(ext);
@@ -101,10 +149,9 @@ extern "C"
         }
         else
         {
-            ext.function<flame::fake_go>("flame\\go",
-            {
-                {"coroutine", php::TYPE::CALLABLE},
-            });
+            ext
+            .function<flame::fake_fn>("flame\\go")
+            .function<flame::fake_fn>("flame\\on");
         }
         return ext;
     }
diff --git a/src/log/log.cpp b/src/log/log.cpp
index 6ced8c2..29eba4e 100644
--- a/src/log/log.cpp
+++ b/src/log/log.cpp
@@ -27,12 +27,12 @@ namespace flame::log
     static void write_ex(int lv, php::parameters& params)
     {
         if(lv < level) return;
-        std::cout << '[' << time::iso() << "] (";
-        std::cout << LEVEL_S[lv];
-        std::cout << "] ";
+        std::clog << '[' << time::iso() << "] (";
+        std::clog << LEVEL_S[lv];
+        std::clog << "] ";
         for (int i = 0; i < params.size(); ++i)
         {
-            std::cout << ' ' << params[i].ptr();
+            std::clog << ' ' << params[i].ptr();
         }
     }
     static php::value trace(php::parameters &params)
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index 46d26bb..1ed0f51 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -45,11 +45,10 @@ namespace flame::rabbitmq
         coroutine_queue<php::object> q(cc_->pf_);
         // 启动若干协程, 然后进行"并行>"消费
         int count = (int)std::ceil(cc_->pf_ / 2.0);
-        std::vector<std::shared_ptr<coroutine>> cos(count);
         for (int i = 0; i < count; ++i)
         {
             // 启动协程开始消费
-            cos[i] = coroutine::start(php::value([this, &q, &count, &ch, i] (php::parameters &params) -> php::value {
+            coroutine::start(php::value([this, &q, &count, &ch, i] (php::parameters &params) -> php::value {
                 coroutine_handler cch {coroutine::current};
                 while(auto x = q.pop(cch))
                 {
@@ -59,7 +58,7 @@ namespace flame::rabbitmq
                     }
                     catch(const php::exception& ex)
                     {
-                        std::clog << "[" << time::iso() << "] (ERROR) " << ex << std::endl;
+                        std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
                 if(--count == 0)
diff --git a/test/exception_1.php b/test/exception_1.php
index 1f97980..f74a289 100644
--- a/test/exception_1.php
+++ b/test/exception_1.php
@@ -1,9 +1,22 @@
 <?php
-flame\init("exception_1");
+flame\init("exception_1", [
+    "logger" => "/tmp/output.txt",
+]);
+
+function abc() {
+    // flame\time\sleep(1000);
+    throw new Exception("Aaaaaaa");
+}
 
 flame\go(function() {
-    flame\time\sleep(1000);
+    flame\time\sleep(5000);
+    echo "Abc\n";
     throw new Exception("Aaaaaaa");
 });
 
-flame\run();
+flame\on("exception", function($ex) {
+    // echo $ex, "\n";
+    flame\quit();
+});
+
+flame\run();
\ No newline at end of file

From b1f0dca22754059b6fae24bf7a9e9cfb55d55761 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 29 Nov 2018 15:25:37 +0800
Subject: [PATCH 015/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E5=8D=8F?=
 =?UTF-8?q?=E7=A8=8B=E5=85=A8=E9=83=A8=E7=BB=93=E6=9D=9F=E5=90=8E=E9=80=80?=
 =?UTF-8?q?=E5=87=BA=E6=A1=86=E6=9E=B6;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/coroutine.cpp | 7 +++++++
 src/coroutine.h   | 1 +
 2 files changed, 8 insertions(+)

diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index ab52e8d..04e1259 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -3,6 +3,7 @@
 
 namespace flame
 {
+    std::size_t coroutine::count = 0;
     // 当前协程
     std::shared_ptr<coroutine> coroutine::current;
     void coroutine::save_context(php_context_t &ctx)
@@ -23,6 +24,7 @@ namespace flame
     }
     std::shared_ptr<coroutine> coroutine::start(php::callable fn, /*std::vector<php::value> ag,*/ zend_execute_data *execute_data)
     {
+        ++coroutine::count;
         auto co = std::make_shared<coroutine>(std::move(fn));
         // 需要即时保存 PHP 堆栈
         co->php_.current_execute_data = execute_data;
@@ -45,6 +47,11 @@ namespace flame
                 // 协程运行完毕
                 zend_vm_stack_destroy();
                 coroutine::current = nullptr;
+                if(--coroutine::count)
+                {
+                    // 所有协程结束后退出
+                    gcontroller->context_x.stop();
+                }
                 return std::move(co->c2_);
             });
             co->resume();
diff --git a/src/coroutine.h b/src/coroutine.h
index 4de9e88..c897132 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -13,6 +13,7 @@ namespace flame {
 			zend_class_entry *scope;
 			zend_execute_data *current_execute_data;
 		};
+		static std::size_t count;
 		// 当前协程
 		static std::shared_ptr<coroutine> current;
 		static void save_context(php_context_t &ctx);

From 98f86f4f052378acb7a6557014d8e490b47fd3c4 Mon Sep 17 00:00:00 2001
From: halivor <hongliang186@aliyun.com>
Date: Thu, 29 Nov 2018 16:23:44 +0800
Subject: [PATCH 016/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90htt?=
 =?UTF-8?q?p=E5=AE=A2=E6=88=B7=E7=AB=AF=E9=87=8D=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

初步完成http客户端重构
---
 src/flame.cpp                 |   5 +-
 src/http/_connection_pool.cpp | 225 ++++++++++++++++++++++++++++++++++
 src/http/_connection_pool.h   |  31 +++++
 src/http/client.cpp           | 127 +++++++++++++++++++
 src/http/client.h             |  19 +++
 src/http/client_request.cpp   | 114 +++++++++++++++++
 src/http/client_request.h     |  16 +++
 src/http/client_response.cpp  |  52 ++++++++
 src/http/client_response.h    |  17 +++
 src/http/http.cpp             | 116 ++++++++++++++++++
 src/http/http.h               |  19 +++
 src/http/value_body.cpp       |  26 ++++
 src/http/value_body.h         |  43 +++++++
 src/http/value_body.ipp       |  30 +++++
 test/http_1.php               |  88 +++++++++++++
 15 files changed, 926 insertions(+), 2 deletions(-)
 create mode 100644 src/http/_connection_pool.cpp
 create mode 100644 src/http/_connection_pool.h
 create mode 100644 src/http/client.cpp
 create mode 100644 src/http/client.h
 create mode 100644 src/http/client_request.cpp
 create mode 100644 src/http/client_request.h
 create mode 100644 src/http/client_response.cpp
 create mode 100644 src/http/client_response.h
 create mode 100644 src/http/http.cpp
 create mode 100644 src/http/http.h
 create mode 100644 src/http/value_body.cpp
 create mode 100644 src/http/value_body.h
 create mode 100644 src/http/value_body.ipp
 create mode 100644 test/http_1.php

diff --git a/src/flame.cpp b/src/flame.cpp
index 0572339..251582e 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -12,6 +12,7 @@
 #include "kafka/kafka.h"
 #include "rabbitmq/rabbitmq.h"
 #include "tcp/tcp.h"
+#include "http/http.h"
 
 namespace flame 
 {
@@ -144,7 +145,7 @@ extern "C"
             flame::rabbitmq::declare(ext);
             flame::tcp::declare(ext);
             // flame::udp::declare(ext);
-            // flame::http::declare(ext);
+            flame::http::declare(ext);
             // flame::log::declare(ext);
         }
         else
@@ -155,4 +156,4 @@ extern "C"
         }
         return ext;
     }
-};
\ No newline at end of file
+};
diff --git a/src/http/_connection_pool.cpp b/src/http/_connection_pool.cpp
new file mode 100644
index 0000000..5184179
--- /dev/null
+++ b/src/http/_connection_pool.cpp
@@ -0,0 +1,225 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "value_body.h"
+#include "client_request.h"
+#include "client_response.h"
+#include "_connection_pool.h"
+#include "../url.h"
+
+namespace flame::http
+{
+    _connection_pool::_connection_pool(uint32_t cph)
+    : max_(cph), tm_(gcontroller->context_x), resolver_(gcontroller->context_x) {
+    }
+
+    _connection_pool::~_connection_pool() {
+    }
+    std::shared_ptr<tcp::socket>
+    _connection_pool::acquire(std::shared_ptr<url> url, int32_t timeout, coroutine_handler &ch) {
+        boost::system::error_code ec_;
+
+        // 超时预处理
+        boost::asio::steady_timer tmo(gcontroller->context_x);
+        auto cc = false;
+        tmo.expires_after(std::chrono::milliseconds(timeout));
+        tmo.async_wait([&cc, &ch, &ec_](const boost::system::error_code &ec) {
+            if(ec == boost::asio::error::operation_aborted)
+                return;
+            cc = true;
+            ec_ = ec;
+            ch.resume();
+        });
+
+        std::shared_ptr<tcp::socket> c;
+        auto type = url->schema + url->host + std::to_string(url->port);
+        while(cp_.count(type) > 0) {
+            auto it = cp_.find(type);
+            if(it != cp_.end()) {
+                auto s = it->second.first;
+                cp_.erase(it);
+                s->async_wait(tcp::socket::wait_write, ch[ec_]);
+                if(!ec_) {
+                    c = s;
+                    goto DONE;
+                }
+                // 若连接存在异常，直接释放
+                --ps_[type];
+            }
+        }
+
+        // 未获取到连接，且连接数达到最大
+        if(!c) {
+            if(ps_[type] >= max_) {
+                auto cb = await_[type].emplace_back([&c, &ch](std::shared_ptr<tcp::socket> s) {
+                    c = s;
+                    ch.resume();
+                });
+                ch.suspend();
+                if(cc) {
+                    for(auto i = await_[type].begin(); i != await_[type].end(); ++i ) {
+                        if(&(*i) == &cb) {
+                            await_[type].erase(i);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+DONE:
+        if(cc == true) {
+
+            throw php::exception(zend_ce_exception, "create connection timeout");
+        }
+        tmo.cancel();
+        if(c)
+            return c;
+
+        return create(url, timeout, ch);
+    }
+    std::shared_ptr<tcp::socket> _connection_pool::create(std::shared_ptr<url> u, int timeout, coroutine_handler& ch) {
+        boost::system::error_code ec_;
+        // 超时预处理
+        boost::asio::steady_timer tmo(gcontroller->context_x);
+        bool cc = false;
+        tmo.expires_after(std::chrono::milliseconds(timeout));
+        tmo.async_wait([&cc, &ch, &ec_] (const boost::system::error_code &ec) {
+            if(ec == boost::asio::error::operation_aborted) {
+                return;
+            }
+            cc = true;
+            ec_ = ec;
+            ch.resume();
+        });
+
+        tcp::resolver::results_type eps_;
+        auto c = std::make_shared<tcp::socket>(gcontroller->context_x);
+        auto type = u->schema + u->host + std::to_string(u->port);
+        ++ps_[type];
+        resolver_.async_resolve(
+            u->host, 
+            std::to_string(u->port), 
+            [&eps_, &ec_, &cc, &ch] (const boost::system::error_code& ec, tcp::resolver::results_type eps) {
+                if(cc){
+                    return ;
+                }
+                ec_ = ec;
+                eps_ = std::move(eps);
+                ch.resume();
+        });
+        ch.suspend();
+        if(ec_ || cc)
+            goto END;
+
+        boost::asio::async_connect(*c, eps_, [&cc, &ch, &ec_] (const boost::system::error_code& ec, const tcp::endpoint& ep) {
+            if(cc)
+                return ;
+            ec_ = ec;
+            ch.resume();
+        });
+        ch.suspend();
+END:
+        if(cc == true) {
+            --ps_[type];
+            throw php::exception(zend_ce_exception, "create new connection timeout");
+        } 
+        tmo.cancel();
+        if(ec_) {
+            --ps_[type];
+            throw php::exception(zend_ce_exception, "create new connection timeout");
+        }
+        return c;
+    }
+
+    php::value _connection_pool::execute(
+            std::string type,
+            std::shared_ptr<tcp::socket> c, 
+            boost::beast::http::message<true, value_body<true>> &ctr_, 
+            int32_t timeout, 
+            coroutine_handler &ch) {
+        boost::system::error_code ec_;
+        // 构建并发送请求
+        bool cc = false;
+        boost::asio::steady_timer tmo(gcontroller->context_x);
+        tmo.expires_after(std::chrono::milliseconds(timeout));
+        tmo.async_wait([&ch, &cc, &ec_](const boost::system::error_code &ec) {
+            if(ec == boost::asio::error::operation_aborted)
+                return;
+            cc = true;
+            ec_ = ec;
+            ch.resume();
+        });
+
+        boost::beast::http::async_write(*c, ctr_, [&cc, &ch, &ec_](const boost::system::error_code& ec, std::size_t n) {
+            if(cc == true) return ;
+            ec_ = ec;
+            ch.resume();
+        });
+        ch.suspend();
+        if(cc)
+            throw php::exception(zend_ce_exception, "failed to write request: timeout");
+        if(ec_)
+            throw php::exception(zend_ce_exception, (boost::format("failed to write request: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
+
+        // 构建并读取响应
+        auto res_ref = php::object(php::class_entry<client_response>::entry());
+        auto res_    = static_cast<client_response*>(php::native(res_ref));
+        boost::beast::flat_buffer b;
+        boost::beast::http::async_read(*c, b, res_->ctr_, [&ch, &cc, &ec_] (const boost::system::error_code& ec, std::size_t n) {
+            if(cc == true) return ;
+            ec_ = ec;
+            ch.resume();
+        });
+
+        ch.suspend();
+        if(cc)
+            throw php::exception(zend_ce_exception, "failed to read response: timeout");
+        if(ec_)
+            throw php::exception(zend_ce_exception, (boost::format("failed to read response: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
+        res_->build_ex();
+        tmo.cancel();
+        if(res_->ctr_.keep_alive() && ctr_.keep_alive())
+            release(type, c);
+        else {
+            --ps_[type];
+            release(type, nullptr);
+        }
+        return res_ref;
+    }
+    void _connection_pool::release(std::string type, std::shared_ptr<tcp::socket> s)
+    {
+        if (await_[type].empty()) {
+            // 无等待分配的请求
+            if(!s)
+                cp_.insert(std::make_pair(type, std::make_pair(s, std::chrono::steady_clock::now())));
+        } else {
+            // 立刻分配使用
+            std::function<void(std::shared_ptr<tcp::socket>)> cb = await_[type].front();
+            await_[type].pop_front();
+            cb(s);
+        }
+    }
+    void _connection_pool::sweep() {
+        tm_.expires_from_now(std::chrono::seconds(30));
+        tm_.async_wait([this] (const boost::system::error_code &ec) {
+            auto now = std::chrono::steady_clock::now();
+            if(ec) return; // 当前对象销毁时会发生对应的 abort 错误
+            for (auto i = cp_.begin(); i != cp_.end();) {
+                auto itvl = std::chrono::duration_cast<std::chrono::seconds>(now - i->second.second);
+                if(itvl.count() > 30) {
+                    --ps_[i->first];
+                    i = cp_.erase(i);
+                } else
+                    ++i;
+            }
+            for(auto i = await_.begin(); i != await_.end(); ) {
+                if(i->second.empty())
+                    i = await_.erase(i);
+                else
+                    ++i;
+            }
+            // 再次启动
+            sweep();
+        });
+    }
+}
+
diff --git a/src/http/_connection_pool.h b/src/http/_connection_pool.h
new file mode 100644
index 0000000..f5de63e
--- /dev/null
+++ b/src/http/_connection_pool.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "../url.h"
+
+namespace flame::http
+{
+    class _connection_pool : public std::enable_shared_from_this<_connection_pool>
+    {
+        public:
+            _connection_pool(uint32_t cph);
+            ~_connection_pool();
+            std::shared_ptr<tcp::socket> acquire(std::shared_ptr<url> url, int32_t timeout, coroutine_handler &ch);
+            std::shared_ptr<tcp::socket> create(std::shared_ptr<url> u, int timeout, coroutine_handler& ch);
+            php::value execute(std::string type, std::shared_ptr<tcp::socket> c, 
+                    boost::beast::http::message<true, value_body<true>> &ctr_, 
+                    int32_t timeout, 
+                    coroutine_handler &ch);
+            void release(std::string type, std::shared_ptr<tcp::socket> s);
+            void sweep();
+        private:
+
+            const std::uint32_t max_;
+
+            boost::asio::ip::tcp::resolver resolver_;
+            using time_t = std::chrono::time_point<std::chrono::steady_clock>;
+            std::multimap<std::string, std::pair<std::shared_ptr<tcp::socket>, time_t>> cp_; // connection_pool
+            std::map<std::string, int32_t> ps_; // pool_size
+            std::map<std::string, std::list<std::function<void(std::shared_ptr<boost::asio::ip::tcp::socket>)>>> await_;
+            boost::asio::steady_timer tm_;
+    };
+
+}
diff --git a/src/http/client.cpp b/src/http/client.cpp
new file mode 100644
index 0000000..5e86c64
--- /dev/null
+++ b/src/http/client.cpp
@@ -0,0 +1,127 @@
+#include "../coroutine.h"
+#include "client.h"
+#include "value_body.h"
+#include "client_request.h"
+#include "client_response.h"
+#include "_connection_pool.h"
+
+namespace flame::http {
+    void client::declare(php::extension_entry& ext) {
+        php::class_entry<client> class_client("flame\\http\\client");
+        class_client
+            .property({"connection_per_host", 4})
+            .method<&client::__construct>("__construct", {
+                {"options", php::TYPE::ARRAY, false, true},
+            })
+            .method<&client::exec>("exec", {
+                {"options", "flame\\http\\client_request"},
+            })
+            .method<&client::get>("get", {
+                {"url", php::TYPE::STRING},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            })
+            .method<&client::post>("post", {
+                {"url", php::TYPE::STRING},
+                {"body", php::TYPE::INTEGER},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            })
+            .method<&client::put>("put", {
+                {"url", php::TYPE::STRING},
+                {"body", php::TYPE::INTEGER},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            })
+            .method<&client::delete_>("delete", {
+                {"url", php::TYPE::STRING},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            });
+            ext.add(std::move(class_client));
+    }
+	client::client()
+    : cp_(std::make_shared<_connection_pool>(16)) {
+    }
+    php::value client::__construct(php::parameters& params) {
+        if(params.size() > 0) {
+            php::array opts = params[0];
+            if(opts.exists("connection_per_host")) {
+                int cph = opts.get("connection_per_host");
+                if(cph > 0 && cph < 512)
+                    cp_ = std::make_shared<_connection_pool>(cph);
+            }
+        }
+        cp_->sweep();
+        return nullptr;
+    }
+    php::value client::exec_ex(const php::object& req) {
+        coroutine_handler ch{coroutine::current};
+        auto req_ = static_cast<client_request*>(php::native(req));
+        req_->build_ex();
+
+        auto type    = req_->url_->schema + req_->url_->host + std::to_string(req_->url_->port);
+        auto timeout = static_cast<int>(req_->get("timeout"));
+        if(req_->url_->schema.compare("http") == 0) {
+            return cp_->execute(type, cp_->acquire(req_->url_, timeout, ch), req_->ctr_, timeout, ch);
+        } else if(req_->url_->schema.compare("https") == 0) {
+            return cp_->execute(type, cp_->acquire(req_->url_, timeout, ch), req_->ctr_, timeout, ch);
+        }
+        throw php::exception(zend_ce_exception, "request protocol not supported");
+    }
+    php::value client::exec(php::parameters& params) {
+        return exec_ex(params[0]);
+    }
+	php::value client::get(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method", "GET");
+        req.set("url", params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body", nullptr);
+        if(params.length() > 1) {
+            req.set("timeout", params[1]);
+        }else{
+            req.set("timeout", 3000);
+        }
+        return exec_ex(req);
+    }
+    php::value client::post(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method",        "POST");
+        req.set("url",        params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body",       params[1]);
+        if(params.length() > 2) {
+            req.set("timeout",params[2]);
+        }else{
+            req.set("timeout",     3000);
+        }
+        return exec_ex(req);
+    }
+    php::value client::put(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method",        "PUT");
+        req.set("url",        params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body",       params[1]);
+        if(params.length() > 2) {
+            req.set("timeout",params[2]);
+        }else{
+            req.set("timeout",     3000);
+        }
+        return exec_ex(req);
+    }
+    php::value client::delete_(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method",         "DELETE");
+        req.set("url",        params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body",         nullptr);
+        if(params.length() > 1) {
+            req.set("timeout", params[1]);
+        }else{
+            req.set("timeout", 3000);
+        }
+        return exec_ex(req);
+    }
+}
diff --git a/src/http/client.h b/src/http/client.h
new file mode 100644
index 0000000..732fe2a
--- /dev/null
+++ b/src/http/client.h
@@ -0,0 +1,19 @@
+#pragma once
+
+namespace flame::http {
+    class _connection_pool;
+    class client: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        client();
+        php::value __construct(php::parameters& params);
+        php::value exec(php::parameters& params);
+        php::value get(php::parameters& params);
+        php::value post(php::parameters& params);
+        php::value put(php::parameters& params);
+        php::value delete_(php::parameters& params);
+    private:
+        php::value exec_ex(const php::object& req);
+        std::shared_ptr<_connection_pool> cp_;
+    };
+}
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
new file mode 100644
index 0000000..3bd4904
--- /dev/null
+++ b/src/http/client_request.cpp
@@ -0,0 +1,114 @@
+#include "../coroutine.h"
+#include "value_body.h"
+#include "client_request.h"
+#include "http.h"
+
+namespace flame {
+namespace http {
+	void client_request::declare(php::extension_entry& ext) {
+		php::class_entry<client_request> class_client_request(
+			"flame\\http\\client_request");
+		class_client_request
+			.property({"timeout", 3000})
+			.property({"method", "GET"})
+			.property({"url", nullptr})
+			.property({"header", nullptr})
+			.property({"cookie", nullptr})
+			.property({"body", ""})
+			.method<&client_request::__construct>("__construct", {
+				{"url", php::TYPE::STRING},
+				{"body", php::TYPE::UNDEFINED, false, true},
+				{"timeout", php::TYPE::INTEGER, false, true},
+			})
+			.method<&client_request::to_string>("__toString");
+
+		ext.add(std::move(class_client_request));
+	}
+	php::value client_request::__construct(php::parameters& params) {
+		if (params.length() > 2) {
+			set("timeout", params[2]);
+		}else{
+			set("timeout", 3000);
+		}
+		if (params.length() > 1 && !params[1].empty()) {
+			set("body", params[1]);
+			set("method", "POST");
+		}else{
+			set("method", "GET");
+		}
+		set("url", params[0]);
+		set("header", php::array(0));
+		set("cookie", php::array(0));
+		return nullptr;
+	}
+	php::value client_request::to_string(php::parameters& params) {
+		std::stringstream os;
+		build_ex();
+		os << ctr_;
+		return os.str();
+	}
+	void client_request::build_ex() {
+		if(url_) return; // 防止重复 build
+		// 目标请求地址
+        php::string u = get("url", true);
+		if(!u.typeof(php::TYPE::STRING)) {
+			throw php::exception(zend_ce_exception, "request 'url' must be a string");
+		}
+
+		url_ = std::make_shared<url>(u);
+		if(!url_->port && !url_->schema.compare(0, 5, "https")) {
+			url_->port = 443;
+		}else if(!url_->port && !url_->schema.compare(0, 4, "http")) {
+			url_->port = 80;
+		}
+		// 消息容器
+		ctr_.method(boost::beast::http::string_to_verb(get("method").to_string()));
+		std::string target(url_->path.length() > 0? url_->path : "/");
+        std::string query;
+        for(auto it = url_->query.begin(); it != url_->query.end(); ++it) {
+            query.append(it->first).append("=").append(it->second).append("&");
+        }
+		if(query.size() > 0) {
+            query.erase(query.length() - 1);
+			target.append("?");
+			target.append(query);
+		}
+		ctr_.target(target);
+		// 头
+		php::array header = get("header");
+		for(auto i=header.begin(); i!=header.end(); ++i) {
+			php::string key = i->first;
+			php::string val = i->second;
+			val.to_string();
+			ctr_.set(boost::string_view(key.c_str(), key.size()), val);
+		}
+		if(ctr_.find(boost::beast::http::field::host) == ctr_.end()) {
+			ctr_.set(boost::beast::http::field::host, url_->host);
+		}
+		// COOKIE
+		php::array cookie = get("cookie");
+		php::buffer cookies;
+		for(auto i=cookie.begin(); i!=cookie.end(); ++i) {
+			php::string key = i->first;
+			php::string val = i->second.to_string();
+			val = php::url_encode(val.c_str(), val.size());
+			cookies.append(key);
+			cookies.push_back('=');
+			cookies.append(val);
+			cookies.push_back(';');
+			cookies.push_back(' ');
+		}
+		ctr_.set(boost::beast::http::field::cookie, php::string(std::move(cookies)));
+		php::string body = get("body");
+CTYPE_AGAIN:
+		auto ctype = ctr_.find(boost::beast::http::field::content_type);
+		if(ctype == ctr_.end()) {
+			ctr_.set(boost::beast::http::field::content_type, "application/x-www-form-urlencoded");
+			goto CTYPE_AGAIN;
+		}
+		if(body.empty()) return;
+		ctr_.body() = ctype_encode(ctype->value(), body);
+		ctr_.prepare_payload();
+	}
+}
+}
diff --git a/src/http/client_request.h b/src/http/client_request.h
new file mode 100644
index 0000000..7155511
--- /dev/null
+++ b/src/http/client_request.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "../url.h"
+
+namespace flame::http {
+	class client_request: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value __construct(php::parameters& params);
+		php::value to_string(php::parameters& params);
+	private:
+		void build_ex();
+		std::shared_ptr<url> url_;
+		boost::beast::http::message<true, value_body<true>> ctr_;
+		friend class client;
+	};
+}
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
new file mode 100644
index 0000000..4587e31
--- /dev/null
+++ b/src/http/client_response.cpp
@@ -0,0 +1,52 @@
+#include "../coroutine.h"
+#include "http.h"
+#include "value_body.h"
+#include "client_response.h"
+
+namespace flame {
+namespace http {
+	void client_response::declare(php::extension_entry& ext) {
+		php::class_entry<client_response>  class_client_response("flame\\http\\client_response");
+		class_client_response
+			.property({"status", 3000})
+			.property({"header", nullptr})
+			.property({"body", nullptr})
+			.property({"rawBody", ""})
+			.method<&client_response::__construct>("__construct", {}, php::PRIVATE)
+			.method<&client_response::to_string>("__toString");
+		ext.add(std::move(class_client_response));
+	}
+	// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+	php::value client_response::__construct(php::parameters& params) {
+		return nullptr;	
+	}
+	php::value client_response::to_string(php::parameters& params) {
+		return get("rawBody");
+	}
+	void client_response::build_ex() {
+		set("status", ctr_.result_int());
+		php::array header = get("header", true);
+		if(header.typeof(php::TYPE::NULLABLE)) {
+			header = php::array(4);
+		}
+		for(auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
+			php::string key {i->name_string().data(), i->name_string().size()};
+			php::string val {i->value().data(), i->value().size()};
+
+			// TODO 多个同名 HEADER 的处理
+			php::lowercase_inplace(key.data(), key.size());
+			header.set(key, val);
+		}
+		php::string body = ctr_.body();
+		if(body.typeof(php::TYPE::STRING)) {
+			set("rawBody", body);
+			auto ctype = ctr_.find(boost::beast::http::field::content_type);
+			if(ctype == ctr_.end()) {
+				set("body", body);
+			}else{
+				set("body", ctype_decode(ctype->value(), body));
+			}	
+		}
+	}
+}
+}
diff --git a/src/http/client_response.h b/src/http/client_response.h
new file mode 100644
index 0000000..1e5d432
--- /dev/null
+++ b/src/http/client_response.h
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace flame {
+namespace http {
+	class client_response: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value to_string(php::parameters& params);
+		// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+		php::value __construct(php::parameters& params);
+	private:
+		boost::beast::http::message<false, value_body<false>> ctr_;
+		void build_ex();
+		friend class _connection_pool;
+	};
+}
+}
diff --git a/src/http/http.cpp b/src/http/http.cpp
new file mode 100644
index 0000000..a30c21f
--- /dev/null
+++ b/src/http/http.cpp
@@ -0,0 +1,116 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "http.h"
+#include "client.h"
+#include "value_body.h"
+#include "client_request.h"
+#include "client_response.h"
+#include "_connection_pool.h"
+
+namespace flame::http {
+	//php::value client_ref;
+    client* client_;
+	
+	void declare(php::extension_entry& ext) {
+		client_ = nullptr;
+		ext.on_module_startup([] (php::extension_entry& ext) -> bool {
+			// 在框架初始化后创建全局HTTP客户端
+            gcontroller->on_init([] (const php::array& opts) {
+                client_ = new client();
+                php::parameters p(0, nullptr);
+                client_->__construct(p);
+            })->on_stop([] {
+                delete client_;
+                client_ = nullptr;
+            });
+			return true;
+		});
+		ext
+			.function<get>("flame\\http\\get")
+			.function<post>("flame\\http\\post")
+			.function<put>("flame\\http\\put")
+			.function<delete_>("flame\\http\\delete")
+			.function<exec>("flame\\http\\exec");
+
+		client_request::declare(ext);
+		client_response::declare(ext);
+		client::declare(ext);
+	}
+	static void init_guard() {
+		if(!client_) {
+			throw php::exception(zend_ce_parse_error, "flame not initialized");
+		}
+	}
+	php::value get(php::parameters& params) {
+		init_guard();
+		return client_->get(params);
+	}
+	php::value post(php::parameters& params) {
+		init_guard();
+		return client_->post(params);
+	}
+	php::value put(php::parameters& params) {
+		init_guard();
+		return client_->put(params);
+	}
+	php::value delete_(php::parameters& params) {
+		init_guard();
+		return client_->delete_(params);
+	}
+	php::value exec(php::parameters& params) {
+		init_guard();
+		return client_->exec(params);
+	}
+	php::string ctype_encode(boost::string_view ctype, const php::value& v) {
+		if(v.typeof(php::TYPE::STRING)) return v;
+
+		php::value r = v;
+		if(ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+			if(r.typeof(php::TYPE::ARRAY)) {
+				r = php::callable("http_build_query")({v});
+			}else{
+				r.to_string();
+			}
+		}else if(ctype.compare(0, 16, "application/json") == 0) {
+			r = php::json_encode(r);
+		}else{
+			r.to_string();
+		}
+		return r;
+	}
+	php::value ctype_decode(boost::string_view ctype, const php::string& v) {
+		if(ctype.compare(0, 16, "application/json") == 0) {
+			return php::json_decode(v);
+		}else if(ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+			php::array data(4);
+			php::callable("parse_str")({v, data.make_ref()});
+			return data;
+		}else if(ctype.compare(0, 19, "multipart/form-data") == 0) {
+			std::size_t begin = ctype.find_first_of(';', 19) + 1;
+			php::string cache;
+			parser::separator_parser<std::string, php::buffer> p1('\0','\0','=','"','"',';', [&cache] (std::pair<std::string, php::buffer> entry) {
+				if(entry.first == "boundary" || entry.first == "name") {
+					cache = php::string(std::move(entry.second));
+				}
+			});
+			p1.parse(ctype.data() + begin, ctype.data() + ctype.size() - (ctype.data() + begin));
+			p1.parse(";", 1); // 结尾分隔符可能不存在(可以认为数据行可能不完整)
+
+			php::array data(4);
+			parser::multipart_parser<std::string, php::buffer> p2(cache, [&p1, &cache, &begin, &data] (std::pair<std::string, php::buffer> entry) {
+				if(entry.first.size() == 0) {
+					data.set(cache, std::move(entry.second));
+				}else{
+					begin = boost::string_view(entry.second.data(), entry.second.size()).find_first_of(';') + 1;
+					p1.parse(entry.second.data() + begin, entry.second.data() + entry.second.size() - (entry.second.data() + begin));
+					p1.parse(";", 1);
+				}
+			});
+			p2.parse(v.data(), v.size());
+
+			return data;
+		}else{
+			return v;
+		}
+	}
+}
diff --git a/src/http/http.h b/src/http/http.h
new file mode 100644
index 0000000..f8602c8
--- /dev/null
+++ b/src/http/http.h
@@ -0,0 +1,19 @@
+#pragma once
+
+namespace flame {
+namespace http {
+    class _connection_pool;
+	class client;
+	extern client* client_;
+
+	void declare(php::extension_entry& ext);
+	php::value get(php::parameters& params);
+	php::value post(php::parameters& params);
+	php::value put(php::parameters& params);
+	php::value delete_(php::parameters& params);
+	php::value exec(php::parameters& params);
+
+	php::string ctype_encode(boost::string_view ctype, const php::value& v);
+	php::value ctype_decode(boost::string_view ctype, const php::string& v);
+}
+}
diff --git a/src/http/value_body.cpp b/src/http/value_body.cpp
new file mode 100644
index 0000000..40d6062
--- /dev/null
+++ b/src/http/value_body.cpp
@@ -0,0 +1,26 @@
+#include "../coroutine.h"
+#include "value_body.h"
+
+namespace flame {
+namespace http {
+    void value_body_reader::init(boost::optional<std::uint64_t> n, boost::system::error_code& error) {
+        error.assign(0, error.category());
+    }
+    void value_body_reader::finish(boost::system::error_code& error) {
+        v_ = std::move(b_);
+        error.assign(0, error.category());
+    }
+    void value_body_writer::init(boost::system::error_code& error) {
+        error.assign(0, error.category());
+    }
+    boost::optional<std::pair<value_body_writer::const_buffers_type, bool>> value_body_writer::get(boost::system::error_code& error) {
+        if(v_.empty()) {
+            return {{boost::asio::const_buffer(nullptr, 0), false}};
+        }else{
+            php::string data = v_;
+            error.assign(0, error.category());
+            return {{boost::asio::const_buffer(data.data(), data.size()), false}};
+        }
+    }
+} // http
+} // flame
diff --git a/src/http/value_body.h b/src/http/value_body.h
new file mode 100644
index 0000000..1f5f350
--- /dev/null
+++ b/src/http/value_body.h
@@ -0,0 +1,43 @@
+#pragma once
+
+namespace flame {
+namespace http {
+    class value_body_reader;
+    class value_body_writer;
+    template <bool isRequest>
+    class value_body {
+    public:
+        // Body
+        using value_type = php::value;
+        static std::uint64_t size(const value_type& v);
+        // BodyReader (Body)
+        using reader = value_body_reader;
+        // BodyWriter (Body)
+        using writer = value_body_writer;
+    };
+    class value_body_reader {
+    public:
+        template <bool isRequest, class Fields>
+        value_body_reader(boost::beast::http::header<isRequest, Fields>& h, php::value& v);
+        void init(boost::optional<std::uint64_t> n, boost::system::error_code& error);
+        template <typename ConstBufferSequence>
+        std::size_t put(ConstBufferSequence s, boost::system::error_code& error);
+        void finish(boost::system::error_code& error);
+    private:
+        php::value& v_;
+        php::buffer b_;
+    };
+    class value_body_writer {
+    public:
+        using const_buffers_type = boost::asio::const_buffer;
+        template <bool isRequest, class Fields>
+        value_body_writer(const boost::beast::http::header<isRequest, Fields>& h, const php::value& v);
+        void init(boost::system::error_code& error);
+        boost::optional<std::pair<const_buffers_type, bool>> get(boost::system::error_code& error);
+    private:
+        // const boost::beast::http::header<true>& h_;
+        const php::value& v_;
+    };
+} // http
+} // flame
+#include "value_body.ipp"
diff --git a/src/http/value_body.ipp b/src/http/value_body.ipp
new file mode 100644
index 0000000..7874d43
--- /dev/null
+++ b/src/http/value_body.ipp
@@ -0,0 +1,30 @@
+#pragma once
+
+namespace flame {
+namespace http {
+    template <bool isRequest, class Fields>
+    value_body_reader::value_body_reader(boost::beast::http::header<isRequest, Fields>& h, php::value& v)
+    : v_(v) {
+
+    }
+    template <typename ConstBufferSequence>
+    std::size_t value_body_reader::put(ConstBufferSequence s, boost::system::error_code& error) {
+        std::size_t n = 0;
+        for(auto i=boost::asio::buffer_sequence_begin(s); i!=boost::asio::buffer_sequence_end(s); ++i) {
+            boost::asio::const_buffer cbuf(*i);
+            b_.append((const char *)cbuf.data(), cbuf.size());
+            n += cbuf.size();
+        }
+        error.assign(0, error.category());
+        return n;
+    }
+    template <bool isRequest, class Fields>
+    value_body_writer::value_body_writer(const boost::beast::http::header<isRequest, Fields>& h, const php::value& v)
+    : v_(v) {}
+    template <bool isRequest>
+    std::uint64_t value_body<isRequest>::size(const value_body<isRequest>::value_type& v) {
+        return v.size();
+    }
+
+} // http
+} // flame
diff --git a/test/http_1.php b/test/http_1.php
new file mode 100644
index 0000000..5e12e57
--- /dev/null
+++ b/test/http_1.php
@@ -0,0 +1,88 @@
+<?php
+flame\init("http_1");
+
+// 清理超时连接
+// 清理被断开连接
+/*flame\go(function() {*/
+    //for($i = 0; $i < 100; ++$i) {
+        //flame\http\get("http://127.0.0.1:8081/");
+        //flame\time\sleep(1000);
+    //}
+//});
+
+
+/*flame\go(function() {*/
+    //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
+    //$req->method = "GET";
+    //$req->body = "aaaaaaaaaa";
+    //$cli->exec($req);
+
+    //flame\time\sleep(100000);
+//});
+
+// 多协程并发
+/*$opt["connection_per_host"] = 20;*/
+//$cli = new flame\http\client($opt);
+//for($i = 0; $i < 300; $i++) {
+    //flame\go(function() use($cli) {
+        //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
+        //$req->method = "GET";
+        //$req->body = "aaaaaaaaaa";
+        //$cli->exec($req);
+    //});
+/*}*/
+
+
+// 直接使用全局连接测试
+// core dump
+flame\go(function() {
+    for($i = 5000; $i < 5500; $i++) {
+        flame\go(function() use($i) {
+            //flame\time\sleep(rand(100,200));
+            flame\http\get("http://127.0.0.1:8081/", 30000);
+            var_dump("global client " . $i);
+        });
+    }
+});
+
+flame\go(function() {
+    for($i = 1000; $i < 1500; $i++) {
+        flame\go(function() use($i) {
+            //flame\time\sleep(rand(100,200));
+            flame\http\get("http://127.0.0.1:8081/", 30000);
+            var_dump("global client " . $i);
+        });
+    }
+});
+
+// 使用独立连接测试
+/*flame\go(function() {*/
+    //$opt["connection_per_host"] = 1;
+    //$cli = new flame\http\client($opt);
+    //for($i = 0; $i < 100; $i++) {
+        //flame\go(function() use($cli) {
+            //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
+            //$req->method = "GET";
+            //$req->body = "aaaaaaaaaa";
+            //$cli->exec($req);
+        //});
+        //flame\time\sleep(100);
+    //}
+    //echo "user client";
+//});
+//flame\go(function() {
+    //$opt["connection_per_host"] = 1;
+    //$cli = new flame\http\client($opt);
+    //for($i = 0; $i < 100; $i++) {
+        //flame\go(function() use($cli) {
+            //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
+            //$req->method = "GET";
+            //$req->body = "aaaaaaaaaa";
+            //$cli->exec($req);
+        //});
+        //flame\time\sleep(100);
+    //}
+    //echo "user client";
+/*});*/
+
+flame\run();

From 09c533fb62d161c034796e01f4bfb98142e9e5cd Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 29 Nov 2018 21:36:58 +0800
Subject: [PATCH 017/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=20=E5=8D=8F?=
 =?UTF-8?q?=E7=A8=8B=E6=9C=AA=E9=87=8A=E6=94=BE;=20Kafka=20=E5=B9=B6?=
 =?UTF-8?q?=E8=A1=8C=20concurrent=20=E5=8F=82=E6=95=B0;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                 |  2 +-
 doc/core.php              |  2 +-
 src/controller_worker.cpp |  7 ++-
 src/coroutine.cpp         | 96 +++++++++++++++++++++++++--------------
 src/coroutine.h           |  4 ++
 src/flame.cpp             |  5 +-
 src/kafka/kafka.cpp       |  4 +-
 src/mysql/client.cpp      |  6 +++
 src/mysql/client.h        |  1 +
 src/vendor.h              |  4 +-
 test/coroutine_1.php      | 12 +++++
 test/sleep_1.php          |  2 +-
 12 files changed, 103 insertions(+), 42 deletions(-)
 create mode 100644 test/coroutine_1.php

diff --git a/README.md b/README.md
index 2d89890..a256467 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 
 ``` Bash
 ./bootstrap.sh --prefix=/data/vendor/boost-1.68.0
-./b2 --prefix=/data/vendor/boost-1.68.0 cxxflags=-fPIC variant=release link=static threading=multi install
+./b2 -j4 --prefix=/data/vendor/boost-1.68.0 cxxflags="-fPIC" segmented-stacks=on context-impl=ucontext define=BOOST_USE_SEGMENTED_STACKS define=BOOST_USE_UCONTEXT variant=release link=static threading=multi install
 ```
 
 #### libphpext
diff --git a/doc/core.php b/doc/core.php
index 46ffa01..028b12a 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -28,7 +28,7 @@ function select(channel $q1, $q2/*, ...*/):channel {}
  * 监听框架的通知
  * @param string $event 目前消息存在以下两种:
  *  * "exception" - 未捕获的异常通知;
- *  * "exit" - 退出消息, 一般用于平滑停止各种服务;
+ *  * "quit" - 退出消息, 一般用于平滑停止各种服务;
  * @param callable 回调函数
  */
 function on(string $event, callable $cb) {}
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index c4b7592..f1bce77 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -17,12 +17,17 @@ namespace flame
         // 1. 停止信号调用退出通知
         signal_->async_wait([this] (const boost::system::error_code& error, int sig)
         {
-            auto ft = gcontroller->cbmap->equal_range("exit");
+            auto ft = gcontroller->cbmap->equal_range("quit");
             for(auto i=ft.first; i!=ft.second; ++i)
             {
                 i->second.call();
             }
             signal_.reset();
+
+            if (sig == SIGINT)
+            {
+                gcontroller->context_x.stop();
+            }
         });
         // 工作线程的使用时随机的, 需要保持其一直存在
         auto work = boost::asio::make_work_guard(gcontroller->context_y);
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 04e1259..32a5d4d 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -3,9 +3,10 @@
 
 namespace flame
 {
+    boost::context::segmented_stack coroutine::stack_allocator(8192);
     std::size_t coroutine::count = 0;
     // 当前协程
-    std::shared_ptr<coroutine> coroutine::current;
+    std::shared_ptr<coroutine> coroutine::current(nullptr);
     void coroutine::save_context(php_context_t &ctx)
     {
         ctx.vm_stack = EG(vm_stack);
@@ -22,45 +23,74 @@ namespace flame
         // EG(fake_scope) = ctx.scope;
         EG(current_execute_data) = ctx.current_execute_data;
     }
+    // 参考 zend_execute.c 
+    static zend_vm_stack zend_vm_stack_new_page(size_t size, zend_vm_stack prev)
+    {
+        zend_vm_stack page = (zend_vm_stack)emalloc(size);
+
+        page->top = ZEND_VM_STACK_ELEMENTS(page);
+        page->end = (zval *)((char *)page + size);
+        page->prev = prev;
+        return page;
+    }
+    // 参考 zend_execute.c
+    static void zend_vm_stack_init(void)
+    {
+        EG(vm_stack) = zend_vm_stack_new_page(sizeof(zval) * 1024, NULL);
+        EG(vm_stack)->top++;
+        EG(vm_stack_top) = EG(vm_stack)->top;
+        EG(vm_stack_end) = EG(vm_stack)->end;
+    }
     std::shared_ptr<coroutine> coroutine::start(php::callable fn, /*std::vector<php::value> ag,*/ zend_execute_data *execute_data)
     {
         ++coroutine::count;
-        auto co = std::make_shared<coroutine>(std::move(fn));
+        auto co_ = std::make_shared<coroutine>(std::move(fn));
         // 需要即时保存 PHP 堆栈
-        co->php_.current_execute_data = execute_data;
+        co_->php_.current_execute_data = execute_data;
         // 事实上的协程启动会稍后
-        boost::asio::post(gcontroller->context_x, [co/*, ag = std::move(ag)*/] {
-            // co->c1_ = boost::context::callcc([co] (auto &&cc) {
-            co->c1_ = boost::context::fiber([co/*, ag = std::move(ag)*/] (boost::context::fiber &&cc) {
-                auto work = boost::asio::make_work_guard(gcontroller->context_x);
-                // 启动进入协程
-                coroutine::current = co;
-                if(co->php_.current_execute_data == nullptr) {
-                    // 若 run 还未执行时, core_execute_data 还没有值;
-                    // 故赋值延迟到此处进行
-                    co->php_.current_execute_data = gcontroller->core_execute_data;
-                }
-                zend_vm_stack_init();
-                co->c2_ = std::move(cc);
-                // 协程运行
-                co->fn_.call(/*ag*/);
-                // 协程运行完毕
-                zend_vm_stack_destroy();
-                coroutine::current = nullptr;
-                if(--coroutine::count)
-                {
-                    // 所有协程结束后退出
-                    gcontroller->context_x.stop();
-                }
-                return std::move(co->c2_);
-            });
-            co->resume();
+        boost::asio::post(gcontroller->context_x, [co_ /*, ag = std::move(ag)*/] {
+            co_->c1_ = boost::context::fiber(
+                std::allocator_arg, coroutine::stack_allocator,
+                [co_ /*, ag = std::move(ag)*/](boost::context::fiber &&cc) {
+                    co_->c2_ = std::move(cc);
+                    auto work = boost::asio::make_work_guard(gcontroller->context_x);
+                    // 启动进入协程
+                    coroutine::current = co_;
+                    if(co_->php_.current_execute_data == nullptr)
+                    {
+                        // 若 run 还未执行时, core_execute_data 还没有值;
+                        // 故赋值延迟到此处进行
+                        co_->php_.current_execute_data = gcontroller->core_execute_data;
+                    }
+                    zend_vm_stack_init();
+                    // 协程运行
+                    co_->fn_.call(/*ag*/);
+                    // 协程运行完毕
+                    zend_vm_stack_destroy();
+                    coroutine::current = nullptr;
+                    boost::asio::post(gcontroller->context_x, [co_]()
+                    {
+                        co_->c1_ = boost::context::fiber();
+                        if (--coroutine::count == 0)
+                        {
+                            // 所有协程结束后退出
+                            gcontroller->context_x.stop();
+                        }
+                    });
+                    return std::move(co_->c2_);
+                });
+            co_->c1_ = std::move(co_->c1_).resume();
         });
-        return co;
+        return co_;
     }
     coroutine::coroutine(php::callable &&fn)
-        : fn_(std::move(fn)), c1_(), c2_() {}
-
+        : fn_(std::move(fn)), c1_(), c2_() {
+            // std::cout << "coroutine\n";
+        }
+    coroutine::~coroutine()
+    {
+        // std::cout << "~coroutine\n";
+    }
     void coroutine::suspend()
     {
         // 保存 PHP 堆栈
@@ -110,7 +140,6 @@ namespace flame
         if(er_) *er_ = e;
         co_->len_ = n;
 
-        assert(std::this_thread::get_id() == gcontroller->mthread_id);
         boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
     coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e)
@@ -120,7 +149,6 @@ namespace flame
     }
     void coroutine_handler::resume()
     {
-        assert(std::this_thread::get_id() == gcontroller->mthread_id);
         boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
     void coroutine_handler::suspend()
diff --git a/src/coroutine.h b/src/coroutine.h
index c897132..e7f63c4 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -13,6 +13,8 @@ namespace flame {
 			zend_class_entry *scope;
 			zend_execute_data *current_execute_data;
 		};
+		//
+		static boost::context::segmented_stack stack_allocator;
 		static std::size_t count;
 		// 当前协程
 		static std::shared_ptr<coroutine> current;
@@ -20,6 +22,7 @@ namespace flame {
 		static void restore_context(php_context_t& ctx);
 		static std::shared_ptr<coroutine> start(php::callable fn, /*std::vector<php::value> ag = std::vector<php::value>(0),*/zend_execute_data* execute_data = nullptr);
 		coroutine(php::callable&& fn);
+		~coroutine();
 
 		void suspend();
 		void resume();
@@ -31,6 +34,7 @@ namespace flame {
 		php_context_t php_;
 		// 用于部分异步请求返回长度信息
 		std::size_t len_;
+		
 	};
 
 	struct coroutine_handler
diff --git a/src/flame.cpp b/src/flame.cpp
index 251582e..592d52a 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -4,6 +4,7 @@
 #include "controller_worker.h"
 #include "coroutine.h"
 #include "core.h"
+#include "log/log.h"
 #include "os/os.h"
 #include "time/time.h"
 #include "mysql/mysql.h"
@@ -56,6 +57,7 @@ namespace flame
             }
             return rv;
         }));
+        // coroutine::start(fn);
         return nullptr;
     }
     static php::value fake_fn(php::parameters& params)
@@ -136,6 +138,7 @@ extern "C"
             })
             .function<flame::quit>("flame\\quit");
             flame::core::declare(ext);
+            flame::log::declare(ext);
             flame::os::declare(ext);
             flame::time::declare(ext);
             flame::mysql::declare(ext);
@@ -146,7 +149,7 @@ extern "C"
             flame::tcp::declare(ext);
             // flame::udp::declare(ext);
             flame::http::declare(ext);
-            // flame::log::declare(ext);
+            
         }
         else
         {
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index 02321b1..75d09ef 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -29,12 +29,12 @@ namespace flame::kafka
         
         php::object obj(php::class_entry<consumer>::entry());
         consumer* ptr = static_cast<consumer*>(php::native(obj));
-        ptr->cs_.reset(new _consumer(config, topics));
         if (config.exists("concurrent"))
         {
-            ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrenty")), 1), 8), 256);
+            ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 8), 256);
             config.erase("concurrent");
         }
+        ptr->cs_.reset(new _consumer(config, topics));
         coroutine_handler ch {coroutine::current};
         // 订阅
         ptr->cs_->subscribe(ch);
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 312edcc..bb08a88 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -13,6 +13,7 @@ namespace flame::mysql
         class_client
             .constant({"AAAA",123})
             .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::__destruct>("__destruct")
             .method<&client::escape>("escape",
             {
                 {"data", php::TYPE::UNDEFINED},
@@ -61,6 +62,11 @@ namespace flame::mysql
     php::value client::__construct(php::parameters& params) {
         return nullptr;
     }
+    php::value client::__destruct(php::parameters &params)
+    {
+        // std::cout << "client destruct\n";
+        return nullptr;
+    }
     php::value client::escape(php::parameters &params)
     {
         coroutine_handler ch {coroutine::current};
diff --git a/src/mysql/client.h b/src/mysql/client.h
index 977857c..21ba8a6 100644
--- a/src/mysql/client.h
+++ b/src/mysql/client.h
@@ -9,6 +9,7 @@ namespace flame::mysql
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params);
+        php::value __destruct(php::parameters &params);
         php::value escape(php::parameters &params);
         php::value begin_tx(php::parameters &params);
         // php::value where(php::parameters &params);
diff --git a/src/vendor.h b/src/vendor.h
index e5d3c1b..d4d4878 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -26,8 +26,10 @@
 #include <parser/multipart_parser.hpp>
 #include <boost/version.hpp>
 #include <boost/core/null_deleter.hpp>
-#include <boost/context/continuation.hpp>
+#define BOOST_USE_UCONTEXT
+#define BOOST_USE_SEGMENTED_STACKS
 #include <boost/context/fiber.hpp>
+#include <boost/context/segmented_stack.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/format.hpp>
 #include <boost/program_options.hpp>
diff --git a/test/coroutine_1.php b/test/coroutine_1.php
new file mode 100644
index 0000000..6fafe93
--- /dev/null
+++ b/test/coroutine_1.php
@@ -0,0 +1,12 @@
+<?php
+flame\init("coroutine_1");
+
+flame\go(function() {
+    // flame\go(function() {
+    //     flame\time\sleep(100);
+    //     echo "done.\n";
+    // });
+    echo "done.\n";
+});
+
+flame\run();
\ No newline at end of file
diff --git a/test/sleep_1.php b/test/sleep_1.php
index ebe30c3..620c891 100644
--- a/test/sleep_1.php
+++ b/test/sleep_1.php
@@ -12,7 +12,7 @@ function mySleep2($ms) {
 for($i=0;$i<200;++$i) {
     flame\go(function() {
         $i = 0;
-        for($x=0;$x<200;++$x) {
+        for($x=0;$x<2000;++$x) {
             echo ++$i, "\n";
             mySleep1();
             // flame\time\sleep(10);

From a45c95890639eddc5bc0b0c143f92545ddfc91ce Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 30 Nov 2018 02:48:01 +0800
Subject: [PATCH 018/146] =?UTF-8?q?=E5=8D=8F=E7=A8=8B=E5=A0=86=E6=A0=88?=
 =?UTF-8?q?=E5=A4=A7=E5=B0=8F;=20=E8=87=AA=E4=B8=BB=E9=80=80=E5=87=BA;=20M?=
 =?UTF-8?q?ongoDB=20=E8=BF=94=E5=9B=9E=E5=8F=96=E6=B6=88=E5=86=85=E9=83=A8?=
 =?UTF-8?q?=E6=95=B0=E6=8D=AE;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_master.cpp        |  2 +-
 src/controller_worker.cpp        | 13 +++++--------
 src/coroutine.cpp                | 32 ++++++++++++++++++--------------
 src/coroutine.h                  |  7 ++++---
 src/flame.cpp                    |  5 +++--
 src/log/log.cpp                  |  3 ++-
 src/mongodb/_connection_base.cpp | 14 +++++++++++++-
 src/mysql/_connection_pool.cpp   |  1 +
 src/mysql/client.cpp             |  2 +-
 src/redis/_connection_pool.cpp   |  2 +-
 10 files changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index a9dd293..44fd8ef 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -144,7 +144,7 @@ namespace flame
                 // 信号的默认处理: 转给所有子进程
                 for(auto i=worker_.begin(); i!=worker_.end(); ++i)
                 {
-                    ::kill( (*i)->id(), sig );
+                    ::kill( (*i)->id(), SIGTERM );
                 }
             }
         });
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index f1bce77..0f00104 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -4,7 +4,7 @@
 namespace flame
 {
     controller_worker::controller_worker()
-        : signal_(new boost::asio::signal_set(gcontroller->context_x, SIGTERM, SIGINT))
+    : signal_(new boost::asio::signal_set(gcontroller->context_x, SIGINT, SIGTERM))
     {
 
     }
@@ -15,19 +15,15 @@ namespace flame
     void controller_worker::run()
     {
         // 1. 停止信号调用退出通知
-        signal_->async_wait([this] (const boost::system::error_code& error, int sig)
+        signal_->async_wait([this](const boost::system::error_code &error, int sig)
         {
+            signal_->clear();
+            
             auto ft = gcontroller->cbmap->equal_range("quit");
             for(auto i=ft.first; i!=ft.second; ++i)
             {
                 i->second.call();
             }
-            signal_.reset();
-
-            if (sig == SIGINT)
-            {
-                gcontroller->context_x.stop();
-            }
         });
         // 工作线程的使用时随机的, 需要保持其一直存在
         auto work = boost::asio::make_work_guard(gcontroller->context_y);
@@ -42,6 +38,7 @@ namespace flame
         }
         // 3. 启动 context_x 运行
         gcontroller->context_x.run();
+        signal_.reset();
         // 4. 确认工作线程停止
         work.reset();
         for (int i = 0; i < thread_.size(); ++i)
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 32a5d4d..4a7af97 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -3,8 +3,9 @@
 
 namespace flame
 {
-    boost::context::segmented_stack coroutine::stack_allocator(8192);
+    boost::context::fixedsize_stack coroutine::stack_allocator(64 * 1024);
     std::size_t coroutine::count = 0;
+    coroutine::php_context_t coroutine::global_context;
     // 当前协程
     std::shared_ptr<coroutine> coroutine::current(nullptr);
     void coroutine::save_context(php_context_t &ctx)
@@ -36,17 +37,16 @@ namespace flame
     // 参考 zend_execute.c
     static void zend_vm_stack_init(void)
     {
-        EG(vm_stack) = zend_vm_stack_new_page(sizeof(zval) * 1024, NULL);
+        EG(vm_stack) = zend_vm_stack_new_page(4 * sizeof(zval) * 1024, NULL);
         EG(vm_stack)->top++;
         EG(vm_stack_top) = EG(vm_stack)->top;
         EG(vm_stack_end) = EG(vm_stack)->end;
     }
-    std::shared_ptr<coroutine> coroutine::start(php::callable fn, /*std::vector<php::value> ag,*/ zend_execute_data *execute_data)
+    std::shared_ptr<coroutine> coroutine::start(php::callable fn)
     {
+        
         ++coroutine::count;
-        auto co_ = std::make_shared<coroutine>(std::move(fn));
-        // 需要即时保存 PHP 堆栈
-        co_->php_.current_execute_data = execute_data;
+        auto co_ = std::make_shared<coroutine>(fn);
         // 事实上的协程启动会稍后
         boost::asio::post(gcontroller->context_x, [co_ /*, ag = std::move(ag)*/] {
             co_->c1_ = boost::context::fiber(
@@ -54,20 +54,21 @@ namespace flame
                 [co_ /*, ag = std::move(ag)*/](boost::context::fiber &&cc) {
                     co_->c2_ = std::move(cc);
                     auto work = boost::asio::make_work_guard(gcontroller->context_x);
+
+                    save_context(coroutine::global_context);
                     // 启动进入协程
                     coroutine::current = co_;
-                    if(co_->php_.current_execute_data == nullptr)
-                    {
-                        // 若 run 还未执行时, core_execute_data 还没有值;
-                        // 故赋值延迟到此处进行
-                        co_->php_.current_execute_data = gcontroller->core_execute_data;
-                    }
+                    zend_execute_data ex;
+                    std::memset(&ex, sizeof(zend_execute_data), 0);
+                    EG(current_execute_data) = &ex;
                     zend_vm_stack_init();
-                    // 协程运行
+                    // 协程运行;
                     co_->fn_.call(/*ag*/);
                     // 协程运行完毕
                     zend_vm_stack_destroy();
                     coroutine::current = nullptr;
+                    
+                    restore_context(coroutine::global_context);
                     boost::asio::post(gcontroller->context_x, [co_]()
                     {
                         co_->c1_ = boost::context::fiber();
@@ -79,11 +80,12 @@ namespace flame
                     });
                     return std::move(co_->c2_);
                 });
+
             co_->c1_ = std::move(co_->c1_).resume();
         });
         return co_;
     }
-    coroutine::coroutine(php::callable &&fn)
+    coroutine::coroutine(php::callable fn)
         : fn_(std::move(fn)), c1_(), c2_() {
             // std::cout << "coroutine\n";
         }
@@ -95,6 +97,7 @@ namespace flame
     {
         // 保存 PHP 堆栈
         coroutine::save_context(php_);
+        coroutine::restore_context(coroutine::global_context);
         // 离开协程
         coroutine::current = nullptr;
         c2_ = std::move(c2_).resume();
@@ -103,6 +106,7 @@ namespace flame
     void coroutine::resume()
     {
         // 恢复 PHP 堆栈
+        coroutine::save_context(coroutine::global_context);
         coroutine::restore_context(php_);
         // 恢复进入协程
         coroutine::current = shared_from_this();
diff --git a/src/coroutine.h b/src/coroutine.h
index e7f63c4..ef252f3 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -14,14 +14,15 @@ namespace flame {
 			zend_execute_data *current_execute_data;
 		};
 		//
-		static boost::context::segmented_stack stack_allocator;
+		static boost::context::fixedsize_stack stack_allocator;
 		static std::size_t count;
 		// 当前协程
 		static std::shared_ptr<coroutine> current;
 		static void save_context(php_context_t &ctx);
 		static void restore_context(php_context_t& ctx);
-		static std::shared_ptr<coroutine> start(php::callable fn, /*std::vector<php::value> ag = std::vector<php::value>(0),*/zend_execute_data* execute_data = nullptr);
-		coroutine(php::callable&& fn);
+		static php_context_t global_context;
+		static std::shared_ptr<coroutine> start(php::callable fn);
+		coroutine(php::callable fn);
 		~coroutine();
 
 		void suspend();
diff --git a/src/flame.cpp b/src/flame.cpp
index 592d52a..0ae02eb 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -49,7 +49,9 @@ namespace flame
                 if(x==0)
                 {
                     std::cerr << "[" << time::iso() << "] (FATAL) " << ex.what() << "\n";
-                    exit(-1);
+                    boost::asio::post(gcontroller->context_x, [] () {
+                        gcontroller->context_x.stop();
+                    });
                 }else
                 {
                     std::cerr << "[" << time::iso() << "] (ERROR) " << ex.what() << "\n";
@@ -57,7 +59,6 @@ namespace flame
             }
             return rv;
         }));
-        // coroutine::start(fn);
         return nullptr;
     }
     static php::value fake_fn(php::parameters& params)
diff --git a/src/log/log.cpp b/src/log/log.cpp
index 29eba4e..569d98c 100644
--- a/src/log/log.cpp
+++ b/src/log/log.cpp
@@ -29,11 +29,12 @@ namespace flame::log
         if(lv < level) return;
         std::clog << '[' << time::iso() << "] (";
         std::clog << LEVEL_S[lv];
-        std::clog << "] ";
+        std::clog << ") ";
         for (int i = 0; i < params.size(); ++i)
         {
             std::clog << ' ' << params[i].ptr();
         }
+        std::clog << '\n';
     }
     static php::value trace(php::parameters &params)
     {
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index 741dcc6..16a275b 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -48,7 +48,19 @@ namespace flame::mongodb
         }
         else
         {
-            return bson2array(rep);
+            php::array r(4);
+
+            bson_iter_t i;
+            bson_oid_t oid;
+            bson_iter_init(&i, rep.get());
+            while (bson_iter_next(&i))
+            {
+                const char* key = bson_iter_key(&i);
+                std::uint32_t len = bson_iter_key_len(&i);
+                if(len > 0 && key[0] == '$') continue;
+                r.set(php::string(key, len), iter2value(&i));
+            }
+            return std::move(r);
         }
     }
 } // namespace flame::mongodb
\ No newline at end of file
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 89afee2..b71936e 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -38,6 +38,7 @@ namespace flame::mysql
                 { // 可用连接
                     MYSQL *c = conn_.front().conn;
                     conn_.pop_front();
+                    mysql_reset_connection(c);
                     release(c);
                     return;
                 }
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index bb08a88..97449af 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -64,7 +64,7 @@ namespace flame::mysql
     }
     php::value client::__destruct(php::parameters &params)
     {
-        // std::cout << "client destruct\n";
+        std::cout << "client destruct\n";
         return nullptr;
     }
     php::value client::escape(php::parameters &params)
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 586d959..b082784 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -65,7 +65,7 @@ namespace flame::redis
         ch.suspend();
         if (!conn)
         {
-            throw php::exception(zend_ce_exception, "failed to connect to MySQL server", -1);
+            throw php::exception(zend_ce_exception, "failed to connect to Redis server", -1);
         }
         // 恢复, 已经填充连接
         return conn;

From 49dcbf02dc664ee1b36c425ba9af32f9ca7dc054 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 30 Nov 2018 02:51:26 +0800
Subject: [PATCH 019/146] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E4=BD=BF=E7=94=A8=20?=
 =?UTF-8?q?fcontext=5Ft=20=E5=B9=B6=E8=AE=BE=E7=BD=AE=2064k=20=E6=A0=88?=
 =?UTF-8?q?=E5=A4=A7=E5=B0=8F;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md    | 2 +-
 src/vendor.h | 2 --
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/README.md b/README.md
index a256467..404ca16 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 
 ``` Bash
 ./bootstrap.sh --prefix=/data/vendor/boost-1.68.0
-./b2 -j4 --prefix=/data/vendor/boost-1.68.0 cxxflags="-fPIC" segmented-stacks=on context-impl=ucontext define=BOOST_USE_SEGMENTED_STACKS define=BOOST_USE_UCONTEXT variant=release link=static threading=multi install
+./b2 -j4 --prefix=/data/vendor/boost-1.68.0 cxxflags="-fPIC" variant=release link=static threading=multi install
 ```
 
 #### libphpext
diff --git a/src/vendor.h b/src/vendor.h
index d4d4878..bc8a272 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -26,8 +26,6 @@
 #include <parser/multipart_parser.hpp>
 #include <boost/version.hpp>
 #include <boost/core/null_deleter.hpp>
-#define BOOST_USE_UCONTEXT
-#define BOOST_USE_SEGMENTED_STACKS
 #include <boost/context/fiber.hpp>
 #include <boost/context/segmented_stack.hpp>
 #include <boost/filesystem.hpp>

From 7e7dc7d75441e73574ff308f091c6f1a9188e5b8 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sat, 1 Dec 2018 18:13:34 +0800
Subject: [PATCH 020/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20HTTP=20=E5=AE=A2?=
 =?UTF-8?q?=E6=88=B7=E7=AB=AF=E7=AE=80=E5=8D=95=E6=96=87=E6=A1=A3=E5=8F=8A?=
 =?UTF-8?q?=20WARNING=20=E6=B5=8B=E8=AF=95=E8=84=9A=E6=9C=AC;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/http.php         | 66 ++++++++++++++++++++++++++++++++++++++++++++
 src/flame.cpp        |  1 +
 test/exception_1.php | 14 ++++------
 test/warning_1.php   |  9 ++++++
 4 files changed, 82 insertions(+), 8 deletions(-)
 create mode 100644 doc/http.php
 create mode 100644 test/warning_1.php

diff --git a/doc/http.php b/doc/http.php
new file mode 100644
index 0000000..773dbfd
--- /dev/null
+++ b/doc/http.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace flame\http;
+
+function exec(client_request $req): client_response {}
+function get(string $url, int $timeout = 3000): client_response {}
+function post(string $url, mixed $data, int $timeout = 3000): client_response {}
+function put(string $url, mixed $data, int $timeout = 3000): client_response {}
+function delete(string $url, int $timeout = 3000): client_response {}
+
+class client_request {
+    /**
+     * @property int
+     */
+    public $timeout;
+    /**
+     * @property string
+     */
+    public $method;
+    /**
+     * @property string
+     */
+    public $url;
+    /**
+     * @property array
+     */
+    public $header;
+    /**
+     * @property array
+     */
+    public $cookie;
+    /**
+     * @property mixed
+     */
+    public $body;
+    /**
+     * 构建请求, 可选的指定请求体 `$body` (将自动设置为 POST 请求方法)及超时时间 `$timeout`
+     */
+    function __construct(string $url, mixed $body = null, int $timeout = 3000) {}
+}
+
+class client_response {
+    /**
+     * 响应码
+     * @property int
+     */
+    public $status;
+    /**
+     * 注意: 目前使用了数组形式, 切暂不支持多个同名 Header 数据的访问;
+     * @property array
+     */
+    public $header;
+    /**
+     * @property mixed 以下几种 Content-Type 将自动进行解析并生成关联数组:
+     *  * "application/json"
+     *  * "application/x-www-form-urlencoded"
+     *  * "multipart/form-data"
+     * 其他类型保持文本的原始数据(与 $rawBody 相同);
+     */
+    public $body;
+    /**
+     * 原始请求体数据
+     * @property string
+     */
+    public $rawBody;
+}
\ No newline at end of file
diff --git a/src/flame.cpp b/src/flame.cpp
index 0ae02eb..812a943 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -89,6 +89,7 @@ namespace flame
     static php::value quit(php::parameters& params)
     {
         gcontroller->context_x.stop();
+        gcontroller->context_y.stop();
         return nullptr;
     }
 }
diff --git a/test/exception_1.php b/test/exception_1.php
index f74a289..e7e39c3 100644
--- a/test/exception_1.php
+++ b/test/exception_1.php
@@ -1,19 +1,17 @@
 <?php
-flame\init("exception_1", [
-    "logger" => "/tmp/output.txt",
-]);
+flame\init("exception_1");
 
-function abc() {
-    // flame\time\sleep(1000);
+function test() {
+    flame\time\sleep(1000);
     throw new Exception("Aaaaaaa");
 }
 
 flame\go(function() {
-    flame\time\sleep(5000);
-    echo "Abc\n";
-    throw new Exception("Aaaaaaa");
+    flame\time\sleep(1000);
+    test();
 });
 
+// 可选
 flame\on("exception", function($ex) {
     // echo $ex, "\n";
     flame\quit();
diff --git a/test/warning_1.php b/test/warning_1.php
new file mode 100644
index 0000000..50d74fc
--- /dev/null
+++ b/test/warning_1.php
@@ -0,0 +1,9 @@
+<?php
+flame\init("exception_1");
+
+flame\go(function() {
+    flame\time\sleep(1000);
+    $a = $b; // WARNING
+});
+
+flame\run();
\ No newline at end of file

From c19e90381df27362ff479c1b082190ddaabed8ef Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 2 Dec 2018 21:01:38 +0800
Subject: [PATCH 021/146] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=20?=
 =?UTF-8?q?HTTP=20=E6=9C=8D=E5=8A=A1=E5=99=A8;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/_handler.cpp        | 264 +++++++++++++++++++++++++++++++++++
 src/http/_handler.h          |  51 +++++++
 src/http/http.cpp            |   7 +
 src/http/server.cpp          | 170 ++++++++++++++++++++++
 src/http/server.h            |  36 +++++
 src/http/server_request.cpp  |  86 ++++++++++++
 src/http/server_request.h    |  15 ++
 src/http/server_response.cpp | 247 ++++++++++++++++++++++++++++++++
 src/http/server_response.h   |  35 +++++
 9 files changed, 911 insertions(+)
 create mode 100644 src/http/_handler.cpp
 create mode 100644 src/http/_handler.h
 create mode 100644 src/http/server.cpp
 create mode 100644 src/http/server.h
 create mode 100644 src/http/server_request.cpp
 create mode 100644 src/http/server_request.h
 create mode 100644 src/http/server_response.cpp
 create mode 100644 src/http/server_response.h

diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
new file mode 100644
index 0000000..ffd0b57
--- /dev/null
+++ b/src/http/_handler.cpp
@@ -0,0 +1,264 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "../time/time.h"
+#include "_handler.h"
+#include "http.h"
+#include "server.h"
+#include "server_request.h"
+#include "server_response.h"
+
+namespace flame::http
+{
+    _handler::_handler(server *svr, tcp::socket &&sock)
+    : svr_ptr(svr)
+    , svr_obj(svr)
+    , socket_(std::move(sock))
+    {
+
+    }
+    _handler::~_handler()
+    {
+
+    }
+    void _handler::start()
+    {
+        // 第一次请求或允许复用
+        if ((gcontroller->status & controller::controller_status::STATUS_SHUTDOWN)
+                || req_ && !req_->keep_alive() || res_ && !res_->keep_alive()) 
+        {
+            req_.reset();
+            res_.reset();
+            return;
+        }
+        read_request();
+    }
+
+    void _handler::read_request()
+    {
+        // 读取请求
+        req_.reset(new boost::beast::http::message<true, value_body<true>>());
+        boost::beast::http::async_read(socket_, buffer_, *req_, [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n)
+        {
+            if(error == boost::beast::http::error::end_of_stream || error == boost::asio::error::operation_aborted)
+            {
+                res_.reset();
+                req_.reset();
+                return;
+            }
+            else if (error)
+            {   
+                res_.reset();
+                req_.reset();
+                std::clog << "[" << time::iso() << "] (WARNING) failed to read http request: " << error.message() << std::endl;
+                return;
+            }
+            res_.reset(new boost::beast::http::message<false, value_body<false>>());
+            // 默认为请求 KeepAlive 配置
+            res_->keep_alive(req_->keep_alive());
+            // 默认应为非 chunked 模式
+            assert(!res_->chunked());
+            call_handler();
+        });
+    }
+
+    void _handler::call_handler()
+    {
+        php::object req = php::object(php::class_entry<server_request>::entry());
+        php::object res = php::object(php::class_entry<server_response>::entry());
+        server_request *req_ptr = static_cast<server_request *>(php::native(req));
+        server_response *res_ptr = static_cast<server_response *>(php::native(res));
+
+        req_ptr->build_ex(*req_); // 将 C++ 请求对象展开到 PHP 中
+        res_ptr->handler_ = shared_from_this();
+
+        coroutine::start(php::callable([self = shared_from_this(), this, req, req_ptr, res, res_ptr] (php::parameters& params) -> php::value
+        {
+            coroutine_handler ch{coroutine::current};
+            std::string route = req.get("method");
+            route.push_back(':');
+            route.append(req.get("path"));
+
+            auto ib = svr_ptr->cb_.find("before"),
+                ih = svr_ptr->cb_.find(route),
+                ia = svr_ptr->cb_.find("after");
+
+            try
+            {
+                php::value rv = true;
+                if(ib != svr_ptr->cb_.end()) 
+                {
+                    rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
+                }
+                if(rv.typeof(php::TYPE::NO))
+                {
+                    goto HANDLE_BREAK;
+                }
+                if(ih != svr_ptr->cb_.end())
+                {
+                    rv = ih->second.call({req, res});
+                }
+                if(rv.typeof(php::TYPE::NO))
+                {
+                    goto HANDLE_BREAK;
+                }
+                if (ia != svr_ptr->cb_.end())
+                {
+                    rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
+                }
+            }
+            catch(const php::exception& ex)
+            {
+                res_.reset();
+                req_.reset();
+                std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                return nullptr;
+            }
+HANDLE_BREAK:
+            if (res_->chunked() && (res_ptr->status_ & server_response::STATUS_BODY_SENT))
+            {
+                // Chunked 方式 已结束
+                start();
+            }
+            else if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/)
+            {
+                res_ptr->build_ex(*res_);
+                // Content 方式, 待结束
+                php::string body = res.get("body");
+                if (!body.empty())
+                {
+                    body = ctype_encode(res_->find(boost::beast::http::field::content_type)->value(), body);
+                }
+                res_->body() = body;
+                res_->prepare_payload();
+
+                boost::beast::http::async_write(socket_, *res_, [self = shared_from_this(), this](const boost::system::error_code &error, std::size_t nsent)
+                {
+                    start();
+                });
+            }/*else
+            {
+                // Chunked 未结束 -> server_response::__destruct() -> finish()
+            }*/
+            return nullptr;
+        }));
+    }
+
+    void _handler::write_head(server_response *res_ptr, coroutine_handler &ch)
+    {
+        if(!res_) return;
+        res_ptr->build_ex(*res_);
+        res_->chunked(true);
+        res_->set(boost::beast::http::field::transfer_encoding, "chunked");
+        boost::system::error_code error;
+
+        boost::beast::http::serializer<false, value_body<false>> sr(*res_);
+        boost::beast::http::async_write_header(socket_, sr, ch[error]);
+        if(error) {
+            res_.reset();
+            req_.reset();
+            throw php::exception(zend_ce_exception,
+                (boost::format("failed to write_head: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+        }
+    }
+    void _handler::write_body(server_response *res_ptr, php::string data, coroutine_handler &ch)
+    {
+        if(!res_) return;
+        if(!res_->chunked()) {
+            throw php::exception(zend_ce_error, "failed to write_body: Transfer-Encoding: chunked required");
+        }
+        boost::system::error_code error;
+        boost::asio::async_write(socket_,
+            boost::beast::http::make_chunk(boost::asio::const_buffer(data.c_str(), data.size())), ch[error]);
+        if (error)
+        {
+            res_.reset();
+            req_.reset();
+            throw php::exception(zend_ce_exception,
+                (boost::format("failed to write_body: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+        }
+    }
+    void _handler::write_end(server_response* res_ptr, coroutine_handler &ch)
+    {
+        if(!res_) return;
+        if (!res_->chunked())
+        {
+            throw php::exception(zend_ce_error, "failed to write_body: Transfer-Encoding: chunked required");
+        }
+        boost::system::error_code error;
+        boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
+        if (error)
+        {
+            res_.reset();
+            req_.reset();
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to write_end: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+        }
+    }
+    void _handler::write_file(server_response *res_ptr, std::string path, coroutine_handler &ch)
+    {
+        if(!res_) return;
+        std::ifstream file(path);
+        char buffer[2048];
+        std::size_t size;
+        std::string err;
+        boost::system::error_code error;
+
+        while(!file.eof()) {
+            boost::asio::post(gcontroller->context_y, [&file, &ch, &buffer, &size, &err] () {
+                try
+                {
+                    file.read(buffer, sizeof(buffer));
+                    size = file.gcount();
+                }
+                catch(const std::exception& ex)
+                {
+                    err.assign(ex.what());
+                }
+                ch.resume();
+            });
+            ch.suspend();
+            if (err.empty())
+            {
+                boost::asio::async_write(socket_, boost::beast::http::make_chunk( boost::asio::buffer(buffer, size) ), ch[error]);
+                if (error)
+                {
+                    goto WRITE_ERROR;
+                }
+            }
+            else
+            {
+                res_.reset();
+                req_.reset();
+                throw php::exception(zend_ce_exception, "failed to write_file: " + err);
+            }
+        }
+        boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
+WRITE_ERROR:
+        if (error)
+        {
+            res_.reset();
+            req_.reset();
+            throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to write_file: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+        }
+        // file.close();
+    }
+    void _handler::finish(server_response *res_ptr, coroutine_handler &ch)
+    {
+        if(res_ && res_->chunked() && !(res_ptr->status_ & server_response::STATUS_BODY_END)) {
+            boost::system::error_code error;
+            boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
+            
+            if(error)
+            {
+                res_.reset();
+                req_.reset();
+                std::clog << "[" << time::iso() << "] (ERROR) failed to write_end: (" << error.value() << ") " << error.message() << std::endl;
+            }
+            else
+            {
+                start();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/http/_handler.h b/src/http/_handler.h
new file mode 100644
index 0000000..09df121
--- /dev/null
+++ b/src/http/_handler.h
@@ -0,0 +1,51 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+
+namespace flame::http
+{
+    class server_response;
+    class server_request;
+
+    template <bool isRequest>
+    class value_body;
+    class server;
+    class _handler : public std::enable_shared_from_this<_handler>
+    {
+    public:
+        _handler(server *svr, boost::asio::ip::tcp::socket &&sock);
+        ~_handler();
+        void start();
+        
+        void read_request();
+        void call_handler();
+
+        void write_head(server_response* res_ptr, coroutine_handler& ch);
+        void write_body(server_response* res_ptr, php::string data, coroutine_handler& ch);
+        void write_end(server_response* res_ptr, coroutine_handler& ch);
+        void write_file(server_response* res_ptr, std::string file, coroutine_handler& ch);
+
+        // 由 server_response 销毁时调用
+        void finish(server_response *res, coroutine_handler &ch);
+
+      private:
+        server *svr_ptr;
+        php::object svr_obj;
+        boost::asio::ip::tcp::socket socket_;
+
+        std::shared_ptr<boost::beast::http::message<true, value_body<true>>> req_;
+        std::shared_ptr<boost::beast::http::message<false, value_body<false>>> res_;
+        boost::beast::flat_buffer buffer_;
+    };
+    // enum
+    // {
+    //     RESPONSE_STATUS_HEADER_BUILT = 0x01,
+    //     RESPONSE_STATUS_HEADER_SENT = 0x02,
+    //     RESPONSE_STATUS_FINISHED = 0x04,
+    //     RESPONSE_STATUS_DETACHED = 0x08,
+
+    //     RESPONSE_TARGET_WRITE_HEADER = 0x01,
+    //     RESPONSE_TARGET_WRITE_CHUNK = 0x02,
+    //     RESPONSE_TARGET_WRITE_CHUNK_LAST = 0x03,
+    // };
+} // namespace flame::http
diff --git a/src/http/http.cpp b/src/http/http.cpp
index a30c21f..4139a33 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -6,6 +6,10 @@
 #include "client_request.h"
 #include "client_response.h"
 #include "_connection_pool.h"
+#include "server.h"
+#include "server_request.h"
+#include "server_response.h"
+#include "_handler.h"
 
 namespace flame::http {
 	//php::value client_ref;
@@ -35,6 +39,9 @@ namespace flame::http {
 		client_request::declare(ext);
 		client_response::declare(ext);
 		client::declare(ext);
+		server::declare(ext);
+		server_request::declare(ext);
+		server_response::declare(ext);
 	}
 	static void init_guard() {
 		if(!client_) {
diff --git a/src/http/server.cpp b/src/http/server.cpp
new file mode 100644
index 0000000..cc1bbd4
--- /dev/null
+++ b/src/http/server.cpp
@@ -0,0 +1,170 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "../tcp/tcp.h"
+#include "server.h"
+#include "_handler.h"
+
+namespace flame::http
+{
+    void server::declare(php::extension_entry &ext)
+    {
+        php::class_entry<server> class_server("flame\\http\\server");
+        class_server
+            .property({"address", "127.0.0.1:7678"})
+            .method<&server::__construct>("__construct",
+            {
+                {"address", php::TYPE::STRING},
+            })
+            .method<&server::before>("before",
+            {
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::after>("after",
+            {
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::put>("put",
+            {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::delete_>("delete",
+            {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::post>("post",
+            {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::patch>("patch",
+            {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::get>("get",
+            {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::head>("head",
+            {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::options>("options",
+            {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::run>("run")
+            .method<&server::close>("close");
+        ext.add(std::move(class_server));
+    }
+
+    server::server()
+    : accp_(gcontroller->context_x)
+    , sock_(gcontroller->context_x)
+    , closed_(false)
+    {}
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+    php::value server::__construct(php::parameters &params)
+    {
+        php::string str = params[0];
+        auto pair = tcp::addr2pair(str);
+        if (pair.first.empty() || pair.second.empty())
+        {
+            throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
+        }
+        boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
+        addr_.address(addr);
+        addr_.port(std::atoi(pair.second.c_str()));
+
+        accp_.open(addr_.protocol());
+        boost::asio::socket_base::reuse_address opt1(true);
+        accp_.set_option(opt1);
+        reuse_port opt2(true);
+        accp_.set_option(opt2);
+
+        set("address", params[0]);
+        return nullptr;
+    }
+    php::value server::before(php::parameters &params)
+    {
+        cb_["before"] = params[0];
+        return this;
+    }
+    php::value server::after(php::parameters &params)
+    {
+        cb_["after"] = params[0];
+        return this;
+    }
+    php::value server::put(php::parameters &params)
+    {
+        cb_[std::string("PUT:") + params[0].to_string()] = params[1];
+        return this;
+    }
+    php::value server::delete_(php::parameters &params)
+    {
+        cb_[std::string("DELETE:") + params[0].to_string()] = params[1];
+        return this;
+    }
+    php::value server::post(php::parameters &params)
+    {
+        cb_[std::string("POST:") + params[0].to_string()] = params[1];
+        return this;
+    }
+    php::value server::get(php::parameters &params)
+    {
+        cb_[std::string("GET:") + params[0].to_string()] = params[1];
+        return this;
+    }
+    php::value server::head(php::parameters &params)
+    {
+        cb_[std::string("HEAD:") + params[0].to_string()] = params[1];
+        return this;
+    }
+    php::value server::options(php::parameters &params)
+    {
+        cb_[std::string("OPTIONS:") + params[0].to_string()] = params[1];
+        return this;
+    }
+    php::value server::patch(php::parameters &params)
+    {
+        cb_[std::string("PATCH:") + params[0].to_string()] = params[1];
+        return this;
+    }
+    php::value server::run(php::parameters &params)
+    {
+        accp_.bind(addr_);
+        accp_.listen(boost::asio::socket_base::max_listen_connections);
+
+        coroutine_handler ch{coroutine::current};
+        while(!closed_)
+        {
+            boost::system::error_code err;
+            accp_.async_accept(sock_, ch[err]);
+            if (err == boost::asio::error::operation_aborted)
+            {
+                break;
+            }
+            else if(err)
+            {
+                throw php::exception(zend_ce_exception,
+                                   (boost::format("failed to accept connection: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+            }else{
+                std::make_shared<_handler>(this, std::move(sock_))->start();
+            }
+        }
+        return nullptr;
+    }
+    php::value server::close(php::parameters &params)
+    {
+        if(closed_) return nullptr;
+        closed_ = true;
+        accp_.cancel();
+        accp_.close();
+        return nullptr;
+    }
+} // namespace flame::http
diff --git a/src/http/server.h b/src/http/server.h
new file mode 100644
index 0000000..a010818
--- /dev/null
+++ b/src/http/server.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::http
+{
+    class acceptor;
+    class server : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+
+        server();
+
+        php::value __construct(php::parameters &params);
+        php::value before(php::parameters &params);
+        php::value after(php::parameters &params);
+        php::value put(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value post(php::parameters &params);
+        php::value patch(php::parameters &params);
+        php::value get(php::parameters &params);
+        php::value head(php::parameters &params);
+        php::value options(php::parameters &params);
+        php::value run(php::parameters &params);
+        php::value close(php::parameters &params);
+
+    private:
+        boost::asio::ip::tcp::endpoint addr_;
+        boost::asio::ip::tcp::acceptor accp_;
+        boost::asio::ip::tcp::socket   sock_;
+        std::map<std::string, php::callable> cb_;
+        bool closed_;
+
+        friend class _handler;
+    };
+} // namespace flame::http
\ No newline at end of file
diff --git a/src/http/server_request.cpp b/src/http/server_request.cpp
new file mode 100644
index 0000000..caed640
--- /dev/null
+++ b/src/http/server_request.cpp
@@ -0,0 +1,86 @@
+#include "../coroutine.h"
+#include "http.h"
+#include "value_body.h"
+#include "server_request.h"
+
+namespace flame {
+namespace http {
+	void server_request::declare(php::extension_entry& ext) {
+		php::class_entry<server_request> class_server_request(
+			"flame\\http\\server_request");
+		class_server_request
+			.property({"timeout", 3000})
+			.property({"method", "GET"})
+			.property({"path", "/"})
+			.property({"query", nullptr})
+			.property({"header", nullptr})
+			.property({"cookie", nullptr})
+			.property({"body", nullptr})
+			.property({"rawBody", ""})
+			.property({"data", nullptr})
+			.method<&server_request::__construct>("__construct", {}, php::PRIVATE);
+
+		ext.add(std::move(class_server_request));
+	}
+	server_request::~server_request() {
+
+	}
+	php::value server_request::__construct(php::parameters& params) {
+		return nullptr;
+	}
+	void server_request::build_ex(const boost::beast::http::message<true, value_body<true>>& ctr_) {
+		auto method = boost::beast::http::to_string(ctr_.method());
+		set("method", php::string(method.data(), method.size()));
+		// 目标请求地址
+		auto target = ctr_.target();
+		std::shared_ptr<php::url> url_ = php::parse_url(target.data(), target.size());
+		if(url_->path) {
+			set("path", php::string(url_->path));
+		}else{
+			set("path", php::string("/", 1));
+		}
+		if(url_->query) {
+			php::array query(4);
+			php::callable("parse_str")({php::string(url_->query), query.make_ref()});
+			set("query", query);
+		}
+		php::array header(4);
+		for(auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
+			php::string key {i->name_string().data(), i->name_string().size()};
+			php::string val {i->value().data(), i->value().size()};
+
+			if(key.size() == 6 && strncasecmp(key.c_str(), "cookie", 6) == 0) {
+				php::array cookie(4);
+				parser::separator_parser<std::string, php::buffer> p1('\0','\0','=','\0','\0',';', [&cookie] (std::pair<std::string, php::buffer> entry) {
+					if(entry.second.size() > 0) {
+						entry.second.shrink( php::url_decode_inplace(entry.second.data(), entry.second.size()) );
+						cookie.set(entry.first, php::string(std::move(entry.second)));
+					}else{
+						cookie.set(entry.first, php::string(0));
+					}
+				});
+				p1.parse(val.c_str(), val.size());
+				p1.parse(";", 1); // 结尾分隔符可能不存在(可以认为数据行可能不完整)
+				set("cookie", cookie);
+			}
+			// TODO 多个同名 HEADER 的处理
+			php::lowercase_inplace(key.data(), key.size());
+			header.set(key, val);
+		}
+		set("header", header);
+
+		php::string body = ctr_.body();
+		if(body.typeof(php::TYPE::STRING)) {
+			auto ctype = ctr_.find(boost::beast::http::field::content_type);
+			if(ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0) {
+				set("rawBody", body); // 不在 multipart 时保留原始数据
+			}
+			if(ctype != ctr_.end()) { // 存在时按照类型进行解析
+				set("body", ctype_decode(ctype->value(), body));
+			}else{ // 不存在与 rawBody 相同
+				set("body", body);
+			}
+		}
+	}
+}
+}
diff --git a/src/http/server_request.h b/src/http/server_request.h
new file mode 100644
index 0000000..0f462a5
--- /dev/null
+++ b/src/http/server_request.h
@@ -0,0 +1,15 @@
+#pragma once
+
+namespace flame::http {
+	class handler;
+	class server_request: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		php::value __construct(php::parameters& params);
+		~server_request();
+	private:
+		void build_ex(const boost::beast::http::message<true, value_body<true>>& ctr);
+
+        friend class _handler;
+    };
+}
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
new file mode 100644
index 0000000..581eaaf
--- /dev/null
+++ b/src/http/server_response.cpp
@@ -0,0 +1,247 @@
+#include "../coroutine.h"
+#include "../time/time.h"
+#include "_handler.h"
+#include "server_response.h"
+
+namespace flame::http {
+	void server_response::declare(php::extension_entry& ext) {
+		php::class_entry<server_response> class_server_response("flame\\http\\server_response");
+		class_server_response
+			.property({"status", 200})
+			.property({"header", nullptr})
+			.property({"cookie", nullptr})
+			.property({"body",   nullptr})
+			.method<&server_response::__construct>("__construct", {}, php::PRIVATE)
+			.method<&server_response::__destruct>("__destruct")
+			.method<&server_response::set_cookie>("set_cookie", {
+				{"name", php::TYPE::STRING},
+				{"value", php::TYPE::STRING, false, true},
+				{"expire", php::TYPE::INTEGER, false, true},
+				{"path", php::TYPE::STRING, false, true},
+				{"domain", php::TYPE::STRING, false, true},
+				{"secure", php::TYPE::BOOLEAN, false, true},
+				{"httponly", php::TYPE::BOOLEAN, false, true},
+			})
+			.method<&server_response::write_header>("write_header", {
+				{"status", php::TYPE::INTEGER, false, true}
+			})
+			.method<&server_response::write>("write", {
+				{"chunk", php::TYPE::UNDEFINED}
+			})
+			.method<&server_response::end>("end", {
+				{"chunk", php::TYPE::UNDEFINED, false, true}
+			})
+			.method<&server_response::file>("file", {
+				{"root", php::TYPE::STRING},
+				{"path", php::TYPE::STRING},
+			});
+
+		ext.add(std::move(class_server_response));
+	}
+	server_response::server_response()
+	: status_(0) {
+
+	}
+	server_response::~server_response()
+	{
+
+	}
+	// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+	php::value server_response::__construct(php::parameters& params)
+	{
+		return nullptr;
+	}
+	php::value server_response::__destruct(php::parameters& params)
+	{
+		// 响应还未完全结束, 由 handler 代理 last_chunk 响应
+		if(handler_) 
+		{
+			coroutine_handler ch{coroutine::current};
+			handler_->finish(this, ch);
+		}
+		return nullptr;
+	}
+	php::value server_response::set_cookie(php::parameters& params) {
+		php::array cookie(4);
+		php::string name = params[0], val;
+		name.to_string();
+		cookie.set("name", name);
+		if(params.size() > 1) {
+			val = params[1];
+			val.to_string();
+		}else{
+			val = php::string("");
+		}
+		cookie.set("value", val);
+		if(params.size() > 2) {
+			// php::object expire = php::datetime(time::now() +  * 1000);
+			cookie.set("expire", params[2].to_integer());
+		}
+		if(params.size() > 3) {
+			php::string path = params[3];
+			path.to_string();
+			cookie.set("path", path);
+		}
+		if(params.size() > 4) {
+			php::string domain = params[4];
+			domain.to_string();
+			cookie.set("domain", domain);
+		}
+		if(params.size() > 5) {
+			bool secure = params[5];
+			cookie.set("secure", secure);
+		}
+		if(params.size() > 6) {
+			bool http_only = params[5];
+			cookie.set("http_only", http_only);
+		}
+
+		php::array cookies = get("cookie", true);
+		if(cookies.typeof(php::TYPE::NULLABLE)) {
+			cookies = php::array(4);
+		}
+		cookies.set(cookies.size(), cookie);
+		return nullptr;
+	}
+	// chunked encoding 用法
+	php::value server_response::write_header(php::parameters& params) {
+		if((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT)) {
+			throw php::exception(zend_ce_exception, "header already sent");
+		}
+		status_ |= STATUS_HEAD_SENT;
+
+		if(params.size() > 0) {
+			set("status", params[0].to_integer());
+		}
+		coroutine_handler ch{coroutine::current};
+		handler_->write_head(this, ch);
+		return nullptr;
+	}
+	php::value server_response::write(php::parameters& params) {
+		if(status_ & STATUS_BODY_END)
+		{
+			throw php::exception(zend_ce_exception, "response already done");
+		}
+		coroutine_handler ch{coroutine::current};
+		if (!(status_ & STATUS_HEAD_SENT))
+		{
+			status_ |= STATUS_HEAD_SENT;
+			handler_->write_head(this, ch);
+		}
+		status_ |= STATUS_BODY_SENT;
+		php::string chunk = params[0].to_string();
+		handler_->write_body(this, chunk, ch);
+		return nullptr;
+	}
+	php::value server_response::end(php::parameters& params) {
+		if (status_ & STATUS_BODY_END)
+		{
+			throw php::exception(zend_ce_exception, "response already done");
+		}
+		coroutine_handler ch{coroutine::current};
+		if (!(status_ & STATUS_HEAD_SENT))
+		{
+			status_ |= STATUS_HEAD_SENT;
+			handler_->write_head(this, ch);
+		}
+		if(params.size() > 0) {
+			status_ |= STATUS_BODY_SENT;
+			php::string chunk = params[0].to_string();
+			handler_->write_body(this, chunk, ch);
+		}else{
+			status_ |= STATUS_BODY_END;
+			handler_->write_end(this, ch);
+			handler_.reset();
+		}
+		return nullptr;
+	}
+	php::value server_response::file(php::parameters& params) {
+		if ((status_ & STATUS_BODY_END) || (status_ & STATUS_BODY_SENT))
+		{
+			throw php::exception(zend_ce_exception, "response already done");
+		}
+		coroutine_handler ch{coroutine::current};
+		if (!(status_ & STATUS_HEAD_SENT))
+		{
+			status_ |= STATUS_HEAD_SENT;
+			handler_->write_head(this, ch);
+		}
+		status_ |= STATUS_BODY_END | STATUS_BODY_SENT;
+
+		boost::filesystem::path root, file;
+		root += params[0].to_string();
+		file += params[1].to_string();
+
+		handler_->write_file(this, (root / file.lexically_normal()).string(), ch);
+		return nullptr;
+	}
+	
+	// 支持 content-length 形式的用法
+	void server_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
+		if(status_ & STATUS_BUILT) return; // 重复使用
+		status_ |= STATUS_BUILT; // 在 chunked_writer 中设置
+		ctr_.result( get("status").to_integer() ); // 非法 status_code 会抛出异常
+		php::array headers = get("header", true);
+		if(headers.typeof(php::TYPE::ARRAY)) {
+			for(auto i=headers.begin(); i!=headers.end(); ++i) {
+				php::string key { i->first };
+				i->second.to_string();
+
+				ctr_.set(boost::string_view(key.c_str(), key.size()), i->second);
+			}
+		}
+		ctr_.set(boost::beast::http::field::connection, ctr_.keep_alive() ? "keep-alive" : "close");
+		php::array cookies = get("cookie", true);
+		if(cookies.typeof(php::TYPE::ARRAY)) {
+			for(auto i=cookies.begin(); i!=cookies.end(); ++i) {
+				php::array  cookie = i->second;
+				std::string buffer;
+				buffer.append(cookie.get("name"));
+				buffer.push_back('=');
+				php::string value = cookie.get("value");
+				if(!value.empty()) {
+					buffer.push_back('"');
+					buffer.append(php::url_encode(value.c_str(), value.size()));
+					buffer.push_back('"');
+				}
+				if(cookie.exists("expire")) {
+					std::int64_t expire = cookie.get("expire");
+					// 范围 30 天内的数值, 按时间长度计算
+					if(expire < 30 * 86400) {
+						buffer.append("; Max-Age=", 10);
+						buffer.append(std::to_string(expire));
+						buffer.append("; Expire=", 9);
+						php::object date = php::datetime(expire * 1000 + std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count());
+						buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
+					}else{
+					// 超过上面范围, 按时间点计算
+						buffer.append("; Expire=", 9);
+						php::object date = php::datetime(expire * 1000);
+						buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
+					}
+				}
+				if(cookie.exists("path")) {
+					buffer.append("; Path=", 7);
+					buffer.append(cookie.get("path"));
+				}
+				if(cookie.exists("domain")) {
+					buffer.append("; Domain=", 9);
+					buffer.append(cookie.get("domain"));
+				}
+				if(!cookie.get("secure").empty()) {
+					buffer.append("; Secure", 8);
+				}
+				if(!cookie.get("http_only").empty()) {
+					buffer.append("; HttpOnly", 10);
+				}
+				ctr_.insert("Set-Cookie", buffer);
+			}
+		}
+CTYPE_AGAIN:
+		auto ctype = ctr_.find(boost::beast::http::field::content_type);
+		if(ctype == ctr_.end()) {
+			ctr_.set(boost::beast::http::field::content_type, "text/plain");
+			goto CTYPE_AGAIN;
+		}
+	}
+}
diff --git a/src/http/server_response.h b/src/http/server_response.h
new file mode 100644
index 0000000..8bd8e20
--- /dev/null
+++ b/src/http/server_response.h
@@ -0,0 +1,35 @@
+#pragma once
+#include "../vendor.h"
+#include "value_body.h"
+
+namespace flame::http {
+	class _handler;
+	class server_response: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+		// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+		php::value __construct(php::parameters& params);
+		php::value __destruct(php::parameters& params);
+		php::value set_cookie(php::parameters& params);
+		php::value write_header(php::parameters& params);
+		php::value write(php::parameters& params);
+		php::value end(php::parameters& params);
+		php::value file(php::parameters& params);
+		server_response();
+		~server_response();
+	private:
+		enum
+		{
+			STATUS_BUILT = 0x01,
+			STATUS_HEAD_SENT = 0x02,
+			STATUS_BODY_SENT = 0x04,
+			STATUS_BODY_END  = 0x08,
+		};
+		int status_;
+		std::shared_ptr<_handler> handler_; // 允许通过 $res 对象保留 handler 持续响应
+
+		void build_ex(boost::beast::http::message<false, value_body<false>> &ctr);
+
+		friend class _handler;
+	};
+}

From cb41d445b36bfc0a9474cea797a2093ae6ee7bcc Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 2 Dec 2018 21:02:00 +0800
Subject: [PATCH 022/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=85=B3=E9=97=AD?=
 =?UTF-8?q?=E6=B5=81=E7=A8=8B;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp        |  6 ++++++
 src/controller.h          |  5 ++++-
 src/controller_master.cpp | 14 +++++++++-----
 src/controller_master.h   |  1 -
 src/controller_worker.cpp | 23 +++++++++++++----------
 src/controller_worker.h   |  1 -
 src/coroutine.cpp         |  8 ++++----
 src/flame.cpp             | 10 +++++++---
 src/mysql/client.cpp      |  2 +-
 src/tcp/server.cpp        |  6 +++---
 10 files changed, 47 insertions(+), 29 deletions(-)

diff --git a/src/controller.cpp b/src/controller.cpp
index 01d71e0..115fa1a 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -72,6 +72,12 @@ namespace flame {
 		}
 		delete cbmap;
 		// 运行完毕
+		(status & controller_status::STATUS_EXCEPTION) ? exit(-1) : exit(0);
+		// 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
+	}
+
+	void controller::stop()
+	{
 		for (auto fn : stop_cb)
 		{
 			fn();
diff --git a/src/controller.h b/src/controller.h
index b39f6e3..fd688b0 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -8,7 +8,7 @@ namespace flame {
 	public:
 		boost::asio::io_context context_x;
 		boost::asio::io_context context_y;
-		zend_execute_data* core_execute_data;
+		zend_execute_data* default_execute_data;
 		enum class process_type
 		{
 			UNKNOWN = 0,
@@ -20,6 +20,8 @@ namespace flame {
 		{
 			STATUS_UNKNOWN = 0,
 			STATUS_INITIALIZED = 1,
+			STATUS_SHUTDOWN = 2,
+			STATUS_EXCEPTION = 4,
 		};
 		int status;
 		std::size_t    worker_size;
@@ -41,6 +43,7 @@ namespace flame {
 		void run();
 		controller *on_init(std::function<void(const php::array &options)> fn);
 		controller* on_stop(std::function<void ()> fn);
+		void stop();
 	};
 
 	extern std::unique_ptr<controller> gcontroller;
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 44fd8ef..972929b 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -73,8 +73,9 @@ namespace flame
                         }
                     });
                 }
-                sout_[i]->close();
-                eout_[i]->close();
+                worker_[i].reset();
+                sout_[i].reset();
+                eout_[i].reset();
             }) );
         
     }
@@ -134,7 +135,8 @@ namespace flame
             redirect_eout(i);
         }
         // 2. 监听信号进行日志重载或停止
-        signal_->async_wait([this] (const boost::system::error_code &error, int sig) {
+        signal_->async_wait([this](const boost::system::error_code &error, int sig) {
+            if(error) return;
             if(sig == SIGUSR2)
             {
                 reload_output();
@@ -144,8 +146,10 @@ namespace flame
                 // 信号的默认处理: 转给所有子进程
                 for(auto i=worker_.begin(); i!=worker_.end(); ++i)
                 {
-                    ::kill( (*i)->id(), SIGTERM );
+                    if(*i) ::kill( (*i)->id(), SIGTERM );
                 }
+                signal_.reset();
+                gcontroller->context_x.stop();
             }
         });
         // 3. 启动运行
@@ -169,7 +173,7 @@ namespace flame
             offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
             // 文件打开失败时不会抛出异常，需要额外的状态检查
 			if(!(*offile_)) {
-                std::clog << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
+                std::cerr << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
             }else{
                 return;
             }
diff --git a/src/controller_master.h b/src/controller_master.h
index 727709e..1968e5d 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -8,7 +8,6 @@ namespace flame
 		controller_master();
         void initialize(const php::array& options);
 		void run();
-
     private:
 		std::vector<std::unique_ptr<boost::process::child>>    worker_;
         std::vector<std::unique_ptr<boost::process::async_pipe>> sout_;
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index 0f00104..cf883ea 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -4,7 +4,7 @@
 namespace flame
 {
     controller_worker::controller_worker()
-    : signal_(new boost::asio::signal_set(gcontroller->context_x, SIGINT, SIGTERM))
+    : signal_(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM))
     {
 
     }
@@ -15,15 +15,18 @@ namespace flame
     void controller_worker::run()
     {
         // 1. 停止信号调用退出通知
-        signal_->async_wait([this](const boost::system::error_code &error, int sig)
+        signal_->async_wait([this] (const boost::system::error_code &error, int sig)
         {
-            signal_->clear();
-            
-            auto ft = gcontroller->cbmap->equal_range("quit");
-            for(auto i=ft.first; i!=ft.second; ++i)
-            {
-                i->second.call();
-            }
+            if(error) return;
+            signal_.reset();
+            boost::asio::post(gcontroller->context_x, [] () {
+                gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
+                auto ft = gcontroller->cbmap->equal_range("quit");
+                for (auto i = ft.first; i != ft.second; ++i)
+                {
+                    i->second.call();
+                }
+            });
         });
         // 工作线程的使用时随机的, 需要保持其一直存在
         auto work = boost::asio::make_work_guard(gcontroller->context_y);
@@ -38,8 +41,8 @@ namespace flame
         }
         // 3. 启动 context_x 运行
         gcontroller->context_x.run();
-        signal_.reset();
         // 4. 确认工作线程停止
+        signal_.reset();
         work.reset();
         for (int i = 0; i < thread_.size(); ++i)
         {
diff --git a/src/controller_worker.h b/src/controller_worker.h
index 438c70c..c2819f4 100644
--- a/src/controller_worker.h
+++ b/src/controller_worker.h
@@ -8,7 +8,6 @@ namespace flame
 		controller_worker();
         void initialize(const php::array& options);
 		void run();
-
 	private:
 		std::vector<std::thread*> thread_;
 		std::unique_ptr<boost::asio::signal_set> signal_;
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 4a7af97..0fa11ca 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -58,12 +58,12 @@ namespace flame
                     save_context(coroutine::global_context);
                     // 启动进入协程
                     coroutine::current = co_;
-                    zend_execute_data ex;
-                    std::memset(&ex, sizeof(zend_execute_data), 0);
-                    EG(current_execute_data) = &ex;
+                    EG(current_execute_data) = gcontroller->default_execute_data;
                     zend_vm_stack_init();
                     // 协程运行;
                     co_->fn_.call(/*ag*/);
+                    // 实际协程销毁可能会较晚, 保证在 Zend 引擎前释放
+                    co_->fn_ = nullptr;
                     // 协程运行完毕
                     zend_vm_stack_destroy();
                     coroutine::current = nullptr;
@@ -75,7 +75,7 @@ namespace flame
                         if (--coroutine::count == 0)
                         {
                             // 所有协程结束后退出
-                            gcontroller->context_x.stop();
+                            gcontroller->stop();
                         }
                     });
                     return std::move(co_->c2_);
diff --git a/src/flame.cpp b/src/flame.cpp
index 812a943..7c6bae9 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -24,7 +24,6 @@ namespace flame
         {
             options = params[1];
         }
-        gcontroller->core_execute_data = EG(current_execute_data);
         gcontroller->initialize(params[0], options);
         return nullptr;
     }
@@ -46,15 +45,20 @@ namespace flame
                     ++x;
                     i->second.call({ex});
                 }
+                // php::object obj = ex;
                 if(x==0)
                 {
+                    // std::cerr << "[" << time::iso() << "] (FATAL) " << obj.call("gettraceasstring") << "\n";
                     std::cerr << "[" << time::iso() << "] (FATAL) " << ex.what() << "\n";
                     boost::asio::post(gcontroller->context_x, [] () {
+                        gcontroller->status |= controller::controller_status::STATUS_EXCEPTION;
                         gcontroller->context_x.stop();
+                        gcontroller->context_y.stop();
                     });
                 }else
                 {
-                    std::cerr << "[" << time::iso() << "] (ERROR) " << ex.what() << "\n";
+                    // std::clog << "[" << time::iso() << "] (ERROR) " << obj.call("gettraceasstring") << "\n";
+                    std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << "\n";
                 }
             }
             return rv;
@@ -79,7 +83,7 @@ namespace flame
     {
         if(gcontroller->status & controller::STATUS_INITIALIZED)
         {
-            gcontroller->core_execute_data = EG(current_execute_data);
+            gcontroller->default_execute_data = EG(current_execute_data);
             gcontroller->run();
         }else{
             throw php::exception(zend_ce_type_error, "failed to run flame: not yet initialized (forget to call 'flame\\init()' ?)");
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 97449af..bb08a88 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -64,7 +64,7 @@ namespace flame::mysql
     }
     php::value client::__destruct(php::parameters &params)
     {
-        std::cout << "client destruct\n";
+        // std::cout << "client destruct\n";
         return nullptr;
     }
     php::value client::escape(php::parameters &params)
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index ecb63cf..a965882 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -34,9 +34,9 @@ namespace flame::tcp
     {
         std::string str_addr = params[0];
         auto pair = addr2pair(str_addr);
-        if(pair.second.empty())
+        if(pair.first.empty() || pair.second.empty())
         {
-            throw php::exception(zend_ce_type_error, "failed to bind tcp socket: missing port");
+            throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
         }
         boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
         addr_.address(addr);
@@ -97,7 +97,7 @@ namespace flame::tcp
                     }
                     catch(const php::exception& ex)
                     {
-                        std::cout << "[" << time::iso << "] (ERROR) " << ex << std::endl;
+                        std::clog << "[" << time::iso << "] (ERROR) " << ex.what() << std::endl;
                     }
                     return nullptr;
                 }));

From eafcb2b65b1eee07c3e67196a8e514e45194c082 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 2 Dec 2018 21:15:25 +0800
Subject: [PATCH 023/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=20HTTP=20=E6=9C=8D?=
 =?UTF-8?q?=E5=8A=A1=E7=AB=AF=E6=96=87=E6=A1=A3;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/http.php                | 172 ++++++++++++++++++++++++++++++++++++
 src/http/_handler.cpp       |   1 +
 src/http/server_request.cpp |   1 -
 test/exception_1.php        |   5 +-
 test/http_2.php             |  56 ++++++++++++
 test/warning_1.php          |   2 +-
 6 files changed, 234 insertions(+), 3 deletions(-)
 create mode 100644 test/http_2.php

diff --git a/doc/http.php b/doc/http.php
index 773dbfd..405edcf 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -63,4 +63,176 @@ class client_response {
      * @property string
      */
     public $rawBody;
+}
+
+class server {
+    /**
+     * 服务端本地监听地址
+     */
+    public $address;
+    /**
+     * 创建服务器并绑定地址
+     */
+    function __construct(string $address) {}
+    /**
+     * 每次 HTTP 请求在执行对应 path 的处理器前, 先执行下述设置的回调;
+     * 回调形式如下:
+     *  function callback(server_request $req, server_response $res, bool $match) {}
+     * 其中 $match 参数标识此请求是否匹配了定义的 path 处理器;
+     * 当 callback 函数返回 false 时, 将**不再**执行后续处理器
+     */
+    function before(callable $cb):server {}
+    /**
+     * 每次 HTTP 请求在执行对应 path 处理器后, 会执行下述设置的回调;
+     * 回调形式同 before();
+     * 注意: 若 before 回调或 path 回调返回了 false, 此 after 回调将不再执行;
+     */
+    function after(callable $cb):server {}
+    /**
+     * 设置一个处理 "PUT $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function put(string $path, callable $cb):server {}
+    /**
+     * 设置一个处理 "DELETE $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function delete(string $path, callable $cb):server {}
+    /**
+     * 设置一个处理 "POST $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function post(string $path, callable $cb):server {}
+    /**
+     * 设置一个处理 "PATCH $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function patch(string $path, callable $cb):server {}
+    /**
+     * 设置一个处理 "GET $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function get(string $path, callable $cb):server {}
+    /**
+     * 设置一个处理 "HEAD $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function head(string $path, callable $cb):server {}
+    /**
+     * 设置一个处理 "OPTIONS $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function options(string $path, callable $cb):server {}
+    
+    /**
+     * 启动服务器, 监听请求并执行对应回调
+     * 注意: 运行服务器将阻塞当前协程;
+     */
+    function run() {}
+    /**
+     * 停止服务器, 不再接收新的连接; 上述 run() 阻塞协程将恢复执行;
+     * 注意: 正在处理中的请求不收影响(继续处理);
+     */
+    function close() {}
+}
+
+class server_request {
+    /**
+     * 请求方法
+     * @property string
+     */
+    public $method = "GET";
+    /**
+     * 请求路径
+     * @property string
+     */
+    public $path = "/";
+    /**
+     * 请求参数, 使用 PHP 内置 parse_str() 函数解析得到;
+     * @property array
+     */
+    public $query;
+    /**
+     * 请求头; 所有头信息字段名被转换为**小写**形式;
+     * 暂不支持多项同名 Header 字段;
+     * @property array
+     */
+    public $header;
+    /**
+     * 请求 Cookie
+     * @property array
+     */
+    public $cookie;
+    /**
+     * 请求体, 以下几种 Content-Type 时自动解析为关联数组:
+     *  * "application/json"
+     *  * "application/x-www-form-urlencoded"
+     *  * "multipart/form-data"
+     * 其他类型时与 $rawBody 相同;
+     * @property mixed
+     */
+    public $body;
+    /**
+     * 原始请求体
+     * @property string
+     */
+    public $rawBody;
+    /**
+     * 用户可设置的请求关联数据;
+     * 例如: 在 before 回调中从 Cookie 中提取用户信息放在 $req->data["user"] 中, 后续处理器即可访问 $req->data["user"] 获取户信息;
+     * @property array
+     */
+    public $data;
+}
+
+/**
+ * 服务端响应对象, 提供接口向客户端返回数据
+ */
+class server_response {
+    /**
+     * 响应状态码, 可以直接设置; 也可以在 Transfer-Encoding: chunked 模式时使用 write_header 指定;
+     * @property int
+     */
+    public $status = 200;
+    /**
+     * 响应头信息, Key/Val 形式
+     * @property array
+     */
+    public $header;
+    // public $cookie;
+    /**
+     * 响应体, 用于在 Content-Length: xxx 响应模式下返回数据;
+     * @property mixed 任意数据, 实际响应会序列化此数据; 以下几种 Content-Type 存在内置的序列化:
+     *  * "application/json" - 使用 json_encode 进行序列化;
+     *  * "application/x-www-form-urlencoded" - 使用 http_build_query 进行序列化;
+     * 其他类型强制转换为文本类型后响应;
+     */
+    public $body;
+    /**
+     * 设置返回的 Set-Cookie 项;
+     * 参数功能与 PHP 内置 set_cookie 函数类似;
+     */
+    function set_cookie(string $name, string $value = null, int $expire = 0, string $path = "/", string $domain = "", bool $secure = false, bool $http_only = false) {}
+    /**
+     * 返回响应头, 用于启用 Transfer-Encoding: chunked 模式, 准备持续响应数据;
+     * @param int $status 当参数存在时, 覆盖上述 $res->status 属性响应码;
+     * 
+     * Transfer-Encoding: chunked 模式可用于 EventSource 等持续响应;
+     */
+    function write_header($status = 0) {}
+    /**
+     * 在 Transfer-Encoding: chunked 模式下, 返回一个 chunk 数据块;
+     */
+    function write(string $chunk) {}
+    /**
+     * 在 Transfer-Encoding: chunked 模式下, 结束响应过程 (返回最后一个结束块);
+     * @param string $chunk 可选, 调用上述 write() 返回一个数据块;
+     */
+    function end(string $chunk = null) {}
+    /**
+     * 响应一个文件的内容; 一般需要自行设置对应文件的 Content-Type 以保证浏览器能正常渲染;
+     * 下述参数中的 $path 将会被正常化处理, 以保证其不超出 $root 指定的根目录范围;
+     * 注意: 此功能一般仅用于调试或少量文件访问使用, 大量生产环境请考虑使用 nginx 代为处理静态文件;
+     */
+    function file(string $root, string $path) {}
 }
\ No newline at end of file
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index ffd0b57..9721e45 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -24,6 +24,7 @@ namespace flame::http
     {
         // 第一次请求或允许复用
         if ((gcontroller->status & controller::controller_status::STATUS_SHUTDOWN)
+                || svr_ptr->closed_
                 || req_ && !req_->keep_alive() || res_ && !res_->keep_alive()) 
         {
             req_.reset();
diff --git a/src/http/server_request.cpp b/src/http/server_request.cpp
index caed640..5f873af 100644
--- a/src/http/server_request.cpp
+++ b/src/http/server_request.cpp
@@ -9,7 +9,6 @@ namespace http {
 		php::class_entry<server_request> class_server_request(
 			"flame\\http\\server_request");
 		class_server_request
-			.property({"timeout", 3000})
 			.property({"method", "GET"})
 			.property({"path", "/"})
 			.property({"query", nullptr})
diff --git a/test/exception_1.php b/test/exception_1.php
index e7e39c3..5a4ccaa 100644
--- a/test/exception_1.php
+++ b/test/exception_1.php
@@ -5,10 +5,13 @@ function test() {
     flame\time\sleep(1000);
     throw new Exception("Aaaaaaa");
 }
+function test2() {
+    test();
+}
 
 flame\go(function() {
     flame\time\sleep(1000);
-    test();
+    test2();
 });
 
 // 可选
diff --git a/test/http_2.php b/test/http_2.php
new file mode 100644
index 0000000..fa3f7ab
--- /dev/null
+++ b/test/http_2.php
@@ -0,0 +1,56 @@
+<?php
+flame\init("http_2");
+
+$server = new flame\http\server(":::8370");
+$poll;
+flame\go(function() use($server, &$poll) {
+
+    $server->before(function($req, $res, $m) {
+        $req->data["start"] = flame\time\now();
+        if(!$m) {
+            $res->status = 404;
+            $res->body = "Not Found\n";
+            return false; // 阻止后续 handle / after 的运行
+        }
+    })->get("/", function($req, $res) {
+        $res->body = json_encode($req);
+    })->get("/poll", function($req, $res) use(&$poll) {
+        $res->header["Content-Type"] = "text/event-stream";
+        $res->header["Cache-Control"] = "no-cache";
+        $res->write_header(200);
+        $poll = $res;
+        // flame\go(function() use($res) {
+        //     var_dump($res);
+        //     for($i=0;$i<50;++$i) {
+        //         flame\time\sleep(1000);
+        //         echo "write\n";
+        //         $res->write("event: time\n");
+        //         $res->write("data: ".flame\time\now()."\n\n");
+        //     }
+        //     echo "done1\n";
+        // });
+    })->get("/push", function($req, $res) use(&$poll) {
+        if($poll) {
+            $poll->write("event: time\n");
+            $poll->write("data: ".flame\time\now()."\n\n");
+            $res->body = true;
+        }else{
+            $res->body = false;
+        }
+    })->get("/abc", function($req, $res) {
+        $res->header["Content-Type"] = "text/html";
+        $res->file(__DIR__, "/coroutine_1.php");
+    })->after(function($req, $res, $m) {
+        $end = flame\time\now();
+        // echo "elapsed: ", ($end - $req->data["start"]), "ms\n";
+    });
+    $server->run();
+    echo "done2\n";
+});
+
+flame\on("quit", function() use($server) {
+    echo "quit\n";
+    $server->close();
+});
+
+flame\run();
\ No newline at end of file
diff --git a/test/warning_1.php b/test/warning_1.php
index 50d74fc..ab7b2c4 100644
--- a/test/warning_1.php
+++ b/test/warning_1.php
@@ -1,5 +1,5 @@
 <?php
-flame\init("exception_1");
+flame\init("warning_1");
 
 flame\go(function() {
     flame\time\sleep(1000);

From 367a3f91a4b6c6a6f24951044ddc2cb6ebc9636e Mon Sep 17 00:00:00 2001
From: halivor <hongliang186@aliyun.com>
Date: Sun, 2 Dec 2018 23:06:12 +0800
Subject: [PATCH 024/146] =?UTF-8?q?*=20=E4=BC=98=E5=8C=96http=E8=AF=B7?=
 =?UTF-8?q?=E6=B1=82=E6=B5=81=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* 优化http请求流程
---
 src/flame.cpp                 |   1 -
 src/http/_connection_pool.cpp | 103 +++++++++++++++++++---------------
 src/http/_connection_pool.h   |  35 ++++++------
 src/http/client.cpp           |  12 ++--
 src/http/client_request.h     |   3 +-
 test/http_1.php               |  98 +++++++++++++++++++-------------
 6 files changed, 143 insertions(+), 109 deletions(-)

diff --git a/src/flame.cpp b/src/flame.cpp
index 7c6bae9..bad73d0 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -155,7 +155,6 @@ extern "C"
             flame::tcp::declare(ext);
             // flame::udp::declare(ext);
             flame::http::declare(ext);
-            
         }
         else
         {
diff --git a/src/http/_connection_pool.cpp b/src/http/_connection_pool.cpp
index 5184179..a340106 100644
--- a/src/http/_connection_pool.cpp
+++ b/src/http/_connection_pool.cpp
@@ -15,12 +15,13 @@ namespace flame::http
     _connection_pool::~_connection_pool() {
     }
     std::shared_ptr<tcp::socket>
-    _connection_pool::acquire(std::shared_ptr<url> url, int32_t timeout, coroutine_handler &ch) {
+    _connection_pool::acquire(std::shared_ptr<url> url, time_t_ ts,coroutine_handler &ch) {
         boost::system::error_code ec_;
 
         // 超时预处理
-        boost::asio::steady_timer tmo(gcontroller->context_x);
         auto cc = false;
+        auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(ts - std::chrono::steady_clock::now()).count();
+        boost::asio::steady_timer tmo(gcontroller->context_x);
         tmo.expires_after(std::chrono::milliseconds(timeout));
         tmo.async_wait([&cc, &ch, &ec_](const boost::system::error_code &ec) {
             if(ec == boost::asio::error::operation_aborted)
@@ -32,11 +33,11 @@ namespace flame::http
 
         std::shared_ptr<tcp::socket> c;
         auto type = url->schema + url->host + std::to_string(url->port);
-        while(cp_.count(type) > 0) {
-            auto it = cp_.find(type);
-            if(it != cp_.end()) {
+        while(p_.count(type) > 0) {
+            auto it = p_.find(type);
+            if(it != p_.end()) {
                 auto s = it->second.first;
-                cp_.erase(it);
+                p_.erase(it);
                 s->async_wait(tcp::socket::wait_write, ch[ec_]);
                 if(!ec_) {
                     c = s;
@@ -50,15 +51,17 @@ namespace flame::http
         // 未获取到连接，且连接数达到最大
         if(!c) {
             if(ps_[type] >= max_) {
-                auto cb = await_[type].emplace_back([&c, &ch](std::shared_ptr<tcp::socket> s) {
+                auto cb = wl_[type].emplace_back([&c, &ch](std::shared_ptr<tcp::socket> s) {
                     c = s;
                     ch.resume();
                 });
                 ch.suspend();
+                // 此处可能由于释放的连接已关闭
+                // 导致此处连接为空，而需要重建连接
                 if(cc) {
-                    for(auto i = await_[type].begin(); i != await_[type].end(); ++i ) {
+                    for(auto i = wl_[type].begin(); i != wl_[type].end(); ++i ) {
                         if(&(*i) == &cb) {
-                            await_[type].erase(i);
+                            wl_[type].erase(i);
                             break;
                         }
                     }
@@ -67,20 +70,22 @@ namespace flame::http
         }
 DONE:
         if(cc == true) {
-
-            throw php::exception(zend_ce_exception, "create connection timeout");
+            throw php::exception(zend_ce_exception, (boost::format("timeout to acquire new connection, host %1%, connections %2%, connection limit %3%") % (url->host + std::to_string(url->port)) % ps_[type] % max_).str());
         }
         tmo.cancel();
         if(c)
             return c;
 
-        return create(url, timeout, ch);
+        return create(url, ts, ch);
     }
-    std::shared_ptr<tcp::socket> _connection_pool::create(std::shared_ptr<url> u, int timeout, coroutine_handler& ch) {
+
+    std::shared_ptr<tcp::socket>
+    _connection_pool::create(std::shared_ptr<url> u, time_t_ ts, coroutine_handler& ch) {
         boost::system::error_code ec_;
         // 超时预处理
-        boost::asio::steady_timer tmo(gcontroller->context_x);
         bool cc = false;
+        auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(ts - std::chrono::steady_clock::now()).count();
+        boost::asio::steady_timer tmo(gcontroller->context_x);
         tmo.expires_after(std::chrono::milliseconds(timeout));
         tmo.async_wait([&cc, &ch, &ec_] (const boost::system::error_code &ec) {
             if(ec == boost::asio::error::operation_aborted) {
@@ -120,25 +125,24 @@ namespace flame::http
 END:
         if(cc == true) {
             --ps_[type];
-            throw php::exception(zend_ce_exception, "create new connection timeout");
+            throw php::exception(zend_ce_exception, (boost::format("timeout to create new connection, host %1%, connections %2%, connection limit %3%") % (u->host + std::to_string(u->port)) % ps_[type] % max_).str());
         } 
         tmo.cancel();
         if(ec_) {
             --ps_[type];
-            throw php::exception(zend_ce_exception, "create new connection timeout");
+            throw php::exception(zend_ce_exception, (boost::format("failed to create new connection: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
         }
         return c;
     }
 
-    php::value _connection_pool::execute(
-            std::string type,
-            std::shared_ptr<tcp::socket> c, 
-            boost::beast::http::message<true, value_body<true>> &ctr_, 
-            int32_t timeout, 
-            coroutine_handler &ch) {
-        boost::system::error_code ec_;
+    php::value _connection_pool::execute(client_request* req, time_t_ ts, coroutine_handler &ch) {
+        auto c = acquire(req->url_, ts, ch);
+
         // 构建并发送请求
-        bool cc = false;
+        bool cc      = false;
+        auto type    = req->url_->schema + req->url_->host + std::to_string(req->url_->port);
+        auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(ts - std::chrono::steady_clock::now()).count();
+        boost::system::error_code ec_;
         boost::asio::steady_timer tmo(gcontroller->context_x);
         tmo.expires_after(std::chrono::milliseconds(timeout));
         tmo.async_wait([&ch, &cc, &ec_](const boost::system::error_code &ec) {
@@ -149,16 +153,20 @@ namespace flame::http
             ch.resume();
         });
 
-        boost::beast::http::async_write(*c, ctr_, [&cc, &ch, &ec_](const boost::system::error_code& ec, std::size_t n) {
+        boost::beast::http::async_write(*c, req->ctr_, [&cc, &ch, &ec_](const boost::system::error_code& ec, std::size_t n) {
             if(cc == true) return ;
             ec_ = ec;
             ch.resume();
         });
         ch.suspend();
-        if(cc)
+        if(cc) {
+            --ps_[type];
             throw php::exception(zend_ce_exception, "failed to write request: timeout");
-        if(ec_)
+        }
+        if(ec_) {
+            --ps_[type];
             throw php::exception(zend_ce_exception, (boost::format("failed to write request: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
+        }
 
         // 构建并读取响应
         auto res_ref = php::object(php::class_entry<client_response>::entry());
@@ -171,30 +179,36 @@ namespace flame::http
         });
 
         ch.suspend();
-        if(cc)
+        if(cc) {
             throw php::exception(zend_ce_exception, "failed to read response: timeout");
-        if(ec_)
+            --ps_[type];
+        }
+        if(ec_) {
             throw php::exception(zend_ce_exception, (boost::format("failed to read response: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
+            --ps_[type];
+        }
+
         res_->build_ex();
         tmo.cancel();
-        if(res_->ctr_.keep_alive() && ctr_.keep_alive())
+
+        if(res_->ctr_.keep_alive() && req->ctr_.keep_alive())
             release(type, c);
-        else {
-            --ps_[type];
+        else
             release(type, nullptr);
-        }
         return res_ref;
     }
-    void _connection_pool::release(std::string type, std::shared_ptr<tcp::socket> s)
-    {
-        if (await_[type].empty()) {
+
+    void _connection_pool::release(std::string type, std::shared_ptr<tcp::socket> s) {
+        if(!s)
+            --ps_[type];
+        if (wl_[type].empty()) {
             // 无等待分配的请求
-            if(!s)
-                cp_.insert(std::make_pair(type, std::make_pair(s, std::chrono::steady_clock::now())));
+            if(s)
+                p_.insert(std::make_pair(type, std::make_pair(s, std::chrono::steady_clock::now())));
         } else {
             // 立刻分配使用
-            std::function<void(std::shared_ptr<tcp::socket>)> cb = await_[type].front();
-            await_[type].pop_front();
+            std::function<void(std::shared_ptr<tcp::socket>)> cb = wl_[type].front();
+            wl_[type].pop_front();
             cb(s);
         }
     }
@@ -203,17 +217,18 @@ namespace flame::http
         tm_.async_wait([this] (const boost::system::error_code &ec) {
             auto now = std::chrono::steady_clock::now();
             if(ec) return; // 当前对象销毁时会发生对应的 abort 错误
-            for (auto i = cp_.begin(); i != cp_.end();) {
+
+            for (auto i = p_.begin(); i != p_.end();) {
                 auto itvl = std::chrono::duration_cast<std::chrono::seconds>(now - i->second.second);
                 if(itvl.count() > 30) {
                     --ps_[i->first];
-                    i = cp_.erase(i);
+                    i = p_.erase(i);
                 } else
                     ++i;
             }
-            for(auto i = await_.begin(); i != await_.end(); ) {
+            for(auto i = wl_.begin(); i != wl_.end(); ) {
                 if(i->second.empty())
-                    i = await_.erase(i);
+                    i = wl_.erase(i);
                 else
                     ++i;
             }
diff --git a/src/http/_connection_pool.h b/src/http/_connection_pool.h
index f5de63e..47a2682 100644
--- a/src/http/_connection_pool.h
+++ b/src/http/_connection_pool.h
@@ -5,27 +5,24 @@ namespace flame::http
 {
     class _connection_pool : public std::enable_shared_from_this<_connection_pool>
     {
-        public:
-            _connection_pool(uint32_t cph);
-            ~_connection_pool();
-            std::shared_ptr<tcp::socket> acquire(std::shared_ptr<url> url, int32_t timeout, coroutine_handler &ch);
-            std::shared_ptr<tcp::socket> create(std::shared_ptr<url> u, int timeout, coroutine_handler& ch);
-            php::value execute(std::string type, std::shared_ptr<tcp::socket> c, 
-                    boost::beast::http::message<true, value_body<true>> &ctr_, 
-                    int32_t timeout, 
-                    coroutine_handler &ch);
-            void release(std::string type, std::shared_ptr<tcp::socket> s);
-            void sweep();
-        private:
+    public:
+        _connection_pool(uint32_t cph);
+        ~_connection_pool();
+        using time_t_ = std::chrono::time_point<std::chrono::steady_clock>;
+        std::shared_ptr<tcp::socket> acquire(std::shared_ptr<url> url, time_t_ timeout, coroutine_handler &ch);
+        std::shared_ptr<tcp::socket> create(std::shared_ptr<url> u, time_t_ timeout, coroutine_handler& ch);
+        php::value execute(client_request* req, time_t_ ts, coroutine_handler &ch);
+        void release(std::string type, std::shared_ptr<tcp::socket> s);
+        void sweep();
+    private:
 
-            const std::uint32_t max_;
+        const std::uint32_t max_;
 
-            boost::asio::ip::tcp::resolver resolver_;
-            using time_t = std::chrono::time_point<std::chrono::steady_clock>;
-            std::multimap<std::string, std::pair<std::shared_ptr<tcp::socket>, time_t>> cp_; // connection_pool
-            std::map<std::string, int32_t> ps_; // pool_size
-            std::map<std::string, std::list<std::function<void(std::shared_ptr<boost::asio::ip::tcp::socket>)>>> await_;
-            boost::asio::steady_timer tm_;
+        boost::asio::ip::tcp::resolver resolver_;
+        std::map<std::string, int32_t> ps_; // pool_size
+        std::multimap<std::string, std::pair<std::shared_ptr<tcp::socket>, time_t_>> p_; // connection pool
+        std::map<std::string, std::list<std::function<void(std::shared_ptr<tcp::socket>)>>> wl_; // await list
+        boost::asio::steady_timer tm_;
     };
 
 }
diff --git a/src/http/client.cpp b/src/http/client.cpp
index 5e86c64..e64ff54 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -57,13 +57,13 @@ namespace flame::http {
         req_->build_ex();
 
         auto type    = req_->url_->schema + req_->url_->host + std::to_string(req_->url_->port);
-        auto timeout = static_cast<int>(req_->get("timeout"));
+        auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(static_cast<int>(req_->get("timeout")));
         if(req_->url_->schema.compare("http") == 0) {
-            return cp_->execute(type, cp_->acquire(req_->url_, timeout, ch), req_->ctr_, timeout, ch);
-        } else if(req_->url_->schema.compare("https") == 0) {
-            return cp_->execute(type, cp_->acquire(req_->url_, timeout, ch), req_->ctr_, timeout, ch);
-        }
-        throw php::exception(zend_ce_exception, "request protocol not supported");
+            return cp_->execute(req_, timeout, ch);
+        } /*else if(req_->url_->schema.compare("https") == 0) {*/
+            //return cp_->execute(req_, timeout, ch);
+        /*}*/
+        throw php::exception(zend_ce_exception, (boost::format("HTTP schema \'%1%\' not implemented)") % req_->url_->schema).str());
     }
     php::value client::exec(php::parameters& params) {
         return exec_ex(params[0]);
diff --git a/src/http/client_request.h b/src/http/client_request.h
index 7155511..73cf68e 100644
--- a/src/http/client_request.h
+++ b/src/http/client_request.h
@@ -12,5 +12,6 @@ namespace flame::http {
 		std::shared_ptr<url> url_;
 		boost::beast::http::message<true, value_body<true>> ctr_;
 		friend class client;
-	};
+		friend class _connection_pool;
+    };
 }
diff --git a/test/http_1.php b/test/http_1.php
index 5e12e57..4b4fd3e 100644
--- a/test/http_1.php
+++ b/test/http_1.php
@@ -1,14 +1,63 @@
 <?php
 flame\init("http_1");
 
+flame\go(function() {
+    flame\http\get("http://127.0.0.1:8081/", 3000);
+    flame\http\post("http://127.0.0.1:8081/", "post", 3000);
+    flame\http\put("http://127.0.0.1:8081/", "put", 3000);
+    flame\http\delete("http://127.0.0.1:8081/", 3000);
+});
+// 使用独立连接测试
+flame\go(function() {
+    $opt["connection_per_host"] = 1;
+    $cli = new flame\http\client($opt);
+    for($i = 0; $i < 50; $i++) {
+        flame\go(function() use($i, $cli) {
+            //flame\time\sleep(rand(30, 50));
+            for($j = 0; $j < 40; ++$j) {
+                $req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 500);
+                $req->method = "GET";
+                $req->body = "aaaaaaaaaa";
+                $cli->exec($req);
+            //var_dump("user client -------" . $i);
+            }
+            //var_dump('sleep ------1------');
+        });
+        //flame\time\sleep(rand(10, 20));
+        //var_dump('sleep ------2------');
+    }
+    flame\time\sleep(3000);
+});
+flame\go(function() {
+    $opt["connection_per_host"] = 1;
+    $cli = new flame\http\client($opt);
+    for($i = 0; $i < 40; $i++) {
+        flame\go(function() use($i, $cli) {
+            flame\time\sleep(rand(30, 50));
+            for($j = 0; $j < 50; ++$j) {
+                $req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 500);
+                $req->method = "GET";
+                $req->body = "aaaaaaaaaa";
+                $cli->exec($req);
+            }
+            //var_dump("user client +++++++" . $i); 
+            //var_dump('sleep ------8------');
+        });
+        //flame\time\sleep(rand(10, 20));
+        //flame\time\sleep(30);
+        //var_dump('sleep ------9------');
+    }
+    //flame\time\sleep(2000);
+});
+
 // 清理超时连接
 // 清理被断开连接
 /*flame\go(function() {*/
-    //for($i = 0; $i < 100; ++$i) {
+    //for($i = 0; $i < 1; ++$i) {
         //flame\http\get("http://127.0.0.1:8081/");
         //flame\time\sleep(1000);
     //}
-//});
+/*});*/
 
 
 /*flame\go(function() {*/
@@ -36,53 +85,26 @@
 // 直接使用全局连接测试
 // core dump
 flame\go(function() {
-    for($i = 5000; $i < 5500; $i++) {
+    for($i = 0; $i < 50; $i++) {
         flame\go(function() use($i) {
-            //flame\time\sleep(rand(100,200));
-            flame\http\get("http://127.0.0.1:8081/", 30000);
-            var_dump("global client " . $i);
+            for($j = 0; $j < 40; ++$j) {
+                flame\time\sleep(rand(10,20));
+                flame\http\get("http://127.0.0.1:8081/get", 300);
+                var_dump("global client " . $i . " " . $j);
+            }
         });
     }
+    flame\time\sleep(3000);
 });
 
 flame\go(function() {
-    for($i = 1000; $i < 1500; $i++) {
+    for($i = 500; $i < 1500; $i++) {
         flame\go(function() use($i) {
             //flame\time\sleep(rand(100,200));
-            flame\http\get("http://127.0.0.1:8081/", 30000);
+            flame\http\get("http://127.0.0.1:8081/get", 300);
             var_dump("global client " . $i);
         });
     }
 });
 
-// 使用独立连接测试
-/*flame\go(function() {*/
-    //$opt["connection_per_host"] = 1;
-    //$cli = new flame\http\client($opt);
-    //for($i = 0; $i < 100; $i++) {
-        //flame\go(function() use($cli) {
-            //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
-            //$req->method = "GET";
-            //$req->body = "aaaaaaaaaa";
-            //$cli->exec($req);
-        //});
-        //flame\time\sleep(100);
-    //}
-    //echo "user client";
-//});
-//flame\go(function() {
-    //$opt["connection_per_host"] = 1;
-    //$cli = new flame\http\client($opt);
-    //for($i = 0; $i < 100; $i++) {
-        //flame\go(function() use($cli) {
-            //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
-            //$req->method = "GET";
-            //$req->body = "aaaaaaaaaa";
-            //$cli->exec($req);
-        //});
-        //flame\time\sleep(100);
-    //}
-    //echo "user client";
-/*});*/
-
 flame\run();

From 03f593d7469b5c045d658c9be26b12dce336ff2c Mon Sep 17 00:00:00 2001
From: halivor <hongliang186@aliyun.com>
Date: Tue, 4 Dec 2018 15:29:11 +0800
Subject: [PATCH 025/146] =?UTF-8?q?*=20=E4=BF=AE=E5=A4=8D=E8=B6=85?=
 =?UTF-8?q?=E6=97=B6=E5=8F=8A=E8=BF=9E=E6=8E=A5=E6=95=B0=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* 修复由于超时引起同时调用 resume 的问题
* 修复连接数限制失败
---
 src/coroutine.cpp             |  16 +-
 src/coroutine.h               |   3 +-
 src/http/_connection_pool.cpp | 271 +++++++++++++++++-----------------
 src/http/_connection_pool.h   |  37 ++++-
 src/http/client.cpp           |   4 +-
 src/http/http.cpp             |   1 -
 test/http_1.php               | 183 ++++++++++++-----------
 7 files changed, 279 insertions(+), 236 deletions(-)

diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 0fa11ca..c169b64 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -115,11 +115,13 @@ namespace flame
     coroutine_handler::coroutine_handler()
         : co_(nullptr)
         , er_(nullptr)
+        , stat_(new std::atomic<int>(0))
     {
     }
     coroutine_handler::coroutine_handler(std::shared_ptr<coroutine> co)
         : co_(co)
         , er_(nullptr)
+        , stat_(new std::atomic<int>(0))
     {
     }
     coroutine_handler::~coroutine_handler()
@@ -129,11 +131,13 @@ namespace flame
     void coroutine_handler::reset()
     {
         co_.reset();
+        *stat_ = 0;
     }
     void coroutine_handler::reset(std::shared_ptr<coroutine> co)
     {
         assert(co_ == nullptr);
-        co_ = co;
+        co_   = co;
+        *stat_ = 0;
     }
     coroutine_handler::operator bool() const
     {
@@ -144,7 +148,7 @@ namespace flame
         if(er_) *er_ = e;
         co_->len_ = n;
 
-        boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
+        resume();
     }
     coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e)
     {
@@ -153,15 +157,17 @@ namespace flame
     }
     void coroutine_handler::resume()
     {
-        boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
+        if(--*stat_ == 0)
+            boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
     void coroutine_handler::suspend()
     {
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
-        co_->suspend();
+        if(++*stat_ == 1)
+            co_->suspend();
     }
     bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2)
     {
         return ch1.co_ < ch2.co_;
     }
-}
\ No newline at end of file
+}
diff --git a/src/coroutine.h b/src/coroutine.h
index ef252f3..932ad24 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -54,6 +54,7 @@ namespace flame {
 		void suspend();
 		boost::system::error_code* er_;
 		std::shared_ptr<coroutine> co_;
+		std::shared_ptr<std::atomic<int>> stat_;
 
 		friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
 
@@ -81,4 +82,4 @@ namespace boost::asio
 	private:
 		::flame::coroutine_handler &ch_;
 	};
-} // namespace boost::asio
\ No newline at end of file
+} // namespace boost::asio
diff --git a/src/http/_connection_pool.cpp b/src/http/_connection_pool.cpp
index a340106..273d667 100644
--- a/src/http/_connection_pool.cpp
+++ b/src/http/_connection_pool.cpp
@@ -14,204 +14,166 @@ namespace flame::http
 
     _connection_pool::~_connection_pool() {
     }
-    std::shared_ptr<tcp::socket>
-    _connection_pool::acquire(std::shared_ptr<url> url, time_t_ ts,coroutine_handler &ch) {
-        boost::system::error_code ec_;
-
-        // 超时预处理
-        auto cc = false;
-        auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(ts - std::chrono::steady_clock::now()).count();
-        boost::asio::steady_timer tmo(gcontroller->context_x);
-        tmo.expires_after(std::chrono::milliseconds(timeout));
-        tmo.async_wait([&cc, &ch, &ec_](const boost::system::error_code &ec) {
-            if(ec == boost::asio::error::operation_aborted)
-                return;
-            cc = true;
-            ec_ = ec;
-            ch.resume();
-        });
 
-        std::shared_ptr<tcp::socket> c;
+    void _connection_pool::acquire(std::shared_ptr<url> url, std::shared_ptr<_connection_pool::context_t> ctx) {
         auto type = url->schema + url->host + std::to_string(url->port);
         while(p_.count(type) > 0) {
             auto it = p_.find(type);
             if(it != p_.end()) {
                 auto s = it->second.first;
                 p_.erase(it);
-                s->async_wait(tcp::socket::wait_write, ch[ec_]);
-                if(!ec_) {
-                    c = s;
+                s->async_wait(tcp::socket::wait_write, [ctx](const boost::system::error_code& ec) {
+                    if(ec == boost::asio::error::operation_aborted || (ctx->timeout())) return ;
+                    ctx->ec_ = ec;
+                    ctx->ch_.resume();
+                });
+                ctx->ch_.suspend();
+                if(ctx->timeout())
+                    goto DONE;
+                if(!ctx->ec_) {
+                    ctx->c_ = s;
                     goto DONE;
                 }
-                // 若连接存在异常，直接释放
-                --ps_[type];
+                // 连接异常，直接释放
+                release(type, nullptr);
             }
+            ctx->ec_ = boost::system::error_code{};
         }
 
         // 未获取到连接，且连接数达到最大
-        if(!c) {
-            if(ps_[type] >= max_) {
-                auto cb = wl_[type].emplace_back([&c, &ch](std::shared_ptr<tcp::socket> s) {
-                    c = s;
-                    ch.resume();
-                });
-                ch.suspend();
-                // 此处可能由于释放的连接已关闭
-                // 导致此处连接为空，而需要重建连接
-                if(cc) {
-                    for(auto i = wl_[type].begin(); i != wl_[type].end(); ++i ) {
-                        if(&(*i) == &cb) {
-                            wl_[type].erase(i);
-                            break;
-                        }
-                    }
-                }
-            }
+        if(!ctx->c_ && ps_[type] >= max_) {
+            // 此处可能由于释放的连接已关闭
+            // 导致此处连接为空，而需要重建连接
+            ctx->wait();
+            // 在此处release连接会导致连接数只增不减
+            // 此处超时不应该减连接数
+            if(!ctx->c_ && !ctx->timeout())
+                --ps_[type];
         }
 DONE:
-        if(cc == true) {
-            throw php::exception(zend_ce_exception, (boost::format("timeout to acquire new connection, host %1%, connections %2%, connection limit %3%") % (url->host + std::to_string(url->port)) % ps_[type] % max_).str());
-        }
-        tmo.cancel();
-        if(c)
-            return c;
-
-        return create(url, ts, ch);
+        // 引处不由于超时，并未获取到连接，所以无需释放连接
+        if(ctx->timeout())
+            throw php::exception(zend_ce_exception, (boost::format("timeout to acquire new connection, host %1%, connection limit %2%") % (url->host + std::to_string(url->port)) % max_).str());
+        if(!ctx->c_)
+            create(url, ctx);
     }
 
-    std::shared_ptr<tcp::socket>
-    _connection_pool::create(std::shared_ptr<url> u, time_t_ ts, coroutine_handler& ch) {
-        boost::system::error_code ec_;
-        // 超时预处理
-        bool cc = false;
-        auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(ts - std::chrono::steady_clock::now()).count();
-        boost::asio::steady_timer tmo(gcontroller->context_x);
-        tmo.expires_after(std::chrono::milliseconds(timeout));
-        tmo.async_wait([&cc, &ch, &ec_] (const boost::system::error_code &ec) {
-            if(ec == boost::asio::error::operation_aborted) {
-                return;
-            }
-            cc = true;
-            ec_ = ec;
-            ch.resume();
-        });
-
-        tcp::resolver::results_type eps_;
-        auto c = std::make_shared<tcp::socket>(gcontroller->context_x);
+    void _connection_pool::create(std::shared_ptr<url> u, std::shared_ptr<_connection_pool::context_t> ctx) {
+        auto c    = std::make_shared<tcp::socket>(gcontroller->context_x);
         auto type = u->schema + u->host + std::to_string(u->port);
+        tcp::resolver::results_type eps_;
         ++ps_[type];
         resolver_.async_resolve(
             u->host, 
             std::to_string(u->port), 
-            [&eps_, &ec_, &cc, &ch] (const boost::system::error_code& ec, tcp::resolver::results_type eps) {
-                if(cc){
-                    return ;
-                }
-                ec_ = ec;
+            [&eps_, ctx, c] (const boost::system::error_code& ec, tcp::resolver::results_type eps) {
+                if(ctx->timeout()) return ;
+                ctx->ec_ = ec;
                 eps_ = std::move(eps);
-                ch.resume();
+                ctx->ch_.resume();
         });
-        ch.suspend();
-        if(ec_ || cc)
+        ctx->ch_.suspend();
+        if(ctx->ec_ || ctx->timeout())
             goto END;
 
-        boost::asio::async_connect(*c, eps_, [&cc, &ch, &ec_] (const boost::system::error_code& ec, const tcp::endpoint& ep) {
-            if(cc)
-                return ;
-            ec_ = ec;
-            ch.resume();
+        ctx->to_ |= context_t::CONNECT;
+        boost::asio::async_connect(*c, eps_, [c, ctx] (const boost::system::error_code& ec, const tcp::endpoint& ep) {
+            if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
+            ctx->ec_ = ec;
+            ctx->ch_.resume();
         });
-        ch.suspend();
+        ctx->ch_.suspend();
 END:
-        if(cc == true) {
-            --ps_[type];
-            throw php::exception(zend_ce_exception, (boost::format("timeout to create new connection, host %1%, connections %2%, connection limit %3%") % (u->host + std::to_string(u->port)) % ps_[type] % max_).str());
+        if(ctx->timeout()) {
+            if(ctx->to_ & context_t::CONNECT)
+                c->cancel();
+            release(type, nullptr);
+            throw php::exception(zend_ce_exception, "timeout to create new connection");
         } 
-        tmo.cancel();
-        if(ec_) {
-            --ps_[type];
-            throw php::exception(zend_ce_exception, (boost::format("failed to create new connection: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
+        if(ctx->ec_) {
+            release(type, nullptr);
+            ctx->tm_.cancel();
+            throw php::exception(zend_ce_exception, (boost::format("failed to create new connection: (%1%) %2%") % ctx->ec_.value() % ctx->ec_.message()).str(), ctx->ec_.value());
         }
-        return c;
+        ctx->c_ = c;
     }
 
-    php::value _connection_pool::execute(client_request* req, time_t_ ts, coroutine_handler &ch) {
-        auto c = acquire(req->url_, ts, ch);
+    php::value _connection_pool::execute(client_request* req, int32_t timeout, coroutine_handler &ch) {
+        auto type = req->url_->schema + req->url_->host + std::to_string(req->url_->port);
+        auto ctx  = std::make_shared<_connection_pool::context_t>(ch, wl_[type], timeout);
+        ctx->expire();
+        acquire(req->url_, ctx);
 
         // 构建并发送请求
-        bool cc      = false;
-        auto type    = req->url_->schema + req->url_->host + std::to_string(req->url_->port);
-        auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(ts - std::chrono::steady_clock::now()).count();
-        boost::system::error_code ec_;
-        boost::asio::steady_timer tmo(gcontroller->context_x);
-        tmo.expires_after(std::chrono::milliseconds(timeout));
-        tmo.async_wait([&ch, &cc, &ec_](const boost::system::error_code &ec) {
-            if(ec == boost::asio::error::operation_aborted)
-                return;
-            cc = true;
-            ec_ = ec;
-            ch.resume();
+        boost::beast::http::async_write(*ctx->c_, req->ctr_, [ctx](const boost::system::error_code& ec, std::size_t n) {
+            if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
+            ctx->ec_ = ec;
+            ctx->ch_.resume();
         });
-
-        boost::beast::http::async_write(*c, req->ctr_, [&cc, &ch, &ec_](const boost::system::error_code& ec, std::size_t n) {
-            if(cc == true) return ;
-            ec_ = ec;
-            ch.resume();
-        });
-        ch.suspend();
-        if(cc) {
-            --ps_[type];
+        ctx->ch_.suspend();
+        if(ctx->timeout()) {
+            ctx->c_->cancel();
+            release(type, nullptr);
             throw php::exception(zend_ce_exception, "failed to write request: timeout");
         }
-        if(ec_) {
-            --ps_[type];
-            throw php::exception(zend_ce_exception, (boost::format("failed to write request: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
+        if(ctx->ec_) {
+            ctx->tm_.cancel();
+            release(type, nullptr);
+            throw php::exception(zend_ce_exception, (boost::format("failed to write request: (%1%) %2%") % ctx->ec_.value() % ctx->ec_.message()).str(), ctx->ec_.value());
         }
 
         // 构建并读取响应
         auto res_ref = php::object(php::class_entry<client_response>::entry());
         auto res_    = static_cast<client_response*>(php::native(res_ref));
         boost::beast::flat_buffer b;
-        boost::beast::http::async_read(*c, b, res_->ctr_, [&ch, &cc, &ec_] (const boost::system::error_code& ec, std::size_t n) {
-            if(cc == true) return ;
-            ec_ = ec;
-            ch.resume();
+        boost::beast::http::async_read(*ctx->c_, b, res_->ctr_, [ctx] (const boost::system::error_code& ec, std::size_t n) {
+            if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
+            ctx->ec_  = ec;
+            ctx->to_ |= context_t::DONE;
+            ctx->ch_.resume();
         });
 
-        ch.suspend();
-        if(cc) {
+        ctx->ch_.suspend();
+        if(ctx->timeout()) {
+            ctx->c_->cancel();
+            release(type, nullptr);
             throw php::exception(zend_ce_exception, "failed to read response: timeout");
-            --ps_[type];
         }
-        if(ec_) {
-            throw php::exception(zend_ce_exception, (boost::format("failed to read response: (%1%) %2%") % ec_.value() % ec_.message()).str(), ec_.value());
-            --ps_[type];
+        if(ctx->ec_) {
+            ctx->tm_.cancel();
+            release(type, nullptr);
+            throw php::exception(zend_ce_exception, (boost::format("failed to read response: (%1%) %2%") % ctx->ec_.value() % ctx->ec_.message()).str(), ctx->ec_.value());
         }
 
         res_->build_ex();
-        tmo.cancel();
+        ctx->tm_.cancel();
 
         if(res_->ctr_.keep_alive() && req->ctr_.keep_alive())
-            release(type, c);
+            release(type, ctx->c_);
         else
             release(type, nullptr);
         return res_ref;
     }
 
     void _connection_pool::release(std::string type, std::shared_ptr<tcp::socket> s) {
-        if(!s)
-            --ps_[type];
+        // read / write timeout，释放空连接
+        // 队列中有等待，导致callback->resume会在io run 排队，
+        // 导致其他协程通过 connections < max 
         if (wl_[type].empty()) {
             // 无等待分配的请求
             if(s)
                 p_.insert(std::make_pair(type, std::make_pair(s, std::chrono::steady_clock::now())));
+            else
+                --ps_[type];
         } else {
             // 立刻分配使用
-            std::function<void(std::shared_ptr<tcp::socket>)> cb = wl_[type].front();
+            auto ctx = wl_[type].front();
             wl_[type].pop_front();
-            cb(s);
+            // 释放空连接时，连接池计数在cb->resume后减小
+            (*ctx)(s);
         }
     }
+
     void _connection_pool::sweep() {
         tm_.expires_from_now(std::chrono::seconds(30));
         tm_.async_wait([this] (const boost::system::error_code &ec) {
@@ -221,7 +183,8 @@ namespace flame::http
             for (auto i = p_.begin(); i != p_.end();) {
                 auto itvl = std::chrono::duration_cast<std::chrono::seconds>(now - i->second.second);
                 if(itvl.count() > 30) {
-                    --ps_[i->first];
+                    //--ps_[i->first];
+                    release(i->first, nullptr);
                     i = p_.erase(i);
                 } else
                     ++i;
@@ -236,5 +199,49 @@ namespace flame::http
             sweep();
         });
     }
+
+    _connection_pool::context_t::context_t(coroutine_handler &ch, _connection_pool::queue_t& q, int32_t to) 
+    : ch_(ch)
+    , to_(NORMAL)
+    , tm_(gcontroller->context_x)
+    , q_(q)
+    , tp_(std::chrono::steady_clock::now() + std::chrono::milliseconds(to) ){
+    }
+    void _connection_pool::context_t::expire() {
+        boost::system::error_code ec;
+        tm_.expires_at(tp_);
+        tm_.async_wait([this, ctx = shared_from_this()](const boost::system::error_code &ec) {
+            if(ec == boost::asio::error::operation_aborted || (to_ & DONE)) return;
+            if(to_ & WAIT) {
+                for(auto i = q_.begin(); i != q_.end(); ++i ) {
+                    if( i->get() == this) {
+                        q_.erase(i);
+                        break;
+                    }
+                }
+                to_ &= ~WAIT;
+            }
+            to_ |= TIMEOUT;
+            ec_  = ec;
+            ch_.resume();
+        });
+    }
+    void _connection_pool::context_t::wait() {
+        to_ |= WAIT;
+        q_.push_back(shared_from_this());
+        ch_.suspend();
+    }
+    bool _connection_pool::context_t::timeout() {
+        return to_ & TIMEOUT;
+    }
+    void _connection_pool::context_t::operator()(std::shared_ptr<tcp::socket> c) {
+        // timeout的resume与此处的resume可能会在io_context上排队
+        // 在timeout时，需要先清理queue，再做resume
+        if(timeout()) return ;
+        to_ &= ~WAIT;
+        c_ = c;
+        ch_.resume();
+    }
 }
 
+
diff --git a/src/http/_connection_pool.h b/src/http/_connection_pool.h
index 47a2682..418c713 100644
--- a/src/http/_connection_pool.h
+++ b/src/http/_connection_pool.h
@@ -5,23 +5,50 @@ namespace flame::http
 {
     class _connection_pool : public std::enable_shared_from_this<_connection_pool>
     {
+        struct context_t;
     public:
         _connection_pool(uint32_t cph);
         ~_connection_pool();
-        using time_t_ = std::chrono::time_point<std::chrono::steady_clock>;
-        std::shared_ptr<tcp::socket> acquire(std::shared_ptr<url> url, time_t_ timeout, coroutine_handler &ch);
-        std::shared_ptr<tcp::socket> create(std::shared_ptr<url> u, time_t_ timeout, coroutine_handler& ch);
-        php::value execute(client_request* req, time_t_ ts, coroutine_handler &ch);
+        void acquire(std::shared_ptr<url> url, std::shared_ptr<context_t> ctx);
+        void create(std::shared_ptr<url> u, std::shared_ptr<context_t> ctx);
+        php::value execute(client_request* req, int32_t timeout, coroutine_handler &ch);
         void release(std::string type, std::shared_ptr<tcp::socket> s);
         void sweep();
     private:
 
+        using time_t_ = std::chrono::time_point<std::chrono::steady_clock>;
+        using queue_t = std::list<std::shared_ptr<context_t>>;
+
+        struct context_t : public std::enable_shared_from_this<context_t>{
+            context_t(coroutine_handler &ch, queue_t& q, int32_t to);
+            void expire();
+            void wait();
+            bool timeout();
+            void operator()(std::shared_ptr<tcp::socket>);
+
+            // enum status  normal canceled
+            enum {
+                NORMAL  = 0x0,
+                TIMEOUT = 0x1,
+                WAIT    = 0x1 << 1,
+                CONNECT = 0x1 << 2,
+                DONE    = 0x1 << 3
+            };
+            std::shared_ptr<tcp::socket> c_;
+            boost::asio::steady_timer    tm_;
+            boost::system::error_code    ec_;
+            coroutine_handler            ch_;
+            queue_t                      &q_;
+            int32_t                      to_;
+            time_t_                      tp_;
+        };
+
         const std::uint32_t max_;
 
         boost::asio::ip::tcp::resolver resolver_;
         std::map<std::string, int32_t> ps_; // pool_size
         std::multimap<std::string, std::pair<std::shared_ptr<tcp::socket>, time_t_>> p_; // connection pool
-        std::map<std::string, std::list<std::function<void(std::shared_ptr<tcp::socket>)>>> wl_; // await list
+        std::map<std::string, queue_t> wl_; // await list
         boost::asio::steady_timer tm_;
     };
 
diff --git a/src/http/client.cpp b/src/http/client.cpp
index e64ff54..324fa56 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -56,10 +56,8 @@ namespace flame::http {
         auto req_ = static_cast<client_request*>(php::native(req));
         req_->build_ex();
 
-        auto type    = req_->url_->schema + req_->url_->host + std::to_string(req_->url_->port);
-        auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(static_cast<int>(req_->get("timeout")));
         if(req_->url_->schema.compare("http") == 0) {
-            return cp_->execute(req_, timeout, ch);
+            return cp_->execute(req_, static_cast<int>(req_->get("timeout")), ch);
         } /*else if(req_->url_->schema.compare("https") == 0) {*/
             //return cp_->execute(req_, timeout, ch);
         /*}*/
diff --git a/src/http/http.cpp b/src/http/http.cpp
index 4139a33..a872df9 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -12,7 +12,6 @@
 #include "_handler.h"
 
 namespace flame::http {
-	//php::value client_ref;
     client* client_;
 	
 	void declare(php::extension_entry& ext) {
diff --git a/test/http_1.php b/test/http_1.php
index 4b4fd3e..8e9778b 100644
--- a/test/http_1.php
+++ b/test/http_1.php
@@ -1,110 +1,115 @@
 <?php
 flame\init("http_1");
 
-flame\go(function() {
-    flame\http\get("http://127.0.0.1:8081/", 3000);
-    flame\http\post("http://127.0.0.1:8081/", "post", 3000);
-    flame\http\put("http://127.0.0.1:8081/", "put", 3000);
-    flame\http\delete("http://127.0.0.1:8081/", 3000);
-});
+/*flame\go(function() {*/
+    //flame\http\get("http://127.0.0.1:8082/", 3000);
+    //flame\http\post("http://127.0.0.1:8082/", "post", 3000);
+    //flame\http\put("http://127.0.0.1:8082/", "put", 3000);
+    //flame\http\delete("http://127.0.0.1:8082/", 3000);
+/*});*/
 // 使用独立连接测试
 flame\go(function() {
-    $opt["connection_per_host"] = 1;
+    $opt["connection_per_host"] = 32;
     $cli = new flame\http\client($opt);
-    for($i = 0; $i < 50; $i++) {
+    for($i = 0; $i < 2000; $i++) {
         flame\go(function() use($i, $cli) {
-            //flame\time\sleep(rand(30, 50));
-            for($j = 0; $j < 40; ++$j) {
-                $req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 500);
-                $req->method = "GET";
-                $req->body = "aaaaaaaaaa";
-                $cli->exec($req);
-            //var_dump("user client -------" . $i);
+            for($j = 0; $j < 5; ++$j) {
+                try {
+                    var_dump('------ ' . $i . ' --- ' . $j . ' 1000');
+                    //$port = rand(8081, 8082);
+                    $port = 8082;
+                    $req = new flame\http\client_request('http://127.0.0.1:' . $port . '/get', null, 1500);
+                    $req->method = "GET";
+                    $cli->exec($req);
+                } catch(Exception $e) {
+                    echo $e;
+                }
             }
-            //var_dump('sleep ------1------');
         });
-        //flame\time\sleep(rand(10, 20));
-        //var_dump('sleep ------2------');
     }
-    flame\time\sleep(3000);
+    flame\go(function() {
+        $opt2["connection_per_host"] = 16;
+        $cli2 = new flame\http\client($opt2);
+        for($i = 0; $i < 100; $i++) {
+            flame\go(function() use($i, $cli2) {
+                for($j = 0; $j < 50; ++$j) {
+                    try {
+                        var_dump('++++++ ' . $i . ' + ' . $j . ' 1000');
+                        //$port = rand(8081, 8082);
+                        $port = 8082;
+                        $req = new flame\http\client_request('http://127.0.0.1:' . $port . '/get', null, 2000);
+                        $req->method = "GET";
+                        $cli2->exec($req);
+                    } catch(Exception $e) {
+                        echo $e;
+                    }
+                }
+            });
+        }
+    });
 });
-flame\go(function() {
-    $opt["connection_per_host"] = 1;
-    $cli = new flame\http\client($opt);
-    for($i = 0; $i < 40; $i++) {
-        flame\go(function() use($i, $cli) {
-            flame\time\sleep(rand(30, 50));
-            for($j = 0; $j < 50; ++$j) {
-                $req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 500);
-                $req->method = "GET";
-                $req->body = "aaaaaaaaaa";
-                $cli->exec($req);
-            }
-            //var_dump("user client +++++++" . $i); 
-            //var_dump('sleep ------8------');
-        });
-        //flame\time\sleep(rand(10, 20));
-        //flame\time\sleep(30);
-        //var_dump('sleep ------9------');
-    }
-    //flame\time\sleep(2000);
-});
-
-// 清理超时连接
-// 清理被断开连接
+// 直接使用全局连接测试
 /*flame\go(function() {*/
-    //for($i = 0; $i < 1; ++$i) {
-        //flame\http\get("http://127.0.0.1:8081/");
-        //flame\time\sleep(1000);
+    //for($i = 0; $i < 50; $i++) {
+        //flame\go(function() use($i) {
+            //for($j = 0; $j < 40; ++$j) {
+                //var_dump('====== ' . $i . ' === ' . $j . ' 1000');
+                //flame\http\get("http://127.0.0.1:8081/get", 300);
+            //}
+        //});
     //}
-/*});*/
+    //flame\time\sleep(3000);
+//});
 
+//flame\go(function() {
+    //for($i = 1000; $i < 1500; $i++) {
+        //flame\go(function() use($i) {
+            //var_dump('****** ' . $i . ' 1000');
+            //$port = rand(8081, 8082);
+            //flame\http\get('http://127.0.0.1:' . $port . '/get', 300);
+        //});
+    //}
+/*});*/
 
+ //连接超时测试
 /*flame\go(function() {*/
-    //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
-    //$req->method = "GET";
-    //$req->body = "aaaaaaaaaa";
-    //$cli->exec($req);
-
-    //flame\time\sleep(100000);
-//});
-
-// 多协程并发
-/*$opt["connection_per_host"] = 20;*/
-//$cli = new flame\http\client($opt);
-//for($i = 0; $i < 300; $i++) {
+    //$opt["connection_per_host"] = 1;
+    //$cli = new flame\http\client($opt);
     //flame\go(function() use($cli) {
-        //$req = new flame\http\client_request("http://127.0.0.1:8080/", null, 5000);
-        //$req->method = "GET";
-        //$req->body = "aaaaaaaaaa";
-        //$cli->exec($req);
+        //try {
+            //var_dump("client ------ 0 4s " . microtime());
+            //$req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 4000);
+            //$req->method = "GET";
+            //$cli->exec($req);
+        //} catch(Exception $e) {
+            //echo $e;
+        //}
+        //var_dump("client ------- 0 4s " . microtime());
+    //});
+    //flame\go(function() use($cli) {
+        //try {
+            //var_dump("client ++++++ 1 1s " . microtime());
+            //$req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 1000);
+            //$req->method = "GET";
+            //$cli->exec($req);
+        //} catch(Exception $e) {
+            //echo $e;
+        //}
+        //var_dump("client ++++++ 1 1s " . microtime());
+    //});
+    //flame\go(function() use($cli) {
+        //try {
+            //var_dump("client ------ 2 15s " . microtime());
+            //$req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 15000);
+            //$req->method = "GET";
+            //$cli->exec($req);
+        //} catch(Exception $e) {
+            //echo $e;
+        //}
+        //var_dump("client ------- 2 15s " . microtime());
     //});
-/*}*/
-
-
-// 直接使用全局连接测试
-// core dump
-flame\go(function() {
-    for($i = 0; $i < 50; $i++) {
-        flame\go(function() use($i) {
-            for($j = 0; $j < 40; ++$j) {
-                flame\time\sleep(rand(10,20));
-                flame\http\get("http://127.0.0.1:8081/get", 300);
-                var_dump("global client " . $i . " " . $j);
-            }
-        });
-    }
-    flame\time\sleep(3000);
-});
 
-flame\go(function() {
-    for($i = 500; $i < 1500; $i++) {
-        flame\go(function() use($i) {
-            //flame\time\sleep(rand(100,200));
-            flame\http\get("http://127.0.0.1:8081/get", 300);
-            var_dump("global client " . $i);
-        });
-    }
-});
+    //flame\time\sleep(20000);
+/*});*/
 
 flame\run();

From aa323769ff3ff969e4db914b793e3b6c314820a3 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 5 Dec 2018 16:51:45 +0800
Subject: [PATCH 026/146] =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=AE=8C=E5=96=84;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/http.php    | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 doc/kafka.php   |  2 +-
 doc/mongodb.php | 36 ++++++++++++++++++++++++++++++------
 3 files changed, 80 insertions(+), 7 deletions(-)

diff --git a/doc/http.php b/doc/http.php
index 405edcf..69a65b8 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -2,12 +2,61 @@
 
 namespace flame\http;
 
+/**
+ * 使用默认客户端, 执行请求
+ */
 function exec(client_request $req): client_response {}
+/**
+ * 使用默认客户端, 构建并执行一个 GET 请求
+ */
 function get(string $url, int $timeout = 3000): client_response {}
+/**
+ * 使用默认客户端, 构建并执行一个 POST 请求
+ */
 function post(string $url, mixed $data, int $timeout = 3000): client_response {}
+/**
+ * 使用默认客户端, 构建并执行一个 PUT 请求
+ */
 function put(string $url, mixed $data, int $timeout = 3000): client_response {}
+/**
+ * 使用默认客户端, 构建并执行一个 DELETE 请求
+ */
 function delete(string $url, int $timeout = 3000): client_response {}
 
+/**
+ * 客户端
+ */
+class client {
+    /**
+     * 构建新的客户端对象
+     * @param array $options 选项, 目前支持的选项如下:
+     *  * "connection_per_host" - 单个 HOST 目标连接限制, 默认 16;
+     */
+    function __construct(array $options = []) {}
+    /**
+     * 执行请求, 返回响应
+     */
+    function exec(client_request $req): client_response {}
+    /**
+     * 构建并执行一个 GET 请求
+     */
+    function get(string $url, int $timeout = 3000): client_response {}
+    /**
+     * 构建并执行一个 POST 请求
+     */
+    function post(string $url, mixed $data, int $timeout = 3000) {}
+    /**
+     * 构建并执行一个 PUT 请求
+     */
+    function put(string $url, mixed $data, int $timeout = 3000): client_response {}
+    /**
+     * 构建并执行一个 DELETE 请求
+     */
+    function delete(string $url, int $timeout = 3000): client_response {}
+}
+/**
+ * 作为客户端的请求对象
+ */
 class client_request {
     /**
      * @property int
diff --git a/doc/kafka.php b/doc/kafka.php
index d6a75d6..66e3e63 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -63,7 +63,7 @@ function publish(string $topic, mixed $message, string $key = null, array $heade
     function flush() {}
 }
 
-class message implements JsonSerializable {
+class message implements \JsonSerializable {
     /**
      * @property string $topic
      */
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 3b62096..c643e85 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -4,11 +4,23 @@
  */
 namespace flame\mongodb;
 
+/**
+ * 连接 Mongodb 并返回客户端对象
+ * @param string $url 连接地址, 形如:
+ *  mongodb://{username}:{password}@{host1}:{port1},{host2}:{port2}/{database}?{option1}={rs1}&{option2}=...
+ */
 function connect(string $url):client {}
 
 class client {
+    /**
+     * 执行以 $command 描述的命令;
+     * 注意: 更新型指令需要自行指定 $write = true
+     */
     function execute(array $command, bool $write = false):mixed {}
     function collection():collection {}
+    /**
+     * 与 collection() 相同, 返回指定的 collection 对象
+     */
     function __get($name):collection {}
     /**
      * @return bool 永远为 true ;
@@ -22,28 +34,40 @@ function insert(array $data, bool $ordered = true):array {}
     function delete(array $query, int $limit = 0):array {}
     function update(array $query, array $update, $upsert = false):array {}
     function find(array $query, array $projection = null, array $sort = null, mixed $limit):cursor {}
+    /**
+     * 查询并返回单个文档
+     */
     function one(array $query, array $sort = null): array {}
+    /**
+     * 查询并返回单个文档的指定字段值
+     */
     function get(array $query, string $field, array $sort = null): mixed {}
     function count(array $query): int {}
     function aggregate(array $pipeline): cursor {}
 }
 class cursor {
-    function fetch_row() {}
-    function fetch_all() {}
+    /**
+     * 读取当前 cursor 返回一个文档; 若返回已完成, 返回 null;
+     */
+    function fetch_row():mixed {}
+    /**
+     * 读取当前 cursor 返回所有文档 (二维)
+     */
+    function fetch_all(): array {}
 }
-class date_time implements JsonSerializable {
+class date_time implements \JsonSerializable {
     function __construct(int $milliseconds = null) {}
     function __toString() {}
-    function __toDateTime() {}
+    function __toDateTime(): \DateTime {}
     function __debugInfo() {}
     function jsonSerialize(): array {}
     function unix(): int {}
     function unix_ms(): int {}
 }
-class object_id implements JsonSerializable {
+class object_id implements \JsonSerializable {
     function __construct(string $object_id = null) {}
     function __toString() {}
-    function __toDateTime() {}
+    function __toDateTime(): \DateTime {}
     function __debugInfo() {}
     function jsonSerialize(): array {}
     function unix(): int {}

From 93dd8556fbb4a541ca07848f395542cae1af30d5 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 5 Dec 2018 20:06:42 +0800
Subject: [PATCH 027/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=20HTTP=20=E6=9C=8D?=
 =?UTF-8?q?=E5=8A=A1=E5=99=A8=E9=94=99=E8=AF=AF=E5=8F=B7=E8=AE=B0=E5=BD=95?=
 =?UTF-8?q?;=20=E4=BF=AE=E6=AD=A3:=20MongoDB=20object=5Fid=20=E5=AE=9E?=
 =?UTF-8?q?=E7=8E=B0=20JSON=20=E5=BA=8F=E5=88=97=E5=8C=96=E6=8E=A5?=
 =?UTF-8?q?=E5=8F=A3;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/_handler.cpp     | 2 +-
 src/mongodb/object_id.cpp | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 9721e45..74ce51d 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -50,7 +50,7 @@ namespace flame::http
             {   
                 res_.reset();
                 req_.reset();
-                std::clog << "[" << time::iso() << "] (WARNING) failed to read http request: " << error.message() << std::endl;
+                std::clog << "[" << time::iso() << "] (WARNING) failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
                 return;
             }
             res_.reset(new boost::beast::http::message<false, value_body<false>>());
diff --git a/src/mongodb/object_id.cpp b/src/mongodb/object_id.cpp
index 578db40..a440949 100644
--- a/src/mongodb/object_id.cpp
+++ b/src/mongodb/object_id.cpp
@@ -6,6 +6,7 @@ namespace flame::mongodb
     {
         php::class_entry<object_id> class_object_id("flame\\mongodb\\object_id");
         class_object_id
+            .implements(&php_json_serializable_ce)
             .method<&object_id::__construct>("__construct")
             .method<&object_id::to_string>("__toString")
             .method<&object_id::to_json>("__toJSON")

From 73274cd66786707f7f0a121ee45156a1fa6a6846 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 6 Dec 2018 19:51:02 +0800
Subject: [PATCH 028/146] =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=BA=BF=E7=A8=8B?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E9=85=8D=E6=AF=94,=20=E9=98=B2=E6=AD=A2?=
 =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E7=AD=89=E5=BE=85=E5=83=B5=E6=AD=BB;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_worker.cpp        |  3 ++-
 src/kafka/_consumer.cpp          |  8 +++++---
 src/kafka/_producer.cpp          | 10 ++++++----
 src/kafka/kafka.cpp              |  5 +++++
 src/mongodb/_connection_pool.cpp |  2 +-
 src/mongodb/object_id.cpp        |  4 +---
 src/mysql/_connection_base.cpp   |  2 ++
 src/mysql/_connection_pool.cpp   |  2 +-
 8 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index cf883ea..9b64789 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -24,6 +24,7 @@ namespace flame
                 auto ft = gcontroller->cbmap->equal_range("quit");
                 for (auto i = ft.first; i != ft.second; ++i)
                 {
+                    // TODO 修复: 需要在协程调用退出回调
                     i->second.call();
                 }
             });
@@ -32,7 +33,7 @@ namespace flame
         auto work = boost::asio::make_work_guard(gcontroller->context_y);
         // 子进程的启动过程:
         // 2. 启动线程池, 并使用线程池运行 context_y
-        thread_.resize(3);
+        thread_.resize(8);
         for (int i = 0; i < thread_.size(); ++i)
         {
             thread_[i] = new std::thread([this] {
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index d73e3c0..9ea5f12 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -48,9 +48,11 @@ namespace flame::kafka
     }
     _consumer::~_consumer()
     {
-        // rd_kafka_consumer_close(conn_);
-        rd_kafka_topic_partition_list_destroy(tops_);
-        rd_kafka_destroy(conn_);
+        if(conn_) {
+            // rd_kafka_consumer_close(conn_);
+            rd_kafka_topic_partition_list_destroy(tops_);
+            rd_kafka_destroy(conn_);
+        }
     }
     void _consumer::subscribe(coroutine_handler &ch)
     {
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index 2356070..b5e4882 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -27,10 +27,12 @@ namespace flame::kafka
     }
     _producer::~_producer()
     {
-        for(auto i=tops_.begin(); i!=tops_.end(); ++i) {
-            rd_kafka_topic_destroy(i->second);
+        if(conn_) {
+            for(auto i=tops_.begin(); i!=tops_.end(); ++i) {
+                rd_kafka_topic_destroy(i->second);
+            }
+            rd_kafka_destroy(conn_);
         }
-        rd_kafka_destroy(conn_);
     }
     void _producer::publish(const php::string &topic, const php::string &key, const php::string &payload, const php::array &headers, coroutine_handler &ch)
     {
@@ -58,6 +60,6 @@ namespace flame::kafka
     }
     void _producer::flush(coroutine_handler& ch)
     {
-        rd_kafka_flush(conn_, 10000);
+        if(conn_) rd_kafka_flush(conn_, 10000);
     }
 } // namespace flame::kafka
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index 75d09ef..aeca1af 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -47,6 +47,11 @@ namespace flame::kafka
         
         php::object obj(php::class_entry<producer>::entry());
         producer* ptr = static_cast<producer*>(php::native(obj));
+        if (config.exists("concurrent"))
+        {
+            // ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 8), 256);
+            config.erase("concurrent");
+        }
         ptr->pd_.reset(new _producer(config, topics));
 
         // TODO 优化: 确认首次连接已建立
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index cb43e7c..1494e03 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -12,7 +12,7 @@ namespace flame::mongodb
             mongoc_uri_set_option_as_utf8(uri.get(), MONGOC_URI_READPREFERENCE, "secondaryPreferred");
         }
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
-        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 16);
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 4);
 
         p_ = mongoc_client_pool_new(uri.get());
     }
diff --git a/src/mongodb/object_id.cpp b/src/mongodb/object_id.cpp
index a440949..7160174 100644
--- a/src/mongodb/object_id.cpp
+++ b/src/mongodb/object_id.cpp
@@ -15,9 +15,7 @@ namespace flame::mongodb
             .method<&object_id::to_json>("jsonSerialize")
             .method<&object_id::to_json>("__debugInfo")
             .method<&object_id::equal>("equal",
-            {
-                {"object_id", "?flame\\mongodb\\object_id"}
-            });
+                                       {{"object_id", "?flame\\mongodb\\object_id"}});
         ext.add(std::move(class_object_id));
     }
     php::value object_id::__construct(php::parameters &params)
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index f22e8f7..3920449 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -73,6 +73,8 @@ namespace flame::mysql
             b.push_back(')');
             break;
         }
+        case IS_OBJECT:
+            // TODO DateTime 类型的 SQL 拼接
         default:
         {
             php::string str = v;
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index b71936e..a4fa9f5 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -5,7 +5,7 @@ namespace flame::mysql
 {
         
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(2), max_(8), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(2), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     {
         if(!url_.query.count("charset")) {
             url_.query["charset"] = "utf8";

From 7cae5999a9b3879233817baeff616b64f9890402 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 7 Dec 2018 16:11:45 +0800
Subject: [PATCH 029/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20Redis=20=E9=AD=94?=
 =?UTF-8?q?=E6=9C=AF=E6=96=B9=E6=B3=95=20=5F=5Fisset=20=E5=8F=AF=E9=AA=8C?=
 =?UTF-8?q?=E8=AF=81=E6=8E=A5=E5=8F=A3=E5=AD=98=E5=9C=A8=E6=80=A7;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/redis.php        | 6 ++++++
 src/redis/client.cpp | 6 ++++++
 src/redis/client.h   | 1 +
 3 files changed, 13 insertions(+)

diff --git a/doc/redis.php b/doc/redis.php
index 279307f..ecd4fe6 100644
--- a/doc/redis.php
+++ b/doc/redis.php
@@ -13,6 +13,12 @@ class client {
      * 用于通用的实现可用 Redis 命令
      */
     function __call($name, $argv) {}
+    /**
+     * @return bool true
+     */
+    function __isset($name) {
+        return true;
+    }
     /**
      * 支持 Redis 大部分指令:
      * 
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index 0d82e62..af20192 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -13,6 +13,9 @@ namespace flame::redis {
 				{"cmd", php::TYPE::STRING},
 				{"arg", php::TYPE::ARRAY},
 			})
+			.method<&client::__isset>("__isset", {
+				{"cmd", php::TYPE::STRING},
+			})
 			.method<&client::mget>("mget", {
 				{"key", php::TYPE::STRING},
 			})
@@ -72,6 +75,9 @@ namespace flame::redis {
         php::array  argv = params[1];
         return cp_->exec(rc, name, argv, reply_type::SIMPLE, ch);
 	}
+	php::value client::__isset(php::parameters& params) {
+		return true;
+	}
 	php::value client::mget(php::parameters& params)
 	{
 		coroutine_handler ch{coroutine::current};
diff --git a/src/redis/client.h b/src/redis/client.h
index 9aaef19..e39e5cb 100644
--- a/src/redis/client.h
+++ b/src/redis/client.h
@@ -10,6 +10,7 @@ namespace flame::redis
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
         php::value __call(php::parameters &params);
+        php::value __isset(php::parameters& params);
         // 处理特殊情况的命令
         php::value mget(php::parameters &params);
         php::value hmget(php::parameters &params);

From 0b4373e635ac50773b03c979a877efaa75b0d6ff Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 7 Dec 2018 16:12:16 +0800
Subject: [PATCH 030/146] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20=E5=A4=9A?=
 =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E5=81=9C=E6=AD=A2=E9=80=9A=E7=9F=A5=E6=9C=BA?=
 =?UTF-8?q?=E5=88=B6;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp        |  8 +++-
 src/controller_master.cpp | 80 +++++++++++++++++++++++----------------
 src/controller_master.h   |  7 +++-
 src/controller_worker.cpp | 22 ++++++-----
 test/worker_1.php         |  7 +++-
 5 files changed, 79 insertions(+), 45 deletions(-)

diff --git a/src/controller.cpp b/src/controller.cpp
index 115fa1a..c824ab3 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -72,7 +72,13 @@ namespace flame {
 		}
 		delete cbmap;
 		// 运行完毕
-		(status & controller_status::STATUS_EXCEPTION) ? exit(-1) : exit(0);
+		if(status & controller_status::STATUS_EXCEPTION) {
+			std::cout << (int)type << ":EXCEPTION\n";
+			exit(-1);
+		} else {
+			std::cout << (int)type << ":NORMAL\n";
+			exit(0);
+		}
 		// 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
 	}
 
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 972929b..59fb99b 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -4,8 +4,7 @@
 namespace flame
 {
     controller_master::controller_master()
-        : signal_(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2))
-        , worker_(gcontroller->worker_size)
+        : worker_(gcontroller->worker_size)
         , sout_(gcontroller->worker_size)
         , sbuf_(gcontroller->worker_size)
         , eout_(gcontroller->worker_size)
@@ -46,7 +45,7 @@ namespace flame
         boost::process::environment env = gcontroller->env;
         env["FLAME_CUR_WORKER"] = std::to_string(i + 1);
 
-        worker_[i].reset( new boost::process::child(cmd, env, group_, gcontroller->context_x,
+        worker_[i].reset( new boost::process::child(cmd, env/*, group_*/, gcontroller->context_x,
             boost::process::std_out > *sout_[i],
             boost::process::std_err > *eout_[i],
             // boost::process::std_out > boost::process::null,
@@ -58,8 +57,8 @@ namespace flame
                 if (exit_code != 0)
                 {
                     // 日志记录
-                    int sec = std::rand() % 3 + 2;
-                    std::cerr << "(FATAL) worker unexpected exit, restart in " << sec << " ...\n";
+                    int sec = std::rand() % 3 + 3;
+                    std::cerr << "(FATAL) worker unexpected exit (" << exit_code << "), restart in " << sec << " ...\n";
 
                     auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
                     tm->async_wait([this, tm, i](const boost::system::error_code &error) {
@@ -77,7 +76,8 @@ namespace flame
                 sout_[i].reset();
                 eout_[i].reset();
             }) );
-        
+        redirect_sout(i);
+        redirect_eout(i);
     }
     void controller_master::redirect_sout(int i)
     {
@@ -131,41 +131,42 @@ namespace flame
         for(int i=0;i<gcontroller->worker_size;++i)
         {
             spawn_worker(i);
-            redirect_sout(i);
-            redirect_eout(i);
         }
         // 2. 监听信号进行日志重载或停止
+        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2));
         signal_->async_wait([this](const boost::system::error_code &error, int sig) {
-            if(error) return;
-            if(sig == SIGUSR2)
-            {
-                reload_output();
-            }
-            else
-            {
-                // 信号的默认处理: 转给所有子进程
-                for(auto i=worker_.begin(); i!=worker_.end(); ++i)
-                {
-                    if(*i) ::kill( (*i)->id(), SIGTERM );
-                }
-                signal_.reset();
-                gcontroller->context_x.stop();
-            }
-        });
+                if (error)
+                    return;
+                boost::asio::post(gcontroller->context_x, [this, sig]() {
+                    if (sig == SIGUSR2)
+                    {
+                        reload_output();
+                    }
+                    else
+                    {
+                        close_worker();
+                        // 似乎主进程与子进程 signal 处理之间有干扰
+                        // 需要后置清理
+                        // signal_.reset();
+                    }
+                });
+            });
         // 3. 启动运行
         thread_ = std::thread([this] {
             gcontroller->context_y.run();
         });
         gcontroller->context_x.run();
         // 4. 等待工作线程结束
-        signal_.reset();
+        if (signal_) signal_.reset();
+        if (timer_) timer_.reset();
         thread_.join();
-        // 5. 等待工作进程结束
-        std::error_code error;
-        if(!group_.wait_for(std::chrono::seconds(10), error))
-        {
-            group_.terminate();
+        // 5. 强制进程结束
+        for(auto i=worker_.begin();i!=worker_.end();++i) {
+            if(*i) {
+                (*i)->terminate();
+            }
         }
+        // if(!group_.wait_for(std::chrono::seconds(1))) group_.terminate();
     }
     void controller_master::reload_output()
     {
@@ -178,7 +179,22 @@ namespace flame
                 return;
             }
 		}
-        
         offile_.reset(&std::clog, boost::null_deleter());
     }
-}
\ No newline at end of file
+    void controller_master::close_worker()
+    {
+        for (auto i = worker_.begin(); i != worker_.end(); ++i)
+        {
+            if (*i)
+            {
+                ::kill((*i)->id(), SIGTERM);
+            }
+        }
+        timer_.reset(new boost::asio::steady_timer(gcontroller->context_y));
+        timer_->expires_after(std::chrono::seconds(10));
+        timer_->async_wait([] (const boost::system::error_code& error) {
+            if(error) return;
+            gcontroller->context_x.stop();
+        });
+    }
+}
diff --git a/src/controller_master.h b/src/controller_master.h
index 1968e5d..16939d8 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -14,8 +14,9 @@ namespace flame
         std::vector<std::unique_ptr<boost::process::async_pipe>> eout_;
         std::vector<boost::asio::streambuf> sbuf_;
         std::vector<boost::asio::streambuf> ebuf_;
-        boost::process::group               group_;
-		std::unique_ptr<boost::asio::signal_set> signal_;
+        // boost::process::group               group_;
+		std::unique_ptr<boost::asio::signal_set>  signal_;
+        std::unique_ptr<boost::asio::steady_timer> timer_;
 		std::thread                        thread_;
         std::size_t                        count_;
 
@@ -26,5 +27,7 @@ namespace flame
         void reload_output();
         void redirect_sout(int i);
         void redirect_eout(int i);
+
+        void close_worker();
     };
 }
\ No newline at end of file
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index 9b64789..5ae0476 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -1,12 +1,12 @@
+#include "coroutine.h"
 #include "controller_worker.h"
 #include "controller.h"
 
 namespace flame
 {
     controller_worker::controller_worker()
-    : signal_(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM))
     {
-
+        
     }
     void controller_worker::initialize(const php::array &options)
     {
@@ -14,23 +14,27 @@ namespace flame
     }
     void controller_worker::run()
     {
+        // 工作线程的使用是随机的, 需要保持其一直存在
+        auto work = boost::asio::make_work_guard(gcontroller->context_y);
         // 1. 停止信号调用退出通知
+        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
         signal_->async_wait([this] (const boost::system::error_code &error, int sig)
         {
             if(error) return;
-            signal_.reset();
-            boost::asio::post(gcontroller->context_x, [] () {
+            // 似乎主进程与子进程 signal 处理之间有干扰
+            // 需要后置清理
+            // signal_.reset();
+            boost::asio::post(gcontroller->context_x, [this] () {
                 gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
                 auto ft = gcontroller->cbmap->equal_range("quit");
                 for (auto i = ft.first; i != ft.second; ++i)
                 {
-                    // TODO 修复: 需要在协程调用退出回调
-                    i->second.call();
+                    // 需要在协程调用退出回调
+                    coroutine::start(i->second);
                 }
             });
         });
-        // 工作线程的使用时随机的, 需要保持其一直存在
-        auto work = boost::asio::make_work_guard(gcontroller->context_y);
+       
         // 子进程的启动过程:
         // 2. 启动线程池, 并使用线程池运行 context_y
         thread_.resize(8);
@@ -43,7 +47,7 @@ namespace flame
         // 3. 启动 context_x 运行
         gcontroller->context_x.run();
         // 4. 确认工作线程停止
-        signal_.reset();
+        if(signal_) signal_.reset();
         work.reset();
         for (int i = 0; i < thread_.size(); ++i)
         {
diff --git a/test/worker_1.php b/test/worker_1.php
index f6b9916..0667be2 100644
--- a/test/worker_1.php
+++ b/test/worker_1.php
@@ -6,7 +6,12 @@
 flame\go(function() {
     flame\time\sleep(1000);
     echo getenv("FLAME_CUR_WORKER").": abc\n";
-    flame\time\sleep(1000);
+    flame\time\sleep(60000);
+});
+
+flame\on("quit", function() {
+    echo "quit\n";
+    flame\quit();
 });
 
 flame\run();

From 4e37ea6e544707eb7c7d6f17634221f7ef9694ce Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 7 Dec 2018 16:33:34 +0800
Subject: [PATCH 031/146] =?UTF-8?q?=E5=8D=95=E8=BF=9B=E7=A8=8B=E7=8A=B6?=
 =?UTF-8?q?=E6=80=81=E5=85=81=E8=AE=B8=E4=BA=8C=E6=AC=A1=E7=AB=8B=E5=8D=B3?=
 =?UTF-8?q?=E5=81=9C=E6=AD=A2;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp        | 18 +++++++++++-------
 src/controller_worker.cpp |  2 +-
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/controller.cpp b/src/controller.cpp
index c824ab3..0a46473 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -13,6 +13,9 @@ namespace flame {
 		, status(STATUS_UNKNOWN)
 		, cbmap(new std::multimap<std::string, php::callable>())
 	{
+		
+		worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
+		worker_size = std::min(std::max((int)worker_size, 1), 512);
 		// FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
 		if (env.count("FLAME_CUR_WORKER") > 0)
 		{
@@ -20,12 +23,11 @@ namespace flame {
 		}
 		else if (env.count("FLAME_MAX_WORKERS") > 0)
 		{
-			worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
-			worker_size = std::min(std::max((int)worker_size, 1), 512);
 			type = process_type::MASTER;
 		} 
 		else
 		{
+			worker_size = 0;
 			type = process_type::WORKER;
 		}
 		mthread_id = std::this_thread::get_id();
@@ -48,9 +50,13 @@ namespace flame {
 		}
 		if(type == process_type::WORKER)
 		{
-			std::string index = env["FLAME_CUR_WORKER"].to_string();
-			if(index.empty()) index = "w";
-			php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
+			if (worker_size > 0)
+			{
+				std::string index = env["FLAME_CUR_WORKER"].to_string();
+				php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
+			}else{
+				php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
+			}
 			worker_.reset(new controller_worker());
 			worker_->initialize(options);
 		}else/* if(type == process_type::MASTER)*/
@@ -73,10 +79,8 @@ namespace flame {
 		delete cbmap;
 		// 运行完毕
 		if(status & controller_status::STATUS_EXCEPTION) {
-			std::cout << (int)type << ":EXCEPTION\n";
 			exit(-1);
 		} else {
-			std::cout << (int)type << ":NORMAL\n";
 			exit(0);
 		}
 		// 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index 5ae0476..88ff00c 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -23,7 +23,7 @@ namespace flame
             if(error) return;
             // 似乎主进程与子进程 signal 处理之间有干扰
             // 需要后置清理
-            // signal_.reset();
+            if(gcontroller->worker_size == 0) signal_.reset();
             boost::asio::post(gcontroller->context_x, [this] () {
                 gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
                 auto ft = gcontroller->cbmap->equal_range("quit");

From f14a3aa880cf7a56c59f55b4f8ddd58ada60c032 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sat, 8 Dec 2018 17:56:39 +0800
Subject: [PATCH 032/146] v0.12.1

---
 src/vendor.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/vendor.h b/src/vendor.h
index bc8a272..ee7c994 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.0"
+#define EXTENSION_VERSION "0.12.1"
 
 #include <cstdio>
 #include <sys/types.h>

From 9b5a90b688c3db1870e83ea8c7f86ede6d099c56 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 10 Dec 2018 17:48:50 +0800
Subject: [PATCH 033/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20=E9=94=99=E8=AF=AF?=
 =?UTF-8?q?=E4=BF=A1=E6=81=AF=20=E5=85=A8=E5=B1=80=E7=8A=B6=E6=80=81?=
 =?UTF-8?q?=E5=88=87=E6=8D=A2;=20=E9=98=B2=E6=AD=A2=20Kafka=20=E7=94=9F?=
 =?UTF-8?q?=E4=BA=A7=E9=98=9F=E5=88=97=E6=8B=A5=E5=A1=9E;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt          |  3 ++-
 src/coroutine.cpp       | 12 +++++++++++-
 src/coroutine.h         |  4 ++++
 src/kafka/_producer.cpp |  2 ++
 4 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 64325a0..947aced 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,6 +19,7 @@ set(VENDOR_RDKAFKA /data/vendor/rdkafka-0.11.6)
 set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.8.1)
 # 基本参数
 set(CMAKE_BUILD_TYPE "Debug")
+# set(CMAKE_BUILD_TYPE "Release")
 # set(CMAKE_C_COMPILER /data/vendor/llvm-7.0.0/bin/clang)
 # set(CMAKE_CXX_COMPILER /data/vendor/llvm-7.0.0/bin/clang++)
 set(CMAKE_C_COMPILER /data/vendor/gcc-8.2.0/bin/gcc)
@@ -70,4 +71,4 @@ target_link_libraries(FLAME
     crypto
     pthread
     rt
-)
\ No newline at end of file
+)
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index c169b64..2342dca 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -15,6 +15,9 @@ namespace flame
         ctx.vm_stack_end = EG(vm_stack_end);
         //   ctx.scope = EG(fake_scope);
         ctx.current_execute_data = EG(current_execute_data);
+        ctx.exception = EG(exception);
+        ctx.exception_class = EG(exception_class);
+        ctx.error_handling = EG(error_handling);
     }
     void coroutine::restore_context(php_context_t &ctx)
     {
@@ -23,6 +26,9 @@ namespace flame
         EG(vm_stack_end) = ctx.vm_stack_end;
         // EG(fake_scope) = ctx.scope;
         EG(current_execute_data) = ctx.current_execute_data;
+        EG(exception) = ctx.exception;
+        EG(exception_class) = ctx.exception_class;
+        EG(error_handling) = ctx.error_handling;
     }
     // 参考 zend_execute.c 
     static zend_vm_stack zend_vm_stack_new_page(size_t size, zend_vm_stack prev)
@@ -58,7 +64,11 @@ namespace flame
                     save_context(coroutine::global_context);
                     // 启动进入协程
                     coroutine::current = co_;
-                    EG(current_execute_data) = gcontroller->default_execute_data;
+
+                    EG(current_execute_data) = nullptr;
+                    EG(error_handling) = EH_NORMAL;
+                    EG(exception_class) = nullptr;
+                    EG(exception) = nullptr;
                     zend_vm_stack_init();
                     // 协程运行;
                     co_->fn_.call(/*ag*/);
diff --git a/src/coroutine.h b/src/coroutine.h
index 932ad24..336789c 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -12,6 +12,10 @@ namespace flame {
 			zval *vm_stack_end;
 			zend_class_entry *scope;
 			zend_execute_data *current_execute_data;
+
+			zend_object *         exception;
+			zend_error_handling_t error_handling;
+			zend_class_entry *    exception_class;
 		};
 		//
 		static boost::context::fixedsize_stack stack_allocator;
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index b5e4882..329eeda 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -47,6 +47,8 @@ namespace flame::kafka
                               RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
                               RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
                               RD_KAFKA_V_END);
+            // TODO 优化: 是否可以考虑按周期间隔进行调用?
+            rd_kafka_poll(conn_, 5);
             ch.resume();
         });
         ch.suspend();

From ff99c91eae8ef29487789b9250858876982740e1 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 11 Dec 2018 13:56:16 +0800
Subject: [PATCH 034/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=20MySQL=20=E5=85=BC?=
 =?UTF-8?q?=E5=AE=B9=20atlas=20=E4=B8=AD=E9=97=B4=E4=BB=B6;=20=E4=BF=AE?=
 =?UTF-8?q?=E6=AD=A3:=20MongoDB=20=E6=9F=A5=E8=AF=A2=E9=94=99=E8=AF=AF?=
 =?UTF-8?q?=E6=8A=9B=E5=87=BA=E5=8F=82=E6=95=B0=E5=A4=9A=E4=BD=99;=20?=
 =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=20HTTP=20=E5=AE=A2=E6=88=B7=E7=AB=AF?=
 =?UTF-8?q?=E6=9E=81=E7=AB=AF=E8=B6=85=E6=97=B6=E5=9C=BA=E6=99=AF=E5=B4=A9?=
 =?UTF-8?q?=E6=BA=83;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_master.cpp        | 38 +++++++++++++----------
 src/controller_master.h          |  1 +
 src/controller_worker.cpp        | 19 +++++++-----
 src/controller_worker.h          |  2 ++
 src/http/_connection_pool.cpp    | 12 ++++----
 src/http/_connection_pool.h      |  5 +++
 src/http/_handler.cpp            | 53 ++++++++++++++++++++------------
 src/http/client.cpp              |  3 +-
 src/http/client_request.cpp      | 16 +++++-----
 src/http/client_request.h        |  5 ++-
 src/http/client_response.cpp     |  2 +-
 src/http/client_response.h       |  3 +-
 src/http/server_response.cpp     |  6 ++--
 src/mongodb/_connection_base.cpp |  2 +-
 src/mysql/_connection_pool.cpp   |  4 ++-
 src/mysql/_connection_pool.h     |  1 +
 src/mysql/client.cpp             | 10 ++++--
 src/mysql/client.h               |  2 +-
 18 files changed, 109 insertions(+), 75 deletions(-)

diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 59fb99b..3866584 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -123,6 +123,26 @@ namespace flame
             }
         });
     }
+    void controller_master::await_signal()
+    {
+        signal_->async_wait([this](const boost::system::error_code &error, int sig) {
+            if (error) return;
+            await_signal();
+            boost::asio::post(gcontroller->context_x, [this, sig]() {
+                if (sig == SIGUSR2)
+                {
+                    reload_output();
+                }
+                else
+                {
+                    close_worker();
+                    // 似乎主进程与子进程 signal 处理之间有干扰
+                    // 需要后置清理
+                    // signal_.reset();
+                }
+            });
+        });
+    }
     // !!! run 实际上是在 initialize 中执行 (防止实际启用了功能)
     void controller_master::run()
     {
@@ -134,23 +154,7 @@ namespace flame
         }
         // 2. 监听信号进行日志重载或停止
         signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2));
-        signal_->async_wait([this](const boost::system::error_code &error, int sig) {
-                if (error)
-                    return;
-                boost::asio::post(gcontroller->context_x, [this, sig]() {
-                    if (sig == SIGUSR2)
-                    {
-                        reload_output();
-                    }
-                    else
-                    {
-                        close_worker();
-                        // 似乎主进程与子进程 signal 处理之间有干扰
-                        // 需要后置清理
-                        // signal_.reset();
-                    }
-                });
-            });
+        await_signal();
         // 3. 启动运行
         thread_ = std::thread([this] {
             gcontroller->context_y.run();
diff --git a/src/controller_master.h b/src/controller_master.h
index 16939d8..77f306e 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -28,6 +28,7 @@ namespace flame
         void redirect_sout(int i);
         void redirect_eout(int i);
 
+        void await_signal();
         void close_worker();
     };
 }
\ No newline at end of file
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index 88ff00c..c771f39 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -12,18 +12,15 @@ namespace flame
     {
         
     }
-    void controller_worker::run()
-    {
-        // 工作线程的使用是随机的, 需要保持其一直存在
-        auto work = boost::asio::make_work_guard(gcontroller->context_y);
-        // 1. 停止信号调用退出通知
-        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
+    void controller_worker::await_signal() {
         signal_->async_wait([this] (const boost::system::error_code &error, int sig)
         {
             if(error) return;
             // 似乎主进程与子进程 signal 处理之间有干扰
             // 需要后置清理
             if(gcontroller->worker_size == 0) signal_.reset();
+            else await_signal();
+
             boost::asio::post(gcontroller->context_x, [this] () {
                 gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
                 auto ft = gcontroller->cbmap->equal_range("quit");
@@ -34,7 +31,15 @@ namespace flame
                 }
             });
         });
-       
+    }
+    void controller_worker::run()
+    {
+        // 工作线程的使用是随机的, 需要保持其一直存在
+        auto work = boost::asio::make_work_guard(gcontroller->context_y);
+        // 1. 停止信号调用退出通知
+        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
+        await_signal();
+
         // 子进程的启动过程:
         // 2. 启动线程池, 并使用线程池运行 context_y
         thread_.resize(8);
diff --git a/src/controller_worker.h b/src/controller_worker.h
index c2819f4..f781da0 100644
--- a/src/controller_worker.h
+++ b/src/controller_worker.h
@@ -11,5 +11,7 @@ namespace flame
 	private:
 		std::vector<std::thread*> thread_;
 		std::unique_ptr<boost::asio::signal_set> signal_;
+
+		void await_signal();
 	};
 }
\ No newline at end of file
diff --git a/src/http/_connection_pool.cpp b/src/http/_connection_pool.cpp
index 273d667..a5fa285 100644
--- a/src/http/_connection_pool.cpp
+++ b/src/http/_connection_pool.cpp
@@ -101,11 +101,12 @@ namespace flame::http
     php::value _connection_pool::execute(client_request* req, int32_t timeout, coroutine_handler &ch) {
         auto type = req->url_->schema + req->url_->host + std::to_string(req->url_->port);
         auto ctx  = std::make_shared<_connection_pool::context_t>(ch, wl_[type], timeout);
+        req->build_ex(ctx->req_);
+        
         ctx->expire();
         acquire(req->url_, ctx);
-
         // 构建并发送请求
-        boost::beast::http::async_write(*ctx->c_, req->ctr_, [ctx](const boost::system::error_code& ec, std::size_t n) {
+        boost::beast::http::async_write(*ctx->c_, ctx->req_, [ctx](const boost::system::error_code& ec, std::size_t n) {
             if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
             ctx->ec_ = ec;
             ctx->ch_.resume();
@@ -125,8 +126,7 @@ namespace flame::http
         // 构建并读取响应
         auto res_ref = php::object(php::class_entry<client_response>::entry());
         auto res_    = static_cast<client_response*>(php::native(res_ref));
-        boost::beast::flat_buffer b;
-        boost::beast::http::async_read(*ctx->c_, b, res_->ctr_, [ctx] (const boost::system::error_code& ec, std::size_t n) {
+        boost::beast::http::async_read(*ctx->c_, ctx->buf_, ctx->res_, [ctx] (const boost::system::error_code& ec, std::size_t n) {
             if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
             ctx->ec_  = ec;
             ctx->to_ |= context_t::DONE;
@@ -145,10 +145,10 @@ namespace flame::http
             throw php::exception(zend_ce_exception, (boost::format("failed to read response: (%1%) %2%") % ctx->ec_.value() % ctx->ec_.message()).str(), ctx->ec_.value());
         }
 
-        res_->build_ex();
+        res_->build_ex(ctx->res_);
         ctx->tm_.cancel();
 
-        if(res_->ctr_.keep_alive() && req->ctr_.keep_alive())
+        if(ctx->req_.keep_alive() && ctx->res_.keep_alive())
             release(type, ctx->c_);
         else
             release(type, nullptr);
diff --git a/src/http/_connection_pool.h b/src/http/_connection_pool.h
index 418c713..de8c5d3 100644
--- a/src/http/_connection_pool.h
+++ b/src/http/_connection_pool.h
@@ -1,4 +1,5 @@
 #pragma once
+#include "../vendor.h"
 #include "../url.h"
 
 namespace flame::http
@@ -41,6 +42,10 @@ namespace flame::http
             queue_t                      &q_;
             int32_t                      to_;
             time_t_                      tp_;
+
+            boost::beast::http::message<true, value_body<true>> req_;
+            boost::beast::http::message<false, value_body<false>> res_;
+            boost::beast::flat_buffer buf_;
         };
 
         const std::uint32_t max_;
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 74ce51d..cf92abd 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -115,13 +115,15 @@ namespace flame::http
                 return nullptr;
             }
 HANDLE_BREAK:
-            if (res_->chunked() && (res_ptr->status_ & server_response::STATUS_BODY_SENT))
-            {
-                // Chunked 方式 已结束
-                start();
-            }
-            else if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/)
+            if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/)
             {
+                // if(!(res_ptr->status_ & server_response::STATUS_HEAD_SENT)) {
+                //     res_ptr->set("status", 404);
+                //     res_ptr->set("body", nullptr);
+                // }
+                if(gcontroller->status & controller::controller_status::STATUS_SHUTDOWN) {
+                    res_->keep_alive(false);
+                }
                 res_ptr->build_ex(*res_);
                 // Content 方式, 待结束
                 php::string body = res.get("body");
@@ -132,10 +134,16 @@ namespace flame::http
                 res_->body() = body;
                 res_->prepare_payload();
 
-                boost::beast::http::async_write(socket_, *res_, [self = shared_from_this(), this](const boost::system::error_code &error, std::size_t nsent)
-                {
+                boost::system::error_code error;
+                boost::beast::http::async_write(socket_, *res_, ch[error]);
+                if(error) {
+                    res_.reset();
+                    req_.reset();
+                    throw php::exception(zend_ce_exception,
+                                         (boost::format("failed to write_head: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+                }else{
                     start();
-                });
+                }
             }/*else
             {
                 // Chunked 未结束 -> server_response::__destruct() -> finish()
@@ -150,6 +158,10 @@ namespace flame::http
         res_ptr->build_ex(*res_);
         res_->chunked(true);
         res_->set(boost::beast::http::field::transfer_encoding, "chunked");
+        if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN)
+        {
+            res_->keep_alive(false);
+        }
         boost::system::error_code error;
 
         boost::beast::http::serializer<false, value_body<false>> sr(*res_);
@@ -246,19 +258,22 @@ namespace flame::http
     }
     void _handler::finish(server_response *res_ptr, coroutine_handler &ch)
     {
-        if(res_ && res_->chunked() && !(res_ptr->status_ & server_response::STATUS_BODY_END)) {
-            boost::system::error_code error;
-            boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
-            
-            if(error)
+        if(res_)
+        {
+            if(res_->chunked() && !(res_ptr->status_ & server_response::STATUS_BODY_END))
             {
+                boost::system::error_code error;
+                boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
                 res_.reset();
                 req_.reset();
-                std::clog << "[" << time::iso() << "] (ERROR) failed to write_end: (" << error.value() << ") " << error.message() << std::endl;
-            }
-            else
-            {
-                start();
+                if (error)
+                {
+                    std::clog << "[" << time::iso() << "] (ERROR) failed to write_end: (" << error.value() << ") " << error.message() << std::endl;
+                }
+                else
+                {
+                    start();
+                }
             }
         }
     }
diff --git a/src/http/client.cpp b/src/http/client.cpp
index 324fa56..b9fdf21 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -54,8 +54,7 @@ namespace flame::http {
     php::value client::exec_ex(const php::object& req) {
         coroutine_handler ch{coroutine::current};
         auto req_ = static_cast<client_request*>(php::native(req));
-        req_->build_ex();
-
+        req_->build_url();
         if(req_->url_->schema.compare("http") == 0) {
             return cp_->execute(req_, static_cast<int>(req_->get("timeout")), ch);
         } /*else if(req_->url_->schema.compare("https") == 0) {*/
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 3bd4904..5d75e48 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -19,8 +19,7 @@ namespace http {
 				{"url", php::TYPE::STRING},
 				{"body", php::TYPE::UNDEFINED, false, true},
 				{"timeout", php::TYPE::INTEGER, false, true},
-			})
-			.method<&client_request::to_string>("__toString");
+			});
 
 		ext.add(std::move(class_client_request));
 	}
@@ -41,13 +40,8 @@ namespace http {
 		set("cookie", php::array(0));
 		return nullptr;
 	}
-	php::value client_request::to_string(php::parameters& params) {
-		std::stringstream os;
-		build_ex();
-		os << ctr_;
-		return os.str();
-	}
-	void client_request::build_ex() {
+	void client_request::build_url()
+	{
 		if(url_) return; // 防止重复 build
 		// 目标请求地址
         php::string u = get("url", true);
@@ -61,6 +55,10 @@ namespace http {
 		}else if(!url_->port && !url_->schema.compare(0, 4, "http")) {
 			url_->port = 80;
 		}
+	}
+	void client_request::build_ex(boost::beast::http::message<true, value_body<true>>& ctr_)
+	{
+		// if(url_) return; // 防止重复 build
 		// 消息容器
 		ctr_.method(boost::beast::http::string_to_verb(get("method").to_string()));
 		std::string target(url_->path.length() > 0? url_->path : "/");
diff --git a/src/http/client_request.h b/src/http/client_request.h
index 73cf68e..0e94b06 100644
--- a/src/http/client_request.h
+++ b/src/http/client_request.h
@@ -6,11 +6,10 @@ namespace flame::http {
 	public:
 		static void declare(php::extension_entry& ext);
 		php::value __construct(php::parameters& params);
-		php::value to_string(php::parameters& params);
 	private:
-		void build_ex();
+		void build_url();
+		void build_ex(boost::beast::http::message<true, value_body<true>>& ctr_);
 		std::shared_ptr<url> url_;
-		boost::beast::http::message<true, value_body<true>> ctr_;
 		friend class client;
 		friend class _connection_pool;
     };
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index 4587e31..a6905b5 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -23,7 +23,7 @@ namespace http {
 	php::value client_response::to_string(php::parameters& params) {
 		return get("rawBody");
 	}
-	void client_response::build_ex() {
+	void client_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
 		set("status", ctr_.result_int());
 		php::array header = get("header", true);
 		if(header.typeof(php::TYPE::NULLABLE)) {
diff --git a/src/http/client_response.h b/src/http/client_response.h
index 1e5d432..787db39 100644
--- a/src/http/client_response.h
+++ b/src/http/client_response.h
@@ -9,8 +9,7 @@ namespace http {
 		// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
 		php::value __construct(php::parameters& params);
 	private:
-		boost::beast::http::message<false, value_body<false>> ctr_;
-		void build_ex();
+		void build_ex(boost::beast::http::message<false, value_body<false>>& ctr_);
 		friend class _connection_pool;
 	};
 }
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index 581eaaf..3cf6bcf 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -185,12 +185,10 @@ namespace flame::http {
 		if(headers.typeof(php::TYPE::ARRAY)) {
 			for(auto i=headers.begin(); i!=headers.end(); ++i) {
 				php::string key { i->first };
-				i->second.to_string();
-
-				ctr_.set(boost::string_view(key.c_str(), key.size()), i->second);
+				php::string val { i->second };
+				ctr_.set(boost::string_view(key.c_str(), key.size()), val);
 			}
 		}
-		ctr_.set(boost::beast::http::field::connection, ctr_.keep_alive() ? "keep-alive" : "close");
 		php::array cookies = get("cookie", true);
 		if(cookies.typeof(php::TYPE::ARRAY)) {
 			for(auto i=cookies.begin(); i!=cookies.end(); ++i) {
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index 16a275b..ae993b4 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -29,7 +29,7 @@ namespace flame::mongodb
         if(!rok)
         {
             throw php::exception(zend_ce_exception,
-                    (boost::format("failed to execute MongoDB command: (%1%) %1%") % err->code % err->message).str(),
+                    (boost::format("failed to execute MongoDB command: (%1%) %2%") % err->code % err->message).str(),
                     err->code);
         }
         else if(bson_has_field(rep.get(), "cursor"))
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index a4fa9f5..fff0b6d 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -38,7 +38,8 @@ namespace flame::mysql
                 { // 可用连接
                     MYSQL *c = conn_.front().conn;
                     conn_.pop_front();
-                    mysql_reset_connection(c);
+                    if(version_ >= 50700) mysql_reset_connection(c);
+                    else mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
                     release(c);
                     return;
                 }
@@ -78,6 +79,7 @@ namespace flame::mysql
         {
             return nullptr;
         }
+        version_ = mysql_get_server_version(c);
         return c;
     }
 
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index f535ca6..11d76f0 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -30,6 +30,7 @@ namespace flame::mysql
         std::list<connection_t> conn_;
         boost::asio::steady_timer tm_;
 
+        unsigned long version_;
         
         MYSQL* create();
         void release(MYSQL *c);
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index bb08a88..0eda44b 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -56,7 +56,8 @@ namespace flame::mysql
                 {"field", php::TYPE::STRING},
                 {"where", php::TYPE::UNDEFINED},
                 {"order", php::TYPE::UNDEFINED, false, true},
-            });
+            })
+            .method<&client::version>("version");
         ext.add(std::move(class_client));
     }
     php::value client::__construct(php::parameters& params) {
@@ -154,5 +155,10 @@ namespace flame::mysql
             return nullptr;
         }
     }
-
+    php::value client::version(php::parameters &params)
+    {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        return mysql_get_server_version(conn.get());
+    }
 } // namespace flame::mysql
diff --git a/src/mysql/client.h b/src/mysql/client.h
index 21ba8a6..fb63bca 100644
--- a/src/mysql/client.h
+++ b/src/mysql/client.h
@@ -22,7 +22,7 @@ namespace flame::mysql
         php::value select(php::parameters &params);
         php::value one(php::parameters &params);
         php::value get(php::parameters &params);
-
+        php::value version(php::parameters& params);
     protected:
         std::shared_ptr<_connection_pool> cp_;
         friend php::value connect(php::parameters &params);

From 86d4054509e62741bfaa491d89a12facd5af0167 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 11 Dec 2018 14:08:44 +0800
Subject: [PATCH 035/146] v0.12.2

---
 src/vendor.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/vendor.h b/src/vendor.h
index ee7c994..127f58b 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.1"
+#define EXTENSION_VERSION "0.12.2"
 
 #include <cstdio>
 #include <sys/types.h>

From 4cac4100df513e7f44ef409ea8492022da0b4c34 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 11 Dec 2018 16:37:29 +0800
Subject: [PATCH 036/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20log=20=E6=97=A5?=
 =?UTF-8?q?=E5=BF=97=E7=AD=89=E7=BA=A7=E7=9A=84=E8=AF=B4=E6=98=8E;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php | 7 +++++++
 doc/log.php  | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/doc/core.php b/doc/core.php
index 028b12a..db4f54b 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -9,6 +9,13 @@
  * @param array  $options 选项配置, 目前可用如下:
  *  * `logger` - 日志输出重定向目标文件(完整路径, 若不提供使用标准输出); 
  *      向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
+ *  * `level` - 日志输出级别, 设置该级别下的日志将不被记录; 可用级别如下
+ *      "debug"
+ *      "info"
+ *      "warning"
+ *      "error"
+ *      "fatal"
+ *  请参见 flame\log 命名空间;
  */
 function init($process_name, $options = []) {}
 /**
diff --git a/doc/log.php b/doc/log.php
index 774b841..f43e4ee 100644
--- a/doc/log.php
+++ b/doc/log.php
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * 日志模块, 可通过框架配置设置日志等级;
+ * 日志模块, 可通过框架配置设置日志等级以过滤日志输出;
  */
 namespace flame\log;
 /**

From 79d7b25e668816ac24dac593a95d4ff8b4ccc353 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 11 Dec 2018 19:12:27 +0800
Subject: [PATCH 037/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E4=BD=BF?=
 =?UTF-8?q?=E7=94=A8=20php.ini=20=E9=85=8D=E7=BD=AE=20post=5Fmax=5Fsize=20?=
 =?UTF-8?q?=E9=99=90=E5=88=B6=E6=9C=8D=E5=8A=A1=E5=99=A8=E8=AF=B7=E6=B1=82?=
 =?UTF-8?q?=E4=BD=93=E8=A7=A3=E6=9E=90;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt        |  2 +-
 src/http/_handler.cpp | 12 ++++++++----
 src/http/_handler.h   |  4 +++-
 src/http/http.cpp     |  4 +++-
 src/http/http.h       |  1 +
 5 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 947aced..be48f6b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,7 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
     OUTPUT_STRIP_TRAILING_WHITESPACE)
-set(VENDOR_PHPEXT /data/vendor/phpext-1.1.0)
+set(VENDOR_PHPEXT /data/vendor/phpext-1.1.1)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.68.0)
 set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index cf92abd..1d08c31 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -30,14 +30,18 @@ namespace flame::http
             req_.reset();
             res_.reset();
             return;
+        }else{
+            req_.reset();
+            res_.reset();
+            read_request();
         }
-        read_request();
     }
 
     void _handler::read_request()
     {
         // 读取请求
-        req_.reset(new boost::beast::http::message<true, value_body<true>>());
+        req_.reset(new boost::beast::http::request_parser<value_body<true>>());
+        req_->body_limit(body_max_size);
         boost::beast::http::async_read(socket_, buffer_, *req_, [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n)
         {
             if(error == boost::beast::http::error::end_of_stream || error == boost::asio::error::operation_aborted)
@@ -55,7 +59,7 @@ namespace flame::http
             }
             res_.reset(new boost::beast::http::message<false, value_body<false>>());
             // 默认为请求 KeepAlive 配置
-            res_->keep_alive(req_->keep_alive());
+            res_->keep_alive(req_->get().keep_alive());
             // 默认应为非 chunked 模式
             assert(!res_->chunked());
             call_handler();
@@ -69,7 +73,7 @@ namespace flame::http
         server_request *req_ptr = static_cast<server_request *>(php::native(req));
         server_response *res_ptr = static_cast<server_response *>(php::native(res));
 
-        req_ptr->build_ex(*req_); // 将 C++ 请求对象展开到 PHP 中
+        req_ptr->build_ex(req_->get()); // 将 C++ 请求对象展开到 PHP 中
         res_ptr->handler_ = shared_from_this();
 
         coroutine::start(php::callable([self = shared_from_this(), this, req, req_ptr, res, res_ptr] (php::parameters& params) -> php::value
diff --git a/src/http/_handler.h b/src/http/_handler.h
index 09df121..822a6ea 100644
--- a/src/http/_handler.h
+++ b/src/http/_handler.h
@@ -33,7 +33,9 @@ namespace flame::http
         php::object svr_obj;
         boost::asio::ip::tcp::socket socket_;
 
-        std::shared_ptr<boost::beast::http::message<true, value_body<true>>> req_;
+        
+        // std::shared_ptr<boost::beast::http::message<true, value_body<true>>> req_;
+        std::shared_ptr<boost::beast::http::request_parser<value_body<true>>> req_;
         std::shared_ptr<boost::beast::http::message<false, value_body<false>>> res_;
         boost::beast::flat_buffer buffer_;
     };
diff --git a/src/http/http.cpp b/src/http/http.cpp
index a872df9..d54e21f 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -13,12 +13,14 @@
 
 namespace flame::http {
     client* client_;
-	
+	std::int64_t body_max_size = 1024 * 1024 * 1024;
+
 	void declare(php::extension_entry& ext) {
 		client_ = nullptr;
 		ext.on_module_startup([] (php::extension_entry& ext) -> bool {
 			// 在框架初始化后创建全局HTTP客户端
             gcontroller->on_init([] (const php::array& opts) {
+				body_max_size = std::max(php::ini("post_max_size").calc(), body_max_size);
                 client_ = new client();
                 php::parameters p(0, nullptr);
                 client_->__construct(p);
diff --git a/src/http/http.h b/src/http/http.h
index f8602c8..21bf6d7 100644
--- a/src/http/http.h
+++ b/src/http/http.h
@@ -5,6 +5,7 @@ namespace http {
     class _connection_pool;
 	class client;
 	extern client* client_;
+	extern std::int64_t body_max_size;
 
 	void declare(php::extension_entry& ext);
 	php::value get(php::parameters& params);

From f1052f3d5db862a8f8df21fbec6b1602b02cb456 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 11 Dec 2018 19:46:23 +0800
Subject: [PATCH 038/146] =?UTF-8?q?=E5=AD=90=E8=BF=9B=E7=A8=8B=E5=BF=BD?=
 =?UTF-8?q?=E7=95=A5=20USR2=20=E4=BF=A1=E5=8F=B7=E6=96=B9=E4=BE=BF?=
 =?UTF-8?q?=E6=95=B4=E4=BD=93=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E9=87=8D?=
 =?UTF-8?q?=E8=BD=BD;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_worker.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index c771f39..2e6a396 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -15,7 +15,7 @@ namespace flame
     void controller_worker::await_signal() {
         signal_->async_wait([this] (const boost::system::error_code &error, int sig)
         {
-            if(error) return;
+            if(error || sig == SIGUSR2) return;
             // 似乎主进程与子进程 signal 处理之间有干扰
             // 需要后置清理
             if(gcontroller->worker_size == 0) signal_.reset();
@@ -37,7 +37,7 @@ namespace flame
         // 工作线程的使用是随机的, 需要保持其一直存在
         auto work = boost::asio::make_work_guard(gcontroller->context_y);
         // 1. 停止信号调用退出通知
-        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
+        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGUSR2, SIGINT, SIGTERM));
         await_signal();
 
         // 子进程的启动过程:

From cd4679f721bd63a79fbec7ca087b2b21c0a1b7e7 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 13 Dec 2018 22:32:25 +0800
Subject: [PATCH 039/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9AHTTP=20?=
 =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?=
 =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/http.php                | 18 +++++++++++
 src/http/client_request.cpp | 11 ++-----
 src/http/http.cpp           | 63 +++++++++++++++++++++++++++----------
 src/http/http.h             |  2 +-
 src/http/server_request.cpp |  5 ++-
 src/url.cpp                 | 38 +++++++++-------------
 src/url.h                   |  5 +--
 src/vendor.h                |  2 +-
 test/http_2.php             | 33 +++++++++++++------
 9 files changed, 115 insertions(+), 62 deletions(-)

diff --git a/doc/http.php b/doc/http.php
index 69a65b8..5494ece 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -226,6 +226,24 @@ class server_request {
      * @property string
      */
     public $rawBody;
+    /**
+     * 上传文件，形如：
+     *  [
+     *      "file_field_1"=>
+     *      [
+     *          "content-type" => "mime_type",
+     *          ... // other header field lowercased if available
+     *          "filename" => "original_file_name",
+     *          "size" => file_data_size,
+     *          "data" => "file_data", // content of the uploaded file
+     *      ],
+     *      "file_filed_2"=>
+     *      ...
+     *  ]
+     * 若无上传文件时可能为空
+     * @property array
+     */
+    public $file;
     /**
      * 用户可设置的请求关联数据;
      * 例如: 在 before 回调中从 Cookie 中提取用户信息放在 $req->data["user"] 中, 后续处理器即可访问 $req->data["user"] 获取户信息;
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 5d75e48..4b493dd 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -49,7 +49,7 @@ namespace http {
 			throw php::exception(zend_ce_exception, "request 'url' must be a string");
 		}
 
-		url_ = std::make_shared<url>(u);
+		url_ = std::make_shared<url>(u, false);
 		if(!url_->port && !url_->schema.compare(0, 5, "https")) {
 			url_->port = 443;
 		}else if(!url_->port && !url_->schema.compare(0, 4, "http")) {
@@ -62,14 +62,9 @@ namespace http {
 		// 消息容器
 		ctr_.method(boost::beast::http::string_to_verb(get("method").to_string()));
 		std::string target(url_->path.length() > 0? url_->path : "/");
-        std::string query;
-        for(auto it = url_->query.begin(); it != url_->query.end(); ++it) {
-            query.append(it->first).append("=").append(it->second).append("&");
-        }
-		if(query.size() > 0) {
-            query.erase(query.length() - 1);
+		if(!url_->query_raw.empty()) {
 			target.append("?");
-			target.append(query);
+			target.append(url_->query_raw);
 		}
 		ctr_.target(target);
 		// 头
diff --git a/src/http/http.cpp b/src/http/http.cpp
index d54e21f..1c8931b 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -86,7 +86,7 @@ namespace flame::http {
 		}
 		return r;
 	}
-	php::value ctype_decode(boost::string_view ctype, const php::string& v) {
+	php::value ctype_decode(boost::string_view ctype, const php::string& v, php::array* files) {
 		if(ctype.compare(0, 16, "application/json") == 0) {
 			return php::json_decode(v);
 		}else if(ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
@@ -94,24 +94,55 @@ namespace flame::http {
 			php::callable("parse_str")({v, data.make_ref()});
 			return data;
 		}else if(ctype.compare(0, 19, "multipart/form-data") == 0) {
-			std::size_t begin = ctype.find_first_of(';', 19) + 1;
-			php::string cache;
-			parser::separator_parser<std::string, php::buffer> p1('\0','\0','=','"','"',';', [&cache] (std::pair<std::string, php::buffer> entry) {
-				if(entry.first == "boundary" || entry.first == "name") {
-					cache = php::string(std::move(entry.second));
+			std::string boundary;
+			std::string field;
+			php::array meta(4);
+			parser::separator_parser<std::string, std::string> p1('\0','\0','=','"','"',';', [&boundary, &field, &meta] (std::pair<std::string, std::string> entry) {
+				if(entry.first == "boundary") {
+					boundary = std::move(entry.second);
+				}else if(entry.first == "content-disposition") {
+					// 此项头信息没有用途
+				}else if(entry.first == "name") {
+					// 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
+					// 表单字段名
+					field = std::move(entry.second);
+				}else{
+					meta.set(entry.first, entry.second);
 				}
 			});
-			p1.parse(ctype.data() + begin, ctype.data() + ctype.size() - (ctype.data() + begin));
-			p1.parse(";", 1); // 结尾分隔符可能不存在(可以认为数据行可能不完整)
+			std::size_t begin = ctype.find_first_of(';', 19) + 1;
+			p1.parse(ctype.data() + begin, ctype.size() - begin);
+			p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
 
-			php::array data(4);
-			parser::multipart_parser<std::string, php::buffer> p2(cache, [&p1, &cache, &begin, &data] (std::pair<std::string, php::buffer> entry) {
-				if(entry.first.size() == 0) {
-					data.set(cache, std::move(entry.second));
-				}else{
-					begin = boost::string_view(entry.second.data(), entry.second.size()).find_first_of(';') + 1;
-					p1.parse(entry.second.data() + begin, entry.second.data() + entry.second.size() - (entry.second.data() + begin));
-					p1.parse(";", 1);
+			php::array data(8);
+			parser::multipart_parser<std::string, php::buffer> p2(boundary, [&p1, &field, &meta, &data, &files] (std::pair<std::string, php::buffer> entry) {
+				if(entry.first.size() == 0) { // Part Data
+					if(meta.exists("filename") > 0) {
+						if(files) { // 接收对应文件
+							// 上传文件 meta 
+							meta["size"] = entry.second.size();
+							meta["data"] = std::move(entry.second);
+							files->set(field, meta);
+						}
+					}else{
+						data.set(field, std::move(entry.second));
+					}
+					meta = php::array(4);
+				}else{ // Part Header
+					php::lowercase_inplace(entry.first.data(), entry.first.size());
+					std::size_t end = boost::string_view(entry.second.data(), entry.second.size()).find_first_of(';');
+					if(end == std::string::npos) {
+						meta[entry.first] = std::move(entry.second);
+					}else if(entry.first == "content-disposition") {
+						// 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
+						std::size_t begin = end + 1;
+						if(entry.second.size() > begin + 2) {
+							p1.parse(entry.second.data() + begin, entry.second.size() - begin);
+							p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
+						}
+					}else{
+						meta[entry.first] = php::string(entry.second.data(), end);
+					}
 				}
 			});
 			p2.parse(v.data(), v.size());
diff --git a/src/http/http.h b/src/http/http.h
index 21bf6d7..9eafcc0 100644
--- a/src/http/http.h
+++ b/src/http/http.h
@@ -15,6 +15,6 @@ namespace http {
 	php::value exec(php::parameters& params);
 
 	php::string ctype_encode(boost::string_view ctype, const php::value& v);
-	php::value ctype_decode(boost::string_view ctype, const php::string& v);
+	php::value ctype_decode(boost::string_view ctype, const php::string& v, php::array* file = nullptr);
 }
 }
diff --git a/src/http/server_request.cpp b/src/http/server_request.cpp
index 5f873af..b8a541d 100644
--- a/src/http/server_request.cpp
+++ b/src/http/server_request.cpp
@@ -16,6 +16,7 @@ namespace http {
 			.property({"cookie", nullptr})
 			.property({"body", nullptr})
 			.property({"rawBody", ""})
+			.property({"file", nullptr})
 			.property({"data", nullptr})
 			.method<&server_request::__construct>("__construct", {}, php::PRIVATE);
 
@@ -75,7 +76,9 @@ namespace http {
 				set("rawBody", body); // 不在 multipart 时保留原始数据
 			}
 			if(ctype != ctr_.end()) { // 存在时按照类型进行解析
-				set("body", ctype_decode(ctype->value(), body));
+				php::array files(4);
+				set("body", ctype_decode(ctype->value(), body, &files));
+				set("file", files);
 			}else{ // 不存在与 rawBody 相同
 				set("body", body);
 			}
diff --git a/src/url.cpp b/src/url.cpp
index 16a18e1..9b53e2a 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -5,7 +5,7 @@ namespace flame
 {
 
     typedef ::parser::separator_parser<std::string, std::string> parser_t;
-    url::url(const php::string &str)
+    url::url(const php::string &str, bool parse_query)
         : raw_(str)
     {
         const char* s = str.c_str();
@@ -43,16 +43,19 @@ namespace flame
         }
         if (x.field_set & (1 << UF_QUERY)) {
             auto y = x.field_data[UF_QUERY];
-            parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
-                std::string key = et.first;
-                std::string val = et.second;
-                // 一律小写 KEY
-                php::lowercase_inplace(key.data(), key.size());
-                val.resize(php::url_decode_inplace(val.data(), val.size()));
-                query[key] = val;
-            });
-            p.parse(s + y.off, y.len);
-            p.end();
+            query_raw.assign(s + y.off, y.len);
+            if(parse_query) {
+                parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
+                    std::string key = et.first;
+                    std::string val = et.second;
+                    // 一律小写 KEY
+                    php::lowercase_inplace(key.data(), key.size());
+                    val.resize(php::url_decode_inplace(val.data(), val.size()));
+                    query[key] = val;
+                });
+                p.parse(s + y.off, y.len);
+                p.end();
+            }
         }
     }
 
@@ -63,18 +66,7 @@ namespace flame
             ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path;
             if(with_query)
             {
-                int x = 0;
-                for(auto i=query.begin();i!=query.end();++i)
-                {
-                    if(++x == 1)
-                    {
-                        ss.put('?');
-                    }else
-                    {
-                        ss.put('&');
-                    }
-                    ss << i->first << "=" << i->second;
-                }
+                ss << "?" << query_raw;
             }
             raw_ = ss.str();
         }
diff --git a/src/url.h b/src/url.h
index 02d63e1..cc08a2f 100644
--- a/src/url.h
+++ b/src/url.h
@@ -1,20 +1,21 @@
 #pragma once
+#include "vendor.h"
 
 namespace flame {
     class url {
     public:
-        url(const php::string& str);
+        url(const php::string& str, bool parse_query=true);
         std::string schema;
         std::string   host;
         std::uint16_t port;
         std::string   path;
         std::map<std::string, std::string> query;
+        std::string   query_raw;
         std::string   user;
         std::string   pass;
 
         std::string   str(bool update = false, bool with_query = true);
     private:
         std::string raw_;
-
     };
 }
\ No newline at end of file
diff --git a/src/vendor.h b/src/vendor.h
index 127f58b..6bebbfc 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.2"
+#define EXTENSION_VERSION "0.12.3"
 
 #include <cstdio>
 #include <sys/types.h>
diff --git a/test/http_2.php b/test/http_2.php
index fa3f7ab..09abcbe 100644
--- a/test/http_2.php
+++ b/test/http_2.php
@@ -1,19 +1,24 @@
 <?php
 flame\init("http_2");
 
-$server = new flame\http\server(":::8370");
-$poll;
-flame\go(function() use($server, &$poll) {
+flame\go(function() {
+    $server = new flame\http\server(":::56120");
+    $poll;
 
     $server->before(function($req, $res, $m) {
         $req->data["start"] = flame\time\now();
         if(!$m) {
             $res->status = 404;
-            $res->body = "Not Found\n";
+            $res->body = "404 Not Found\n";
             return false; // 阻止后续 handle / after 的运行
         }
-    })->get("/", function($req, $res) {
-        $res->body = json_encode($req);
+    })->POST("/", function($req, $res) {
+        $res->status = 200;
+        $res->header["Content-Type"] = "application/json";
+        $res->body = $req->body;
+        // 文件上传
+        var_dump($req->file);
+        // return $res->body = json_encode($req);
     })->get("/poll", function($req, $res) use(&$poll) {
         $res->header["Content-Type"] = "text/event-stream";
         $res->header["Cache-Control"] = "no-cache";
@@ -29,6 +34,10 @@
         //     }
         //     echo "done1\n";
         // });
+    })->get("/404", function($req, $res) {
+        $res->status = 404;
+        $res->header["Connection"] = "close";
+        $res->body = "404 Not Found\n";
     })->get("/push", function($req, $res) use(&$poll) {
         if($poll) {
             $poll->write("event: time\n");
@@ -40,6 +49,10 @@
     })->get("/abc", function($req, $res) {
         $res->header["Content-Type"] = "text/html";
         $res->file(__DIR__, "/coroutine_1.php");
+    })->get("/from", function($req, $res) {
+        $r = flame\http\get("http://127.0.0.1:56120/");
+        flame\time\sleep(10000);
+        $res->body = $r->body;
     })->after(function($req, $res, $m) {
         $end = flame\time\now();
         // echo "elapsed: ", ($end - $req->data["start"]), "ms\n";
@@ -48,9 +61,9 @@
     echo "done2\n";
 });
 
-flame\on("quit", function() use($server) {
-    echo "quit\n";
-    $server->close();
-});
+// flame\on("quit", function() use($server) {
+//     echo "quit\n";
+//     $server->close();
+// });
 
 flame\run();
\ No newline at end of file

From ad21d32f84dea115cc5abba045491594e8d84c7a Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 14 Dec 2018 19:17:51 +0800
Subject: [PATCH 040/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9AMongoDB=20?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=B1=A0=E5=B9=B6=E5=8F=91=E9=87=8F=E5=A4=A7?=
 =?UTF-8?q?=E6=97=B6=E9=98=BB=E5=A1=9E=EF=BC=9B=E4=BC=98=E5=8C=96=EF=BC=9A?=
 =?UTF-8?q?=E5=9C=A8=E7=BB=93=E6=9E=9C=E9=9B=86=E6=B8=B8=E6=A0=87=E8=AF=BB?=
 =?UTF-8?q?=E5=8F=96=E5=AE=8C=E6=AF=95=E6=97=B6=E6=8F=90=E5=89=8D=E9=87=8A?=
 =?UTF-8?q?=E6=94=BE=E8=BF=9E=E6=8E=A5=EF=BC=9B=E6=96=87=E6=A1=A3=EF=BC=9A?=
 =?UTF-8?q?=E8=A1=A5=E5=85=85=E4=B8=8D=E8=A7=84=E8=8C=83=E4=BD=BF=E7=94=A8?=
 =?UTF-8?q?=E6=97=B6=E5=8F=AF=E8=83=BD=E5=AF=BC=E8=87=B4=E6=AD=BB=E9=94=81?=
 =?UTF-8?q?=E7=9A=84=E8=AF=B4=E6=98=8E=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                   |  4 ++--
 doc/mongodb.php                  |  6 ++++--
 doc/mysql.php                    | 12 ++++++-----
 src/mongodb/_connection_pool.cpp | 28 ++++++++++++++++++-------
 src/mongodb/_connection_pool.h   |  3 +++
 src/mongodb/cursor.cpp           | 32 +++++++++++++++++++++--------
 src/mysql/_connection_pool.cpp   |  7 ++++---
 src/mysql/result.cpp             | 35 +++++++++++++++++++++++---------
 test/mongodb_1.php               | 28 ++++++++++++++++---------
 9 files changed, 108 insertions(+), 47 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index be48f6b..0c90cc7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,8 +18,8 @@ set(VENDOR_AMQP /data/vendor/amqpcpp-4.0.0)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-0.11.6)
 set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.8.1)
 # 基本参数
-set(CMAKE_BUILD_TYPE "Debug")
-# set(CMAKE_BUILD_TYPE "Release")
+# set(CMAKE_BUILD_TYPE "Debug")
+set(CMAKE_BUILD_TYPE "Release")
 # set(CMAKE_C_COMPILER /data/vendor/llvm-7.0.0/bin/clang)
 # set(CMAKE_CXX_COMPILER /data/vendor/llvm-7.0.0/bin/clang++)
 set(CMAKE_C_COMPILER /data/vendor/gcc-8.2.0/bin/gcc)
diff --git a/doc/mongodb.php b/doc/mongodb.php
index c643e85..f2de53d 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -1,6 +1,8 @@
 <?php
 /**
  * MongoDB 基本客户端
+ * 注意:
+ * 1. 同一协程中连续进行 MongoDB 查询操作, 且游标不读取数据不销毁 (或 不读取完不销毁) 可能导致进程死锁; (请将游标读取完毕 或 主动 unset 释放游标对象)
  */
 namespace flame\mongodb;
 
@@ -47,11 +49,11 @@ function aggregate(array $pipeline): cursor {}
 }
 class cursor {
     /**
-     * 读取当前 cursor 返回一个文档; 若返回已完成, 返回 null;
+     * 读取当前 cursor 返回一个文档; 若读取已完成, 返回 null;
      */
     function fetch_row():mixed {}
     /**
-     * 读取当前 cursor 返回所有文档 (二维)
+     * 读取当前 cursor 返回所有文档 (二维); 若读取已完成, 返回 null;
      */
     function fetch_all(): array {}
 }
diff --git a/doc/mysql.php b/doc/mysql.php
index dba29a2..850d1f1 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -1,6 +1,8 @@
 <?php
 /**
- * 
+ * MySQL 客户端
+ * 注意:
+ * 1. 同一协程中连续进行 MySQL 查询操作, 且结果集数据不读取且不销毁(或不读取完且不销毁)可能导致进程死锁; (请将结果集读取完 或 主动 unset 释放结果集对象)
  */
 namespace flame\mysql;
 /**
@@ -186,15 +188,15 @@ class result {
      */
     public $fetched_rows;
     /**
-     * 取出下一行
+     * 读取下一行
      * @return 下一行数据关联数组;
-     *  若不存在下一行, 返回 NULL
+     *  若读取已完成, 返回 NULL
      */
     function fetch_row():?array {}
     /**
-     * 取出(剩余)全部行
+     * 读取 (剩余) 全部行
      * @return 二维数组, 可能为空数组; 
-     *  若当前对象不包含结果集(更新型查询), 返回 NULL
+     *  若读取已完成, 返回 NULL
      */
     function fetch_all():?array {}
 };
\ No newline at end of file
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index 1494e03..515121b 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -4,6 +4,7 @@
 namespace flame::mongodb 
 {
     _connection_pool::_connection_pool(const std::string& url)
+    : guard_(gcontroller->context_y)
     {
         std::unique_ptr<mongoc_uri_t, void (*)(mongoc_uri_t *)> uri(mongoc_uri_new(url.c_str()), mongoc_uri_destroy);
         const bson_t* options = mongoc_uri_get_options(uri.get());
@@ -23,16 +24,29 @@ namespace flame::mongodb
     {
         std::shared_ptr<mongoc_client_t> conn;
         auto self = shared_from_this();
-        boost::asio::post(gcontroller->context_y, [this, self, &conn, &ch] () {
-            // 对应释放(归还)连接过程, 须持有当前对象的引用 self 
-            // (否则当前对象可能先于连接释放被销毁)
-            conn.reset(mongoc_client_pool_pop(p_), [this, self] (mongoc_client_t* c)
-            {
-                mongoc_client_pool_push(p_, c);
+        boost::asio::post(guard_, [this, self, &conn, &ch] () {
+            await_.push_back([this, self, &conn, &ch] (mongoc_client_t* c) {
+                // 对应释放(归还)连接过程, 须持有当前对象的引用 self (否则当前对象可能先于连接释放被销毁)
+                conn.reset(c, [this, self] (mongoc_client_t* c) {
+                    boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+                });
+                ch.resume();
             });
-            ch.resume();
+            mongoc_client_t* c = mongoc_client_pool_try_pop(p_);
+            std::cout << "try pop: " << c << "\n";
+            if(c) release(c);
         });
         ch.suspend();
         return conn;
     }
+    void _connection_pool::release(mongoc_client_t* c) {
+        std::cout << "release: " << c << std::endl;
+        if(await_.empty()) {
+            mongoc_client_pool_push(p_, c);
+        }else{
+            auto cb = await_.front();
+            await_.pop_front();
+            cb(c);
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/mongodb/_connection_pool.h b/src/mongodb/_connection_pool.h
index 1674a55..7c18993 100644
--- a/src/mongodb/_connection_pool.h
+++ b/src/mongodb/_connection_pool.h
@@ -10,7 +10,10 @@ namespace flame::mongodb {
         _connection_pool(const std::string& url);
         ~_connection_pool();
         std::shared_ptr<mongoc_client_t> acquire(coroutine_handler &ch) override;
+        void release(mongoc_client_t* c);
     private:
         mongoc_client_pool_t* p_;
+        boost::asio::io_context::strand guard_; // 防止下面队列并行访问
+        std::list<std::function<void(mongoc_client_t *)>> await_;
     };
 }
diff --git a/src/mongodb/cursor.cpp b/src/mongodb/cursor.cpp
index 4b5846e..1549d4f 100644
--- a/src/mongodb/cursor.cpp
+++ b/src/mongodb/cursor.cpp
@@ -15,17 +15,33 @@ namespace flame::mongodb
     }
     php::value cursor::fetch_row(php::parameters &params)
     {
-        coroutine_handler ch{coroutine::current};
-        return cl_->fetch(cs_, ch);
+        php::array row(nullptr);
+        if(cs_ && cl_) {
+            coroutine_handler ch{coroutine::current};
+            row = cl_->fetch(cs_, ch);
+        }
+        if(row.typeof(php::TYPE::NULLABLE)) {
+            // 尽早释放连接
+            cs_.reset();
+            cl_.reset();
+        }
+        return row;
     }
     php::value cursor::fetch_all(php::parameters &params)
     {
-        coroutine_handler ch{coroutine::current};
-        php::array data(8);
-        for(php::array row = cl_->fetch(cs_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
-        {
-            data.set(data.size(), row);
+        if(cs_ && cl_) {
+            coroutine_handler ch{coroutine::current};
+            php::array data(8);
+            for(php::array row = cl_->fetch(cs_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
+            {
+                data.set(data.size(), row);
+            }
+            // 尽早释放连接
+            cs_.reset();
+            cl_.reset();
+            return data;
+        }else{
+            return nullptr;
         }
-        return data;
     }
 } // namespace flame::mongodb
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index fff0b6d..0ca9c0a 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -38,8 +38,6 @@ namespace flame::mysql
                 { // 可用连接
                     MYSQL *c = conn_.front().conn;
                     conn_.pop_front();
-                    if(version_ >= 50700) mysql_reset_connection(c);
-                    else mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
                     release(c);
                     return;
                 }
@@ -91,6 +89,10 @@ namespace flame::mysql
         }
         else
         { // 立刻分配使用
+            // 每次连接复用前，需要清理状态
+            if(version_ >= 50700) mysql_reset_connection(c);
+            else mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+
             std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
             await_.pop_front();
             auto self = this->shared_from_this();
@@ -102,7 +104,6 @@ namespace flame::mysql
                 }));
         }
     }
-
     void _connection_pool::sweep() {
         tm_.expires_from_now(std::chrono::seconds(60));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
diff --git a/src/mysql/result.cpp b/src/mysql/result.cpp
index 7b20112..923bf64 100644
--- a/src/mysql/result.cpp
+++ b/src/mysql/result.cpp
@@ -16,19 +16,34 @@ namespace flame::mysql
     }
     php::value result::fetch_row(php::parameters &params)
     {
-        coroutine_handler ch{coroutine::current};
-        return cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch);
+        php::array row(nullptr);
+        if(cl_ && rs_) {
+            coroutine_handler ch{coroutine::current};
+            row = cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch);
+        }
+        if(row.typeof(php::TYPE::NULLABLE)) {
+            // 尽早的释放连接
+            rs_.reset();
+            cl_.reset();
+        }
+        return row;
     }
     php::value result::fetch_all(php::parameters &params)
     {
-        coroutine_handler ch{coroutine::current};
-        auto conn = cl_->acquire(ch);
-        php::array data {4}, row;
-        
-        if(!rs_) return nullptr;
-        for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) {
-            data.set(data.size(), row);
+        if(cl_ && rs_) {
+            coroutine_handler ch{coroutine::current};
+            auto conn = cl_->acquire(ch);
+            php::array data {4}, row;
+
+            for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) {
+                data.set(data.size(), row);
+            }
+            // 尽早的释放连接
+            rs_.reset();
+            cl_.reset();
+            return data;
+        }else{
+            return nullptr;
         }
-        return data;
     }
 } // namespace flame::mysql
diff --git a/test/mongodb_1.php b/test/mongodb_1.php
index e761fc9..c015991 100644
--- a/test/mongodb_1.php
+++ b/test/mongodb_1.php
@@ -3,16 +3,24 @@
 
 flame\go(function() {
     $cli = flame\mongodb\connect("mongodb://username:password@host1:port1,host2:port2/database?replicaSet=rs1");
-    $cs = $cli->execute([
-        "find"   => "photo",
-        "filter" => ["uid" => 25865119921602568],
-        "limit"  => 10,
-    ]);
-    var_dump( $cs->fetch_row() );
-    var_dump( $cs->fetch_all() );
-
-    $cs = $cli->photo->find(["uid" => 25865119921602568]);
-    var_dump( $cs->fetch_all() );
+    for($i=0;$i<10;++$i) {
+        flame\go(function() use($cli, $i) {
+            $cs = $cli->execute([
+                "find"   => "photo",
+                "filter" => ["uid" => 25865119921602568],
+                "limit"  => 10,
+            ]);
+            echo "execute\n";
+            // var_dump( $cs->fetch_row() );
+            $cs->fetch_all();
+            flame\time\sleep(100);
+            // unset($cs);
+            echo "sleep\n";
+            $cs = $cli->photo->find(["uid" => 25865119921602568]);
+            // var_dump( $cs->fetch_all() );
+            echo "done: ". $i. "\n";
+        });
+    }
 });
 
 

From f83d80d65e68c224019c83b2c453e39938143a47 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 16 Dec 2018 19:55:48 +0800
Subject: [PATCH 041/146] v0.12.4

---
 src/mongodb/_connection_pool.cpp | 2 --
 src/vendor.h                     | 2 +-
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index 515121b..a6a463d 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -33,14 +33,12 @@ namespace flame::mongodb
                 ch.resume();
             });
             mongoc_client_t* c = mongoc_client_pool_try_pop(p_);
-            std::cout << "try pop: " << c << "\n";
             if(c) release(c);
         });
         ch.suspend();
         return conn;
     }
     void _connection_pool::release(mongoc_client_t* c) {
-        std::cout << "release: " << c << std::endl;
         if(await_.empty()) {
             mongoc_client_pool_push(p_, c);
         }else{
diff --git a/src/vendor.h b/src/vendor.h
index 6bebbfc..90f97ef 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.3"
+#define EXTENSION_VERSION "0.12.4"
 
 #include <cstdio>
 #include <sys/types.h>

From 9e43c78c535b42b5958617fd9c23c79df643cffe Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 18 Dec 2018 14:22:45 +0800
Subject: [PATCH 042/146] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?=
 =?UTF-8?q?=E7=89=88=E6=9C=AC=EF=BC=9Bv0.12.5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                 | 52 ++++++++++++++++++++--------------
 README.md                      | 10 +++----
 src/flame.cpp                  |  2 +-
 src/http/_handler.cpp          |  2 +-
 src/mongodb/client.cpp         |  4 +++
 src/mysql/_connection_base.cpp |  2 +-
 src/redis/_connection_pool.cpp |  2 +-
 src/tcp/server.cpp             |  2 +-
 src/vendor.h                   |  2 +-
 9 files changed, 45 insertions(+), 33 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0c90cc7..4cebbbf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,42 +1,50 @@
 cmake_minimum_required(VERSION 3.10)
-
+# 默认编译器
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE "Debug")
+endif(NOT CMAKE_BUILD_TYPE)
+if(NOT CMAKE_C_COMPILER)
+    set(CMAKE_C_COMPILER /data/vendor/llvm-7.0.0/bin/clang)
+endif()
+if(NOT CMAKE_CXX_COMPILER)
+    set(CMAKE_CXX_COMPILER /data/vendor/llvm-7.0.0/bin/clang++)
+endif()
+project(FLAME)
+# 项目源文件
+execute_process(COMMAND find ${CMAKE_SOURCE_DIR}/src -name *.cpp
+    COMMAND tr "\n" ";"
+    OUTPUT_VARIABLE SOURCES)
+add_library(FLAME SHARED ${SOURCES})
 # 依赖项目录
 set(VENDOR_GCC /data/vendor/gcc-8.2.0)
 set(VENDOR_LLVM /data/vendor/llvm-7.0.0)
-set(VENDOR_PHP /data/vendor/php-7.2.11)
+set(VENDOR_PHP /data/vendor/php-7.2.13)
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
     OUTPUT_STRIP_TRAILING_WHITESPACE)
 set(VENDOR_PHPEXT /data/vendor/phpext-1.1.1)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
-set(VENDOR_BOOST /data/vendor/boost-1.68.0)
+set(VENDOR_BOOST /data/vendor/boost-1.69.0)
 set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.13.0)
-set(VENDOR_AMQP /data/vendor/amqpcpp-4.0.0)
+set(VENDOR_AMQP /data/vendor/amqpcpp-4.0.1)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-0.11.6)
 set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.8.1)
-# 基本参数
-# set(CMAKE_BUILD_TYPE "Debug")
-set(CMAKE_BUILD_TYPE "Release")
-# set(CMAKE_C_COMPILER /data/vendor/llvm-7.0.0/bin/clang)
-# set(CMAKE_CXX_COMPILER /data/vendor/llvm-7.0.0/bin/clang++)
-set(CMAKE_C_COMPILER /data/vendor/gcc-8.2.0/bin/gcc)
-set(CMAKE_CXX_COMPILER /data/vendor/gcc-8.2.0/bin/g++)
+# 编译选项
+set(CUSTOM_COMPILE_FLAGS "-std=c++17")
+set(CUSTOM_LINK_FLAGS "-static-libstdc++")
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+    set(CUSTOM_COMPILE_FLAGS "${CUSTOM_COMPILE_FLAGS} --gcc-toolchain=${VENDOR_GCC}")
+    set(CUSTOM_LINK_FLAGS "${CUSTOM_LINK_FLAGS} --gcc-toolchain=${VENDOR_GCC}")
+endif()
 set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
-# 项目
-project(FLAME)
-execute_process(COMMAND find ${CMAKE_SOURCE_DIR}/src -name *.cpp
-    COMMAND tr "\n" ";"
-    OUTPUT_VARIABLE SOURCES)
-add_library(FLAME SHARED ${SOURCES})
 set_target_properties(FLAME PROPERTIES
-#   COMPILE_FLAGS "--gcc-toolchain=${VENDOR_GCC} -std=c++17"
-   COMPILE_FLAGS "-std=c++17"
-   LINK_FLAGS "-static-libstdc++"
-   PREFIX ""
-   OUTPUT_NAME "flame")
+    COMPILE_FLAGS "${CUSTOM_COMPILE_FLAGS}"
+    LINK_FLAGS "${CUSTOM_LINK_FLAGS}"
+    PREFIX ""
+    OUTPUT_NAME "flame")
 # 包含路径
 target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_PHP_INCLUDES}
diff --git a/README.md b/README.md
index 404ca16..9276f71 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,13 @@
 
 #### PHP
 ``` Bash
-./configure  --prefix=/data/vendor/php-7.2.11 --with-config-file-path=/data/vendor/php-7.0.32/etc --disable-fpm --disable-phar --disable-dom --disable-libxml --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-openssl --with-readline --enable-mbstring --without-pear
+./configure  --prefix=/data/vendor/php-7.2.13 --with-config-file-path=/data/vendor/php-7.2.13/etc --disable-fpm --disable-phar --disable-dom --disable-libxml --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-openssl --with-readline --enable-mbstring --without-pear
 ```
 #### Boost
 
 ``` Bash
-./bootstrap.sh --prefix=/data/vendor/boost-1.68.0
-./b2 -j4 --prefix=/data/vendor/boost-1.68.0 cxxflags="-fPIC" variant=release link=static threading=multi install
+./bootstrap.sh --prefix=/data/vendor/boost-1.69.0
+./b2 -j4 --prefix=/data/vendor/boost-1.69.0 cxxflags="-fPIC" variant=release link=static threading=multi install
 ```
 
 #### libphpext
@@ -40,10 +40,10 @@ rm /data/vendor/mongoc-1.13.0/lib/*.so*
 #### AMQP-CPP
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake3 -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.0.0 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
+CC=gcc CXX=g++ cmake3 -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.0.1 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
 make
 make install
-rm /data/vendor/amqpcpp-4.0.0/lib/*.so*
+# rm /data/vendor/amqpcpp-4.0.1/lib/*.so*
 ```
 
 #### Rdkafka
diff --git a/src/flame.cpp b/src/flame.cpp
index bad73d0..4644dfe 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -117,7 +117,7 @@ extern "C"
         ext
             .desc({"vendor/boost", BOOST_LIB_VERSION})
             .desc({"vendor/libphpext", PHPEXT_LIB_VERSION})
-            .desc({"vendor/amqpcpp", "4.0.0"})
+            .desc({"vendor/amqpcpp", "4.0.1"})
             .desc({"vendor/mysqlc", PACKAGE_VERSION})
             .desc({"vendor/librdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S});
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 1d08c31..d3f1776 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -25,7 +25,7 @@ namespace flame::http
         // 第一次请求或允许复用
         if ((gcontroller->status & controller::controller_status::STATUS_SHUTDOWN)
                 || svr_ptr->closed_
-                || req_ && !req_->keep_alive() || res_ && !res_->keep_alive()) 
+                || (req_ && !req_->keep_alive()) || (res_ && !res_->keep_alive()))
         {
             req_.reset();
             res_.reset();
diff --git a/src/mongodb/client.cpp b/src/mongodb/client.cpp
index 75285a6..c9f3b5f 100644
--- a/src/mongodb/client.cpp
+++ b/src/mongodb/client.cpp
@@ -29,6 +29,10 @@ namespace flame::mongodb
             });
         ext.add(std::move(class_client));
     }
+    php::value client::__construct(php::parameters &params)
+    {
+        return nullptr;
+    }
     php::value client::execute(php::parameters &params)
     {
         bool write = false;
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 3920449..3ef5255 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -126,7 +126,7 @@ ESCAPE_FINISHED:;
         else // 更新型操作
         {
             php::array data(2);
-            data.set(php::string("affected_rows", 11), static_cast<std::int64_t>(mysql_affected_rows(conn.get())));
+            data.set(php::string("affected_rows", 13), static_cast<std::int64_t>(mysql_affected_rows(conn.get())));
             data.set(php::string("insert_id", 9), static_cast<std::int64_t>(mysql_insert_id(conn.get())));
             return std::move(data);
         }
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index b082784..5716079 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -50,7 +50,7 @@ namespace flame::redis
                 return; // 已建立了足够多的连接, 需要等待已分配连接释放
 
             redisContext *c = create();
-            if (c == nullptr)
+            if (c == nullptr) // 创建新连接失败
             {
                 await_.pop_back();
                 ch.resume();
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index a965882..2beb37c 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -97,7 +97,7 @@ namespace flame::tcp
                     }
                     catch(const php::exception& ex)
                     {
-                        std::clog << "[" << time::iso << "] (ERROR) " << ex.what() << std::endl;
+                        std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                     return nullptr;
                 }));
diff --git a/src/vendor.h b/src/vendor.h
index 90f97ef..e746e7a 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.4"
+#define EXTENSION_VERSION "0.12.5"
 
 #include <cstdio>
 #include <sys/types.h>

From 64940894d34894faddfa2e406db19166e71434eb Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 21 Dec 2018 12:55:23 +0800
Subject: [PATCH 043/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=AE=8C?=
 =?UTF-8?q?=E5=96=84=E8=A1=A5=E5=85=85=E9=94=99=E8=AF=AF=E5=A0=86=E6=A0=88?=
 =?UTF-8?q?=E4=BF=A1=E6=81=AF=EF=BC=9B=E4=BC=98=E5=8C=96=EF=BC=9ARedis=20?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=AD=98=E6=B4=BB=E6=A3=80=E6=9F=A5=E9=A2=91?=
 =?UTF-8?q?=E7=8E=87=E8=BF=87=E9=AB=98=EF=BC=9B=E4=BF=AE=E6=AD=A3:=20Redis?=
 =?UTF-8?q?=20=E8=BF=9E=E6=8E=A5=E5=AD=98=E6=B4=BB=E6=A3=80=E6=9F=A5?=
 =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E5=86=85=E5=AD=98=E6=B3=84=E9=9C=B2?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                 |  1 -
 src/flame.cpp                  | 10 ++++----
 src/http/_handler.cpp          |  8 +++---
 src/kafka/consumer.cpp         |  6 +++--
 src/log/log.cpp                | 11 ++++----
 src/rabbitmq/consumer.cpp      |  4 ++-
 src/redis/_connection_lock.cpp |  2 ++
 src/redis/_connection_pool.cpp | 47 +++++++++++++++++++---------------
 src/tcp/server.cpp             |  4 ++-
 src/vendor.h                   |  2 +-
 10 files changed, 55 insertions(+), 40 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4cebbbf..0ac7b06 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,7 +39,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
     set(CUSTOM_COMPILE_FLAGS "${CUSTOM_COMPILE_FLAGS} --gcc-toolchain=${VENDOR_GCC}")
     set(CUSTOM_LINK_FLAGS "${CUSTOM_LINK_FLAGS} --gcc-toolchain=${VENDOR_GCC}")
 endif()
-set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
 set_target_properties(FLAME PROPERTIES
     COMPILE_FLAGS "${CUSTOM_COMPILE_FLAGS}"
     LINK_FLAGS "${CUSTOM_LINK_FLAGS}"
diff --git a/src/flame.cpp b/src/flame.cpp
index 4644dfe..47e81ad 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -45,11 +45,11 @@ namespace flame
                     ++x;
                     i->second.call({ex});
                 }
-                // php::object obj = ex;
+                php::object obj = ex;
                 if(x==0)
                 {
-                    // std::cerr << "[" << time::iso() << "] (FATAL) " << obj.call("gettraceasstring") << "\n";
-                    std::cerr << "[" << time::iso() << "] (FATAL) " << ex.what() << "\n";
+                    std::cerr << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
+                    // std::cerr << "[" << time::iso() << "] (FATAL) " << ex.what() << "\n";
                     boost::asio::post(gcontroller->context_x, [] () {
                         gcontroller->status |= controller::controller_status::STATUS_EXCEPTION;
                         gcontroller->context_x.stop();
@@ -57,8 +57,8 @@ namespace flame
                     });
                 }else
                 {
-                    // std::clog << "[" << time::iso() << "] (ERROR) " << obj.call("gettraceasstring") << "\n";
-                    std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << "\n";
+                    std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
+                    // std::cerr << "[" << time::iso() << "] (ERROR) " << ex.what() << "\n";
                 }
             }
             return rv;
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index d3f1776..1a8a308 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -54,7 +54,7 @@ namespace flame::http
             {   
                 res_.reset();
                 req_.reset();
-                std::clog << "[" << time::iso() << "] (WARNING) failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
+                std::cerr << "[" << time::iso() << "] (WARNING) failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
                 return;
             }
             res_.reset(new boost::beast::http::message<false, value_body<false>>());
@@ -115,7 +115,9 @@ namespace flame::http
             {
                 res_.reset();
                 req_.reset();
-                std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                php::object obj = ex;
+                std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
+                // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                 return nullptr;
             }
 HANDLE_BREAK:
@@ -272,7 +274,7 @@ namespace flame::http
                 req_.reset();
                 if (error)
                 {
-                    std::clog << "[" << time::iso() << "] (ERROR) failed to write_end: (" << error.value() << ") " << error.message() << std::endl;
+                    std::cerr << "[" << time::iso() << "] (ERROR) failed to write_end: (" << error.value() << ") " << error.message() << std::endl;
                 }
                 else
                 {
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index 8e6c6ca..1322d77 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -52,9 +52,11 @@ namespace flame::kafka {
                     {
                         cb_.call({msg});
                     }
-                    catch(const std::exception& ex)
+                    catch(const php::exception& ex)
                     {
-                        std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                        php::object obj = ex;
+                        std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
+                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
                 if (--count == 0) // 当所有并行协程结束后, 停止消费
diff --git a/src/log/log.cpp b/src/log/log.cpp
index 569d98c..3bb8823 100644
--- a/src/log/log.cpp
+++ b/src/log/log.cpp
@@ -27,14 +27,15 @@ namespace flame::log
     static void write_ex(int lv, php::parameters& params)
     {
         if(lv < level) return;
-        std::clog << '[' << time::iso() << "] (";
-        std::clog << LEVEL_S[lv];
-        std::clog << ") ";
+        std::ostream& os = lv > LEVEL_WARNING ? std::cout : std::cerr;
+        os << '[' << time::iso() << "] (";
+        os << LEVEL_S[lv];
+        os << ") ";
         for (int i = 0; i < params.size(); ++i)
         {
-            std::clog << ' ' << params[i].ptr();
+            os << ' ' << params[i].ptr();
         }
-        std::clog << '\n';
+        os << std::endl;
     }
     static php::value trace(php::parameters &params)
     {
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index 1ed0f51..0113fee 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -58,7 +58,9 @@ namespace flame::rabbitmq
                     }
                     catch(const php::exception& ex)
                     {
-                        std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                        php::object obj = ex;
+                        std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
+                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
                 if(--count == 0)
diff --git a/src/redis/_connection_lock.cpp b/src/redis/_connection_lock.cpp
index 682e5bc..d4807da 100644
--- a/src/redis/_connection_lock.cpp
+++ b/src/redis/_connection_lock.cpp
@@ -46,10 +46,12 @@ namespace flame::redis
             ch.resume();
         });
         ch.suspend();
+        // TODO 若执行过程当中存在异常，如何上报？（似乎 Redis 原则上不会部分失败）
         // 整合个命令返回值
         php::array data(cmds_.size());
         for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) {
             data.set(data.size(), reply2value(i->reply, i->argv, i->type));
+            freeReplyObject(i->reply);
         }
         // 命令执行完毕 (所有返回值处理完成)
         cmds_.clear();
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 5716079..7bea211 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -29,9 +29,10 @@ namespace flame::redis
                 // RESUME 需要在主线程进行
                 ch.resume();
             });
+            auto now = std::chrono::steady_clock::now();
             while (!conn_.empty())
             {
-                if(ping(conn_.front().conn))
+                if(now - conn_.front().ttl < std::chrono::seconds(15) || ping(conn_.front().conn))
                 {
                     // 可用连接
                     redisContext *c = conn_.front().conn;
@@ -81,9 +82,15 @@ namespace flame::redis
             ch.resume();
         });
         ch.suspend();
-        php::value rv = reply2value(rp, argv, rt);
-        freeReplyObject(rp);
-        return std::move(rv);
+        if(rp) {
+            php::value rv = reply2value(rp, argv, rt);
+            freeReplyObject(rp);
+            return std::move(rv);
+        }else if(rc->err) {
+            throw php::exception(zend_ce_exception, (boost::format("failed to exec Redis command: (%d) %s") % rc->err % rc->errstr).str(), rc->err);
+        }else{
+            return nullptr;
+        }
     }
     php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler& ch)
     {
@@ -98,25 +105,19 @@ namespace flame::redis
         if(!c || c->err) {
             return nullptr;
         }
-        if(!url_.pass.empty()) {
-            // 认证
-            redisReply* rp = (redisReply*)redisCommand(c, "AUTH %s", url_.pass.c_str());
+        if(!url_.pass.empty()) { // 认证
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply*)redisCommand(c, "AUTH %s", url_.pass.c_str()),
+                (void (*)(redisReply*))freeReplyObject);
             if(!rp || rp->type == REDIS_REPLY_ERROR) {
-                if(rp) {
-                    redisFree(c);
-                    freeReplyObject(rp);
-                }
+                redisFree(c);
                 return nullptr;
             }
         }
-        if(url_.path.length() > 1) {
-            // 指定数据库
-            redisReply *rp = (redisReply *)redisCommand(c, "SELECT %d", std::atoi(url_.path.c_str() + 1));
+        if(url_.path.length() > 1) { // 指定数据库
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "SELECT %d", std::atoi(url_.path.c_str() + 1)),
+                (void (*)(redisReply*))freeReplyObject);
             if(!rp || rp->type == REDIS_REPLY_ERROR) {
-                if(rp) {
-                    redisFree(c);
-                    freeReplyObject(rp);
-                }
+                redisFree(c);
                 return nullptr;
             }
         }
@@ -124,7 +125,12 @@ namespace flame::redis
     }
     void _connection_pool::release(redisContext *c)
     {
-        if (await_.empty())
+        if(c->err)
+        {
+            // 出现上下文异常的连接直接抛弃
+            --size_;
+        }
+        else if (await_.empty())
         { // 无等待分配的请求
             conn_.push_back({c, std::chrono::steady_clock::now()});
         }
@@ -143,10 +149,9 @@ namespace flame::redis
         }
     }
     bool _connection_pool::ping(redisContext* c) {
-        redisReply *rp = (redisReply *)redisCommand(c, "PING");
+        std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "PING"), (void (*)(redisReply*))freeReplyObject);
         if (!rp || rp->type == REDIS_REPLY_ERROR)
         {
-            if (rp) freeReplyObject(rp);
             return false;
         }
         return true;
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index 2beb37c..51e0e5b 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -97,7 +97,9 @@ namespace flame::tcp
                     }
                     catch(const php::exception& ex)
                     {
-                        std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                        php::object obj = ex;
+                        std::cerr << "[" << time::iso() << "] (ERROR) "<< obj.call("__toString") << "\n";
+                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                     return nullptr;
                 }));
diff --git a/src/vendor.h b/src/vendor.h
index e746e7a..06c56f7 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.5"
+#define EXTENSION_VERSION "0.12.6"
 
 #include <cstdio>
 #include <sys/types.h>

From 9c6b41548e712a6c80e41ac47def4a638de67cfb Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 23 Dec 2018 19:31:42 +0800
Subject: [PATCH 044/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9Aflame\co=5Fi?=
 =?UTF-8?q?d()=20=E8=BF=94=E5=9B=9E=E5=8D=8F=E7=A8=8B=E6=A0=87=E8=AF=86?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/core.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/core.cpp b/src/core.cpp
index 79d18a1..2e53e79 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -25,9 +25,13 @@ namespace flame::core {
         std::shared_ptr<coroutine_queue<php::value>> q = select_queue(qs, ch);
         return mm[q];
     }
+    php::value co_id(php::parameters& params) {
+        return reinterpret_cast<uintptr_t>(coroutine::current.get());
+    }
     void declare(php::extension_entry &ext) {
         ext
-            .function<select>("flame\\select");
+            .function<select>("flame\\select")
+            .function<co_id>("flame\\co_id");
 
         queue::declare(ext);
         mutex::declare(ext);

From 341b8e64c27ed7906ddbad1ae21657fc0c520b4a Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 25 Dec 2018 13:44:15 +0800
Subject: [PATCH 045/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E7=94=B1?=
 =?UTF-8?q?=E4=BA=8E=20mysql=5Freset=5Fconnection=20=E5=A4=8D=E7=94=A8?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=97=B6=E9=87=8D=E7=BD=AE=EF=BC=8C=E5=AF=BC?=
 =?UTF-8?q?=E8=87=B4=E5=AD=97=E7=AC=A6=E9=9B=86=E8=AE=BE=E7=BD=AE=E4=B8=A2?=
 =?UTF-8?q?=E5=A4=B1=E9=97=AE=E9=A2=98=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mysql.php                  |  2 +-
 src/mysql/_connection_pool.cpp | 29 ++++++++++++++++++++++++++---
 src/mysql/_connection_pool.h   |  2 ++
 3 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/doc/mysql.php b/doc/mysql.php
index 850d1f1..4f58592 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -11,7 +11,7 @@
  *  mysql://{user}:{pass}@{host}:{port}/{database}?opt1=val1
  *  目前可用的选项如下:
  *      * "charset" => 字符集
- * 
+ * 注意：若服务端配置默认字符集与上述字符集不同，每次连接使用（复用）均会进行字符集设置，这里可能会产生部分额外消耗；
  * @return client 客户端对象
  */
 function connect($url): client {}
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 0ca9c0a..2ae2214 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -6,6 +6,7 @@ namespace flame::mysql
         
     _connection_pool::_connection_pool(url u)
         : url_(std::move(u)), min_(2), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        , charset_(boost::logic::indeterminate)
     {
         if(!url_.query.count("charset")) {
             url_.query["charset"] = "utf8";
@@ -56,6 +57,7 @@ namespace flame::mysql
                 ch.resume();
             }else{
                 ++size_; // 当前还存在的连接数量
+                version_ = mysql_get_server_version(c);
                 release(c);
             }
         });
@@ -68,16 +70,17 @@ namespace flame::mysql
         return conn;
     }
 
-    MYSQL* _connection_pool::create() {
+    MYSQL* _connection_pool::create()
+    {
         MYSQL* c = mysql_init(nullptr);
-        mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
+        // 这里的 CHARSET 设定会被 reset_connection 重置同时不会影响 client_charactor_set_name 变量
+        // mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
         unsigned int timeout = 5; // 连接超时
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
         if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0))
         {
             return nullptr;
         }
-        version_ = mysql_get_server_version(c);
         return c;
     }
 
@@ -92,6 +95,9 @@ namespace flame::mysql
             // 每次连接复用前，需要清理状态
             if(version_ >= 50700) mysql_reset_connection(c);
             else mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+            // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
+            if(boost::logic::indeterminate(charset_)) query_charset(c);
+            if(charset_) mysql_set_character_set(c, url_.query["charset"].c_str());
 
             std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
             await_.pop_front();
@@ -104,6 +110,23 @@ namespace flame::mysql
                 }));
         }
     }
+
+    void _connection_pool::query_charset(MYSQL* c) {
+        // 使用 mysql_get_charactor_set_name() 获取到的字符集与下述查询不同
+        int err = mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59);
+        charset_ = false; // 乐观
+        if (err == 0)
+        {
+            std::unique_ptr<MYSQL_RES> rst(mysql_store_result(c));
+            if (!rst) return;
+            MYSQL_ROW row = mysql_fetch_row(rst.get());
+            if(!row) return;
+            unsigned long* len = mysql_fetch_lengths(rst.get());
+            std::string charset(row[1], len[1]);
+            charset_ = charset != url_.query["charset"];
+        }
+    }
+
     void _connection_pool::sweep() {
         tm_.expires_from_now(std::chrono::seconds(60));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index 11d76f0..92c73bb 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -31,9 +31,11 @@ namespace flame::mysql
         boost::asio::steady_timer tm_;
 
         unsigned long version_;
+        boost::logic::tribool charset_;
         
         MYSQL* create();
         void release(MYSQL *c);
+        void query_charset(MYSQL* c);
     };
 
 } // namespace flame::mysql

From 464d0744661a3beb7fc778cfe93e73411075354d Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 27 Dec 2018 14:41:22 +0800
Subject: [PATCH 046/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9AMySQL=20/=20?=
 =?UTF-8?q?Redis=20=E9=BB=98=E8=AE=A4=E7=AB=AF=E5=8F=A3=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mongodb.php                | 5 ++++-
 src/mysql/_connection_pool.cpp | 1 +
 src/redis/_connection_pool.cpp | 2 +-
 src/vendor.h                   | 2 +-
 4 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/doc/mongodb.php b/doc/mongodb.php
index f2de53d..7249b58 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -35,7 +35,10 @@ class collection {
     function insert(array $data, bool $ordered = true):array {}
     function delete(array $query, int $limit = 0):array {}
     function update(array $query, array $update, $upsert = false):array {}
-    function find(array $query, array $projection = null, array $sort = null, mixed $limit):cursor {}
+    /**
+     * @param mixed $limit 可以为 int 设置 limit 值，或 array 设置 [skip, $limit] 
+     */
+    function find(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {}
     /**
      * 查询并返回单个文档
      */
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 2ae2214..99767a1 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -8,6 +8,7 @@ namespace flame::mysql
         : url_(std::move(u)), min_(2), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
         , charset_(boost::logic::indeterminate)
     {
+        if(url_.port < 10) url_.port = 3306;
         if(!url_.query.count("charset")) {
             url_.query["charset"] = "utf8";
         }
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 7bea211..5a1d7f1 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -7,7 +7,7 @@ namespace flame::redis
     _connection_pool::_connection_pool(url u)
         : url_(std::move(u)), min_(1), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     {
-        
+        if(url_.port < 10) url_.port = 6379;
     }
     _connection_pool::~_connection_pool()
     {
diff --git a/src/vendor.h b/src/vendor.h
index 06c56f7..5a30a11 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.6"
+#define EXTENSION_VERSION "0.12.7"
 
 #include <cstdio>
 #include <sys/types.h>

From c45b25dfe7749cb8a95f1a1988a379830d6ca68b Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 27 Dec 2018 15:31:16 +0800
Subject: [PATCH 047/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9AHTTP=20?=
 =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=AF=B9=E8=B1=A1=20PUT/POST=20?=
 =?UTF-8?q?=E6=8E=A5=E5=8F=A3=20body=20=E6=95=B0=E6=8D=AE=E7=B1=BB?=
 =?UTF-8?q?=E5=9E=8B=E9=99=90=E5=AE=9A=E9=94=99=E8=AF=AF=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/client.cpp            |  4 ++--
 src/mysql/_connection_base.cpp | 14 ++++++++++++--
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/http/client.cpp b/src/http/client.cpp
index b9fdf21..c017da5 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -22,12 +22,12 @@ namespace flame::http {
             })
             .method<&client::post>("post", {
                 {"url", php::TYPE::STRING},
-                {"body", php::TYPE::INTEGER},
+                {"body", php::TYPE::UNDEFINED},
                 {"timeout", php::TYPE::INTEGER, false, true}
             })
             .method<&client::put>("put", {
                 {"url", php::TYPE::STRING},
-                {"body", php::TYPE::INTEGER},
+                {"body", php::TYPE::UNDEFINED},
                 {"timeout", php::TYPE::INTEGER, false, true}
             })
             .method<&client::delete_>("delete", {
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 3ef5255..88112d0 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -73,8 +73,18 @@ namespace flame::mysql
             b.push_back(')');
             break;
         }
-        case IS_OBJECT:
-            // TODO DateTime 类型的 SQL 拼接
+        case IS_OBJECT: {
+            php::object obj = v;
+            php::string str;
+            if(obj.instanceof(php_date_get_date_ce())) {
+                // DateTime 类型的 SQL 拼接
+                str = obj.call("format", {php::string("Y-m-d H:i:s")});
+            }else{
+                str = obj.to_string();
+            }
+            escape(conn, b, str, quote);
+            break;
+        }
         default:
         {
             php::string str = v;

From abcc11316c63f99a953df098e53f2b28d581cf66 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 27 Dec 2018 22:29:28 +0800
Subject: [PATCH 048/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9AMySQL=20?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=A4=8D=E7=94=A8=E6=B8=85=E7=90=86=E5=8A=A8?=
 =?UTF-8?q?=E4=BD=9C=E5=85=BC=E5=AE=B9=E9=9D=9E=E5=AE=98=E6=96=B9=20MySQL?=
 =?UTF-8?q?=20=E5=88=86=E6=94=AF=EF=BC=9B=E4=BC=98=E5=8C=96=EF=BC=9A?=
 =?UTF-8?q?=E9=80=82=E5=BD=93=E8=B0=83=E6=95=B4=20MySQL/MongoDB=20?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=B1=A0=E5=A4=A7=E5=B0=8F=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mongodb/_connection_pool.cpp |  2 +-
 src/mysql/_connection_pool.cpp   | 14 +++++++++-----
 src/mysql/_connection_pool.h     |  2 +-
 3 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index a6a463d..9bc6358 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -13,7 +13,7 @@ namespace flame::mongodb
             mongoc_uri_set_option_as_utf8(uri.get(), MONGOC_URI_READPREFERENCE, "secondaryPreferred");
         }
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
-        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 4);
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 6);
 
         p_ = mongoc_client_pool_new(uri.get());
     }
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 99767a1..f459d97 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -5,7 +5,7 @@ namespace flame::mysql
 {
         
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(2), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
         , charset_(boost::logic::indeterminate)
     {
         if(url_.port < 10) url_.port = 3306;
@@ -58,7 +58,6 @@ namespace flame::mysql
                 ch.resume();
             }else{
                 ++size_; // 当前还存在的连接数量
-                version_ = mysql_get_server_version(c);
                 release(c);
             }
         });
@@ -93,9 +92,14 @@ namespace flame::mysql
         }
         else
         { // 立刻分配使用
-            // 每次连接复用前，需要清理状态
-            if(version_ >= 50700) mysql_reset_connection(c);
-            else mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+            // 每次连接复用前，需要清理状态;
+            // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
+            // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
+            if(boost::logic::indeterminate(reset_) || reset_) {
+                if(mysql_reset_connection(c) != 0) reset_ = false;
+            }else{
+                mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+            }
             // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
             if(boost::logic::indeterminate(charset_)) query_charset(c);
             if(charset_) mysql_set_character_set(c, url_.query["charset"].c_str());
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index 92c73bb..d92b824 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -30,7 +30,7 @@ namespace flame::mysql
         std::list<connection_t> conn_;
         boost::asio::steady_timer tm_;
 
-        unsigned long version_;
+        boost::logic::tribool reset_;
         boost::logic::tribool charset_;
         
         MYSQL* create();

From 5a961d840d9aed77b3b469c1e83b06e9a9efdb30 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 28 Dec 2018 17:16:42 +0800
Subject: [PATCH 049/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9AMySQL=20?=
 =?UTF-8?q?=E7=A9=BA=20NULL=20=E5=AD=97=E6=AE=B5=E5=AF=BC=E8=87=B4?=
 =?UTF-8?q?=E5=B4=A9=E6=BA=83=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mysql/_connection_base.cpp | 5 +++++
 src/vendor.h                   | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 88112d0..b585f4a 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -168,6 +168,11 @@ ESCAPE_FINISHED:;
         for(int i=0;i<n;++i) {
             php::string field(f[i].name, f[i].name_length);
             php::value  value;
+            if(row[i] == nullptr) {
+                value = nullptr;
+                php_row.set(field, value);
+                continue;
+            }
             switch(f[i].type) {
                 case MYSQL_TYPE_DOUBLE:
                 case MYSQL_TYPE_FLOAT:
diff --git a/src/vendor.h b/src/vendor.h
index 5a30a11..c7e6b35 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.7"
+#define EXTENSION_VERSION "0.12.8"
 
 #include <cstdio>
 #include <sys/types.h>

From fda585b2c071ea6366c834fd8b4af7ef3bb12148 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 28 Dec 2018 21:26:06 +0800
Subject: [PATCH 050/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9AHTTP=20?=
 =?UTF-8?q?=E5=86=85=E7=BD=AE=E8=BF=94=E5=9B=9E=E5=BA=8F=E5=88=97=E5=8C=96?=
 =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=8F=91=E7=94=9F=E5=BC=82=E5=B8=B8=E4=B8=8D?=
 =?UTF-8?q?=E5=BA=94=E7=9B=B4=E6=8E=A5=E5=AF=BC=E8=87=B4=E8=BF=9B=E7=A8=8B?=
 =?UTF-8?q?=E9=80=80=E5=87=BA=EF=BC=9B=E6=9B=B4=E6=96=B0=20phpext=201.1.1?=
 =?UTF-8?q?=20=3D>=201.1.2=20=E7=89=88=E6=9C=AC=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt        |  2 +-
 doc/mongodb.php       |  2 ++
 doc/mysql.php         |  3 ++
 src/flame.cpp         |  6 +++-
 src/http/_handler.cpp | 69 +++++++++++++++++++++----------------------
 src/vendor.h          |  2 +-
 6 files changed, 46 insertions(+), 38 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0ac7b06..0dafca0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,7 +23,7 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
     OUTPUT_STRIP_TRAILING_WHITESPACE)
-set(VENDOR_PHPEXT /data/vendor/phpext-1.1.1)
+set(VENDOR_PHPEXT /data/vendor/phpext-1.1.2)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
 set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 7249b58..3bfea2c 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -3,6 +3,8 @@
  * MongoDB 基本客户端
  * 注意:
  * 1. 同一协程中连续进行 MongoDB 查询操作, 且游标不读取数据不销毁 (或 不读取完不销毁) 可能导致进程死锁; (请将游标读取完毕 或 主动 unset 释放游标对象)
+ * 2. 对应 MongoDB ObjectID 映射类型 class flame\mongodb\object_id;
+ * 3. 对应 MongoDB DateTime 映射类型 class flame\mongodb\date_time;
  */
 namespace flame\mongodb;
 
diff --git a/doc/mysql.php b/doc/mysql.php
index 4f58592..a7f3b5e 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -3,6 +3,9 @@
  * MySQL 客户端
  * 注意:
  * 1. 同一协程中连续进行 MySQL 查询操作, 且结果集数据不读取且不销毁(或不读取完且不销毁)可能导致进程死锁; (请将结果集读取完 或 主动 unset 释放结果集对象)
+ * 2. MySQL 读取数据时数值型字段读取映射为 PHP 对应数值类型；`DATETIME` 映射为 PHP 内置类型 `DateTime`;
+ * 3. 客户端提供的简化方法如 `insert()` `update()` 也同时支持与上述反向的映射;
+ * 
  */
 namespace flame\mysql;
 /**
diff --git a/src/flame.cpp b/src/flame.cpp
index 47e81ad..566561b 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -98,6 +98,9 @@ namespace flame
     }
 }
 
+#define VERSION_MACRO(major, minor, patch) VERSION_JOIN(major, minor, patch)
+#define VERSION_JOIN(major, minor, patch) #major"."#minor"."#patch
+
 extern "C"
 {
     ZEND_DLEXPORT zend_module_entry *get_module()
@@ -117,8 +120,9 @@ extern "C"
         ext
             .desc({"vendor/boost", BOOST_LIB_VERSION})
             .desc({"vendor/libphpext", PHPEXT_LIB_VERSION})
+            .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
+            .desc({"vendor/mysqlc", mysql_get_client_info()})
             .desc({"vendor/amqpcpp", "4.0.1"})
-            .desc({"vendor/mysqlc", PACKAGE_VERSION})
             .desc({"vendor/librdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S});
 
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 1a8a308..114fa03 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -110,6 +110,40 @@ namespace flame::http
                 {
                     rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
                 }
+HANDLE_BREAK:
+                if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/)
+                {
+                    // if(!(res_ptr->status_ & server_response::STATUS_HEAD_SENT)) {
+                    //     res_ptr->set("status", 404);
+                    //     res_ptr->set("body", nullptr);
+                    // }
+                    if(gcontroller->status & controller::controller_status::STATUS_SHUTDOWN) {
+                        res_->keep_alive(false);
+                    }
+                    res_ptr->build_ex(*res_);
+                    // Content 方式, 待结束
+                    php::string body = res.get("body");
+                    if (!body.empty())
+                    {
+                        body = ctype_encode(res_->find(boost::beast::http::field::content_type)->value(), body);
+                    }
+                    res_->body() = body;
+                    res_->prepare_payload();
+
+                    boost::system::error_code error;
+                    boost::beast::http::async_write(socket_, *res_, ch[error]);
+                    if(error) {
+                        res_.reset();
+                        req_.reset();
+                        throw php::exception(zend_ce_exception,
+                                            (boost::format("failed to write_head: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+                    }else{
+                        start();
+                    }
+                }/*else
+                {
+                    // Chunked 未结束 -> server_response::__destruct() -> finish()
+                }*/
             }
             catch(const php::exception& ex)
             {
@@ -118,42 +152,7 @@ namespace flame::http
                 php::object obj = ex;
                 std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
                 // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
-                return nullptr;
             }
-HANDLE_BREAK:
-            if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/)
-            {
-                // if(!(res_ptr->status_ & server_response::STATUS_HEAD_SENT)) {
-                //     res_ptr->set("status", 404);
-                //     res_ptr->set("body", nullptr);
-                // }
-                if(gcontroller->status & controller::controller_status::STATUS_SHUTDOWN) {
-                    res_->keep_alive(false);
-                }
-                res_ptr->build_ex(*res_);
-                // Content 方式, 待结束
-                php::string body = res.get("body");
-                if (!body.empty())
-                {
-                    body = ctype_encode(res_->find(boost::beast::http::field::content_type)->value(), body);
-                }
-                res_->body() = body;
-                res_->prepare_payload();
-
-                boost::system::error_code error;
-                boost::beast::http::async_write(socket_, *res_, ch[error]);
-                if(error) {
-                    res_.reset();
-                    req_.reset();
-                    throw php::exception(zend_ce_exception,
-                                         (boost::format("failed to write_head: (%1%) %2%") % error.value() % error.message()).str(), error.value());
-                }else{
-                    start();
-                }
-            }/*else
-            {
-                // Chunked 未结束 -> server_response::__destruct() -> finish()
-            }*/
             return nullptr;
         }));
     }
diff --git a/src/vendor.h b/src/vendor.h
index c7e6b35..119ed01 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.8"
+#define EXTENSION_VERSION "0.12.9"
 
 #include <cstdio>
 #include <sys/types.h>

From 77f474cb81179a616cc089f5665a86d9f3c5d743 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 2 Jan 2019 21:10:47 +0800
Subject: [PATCH 051/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E8=BF=87?=
 =?UTF-8?q?=E6=97=A9=E5=88=A4=E5=AE=9A=20flame\run()=20=E7=BC=BA=E5=A4=B1?=
 =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E6=AD=A3=EF=BC=9A=E9=98=B2=E6=AD=A2=E5=BC=82?=
 =?UTF-8?q?=E6=AD=A5=E5=83=B5=E5=B0=B8=E8=BF=9B=E7=A8=8B=EF=BC=9B=E4=BC=98?=
 =?UTF-8?q?=E5=8C=96=EF=BC=9A=E7=BC=BA=E5=B0=91=E9=BB=98=E8=AE=A4=20charse?=
 =?UTF-8?q?t=20=E5=AF=BC=E8=87=B4=E5=A4=9A=E4=BD=99=E7=9A=84=E5=AD=97?=
 =?UTF-8?q?=E7=AC=A6=E9=9B=86=E8=AE=BE=E7=BD=AE=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php                   |  2 +-
 doc/os.php                     |  3 +++
 src/controller.cpp             |  1 +
 src/controller.h               |  9 +++++----
 src/core.cpp                   |  9 +++++++++
 src/mysql/_connection_pool.cpp | 11 +++++++----
 src/os/process.cpp             | 27 +++++++++++++++++++--------
 src/os/process.h               |  5 ++++-
 src/vendor.h                   |  1 +
 test/os_1.php                  | 18 +++++++++++++++++-
 10 files changed, 67 insertions(+), 19 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index db4f54b..eb19957 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -30,7 +30,7 @@ function run() {}
  * 从若干个队列中选择(等待)一个有数据队列
  * @return 若所有通道已关闭, 返回 null; 否则返回一个有数据的通道, 即: 可以无等待 pop()
  */
-function select(channel $q1, $q2/*, ...*/):channel {}
+function select(queue $q1, $q2/*, ...*/):queue {}
 /**
  * 监听框架的通知
  * @param string $event 目前消息存在以下两种:
diff --git a/doc/os.php b/doc/os.php
index 16bff6a..77c618c 100644
--- a/doc/os.php
+++ b/doc/os.php
@@ -42,6 +42,9 @@ function interfaces():array {}
 function spawn(string $command, array $argv = [], array $options = []):process {}
 /**
  * 调用上述 spawn() 异步启动进程, 并等待其结束, 返回进程标准输出
+ * @param array $options 目前可用的选项如下：
+ *  * "cwd" - string - 工作路径;
+ *  * "env" - array - 环境变量，K/V 结构文本;
  * @return string 进程标准输出内容
  */
 function exec(string $command, array $argv, array $options = []):string {}
diff --git a/src/controller.cpp b/src/controller.cpp
index 0a46473..7ddc8f1 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -68,6 +68,7 @@ namespace flame {
 	}
 
 	void controller::run() {
+		status &= controller_status::STATUS_RUN;
 		if(type == process_type::WORKER)
 		{
 			worker_->run();
diff --git a/src/controller.h b/src/controller.h
index fd688b0..ea20ab7 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -18,10 +18,11 @@ namespace flame {
 		boost::process::environment env;
 		enum controller_status
 		{
-			STATUS_UNKNOWN = 0,
-			STATUS_INITIALIZED = 1,
-			STATUS_SHUTDOWN = 2,
-			STATUS_EXCEPTION = 4,
+			STATUS_UNKNOWN     = 0x00,
+			STATUS_INITIALIZED = 0x01,
+			STATUS_SHUTDOWN    = 0x02,
+			STATUS_EXCEPTION   = 0x04,
+			STATUS_RUN         = 0x08,
 		};
 		int status;
 		std::size_t    worker_size;
diff --git a/src/core.cpp b/src/core.cpp
index 2e53e79..4f445bf 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -30,6 +30,15 @@ namespace flame::core {
     }
     void declare(php::extension_entry &ext) {
         ext
+            .on_request_shutdown([] (php::extension_entry& ext) -> bool
+            {
+                if(coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0)
+                {
+                    std::cerr << "[FATAL] process exited prematurely: missing 'flame\\run();' ?\n";
+                    exit(-1);
+                }
+                return true;
+            })
             .function<select>("flame\\select")
             .function<co_id>("flame\\co_id");
 
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index f459d97..a51d4d3 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -73,8 +73,8 @@ namespace flame::mysql
     MYSQL* _connection_pool::create()
     {
         MYSQL* c = mysql_init(nullptr);
-        // 这里的 CHARSET 设定会被 reset_connection 重置同时不会影响 client_charactor_set_name 变量
-        // mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
+        // 这里的 CHARSET 设定会被 reset_connection 重置为系统值
+        mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
         unsigned int timeout = 5; // 连接超时
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
         if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0))
@@ -95,8 +95,11 @@ namespace flame::mysql
             // 每次连接复用前，需要清理状态;
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
-            if(boost::logic::indeterminate(reset_) || reset_) {
-                if(mysql_reset_connection(c) != 0) reset_ = false;
+            if(boost::logic::indeterminate(reset_)) {
+                if(mysql_reset_connection(c)) reset_ = true;
+                else reset_ = false;
+            }else if(reset_) {
+                mysql_reset_connection(c);
             }else{
                 mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
             }
diff --git a/src/os/process.cpp b/src/os/process.cpp
index a5f75e3..b6e7584 100644
--- a/src/os/process.cpp
+++ b/src/os/process.cpp
@@ -16,10 +16,12 @@ namespace flame::os
         class_process
             .property({"pid", 0})
             .method<&process::__construct>("__construct", {}, php::PRIVATE)
+            .method<&process::__destruct>("__destruct")
             .method<&process::kill>("kill",
             {
                 {"signal", php::TYPE::INTEGER, false, true}
             })
+            .method<&process::detach>("detach")
             .method<&process::wait>("wait")
             .method<&process::stdout>("stdout")
             .method<&process::stderr>("stderr");
@@ -29,23 +31,32 @@ namespace flame::os
     {
         return nullptr;
     }
-    php::value process::kill(php::parameters &params)
+    php::value process::__destruct(php::parameters& params)
     {
-        if (params.size() > 0)
-        {
-            ::kill(get("pid"), params[0].to_integer());
-        }
-        else
-        {
-            ::kill(get("pid"), SIGTERM);
+        if(!detach_) {
+            if(!c_.wait_for(std::chrono::milliseconds(10000))) c_.terminate();
+            if(c_.joinable()) c_.join();
         }
         return nullptr;
     }
+    php::value process::kill(php::parameters &params)
+    {
+        if (params.size() > 0) ::kill(get("pid"), params[0].to_integer());
+        else ::kill(get("pid"), SIGTERM);
+        return nullptr;
+    }
+    php::value process::detach(php::parameters& params) 
+    {
+        detach_ = true;
+        c_.detach();
+        return nullptr;
+    }
     php::value process::wait(php::parameters &params)
     {
         if (!exit_) {
             ch_.reset(coroutine::current);
             ch_.suspend();
+            c_.join();
         }
         return nullptr;
     }
diff --git a/src/os/process.h b/src/os/process.h
index a34e1bf..e24920f 100644
--- a/src/os/process.h
+++ b/src/os/process.h
@@ -7,8 +7,10 @@ namespace flame::os {
 	public:
 		static void declare(php::extension_entry& ext);
 		php::value __construct(php::parameters& params);
+		php::value __destruct(php::parameters& params);
 		php::value kill(php::parameters& params);
 		php::value wait(php::parameters& params);
+		php::value detach(php::parameters& params);
 		php::value stdout(php::parameters& params);
 		php::value stderr(php::parameters& params);
 	private:
@@ -16,7 +18,8 @@ namespace flame::os {
         coroutine_handler         ch_;
 		std::future<std::string> out_;
 		std::future<std::string> err_;
-		bool exit_;
+		bool exit_ = false;
+		bool detach_ = false;
 		friend class php::value spawn(php::parameters& params);
 		friend class php::value exec(php::parameters& params);
 	};
diff --git a/src/vendor.h b/src/vendor.h
index 119ed01..6fe4df9 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -34,6 +34,7 @@
 #include <boost/process.hpp>
 #include <boost/process/async.hpp>
 #include <boost/logic/tribool.hpp>
+#include <boost/logic/tribool_io.hpp>
 #include <boost/asio.hpp>
 #include <boost/asio/ssl.hpp>
 #include <boost/asio/coroutine.hpp>
diff --git a/test/os_1.php b/test/os_1.php
index 855f941..e861013 100644
--- a/test/os_1.php
+++ b/test/os_1.php
@@ -8,6 +8,22 @@
 	]);
 	$proc->wait();
 	var_dump( $proc->stdout() );
-	var_dump( flame\os\exec("ping", ["-c", 3, "www.baidu.com"]) );
+
+	$proc = flame\os\spawn("ping", ["-c", 5, "www.baidu.com"], [
+		"cwd" => "/data/htdocs",
+	]);
+	flame\time\sleep(1000);
+	$proc->wait();
+	
+	
+	for($i = 0; $i<10; ++$i) {
+		flame\go(function() {
+			for($j=0;$j<100;++$j) {
+				flame\os\exec("ping", ["-c", 1, "www.baidu.com"]);
+			}
+		});
+	}
+	flame\time\sleep(60000);
+	echo "done.\n";
 });
 flame\run();

From a3444f63cf04550d25e92b0704478ab6177d0ba9 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 4 Jan 2019 12:49:38 +0800
Subject: [PATCH 052/146] =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=B1=A0=E4=BD=8E?=
 =?UTF-8?q?=E6=B0=B4=E4=BD=8D=E8=B0=83=E6=95=B4=EF=BC=9B=E6=96=87=E6=A1=A3?=
 =?UTF-8?q?=E5=AE=8C=E5=96=84=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                      | 16 ++++++
 doc/core.php                   | 18 +++++--
 doc/http.php                   | 23 ++++++---
 doc/kafka.php                  | 15 ++++--
 doc/log.php                    |  1 +
 doc/mongodb.php                | 90 ++++++++++++++++++++++++++++++++--
 doc/mysql.php                  | 11 +++--
 doc/os.php                     | 31 ++++++++++--
 doc/rabbitmq.php               | 30 ++++++++++--
 doc/redis.php                  |  8 +--
 doc/tcp.php                    | 11 +++++
 doc/time.php                   | 22 ++++++---
 src/core.cpp                   |  2 +-
 src/kafka/kafka.cpp            |  7 ++-
 src/log/log.cpp                |  2 +-
 src/mysql/_connection_pool.cpp |  2 +-
 src/redis/_connection_pool.cpp |  2 +-
 test/coroutine_1.php           | 21 ++++++--
 test/http_2.php                | 25 +++++++---
 19 files changed, 279 insertions(+), 58 deletions(-)

diff --git a/README.md b/README.md
index 9276f71..2ee6224 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,19 @@
+### 功能项
+
+* **core** - 核心，框架初始化设置，协程启动，协程队列，协程锁等；
+* **time** - 时间相关，协程休眠，当前时间等；
+* **log** - 简单日志记录功能，多进程支持，支持日志登记过滤设置；
+* **os** - 操作系统相关信息获取，异步进程启停操作等；
+* **tcp** - 封装 TCP 相关服务端客户端功能；
+* **http** - 简单的 HTTP 客户端，支持长连 Keep-Alive 及相关连接数控制；HTTP 服务端，简单 PATH 处理器；（暂不支持 HTTPS 相关）
+* **mysql** - 简单 MySQL 客户端（连接池），提供部分简化方法，如 `insert/delete/one` 等；
+* **redis** - 简单 Redis 客户端（连接池）；
+* **mongodb** - 简单 MongoDB 客户端（连接池），提供部分简化方法，如 `insert/delete/one/count` 等；
+* **rabbitmq** - 简单 RabbitMQ 生产消费支持；
+* **kafka** - 简单 Kafka 生产消费支持（仅支持 Kafka 0.10+ 群组消费）；
+
+请参看 `/doc` 目录下相关 PHP 文件的文档注释，或将该目录挂接再 IDE 内提供自动完成；
+
 ### 依赖库
 
 #### PHP
diff --git a/doc/core.php b/doc/core.php
index eb19957..ae49930 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -7,15 +7,18 @@
  * 初始化框架, 设置进程名称及相关配置;
  * @param string $process_name 进程名称
  * @param array  $options 选项配置, 目前可用如下:
- *  * `logger` - 日志输出重定向目标文件(完整路径, 若不提供使用标准输出); 
+ *  * "logger" - 日志输出重定向目标文件(完整路径, 若不提供使用标准输出); 
  *      向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
- *  * `level` - 日志输出级别, 设置该级别下的日志将不被记录; 可用级别如下
+ *  * "level" - 日志输出级别, 设置该级别下的日志将不被记录; 可用级别如下
  *      "debug"
  *      "info"
  *      "warning"
  *      "error"
  *      "fatal"
- *  请参见 flame\log 命名空间;
+ *  @see flame\log
+ * 
+ * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式
+ * 主进程将自动进行日志文件写入，子进程自动拉起；
  */
 function init($process_name, $options = []) {}
 /**
@@ -24,6 +27,7 @@ function init($process_name, $options = []) {}
 function go(callable $cb) {}
 /**
  * 框架调度, 上述协程会在框架开始调度运行后启动
+ * 注意：协程异步调度需要 run() 才能启动执行；
  */
 function run() {}
 /**
@@ -65,7 +69,9 @@ function pop() {}
      */
     function close() {}
 }
-
+/**
+ * 协程互斥量（锁）
+ */
 class mutex {
     /**
      * 构建一个协程式 mutex 对象
@@ -80,7 +86,9 @@ function lock() {}
      */
     function unlock() {}
 }
-
+/**
+ * 协程锁（使用上述 mutex 构建自动加锁解锁流程）
+ */
 class guard {
     /**
      * 构建守护并锁定 mutex 
diff --git a/doc/http.php b/doc/http.php
index 5494ece..7db1514 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -1,5 +1,8 @@
 <?php
-
+/**
+ * 提供基本的 HTTP 客户端、服务端封装；
+ * 注意：暂不支持作为 HTTPS 客户端、服务端；
+ */
 namespace flame\http;
 
 /**
@@ -24,7 +27,7 @@ function put(string $url, mixed $data, int $timeout = 3000): client_response {}
 function delete(string $url, int $timeout = 3000): client_response {}
 
 /**
- * 客户端
+ * 客户端，用于执行请求
  */
 class client {
     /**
@@ -55,7 +58,7 @@ function put(string $url, mixed $data, int $timeout = 3000): client_response {}
     function delete(string $url, int $timeout = 3000): client_response {}
 }
 /**
- * 作为客户端的请求对象
+ * 客户端请求对象，用于拼装生成一个客户端请求（待执行）
  */
 class client_request {
     /**
@@ -87,7 +90,9 @@ class client_request {
      */
     function __construct(string $url, mixed $body = null, int $timeout = 3000) {}
 }
-
+/**
+ * 客户端响应对象，执行请求后得到
+ */
 class client_response {
     /**
      * 响应码
@@ -113,7 +118,9 @@ class client_response {
      */
     public $rawBody;
 }
-
+/**
+ * HTTP 服务器
+ */
 class server {
     /**
      * 服务端本地监听地址
@@ -184,7 +191,9 @@ function run() {}
      */
     function close() {}
 }
-
+/**
+ * 服务端请求对象（由 server 对应回调获得）
+ */
 class server_request {
     /**
      * 请求方法
@@ -253,7 +262,7 @@ class server_request {
 }
 
 /**
- * 服务端响应对象, 提供接口向客户端返回数据
+ * 服务端响应对象（由 server 对应处理回调获得）提供接口向客户端返回数据
  */
 class server_response {
     /**
diff --git a/doc/kafka.php b/doc/kafka.php
index 66e3e63..678c57f 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -1,4 +1,7 @@
 <?php
+/**
+ * 提供 Kafka 基本生产消费功能封装
+ */
 namespace flame\kafka;
 
 /**
@@ -22,7 +25,9 @@ function consume(array $config, array $topics) {}
  * @param array $topics 待生产的目标 TOPIC 名称列表
  */
 function produce(array $config, array $topics) {}
-
+/**
+ * 消费者
+ */
 class consumer {
     /**
      * 执行消费, 实际的回调函数会被对应的协程回调执行
@@ -41,7 +46,9 @@ function commit(message $msg) {}
      */
     function close() {}
 }
-
+/**
+ * 生产者
+ */
 class producer {
     /**
      * 生产消息
@@ -62,7 +69,9 @@ function publish(string $topic, mixed $message, string $key = null, array $heade
      */
     function flush() {}
 }
-
+/**
+ * 消息对象
+ */
 class message implements \JsonSerializable {
     /**
      * @property string $topic
diff --git a/doc/log.php b/doc/log.php
index f43e4ee..b2b2dbb 100644
--- a/doc/log.php
+++ b/doc/log.php
@@ -2,6 +2,7 @@
 
 /**
  * 日志模块, 可通过框架配置设置日志等级以过滤日志输出;
+ * @see flame\init()
  */
 namespace flame\log;
 /**
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 3bfea2c..46a498a 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * MongoDB 基本客户端
+ * 提供基本的 MongoDB 基本客户端封装
  * 注意:
  * 1. 同一协程中连续进行 MongoDB 查询操作, 且游标不读取数据不销毁 (或 不读取完不销毁) 可能导致进程死锁; (请将游标读取完毕 或 主动 unset 释放游标对象)
  * 2. 对应 MongoDB ObjectID 映射类型 class flame\mongodb\object_id;
@@ -14,16 +14,22 @@
  *  mongodb://{username}:{password}@{host1}:{port1},{host2}:{port2}/{database}?{option1}={rs1}&{option2}=...
  */
 function connect(string $url):client {}
-
+/**
+ * MongoDB 客户端（内部使用连接池）
+ */
 class client {
     /**
      * 执行以 $command 描述的命令;
+     * 请参考：https://docs.mongodb.com/master/reference/command/ 编写指令；
      * 注意: 更新型指令需要自行指定 $write = true
      */
     function execute(array $command, bool $write = false):mixed {}
-    function collection():collection {}
     /**
-     * 与 collection() 相同, 返回指定的 collection 对象
+     * 获取指定名称的 colleciton 对象
+     */
+    function collection($name):collection {}
+    /**
+     * 与 `collection()` 相同, 返回指定的 collection 对象
      */
     function __get($name):collection {}
     /**
@@ -34,24 +40,72 @@ function __isset($name):bool {
     }
 }
 class collection {
+    /**
+     * 执行插入
+     * @param array $data 一维关联数组插入单个文档; 二维时插入多个；
+     * @param bool $ordered 插入多个文档时，是否顺序执行（在遇到的第一个插入错误出停止）
+     * @return array 返回插入结果，形如：
+     *  array(
+     *      "n" => 1,  // 插入数量
+     *      "ok" => 1, // 插入结果
+     *      ...
+     *  )
+     */
     function insert(array $data, bool $ordered = true):array {}
+    /**
+     * 执行删除
+     * @return array 形式同 insert() 类似：
+     *  array(
+     *      "n" => 1,  // 删除数量
+     *      "ok" => 1, // 删除结果
+     *      ...
+     *  )
+     */
     function delete(array $query, int $limit = 0):array {}
+    /**
+     * 执行更新动作
+     * @param array $update 一般使用类似如下形式进行更新设置：
+     *  ['$set'=>['a'=>'b'],'$inc'=>['c'=>2]]
+     * @return array 形式同 insert() 类似：
+     *  array(
+     *      "n" => 1,  // 更新数量
+     *      "ok" => 1, // 更新结果
+     *      ...
+     *  )
+     */
     function update(array $query, array $update, $upsert = false):array {}
     /**
-     * @param mixed $limit 可以为 int 设置 limit 值，或 array 设置 [skip, $limit] 
+     * 执行指定查询，返回游标对象
+     * @param array $sort 一般使用如下形式表达排序：
+     *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
+     * @param mixed $limit 可以为 int 设置 `limit` 值，或 array 设置 `[$skip, $limit]` 
      */
     function find(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {}
     /**
      * 查询并返回单个文档
+     * @param array $sort 一般使用如下形式表达排序：
+     *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
      */
     function one(array $query, array $sort = null): array {}
     /**
      * 查询并返回单个文档的指定字段值
+     * @param array $sort 一般使用如下形式表达排序：
+     *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
      */
     function get(array $query, string $field, array $sort = null): mixed {}
+    /**
+     * 查询并返回匹配文档数量
+     */
     function count(array $query): int {}
+    /**
+     * 执行指定聚合操作，返回游标对象；
+     * 请参考 https://docs.mongodb.com/master/reference/operator/aggregation-pipeline/ 编写；
+     */
     function aggregate(array $pipeline): cursor {}
 }
+/**
+ * 游标对象
+ */
 class cursor {
     /**
      * 读取当前 cursor 返回一个文档; 若读取已完成, 返回 null;
@@ -62,21 +116,47 @@ function fetch_row():mixed {}
      */
     function fetch_all(): array {}
 }
+/**
+ * 对应 MongoDB 日期时间字段，毫秒级精度
+ */
 class date_time implements \JsonSerializable {
+    /**
+     * 构建日期时间对象
+     * @param int $milliseconds 指定毫秒级时间，默认 null 使用当前时间
+     */
     function __construct(int $milliseconds = null) {}
     function __toString() {}
     function __toDateTime(): \DateTime {}
     function __debugInfo() {}
     function jsonSerialize(): array {}
+    /**
+     * 返回秒级时间戳
+     */
     function unix(): int {}
+    /**
+     * 返回毫秒级时间戳
+     */
     function unix_ms(): int {}
 }
+/**
+ * 对应 MongoDB 对象ID
+ */
 class object_id implements \JsonSerializable {
+    /**
+     * 构建
+     * @param string $object_id 从文本形式构建（恢复）一个对象，默认 null，构建一个新的对象
+     */
     function __construct(string $object_id = null) {}
     function __toString() {}
     function __toDateTime(): \DateTime {}
     function __debugInfo() {}
     function jsonSerialize(): array {}
+    /**
+     * 秒级时间戳
+     */
     function unix(): int {}
+    /**
+     * 当前对象与另一 object_id 对象是否**值相等**
+     */
     function equal(object_id $oid): bool {}
 }
diff --git a/doc/mysql.php b/doc/mysql.php
index a7f3b5e..17fee10 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -1,15 +1,14 @@
 <?php
 /**
- * MySQL 客户端
+ * 提供基本的 MySQL 客户端封装，内部使用连接池
  * 注意:
  * 1. 同一协程中连续进行 MySQL 查询操作, 且结果集数据不读取且不销毁(或不读取完且不销毁)可能导致进程死锁; (请将结果集读取完 或 主动 unset 释放结果集对象)
  * 2. MySQL 读取数据时数值型字段读取映射为 PHP 对应数值类型；`DATETIME` 映射为 PHP 内置类型 `DateTime`;
  * 3. 客户端提供的简化方法如 `insert()` `update()` 也同时支持与上述反向的映射;
- * 
  */
 namespace flame\mysql;
 /**
- * 连接 MySQL 服务器
+ * 建立（连接）到 MySQL 服务器的客户端对象
  * @param string $url 服务端地址, 形如:
  *  mysql://{user}:{pass}@{host}:{port}/{database}?opt1=val1
  *  目前可用的选项如下:
@@ -80,7 +79,7 @@ function connect($url): client {}
 
 
 /**
- * MySQL 客户端
+ * MySQL 客户端（内部使用连接池）
  */
 class client {
     /**
@@ -184,7 +183,9 @@ function one(string $table, array $where, mixed $order = null): array {}
      */
     function get(string $table, string $field, array $where, mixed $order = null): mixed {}
 };
-
+/**
+ * 结果集
+ */
 class result {
     /**
      * @property 结果集包含的记录行数
diff --git a/doc/os.php b/doc/os.php
index 77c618c..73b2a6c 100644
--- a/doc/os.php
+++ b/doc/os.php
@@ -1,15 +1,31 @@
 <?php
-
+/**
+ * 提供操作系统相关信息读取、进程操作等功能封装
+ */
 namespace flame\os;
-
+/**
+ * 信号
+ */
 const SIGTERM = 15;
+/**
+ * 信号
+ */
 const SIGKILL = 9;
+/**
+ * 信号
+ */
 const SIGINT  = 2;
+/**
+ * 信号
+ */
 const SIGUSR1 = 10;
+/**
+ * 信号
+ */
 const SIGUSR2 = 12;
 
 /**
- * 获取当前系统网卡绑定地址
+ * 获取当前系统网卡绑定地址等信息
  * @example
  *  array(2) {
  *      ["lo"]=>
@@ -53,11 +69,20 @@ function exec(string $command, array $argv, array $options = []):string {}
  * 进程对象
  */
 class process {
+    /**
+     * 向进程发送指定信号
+     */
     function kill(int $signal = SIGTERM) {}
     /**
      * 等待进程结束
      */
     function wait() {}
+    /**
+     * 获取进程标准输出（若进程还未结束需要等待）
+     */
     function stdout():string {}
+    /**
+     * 获取进程错误输出（若进程还未结束需要等待）
+     */
     function stderr():string {}
 }
\ No newline at end of file
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
index 185da44..1d2d988 100644
--- a/doc/rabbitmq.php
+++ b/doc/rabbitmq.php
@@ -1,11 +1,15 @@
 <?php
-
+/**
+ * 提供简单的 RabbitMQ 生产消费封装
+ */
 namespace flame\rabbitmq;
 
 /**
  * 通过 RabbitMQ 连接地址连接并准备消费
  * @param string $url 连接地址, 形如:
- *  amqp://{username}:{password}@{host}:{port}/{vhost}
+ *  amqp://{username}:{password}@{host}:{port}/{vhost}?key1=val1
+ * 可使用参数:
+ *  * `prefetch` - int - 用于指定未 ack 时最大读取数量
  */
 function consume(string $url, string $queue): consumer {}
 /**
@@ -29,6 +33,7 @@ function run(callable $cb) {}
     function confirm(message $msg) {}
     /**
      * 拒绝消息(可选的返回队列, 重复消费)
+     * @param bool $requeue 若为真值，将被拒绝的消息放回队列重新等待消费；
      */
     function reject(message $msg, bool $requeue = false) {}
     /**
@@ -37,15 +42,28 @@ function reject(message $msg, bool $requeue = false) {}
      */
     function close() {}
 }
+/**
+ * 消费者
+ */
 class producer {
     /**
+     * 通过指定 exchange 生产（发送）一条消息
      * @param mixed $message 为 string 时表示生产的消息的包体; 也可以为 message 类型的对象;
      * @param string $routing_key 路由KEY, 可以覆盖 $message 对象内部的 routing_key 属性
      */
     function publish(string $exchange, mixed $message, string $routing_key = null) {}
 }
+/**
+ * 消息对象
+ */
 class message implements JsonSerializable {
+    /**
+     * 路由键值
+     */
     public $routing_key;
+    /**
+     * 消息体
+     */
     public $body;
     public $expiration;
     public $reply_to;
@@ -62,15 +80,21 @@ class message implements JsonSerializable {
     public $app_id;
     public $user_id;
     public $type_name;
+    /**
+     * 消息时间戳，一般为秒级（由于可用户自定指定，含义可能发生变异）
+     */
     public $timestamp;
     public $message_id;
     /**
      * 构建一条新的 message 消息
      */
     function __construct($body = "", $routing_key = "") {}
+    /**
+     * 返回消息体
+     */
     function __toString() {}
     /**
-     * 自定义 JSON 序列化, 返回 routing_key / body / timestamp 字段;
+     * 自定义 JSON 序列化, 返回包含 routing_key / body / timestamp 字段的数组;
      */
     function jsonSerialize() {}
 }
diff --git a/doc/redis.php b/doc/redis.php
index ecd4fe6..a28270b 100644
--- a/doc/redis.php
+++ b/doc/redis.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * MongoDB 基本客户端
+ * 提供 Redis 基本客户端封装
  */
 namespace flame\redis;
 
@@ -10,7 +10,8 @@ function connect(string $url):client {}
  */
 class client {
     /**
-     * 用于通用的实现可用 Redis 命令
+     * 用于通用的实现可用 Redis 命令，支持 REDIS 大部分指令
+     * @return mixed 可能返回 int/string/array/null 等类型
      */
     function __call($name, $argv) {}
     /**
@@ -60,7 +61,7 @@ function psubscribe() {}
     function punsubscribe() {}
 }
 /**
- * 
+ * 用于记录待执行的命令并批量执行
  */
 class tx {
     /**
@@ -73,6 +74,7 @@ function __call($name, $argv): tx {
     }
     /**
      * 执行目前提交的命令并带回所有响应返回
+     * @return array 返回数据项与命令一一对应
      */
     function exec():array {}
 }
\ No newline at end of file
diff --git a/doc/tcp.php b/doc/tcp.php
index fdea99e..6b94460 100644
--- a/doc/tcp.php
+++ b/doc/tcp.php
@@ -1,4 +1,7 @@
 <?php
+/**
+ * 提供对 TCP 网络操作的相关封装
+ */
 namespace flame\tcp;
 
 /**
@@ -8,7 +11,15 @@
 function connect(string $address):socket {}
 
 class socket {
+    /**
+     * 本地地址（含端口）
+     * @property string
+     */
     public $local_address;
+    /**
+     * 远端地址（含端口）
+     * @property string
+     */
     public $remote_address;
     /**
      * 读取一定量的数据, 实际读取方式根据 $completion 参数决定;
diff --git a/doc/time.php b/doc/time.php
index 02c5765..2da1b17 100644
--- a/doc/time.php
+++ b/doc/time.php
@@ -1,11 +1,21 @@
 <?php
-
+/**
+ * 时间相关函数
+ */
 namespace flame\time;
 
-// 当前协程休眠 $ms 毫秒
+/**
+ * 当前协程休眠 $ms 毫秒
+ */
 function sleep(int $ms) {}
-// 获取毫秒时间戳
-function now() {}
-// 获取标准时间 YYYY-mm-dd HH:ii:ss
-function iso() {}
+/**
+ * 获取毫秒时间戳
+ * (此时间戳采用框架缓存机制，较默认获取系统时间函数消耗较小)
+ */
+function now():int {}
+/**
+ * 获取标准时间 "YYYY-mm-dd HH:ii:ss"
+ * (格式话输出上述 `now()` 函数输出)
+ */
+function iso():string {}
 
diff --git a/src/core.cpp b/src/core.cpp
index 4f445bf..c846960 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -34,7 +34,7 @@ namespace flame::core {
             {
                 if(coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0)
                 {
-                    std::cerr << "[FATAL] process exited prematurely: missing 'flame\\run();' ?\n";
+                    std::cerr << "[FATAL] process exited prematurely (missing 'flame\\run();'?)\n";
                     exit(-1);
                 }
                 return true;
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index aeca1af..171c486 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -34,6 +34,9 @@ namespace flame::kafka
             ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 8), 256);
             config.erase("concurrent");
         }
+        if (!config.exists("log.connection.close")) {
+            config.set("log.connection.close", "false");
+        }
         ptr->cs_.reset(new _consumer(config, topics));
         coroutine_handler ch {coroutine::current};
         // 订阅
@@ -52,8 +55,10 @@ namespace flame::kafka
             // ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 8), 256);
             config.erase("concurrent");
         }
+        if (!config.exists("log.connection.close")) {
+            config.set("log.connection.close", "false");
+        }
         ptr->pd_.reset(new _producer(config, topics));
-
         // TODO 优化: 确认首次连接已建立
         return std::move(obj);
     }
diff --git a/src/log/log.cpp b/src/log/log.cpp
index 3bb8823..67a8257 100644
--- a/src/log/log.cpp
+++ b/src/log/log.cpp
@@ -30,7 +30,7 @@ namespace flame::log
         std::ostream& os = lv > LEVEL_WARNING ? std::cout : std::cerr;
         os << '[' << time::iso() << "] (";
         os << LEVEL_S[lv];
-        os << ") ";
+        os << ")";
         for (int i = 0; i < params.size(); ++i)
         {
             os << ' ' << params[i].ptr();
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index a51d4d3..e9e8165 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -5,7 +5,7 @@ namespace flame::mysql
 {
         
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(3), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
         , charset_(boost::logic::indeterminate)
     {
         if(url_.port < 10) url_.port = 3306;
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 5a1d7f1..13e2c2f 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -5,7 +5,7 @@ namespace flame::redis
 {
 
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(1), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(3), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     {
         if(url_.port < 10) url_.port = 6379;
     }
diff --git a/test/coroutine_1.php b/test/coroutine_1.php
index 6fafe93..2c8d4e2 100644
--- a/test/coroutine_1.php
+++ b/test/coroutine_1.php
@@ -2,11 +2,22 @@
 flame\init("coroutine_1");
 
 flame\go(function() {
-    // flame\go(function() {
-    //     flame\time\sleep(100);
-    //     echo "done.\n";
-    // });
-    echo "done.\n";
+    flame\go(function() {
+        for($i=0;$i<1000;++$i) {
+            flame\time\sleep(1);
+            flame\go(function() use($i) {
+                echo "x: ".flame\co_id()."\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "a: $i\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "b: $i\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "c: $i\n";
+            });
+        }
+        echo "done2\n";
+    });
+    echo "done1\n";
 });
 
 flame\run();
\ No newline at end of file
diff --git a/test/http_2.php b/test/http_2.php
index 09abcbe..69fd6a0 100644
--- a/test/http_2.php
+++ b/test/http_2.php
@@ -1,16 +1,26 @@
 <?php
 flame\init("http_2");
 
+class JsonObject implements JsonSerializable {
+    function jsonSerialize()
+    {
+        $date = new DateTime();
+        return ["date" => $date->unix()];
+    }
+}
+
 flame\go(function() {
-    $server = new flame\http\server(":::56120");
+    $server = new flame\http\server(":::56101");
     $poll;
 
     $server->before(function($req, $res, $m) {
         $req->data["start"] = flame\time\now();
         if(!$m) {
-            $res->status = 404;
-            $res->body = "404 Not Found\n";
-            return false; // 阻止后续 handle / after 的运行
+            $res->status = intval(substr($req->path, 1));
+            $res->header["Access-Control-Allow-Origin"] = "*";
+            $res->header["Content-Type"] = "application/json";
+            $res->body = ["a" => "bbbb"];
+            return false;
         }
     })->POST("/", function($req, $res) {
         $res->status = 200;
@@ -34,10 +44,6 @@
         //     }
         //     echo "done1\n";
         // });
-    })->get("/404", function($req, $res) {
-        $res->status = 404;
-        $res->header["Connection"] = "close";
-        $res->body = "404 Not Found\n";
     })->get("/push", function($req, $res) use(&$poll) {
         if($poll) {
             $poll->write("event: time\n");
@@ -53,6 +59,9 @@
         $r = flame\http\get("http://127.0.0.1:56120/");
         flame\time\sleep(10000);
         $res->body = $r->body;
+    })->get("/json", function($req, $res) {
+        $res->header["content-type"] = "application/json";
+        $res->body = ["data" => new JsonObject()];
     })->after(function($req, $res, $m) {
         $end = flame\time\now();
         // echo "elapsed: ", ($end - $req->data["start"]), "ms\n";

From 5cba16f62974262b5b774254ad7a49e048aad8a6 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 4 Jan 2019 15:18:23 +0800
Subject: [PATCH 053/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E6=9C=AA?=
 =?UTF-8?q?=E7=9F=A5=E9=85=8D=E7=BD=AE=E5=AF=BC=E8=87=B4=E5=88=9B=E5=BB=BA?=
 =?UTF-8?q?=E5=BC=82=E5=B8=B8=E6=9C=AA=E8=83=BD=E6=AD=A3=E7=A1=AE=E4=B8=8A?=
 =?UTF-8?q?=E6=8A=A5=EF=BC=88=E9=94=80=E6=AF=81=E6=AE=B5=E9=94=99=E8=AF=AF?=
 =?UTF-8?q?=E5=B4=A9=E6=BA=83=EF=BC=89=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/kafka/producer.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/kafka/producer.cpp b/src/kafka/producer.cpp
index 540e5d3..691cc8d 100644
--- a/src/kafka/producer.cpp
+++ b/src/kafka/producer.cpp
@@ -25,8 +25,10 @@ namespace flame::kafka
     }
     php::value producer::__destruct(php::parameters &params)
     {
-        coroutine_handler ch {coroutine::current};
-        pd_->flush(ch);
+        if(pd_) {
+            coroutine_handler ch {coroutine::current};
+            pd_->flush(ch);
+        }
         return nullptr;
     }
     php::value producer::publish(php::parameters &params)

From 842f7d2b367957faf66163c4c90c460d3297e4c2 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 7 Jan 2019 16:28:32 +0800
Subject: [PATCH 054/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E4=BD=BF?=
 =?UTF-8?q?=E7=94=A8=20AND/OR=20=E4=BB=A3=E6=9B=BF=20&&/||=20=E5=85=BC?=
 =?UTF-8?q?=E5=AE=B9=E4=B8=AD=E9=97=B4=E4=BB=B6=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mysql.php       |  9 +++++----
 src/mysql/mysql.cpp | 14 +++++++-------
 src/vendor.h        |  2 +-
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/doc/mysql.php b/doc/mysql.php
index 17fee10..d722eb7 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -23,18 +23,18 @@ function connect($url): client {}
  * 
  * @example
  *  $where = ["a"=>"1", "b"=>[2,"3","4"], "c"=>null, "d"=>["{!=}"=>5]];
- *  // " WHERE (`a`='1' && `b`  IN ('2','3','4') && `c` IS NULL && `d`!='5')"
+ *  // " WHERE (`a`='1' AND `b`  IN ('2','3','4') AND `c` IS NULL AND `d`!='5')"
  * @example
  *  $where = ["{OR}"=>["a"=>["{!=}"=>1], "b"=>["{><}"=>[1, 10, 3]], "c"=>["{~}"=>"aaa%"]]];
- *  // $sql == " WHERE (`a`!='1' || `b` NOT BETWEEN 1 AND 10 || `c` LIKE 'aaa%')"
+ *  // $sql == " WHERE (`a`!='1' OR `b` NOT BETWEEN 1 AND 10 OR `c` LIKE 'aaa%')"
  * @example
  *  $where = "`a`=1";
  *  // $sql == " WHERE `a`=1";
  * 
  * @param 特殊的符号均以 "{}" 进行包裹, 存在以下可用项:
  *  * `{NOT}` / `{!}` - 逻辑非, 对逻辑子句取反, 生成形式: `NOT (.........)`;
- *  * `{OR}` / `{||}` - 逻辑或, 对逻辑子句进行逻辑或拼接, 生成形式: `... || ... || ...`;
- *  * `{AND}` / `{||}` - 逻辑与, 对逻辑子句进行逻辑与拼接 (默认拼接方式), 生成形式: `... && ... && ...`;
+ *  * `{OR}` / `{||}` - 逻辑或, 对逻辑子句进行逻辑或拼接, 生成形式: `... OR ... OR ...`;
+ *  * `{AND}` / `{&&}` - 逻辑与, 对逻辑子句进行逻辑与拼接 (默认拼接方式), 生成形式: `... AND ... AND ...`;
  *  * `{!=}` - 不等, 生成形式: `...!='...'` / ` ... IS NOT NULL`;
  *  * `{>}` - 大于, 生成形式: `...>...`;
  *  * `{<}` - 小于, 生成形式: `...<...`;
@@ -82,6 +82,7 @@ function connect($url): client {}
  * MySQL 客户端（内部使用连接池）
  */
 class client {
+    function escape($value):string {}
     /**
      * 返回事务对象 (绑定在一个连接上)
      */
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index cc1c184..85fa8ac 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -200,7 +200,7 @@ namespace flame::mysql {
             {
                 if (i->second.typeof(php::TYPE::ARRAY))
                 {
-                    where_ex(cc, buf, i->second, i->first, " && ");
+                    where_ex(cc, buf, i->second, i->first, " AND ");
                 }
                 else
                 {
@@ -218,17 +218,17 @@ namespace flame::mysql {
                     {
                         assert(i->second.typeof(php::TYPE::ARRAY));
                         buf.append(" NOT ", 5);
-                        where_ex(cc, buf, i->second, php::string(nullptr), " && ");
+                        where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
                     }
                     else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0 || strncasecmp(key.c_str(), "{||}", 4) == 0))
                     {
                         assert(i->second.typeof(php::TYPE::ARRAY));
-                        where_ex(cc, buf, i->second, php::string(nullptr), " || ");
+                        where_ex(cc, buf, i->second, php::string(nullptr), " OR ");
                     }
                     else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0) || (key.size() == 4 && strncasecmp(key.c_str(), "{&&}", 4) == 0))
                     {
                         assert(i->second.typeof(php::TYPE::ARRAY));
-                        where_ex(cc, buf, i->second, php::string(nullptr), " && ");
+                        where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
                     }
                     else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0)
                     {
@@ -289,7 +289,7 @@ namespace flame::mysql {
                         }
                         else
                         {
-                            where_ex(cc, buf, cond, key, " && ");
+                            where_ex(cc, buf, cond, key, " AND ");
                         }
                     }
                     else
@@ -313,7 +313,7 @@ namespace flame::mysql {
         }
         else if (data.typeof(php::TYPE::ARRAY))
         {
-            where_ex(cc, buf, data, php::string(0), " && ");
+            where_ex(cc, buf, data, php::string(0), " AND ");
         }
         else
         {
@@ -353,7 +353,7 @@ namespace flame::mysql {
                         buf.push_back(' ');
                         buf.append(i->second);
                     }
-                    else if (i->second.typeof(php::TYPE::YES) || (i->second.typeof(php::TYPE::INTEGER) &&static_cast<int>(i->second) >= 0))
+                    else if (i->second.typeof(php::TYPE::YES) || (i->second.typeof(php::TYPE::INTEGER) && static_cast<int>(i->second) >= 0))
                     {
                         buf.append(" ASC", 4);
                     }
diff --git a/src/vendor.h b/src/vendor.h
index 6fe4df9..316932b 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.9"
+#define EXTENSION_VERSION "0.12.10"
 
 #include <cstdio>
 #include <sys/types.h>

From 68b65ebcb9bec05189ea190e2515bebdddf7a186 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 9 Jan 2019 15:16:45 +0800
Subject: [PATCH 055/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20co=5Fid()=20?=
 =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/doc/core.php b/doc/core.php
index ae49930..6cb3bb4 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -25,6 +25,10 @@ function init($process_name, $options = []) {}
  *  启动协程(运行对应回调函数)
  */
 function go(callable $cb) {}
+/**
+ * 获取一个当前协程 ID 标识
+ */
+function co_id():int {}
 /**
  * 框架调度, 上述协程会在框架开始调度运行后启动
  * 注意：协程异步调度需要 run() 才能启动执行；

From 169412bbd92196bb0281698e9b55148e496f1248 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 10 Jan 2019 11:32:50 +0800
Subject: [PATCH 056/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E6=9C=80?=
 =?UTF-8?q?=E5=A4=A7=E5=85=81=E8=AE=B8=E5=B9=B6=E8=A1=8C=20256=20=E4=B8=AA?=
 =?UTF-8?q?=E6=B6=88=E8=B4=B9=E5=8D=8F=E7=A8=8B=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/kafka/kafka.cpp      | 2 +-
 src/rabbitmq/_client.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index 171c486..90a9e91 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -31,7 +31,7 @@ namespace flame::kafka
         consumer* ptr = static_cast<consumer*>(php::native(obj));
         if (config.exists("concurrent"))
         {
-            ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 8), 256);
+            ptr->cc_ = std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 256);
             config.erase("concurrent");
         }
         if (!config.exists("log.connection.close")) {
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
index 80363a0..fc254be 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/rabbitmq/_client.cpp
@@ -39,7 +39,7 @@ namespace flame::rabbitmq
         if(i == u.query.end()) {
             pf_ = 8;
         }else{
-            pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 1024);
+            pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 256);
         }
         ch_.setQos(pf_);
 

From 5603558934bf7b07a2616bc502f994564377389f Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 11 Jan 2019 01:26:55 +0800
Subject: [PATCH 057/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E9=87=8D?=
 =?UTF-8?q?=E5=A4=8D=E8=BF=9B=E8=A1=8C=E5=85=B3=E9=97=AD=E6=B5=81=E7=A8=8B?=
 =?UTF-8?q?=E4=B8=8D=E5=BA=94=E9=87=8D=E7=BD=AE=E9=80=80=E5=87=BA=E8=B6=85?=
 =?UTF-8?q?=E6=97=B6=EF=BC=9B=E4=BF=AE=E6=AD=A3=EF=BC=9AMySQL=20=E6=9F=A5?=
 =?UTF-8?q?=E8=AF=A2=E5=AD=97=E7=AC=A6=E9=9B=86=E6=B3=84=E6=BC=8F=E4=B8=80?=
 =?UTF-8?q?=E4=B8=AA=20MYSQL=5FRES=20=E6=8C=87=E9=92=88=EF=BC=9B=E4=BF=AE?=
 =?UTF-8?q?=E6=AD=A3=EF=BC=9AMongoDB=20=E5=91=BD=E4=BB=A4=E6=89=A7?=
 =?UTF-8?q?=E8=A1=8C=E6=B5=81=E7=A8=8B=20reply=20=E5=9C=A8=E5=A0=86?=
 =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=AF=BC=E8=87=B4=E5=86=85=E5=AD=98=E6=B3=84?=
 =?UTF-8?q?=E6=BC=8F=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_master.cpp        |  3 +++
 src/controller_master.h          |  1 +
 src/kafka/_producer.cpp          | 26 ++++++++++++++++++--------
 src/mongodb/_connection_base.cpp |  9 ++++++---
 src/mysql/_connection_pool.cpp   |  2 +-
 5 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 3866584..0425353 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -187,6 +187,9 @@ namespace flame
     }
     void controller_master::close_worker()
     {
+        if(close_) return;
+        // 关闭动作仅作一次（组织重置 timer_ 超时时间）
+        close_ = true;
         for (auto i = worker_.begin(); i != worker_.end(); ++i)
         {
             if (*i)
diff --git a/src/controller_master.h b/src/controller_master.h
index 77f306e..14ccdab 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -29,6 +29,7 @@ namespace flame
         void redirect_eout(int i);
 
         void await_signal();
+        bool close_ = false;
         void close_worker();
     };
 }
\ No newline at end of file
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index 329eeda..030be05 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -39,14 +39,24 @@ namespace flame::kafka
         rd_kafka_resp_err_t err;
         rd_kafka_headers_t* hdrs = array2hdrs(headers);
         boost::asio::post(gcontroller->context_y, [this, &err, &topic, &key, &payload, &hdrs, &ch]() {
-            err = rd_kafka_producev(conn_,
-                              RD_KAFKA_V_TOPIC(topic.c_str()),
-                              RD_KAFKA_V_KEY(key.c_str(), key.size()),
-                              RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
-                              RD_KAFKA_V_HEADERS(hdrs),
-                              RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
-                              RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
-                              RD_KAFKA_V_END);
+            if(key.size() > 0) {
+                err = rd_kafka_producev(conn_,
+                    RD_KAFKA_V_TOPIC(topic.c_str()),
+                    RD_KAFKA_V_KEY(key.c_str(), key.size()),
+                    RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
+                    RD_KAFKA_V_HEADERS(hdrs),
+                    RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
+                    RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
+                    RD_KAFKA_V_END);
+            }else{ // 无 KEY 时默认 partitioner 会随机分配
+                err = rd_kafka_producev(conn_,
+                    RD_KAFKA_V_TOPIC(topic.c_str()),
+                    RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
+                    RD_KAFKA_V_HEADERS(hdrs),
+                    RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
+                    RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
+                    RD_KAFKA_V_END);
+            }
             // TODO 优化: 是否可以考虑按周期间隔进行调用?
             rd_kafka_poll(conn_, 5);
             ch.resume();
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index ae993b4..1d3f06b 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -9,9 +9,12 @@ namespace flame::mongodb
     void _connection_base::fake_deleter(bson_t *doc) {}
     php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, bool write, coroutine_handler &ch)
     {
-        auto cmd = array2bson(pcmd);
-        std::shared_ptr<bson_t> rep(bson_new(), bson_destroy);
-        auto err = std::make_shared<bson_error_t>();
+        std::shared_ptr<bson_t> cmd = array2bson(pcmd);
+        // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏
+        //（实际对 reply 的使用流程会重新初始化 reply 导致其值被至于 stack 上，导致此处 heap 内存泄漏）
+        bson_t reply;
+        std::shared_ptr<bson_t> rep(&reply, bson_destroy);
+        std::shared_ptr<bson_error_t> err = std::make_shared<bson_error_t>();
         int  rok = 0;
         boost::asio::post(gcontroller->context_y, [conn, write, &cmd, &rep, &err, &rok, &ch] () {
             const mongoc_uri_t *uri = mongoc_client_get_uri(conn.get());
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index e9e8165..d94601e 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -125,7 +125,7 @@ namespace flame::mysql
         charset_ = false; // 乐观
         if (err == 0)
         {
-            std::unique_ptr<MYSQL_RES> rst(mysql_store_result(c));
+            std::unique_ptr<MYSQL_RES, void (*)(MYSQL_RES*)> rst(mysql_store_result(c), mysql_free_result);
             if (!rst) return;
             MYSQL_ROW row = mysql_fetch_row(rst.get());
             if(!row) return;

From d062f7bd94d96a41b128abd247781990830dece2 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sat, 12 Jan 2019 02:44:50 +0800
Subject: [PATCH 058/146] =?UTF-8?q?=E6=8C=82=E6=8E=A5=20jemalloc=20+=20?=
 =?UTF-8?q?=E7=AE=80=E5=8D=95=E5=8E=8B=E6=B5=8B=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt        |  2 ++
 README.md             | 17 +++++++++++++++++
 src/http/_handler.cpp |  2 +-
 src/vendor.h          |  2 +-
 test/http_2.php       |  4 +++-
 5 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0dafca0..c970a63 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,6 +18,7 @@ add_library(FLAME SHARED ${SOURCES})
 # 依赖项目录
 set(VENDOR_GCC /data/vendor/gcc-8.2.0)
 set(VENDOR_LLVM /data/vendor/llvm-7.0.0)
+set(VENDOR_JEMALLOC /data/vendor/jemalloc-5.1.0)
 set(VENDOR_PHP /data/vendor/php-7.2.13)
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
@@ -76,6 +77,7 @@ target_link_libraries(FLAME
     sasl2
     ssl
     crypto
+    ${VENDOR_JEMALLOC}/lib/libjemalloc.a
     pthread
     rt
 )
diff --git a/README.md b/README.md
index 2ee6224..3d9d493 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,23 @@
 
 请参看 `/doc` 目录下相关 PHP 文件的文档注释，或将该目录挂接再 IDE 内提供自动完成；
 
+### 简单测试
+
+``` bash
+/data/vendor/php-7.2.13/bin/php test/http_2.php
+siege -q -l ~/siege.log -c 200 -b --time=60S http://127.0.0.1:56101/
+siege -q -l ~/siege.log -c 20 -b --time=60S http://127.0.0.1:56101/
+```
+
+| Date & Time | Trans | Elap Time | Data Trans | Resp Time | Trans Rate | Throughput | Concurrent | OKay | Timeout |
+| ----------- | ----- | --------- | ---------- | --------- | ---------- | ---------- | ---------- | ---- | ------- |
+| 单进程 | 短连接 |
+| 2019-01-12 01:47:26 | 1143144 | 59.98 | 320 | 0.01 | **19058.75** | 5.34 | 196.92 | 1143144 | 0 |
+| 2019-01-12 02:07:18 | 1152759 | 59.43 | 323 | 0.00 | **19396.92** | 5.43 | 19.44 | 1152759 | 0 |
+| 单进程 | 长连接 |
+| 2019-01-12 01:58:08 | 1632454 | 59.33 | 465 | 0.01 | **27514.81** | 7.84 | 198.67 | 1632454 | 0 |
+| 2019-01-12 02:02:40 | 2032857 | 59.50 | 579 | 0.00 | **34165.66** | 9.73 | 19.25 | 2032857 | 0 |
+
 ### 依赖库
 
 #### PHP
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 114fa03..0b31d56 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -44,7 +44,7 @@ namespace flame::http
         req_->body_limit(body_max_size);
         boost::beast::http::async_read(socket_, buffer_, *req_, [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n)
         {
-            if(error == boost::beast::http::error::end_of_stream || error == boost::asio::error::operation_aborted)
+            if(error == boost::beast::http::error::end_of_stream || error == boost::asio::error::operation_aborted || boost::asio::error::connection_reset)
             {
                 res_.reset();
                 req_.reset();
diff --git a/src/vendor.h b/src/vendor.h
index 316932b..38f9396 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.10"
+#define EXTENSION_VERSION "0.12.11"
 
 #include <cstdio>
 #include <sys/types.h>
diff --git a/test/http_2.php b/test/http_2.php
index 69fd6a0..0c9b14c 100644
--- a/test/http_2.php
+++ b/test/http_2.php
@@ -22,7 +22,9 @@ function jsonSerialize()
             $res->body = ["a" => "bbbb"];
             return false;
         }
-    })->POST("/", function($req, $res) {
+    })->get("/", function($req, $res) {
+        $res->body = json_encode($req);
+    })->post("/", function($req, $res) {
         $res->status = 200;
         $res->header["Content-Type"] = "application/json";
         $res->body = $req->body;

From 642aa1f41c6962883809b2d5d8a20a1f802a097a Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 13 Jan 2019 23:15:19 +0800
Subject: [PATCH 059/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E9=80=80?=
 =?UTF-8?q?=E5=87=BA=E8=B6=85=E6=97=B6=E9=85=8D=E7=BD=AE=E5=8C=96=EF=BC=8C?=
 =?UTF-8?q?=E9=BB=98=E8=AE=A4=2010=20=E7=A7=92=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                 |  9 ++++++++-
 src/controller.cpp        |  6 ++++++
 src/controller.h          |  1 +
 src/controller_master.cpp | 12 +++++++++---
 4 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 3d9d493..179c0e7 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,11 @@
 * **rabbitmq** - 简单 RabbitMQ 生产消费支持；
 * **kafka** - 简单 Kafka 生产消费支持（仅支持 Kafka 0.10+ 群组消费）；
 
-请参看 `/doc` 目录下相关 PHP 文件的文档注释，或将该目录挂接再 IDE 内提供自动完成；
+请参看 `/doc` 目录下相关 PHP 文件的文档注释，或将该目录挂接再 IDE 内提供**自动完成**；
+
+### 常见问题
+
+https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
 
 ### 简单测试
 
@@ -30,6 +34,9 @@ siege -q -l ~/siege.log -c 20 -b --time=60S http://127.0.0.1:56101/
 | 单进程 | 长连接 |
 | 2019-01-12 01:58:08 | 1632454 | 59.33 | 465 | 0.01 | **27514.81** | 7.84 | 198.67 | 1632454 | 0 |
 | 2019-01-12 02:02:40 | 2032857 | 59.50 | 579 | 0.00 | **34165.66** | 9.73 | 19.25 | 2032857 | 0 |
+| 8 进程 | 短连接 |
+| 8 进程 | 长连接 |
+
 
 ### 依赖库
 
diff --git a/src/controller.cpp b/src/controller.cpp
index 7ddc8f1..3e6f7e9 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -43,6 +43,12 @@ namespace flame {
 		return this;
 	}
 	void controller::initialize(const std::string& title, const php::array& options) {
+		// 多进程模式退出超时时间
+		if(options.exists("timeout")) {
+			worker_quit = std::max(static_cast<int>(options.get("timeout")), 64);
+		}else{
+			worker_quit = 10;
+		}
 		status |= STATUS_INITIALIZED;
 		for (auto fn : init_cb)
 		{
diff --git a/src/controller.h b/src/controller.h
index ea20ab7..9c840bd 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -26,6 +26,7 @@ namespace flame {
 		};
 		int status;
 		std::size_t    worker_size;
+		std::size_t    worker_quit; // 多进程退出超时时间
 		std::thread::id mthread_id;
 		// 防止 PHP 提前回收, 使用堆容器
 		std::multimap<std::string, php::callable>* cbmap;
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 0425353..1bd81d9 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -187,8 +187,14 @@ namespace flame
     }
     void controller_master::close_worker()
     {
-        if(close_) return;
-        // 关闭动作仅作一次（组织重置 timer_ 超时时间）
+        // 第二次进行 “强制关闭”
+        if(close_)
+        {
+            timer_.reset();
+            gcontroller->context_x.stop();
+            return;
+        }
+        // 第一次设置超时
         close_ = true;
         for (auto i = worker_.begin(); i != worker_.end(); ++i)
         {
@@ -198,7 +204,7 @@ namespace flame
             }
         }
         timer_.reset(new boost::asio::steady_timer(gcontroller->context_y));
-        timer_->expires_after(std::chrono::seconds(10));
+        timer_->expires_after(std::chrono::seconds(gcontroller->worker_quit));
         timer_->async_wait([] (const boost::system::error_code& error) {
             if(error) return;
             gcontroller->context_x.stop();

From fda768a98d2951bc32d77f294a2e772f7a04cdc0 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 16 Jan 2019 00:41:18 +0800
Subject: [PATCH 060/146] =?UTF-8?q?=E6=B6=88=E8=B4=B9=E8=87=AA=E8=BA=AB?=
 =?UTF-8?q?=E8=87=AA=E8=BA=AB=E5=BC=82=E5=B8=B8=E4=B9=9F=E5=BA=94=E8=A2=AB?=
 =?UTF-8?q?=E6=8D=95=E8=8E=B7=E5=8D=8F=E7=A8=8B=E7=BA=A7=E5=A4=84=E7=90=86?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/kafka/_consumer.cpp |  3 ++-
 src/kafka/_producer.cpp |  6 ++++--
 src/kafka/consumer.cpp  | 15 ++++++++-------
 3 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index 9ea5f12..d815432 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -97,8 +97,9 @@ namespace flame::kafka
             ch.resume();
         });
         ch.suspend();
-        if(err == RD_KAFKA_RESP_ERR__PARTITION_EOF)
+        if(err == RD_KAFKA_RESP_ERR__PARTITION_EOF/* || err == RD_KAFKA_RESP_ERR__TRANSPORT*/)
         {
+            // TODO RD_KAFKA_RESP_ERR__TRANSPORT 是否要忽略？
             return nullptr;
         }
         else if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index 030be05..ca75ef4 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -57,8 +57,10 @@ namespace flame::kafka
                     RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
                     RD_KAFKA_V_END);
             }
-            // TODO 优化: 是否可以考虑按周期间隔进行调用?
-            rd_kafka_poll(conn_, 5);
+            if(err == RD_KAFKA_RESP_ERR_NO_ERROR) {
+                // TODO 优化: 是否可以考虑按周期间隔进行调用?
+                rd_kafka_poll(conn_, 5);
+            }
             ch.resume();
         });
         ch.suspend();
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index 1322d77..b633428 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -42,14 +42,15 @@ namespace flame::kafka {
                 coroutine_handler ch {coroutine::current};
                 while(!close_)
                 {
-                    php::object msg = cs_->consume(ch);
-                    // 可能出现为 NULL 的情况
-                    if (msg.typeof(php::TYPE::NULLABLE))
-                    {
-                        continue;
-                    }
                     try
-                    {
+                    {  
+                        // consume 本身可能出现异常，不应导致进程停止
+                        php::object msg = cs_->consume(ch);
+                        // 可能出现为 NULL 的情况
+                        if (msg.typeof(php::TYPE::NULLABLE))
+                        {
+                            continue;
+                        }
                         cb_.call({msg});
                     }
                     catch(const php::exception& ex)

From 38a5a27e8aa427b0f6da3a4556bb3b1eccccb2db Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 18 Jan 2019 20:19:07 +0800
Subject: [PATCH 061/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9AHTTP=20?=
 =?UTF-8?q?=E5=A4=84=E7=90=86=E5=99=A8=20RESET=20=E9=94=99=E8=AF=AF?=
 =?UTF-8?q?=E5=88=A4=E5=AE=9A=E9=97=AE=E9=A2=98=EF=BC=9B=E4=BF=AE=E6=AD=A3?=
 =?UTF-8?q?=EF=BC=9A=E5=8F=96=E6=B6=88=20jemalloc=20=E9=98=B2=E6=AD=A2?=
 =?UTF-8?q?=E5=86=B2=E7=AA=81=E5=BC=82=E5=B8=B8=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt        | 2 --
 src/http/_handler.cpp | 2 +-
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c970a63..0dafca0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,6 @@ add_library(FLAME SHARED ${SOURCES})
 # 依赖项目录
 set(VENDOR_GCC /data/vendor/gcc-8.2.0)
 set(VENDOR_LLVM /data/vendor/llvm-7.0.0)
-set(VENDOR_JEMALLOC /data/vendor/jemalloc-5.1.0)
 set(VENDOR_PHP /data/vendor/php-7.2.13)
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
@@ -77,7 +76,6 @@ target_link_libraries(FLAME
     sasl2
     ssl
     crypto
-    ${VENDOR_JEMALLOC}/lib/libjemalloc.a
     pthread
     rt
 )
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 0b31d56..2880646 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -44,7 +44,7 @@ namespace flame::http
         req_->body_limit(body_max_size);
         boost::beast::http::async_read(socket_, buffer_, *req_, [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n)
         {
-            if(error == boost::beast::http::error::end_of_stream || error == boost::asio::error::operation_aborted || boost::asio::error::connection_reset)
+            if(error == boost::beast::http::error::end_of_stream || error == boost::asio::error::operation_aborted || error == boost::asio::error::connection_reset)
             {
                 res_.reset();
                 req_.reset();

From 7d7a7094610c7969fdb5aca86323fbdc86c3a936 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 18 Jan 2019 20:26:37 +0800
Subject: [PATCH 062/146] =?UTF-8?q?=E6=9A=82=E6=97=B6=E5=8F=96=E6=B6=88?=
 =?UTF-8?q?=E5=8E=8B=E6=B5=8B=E7=BB=93=E6=9E=9C=EF=BC=88=E7=AD=89=E5=90=8E?=
 =?UTF-8?q?=E7=BB=AD=E9=87=8D=E6=96=B0=E6=B5=8B=E5=AE=9A=EF=BC=89=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md | 22 ----------------------
 1 file changed, 22 deletions(-)

diff --git a/README.md b/README.md
index 179c0e7..79f9892 100644
--- a/README.md
+++ b/README.md
@@ -18,26 +18,6 @@
 
 https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
 
-### 简单测试
-
-``` bash
-/data/vendor/php-7.2.13/bin/php test/http_2.php
-siege -q -l ~/siege.log -c 200 -b --time=60S http://127.0.0.1:56101/
-siege -q -l ~/siege.log -c 20 -b --time=60S http://127.0.0.1:56101/
-```
-
-| Date & Time | Trans | Elap Time | Data Trans | Resp Time | Trans Rate | Throughput | Concurrent | OKay | Timeout |
-| ----------- | ----- | --------- | ---------- | --------- | ---------- | ---------- | ---------- | ---- | ------- |
-| 单进程 | 短连接 |
-| 2019-01-12 01:47:26 | 1143144 | 59.98 | 320 | 0.01 | **19058.75** | 5.34 | 196.92 | 1143144 | 0 |
-| 2019-01-12 02:07:18 | 1152759 | 59.43 | 323 | 0.00 | **19396.92** | 5.43 | 19.44 | 1152759 | 0 |
-| 单进程 | 长连接 |
-| 2019-01-12 01:58:08 | 1632454 | 59.33 | 465 | 0.01 | **27514.81** | 7.84 | 198.67 | 1632454 | 0 |
-| 2019-01-12 02:02:40 | 2032857 | 59.50 | 579 | 0.00 | **34165.66** | 9.73 | 19.25 | 2032857 | 0 |
-| 8 进程 | 短连接 |
-| 8 进程 | 长连接 |
-
-
 ### 依赖库
 
 #### PHP
@@ -109,5 +89,3 @@ make
 PREFIX=/data/vendor/hiredis-0.14.0 make install
 rm /data/vendor/hiredis-0.14.0/lib/*.so*
 ```
-
-

From 220e3e8daf10c5b65c66c3b948417ce2e9868bdc Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 18 Jan 2019 20:57:21 +0800
Subject: [PATCH 063/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20timeout=20?=
 =?UTF-8?q?=E9=80=89=E9=A1=B9=E8=AF=B4=E6=98=8E=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index 6cb3bb4..b1c54fe 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -7,7 +7,7 @@
  * 初始化框架, 设置进程名称及相关配置;
  * @param string $process_name 进程名称
  * @param array  $options 选项配置, 目前可用如下:
- *  * "logger" - 日志输出重定向目标文件(完整路径, 若不提供使用标准输出); 
+ *  * "logger" - 日志输出重定向目标文件(完整路径, 若不提供使用标准输出);
  *      向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
  *  * "level" - 日志输出级别, 设置该级别下的日志将不被记录; 可用级别如下
  *      "debug"
@@ -15,8 +15,9 @@
  *      "warning"
  *      "error"
  *      "fatal"
+ *  * "timeout" - 多进程退出超时（超时后会被强制杀死）
  *  @see flame\log
- * 
+ *
  * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式
  * 主进程将自动进行日志文件写入，子进程自动拉起；
  */
@@ -95,7 +96,7 @@ function unlock() {}
  */
 class guard {
     /**
-     * 构建守护并锁定 mutex 
+     * 构建守护并锁定 mutex
      * @param mutex $mutex 实际保护使用的 mutex 对象
      */
     function __construct(mutex $mutex) {}
@@ -103,4 +104,4 @@ function __construct(mutex $mutex) {}
      * 解锁 mutex 并销毁守护
      */
     function __destruct() {}
-}
\ No newline at end of file
+}

From f9619d6944c6a2d46724bd70f69e6e840f7c2d4c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 26 Feb 2019 16:09:22 +0800
Subject: [PATCH 064/146] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E4=BE=9D=E8=B5=96=20?=
 =?UTF-8?q?librdkafka=20v1.0.0=20/=20mongoc=20v1.14.0=20/=20mysqlc=20v8.0.?=
 =?UTF-8?q?15;=20=E4=BC=98=E5=8C=96=EF=BC=9A=E4=BD=BF=E7=94=A8=E5=86=85?=
 =?UTF-8?q?=E7=BD=AE=E9=98=9F=E5=88=97=E4=BC=98=E5=8C=96=20Kafka=20?=
 =?UTF-8?q?=E6=B6=88=E8=B4=B9=E6=B5=81=E7=A8=8B=EF=BC=8C=E9=81=BF=E5=85=8D?=
 =?UTF-8?q?=E5=87=BA=E7=8E=B0=E8=BF=87=E5=A4=9A=E6=97=A0=E6=95=B0=E6=8D=AE?=
 =?UTF-8?q?=E7=A9=BA=E7=AD=89=E5=BE=85=EF=BC=9B=E4=BC=98=E5=8C=96=EF=BC=9A?=
 =?UTF-8?q?MongoDB=20=E7=A9=BA=E6=95=B0=E7=BB=84=E3=80=81=E6=9C=AA?=
 =?UTF-8?q?=E7=9F=A5=E5=AF=B9=E8=B1=A1=E8=BD=AC=20BSON=20=E7=B1=BB?=
 =?UTF-8?q?=E5=9E=8B=E4=B8=A5=E6=A0=BC=E5=AF=B9=E5=BA=94=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                       |  4 +++
 CMakeLists.txt                   | 53 +++++++++++++++++---------------
 README.md                        | 36 ++++++++++++----------
 src/flame.cpp                    |  4 +--
 src/http/_handler.cpp            |  6 ++--
 src/kafka/_consumer.cpp          | 36 +++++++++++-----------
 src/kafka/_consumer.h            |  9 ++++--
 src/kafka/consumer.cpp           | 38 +++++++++--------------
 src/kafka/consumer.h             |  2 --
 src/mongodb/_connection_pool.cpp | 10 +++---
 src/mongodb/client.cpp           | 15 +++++++--
 src/mongodb/client.h             |  3 +-
 src/mongodb/collection.cpp       |  2 +-
 src/mongodb/mongodb.cpp          | 10 +++---
 src/mysql/_connection_base.cpp   | 12 ++------
 src/vendor.h                     |  4 +--
 16 files changed, 127 insertions(+), 117 deletions(-)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca74814
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.ccls-cache
+.vscode
+build
+compile_commands.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0dafca0..a964ed4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,24 +1,23 @@
-cmake_minimum_required(VERSION 3.10)
+cmake_minimum_required(VERSION 3.13)
+# -----------------------------------------------------------------------------
 # 默认编译器
+# -----------------------------------------------------------------------------
 if(NOT CMAKE_BUILD_TYPE)
-    set(CMAKE_BUILD_TYPE "Debug")
+    set(CMAKE_BUILD_TYPE Debug)
 endif(NOT CMAKE_BUILD_TYPE)
-if(NOT CMAKE_C_COMPILER)
-    set(CMAKE_C_COMPILER /data/vendor/llvm-7.0.0/bin/clang)
-endif()
-if(NOT CMAKE_CXX_COMPILER)
-    set(CMAKE_CXX_COMPILER /data/vendor/llvm-7.0.0/bin/clang++)
-endif()
-project(FLAME)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+# -----------------------------------------------------------------------------
 # 项目源文件
+# -----------------------------------------------------------------------------
+project(FLAME)
 execute_process(COMMAND find ${CMAKE_SOURCE_DIR}/src -name *.cpp
     COMMAND tr "\n" ";"
     OUTPUT_VARIABLE SOURCES)
 add_library(FLAME SHARED ${SOURCES})
+# -----------------------------------------------------------------------------
 # 依赖项目录
-set(VENDOR_GCC /data/vendor/gcc-8.2.0)
-set(VENDOR_LLVM /data/vendor/llvm-7.0.0)
-set(VENDOR_PHP /data/vendor/php-7.2.13)
+# -----------------------------------------------------------------------------
+set(VENDOR_PHP /data/vendor/php-7.2.15)
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
@@ -26,22 +25,27 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
 set(VENDOR_PHPEXT /data/vendor/phpext-1.1.2)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
-set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
+# set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
+set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
-set(VENDOR_MONGODB /data/vendor/mongoc-1.13.0)
-set(VENDOR_AMQP /data/vendor/amqpcpp-4.0.1)
-set(VENDOR_RDKAFKA /data/vendor/rdkafka-0.11.6)
-set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.8.1)
+set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
+set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.3)
+set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.0)
+set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.9.0)
+# -----------------------------------------------------------------------------
 # 编译选项
-set(CUSTOM_COMPILE_FLAGS "-std=c++17")
-set(CUSTOM_LINK_FLAGS "-static-libstdc++")
-if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
-    set(CUSTOM_COMPILE_FLAGS "${CUSTOM_COMPILE_FLAGS} --gcc-toolchain=${VENDOR_GCC}")
-    set(CUSTOM_LINK_FLAGS "${CUSTOM_LINK_FLAGS} --gcc-toolchain=${VENDOR_GCC}")
+# -----------------------------------------------------------------------------
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND WITH_GCC_TOOLCHAIN)
+    set(CLANG_EXTRA_COMPILE_FLAGS "--gcc-toolchain=${WITH_GCC_TOOLCHAIN}")
+    set(CLANG_EXTRA_LINK_FLAGS "--gcc-toolchain=${WITH_GCC_TOOLCHAIN}")
 endif()
+target_compile_options(FLAME BEFORE
+	PRIVATE "-std=c++17"
+	PRIVATE "${CLANG_EXTRA_COMPILE_FLAGS}")
+target_link_options(FLAME BEFORE
+	PRIVATE "-static-libstdc++"
+	PRIVATE "${CLANG_EXTRA_COMPILE_FLAGS}")
 set_target_properties(FLAME PROPERTIES
-    COMPILE_FLAGS "${CUSTOM_COMPILE_FLAGS}"
-    LINK_FLAGS "${CUSTOM_LINK_FLAGS}"
     PREFIX ""
     OUTPUT_NAME "flame")
 # 包含路径
@@ -58,6 +62,7 @@ target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_RDKAFKA}/include
     ${VENDOR_HTTPPARSER}/include
 )
+target_link_directories(FLAME PUBLIC /usr/lib/openssl-1.0)
 # 链接库
 target_link_libraries(FLAME
     ${VENDOR_HTTPPARSER}/lib/libhttp_parser.o
diff --git a/README.md b/README.md
index 79f9892..166833b 100644
--- a/README.md
+++ b/README.md
@@ -22,12 +22,12 @@ https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
 
 #### PHP
 ``` Bash
-./configure  --prefix=/data/vendor/php-7.2.13 --with-config-file-path=/data/vendor/php-7.2.13/etc --disable-fpm --disable-phar --disable-dom --disable-libxml --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-openssl --with-readline --enable-mbstring --without-pear
+./configure --prefix=/data/vendor/php-7.2.15 --with-config-file-path=/data/vendor/php-7.2.15/etc --disable-fpm --disable-phar --disable-dom --disable-libxml --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-openssl --with-readline --enable-mbstring --without-pear --with-curl --enable-mbstring --host=x86_64-linux-gnu --target=x86_64-linux-gnu
 ```
 #### Boost
 
 ``` Bash
-./bootstrap.sh --prefix=/data/vendor/boost-1.69.0
+./bootstrap.sh --prefix=/data/vendor/boost-1.69.0~/
 ./b2 -j4 --prefix=/data/vendor/boost-1.69.0 cxxflags="-fPIC" variant=release link=static threading=multi install
 ```
 
@@ -42,50 +42,52 @@ make install
 make install
 ```
 
-#### mysql-connector-c v6.1.11
+#### mysql-connector-c v8.0.15
 ``` Bash
-mv mysql-connector-c-6.1.11-src/ /data/vendor/mysqlc-6.1.11
-rm /data/vendor/mysqlc-6.1.11/lib/*.so*
+mkdir -p /data/vendor/mysqlc-8.0.15/lib
+cp -R mysql-8.0.15-linux-glibc2.12-x86_64/include /data/vendor/mysqlc-8.0.15
+cp mysql-8.0.15-linux-glibc2.12-x86_64/lib/libmysqlclient.a /data/vendor/mysqlc-8.0.15/lib
 ```
 
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.13.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+# with openssl 1.0 in another path
+# -DOPENSSL_INCLUDE_DIR=/usr/include/openssl-1.0 -DOPENSSL_SSL_LIBRARY=/usr/lib/openssl-1.0/libssl.so -DOPENSSL_CRYPTO_LIBRARY=/usr/lib/openssl-1.0/libcrypto.so
 make
 make install
-rm /data/vendor/mongoc-1.13.0/lib/*.so*
+rm /data/vendor/mongoc-1.14.0/lib/*.so*
 ```
 
 #### AMQP-CPP
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake3 -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.0.1 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.3 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
 make
 make install
-# rm /data/vendor/amqpcpp-4.0.1/lib/*.so*
 ```
 
 #### Rdkafka
 ``` Bash
-./configure --prefix=/data/vendor/rdkafka-0.11.6
+./configure --prefix=/data/vendor/rdkafka-1.0.0
 make
 make install
-rm /data/vendor/rdkafka-0.11.6/lib/*.so*
+rm /data/vendor/rdkafka-1.0.0/lib/*.so*
 ```
 
 #### HttpParser
 ``` Bash
-mkdir -p /data/vendor/http-parser-2.8.1/lib
-mkdir -p /data/vendor/http-parser-2.8.1/include
-CFLAGS=-fPIC make libhttp_parser.o
-cp libhttp_parser.o /data/vendor/http-parser-2.8.1/lib
-cp http_parser.h /data/vendor/http-parser-2.8.1/include
+mkdir -p /data/vendor/http-parser-2.9.0/lib
+mkdir -p /data/vendor/http-parser-2.9.0/include
+CC=gcc CFLAGS=-fPIC make libhttp_parser.o
+cp libhttp_parser.o /data/vendor/http-parser-2.9.0/lib
+cp http_parser.h /data/vendor/http-parser-2.9.0/include
 ```
 
 #### HiRedis
 ``` Bash
-make
+CC=gcc make
 PREFIX=/data/vendor/hiredis-0.14.0 make install
 rm /data/vendor/hiredis-0.14.0/lib/*.so*
 ```
diff --git a/src/flame.cpp b/src/flame.cpp
index 566561b..aab09eb 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -15,7 +15,7 @@
 #include "tcp/tcp.h"
 #include "http/http.h"
 
-namespace flame 
+namespace flame
 {
     static php::value init(php::parameters& params)
     {
@@ -122,7 +122,7 @@ extern "C"
             .desc({"vendor/libphpext", PHPEXT_LIB_VERSION})
             .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
             .desc({"vendor/mysqlc", mysql_get_client_info()})
-            .desc({"vendor/amqpcpp", "4.0.1"})
+            .desc({"vendor/amqpcpp", "4.1.3"})
             .desc({"vendor/librdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S});
 
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 2880646..9b94907 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -51,7 +51,7 @@ namespace flame::http
                 return;
             }
             else if (error)
-            {   
+            {
                 res_.reset();
                 req_.reset();
                 std::cerr << "[" << time::iso() << "] (WARNING) failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
@@ -90,7 +90,7 @@ namespace flame::http
             try
             {
                 php::value rv = true;
-                if(ib != svr_ptr->cb_.end()) 
+                if(ib != svr_ptr->cb_.end())
                 {
                     rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
                 }
@@ -282,4 +282,4 @@ namespace flame::http
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index d815432..d962364 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -2,6 +2,7 @@
 #include "_consumer.h"
 #include "kafka.h"
 #include "message.h"
+#include "../coroutine_queue.h"
 
 namespace flame::kafka
 {
@@ -15,7 +16,7 @@ namespace flame::kafka
         return t;
     }
     _consumer::_consumer(php::array &config, php::array &topics)
-    : strd_(gcontroller->context_y)
+    : close_(false)
     {
         if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
         {
@@ -70,37 +71,34 @@ namespace flame::kafka
                 err);
         }
     }
-    php::object _consumer::consume(coroutine_handler& ch)
+    void _consumer::consume(coroutine_queue<php::object>& q, coroutine_handler& ch)
     {
         rd_kafka_resp_err_t err;
         rd_kafka_message_t* msg = nullptr;
+POLL_MESSAGE:
         // 由于 poll 动作本身阻塞, 为防止所有工作线被占用, 这里绑定到 strd_ 执行
         // (理论上仅占用一个协程)
-        boost::asio::post(strd_, [this, &err, &msg, &ch]()
+        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]()
         {
-            msg = rd_kafka_consumer_poll(conn_, 200);
-            if(!msg)
-            {
-                // 这里考虑关闭 consumer 的即时型, 需要在 200ms 超时后进行关闭判定
-                // 考虑到可能并行协程数量, 最长需要 concurrent * 200ms 才能完全关闭
-                err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
-            }
+            msg = rd_kafka_consumer_poll(conn_, 500);
+            if(!msg) err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
             else if (msg->err)
             {
                 err = msg->err;
                 rd_kafka_message_destroy(msg);
             }
-            else
-            {
-                err = RD_KAFKA_RESP_ERR_NO_ERROR;
-            }
+            else err = RD_KAFKA_RESP_ERR_NO_ERROR;
             ch.resume();
         });
         ch.suspend();
-        if(err == RD_KAFKA_RESP_ERR__PARTITION_EOF/* || err == RD_KAFKA_RESP_ERR__TRANSPORT*/)
+        if(err == RD_KAFKA_RESP_ERR__PARTITION_EOF)
         {
-            // TODO RD_KAFKA_RESP_ERR__TRANSPORT 是否要忽略？
-            return nullptr;
+            if(close_)
+            {
+                q.close(); // 关闭队列使对应消费协程自行退出
+                return;
+            } // 消费已被关闭
+            else goto POLL_MESSAGE;
         }
         else if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
         {
@@ -113,7 +111,8 @@ namespace flame::kafka
             php::object obj(php::class_entry<message>::entry());
             message* ptr = static_cast<message*>(php::native(obj));
             ptr->build_ex(msg); // msg 交由 message 对象管理
-            return std::move(obj);
+            q.push(std::move(obj), ch);
+            goto POLL_MESSAGE;
         }
     }
     void _consumer::commit(const php::object& obj, coroutine_handler& ch)
@@ -147,6 +146,7 @@ namespace flame::kafka
                                  (boost::format("failed to close Kafka consumer: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
                                  err);
         }
+        close_ = true;
     }
 
 } // namespace flame::kafka
diff --git a/src/kafka/_consumer.h b/src/kafka/_consumer.h
index 1961855..fef9bd5 100644
--- a/src/kafka/_consumer.h
+++ b/src/kafka/_consumer.h
@@ -2,6 +2,10 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 
+namespace flame {
+    template <class T>
+    class coroutine_queue;
+}
 namespace flame::kafka
 {
     class _consumer
@@ -10,13 +14,12 @@ namespace flame::kafka
         _consumer(php::array& config, php::array& topics);
         ~_consumer();
         void subscribe(coroutine_handler& ch);
-        php::object consume(coroutine_handler& ch);
+        void consume(coroutine_queue<php::object>& q, coroutine_handler& ch);
         void commit(const php::object& msg, coroutine_handler& ch);
         void close(coroutine_handler& ch);
     private:
         rd_kafka_t* conn_;
         rd_kafka_topic_partition_list_t* tops_;
-        // 此 strand 用于限制消费动作, 防止其堵塞所有工作线程
-        boost::asio::io_context::strand  strd_;
+        bool       close_;
     };
 } // namespace flame::kafka
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index b633428..4aa7609 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -4,6 +4,7 @@
 #include "_consumer.h"
 #include "kafka.h"
 #include "message.h"
+#include "../coroutine_queue.h"
 
 namespace flame::kafka {
 
@@ -29,29 +30,24 @@ namespace flame::kafka {
 	}
 	php::value consumer::run(php::parameters& params)
     {
-        close_ = false;
-        ex_ = EG(current_execute_data);
         cb_ = params[0];
-        
+        coroutine_handler ch_run {coroutine::current};
+        coroutine_queue<php::object> q;
         // 启动若干协程, 然后进行"并行"消费
         int count = cc_;
         for (int i = 0; i < count; ++i)
         {
             // 启动协程开始消费
-            coroutine::start(php::value([this, &count](php::parameters &params) -> php::value {
+            coroutine::start(php::value([this, &ch_run, &q, &count](php::parameters &params) -> php::value {
                 coroutine_handler ch {coroutine::current};
-                while(!close_)
+                while(true)
                 {
                     try
-                    {  
+                    {
                         // consume 本身可能出现异常，不应导致进程停止
-                        php::object msg = cs_->consume(ch);
-                        // 可能出现为 NULL 的情况
-                        if (msg.typeof(php::TYPE::NULLABLE))
-                        {
-                            continue;
-                        }
-                        cb_.call({msg});
+                        std::optional<php::object> m = q.pop(ch);
+                        if(m) cb_.call({m.value()});
+                        else break;
                     }
                     catch(const php::exception& ex)
                     {
@@ -60,30 +56,26 @@ namespace flame::kafka {
                         // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
-                if (--count == 0) // 当所有并行协程结束后, 停止消费
-                {
-                    ch_.resume();
-                }
+                if(--count == 0) ch_run.resume();
+
                 return nullptr;
             }));
         }
-        // 暂停当前消费协程 (等待消费停止)
-        ch_.reset(coroutine::current);
-        ch_.suspend();
+        cs_->consume(q, ch_run);
+        ch_run.suspend();
         return nullptr;
 	}
     php::value consumer::commit(php::parameters& params)
     {
         php::object obj = params[0];
         coroutine_handler ch {coroutine::current};
-        
+
         cs_->commit(obj, ch);
         return nullptr;
     }
 	php::value consumer::close(php::parameters& params) {
         coroutine_handler ch {coroutine::current};
         cs_->close(ch);
-        close_ = true;
         return nullptr;
 	}
-} // namespace 
+} // namespace
diff --git a/src/kafka/consumer.h b/src/kafka/consumer.h
index bc42d37..fdcea60 100644
--- a/src/kafka/consumer.h
+++ b/src/kafka/consumer.h
@@ -16,11 +16,9 @@ namespace flame::kafka
 
     private:
         std::shared_ptr<_consumer> cs_;
-        coroutine_handler          ch_;
         int                        cc_ = 8;
         php::callable              cb_;
         zend_execute_data*         ex_;
-        bool close_ = false;
 
         friend php::value consume(php::parameters &params);
     };
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index 9bc6358..f2b1480 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -1,7 +1,7 @@
 #include "../controller.h"
 #include "_connection_pool.h"
 
-namespace flame::mongodb 
+namespace flame::mongodb
 {
     _connection_pool::_connection_pool(const std::string& url)
     : guard_(gcontroller->context_y)
@@ -10,8 +10,10 @@ namespace flame::mongodb
         const bson_t* options = mongoc_uri_get_options(uri.get());
         if (!bson_has_field(options, MONGOC_URI_READPREFERENCE))
         {
-            mongoc_uri_set_option_as_utf8(uri.get(), MONGOC_URI_READPREFERENCE, "secondaryPreferred");
-        }
+			mongoc_read_prefs_t* pref = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); // secondaryPreferred
+			mongoc_uri_set_read_prefs_t(uri.get(), pref);
+			mongoc_read_prefs_destroy(pref);
+		}
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 6);
 
@@ -47,4 +49,4 @@ namespace flame::mongodb
             cb(c);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/mongodb/client.cpp b/src/mongodb/client.cpp
index c9f3b5f..9e4e95e 100644
--- a/src/mongodb/client.cpp
+++ b/src/mongodb/client.cpp
@@ -1,4 +1,5 @@
 #include "../coroutine.h"
+#include "mongodb.h"
 #include "_connection_pool.h"
 #include "client.h"
 #include "collection.h"
@@ -10,6 +11,9 @@ namespace flame::mongodb
         php::class_entry<client> class_client("flame\\mongodb\\client");
         class_client
             .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::dump>("dump", {
+                {"data", php::TYPE::ARRAY},
+            })
             .method<&client::execute>("execute",
             {
                 {"command", php::TYPE::ARRAY},
@@ -23,7 +27,7 @@ namespace flame::mongodb
             {
                 {"name", php::TYPE::STRING}
             })
-            .method<&client::__isset>("__isset", 
+            .method<&client::__isset>("__isset",
             {
                 {"name", php::TYPE::STRING}
             });
@@ -33,13 +37,18 @@ namespace flame::mongodb
     {
         return nullptr;
     }
+    php::value client::dump(php::parameters& params)
+    {
+        std::cout << bson_as_relaxed_extended_json(array2bson(params[0]).get(), nullptr) << std::endl;
+        return nullptr;
+    }
     php::value client::execute(php::parameters &params)
     {
         bool write = false;
         if (params.size() > 1) write = params[1].to_boolean();
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        
+
         php::array cmd = params[0];
         return cp_->exec(conn_, cmd, write, ch);
     }
@@ -56,4 +65,4 @@ namespace flame::mongodb
     {
         return true;
     }
-} // namespace flame::mongodb
\ No newline at end of file
+} // namespace flame::mongodb
diff --git a/src/mongodb/client.h b/src/mongodb/client.h
index 5f8b98a..594a582 100644
--- a/src/mongodb/client.h
+++ b/src/mongodb/client.h
@@ -9,6 +9,7 @@ namespace flame::mongodb
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params);
+        php::value dump(php::parameters &params);
         php::value execute(php::parameters &params);
         php::value __get(php::parameters &params);
         php::value __isset(php::parameters &params);
@@ -16,4 +17,4 @@ namespace flame::mongodb
         std::shared_ptr<_connection_pool> cp_;
         friend php::value connect(php::parameters &params);
     };
-} // namespace flame::mongodb
\ No newline at end of file
+} // namespace flame::mongodb
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index a0495b6..f814279 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -239,7 +239,7 @@ namespace flame::mongodb
         if (!pipeline.exists(0))
             throw php::exception(zend_ce_type_error, "aggregate pipeline must be a array of stages (array)");
         cmd.set("pipeline", params[0]);
-        cmd.set("cursor", php::array(0));
+        cmd.set("cursor", php::object(php::CLASS(zend_standard_class_def)));
 
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
diff --git a/src/mongodb/mongodb.cpp b/src/mongodb/mongodb.cpp
index 16b99e0..3ad889a 100644
--- a/src/mongodb/mongodb.cpp
+++ b/src/mongodb/mongodb.cpp
@@ -99,7 +99,7 @@ namespace flame::mongodb
     php::array bson2array(bson_t* v)
     {
         if(v == nullptr) return nullptr;
-        
+
         php::array doc(4);
 
         bson_iter_t i;
@@ -149,7 +149,7 @@ namespace flame::mongodb
                 case IS_ARRAY:
                 {
                     auto a = array2bson(val);
-                    if (bson_has_field(a.get(), "0"))
+                    if (bson_has_field(a.get(), "0") || bson_count_keys(a.get()) == 0)
                     {
                         bson_append_array(doc, key.c_str(), key.size(), a.get());
                     }
@@ -159,7 +159,7 @@ namespace flame::mongodb
                     }
                     break;
                 }
-                case IS_OBJECT: 
+                case IS_OBJECT:
                 {
                     php::object o = val;
                     if (o.instanceof (php_date_get_date_ce()))
@@ -178,7 +178,9 @@ namespace flame::mongodb
                     }
                     else
                     {
-                        bson_append_null(doc, key.c_str(), key.size());
+                        bson_t uninitialized;
+                        bson_append_document_begin(doc, key.c_str(), key.size(), &uninitialized);
+                        bson_append_document_end(doc, &uninitialized);
                     }
                 }
             }
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index b585f4a..9950885 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -46,15 +46,7 @@ namespace flame::mysql
             char *to = b.prepare(str.size() * 2 + 2);
             std::size_t n = 0;
             to[n++] = quote;
-            // 摘自 mysql_real_escape_string_quote() @ libmysql.c:1228 相关流程
-            if (conn->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
-            {
-                n += escape_quotes_for_mysql(conn->charset, to + 1, str.size() * 2 + 1, str.c_str(), str.size(), quote);
-            }
-            else
-            {
-                n += escape_string_for_mysql(conn->charset, to + 1, str.size() * 2 + 1, str.c_str(), str.size());
-            }
+            n += mysql_real_escape_string_quote(conn.get(), to + n, str.c_str(), str.size(), quote);
             to[n++] = quote;
             b.commit(n);
             break;
@@ -121,7 +113,7 @@ ESCAPE_FINISHED:;
                                  (boost::format("failed to query MySQL server: (%1%) %2%") % err % mysql_error(conn.get())).str(),
                                  err);
         }
-        
+
         if(rst) // 存在结果集
         {
             php::object obj(php::class_entry<result>::entry());
diff --git a/src/vendor.h b/src/vendor.h
index 38f9396..20b4e74 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.11"
+#define EXTENSION_VERSION "0.12.12"
 
 #include <cstdio>
 #include <sys/types.h>
@@ -47,6 +47,6 @@ namespace ssl = boost::asio::ssl;
 #include <amqpcpp.h>
 #include <amqpcpp/libboostasio.h>
 #include <mysql.h>
-#include <my_sys.h>
+// #include <my_sys.h>
 #include <mongoc.h>
 #include <librdkafka/rdkafka.h>

From e5677eecba1d56921c753a50c4b22fa879462da2 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 27 Feb 2019 18:14:44 +0800
Subject: [PATCH 065/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9Afind=5Fand?=
 =?UTF-8?q?=5F*=20=E7=AE=80=E5=8C=96=E5=87=BD=E6=95=B0=EF=BC=9B=E4=BC=98?=
 =?UTF-8?q?=E5=8C=96=EF=BC=9Amongodb=20=E5=91=BD=E4=BB=A4=E6=89=A7?=
 =?UTF-8?q?=E8=A1=8C=E6=96=B9=E5=BC=8F=EF=BC=9B=E4=BF=AE=E6=AD=A3=EF=BC=9A?=
 =?UTF-8?q?cursor=20=E4=B8=8E=E5=AF=B9=E5=BA=94=20server=20=E7=BB=91?=
 =?UTF-8?q?=E5=AE=9A=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mongodb/_connection_base.cpp |  49 +++++++----
 src/mongodb/_connection_base.h   |  10 ++-
 src/mongodb/_connection_lock.cpp |  21 +++--
 src/mongodb/_connection_lock.h   |   2 +
 src/mongodb/client.cpp           |  10 ++-
 src/mongodb/collection.cpp       | 135 ++++++++++++++++++++++++++++---
 src/mongodb/collection.h         |   9 ++-
 src/mongodb/cursor.cpp           |   1 +
 src/mongodb/cursor.h             |   3 +
 9 files changed, 197 insertions(+), 43 deletions(-)

diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index 1d3f06b..56c81ae 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -7,24 +7,44 @@
 namespace flame::mongodb
 {
     void _connection_base::fake_deleter(bson_t *doc) {}
-    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, bool write, coroutine_handler &ch)
+    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, int type, coroutine_handler &ch)
     {
         std::shared_ptr<bson_t> cmd = array2bson(pcmd);
         // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏
         //（实际对 reply 的使用流程会重新初始化 reply 导致其值被至于 stack 上，导致此处 heap 内存泄漏）
-        bson_t reply;
-        std::shared_ptr<bson_t> rep(&reply, bson_destroy);
+        bson_t reply, option = BSON_INITIALIZER;
+        std::shared_ptr<bson_t> rep(&reply, bson_destroy), opt(&option, bson_destroy);
         std::shared_ptr<bson_error_t> err = std::make_shared<bson_error_t>();
+        std::uint32_t server_id;
         int  rok = 0;
-        boost::asio::post(gcontroller->context_y, [conn, write, &cmd, &rep, &err, &rok, &ch] () {
-            const mongoc_uri_t *uri = mongoc_client_get_uri(conn.get());
-            if(write)
-            {
-                rok = mongoc_client_write_command_with_opts(conn.get(), mongoc_uri_get_database(uri), cmd.get(), nullptr, rep.get(), err.get());
+
+        boost::asio::post(gcontroller->context_y, [conn, type, &server_id, &cmd, &rep, &opt, &err, &rok, &ch] () {
+            mongoc_server_description_t* server;
+            switch(type) {
+            case COMMAND_READ:
+                server = mongoc_client_select_server(conn.get(), false, nullptr, nullptr);
+            case COMMAND_RAW:
+            case COMMAND_WRITE:
+            case COMMAND_READ_WRITE:
+            default:
+                server = mongoc_client_select_server(conn.get(), true, nullptr, nullptr);
             }
-            else
-            {
-                rok = mongoc_client_read_command_with_opts(conn.get(), mongoc_uri_get_database(uri), cmd.get(), mongoc_uri_get_read_prefs_t(uri), nullptr, rep.get(), err.get());
+            server_id = mongoc_server_description_id(server);
+            mongoc_server_description_destroy(server);
+            bson_append_int32(opt.get(), "serverId", 8, server_id);
+            switch(type) {
+            case COMMAND_READ:
+                rok = mongoc_client_read_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+                break;
+            case COMMAND_WRITE:
+                rok = mongoc_client_write_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), opt.get(), rep.get(), err.get());
+                break;
+            case COMMAND_READ_WRITE:
+                rok = mongoc_client_read_write_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+                break;
+            case COMMAND_RAW:
+            default:
+                rok = mongoc_client_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
             }
             ch.resume();
         });
@@ -40,10 +60,11 @@ namespace flame::mongodb
             php::object obj{php::class_entry<cursor>::entry()};
             auto ptr = static_cast<cursor *>(php::native(obj));
             ptr->cl_.reset(new _connection_lock(conn));
+
             ptr->cs_.reset(
-                mongoc_cursor_new_from_command_reply_with_opts(conn.get(), rep.get(), nullptr),
+                mongoc_cursor_new_from_command_reply_with_opts(conn.get(), rep.get(), opt.get()),
                 mongoc_cursor_destroy);
-            // cursor 实际会窃取 rep 对应的 bson_t 结构; 
+            // cursor 实际会窃取 rep 对应的 bson_t 结构;
             // 按文档说会被上面函数 "销毁"
             // 参考: http://mongoc.org/libmongoc/current/mongoc_cursor_new_from_command_reply_with_opts.html
             *std::get_deleter<void (*)(bson_t*)>(rep) = fake_deleter;
@@ -66,4 +87,4 @@ namespace flame::mongodb
             return std::move(r);
         }
     }
-} // namespace flame::mongodb
\ No newline at end of file
+} // namespace flame::mongodb
diff --git a/src/mongodb/_connection_base.h b/src/mongodb/_connection_base.h
index 07bc2ea..1a3c6aa 100644
--- a/src/mongodb/_connection_base.h
+++ b/src/mongodb/_connection_base.h
@@ -6,9 +6,15 @@ namespace flame::mongodb {
     class _connection_base
     {
     public:
+        enum command_type_t {
+            COMMAND_RAW,
+            COMMAND_READ,
+            COMMAND_WRITE,
+            COMMAND_READ_WRITE,
+        };
         virtual std::shared_ptr<mongoc_client_t> acquire(coroutine_handler& ch) = 0;
-        php::value exec(std::shared_ptr<mongoc_client_t> conn, php::array& cmd, bool write, coroutine_handler& ch);
+        php::value exec(std::shared_ptr<mongoc_client_t> conn, php::array& cmd, int type, coroutine_handler& ch);
 
         static void fake_deleter(bson_t *doc);
     };
-}
\ No newline at end of file
+}
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
index b079fcf..bb1c9da 100644
--- a/src/mongodb/_connection_lock.cpp
+++ b/src/mongodb/_connection_lock.cpp
@@ -5,8 +5,12 @@
 namespace flame::mongodb
 {
     _connection_lock::_connection_lock(std::shared_ptr<mongoc_client_t> c)
-        : conn_(c)
+    : conn_(c)
+    , guard_(gcontroller->context_y)
     {
+
+    }
+    _connection_lock::~_connection_lock() {
         
     }
     std::shared_ptr<mongoc_client_t> _connection_lock::acquire(coroutine_handler &ch)
@@ -18,21 +22,16 @@ namespace flame::mongodb
         bool has = false;
         auto err = std::make_shared<bson_error_t>();
         const bson_t *doc;
-        boost::asio::post(gcontroller->context_y, [this, &cs, &ch, &has, &err, &doc]() {
-            if(!mongoc_cursor_next(cs.get(), &doc))
-            {
-                has = mongoc_cursor_error(cs.get(), err.get());
-            }
+        boost::asio::post(guard_, [this, &cs, &ch, &has, &err, &doc]() {
+            if(!mongoc_cursor_next(cs.get(), &doc)) has = mongoc_cursor_error(cs.get(), err.get());
             ch.resume();
         });
         ch.suspend();
-        if (has) // 发生了错误
-        {
-            throw php::exception(zend_ce_exception,
+        // 发生了错误
+        if (has) throw php::exception(zend_ce_exception,
                     (boost::format("failed to fetch document: (%1%) %2%") % err->code % err->message).str(),
                     err->code);
-        }
         // 文档 doc 仅为 "引用" 流程
         return bson2array(const_cast<bson_t*>(doc));
     }
-} // namespace flame::mongodb
\ No newline at end of file
+} // namespace flame::mongodb
diff --git a/src/mongodb/_connection_lock.h b/src/mongodb/_connection_lock.h
index 72b2b46..573b8fc 100644
--- a/src/mongodb/_connection_lock.h
+++ b/src/mongodb/_connection_lock.h
@@ -8,9 +8,11 @@ namespace flame::mongodb
     {
     public:
         _connection_lock(std::shared_ptr<mongoc_client_t> c);
+        ~_connection_lock();
         std::shared_ptr<mongoc_client_t> acquire(coroutine_handler &ch) override;
         php::array fetch(std::shared_ptr<mongoc_cursor_t> cs, coroutine_handler &ch);
     private:
+        boost::asio::io_context::strand guard_; // 防止对 cursor 的并行访问
         std::shared_ptr<mongoc_client_t> conn_;
     };
 } // namespace flame::mongodb
diff --git a/src/mongodb/client.cpp b/src/mongodb/client.cpp
index 9e4e95e..77c6800 100644
--- a/src/mongodb/client.cpp
+++ b/src/mongodb/client.cpp
@@ -10,6 +10,10 @@ namespace flame::mongodb
     {
         php::class_entry<client> class_client("flame\\mongodb\\client");
         class_client
+            .constant({"COMMAND_RAW", _connection_base::COMMAND_RAW})
+            .constant({"COMMAND_READ", _connection_base::COMMAND_READ})
+            .constant({"COMMAND_READ_WRITE", _connection_base::COMMAND_READ_WRITE})
+            .constant({"COMMAND_WRITE", _connection_base::COMMAND_WRITE})
             .method<&client::__construct>("__construct", {}, php::PRIVATE)
             .method<&client::dump>("dump", {
                 {"data", php::TYPE::ARRAY},
@@ -17,7 +21,7 @@ namespace flame::mongodb
             .method<&client::execute>("execute",
             {
                 {"command", php::TYPE::ARRAY},
-                {"write", php::TYPE::BOOLEAN, false, true},
+                {"write", php::TYPE::INTEGER, false, true},
             })
             .method<&client::__get>("collection",
             {
@@ -44,8 +48,8 @@ namespace flame::mongodb
     }
     php::value client::execute(php::parameters &params)
     {
-        bool write = false;
-        if (params.size() > 1) write = params[1].to_boolean();
+        int write = _connection_base::COMMAND_RAW;
+        if (params.size() > 1) write = params[1].to_integer();
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
 
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index f814279..072bb58 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -16,17 +16,47 @@ namespace flame::mongodb
                 {"data", php::TYPE::ARRAY},
                 {"ordered", php::TYPE::BOOLEAN, false, true} // true
             })
+            .method<&collection::insert>("insert_many",
+            {
+                {"data", php::TYPE::ARRAY},
+                {"ordered", php::TYPE::BOOLEAN, false, true} // true
+            })
+            .method<&collection::insert>("insert_one",
+            {
+                {"data", php::TYPE::ARRAY},
+            })
             .method<&collection::delete_>("delete",
             {
                 {"query", php::TYPE::ARRAY},
                 {"limit", php::TYPE::INTEGER, false, true}, // 0
             })
+            .method<&collection::delete_>("delete_many",
+            {
+                {"query", php::TYPE::ARRAY},
+                {"limit", php::TYPE::INTEGER, false, true}, // 0
+            })
+            .method<&collection::delete_>("delete_one",
+            {
+                {"query", php::TYPE::ARRAY},
+            })
             .method<&collection::update>("update",
             {
                 {"query", php::TYPE::ARRAY},
                 {"update", php::TYPE::ARRAY},
                 {"upsert", php::TYPE::BOOLEAN, false, true}, // false
             })
+            .method<&collection::update>("update_many",
+            {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"upsert", php::TYPE::BOOLEAN, false, true}, // false
+            })
+            .method<&collection::update_one>("update_one",
+            {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"upsert", php::TYPE::BOOLEAN, false, true}, // false
+            })
             .method<&collection::find>("find",
             {
                 {"filter", php::TYPE::ARRAY},
@@ -34,7 +64,12 @@ namespace flame::mongodb
                 {"sort", php::TYPE::ARRAY, false, true},
                 {"limit", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&collection::one>("one",
+            .method<&collection::find_one>("one", // 兼容原 API 名称定义
+            {
+                {"filter", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::find_one>("find_one",
             {
                 {"filter", php::TYPE::ARRAY},
                 {"sort", php::TYPE::ARRAY, false, true},
@@ -52,6 +87,22 @@ namespace flame::mongodb
             .method<&collection::aggregate>("aggregate",
             {
                 {"pipeline", php::TYPE::ARRAY},
+            })
+            .method<&collection::find_and_delete>("find_and_delete",
+            {
+                {"query", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+                {"upsert", php::TYPE::BOOLEAN, false, true},
+                {"fields", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::find_and_update>("find_and_update",
+            {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+                {"upsert", php::TYPE::BOOLEAN, false, true},
+                {"fields", php::TYPE::ARRAY, false, true},
+                {"new", php::TYPE::BOOLEAN, false, true},
             });
         ext.add(std::move(class_collection));
     }
@@ -72,7 +123,7 @@ namespace flame::mongodb
         }
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        return cp_->exec(conn_, cmd, true, ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
     }
     php::value collection::delete_(php::parameters &params)
     {
@@ -92,7 +143,10 @@ namespace flame::mongodb
         cmd.set("deletes", deletes);
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        return cp_->exec(conn_, cmd, true, ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
+    }
+    php::value collection::delete_one(php::parameters& params) {
+        return call("delete", {params[0], 1});
     }
     php::value collection::update(php::parameters &params)
     {
@@ -110,7 +164,24 @@ namespace flame::mongodb
         cmd.set("updates", updates);
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        return cp_->exec(conn_, cmd, true, ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
+    }
+    php::value collection::update_one(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("update", name_);
+        php::array updates(2), updateo(4);
+        updateo.set("q", params[0]);
+        updateo.set("u", params[1]);
+        if (params.size() > 2)
+        {
+            updateo.set("upsert", params[2].to_boolean());
+        }
+        // updateo.set("multi", false);
+        updates.set(0, updateo);
+        cmd.set("updates", updates);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
     }
     php::value collection::find(php::parameters &params)
     {
@@ -176,9 +247,9 @@ namespace flame::mongodb
         }
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        return cp_->exec(conn_, cmd, false, ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
     }
-    php::value collection::one(php::parameters &params)
+    php::value collection::find_one(php::parameters &params)
     {
         php::array cmd(8);
         cmd.set("find", name_);
@@ -190,7 +261,7 @@ namespace flame::mongodb
         cmd.set("limit", 1);
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        php::object cs = cp_->exec(conn_, cmd, true, ch);
+        php::object cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
         assert(cs.instanceof(php::class_entry<cursor>::entry()));
         return cs.call("fetch_row");
     }
@@ -211,7 +282,7 @@ namespace flame::mongodb
 
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        php::object cs = cp_->exec(conn_, cmd, true, ch);
+        php::object cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
         assert(cs.instanceof (php::class_entry<cursor>::entry()));
         php::array row = cs.call("fetch_row");
         if(row.empty()) {
@@ -228,7 +299,7 @@ namespace flame::mongodb
 
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        php::array cs = cp_->exec(conn_, cmd, true, ch);
+        php::array cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
         return cs.get("n");
     }
     php::value collection::aggregate(php::parameters &params)
@@ -243,6 +314,50 @@ namespace flame::mongodb
 
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        return cp_->exec(conn_, cmd, false, ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
+    }
+    php::value collection::find_and_delete(php::parameters& params) {
+        php::array cmd(8);
+        cmd.set("query", params[0]);
+        cmd.set("remove", true);
+        if(params.size() > 1) {
+            php::value f = params[1];
+            if(f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
+        }
+        if(params.size() > 2) {
+            cmd.set("upsert", params[2].to_boolean());
+        }
+        if(params.size() > 3) {
+            php::value f = params[3];
+            if(f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
+        }
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ_WRITE, ch);
+    }
+    php::value collection::find_and_update(php::parameters& params) {
+        php::array cmd(8);
+        cmd.set("query", params[0]);
+        if(params.size() > 1) {
+            php::value f = params[1];
+            if(f.typeof(php::TYPE::ARRAY)) cmd.set("update", f);
+        }
+        if(params.size() > 2) {
+            php::value f = params[2];
+            if(f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
+        }
+        if(params.size() > 3) {
+            cmd.set("upsert", params[3].to_boolean());
+        }
+        if(params.size() > 4) {
+            php::value f = params[4];
+            if(f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
+        }
+        if(params.size() > 5) {
+            cmd.set("new", params[5].to_boolean());
+        }
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ_WRITE, ch);
     }
 } // namespace flame::mongodb
diff --git a/src/mongodb/collection.h b/src/mongodb/collection.h
index e0a346f..ae553d3 100644
--- a/src/mongodb/collection.h
+++ b/src/mongodb/collection.h
@@ -13,16 +13,19 @@ namespace flame::mongodb
         }
         php::value insert(php::parameters &params);
         php::value delete_(php::parameters &params);
+        php::value delete_one(php::parameters& params);
         php::value update(php::parameters &params);
+        php::value update_one(php::parameters& params);
         php::value find(php::parameters &params);
-        php::value one(php::parameters &params);
+        php::value find_one(php::parameters &params);
         php::value get(php::parameters &params);
         php::value count(php::parameters &params);
         php::value aggregate(php::parameters &params);
-
+        php::value find_and_update(php::parameters& params);
+        php::value find_and_delete(php::parameters& params);
     private:
         std::shared_ptr<_connection_pool> cp_;
         php::string name_;
         friend class client;
     };
-} // namespace flame::mongodb
\ No newline at end of file
+} // namespace flame::mongodb
diff --git a/src/mongodb/cursor.cpp b/src/mongodb/cursor.cpp
index 1549d4f..55133e7 100644
--- a/src/mongodb/cursor.cpp
+++ b/src/mongodb/cursor.cpp
@@ -9,6 +9,7 @@ namespace flame::mongodb
         php::class_entry<cursor> class_cursor("flame\\mongodb\\cursor");
         class_cursor
             .method<&cursor::__construct>("__construct", {}, php::PRIVATE)
+            .method<&cursor::__destruct>("__destruct")
             .method<&cursor::fetch_row>("fetch_row")
             .method<&cursor::fetch_all>("fetch_all");
         ext.add(std::move(class_cursor));
diff --git a/src/mongodb/cursor.h b/src/mongodb/cursor.h
index 32cf8d8..fa73402 100644
--- a/src/mongodb/cursor.h
+++ b/src/mongodb/cursor.h
@@ -9,6 +9,9 @@ namespace flame::mongodb {
 		php::value __construct(php::parameters& params) { // 私有
 			return nullptr;
 		}
+		php::value __destruct(php::parameters& params) {
+			return nullptr;
+		}
 		php::value fetch_row(php::parameters& params);
 		php::value fetch_all(php::parameters& params);
 	private:

From 20162c5a885fcdd6122c914c26698ea26f302630 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 27 Feb 2019 18:15:05 +0800
Subject: [PATCH 066/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E7=BA=BF?=
 =?UTF-8?q?=E7=A8=8B=E6=B1=A0=EF=BC=8C=E8=BF=9E=E6=8E=A5=E6=B1=A0=E6=95=B0?=
 =?UTF-8?q?=E9=87=8F=E8=B0=83=E6=95=B4=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_worker.cpp        | 8 ++++----
 src/mongodb/_connection_pool.cpp | 2 +-
 src/mysql/_connection_pool.cpp   | 8 ++++----
 src/redis/_connection_pool.cpp   | 4 ++--
 4 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index 2e6a396..ddf62d9 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -6,11 +6,11 @@ namespace flame
 {
     controller_worker::controller_worker()
     {
-        
+
     }
     void controller_worker::initialize(const php::array &options)
     {
-        
+
     }
     void controller_worker::await_signal() {
         signal_->async_wait([this] (const boost::system::error_code &error, int sig)
@@ -42,7 +42,7 @@ namespace flame
 
         // 子进程的启动过程:
         // 2. 启动线程池, 并使用线程池运行 context_y
-        thread_.resize(8);
+        thread_.resize(4);
         for (int i = 0; i < thread_.size(); ++i)
         {
             thread_[i] = new std::thread([this] {
@@ -60,4 +60,4 @@ namespace flame
             delete thread_[i];
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index f2b1480..b3ec2b0 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -15,7 +15,7 @@ namespace flame::mongodb
 			mongoc_read_prefs_destroy(pref);
 		}
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
-        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 6);
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 8);
 
         p_ = mongoc_client_pool_new(uri.get());
     }
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index d94601e..408278b 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -3,9 +3,9 @@
 
 namespace flame::mysql
 {
-        
+
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(3), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(2), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
         , charset_(boost::logic::indeterminate)
     {
         if(url_.port < 10) url_.port = 3306;
@@ -13,7 +13,7 @@ namespace flame::mysql
             url_.query["charset"] = "utf8";
         }
     }
-    
+
     _connection_pool::~_connection_pool()
     {
         while (!conn_.empty())
@@ -22,7 +22,7 @@ namespace flame::mysql
             conn_.pop_front();
         }
     }
-    
+
     std::shared_ptr<MYSQL> _connection_pool::acquire(coroutine_handler& ch)
     {
         std::shared_ptr<MYSQL> conn;
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 13e2c2f..b4edd3b 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -5,7 +5,7 @@ namespace flame::redis
 {
 
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(3), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(1), max_(3), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     {
         if(url_.port < 10) url_.port = 6379;
     }
@@ -71,7 +71,7 @@ namespace flame::redis
         // 恢复, 已经填充连接
         return conn;
     }
-    
+
     php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler& ch)
     {
         std::string ss = format(name, argv);

From 149626992fe030be70f95b3672fd67b9e3856917 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 1 Mar 2019 21:03:38 +0800
Subject: [PATCH 067/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20hash/encoding/com?=
 =?UTF-8?q?press=20=E5=8C=85=E8=A3=85=E5=86=85=E9=83=A8=E5=B7=B2=E5=AD=98?=
 =?UTF-8?q?=E5=9C=A8=E7=9A=84=E7=AE=97=E6=B3=95;=20=E5=B0=8F=E4=BC=98?=
 =?UTF-8?q?=E5=8C=96=E8=8B=A5=E5=B9=B2;=20=E8=A1=A5=E5=85=85=E6=96=87?=
 =?UTF-8?q?=E6=A1=A3;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                     |  3 ++-
 CMakeLists.txt                 |  1 -
 README.md                      |  9 ++++---
 doc/compress.php               | 13 +++++++++
 doc/core.php                   | 11 +++++++-
 doc/encoding.php               | 13 +++++++++
 doc/hash.php                   | 21 +++++++++++++++
 doc/http.php                   | 12 +++++----
 doc/kafka.php                  | 21 +++++++--------
 doc/mongodb.php                | 27 +++++++++++++++++--
 doc/mysql.php                  | 41 +++++++++++++++++-----------
 doc/redis.php                  | 13 +++++----
 doc/time.php                   |  3 +--
 src/compress/compress.cpp      | 41 ++++++++++++++++++++++++++++
 src/compress/compress.h        |  6 +++++
 src/core.cpp                   | 10 ++++---
 src/coroutine_queue.h          | 10 ++++---
 src/encoding/encoding.cpp      | 31 +++++++++++++++++++++
 src/encoding/encoding.h        |  6 +++++
 src/flame.cpp                  |  6 +++++
 src/hash/hash.cpp              | 49 ++++++++++++++++++++++++++++++++++
 src/hash/hash.h                |  6 +++++
 src/kafka/kafka.cpp            | 27 ++++++-------------
 src/mongodb/date_time.cpp      |  8 ++++--
 src/mongodb/date_time.h        |  3 ++-
 src/mysql/_connection_base.cpp |  9 ++++---
 src/mysql/_connection_base.h   |  6 +++--
 src/mysql/client.cpp           | 10 ++++---
 src/mysql/client.h             |  3 ++-
 src/mysql/mysql.cpp            | 37 ++++++++-----------------
 src/queue.cpp                  | 10 ++++---
 src/queue.h                    |  5 ++--
 src/rabbitmq/consumer.cpp      |  5 ++--
 src/rabbitmq/consumer.h        |  1 -
 src/redis/client.cpp           |  4 +--
 src/redis/client.h             |  2 +-
 src/time/time.cpp              | 15 ++++++-----
 src/time/time.h                |  5 ++--
 src/vendor.h                   |  9 +++++--
 test/compress_1.php            | 15 +++++++++++
 test/encoding_1.php            | 11 ++++++++
 test/hash_1.php                | 14 ++++++++++
 42 files changed, 421 insertions(+), 131 deletions(-)
 create mode 100644 doc/compress.php
 create mode 100644 doc/encoding.php
 create mode 100644 doc/hash.php
 create mode 100644 src/compress/compress.cpp
 create mode 100644 src/compress/compress.h
 create mode 100644 src/encoding/encoding.cpp
 create mode 100644 src/encoding/encoding.h
 create mode 100644 src/hash/hash.cpp
 create mode 100644 src/hash/hash.h
 create mode 100644 test/compress_1.php
 create mode 100644 test/encoding_1.php
 create mode 100644 test/hash_1.php

diff --git a/.gitignore b/.gitignore
index ca74814..06dbfd8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .ccls-cache
 .vscode
-build
+debug
+release
 compile_commands.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a964ed4..82bff46 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,7 +25,6 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
 set(VENDOR_PHPEXT /data/vendor/phpext-1.1.2)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
-# set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
 set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
diff --git a/README.md b/README.md
index 166833b..c40f2e7 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
 #### Boost
 
 ``` Bash
-./bootstrap.sh --prefix=/data/vendor/boost-1.69.0~/
+./bootstrap.sh --prefix=/data/vendor/boost-1.69.0
 ./b2 -j4 --prefix=/data/vendor/boost-1.69.0 cxxflags="-fPIC" variant=release link=static threading=multi install
 ```
 
@@ -42,7 +42,7 @@ make install
 make install
 ```
 
-#### mysql-connector-c v8.0.15
+#### mysql-connector-c
 ``` Bash
 mkdir -p /data/vendor/mysqlc-8.0.15/lib
 cp -R mysql-8.0.15-linux-glibc2.12-x86_64/include /data/vendor/mysqlc-8.0.15
@@ -53,7 +53,7 @@ cp mysql-8.0.15-linux-glibc2.12-x86_64/lib/libmysqlclient.a /data/vendor/mysqlc-
 ``` Bash
 mkdir stage && cd stage
 CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
-# with openssl 1.0 in another path
+# 由于 mysql-connector-c 官方提供的版本需要 openssl-1.0 此处也须统一版本
 # -DOPENSSL_INCLUDE_DIR=/usr/include/openssl-1.0 -DOPENSSL_SSL_LIBRARY=/usr/lib/openssl-1.0/libssl.so -DOPENSSL_CRYPTO_LIBRARY=/usr/lib/openssl-1.0/libcrypto.so
 make
 make install
@@ -74,6 +74,9 @@ make install
 make
 make install
 rm /data/vendor/rdkafka-1.0.0/lib/*.so*
+cp src/snappy.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
+cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
+cp src/xxhash.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
 ```
 
 #### HttpParser
diff --git a/doc/compress.php b/doc/compress.php
new file mode 100644
index 0000000..fcbdb2f
--- /dev/null
+++ b/doc/compress.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * 补充部分在框架中存在的压缩算法
+ */
+namespace flame\compress;
+/**
+ * snappy 压缩（与 https://github.com/kjdev/php-ext-snappy 兼容）
+ */
+function snappy_compress(string $data):string {}
+/**
+ * snappy 解压（与 https://github.com/kjdev/php-ext-snappy 兼容）
+ */
+function snappy_uncompress(string $data):string {}
diff --git a/doc/core.php b/doc/core.php
index b1c54fe..607122b 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -23,13 +23,18 @@
  */
 function init($process_name, $options = []) {}
 /**
- *  启动协程(运行对应回调函数)
+ * 启动协程(运行对应回调函数)
+ * 注意：框架使用 固定栈大小 方式进行协程调度（相对效率较高）；但为满足大部分需求，栈空间占用相对（初始值）较高；不应启动过多的协程；
  */
 function go(callable $cb) {}
 /**
  * 获取一个当前协程 ID 标识
  */
 function co_id():int {}
+/**
+ * 获取运行中的协程数量
+ */
+function co_count():int {}
 /**
  * 框架调度, 上述协程会在框架开始调度运行后启动
  * 注意：协程异步调度需要 run() 才能启动执行；
@@ -73,6 +78,10 @@ function pop() {}
      * 原则上仅能在生产者方向关闭队列;
      */
     function close() {}
+    /**
+     * 队列是否已关闭
+     */
+    function is_closed(): boolean {}
 }
 /**
  * 协程互斥量（锁）
diff --git a/doc/encoding.php b/doc/encoding.php
new file mode 100644
index 0000000..974aa35
--- /dev/null
+++ b/doc/encoding.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * 补充部分在框架中存在的编码功能
+ */
+namespace flame\encoding;
+/**
+ * 将 PHP 数组转换为 BSON 格式，并返回实际 BSON 的原始二进制数据
+ */
+function bson_encode(array $data):string {}
+/**
+ * 还原由上述 `bson_encode` 生成的原始二进制数据，返回对应的数组
+ */
+function bson_decode(string $data):array {}
diff --git a/doc/hash.php b/doc/hash.php
new file mode 100644
index 0000000..2ff9919
--- /dev/null
+++ b/doc/hash.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * 补充部分在框架中存在的哈希算法函数
+ */
+namespace flame\hash;
+/**
+ * 默认情况 kafka 内部使用的哈希算法（返回 )
+ * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
+ */
+function murmur2(string $data, bool $raw = false): mixed {}
+/**
+ * 一种效率极高且哈希结果理想度高的哈希算法（返回 uint64)
+ * @param int $seed 用于对哈希结果进行预期内的调整
+ * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
+ */
+function xxh64(string $data, int $seed = 0, bool $raw = false): mixed {}
+/**
+ * 根据 EMCA 定义实现的 crc64 哈希（与 Golang 对应 hash/crc64 ECMA 计算值一直）
+ * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
+ */
+function crc64(string $data, bool $raw = false): mixed {}
diff --git a/doc/http.php b/doc/http.php
index 7db1514..d97f5b2 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -1,7 +1,9 @@
 <?php
 /**
  * 提供基本的 HTTP 客户端、服务端封装；
- * 注意：暂不支持作为 HTTPS 客户端、服务端；
+ * 注意：
+ * 1. 暂不支持作为 HTTPS 客户端、服务端；请使用 CURL 库或 NGINX 反向代理；
+ * 2. 默认状态客户端对请求连接进行 KeepAlive 复用；若需要使用短连接，请自行制定 Connection: close 头；
  */
 namespace flame\http;
 
@@ -74,6 +76,7 @@ class client_request {
      */
     public $url;
     /**
+     * 请求头；可通过指定 "Connection" => "close" 防止连接复用；
      * @property array
      */
     public $header;
@@ -179,7 +182,6 @@ function head(string $path, callable $cb):server {}
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
     function options(string $path, callable $cb):server {}
-    
     /**
      * 启动服务器, 监听请求并执行对应回调
      * 注意: 运行服务器将阻塞当前协程;
@@ -212,7 +214,7 @@ class server_request {
     public $query;
     /**
      * 请求头; 所有头信息字段名被转换为**小写**形式;
-     * 暂不支持多项同名 Header 字段;
+     * 注意：暂不支持多项同名 Header 字段;
      * @property array
      */
     public $header;
@@ -292,7 +294,7 @@ function set_cookie(string $name, string $value = null, int $expire = 0, string
     /**
      * 返回响应头, 用于启用 Transfer-Encoding: chunked 模式, 准备持续响应数据;
      * @param int $status 当参数存在时, 覆盖上述 $res->status 属性响应码;
-     * 
+     *
      * Transfer-Encoding: chunked 模式可用于 EventSource 等持续响应;
      */
     function write_header($status = 0) {}
@@ -311,4 +313,4 @@ function end(string $chunk = null) {}
      * 注意: 此功能一般仅用于调试或少量文件访问使用, 大量生产环境请考虑使用 nginx 代为处理静态文件;
      */
     function file(string $root, string $path) {}
-}
\ No newline at end of file
+}
diff --git a/doc/kafka.php b/doc/kafka.php
index 678c57f..d3e5c04 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -3,15 +3,14 @@
  * 提供 Kafka 基本生产消费功能封装
  */
 namespace flame\kafka;
-
 /**
- * @param array $config 基本 Kafka 配置, 一下两个选项必要:
+ * @param array $config 基本 Kafka 配置, 以下两个选项必要:
  *  * "bootstrap.servers" - string 服务器地址(9092);
  *  * "group.id" - string 消费组名称(同一消费组共享消费进度);
  * 可以使用一个自定义的的参数:
- *  * "concurrent" - 控制并行消费协程数量, 默认为 8 (数值类型);
- * @see 其他可配置选项均为字符串类型(非字符串会被强制转换), 请参考: 
- *  https://github.com/edenhill/librdkafka/blob/v0.11.6/CONFIGURATION.md
+ *  * "concurrent" - 控制并行消费协程数量, 默认为 8 (数值类型)，上限为 256;
+ * @see 其他可配置选项均为字符串类型(非字符串会被强制转换), 请参考:
+ *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
  * @param array $topics 待消费的数据源 TOPIC 名称列表
  */
 function consume(array $config, array $topics) {}
@@ -20,8 +19,8 @@ function consume(array $config, array $topics) {}
  * @param array $config 基本 Kafka 配置, 一下两个选项必要:
  *  * "bootstrap.servers" - 服务器地址(9092);
  *  * "group.id" - 消费组名称(同一消费组共享消费进度);
- * @see 其他可配置选项请参考: 
- *  https://github.com/edenhill/librdkafka/blob/v0.11.6/CONFIGURATION.md
+ * @see 其他可配置选项请参考:
+ *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
  * @param array $topics 待生产的目标 TOPIC 名称列表
  */
 function produce(array $config, array $topics) {}
@@ -36,7 +35,7 @@ class consumer {
      */
     function run(callable $cb) {}
     /**
-     * 手动提交消息(的偏移), 一般需要将 "enable.auto.commit" 设为 "false" 
+     * 手动提交消息(的偏移), 一般需要将 "enable.auto.commit" 设为 "false"
      * @see consume();
      */
     function commit(message $msg) {}
@@ -52,15 +51,15 @@ function close() {}
 class producer {
     /**
      * 生产消息
-     * 注意: 此函数会将消息放入"生产队列", 并通过后台线程传输; 
+     * 注意: 此函数会将消息放入"生产队列", 并通过后台线程传输;
      * (为保证完成, 请使用 `flush()`)
      * @see producer::flush()
      * @param string $topic 生成目标 TOPIC 名称, 必须是 待生产 $topics 数组中的一个;
      * @see produce()
-     * @param mixed $mssage 若为 message 类型的对象, 则后续参数无效; 
+     * @param mixed $mssage 若为 message 类型的对象, 则后续参数无效;
      *  否则为现场 message 对象的 payload 数据;
      * @see class message;
-     * @param string $key 
+     * @param string $key
      * @param array $header
      */
     function publish(string $topic, mixed $message, string $key = null, array $header = []) {}
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 46a498a..cec1d25 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -1,10 +1,11 @@
 <?php
 /**
- * 提供基本的 MongoDB 基本客户端封装
+ * 提供基本的 MongoDB 基本客户端封装，使用连接池维护与服务器连接
  * 注意:
  * 1. 同一协程中连续进行 MongoDB 查询操作, 且游标不读取数据不销毁 (或 不读取完不销毁) 可能导致进程死锁; (请将游标读取完毕 或 主动 unset 释放游标对象)
  * 2. 对应 MongoDB ObjectID 映射类型 class flame\mongodb\object_id;
  * 3. 对应 MongoDB DateTime 映射类型 class flame\mongodb\date_time;
+ * 4. 单客户端内连接池上限大小为 8（单进程）；
  */
 namespace flame\mongodb;
 
@@ -52,6 +53,8 @@ class collection {
      *  )
      */
     function insert(array $data, bool $ordered = true):array {}
+    function insert_many(array $data, bool $ordered = true):array {}
+    function insert_one(array $data):array {}
     /**
      * 执行删除
      * @return array 形式同 insert() 类似：
@@ -62,6 +65,8 @@ function insert(array $data, bool $ordered = true):array {}
      *  )
      */
     function delete(array $query, int $limit = 0):array {}
+    function delete_many(array $query, int $limit = 0):array {}
+    function delete_one(array $query):array {}
     /**
      * 执行更新动作
      * @param array $update 一般使用类似如下形式进行更新设置：
@@ -74,18 +79,23 @@ function delete(array $query, int $limit = 0):array {}
      *  )
      */
     function update(array $query, array $update, $upsert = false):array {}
+    function update_many(array $query, array $update, $upsert = false):array {}
+    function update_one(array $query, array $update, $upsert = false):array {}
     /**
      * 执行指定查询，返回游标对象
      * @param array $sort 一般使用如下形式表达排序：
      *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
-     * @param mixed $limit 可以为 int 设置 `limit` 值，或 array 设置 `[$skip, $limit]` 
+     * @param mixed $limit 可以为 int 设置 `limit` 值，或 array 设置 `[$skip, $limit]`
      */
     function find(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {}
+    function find_many(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {}
+
     /**
      * 查询并返回单个文档
      * @param array $sort 一般使用如下形式表达排序：
      *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
      */
+    function find_one(array $query, array $sort = null): array {}
     function one(array $query, array $sort = null): array {}
     /**
      * 查询并返回单个文档的指定字段值
@@ -102,6 +112,15 @@ function count(array $query): int {}
      * 请参考 https://docs.mongodb.com/master/reference/operator/aggregation-pipeline/ 编写；
      */
     function aggregate(array $pipeline): cursor {}
+    /**
+     * 查找满足条件的第一个文档，删除并将其返回
+     */
+    function find_and_delete(array $query, array $sort = null, $upsert = false, $fields = null) {}
+    /**
+     * 查找满足条件的第一个文档，对其进行更新，并返回
+     * @param $new boolean 返回更新后的文档
+     */
+    function find_and_update(array $query, array $update, array $sort = null, $upsert = false, array $fields = null, $new = false) {}
 }
 /**
  * 游标对象
@@ -137,6 +156,10 @@ function unix(): int {}
      * 返回毫秒级时间戳
      */
     function unix_ms(): int {}
+    /**
+     * 返回标准的 YYYY-MM-DD hh:mm:ss 文本形式的时间
+     */
+    function iso(): string {}
 }
 /**
  * 对应 MongoDB 对象ID
diff --git a/doc/mysql.php b/doc/mysql.php
index d722eb7..2c8cfd1 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -5,6 +5,7 @@
  * 1. 同一协程中连续进行 MySQL 查询操作, 且结果集数据不读取且不销毁(或不读取完且不销毁)可能导致进程死锁; (请将结果集读取完 或 主动 unset 释放结果集对象)
  * 2. MySQL 读取数据时数值型字段读取映射为 PHP 对应数值类型；`DATETIME` 映射为 PHP 内置类型 `DateTime`;
  * 3. 客户端提供的简化方法如 `insert()` `update()` 也同时支持与上述反向的映射;
+ * 4. 单客户端内连接池上限大小为 4（单进程）；
  */
 namespace flame\mysql;
 /**
@@ -20,7 +21,7 @@ function connect($url): client {}
 
 /**
  * WHERE 字句语法规则:
- * 
+ *
  * @example
  *  $where = ["a"=>"1", "b"=>[2,"3","4"], "c"=>null, "d"=>["{!=}"=>5]];
  *  // " WHERE (`a`='1' AND `b`  IN ('2','3','4') AND `c` IS NULL AND `d`!='5')"
@@ -30,7 +31,7 @@ function connect($url): client {}
  * @example
  *  $where = "`a`=1";
  *  // $sql == " WHERE `a`=1";
- * 
+ *
  * @param 特殊的符号均以 "{}" 进行包裹, 存在以下可用项:
  *  * `{NOT}` / `{!}` - 逻辑非, 对逻辑子句取反, 生成形式: `NOT (.........)`;
  *  * `{OR}` / `{||}` - 逻辑或, 对逻辑子句进行逻辑或拼接, 生成形式: `... OR ... OR ...`;
@@ -54,7 +55,7 @@ function connect($url): client {}
  * @example
  *  $order = ["a"=>1, "b"=>-1, "c"=>true, "d"=>false, "e"=>"ASC", "f"=>"DESC"];
  *  // $sql == " ORDER BY `a` ASC, `b` DESC, `c` ASC, `d` DESC, `e` ASC, `f` DESC"
- * 
+ *
  * @param
  *  * 当 value 位正数或 `true` 时, 生成 `ASC`; 否则生成 `DESC`;
  *  * 当 value 为文本时, 直接拼接;
@@ -77,11 +78,13 @@ function connect($url): client {}
  *  * 当 $limit 为数组时, 拼接前两个值;
  */
 
-
 /**
  * MySQL 客户端（内部使用连接池）
  */
 class client {
+    /**
+     * 将数据进行转移，防止可能出现的 SQL 注入等问题；
+     */
     function escape($value):string {}
     /**
      * 返回事务对象 (绑定在一个连接上)
@@ -89,23 +92,28 @@ function escape($value):string {}
     function begin_tx(): ?tx {}
     /**
      * 执行制定的 SQL 查询, 并返回结果
-     * @param string $sql 
+     * @param string $sql
      * @return mixed SELECT 型语句返回结果集对象 `result`; 否则返回关联数组, 一般包含 affected_rows / insert_id 两项;
      */
     function query(string $sql): result {}
     /**
-     * 向指定表插入一行或多行数据
+     * 返回最近一次执行的 SQL 语句
+     * 注意：由于协程的“并行”（穿插）执行，本函数返回的语句可能与当前上下问代码不对应；
+     */
+    function last_query(): string {}
+    /**
+     * 向指定表插入一行或多行数据 (自动进行 ESCAPE 转义)
      * @param string $table 表名
      * @param array $data 待插入数据, 多行关联数组插入多行数据(以首行 KEY 做字段名); 普通关联数组插入一行数据;
      * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
     function insert(string $table, array $data): result {}
     /**
-     * 从指定表格删除数
-     * @param string $table 表明
-     * @param mixed $where WHERE 子句, 请参考上文;
-     * @param mixed $order ORDER 子句, 请参考上文;
-     * @param mixed $limit LIMIT 子句, 请参考上文;
+     * 从指定表格删除匹配的数据
+     * @param string $table 表名
+     * @param mixed $where WHERE 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
+     * @param mixed $order ORDER 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
+     * @param mixed $limit LIMIT 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
      * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
     function delete(string $table, mixed $where, mixed $order, mixed $limit): result {}
@@ -122,7 +130,7 @@ function update(string $table, mixed $where, mixed $modify, mixed $order, mixed
     /**
      * 从指定表筛选获取数据
      * @param string $table 表名
-     * @param mixed $fields 待选取字段, 为数组时其元素表示各个字段; 文本时直接拼接在 "SELECT $fields FROM $table"; 
+     * @param mixed $fields 待选取字段, 为数组时其元素表示各个字段; 文本时直接拼接在 "SELECT $fields FROM $table";
      *  关联数组的 KEY 表示对应函数, 仅可用于简单函数调用, 例如:
      *      "SUM" => "a"
      *  表示:
@@ -140,7 +148,10 @@ function one(string $table, mixed $where, mixed $order): array {}
      * 从指定表获取一行数据的指定字段值
      */
     function get(string $table, string $field, array $where, mixed $order): mixed {}
-    
+    /**
+     * 获取服务端版本
+     */
+    function server_version():string {}
 };
 
 /**
@@ -200,8 +211,8 @@ class result {
     function fetch_row():?array {}
     /**
      * 读取 (剩余) 全部行
-     * @return 二维数组, 可能为空数组; 
+     * @return 二维数组, 可能为空数组;
      *  若读取已完成, 返回 NULL
      */
     function fetch_all():?array {}
-};
\ No newline at end of file
+};
diff --git a/doc/redis.php b/doc/redis.php
index a28270b..11528ad 100644
--- a/doc/redis.php
+++ b/doc/redis.php
@@ -1,12 +1,15 @@
 <?php
 /**
- * 提供 Redis 基本客户端封装
+ * 提供 Redis 基本客户端封装，内部使用连接池：
+ * 注意：
+ * 1. 暂未实现 SUBSCRIBE 相关命令；
+ * 2. 单客户端内连接池大小上限为 3 （单进程）；
  */
 namespace flame\redis;
 
 function connect(string $url):client {}
 /**
- * 
+ * Redis 客户端
  */
 class client {
     /**
@@ -22,11 +25,11 @@ function __isset($name) {
     }
     /**
      * 支持 Redis 大部分指令:
-     * 
+     *
      * @method get()
      * @method set()
      * @method incr()
-     * @method zincr()   
+     * @method zincr()
      * @method mset()
      * @method mget()
      * @method hmset()
@@ -77,4 +80,4 @@ function __call($name, $argv): tx {
      * @return array 返回数据项与命令一一对应
      */
     function exec():array {}
-}
\ No newline at end of file
+}
diff --git a/doc/time.php b/doc/time.php
index 2da1b17..e235953 100644
--- a/doc/time.php
+++ b/doc/time.php
@@ -15,7 +15,6 @@ function sleep(int $ms) {}
 function now():int {}
 /**
  * 获取标准时间 "YYYY-mm-dd HH:ii:ss"
- * (格式话输出上述 `now()` 函数输出)
+ * (按标准格式输出上述 `now()` 对应时间)
  */
 function iso():string {}
-
diff --git a/src/compress/compress.cpp b/src/compress/compress.cpp
new file mode 100644
index 0000000..c3e5405
--- /dev/null
+++ b/src/compress/compress.cpp
@@ -0,0 +1,41 @@
+#include "compress.h"
+
+namespace flame::compress {
+    static php::value snappy_compress(php::parameters& params) {
+        php::string data = params[0].to_string();
+        struct iovec iov_in = {
+    		.iov_base = data.data(),
+    		.iov_len = data.size(),
+    	};
+        std::size_t len = rd_kafka_snappy_max_compressed_length(data.size());
+        php::string out(len);
+    	struct iovec iov_out = {
+    		.iov_base = out.data(),
+    		.iov_len = 0xffffffff,
+    	};
+        struct snappy_env env;
+        rd_kafka_snappy_init_env(&env);
+        rd_kafka_snappy_compress_iov(&env, &iov_in, 1, data.size(), &iov_out);
+        rd_kafka_snappy_free_env(&env);
+        out.shrink(iov_out.iov_len);
+        return std::move(out);
+    }
+    static php::value snappy_uncompress(php::parameters& params) {
+        php::string data = params[0].to_string();
+        std::size_t len;
+        rd_kafka_snappy_uncompressed_length(data.c_str(), data.size(), &len);
+        php::string out(len);
+        rd_kafka_snappy_uncompress(data.c_str(), data.size(), out.data());
+        return std::move(out);
+    }
+    void declare(php::extension_entry &ext)
+    {
+        ext
+            .function<snappy_compress>("flame\\compress\\snappy_compress", {
+                {"data", php::TYPE::STRING},
+            })
+            .function<snappy_uncompress>("flame\\compress\\snappy_uncompress", {
+                {"data", php::TYPE::STRING},
+            });
+    }
+}
diff --git a/src/compress/compress.h b/src/compress/compress.h
new file mode 100644
index 0000000..f10fe17
--- /dev/null
+++ b/src/compress/compress.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::compress {
+    void declare(php::extension_entry &ext);
+} // namespace flame::compress
diff --git a/src/core.cpp b/src/core.cpp
index c846960..18c8d43 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -28,6 +28,9 @@ namespace flame::core {
     php::value co_id(php::parameters& params) {
         return reinterpret_cast<uintptr_t>(coroutine::current.get());
     }
+    php::value co_count(php::parameters& params) {
+        return coroutine::count;
+    }
     void declare(php::extension_entry &ext) {
         ext
             .on_request_shutdown([] (php::extension_entry& ext) -> bool
@@ -40,11 +43,12 @@ namespace flame::core {
                 return true;
             })
             .function<select>("flame\\select")
-            .function<co_id>("flame\\co_id");
+            .function<co_id>("flame\\co_id")
+            .function<co_count>("flame\\co_count");
 
         queue::declare(ext);
         mutex::declare(ext);
         guard::declare(ext);
     }
-    
-}
\ No newline at end of file
+
+}
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
index 1ece214..f442b7f 100644
--- a/src/coroutine_queue.h
+++ b/src/coroutine_queue.h
@@ -22,6 +22,10 @@ namespace flame
                 ch.resume();
             }
         }
+        bool is_closed()
+        {
+            return q_.empty() && closed_;
+        }
         void push(const T& t, coroutine_handler& ch)
         {
             if(closed_)
@@ -48,7 +52,7 @@ namespace flame
                     // 有数据消费
                     break;
                 }
-                else if(closed_) 
+                else if(closed_)
                 {
                     // 无数据关闭
                     return std::optional<T>();
@@ -71,7 +75,7 @@ namespace flame
 
             return std::optional<T>(t);
         }
-    
+
     /* private: */
         std::size_t                 n_;
         std::list<T>                q_;
@@ -112,4 +116,4 @@ namespace flame
         goto TRY_ALL;
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/encoding/encoding.cpp b/src/encoding/encoding.cpp
new file mode 100644
index 0000000..f3f29de
--- /dev/null
+++ b/src/encoding/encoding.cpp
@@ -0,0 +1,31 @@
+#include "encoding.h"
+#include "../mongodb/mongodb.h"
+
+namespace flame::encoding {
+    static php::value bson_encode(php::parameters &params) {
+        if(params[0].typeof(php::TYPE::ARRAY)) {
+            auto doc = flame::mongodb::array2bson(params[0]);
+            return php::string((const char*)bson_get_data(doc.get()), doc->len);
+        }else{
+            throw php::exception(zend_ce_exception, "Unable to encode type other than Array");
+        }
+    }
+    static php::value bson_decode(php::parameters &params) {
+        php::string data = params[0].to_string();
+        bson_t* doc = bson_new_from_data((const uint8_t*)data.data(), data.size());
+        if(doc == nullptr) return nullptr;
+        php::array rv = flame::mongodb::bson2array(doc);
+        bson_destroy(doc);
+        return std::move(rv);
+    }
+    void declare(php::extension_entry &ext)
+    {
+        ext
+            .function<bson_encode>("flame\\encoding\\bson_encode", {
+                {"data", php::TYPE::ARRAY},
+            })
+            .function<bson_decode>("flame\\encoding\\bson_decode", {
+                {"data", php::TYPE::STRING},
+            });
+    }
+}
diff --git a/src/encoding/encoding.h b/src/encoding/encoding.h
new file mode 100644
index 0000000..9ab48f9
--- /dev/null
+++ b/src/encoding/encoding.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::encoding {
+    void declare(php::extension_entry &ext);
+} // namespace flame::encoding
diff --git a/src/flame.cpp b/src/flame.cpp
index aab09eb..75d543c 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -14,6 +14,9 @@
 #include "rabbitmq/rabbitmq.h"
 #include "tcp/tcp.h"
 #include "http/http.h"
+#include "hash/hash.h"
+#include "encoding/encoding.h"
+#include "compress/compress.h"
 
 namespace flame
 {
@@ -159,6 +162,9 @@ extern "C"
             flame::tcp::declare(ext);
             // flame::udp::declare(ext);
             flame::http::declare(ext);
+            flame::hash::declare(ext);
+            flame::encoding::declare(ext);
+            flame::compress::declare(ext);
         }
         else
         {
diff --git a/src/hash/hash.cpp b/src/hash/hash.cpp
new file mode 100644
index 0000000..d37667e
--- /dev/null
+++ b/src/hash/hash.cpp
@@ -0,0 +1,49 @@
+#include "hash.h"
+
+
+namespace flame::hash {
+    template <typename INTEGER_TYPE>
+    static php::string dec2hex(INTEGER_TYPE dec) {
+        php::string hex(sizeof(INTEGER_TYPE) * 2);
+        sprintf(hex.data(), "%0*lx", sizeof(INTEGER_TYPE) * 2, dec);
+        return std::move(hex);
+    }
+    static php::value murmur2(php::parameters& params) {
+        php::string data = params[0].to_string();
+        std::uint32_t raw = rd_murmur2(data.c_str(), data.size());
+        if(params.size() > 1 && params[1].to_boolean()) return raw;
+        return dec2hex<std::uint32_t>(raw);
+    }
+    static php::value xxh64(php::parameters& params) {
+        php::string data = params[0].to_string();
+        unsigned long long seed = 0;
+        if(params.size() > 1) {
+            seed = params[1].to_integer();
+        }
+        std::uint64_t raw = XXH64(data.c_str(), data.size(), seed);
+        if(params.size() > 2 && params[2].to_boolean()) return raw;
+        return dec2hex<std::uint64_t>(raw);
+    }
+    static php::value crc64(php::parameters& params) {
+        php::string data = params[0].to_string();
+        boost::crc_optimal<64, 0x42F0E1EBA9EA3693, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true, true>  crc64;
+        crc64.process_bytes(data.c_str(), data.size());
+        std::uint64_t raw = crc64.checksum();
+        if(params.size() > 1 && params[1].to_boolean()) return raw;
+        return dec2hex<std::uint64_t>(raw);
+    }
+    void declare(php::extension_entry &ext)
+    {
+        ext
+            .function<murmur2>("flame\\hash\\murmur2", {
+                {"data", php::TYPE::STRING},
+            })
+            .function<xxh64>("flame\\hash\\xxh64", {
+                {"data", php::TYPE::STRING},
+                {"seed", php::TYPE::INTEGER, false, true},
+            })
+            .function<crc64>("flame\\hash\\crc64", {
+                {"data", php::TYPE::STRING},
+            });
+    }
+}
diff --git a/src/hash/hash.h b/src/hash/hash.h
new file mode 100644
index 0000000..8a2580a
--- /dev/null
+++ b/src/hash/hash.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::hash {
+    void declare(php::extension_entry &ext);
+} // namespace flame::hash
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index 90a9e91..14093fe 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -22,11 +22,12 @@ namespace flame::kafka
         consumer::declare(ext);
 		producer::declare(ext);
     }
+    
     php::value consume(php::parameters& params)
     {
         php::array config = params[0];
         php::array topics = params[1];
-        
+
         php::object obj(php::class_entry<consumer>::entry());
         consumer* ptr = static_cast<consumer*>(php::native(obj));
         if (config.exists("concurrent"))
@@ -47,14 +48,9 @@ namespace flame::kafka
     {
         php::array config = params[0];
         php::array topics = params[1];
-        
+
         php::object obj(php::class_entry<producer>::entry());
         producer* ptr = static_cast<producer*>(php::native(obj));
-        if (config.exists("concurrent"))
-        {
-            // ptr->cc_ = std::min(std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 8), 256);
-            config.erase("concurrent");
-        }
         if (!config.exists("log.connection.close")) {
             config.set("log.connection.close", "false");
         }
@@ -64,21 +60,17 @@ namespace flame::kafka
     }
 
 
-    rd_kafka_conf_t* array2conf(const php::array &config)
-    {
+    rd_kafka_conf_t* array2conf(const php::array &config) {
         if(config.empty()) return nullptr;
         char err[256];
         rd_kafka_conf_t *conf = rd_kafka_conf_new();
-        for (auto i = config.begin(); i != config.end(); ++i)
-        {
+        for (auto i = config.begin(); i != config.end(); ++i) {
             php::string key = i->first.to_string();
             php::string val = i->second.to_string();
 
             if(RD_KAFKA_CONF_OK != rd_kafka_conf_set(conf, key.data(), val.data(), err, sizeof(err)))
-            {
                 throw php::exception(zend_ce_type_error,
                         (boost::format("unable to set Kafka config: %1%") % err).str(), -1);
-            }
         }
         return conf;
     }
@@ -100,11 +92,8 @@ namespace flame::kafka
         return hdrs;
     }
     php::array hdrs2array(rd_kafka_headers_t* hdrs)
-    {   
-        if(hdrs == nullptr)
-        {
-            return php::array(0);
-        }
+    {
+        if(hdrs == nullptr) return php::array(0);
         php::array header(rd_kafka_header_cnt(hdrs));
         const char *key;
         const void *val;
@@ -115,4 +104,4 @@ namespace flame::kafka
         }
         return header;
     }
-}
\ No newline at end of file
+}
diff --git a/src/mongodb/date_time.cpp b/src/mongodb/date_time.cpp
index 88eb077..d841d6c 100644
--- a/src/mongodb/date_time.cpp
+++ b/src/mongodb/date_time.cpp
@@ -16,7 +16,8 @@ namespace mongodb {
 			.method<&date_time::unix>("unix")
 			.method<&date_time::unix_ms>("unix_ms")
 			.method<&date_time::to_json>("jsonSerialize")
-			.method<&date_time::to_json>("__debugInfo");
+			.method<&date_time::to_json>("__debugInfo")
+			.method<&date_time::iso>("iso");
 		ext.add(std::move(class_date_time));
 	}
 	php::value date_time::__construct(php::parameters& params) {
@@ -50,5 +51,8 @@ namespace mongodb {
 		ret.set("$date", num);
 		return std::move(ret);
 	}
+	php::value date_time::iso(php::parameters& params) {
+		return time::iso(std::chrono::system_clock::time_point( std::chrono::milliseconds(tm_) ));
+	}
+}
 }
-}
\ No newline at end of file
diff --git a/src/mongodb/date_time.h b/src/mongodb/date_time.h
index 906e711..757e263 100644
--- a/src/mongodb/date_time.h
+++ b/src/mongodb/date_time.h
@@ -12,9 +12,10 @@ namespace flame::mongodb {
 		php::value unix_ms(php::parameters& params);
 		php::value to_datetime(php::parameters& params);
 		php::value to_json(php::parameters& params);
+		php::value iso(php::parameters& params);
 	private:
 		std::int64_t tm_;
 		friend php::value iter2value(bson_iter_t *i);
 		friend std::shared_ptr<bson_t> array2bson(const php::array &v);
 	};
-}
\ No newline at end of file
+}
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 9950885..a2eaded 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -88,12 +88,13 @@ ESCAPE_FINISHED:;
     }
 
     php::object _connection_base::query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch) {
+        last_query_ = std::move(sql);
         MYSQL_RES* rst = nullptr;
         int err = 0;
-        boost::asio::post(gcontroller->context_y, [&err, &conn, &ch, query = std::move(sql), &rst] ()
+        boost::asio::post(gcontroller->context_y, [&err, &conn, &ch, query=&last_query_, &rst] ()
         {
             // 在工作线程执行查询
-            err = mysql_real_query(conn.get(), query.c_str(), query.size());
+            err = mysql_real_query(conn.get(), query->c_str(), query->size());
             if (err == 0)
             {
                 // 防止锁表, 均使用 store 方式
@@ -133,7 +134,9 @@ ESCAPE_FINISHED:;
             return std::move(data);
         }
     }
-
+    const std::string& _connection_base::last_query() {
+        return last_query_;
+    }
     php::array _connection_base::fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch)
     {
         MYSQL_ROW row;
diff --git a/src/mysql/_connection_base.h b/src/mysql/_connection_base.h
index 75240dc..bf61930 100644
--- a/src/mysql/_connection_base.h
+++ b/src/mysql/_connection_base.h
@@ -13,7 +13,9 @@ namespace flame::mysql
         virtual std::shared_ptr<MYSQL> acquire(coroutine_handler& ch) = 0;
         php::object query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch);
         php::array fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch);
-
-      protected:
+        const std::string& last_query();
+    protected:
+    private:
+        std::string last_query_;
     };
 } // namespace flame::mysql
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 0eda44b..9d20b56 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -57,7 +57,8 @@ namespace flame::mysql
                 {"where", php::TYPE::UNDEFINED},
                 {"order", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&client::version>("version");
+            .method<&client::last_query>("last_query")
+            .method<&client::server_version>("server_version");
         ext.add(std::move(class_client));
     }
     php::value client::__construct(php::parameters& params) {
@@ -72,7 +73,7 @@ namespace flame::mysql
     {
         coroutine_handler ch {coroutine::current};
         std::shared_ptr<MYSQL> conn = cp_->acquire(ch);
-        
+
         php::buffer buffer;
         char quote = '\'';
         if (params.size() > 1 && params[1].typeof(php::TYPE::STRING))
@@ -155,7 +156,10 @@ namespace flame::mysql
             return nullptr;
         }
     }
-    php::value client::version(php::parameters &params)
+    php::value client::last_query(php::parameters& params) {
+        return cp_->last_query();
+    }
+    php::value client::server_version(php::parameters &params)
     {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
diff --git a/src/mysql/client.h b/src/mysql/client.h
index fb63bca..901936d 100644
--- a/src/mysql/client.h
+++ b/src/mysql/client.h
@@ -22,7 +22,8 @@ namespace flame::mysql
         php::value select(php::parameters &params);
         php::value one(php::parameters &params);
         php::value get(php::parameters &params);
-        php::value version(php::parameters& params);
+        php::value server_version(php::parameters& params);
+        php::value last_query(php::parameters& params);
     protected:
         std::shared_ptr<_connection_pool> cp_;
         friend php::value connect(php::parameters &params);
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 85fa8ac..b68a81d 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -8,7 +8,7 @@
 #include "tx.h"
 
 namespace flame::mysql {
-    
+
     void declare(php::extension_entry &ext)
     {
         gcontroller
@@ -393,7 +393,7 @@ namespace flame::mysql {
         }
         else
         {
-            throw php::exception(zend_ce_type_error, "failed to build 'LIMIT' clause: unsupported type");
+            throw php::exception(zend_ce_type_error, "failed to build 'LIMIT' clause: unsupported type specified for `limit`");
         }
     }
     static void insert_row(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &row)
@@ -493,10 +493,7 @@ namespace flame::mysql {
                 _connection_base::escape(cc, buf, i->second);
             }
         }
-        else
-        {
-            throw php::exception(zend_ce_type_error, "failed to build 'UPDATE' clause: unsupported type");
-        }
+        else throw php::exception(zend_ce_type_error, "failed to build 'UPDATE' clause: unsupported type specified for `update`");
         // 条件
         build_where(cc, buf, params[1]);
         // 排序
@@ -511,12 +508,8 @@ namespace flame::mysql {
         buf.append("SELECT ", 7);
         // 字段
         php::value fields = params[1];
-        if (fields.typeof(php::TYPE::STRING))
-        {
-            buf.append(fields);
-        }
-        else if (fields.typeof(php::TYPE::ARRAY))
-        {
+        if (fields.typeof(php::TYPE::STRING)) buf.append(fields);
+        else if (fields.typeof(php::TYPE::ARRAY)) {
             php::array a = fields;
             int j = -1;
             for (auto i = a.begin(); i != a.end(); ++i)
@@ -536,10 +529,8 @@ namespace flame::mysql {
                 }
             }
         }
-        else
-        {
-            throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type");
-        }
+        else if(fields.typeof(php::TYPE::NULLABLE)) buf.push_back('*');
+        else throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type specified for `fields`");
         // 表名
         buf.append(" FROM ", 6);
         _connection_base::escape(cc, buf, params[0], '`');
@@ -555,7 +546,7 @@ namespace flame::mysql {
     }
     void build_one(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
     {
-        buf.append("SELECT *  FROM ", 15);
+        buf.append("SELECT * FROM ", 15);
         // 表名
         _connection_base::escape(cc, buf, params[0], '`');
         // 条件
@@ -570,14 +561,8 @@ namespace flame::mysql {
     {
         buf.append("SELECT ", 7);
         // 字段
-        if (params[1].typeof(php::TYPE::STRING))
-        {
-            _connection_base::escape(cc, buf, params[1], '`');
-        }
-        else
-        {
-            throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type");
-        }
+        if (params[1].typeof(php::TYPE::STRING)) _connection_base::escape(cc, buf, params[1], '`');
+        else throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type specified for `field`");
         // 表名
         buf.append(" FROM ", 6);
         _connection_base::escape(cc, buf, params[0], '`');
@@ -589,4 +574,4 @@ namespace flame::mysql {
             build_order(cc, buf, params[3]);
         buf.append(" LIMIT 1", 8);
     }
-}
\ No newline at end of file
+}
diff --git a/src/queue.cpp b/src/queue.cpp
index 02c7d2c..00fca99 100644
--- a/src/queue.cpp
+++ b/src/queue.cpp
@@ -16,11 +16,12 @@ namespace flame
                 {"value", php::TYPE::UNDEFINED}
             })
             .method<&queue::pop>("pop")
-            .method<&queue::close>("close");
+            .method<&queue::close>("close")
+            .method<&queue::is_closed>("is_closed");
         ext.add(std::move(class_queue));
     }
     php::value queue::__construct(php::parameters& params)
-    {   
+    {
         if(params.size() > 0)
         {
             q_.reset(new coroutine_queue<php::value>(static_cast<int>(params[0])));
@@ -53,4 +54,7 @@ namespace flame
         q_->close();
         return nullptr;
     }
-}
\ No newline at end of file
+    php::value queue::is_closed(php::parameters& params) {
+        return q_->is_closed();
+    }
+}
diff --git a/src/queue.h b/src/queue.h
index 680da61..59fb0d5 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -8,7 +8,7 @@ namespace flame
     {
         php::value select(php::parameters &params);
     }
-    class queue: public php::class_base 
+    class queue: public php::class_base
     {
     public:
         static void declare(php::extension_entry &ext);
@@ -16,9 +16,10 @@ namespace flame
         php::value push(php::parameters &params);
         php::value pop(php::parameters &params);
         php::value close(php::parameters &params);
+        php::value is_closed(php::parameters &params);
     private:
         std::shared_ptr<coroutine_queue<php::value>> q_;
 
         friend php::value core::select(php::parameters &params);
     };
-}
\ No newline at end of file
+}
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index 0113fee..c477250 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -39,7 +39,7 @@ namespace flame::rabbitmq
     php::value consumer::run(php::parameters &params)
     {
         cb_ = params[0];
-        
+
         coroutine_handler ch {coroutine::current};
         // 下述 q 需要在堆分配, 否则当 cc_->consume() 结束后可能与被提前销毁 (协程销毁稍晚)
         coroutine_queue<php::object> q(cc_->pf_);
@@ -76,7 +76,7 @@ namespace flame::rabbitmq
         return nullptr;
     }
     php::value consumer::confirm(php::parameters &params)
-    {   
+    {
         php::object obj = params[0];
         message *ptr = static_cast<message *>(php::native(obj));
         coroutine_handler ch {coroutine::current};
@@ -99,5 +99,4 @@ namespace flame::rabbitmq
         cc_->close_consumer(ch);
         return nullptr;
     }
-
 } // namespace flame::rabbitmq
diff --git a/src/rabbitmq/consumer.h b/src/rabbitmq/consumer.h
index ddcd423..3c0d165 100644
--- a/src/rabbitmq/consumer.h
+++ b/src/rabbitmq/consumer.h
@@ -14,7 +14,6 @@ namespace flame::rabbitmq
         php::value confirm(php::parameters &params);
         php::value reject(php::parameters &params);
         php::value close(php::parameters &params);
-
     private:
         // 实际的客户端对象可能超过当前对象的生存期
         std::shared_ptr<_client> cc_;
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index af20192..e719f30 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -59,7 +59,7 @@ namespace flame::redis {
 			.method<&client::unimplement>("psubscribe")
             .method<&client::unimplement>("unsubscribe")
 			.method<&client::unimplement>("punsubscribe");
-        
+
 		ext.add(std::move(class_client));
 	}
 
@@ -124,7 +124,7 @@ namespace flame::redis {
 		{
 			return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
 		}
-	} 
+	}
 	php::value client::zrevrange(php::parameters& params) {
 		coroutine_handler ch{coroutine::current};
 		auto rc = cp_->acquire(ch);
diff --git a/src/redis/client.h b/src/redis/client.h
index e39e5cb..98ccef8 100644
--- a/src/redis/client.h
+++ b/src/redis/client.h
@@ -32,4 +32,4 @@ namespace flame::redis
         std::shared_ptr<_connection_pool> cp_;
         friend php::value connect(php::parameters &params);
     };
-} // namespace flame::redis
\ No newline at end of file
+} // namespace flame::redis
diff --git a/src/time/time.cpp b/src/time/time.cpp
index 87b5c8d..f763bef 100644
--- a/src/time/time.cpp
+++ b/src/time/time.cpp
@@ -26,10 +26,9 @@ namespace flame::time
             std::chrono::steady_clock::now() - time_steady);
         return time_system + diff;
     }
-    std::string iso()
-    {
-        std::string data(19, '\0');
-        std::time_t t = std::chrono::system_clock::to_time_t(now());
+    php::string iso(const std::chrono::time_point<std::chrono::system_clock>& now) {
+        php::string data(19);
+        std::time_t t = std::chrono::system_clock::to_time_t(now);
         struct tm *m = std::localtime(&t);
         sprintf(data.data(), "%04d-%02d-%02d %02d:%02d:%02d",
                 1900 + m->tm_year,
@@ -38,7 +37,11 @@ namespace flame::time
                 m->tm_hour,
                 m->tm_min,
                 m->tm_sec);
-        return data;
+        return std::move(data);
+    }
+    php::string iso()
+    {
+        return iso(now());
     }
 
     static php::value now(php::parameters &params)
@@ -67,4 +70,4 @@ namespace flame::time
 
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/time/time.h b/src/time/time.h
index d8a8a77..f4a105f 100644
--- a/src/time/time.h
+++ b/src/time/time.h
@@ -4,5 +4,6 @@ namespace flame::time
 {
     void declare(php::extension_entry &ext);
     std::chrono::time_point<std::chrono::system_clock> now();
-    std::string iso();
-}
\ No newline at end of file
+    php::string iso();
+    php::string iso(const std::chrono::time_point<std::chrono::system_clock>& now);
+}
diff --git a/src/vendor.h b/src/vendor.h
index 20b4e74..81616bd 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -3,12 +3,11 @@
 #define EXTENSION_NAME "flame"
 #define EXTENSION_VERSION "0.12.12"
 
-#include <cstdio>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <ifaddrs.h>
 #include <netdb.h>
-
+#include <cstdio>
 #include <limits>
 #include <algorithm>
 #include <functional> // for std::functional
@@ -44,9 +43,15 @@ namespace ssl = boost::asio::ssl;
 #include <boost/beast/core.hpp>
 #include <boost/beast/http.hpp>
 #include <boost/beast/websocket.hpp>
+#include <boost/crc.hpp>
 #include <amqpcpp.h>
 #include <amqpcpp/libboostasio.h>
 #include <mysql.h>
 // #include <my_sys.h>
 #include <mongoc.h>
 #include <librdkafka/rdkafka.h>
+extern "C" {
+    #include <librdkafka/snappy.h>
+    #include <librdkafka/rdmurmur2.h>
+    #include <librdkafka/xxhash.h>
+}
diff --git a/test/compress_1.php b/test/compress_1.php
new file mode 100644
index 0000000..b2e5487
--- /dev/null
+++ b/test/compress_1.php
@@ -0,0 +1,15 @@
+<?php
+flame\init("compress_1");
+
+flame\go(function() {
+    $x = flame\compress\snappy_compress(json_encode(["a"=>1234567890123,"b"=>1234567890123,"x"=>"中文","y"=>"中文"], JSON_UNESCAPED_UNICODE));
+    echo "(", strlen($x), ") ", bin2hex($x), "\n";
+    $y = flame\compress\snappy_uncompress($x);
+    echo "(", strlen($y), ") ", print_r(json_decode($y,true)), "\n";
+
+    $x = "3f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d84225600000000000000000000040000000000000000";
+    $y = flame\compress\snappy_compress($x);
+    echo strlen($x), " < ", strlen($y), "\n";
+});
+
+flame\run();
diff --git a/test/encoding_1.php b/test/encoding_1.php
new file mode 100644
index 0000000..4b7d247
--- /dev/null
+++ b/test/encoding_1.php
@@ -0,0 +1,11 @@
+<?php
+flame\init("hash_1");
+
+flame\go(function() {
+    $x = flame\encoding\bson_encode(["a"=>1234567890123,"x"=>"中文"]);
+    echo "(",strlen($x),") ", bin2hex($x), "\n";
+    $y = flame\encoding\bson_decode($x);
+    var_dump( $y );
+});
+
+flame\run();
diff --git a/test/hash_1.php b/test/hash_1.php
new file mode 100644
index 0000000..8993905
--- /dev/null
+++ b/test/hash_1.php
@@ -0,0 +1,14 @@
+<?php
+flame\init("hash_1");
+
+flame\go(function() {
+    var_dump( flame\hash\murmur2("abcdef", true) );
+    var_dump( flame\hash\xxh64("abcdef", 0, true) );
+    var_dump( flame\hash\crc64("abcdef", true) );
+
+    var_dump( flame\hash\murmur2("abcdef") );
+    var_dump( flame\hash\xxh64("abcdef") );
+    var_dump( flame\hash\crc64("abcdef") );
+});
+
+flame\run();

From f6b9283f332bdb8f65b5dec04cbeaef17aa7bf71 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 1 Mar 2019 21:34:09 +0800
Subject: [PATCH 068/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=E7=AE=80=E4=BB=8B?=
 =?UTF-8?q?=E5=8F=8A=E7=AE=80=E5=8D=95=20HTTP=20=E7=A4=BA=E4=BE=8B;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 51 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index c40f2e7..8afc786 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,50 @@
+### FLAME
+最初开发 FLAME 是为了给咱 PHP 争口气，开发者不能总是因为 “并行处理能力不足” 被各方高玩鄙视；最初试用了 [Swoole](https://www.swoole.com/) 但被其进程模型搞得一头雾水，而且可能也是因为早期版本，还有出现了各式各样的问题；于是在各方支持下，自行开发了 FLAME 框架；
+
+FLAME 能够在很大程度上提高 PHP 处理并行能力, 其定位在于简化高性能常见互联网相关业务程序开发的难度和代价：
+* 网络底层异步化；
+* 同步驱动连接池挂接线程池；
+* 使用 PHP 内置协程 (Generator) 机制；
+* 对等多进程工作模式；
+
+陆续接入了 MySQL / Kafka / Redis / MongoDB / RabbitMQ 等第三方驱动，满足日常大多数接口型开发需求；
+
+为了简化框架的学习成本、使用难度，在后期使用内置协程机制代替 `yield` 显式协程，并补充完善了很多功能：
+* 协程队列，协程管道；
+* 协程、异步错误捕获；
+* 异步进程；网卡地址读取；毫秒、标准时间；...
+* 平滑起停控制；
+* 多进程日志、重载；
+* ...
+
+在适配到 PHP 7.2 版本以后，陆陆续续已经使用在了公司内各种业务线；在稳定性方面已经有了一定的底子；
+
+当然说的很高大上、白富美，但实在能力有限，同时各种业务压力，没有时间详尽的测试整个框架的各项组合功能，框架可能存在各类问题、缺陷；迫切期望大家能帮忙发现和完善，在此感谢~ 大礼参拜~
+
+以下代码实现了几个简单的 HTTP 接口：
+``` PHP
+flame\init("http_server_demo");
+flame\go(function() {
+    $server = new flame\http\server(":::56101");
+    $server->before(function($req, $res) {
+            $req->data["before"] = flame\time\now();
+        })->get("/hello", function($req, $res) {
+            $res->body = "world";
+        })->post("/path", function($req, $res) {
+            $res->write("CHUNKED RESPONSE:")
+            $res->write($res->body);
+            $res->end();
+        })->after(function($req, $res, $r) {
+            if(!$r) {
+                $res->status = 404;
+                $res->file(__DIR__."/404.html");
+            }
+        });
+    $server->run();
+});
+flame\run();
+```
+
 ### 功能项
 
 * **core** - 核心，框架初始化设置，协程启动，协程队列，协程锁等；
@@ -11,8 +58,11 @@
 * **mongodb** - 简单 MongoDB 客户端（连接池），提供部分简化方法，如 `insert/delete/one/count` 等；
 * **rabbitmq** - 简单 RabbitMQ 生产消费支持；
 * **kafka** - 简单 Kafka 生产消费支持（仅支持 Kafka 0.10+ 群组消费）；
+* **hash** - 提供了若干哈希算法, 例如 murmur2 / crc64 / xxhash64 等；
+* **encoding** - 提供了若干编码、序列化函数，例如 bson 等；
+* **compress** - 提供了若干压缩算法，例如 snappy 等；
 
-请参看 `/doc` 目录下相关 PHP 文件的文档注释，或将该目录挂接再 IDE 内提供**自动完成**；
+请参看 `/doc` `/test` 目录下相关 PHP 文件的文档注释；也可将 `/doc` 挂接在 IDE 内方便提供 **自动完成** 等；
 
 ### 常见问题
 

From effe873cf0cbe756bd774dfda49bccad37c50ceb Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 15 Mar 2019 16:31:17 +0800
Subject: [PATCH 069/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E8=BF=9E?=
 =?UTF-8?q?=E6=8E=A5=E6=B1=A0=E5=A4=A7=E5=B0=8F=EF=BC=9B=E4=BC=98=E5=8C=96?=
 =?UTF-8?q?=EF=BC=9A=E9=BB=98=E8=AE=A4=E8=BF=9B=E7=A8=8B=E5=BC=BA=E5=88=B6?=
 =?UTF-8?q?=E5=81=9C=E6=AD=A2=E8=B6=85=E6=97=B6=2010=20->=201s=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php                     | 4 ++--
 src/controller.cpp               | 8 ++++----
 src/mongodb/_connection_pool.cpp | 3 ++-
 src/mongodb/mongodb.cpp          | 3 +--
 src/mysql/_connection_pool.cpp   | 3 +--
 src/mysql/mysql.h                | 2 +-
 src/redis/_connection_pool.cpp   | 3 ++-
 src/redis/redis.cpp              | 2 +-
 src/redis/redis.h                | 2 +-
 9 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index 607122b..b6fbd86 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -15,7 +15,7 @@
  *      "warning"
  *      "error"
  *      "fatal"
- *  * "timeout" - 多进程退出超时（超时后会被强制杀死）
+ *  * "timeout" - 多进程退出超时（超时后会被强制杀死）(v0.12.12+ 默认 1s 超时 / v0.12.11- 默认 10s 超时）
  *  @see flame\log
  *
  * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式
@@ -48,7 +48,7 @@ function select(queue $q1, $q2/*, ...*/):queue {}
 /**
  * 监听框架的通知
  * @param string $event 目前消息存在以下两种:
- *  * "exception" - 未捕获的异常通知;
+ *  * "exception" - 当协程发生未捕获异常, 执行对应的回调，并记录错误信息（随后进程会退出）;
  *  * "quit" - 退出消息, 一般用于平滑停止各种服务;
  * @param callable 回调函数
  */
diff --git a/src/controller.cpp b/src/controller.cpp
index 3e6f7e9..bc45cc8 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -13,7 +13,7 @@ namespace flame {
 		, status(STATUS_UNKNOWN)
 		, cbmap(new std::multimap<std::string, php::callable>())
 	{
-		
+
 		worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
 		worker_size = std::min(std::max((int)worker_size, 1), 512);
 		// FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
@@ -24,7 +24,7 @@ namespace flame {
 		else if (env.count("FLAME_MAX_WORKERS") > 0)
 		{
 			type = process_type::MASTER;
-		} 
+		}
 		else
 		{
 			worker_size = 0;
@@ -43,11 +43,11 @@ namespace flame {
 		return this;
 	}
 	void controller::initialize(const std::string& title, const php::array& options) {
-		// 多进程模式退出超时时间
+		// 多进程模式退出超时时间，默认 1s 超时强制退出
 		if(options.exists("timeout")) {
 			worker_quit = std::max(static_cast<int>(options.get("timeout")), 64);
 		}else{
-			worker_quit = 10;
+			worker_quit = 1;
 		}
 		status |= STATUS_INITIALIZED;
 		for (auto fn : init_cb)
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index b3ec2b0..df13879 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -1,4 +1,5 @@
 #include "../controller.h"
+#include "mongodb.h"
 #include "_connection_pool.h"
 
 namespace flame::mongodb
@@ -15,7 +16,7 @@ namespace flame::mongodb
 			mongoc_read_prefs_destroy(pref);
 		}
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
-        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 8);
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 6);
 
         p_ = mongoc_client_pool_new(uri.get());
     }
diff --git a/src/mongodb/mongodb.cpp b/src/mongodb/mongodb.cpp
index 3ad889a..815249c 100644
--- a/src/mongodb/mongodb.cpp
+++ b/src/mongodb/mongodb.cpp
@@ -15,8 +15,7 @@ namespace flame::mongodb
     {
         gcontroller->on_init([] (const php::array& options) {
                 mongoc_init();
-            })
-            ->on_stop([]() {
+            })->on_stop([]() {
                 mongoc_cleanup();
             });
         ext
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 408278b..84b1fea 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -3,9 +3,8 @@
 
 namespace flame::mysql
 {
-
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(2), max_(4), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
         , charset_(boost::logic::indeterminate)
     {
         if(url_.port < 10) url_.port = 3306;
diff --git a/src/mysql/mysql.h b/src/mysql/mysql.h
index 7b6f623..1786d99 100644
--- a/src/mysql/mysql.h
+++ b/src/mysql/mysql.h
@@ -14,4 +14,4 @@ namespace flame::mysql
     void build_select(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
     void build_one(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
     void build_get(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
-}
\ No newline at end of file
+}
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index b4edd3b..abf0bc6 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -1,11 +1,12 @@
 #include "../controller.h"
+#include "redis.h"
 #include "_connection_pool.h"
 
 namespace flame::redis
 {
 
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(1), max_(3), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        : url_(std::move(u)), min_(1), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     {
         if(url_.port < 10) url_.port = 6379;
     }
diff --git a/src/redis/redis.cpp b/src/redis/redis.cpp
index 21e0010..c4caab0 100644
--- a/src/redis/redis.cpp
+++ b/src/redis/redis.cpp
@@ -27,4 +27,4 @@ namespace flame::redis
     }
 
 
-}
\ No newline at end of file
+}
diff --git a/src/redis/redis.h b/src/redis/redis.h
index 3369ee9..b8e920f 100644
--- a/src/redis/redis.h
+++ b/src/redis/redis.h
@@ -5,4 +5,4 @@ namespace flame::redis
 {
     void declare(php::extension_entry &ext);
     php::value connect(php::parameters &params);
-} // namespace flame::redis
\ No newline at end of file
+} // namespace flame::redis

From d67544e2a746b443ec1df222f3cc04465cb2125b Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 17 Mar 2019 11:05:08 +0800
Subject: [PATCH 070/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20UDP=20=E6=94=AF?=
 =?UTF-8?q?=E6=8C=81;=20=E4=BC=98=E5=8C=96:=20=E7=AE=80=E5=8C=96=20TCP=20?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=BB=BA=E7=AB=8B=E3=80=81=E5=9F=9F=E5=90=8D?=
 =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=AE=9E=E7=8E=B0=EF=BC=9B=E4=BC=98=E5=8C=96?=
 =?UTF-8?q?=EF=BC=9Alibphpext=20->=201.1.3=20=E4=BF=AE=E5=A4=8D=20BUFFER?=
 =?UTF-8?q?=20=E5=BC=82=E5=B8=B8=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt     |   2 +-
 README.md          |   4 +-
 doc/udp.php        |  85 +++++++++++++++++++++++++
 src/flame.cpp      |   3 +-
 src/tcp/tcp.cpp    |  67 +++++++-------------
 src/udp/server.cpp | 152 ++++++++++++++++++++++++++++++++++++++++++++
 src/udp/server.h   |  22 +++++++
 src/udp/socket.cpp | 155 +++++++++++++++++++++++++++++++++++++++++++++
 src/udp/socket.h   |  29 +++++++++
 src/udp/udp.cpp    |  65 +++++++++++++++++++
 src/udp/udp.h      |  10 +++
 src/vendor.h       |   1 -
 test/udp_1.php     |  26 ++++++++
 13 files changed, 571 insertions(+), 50 deletions(-)
 create mode 100644 doc/udp.php
 create mode 100644 src/udp/server.cpp
 create mode 100644 src/udp/server.h
 create mode 100644 src/udp/socket.cpp
 create mode 100644 src/udp/socket.h
 create mode 100644 src/udp/udp.cpp
 create mode 100644 src/udp/udp.h
 create mode 100644 test/udp_1.php

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 82bff46..27cdf7c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,7 +22,7 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
     OUTPUT_STRIP_TRAILING_WHITESPACE)
-set(VENDOR_PHPEXT /data/vendor/phpext-1.1.2)
+set(VENDOR_PHPEXT /data/vendor/phpext-1.1.3)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
 set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
diff --git a/README.md b/README.md
index 8afc786..a9c12ae 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 ### FLAME
-最初开发 FLAME 是为了给咱 PHP 争口气，开发者不能总是因为 “并行处理能力不足” 被各方高玩鄙视；最初试用了 [Swoole](https://www.swoole.com/) 但被其进程模型搞得一头雾水，而且可能也是因为早期版本，还有出现了各式各样的问题；于是在各方支持下，自行开发了 FLAME 框架；
+最初开发 FLAME 是为了给咱 PHP 争口气，开发者不能总是因为 “并行处理能力不足” 被各方高玩鄙视；最初试用了 [Swoole](https://www.swoole.com/) 但被其进程模型搞得一头雾水，而且可能也是因为早期版本，还出现了各式各样的问题；于是在各方支持下，自行开发了 FLAME 框架；
 
-FLAME 能够在很大程度上提高 PHP 处理并行能力, 其定位在于简化高性能常见互联网相关业务程序开发的难度和代价：
+FLAME 能够在很大程度上提高 PHP 处理并行能力, 其定位在于简化开发常见互联网业务高性能服务程序的难度和代价：
 * 网络底层异步化；
 * 同步驱动连接池挂接线程池；
 * 使用 PHP 内置协程 (Generator) 机制；
diff --git a/doc/udp.php b/doc/udp.php
new file mode 100644
index 0000000..3994d74
--- /dev/null
+++ b/doc/udp.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * 提供对 UDP 网络操作的简单封装
+ */
+namespace flame\udp;
+
+/**
+ * 连接指定地址, 并返回 socket 对象
+ * @param string $address 连接地址, 例如: "127.0.0.1:8687"
+ * 注意: UDP "链接" 能在一定程度上提高效率;
+ */
+function connect(string $address):socket {}
+
+class socket {
+    /**
+     * 构建 socket 可选的进行地址绑定
+     * 实际函数拥有两种形式:
+     *  __construct(string $address, array $option = [])
+     * 或
+     *  __construct(array $option = [])
+     * 前者可用于地址绑定;
+     * @param array $option 可用选项如下:
+     * * `max` integer - 接收缓冲区上限(默认为 64k 可减小已提升效率)
+     */
+    function __construct(mixed $opt_or_address = null, array $option = []) {}
+    /**
+     * 本地地址（含端口）
+     * @property string
+     */
+    public $local_address;
+    /**
+     * 远端地址（含端口）
+     * @property string
+     */
+    public $remote_address;
+    /**
+     * 发送一个 UDP 包给已连接的对端
+     * 注意: 只能在"连接"形态下使用
+     * @see flame\udp\connect()
+     */
+    function send(string $data) {}
+    /**
+     * 向指定目标 $to 发送一个 UDP 包
+     * @param string $to 目标地址, 可以是域名或地址;
+     */
+    function send_to(string $data, string $to) {}
+    /**
+     * 接收并返回一个 UDP 包
+     * @return mixed 返回接收到的数据包(文本 string); 若 socket 被关闭则返回 null;
+     */
+    function recv():mixed {}
+    /**
+     * 关闭连接
+     * 注意: 正在进行的发送 send(_to) 或 recv(_from) 接收动作将被终止;
+     */
+    function close() {}
+}
+/**
+ * 简化的服务端, 相较使用上述 socket 自行实现效率稍高
+ */
+class server {
+    /**
+     * 创建一个服务器, 绑定在指定的地址上, 准备服务
+     * @param string $address 绑定地址, 例如: "0.0.0.0:8888" 或 ":::8888"
+     * @param string
+     */
+    function __construct(string $address, array $options = []) {}
+    /**
+     * 启动服务器, 当有连接建立时, 启用协程执行回调函数;
+     * @param callable $cb 回调函数, 形如:
+     *  function(string $data, string $from) {}
+     *  $data - 接收的数据包
+     *  $from - 来源地址
+     */
+    function run(callable $cb) {}
+    /**
+     * 使用当前服务器对应 UDP 套接字, 向指定地址发送消息
+     * @see flame::socket::send_to()
+     */
+    function send_to(string $data, string $address) {}
+    /**
+     * 关闭服务器 (结束 run() 过程, 保证正在进行处理的"消费"协程完成)
+     */
+    function close() {}
+}
diff --git a/src/flame.cpp b/src/flame.cpp
index 75d543c..75ad3cd 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -13,6 +13,7 @@
 #include "kafka/kafka.h"
 #include "rabbitmq/rabbitmq.h"
 #include "tcp/tcp.h"
+#include "udp/udp.h"
 #include "http/http.h"
 #include "hash/hash.h"
 #include "encoding/encoding.h"
@@ -160,7 +161,7 @@ extern "C"
             flame::kafka::declare(ext);
             flame::rabbitmq::declare(ext);
             flame::tcp::declare(ext);
-            // flame::udp::declare(ext);
+            flame::udp::declare(ext);
             flame::http::declare(ext);
             flame::hash::declare(ext);
             flame::encoding::declare(ext);
diff --git a/src/tcp/tcp.cpp b/src/tcp/tcp.cpp
index 222de2f..dbe51cd 100644
--- a/src/tcp/tcp.cpp
+++ b/src/tcp/tcp.cpp
@@ -1,5 +1,6 @@
 #include "../controller.h"
 #include "../coroutine.h"
+#include "../udp/udp.h"
 #include "tcp.h"
 #include "socket.h"
 #include "server.h"
@@ -24,63 +25,39 @@ namespace flame::tcp {
 		server::declare(ext);
 	}
 
-    std::pair<std::string, std::string> addr2pair(const std::string &addr)
-    {
-        char *s = const_cast<char*>(addr.data()), *p, *e = s + addr.size();
-        for (p = e - 1; p > s; --p)
-        {
-            if (*p == ':')
-                break; // 分离 地址与端口
-        }
-        if (*p != ':')
-        {
-            return std::make_pair<std::string, std::string>(std::string(addr), "");
-        }
-        else
-        {
-            return std::make_pair<std::string, std::string>(
-                std::string(s, p - s), std::string(p+1, e-p-1));
-        }
-    }
     php::value connect(php::parameters& params) {
 		php::object obj(php::class_entry<socket>::entry());
         socket *ptr = static_cast<socket *>(php::native(obj));
 
         std::string str = params[0];
-        auto pair = addr2pair(str);
-        if(pair.second.empty()) {
-            throw php::exception(zend_ce_type_error, "failed to connect tcp socket: missing port");
-        }
+        auto pair = udp::addr2pair(str);
+        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to connect tcp socket: illegal address format");
 
 		coroutine_handler ch{coroutine::current};
         boost::system::error_code err;
+		boost::asio::ip::tcp::resolver::results_type eps;
 		// DNS 地址解析
-		resolver_->async_resolve(pair.first, pair.second, [&err, &obj, &ptr, &ch] (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type edps) {
-			if(error) 
-            {
-                err = error;
-                ch.resume();
-                return;
-            }
-			// 连接
-            boost::asio::async_connect(ptr->socket_, edps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::tcp::endpoint &edp) {
-                if (error)
-                {
-                    err = error;
-                    ch.resume();
-                    return;
-                }
-                obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
-				obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
-                ch.resume();
-            });
+		resolver_->async_resolve(pair.first, pair.second, [&err, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type results) {
+			if(error) err = error;
+			else eps = results;
+            ch.resume();
 		});
+		ch.suspend();
+		if(err) throw php::exception(zend_ce_exception,
+                (boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+
+		// 连接
+        boost::asio::async_connect(ptr->socket_, eps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::tcp::endpoint &edp) {
+            if (error) err = error;
+			else {
+	            obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
+				obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+			}
+            ch.resume();
+        });
         ch.suspend();
-        if(err) 
-        {
-            throw php::exception(zend_ce_exception,
+        if(err) throw php::exception(zend_ce_exception,
                 (boost::format("failed to connect tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
-        }
         return std::move(obj);
 	}
 } // namespace flame::tcp
diff --git a/src/udp/server.cpp b/src/udp/server.cpp
new file mode 100644
index 0000000..f2b48f5
--- /dev/null
+++ b/src/udp/server.cpp
@@ -0,0 +1,152 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "../coroutine_queue.h"
+#include "../time/time.h"
+#include "udp.h"
+#include "server.h"
+
+namespace flame::udp {
+    void server::declare(php::extension_entry& ext)
+    {
+		php::class_entry<server> class_server("flame\\udp\\server");
+		class_server
+			.method<&server::__construct>("__construct", {
+				{"address", php::TYPE::STRING, false, false},
+				{"options", php::TYPE::ARRAY, false, true},
+			})
+			.method<&server::run>("run", {
+				{"length", php::TYPE::CALLABLE, false, false},
+			})
+            .method<&server::send_to>("send_to", {
+				{"data", php::TYPE::STRING, false, false},
+				{"address", php::TYPE::STRING, false, false},
+			})
+			.method<&server::close>("close");
+		ext.add(std::move(class_server));
+	}
+    server::server()
+	: socket_(gcontroller->context_x)
+    , closed_(false)
+    , concurrent_(8)
+    , max_(64 * 1024) {
+
+	}
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+
+    php::value server::__construct(php::parameters& params) {
+        auto pair = addr2pair(params[0]);
+		boost::system::error_code err;
+		boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
+		if(err) throw php::exception(zend_ce_exception,
+            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		std::uint16_t port = std::stoi(pair.second);
+        addr_.address(address);
+        addr_.port(port);
+
+        socket_.open(addr_.protocol());
+        boost::asio::socket_base::reuse_address opt1(true);
+        socket_.set_option(opt1);
+        reuse_port opt2(true);
+        socket_.set_option(opt2);
+		socket_.bind(addr_, err);
+		if(err) throw php::exception(zend_ce_exception,
+            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+
+        if(params.size() > 1) {
+            php::array options = params[1];
+            if(options.exists("concurrent")) {
+                concurrent_ = std::max(std::min(static_cast<int>(options.get("concurrent")), 256), 1);
+            }
+            if(options.exists("max")) {
+                max_ = std::min(std::max(static_cast<int>(options.get("concurrent")) , 1), 64 * 1024);
+            }
+        }
+        return nullptr;
+    }
+
+    php::value server::run(php::parameters& params) {
+		php::callable cb_ = params[0];
+
+        coroutine_handler ch {coroutine::current};
+        coroutine_queue<std::pair<php::string, php::string>> q(128);
+        // 启动若干协程, 然后进行"并行>"消费
+        int count = concurrent_;
+        for (int i = 0; i < concurrent_; ++i) {
+            // 启动协程开始消费
+            coroutine::start(php::value([&q, &count, &ch, &cb_, i] (php::parameters &params) -> php::value {
+                coroutine_handler cch {coroutine::current};
+                while(auto x = q.pop(cch)) {
+                    try {
+                        cb_.call({x->first, x->second});
+                    } catch(const php::exception& ex) {
+                        php::object obj = ex;
+                        std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
+                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                    }
+                }
+                if(--count == 0) ch.resume();
+                return nullptr;
+            }));
+        }
+        // 生产
+        std::size_t len = 0;
+        boost::system::error_code err;
+		boost::asio::ip::udp::endpoint ep;
+        php::buffer buffer;
+        while(!closed_) {
+    		socket_.async_receive_from(boost::asio::buffer(buffer.prepare(max_), max_), ep, [&len, &err, &ch] (const boost::system::error_code& error, std::size_t nread) {
+                if(error) err = error;
+                else len = nread;
+                ch.resume();
+            });
+            ch.suspend();
+            // 存在可能被关闭, 直接返回
+            if(err == boost::asio::error::operation_aborted) goto CLOSING;
+            else if(err) throw php::exception(zend_ce_exception,
+                                     (boost::format("failed to read tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+            buffer.commit(len);
+            q.push(std::make_pair<php::string, php::string>(php::string(std::move(buffer)), (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str()), ch);
+        }
+CLOSING:
+        q.close();
+        ch.suspend();
+        return nullptr;
+	}
+
+    php::value server::send_to(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+
+        boost::system::error_code err;
+		php::string data = params[0];
+		auto pair = addr2pair(params[1]);
+		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to send udp packet: illegal address format");
+
+		boost::asio::ip::udp::resolver::results_type eps;
+		resolver_->async_resolve(pair.first, pair.second, [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+			if(error) err = error;
+			else eps = results;
+			ch.resume();
+		});
+		ch.suspend();
+		if (err == boost::asio::error::operation_aborted) return nullptr;
+		else if(err) throw php::exception(zend_ce_exception,
+			(boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+
+		// 发送
+		int sent = 0;
+		for(auto i=eps.begin(); i!=eps.end(); ++i) {
+			socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
+			if(!err) return nullptr;
+		}
+
+		throw php::exception(zend_ce_exception,
+			(boost::format("failed to send: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+	}
+
+    php::value server::close(php::parameters& params) {
+        closed_ = true;
+        boost::system::error_code err;
+        socket_.close(err);
+        return true;
+    }
+}
diff --git a/src/udp/server.h b/src/udp/server.h
new file mode 100644
index 0000000..11e5fbb
--- /dev/null
+++ b/src/udp/server.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "../coroutine_mutex.h"
+
+namespace flame::udp {
+    class server: public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        server();
+        php::value __construct(php::parameters& params);
+        php::value run(php::parameters& params);
+        php::value send_to(php::parameters& params);
+        php::value close(php::parameters& params);
+    private:
+        boost::asio::ip::udp::socket socket_;
+        boost::asio::ip::udp::endpoint addr_;
+        int concurrent_;
+        int max_;
+        bool closed_;
+    };
+}
diff --git a/src/udp/socket.cpp b/src/udp/socket.cpp
new file mode 100644
index 0000000..a0ca186
--- /dev/null
+++ b/src/udp/socket.cpp
@@ -0,0 +1,155 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "udp.h"
+#include "socket.h"
+
+namespace flame::udp {
+	void socket::declare(php::extension_entry& ext)
+    {
+		php::class_entry<socket> class_socket("flame\\udp\\socket");
+		class_socket
+			.property({"local_address", ""})
+			.property({"remote_address", ""})
+			.method<&socket::__construct>("__construct", {
+				{"address", php::TYPE::STRING, false, true},
+			})
+			.method<&socket::recv_from>("recv_from", {
+				{"address", php::TYPE::STRING, true, true},
+			})
+			.method<&socket::recv>("recv")
+			.method<&socket::send_to>("send_to", {
+				{"data", php::TYPE::STRING, false, false},
+				{"address", php::TYPE::STRING, false, false},
+			})
+			.method<&socket::send>("send")
+			.method<&socket::close>("close");
+		ext.add(std::move(class_socket));
+	}
+	socket::socket()
+	: socket_(gcontroller->context_x)
+	, connected_(false)
+	, max_(64 * 1024) {
+
+	}
+
+	typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+
+	php::value socket::__construct(php::parameters& params) {
+		boost::system::error_code err;
+		php::array option;
+		if(params.size() > 0 && params[0].typeof(php::TYPE::STRING)) {
+			auto pair = addr2pair(params[0]);
+			boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
+			if(err) throw php::exception(zend_ce_exception,
+	            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+			std::uint16_t port = std::stoi(pair.second);
+			auto ep = boost::asio::ip::udp::endpoint(address, port);
+			socket_.open(ep.protocol());
+			boost::asio::socket_base::reuse_address opt1(true);
+	        socket_.set_option(opt1);
+	        reuse_port opt2(true);
+	        socket_.set_option(opt2);
+			socket_.bind(ep, err);
+			if(err) throw php::exception(zend_ce_exception,
+	            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+			if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
+				option = params[0];
+				goto PARSE_OPTION;
+			}
+		}else{
+			socket_.open(boost::asio::ip::udp::v6(), err);
+			if(err) socket_.open(boost::asio::ip::udp::v4(), err);
+			if(err) throw php::exception(zend_ce_exception,
+	            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+			if(params.size() > 0 && params[0].typeof(php::TYPE::ARRAY)) {
+				option = params[0];
+				goto PARSE_OPTION;
+			}
+		}
+		return nullptr;
+PARSE_OPTION:
+		if(option.exists("max")) {
+			max_ = std::min(std::max(static_cast<int>(option.get("max")), 64), 64 * 1024);
+		}
+		return nullptr;
+	}
+	php::value socket::recv(php::parameters& params) {
+		if(!connected_) throw php::exception(zend_ce_exception, "failed to send: socket not connected");
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时进行读取
+        coroutine_guard guard(rmutex_, ch);
+		boost::system::error_code err;
+        std::size_t len = 0;
+        len = socket_.async_receive(boost::asio::buffer(buffer_.prepare(max_), max_), ch[err]);
+		buffer_.commit(len);
+        // 数据返回
+        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to recv from socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        else return php::string(std::move(buffer_));
+    }
+	php::value socket::recv_from(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时进行读取
+        coroutine_guard guard(rmutex_, ch);
+
+		boost::system::error_code err;
+		std::size_t len = 0;
+		boost::asio::ip::udp::endpoint ep;
+		len = socket_.async_receive_from(boost::asio::buffer(buffer_.prepare(max_), max_), ep, ch[err]);
+		buffer_.commit(len);
+        // 数据返回
+        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception,
+                                 (boost::format("failed to read tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		if(params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
+		return php::string(std::move(buffer_));
+    }
+	php::value socket::send(php::parameters& params) {
+		if(!connected_) throw php::exception(zend_ce_exception, "failed to send: connected socket required");
+        coroutine_handler ch{coroutine::current};
+
+        boost::system::error_code err;
+		std::string data = params[0];
+        socket_.async_send(boost::asio::buffer(data), ch[err]);
+
+        if (!err || err == boost::asio::error::operation_aborted) return nullptr;
+        else throw php::exception(zend_ce_exception,
+                (boost::format("failed to write tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        return nullptr;
+	}
+	php::value socket::send_to(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+
+        boost::system::error_code err;
+		php::string data = params[0];
+		auto pair = addr2pair(params[1]);
+		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to send udp packet: illegal address format");
+
+		boost::asio::ip::udp::resolver::results_type eps;
+		resolver_->async_resolve(pair.first, pair.second, [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+			if(error) err = error;
+			else eps = results;
+			ch.resume();
+		});
+		ch.suspend();
+		if (err == boost::asio::error::operation_aborted) return nullptr;
+		else if(err) throw php::exception(zend_ce_exception,
+			(boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+
+		// 发送
+		int sent = 0;
+		for(auto i=eps.begin(); i!=eps.end(); ++i) {
+			socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
+			if(!err) return nullptr;
+		}
+
+		throw php::exception(zend_ce_exception,
+			(boost::format("failed to send: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+	}
+	php::value socket::close(php::parameters& params) {
+		connected_ = false;
+		socket_.shutdown(boost::asio::socket_base::shutdown_both);
+		return nullptr;
+	}
+}
diff --git a/src/udp/socket.h b/src/udp/socket.h
new file mode 100644
index 0000000..aebddd6
--- /dev/null
+++ b/src/udp/socket.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "../coroutine_mutex.h"
+
+namespace flame::udp {
+    class socket : public php::class_base
+    {
+    public:
+        static void declare(php::extension_entry &ext);
+        socket();
+        php::value __construct(php::parameters &param);
+        php::value recv_from(php::parameters &param);
+        php::value recv(php::parameters& params);
+        php::value send_to(php::parameters &params);
+        php::value send(php::parameters& params);
+        php::value close(php::parameters &params);
+
+    private:
+        boost::asio::ip::udp::socket socket_;
+        php::buffer                  buffer_;
+        coroutine_mutex              rmutex_;
+        bool                      connected_;
+        int                             max_;
+
+        friend class server;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::udp
diff --git a/src/udp/udp.cpp b/src/udp/udp.cpp
new file mode 100644
index 0000000..2f56e38
--- /dev/null
+++ b/src/udp/udp.cpp
@@ -0,0 +1,65 @@
+#include "../controller.h"
+#include "../coroutine.h"
+#include "udp.h"
+#include "socket.h"
+#include "server.h"
+
+namespace flame::udp {
+	std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
+	void declare(php::extension_entry& ext)
+    {
+		gcontroller->on_init([] (const php::array& options) {
+            resolver_.reset(new boost::asio::ip::udp::resolver(gcontroller->context_x));
+        })->on_stop([] () {
+            resolver_.reset();
+        });
+		ext
+			.function<connect>("flame\\udp\\connect", {
+				{"address", php::TYPE::STRING},
+			});
+		socket::declare(ext);
+		server::declare(ext);
+	}
+	php::value connect(php::parameters& params) {
+		php::object obj(php::class_entry<socket>::entry());
+        socket *ptr = static_cast<socket *>(php::native(obj));
+
+        std::string str = params[0];
+        auto pair = addr2pair(str);
+        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to connect udp socket: illegal address format");
+
+		coroutine_handler ch{coroutine::current};
+        boost::system::error_code err;
+		boost::asio::ip::udp::resolver::results_type eps;
+		resolver_->async_resolve(pair.first, pair.second, [&err, &obj, &ptr, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) { // DNS 地址解析
+			if(error) err = error;
+			else eps = results;
+			ch.resume();
+		});
+		ch.suspend();
+		if(err) throw php::exception(zend_ce_exception,
+                (boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+
+        boost::asio::async_connect(ptr->socket_, eps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::udp::endpoint &edp) { // 连接
+            if (error) err = error;
+			else {
+            	obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
+				obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+			}
+            ch.resume();
+        });
+        ch.suspend();
+        if(err) throw php::exception(zend_ce_exception,
+                (boost::format("failed to connect udp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		ptr->connected_ = true;
+        return std::move(obj);
+	}
+    std::pair<std::string, std::string> addr2pair(const std::string &addr) {
+        char *s = const_cast<char*>(addr.data()), *p, *e = s + addr.size();
+        for (p = e - 1; p > s; --p) if (*p == ':') break; // 分离 地址与端口
+        if (*p != ':') return std::make_pair<std::string, std::string>(std::string(addr), "");
+        else return std::make_pair<std::string, std::string>(
+                std::string(s, p - s), std::string(p+1, e-p-1));
+    }
+
+} // namespace flame::tcp
diff --git a/src/udp/udp.h b/src/udp/udp.h
new file mode 100644
index 0000000..bce6130
--- /dev/null
+++ b/src/udp/udp.h
@@ -0,0 +1,10 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::udp {
+    extern std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
+    void declare(php::extension_entry &ext);
+    php::value bind_and_listen(php::parameters& params);
+    php::value connect(php::parameters &params);
+    std::pair<std::string, std::string> addr2pair(const std::string& addr);
+} // namespace flame::tcp
diff --git a/src/vendor.h b/src/vendor.h
index 81616bd..86cf1fb 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -17,7 +17,6 @@
 #include <stack>
 #include <optional>
 #include <unordered_set>
-#include <tuple>
 #include <phpext/phpext.h>
 #include <http_parser.h>
 #include <hiredis/hiredis.h>
diff --git a/test/udp_1.php b/test/udp_1.php
new file mode 100644
index 0000000..9ba02b5
--- /dev/null
+++ b/test/udp_1.php
@@ -0,0 +1,26 @@
+<?php
+flame\init("udp_1");
+
+flame\go(function() {
+    $server = new flame\udp\server(":::6666");
+    flame\go(function() use($server) {
+        flame\time\sleep(1000);
+        $socket = new flame\udp\socket();
+        $socket->send_to("hello", "127.0.0.1:6666"); // 服务器 IPv4 兼容, 也可以收到
+        $socket->send_to("world", "::1:6666");
+        $socket->send_to("!", "localhost:6666");
+        flame\time\sleep(1000);
+        $socket = flame\udp\connect("127.0.0.1:6666");
+        $socket->send("hello");
+        $socket->send("world!");
+        echo "received: ". $socket->recv()."\n";
+        flame\time\sleep(10000);
+        $server->close();
+    });
+    $server->run(function($data, $from) use($server) {
+        echo "(".$from.") [".$data."]\n";
+        $server->send_to($data, $from); // echo back
+    });
+});
+
+flame\run();

From 6ad843f6a3202fc57bd8bff6f7f842964a699b42 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 17 Mar 2019 11:06:20 +0800
Subject: [PATCH 071/146] v0.12.13

---
 src/vendor.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/vendor.h b/src/vendor.h
index 86cf1fb..4db7e08 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.12"
+#define EXTENSION_VERSION "0.12.13"
 
 #include <sys/types.h>
 #include <sys/socket.h>

From e2aed7ca373fdcd0819671bf03b50a4f22a24e9c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 17 Mar 2019 12:00:54 +0800
Subject: [PATCH 072/146] =?UTF-8?q?=E5=8D=87=E7=BA=A7=20amqpcpp=20?=
 =?UTF-8?q?=E4=BE=9D=E8=B5=96=20->=204.1.4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt | 2 +-
 README.md      | 1 +
 src/flame.cpp  | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 27cdf7c..f8aaf2e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,7 +28,7 @@ set(VENDOR_BOOST /data/vendor/boost-1.69.0)
 set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
-set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.3)
+set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.0)
 set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.9.0)
 # -----------------------------------------------------------------------------
diff --git a/README.md b/README.md
index a9c12ae..8d23eab 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ flame\run();
 * **log** - 简单日志记录功能，多进程支持，支持日志登记过滤设置；
 * **os** - 操作系统相关信息获取，异步进程启停操作等；
 * **tcp** - 封装 TCP 相关服务端客户端功能；
+* **udp** - 封装 UDP 相关服务端客户端功能；
 * **http** - 简单的 HTTP 客户端，支持长连 Keep-Alive 及相关连接数控制；HTTP 服务端，简单 PATH 处理器；（暂不支持 HTTPS 相关）
 * **mysql** - 简单 MySQL 客户端（连接池），提供部分简化方法，如 `insert/delete/one` 等；
 * **redis** - 简单 Redis 客户端（连接池）；
diff --git a/src/flame.cpp b/src/flame.cpp
index 75ad3cd..96a11c5 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -126,7 +126,7 @@ extern "C"
             .desc({"vendor/libphpext", PHPEXT_LIB_VERSION})
             .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
             .desc({"vendor/mysqlc", mysql_get_client_info()})
-            .desc({"vendor/amqpcpp", "4.1.3"})
+            .desc({"vendor/amqpcpp", "4.1.4"})
             .desc({"vendor/librdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S});
 

From da206c9a56161cf56782d7296e1e0afb08c24e97 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 17 Mar 2019 14:47:21 +0800
Subject: [PATCH 073/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=AE=8C?=
 =?UTF-8?q?=E5=96=84=E6=9C=AA=E6=8D=95=E8=8E=B7=E5=BC=82=E5=B8=B8=E7=9A=84?=
 =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/flame.cpp        | 30 +++++++++++-------------------
 test/exception_1.php |  7 ++++---
 2 files changed, 15 insertions(+), 22 deletions(-)

diff --git a/src/flame.cpp b/src/flame.cpp
index 96a11c5..90489af 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -34,36 +34,28 @@ namespace flame
     static php::value go(php::parameters& params)
     {
         php::callable fn = params[0];
-        coroutine::start(php::callable([fn] (php::parameters& params) -> php::value
-        {
+        coroutine::start(php::callable([fn] (php::parameters& params) -> php::value {
             php::value rv;
-            try{
+            try {
                 rv = fn.call();
-            }
-            catch (const php::exception &ex)
-            {
+            }catch (const php::exception &ex) {
                 int x = 0;
                 auto ft = gcontroller->cbmap->equal_range("exception");
-                for(auto i=ft.first; i!=ft.second; ++i)
-                {
+                for(auto i=ft.first; i!=ft.second; ++i) {
                     ++x;
                     i->second.call({ex});
                 }
                 php::object obj = ex;
-                if(x==0)
-                {
+                if(x==0) {
                     std::cerr << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
-                    // std::cerr << "[" << time::iso() << "] (FATAL) " << ex.what() << "\n";
-                    boost::asio::post(gcontroller->context_x, [] () {
-                        gcontroller->status |= controller::controller_status::STATUS_EXCEPTION;
-                        gcontroller->context_x.stop();
-                        gcontroller->context_y.stop();
-                    });
-                }else
-                {
+                }else {
                     std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
-                    // std::cerr << "[" << time::iso() << "] (ERROR) " << ex.what() << "\n";
                 }
+                boost::asio::post(gcontroller->context_x, [] () {
+                    gcontroller->status |= controller::controller_status::STATUS_EXCEPTION;
+                    gcontroller->context_x.stop();
+                    gcontroller->context_y.stop();
+                });
             }
             return rv;
         }));
diff --git a/test/exception_1.php b/test/exception_1.php
index 5a4ccaa..ca46bfd 100644
--- a/test/exception_1.php
+++ b/test/exception_1.php
@@ -16,8 +16,9 @@ function test2() {
 
 // 可选
 flame\on("exception", function($ex) {
-    // echo $ex, "\n";
-    flame\quit();
+    echo "uncaught exception: ", $ex->getMessage() , ", you can do something with it.\n";
+    flame\time\sleep(1000);
+    echo "quit\n";
 });
 
-flame\run();
\ No newline at end of file
+flame\run();

From 44b762ac2735faa8642e659f72c8ef5ba8ad4d2c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 17 Mar 2019 14:54:47 +0800
Subject: [PATCH 074/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20quit/exception=20?=
 =?UTF-8?q?=E4=B8=80=E8=88=AC=E7=94=A8=E6=B3=95=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/core.php b/doc/core.php
index b6fbd86..639901e 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -51,6 +51,7 @@ function select(queue $q1, $q2/*, ...*/):queue {}
  *  * "exception" - 当协程发生未捕获异常, 执行对应的回调，并记录错误信息（随后进程会退出）;
  *  * "quit" - 退出消息, 一般用于平滑停止各种服务;
  * @param callable 回调函数
+ * 一般在 exception 中进行错误报告或报警，在 quit 中进行退出清理工作；
  */
 function on(string $event, callable $cb) {}
 /**

From 4448e0781f5b661faa7e984e192c16de790347ed Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 17 Mar 2019 15:03:45 +0800
Subject: [PATCH 075/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20quit/exception=20?=
 =?UTF-8?q?=E4=B8=80=E8=88=AC=E7=94=A8=E6=B3=95=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/doc/core.php b/doc/core.php
index 639901e..9488547 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -55,7 +55,10 @@ function select(queue $q1, $q2/*, ...*/):queue {}
  */
 function on(string $event, callable $cb) {}
 /**
- * 用于在用户异常处理流程中退出整个框架
+ * 用于在用户处理流程中退出
+ * 注意：
+ * 使用 PHP 内置 exit() 进行退出可能导致框架提供的清理、退出机制无效；
+ * 请示用本函数代替；
  */
 function quit() {}
 /**

From 7d15559ec2f76b90f5ba980af1102bb0eb205fab Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 18 Mar 2019 13:00:19 +0800
Subject: [PATCH 076/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9Aaddr2pair=20?=
 =?UTF-8?q?=E5=BC=95=E7=94=A8=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/server.cpp | 9 +++------
 src/tcp/server.cpp  | 9 +++++----
 src/tcp/tcp.h       | 3 +--
 3 files changed, 9 insertions(+), 12 deletions(-)

diff --git a/src/http/server.cpp b/src/http/server.cpp
index cc1bbd4..71b88d7 100644
--- a/src/http/server.cpp
+++ b/src/http/server.cpp
@@ -1,6 +1,6 @@
 #include "../controller.h"
 #include "../coroutine.h"
-#include "../tcp/tcp.h"
+#include "../udp/udp.h"
 #include "server.h"
 #include "_handler.h"
 
@@ -72,11 +72,8 @@ namespace flame::http
     php::value server::__construct(php::parameters &params)
     {
         php::string str = params[0];
-        auto pair = tcp::addr2pair(str);
-        if (pair.first.empty() || pair.second.empty())
-        {
-            throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
-        }
+        auto pair = udp::addr2pair(str);
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
         boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
         addr_.address(addr);
         addr_.port(std::atoi(pair.second.c_str()));
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index 51e0e5b..c0a1c50 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -1,9 +1,10 @@
 #include "../controller.h"
 #include "../coroutine.h"
+#include "../udp/udp.h"
+#include "../time/time.h"
 #include "server.h"
 #include "tcp.h"
 #include "socket.h"
-#include "../time/time.h"
 
 namespace flame::tcp
 {
@@ -33,7 +34,7 @@ namespace flame::tcp
     php::value server::__construct(php::parameters &params)
     {
         std::string str_addr = params[0];
-        auto pair = addr2pair(str_addr);
+        auto pair = udp::addr2pair(str_addr);
         if(pair.first.empty() || pair.second.empty())
         {
             throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
@@ -53,7 +54,7 @@ namespace flame::tcp
         acceptor_.bind(addr_, err);
         if(err)
         {
-            throw php::exception(zend_ce_error, 
+            throw php::exception(zend_ce_error,
                 (boost::format("failed to bind tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
         }
         acceptor_.listen(boost::asio::socket_base::max_listen_connections, err);
@@ -116,4 +117,4 @@ namespace flame::tcp
         }
         return nullptr;
     }
-}
\ No newline at end of file
+}
diff --git a/src/tcp/tcp.h b/src/tcp/tcp.h
index 57fa815..8da2a39 100644
--- a/src/tcp/tcp.h
+++ b/src/tcp/tcp.h
@@ -5,5 +5,4 @@ namespace flame::tcp
 {
     void declare(php::extension_entry &ext);
     php::value connect(php::parameters &params);
-    std::pair<std::string, std::string> addr2pair(const std::string& addr);
-} // namespace flame::tcp
\ No newline at end of file
+} // namespace flame::tcp

From d4da44ab7b828d268ae7e89d7053c2dc7188cc8e Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 18 Mar 2019 17:14:51 +0800
Subject: [PATCH 077/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E7=AE=80?=
 =?UTF-8?q?=E5=8C=96=20ONE=20=E6=9F=A5=E8=AF=A2=E9=95=BF=E5=BA=A6=E6=8B=BC?=
 =?UTF-8?q?=E6=8E=A5=E9=94=99=E8=AF=AF=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mysql/_connection_base.cpp | 23 ++++++++---------------
 src/mysql/_connection_pool.cpp | 17 +++++------------
 src/mysql/mysql.cpp            |  2 +-
 3 files changed, 14 insertions(+), 28 deletions(-)

diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index a2eaded..d976ae3 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -88,35 +88,29 @@ ESCAPE_FINISHED:;
     }
 
     php::object _connection_base::query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch) {
-        last_query_ = std::move(sql);
         MYSQL_RES* rst = nullptr;
         int err = 0;
-        boost::asio::post(gcontroller->context_y, [&err, &conn, &ch, query=&last_query_, &rst] ()
-        {
+        boost::asio::post(gcontroller->context_y, [&err, &conn, &ch, &rst, &sql] () {
             // 在工作线程执行查询
-            err = mysql_real_query(conn.get(), query->c_str(), query->size());
-            if (err == 0)
-            {
+            err = mysql_real_query(conn.get(), sql.c_str(), sql.size());
+            if (err == 0) {
                 // 防止锁表, 均使用 store 方式
                 rst = mysql_store_result(conn.get());
-                if (!rst && mysql_field_count(conn.get()) > 0)
-                {
+                if (!rst && mysql_field_count(conn.get()) > 0) {
                     err = -1;
                 }
             }
             ch.resume();
         });
         ch.suspend();
-        if(err != 0)
-        {
+        last_query_ = sql;
+        if(err != 0) {
             int err = mysql_errno(conn.get());
             throw php::exception(zend_ce_exception,
                                  (boost::format("failed to query MySQL server: (%1%) %2%") % err % mysql_error(conn.get())).str(),
                                  err);
         }
-
-        if(rst) // 存在结果集
-        {
+        if(rst) { // 存在结果集
             php::object obj(php::class_entry<result>::entry());
             result* ptr = static_cast<result*>(php::native(obj));
             ptr->cl_.reset(new _connection_lock(conn));
@@ -126,8 +120,7 @@ ESCAPE_FINISHED:;
             obj.set("stored_rows", static_cast<std::int64_t>(mysql_num_rows(rst)));
             return std::move(obj);
         }
-        else // 更新型操作
-        {
+        else { // 更新型操作
             php::array data(2);
             data.set(php::string("affected_rows", 13), static_cast<std::int64_t>(mysql_affected_rows(conn.get())));
             data.set(php::string("insert_id", 9), static_cast<std::int64_t>(mysql_insert_id(conn.get())));
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 84b1fea..5f6a338 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -85,12 +85,9 @@ namespace flame::mysql
 
     void _connection_pool::release(MYSQL *c)
     {
-        if (await_.empty())
-        { // 无等待分配的请求
+        if (await_.empty()) { // 无等待分配的请求
             conn_.push_back({c, std::chrono::steady_clock::now()});
-        }
-        else
-        { // 立刻分配使用
+        }else { // 立刻分配使用
             // 每次连接复用前，需要清理状态;
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
@@ -140,18 +137,14 @@ namespace flame::mysql
         tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
             if(error) return; // 当前对象销毁时会发生对应的 abort 错误
             auto now = std::chrono::steady_clock::now();
-            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)
-            {
+            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;) {
                 // 超低水位，关闭不活跃或已丢失的连接
                 auto duration = now - (*i).ttl;
-                if (duration > std::chrono::seconds(60) || mysql_ping((*i).conn) != 0)
-                {
+                if (duration > std::chrono::seconds(60) || mysql_ping((*i).conn) != 0) {
                     mysql_close((*i).conn);
                     --size_;
                     i = conn_.erase(i);
-                }
-                else
-                {
+                } else {
                     ++i;
                 } // 所有连接还活跃（即使超过低水位）
             }
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index b68a81d..15a4132 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -546,7 +546,7 @@ namespace flame::mysql {
     }
     void build_one(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
     {
-        buf.append("SELECT * FROM ", 15);
+        buf.append("SELECT * FROM ", 14);
         // 表名
         _connection_base::escape(cc, buf, params[0], '`');
         // 条件

From 027d30ef42c40986cca247f819709d387a239752 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 19 Mar 2019 14:03:15 +0800
Subject: [PATCH 078/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=87=8F?=
 =?UTF-8?q?=E5=B0=91=20MySQL=20=E5=A4=8D=E7=94=A8=20PING=20=E8=B0=83?=
 =?UTF-8?q?=E7=94=A8=EF=BC=9B=E6=96=B0=E5=A2=9E=EF=BC=9ASSL=20=E5=8F=82?=
 =?UTF-8?q?=E6=95=B0=E5=B9=B6=E9=BB=98=E8=AE=A4=E5=85=B3=E9=97=AD=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mongodb.php                |  2 +-
 doc/mysql.php                  |  4 +++-
 doc/redis.php                  |  2 +-
 src/mysql/_connection_pool.cpp | 43 ++++++++++++++++++++++------------
 src/mysql/mysql.cpp            |  8 +++++++
 5 files changed, 41 insertions(+), 18 deletions(-)

diff --git a/doc/mongodb.php b/doc/mongodb.php
index cec1d25..d8bb57e 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -5,7 +5,7 @@
  * 1. 同一协程中连续进行 MongoDB 查询操作, 且游标不读取数据不销毁 (或 不读取完不销毁) 可能导致进程死锁; (请将游标读取完毕 或 主动 unset 释放游标对象)
  * 2. 对应 MongoDB ObjectID 映射类型 class flame\mongodb\object_id;
  * 3. 对应 MongoDB DateTime 映射类型 class flame\mongodb\date_time;
- * 4. 单客户端内连接池上限大小为 8（单进程）；
+ * 4. 单客户端内连接池上限大小为 6（单进程）；
  */
 namespace flame\mongodb;
 
diff --git a/doc/mysql.php b/doc/mysql.php
index 2c8cfd1..609a05d 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -5,7 +5,8 @@
  * 1. 同一协程中连续进行 MySQL 查询操作, 且结果集数据不读取且不销毁(或不读取完且不销毁)可能导致进程死锁; (请将结果集读取完 或 主动 unset 释放结果集对象)
  * 2. MySQL 读取数据时数值型字段读取映射为 PHP 对应数值类型；`DATETIME` 映射为 PHP 内置类型 `DateTime`;
  * 3. 客户端提供的简化方法如 `insert()` `update()` 也同时支持与上述反向的映射;
- * 4. 单客户端内连接池上限大小为 4（单进程）；
+ * 4. 单客户端内连接池上限大小为 6（单进程）；
+ * 5. 对 MySQL < 5.7 版本特定用法可能发生连接复用前后干扰的情况；
  */
 namespace flame\mysql;
 /**
@@ -14,6 +15,7 @@
  *  mysql://{user}:{pass}@{host}:{port}/{database}?opt1=val1
  *  目前可用的选项如下:
  *      * "charset" => 字符集
+ *      * "ssl" => SSL 模式，可用值 "disabled" (默认) / "preferred" / "verify_ca" / "verify_identity"
  * 注意：若服务端配置默认字符集与上述字符集不同，每次连接使用（复用）均会进行字符集设置，这里可能会产生部分额外消耗；
  * @return client 客户端对象
  */
diff --git a/doc/redis.php b/doc/redis.php
index 11528ad..d8ac01f 100644
--- a/doc/redis.php
+++ b/doc/redis.php
@@ -3,7 +3,7 @@
  * 提供 Redis 基本客户端封装，内部使用连接池：
  * 注意：
  * 1. 暂未实现 SUBSCRIBE 相关命令；
- * 2. 单客户端内连接池大小上限为 3 （单进程）；
+ * 2. 单客户端内连接池大小上限为 6 （单进程）；
  */
 namespace flame\redis;
 
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 5f6a338..95a1018 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -5,12 +5,10 @@ namespace flame::mysql
 {
     _connection_pool::_connection_pool(url u)
         : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+        , reset_(boost::logic::indeterminate)
         , charset_(boost::logic::indeterminate)
     {
-        if(url_.port < 10) url_.port = 3306;
-        if(!url_.query.count("charset")) {
-            url_.query["charset"] = "utf8";
-        }
+
     }
 
     _connection_pool::~_connection_pool()
@@ -33,9 +31,9 @@ namespace flame::mysql
                 // RESUME 需要在主线程进行
                 ch.resume();
             });
-            while (!conn_.empty())
-            {
-                if (mysql_ping(conn_.front().conn) == 0)
+            auto now = std::chrono::steady_clock::now();
+            while (!conn_.empty()) {
+                if (now - conn_.front().ttl < std::chrono::seconds(15) || mysql_ping(conn_.front().conn) == 0)
                 { // 可用连接
                     MYSQL *c = conn_.front().conn;
                     conn_.pop_front();
@@ -74,10 +72,24 @@ namespace flame::mysql
         MYSQL* c = mysql_init(nullptr);
         // 这里的 CHARSET 设定会被 reset_connection 重置为系统值
         mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
+        unsigned int ssl = SSL_MODE_DISABLED;
+        if(url_.query.count("ssl") > 0) {
+            std::string sstr = url_.query["ssl"];
+            if(strncasecmp(sstr.c_str(), "preferred", 9) == 0) {
+                ssl = SSL_MODE_PREFERRED;
+            }else if(strncasecmp(sstr.c_str(), "required", 8) == 0) {
+                ssl = SSL_MODE_REQUIRED;
+            }else if(strncasecmp(sstr.c_str(), "verify_ca", 9) == 0) {
+                ssl = SSL_MODE_VERIFY_CA;
+            }else if(strncasecmp(sstr.c_str(), "verify_identity", 15) == 0) {
+                ssl = SSL_MODE_VERIFY_IDENTITY;
+            }
+        }
+        mysql_options(c, MYSQL_OPT_SSL_MODE, &ssl);
         unsigned int timeout = 5; // 连接超时
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
-        if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0))
-        {
+        if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
+            mysql_close(c);
             return nullptr;
         }
         return c;
@@ -92,12 +104,14 @@ namespace flame::mysql
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
             if(boost::logic::indeterminate(reset_)) {
-                if(mysql_reset_connection(c)) reset_ = true;
+                if(mysql_reset_connection(c) == 0) reset_ = true;
                 else reset_ = false;
             }else if(reset_) {
                 mysql_reset_connection(c);
             }else{
-                mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+                // FIXME: 策略上 fallback 使用下面流程进行连接重置，但此处逻辑可能发生故障异常
+                // (Access denied for user 'abc'@'172.30.20.2' (using password: NO)
+                // int r = mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
             }
             // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
             if(boost::logic::indeterminate(charset_)) query_charset(c);
@@ -117,10 +131,7 @@ namespace flame::mysql
 
     void _connection_pool::query_charset(MYSQL* c) {
         // 使用 mysql_get_charactor_set_name() 获取到的字符集与下述查询不同
-        int err = mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59);
-        charset_ = false; // 乐观
-        if (err == 0)
-        {
+        if(0 == mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59)) {
             std::unique_ptr<MYSQL_RES, void (*)(MYSQL_RES*)> rst(mysql_store_result(c), mysql_free_result);
             if (!rst) return;
             MYSQL_ROW row = mysql_fetch_row(rst.get());
@@ -128,6 +139,8 @@ namespace flame::mysql
             unsigned long* len = mysql_fetch_lengths(rst.get());
             std::string charset(row[1], len[1]);
             charset_ = charset != url_.query["charset"];
+        }else{ // 这里忽略了 charset 查询的错误
+            charset_ = false;
         }
     }
 
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 15a4132..0e4f40d 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -33,6 +33,14 @@ namespace flame::mysql {
     php::value connect(php::parameters& params)
     {
         url u(params[0]);
+        if(u.port < 10) u.port = 3306;
+        if(!u.query.count("charset")) {
+            u.query["charset"] = "utf8";
+        }
+        if(!u.query.count("ssl")) {
+            u.query["ssl"] = "disabled";
+        }
+
         php::object obj(php::class_entry<client>::entry());
         client *ptr = static_cast<client *>(php::native(obj));
         ptr->cp_.reset(new _connection_pool(u));

From 8c9caf81315c36342b24f32ad384391d979ea1b1 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 19 Mar 2019 17:33:23 +0800
Subject: [PATCH 079/146] =?UTF-8?q?=E8=BF=98=E5=8E=9F=20mysql=20->=206.1.1?=
 =?UTF-8?q?1=20=E9=A9=B1=E5=8A=A8=EF=BC=88=E5=85=BC=E5=AE=B9=E6=80=A7?=
 =?UTF-8?q?=E9=97=AE=E9=A2=98/Connect/ChangeUser=E7=AD=89=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                 |  2 +-
 src/mysql/_connection_pool.cpp | 14 ++++++++------
 src/mysql/_connection_pool.h   |  3 ++-
 src/mysql/client.cpp           |  2 +-
 4 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f8aaf2e..a8f1b27 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,7 +25,7 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
 set(VENDOR_PHPEXT /data/vendor/phpext-1.1.3)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
-set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
+set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 95a1018..e7f6bba 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -103,15 +103,13 @@ namespace flame::mysql
             // 每次连接复用前，需要清理状态;
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
-            if(boost::logic::indeterminate(reset_)) {
-                if(mysql_reset_connection(c) == 0) reset_ = true;
-                else reset_ = false;
-            }else if(reset_) {
+            if(boost::logic::indeterminate(reset_)) query_version(c);
+            if(reset_) {
                 mysql_reset_connection(c);
             }else{
-                // FIXME: 策略上 fallback 使用下面流程进行连接重置，但此处逻辑可能发生故障异常
+                // FIXME: 8.0.15 驱动使用下面函数回出现错误
                 // (Access denied for user 'abc'@'172.30.20.2' (using password: NO)
-                // int r = mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+                mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
             }
             // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
             if(boost::logic::indeterminate(charset_)) query_charset(c);
@@ -144,6 +142,10 @@ namespace flame::mysql
         }
     }
 
+    void _connection_pool::query_version(MYSQL* c) {
+        reset_ = mysql_get_server_version(c) >= 50700;
+    }
+
     void _connection_pool::sweep() {
         tm_.expires_from_now(std::chrono::seconds(60));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index d92b824..304ba3b 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -32,10 +32,11 @@ namespace flame::mysql
 
         boost::logic::tribool reset_;
         boost::logic::tribool charset_;
-        
+
         MYSQL* create();
         void release(MYSQL *c);
         void query_charset(MYSQL* c);
+        void query_version(MYSQL* c);
     };
 
 } // namespace flame::mysql
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 9d20b56..ba4560a 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -163,6 +163,6 @@ namespace flame::mysql
     {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
-        return mysql_get_server_version(conn.get());
+        return php::string(mysql_get_host_info(conn.get()));
     }
 } // namespace flame::mysql

From f5768779a2a00a6e613ad3dda768c848b16b95fb Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 19 Mar 2019 20:48:09 +0800
Subject: [PATCH 080/146] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20mysqlc=208.0.15=20?=
 =?UTF-8?q?=E4=BD=86=E8=A1=A5=E5=85=85=20proxy=20=E5=8F=82=E6=95=B0?=
 =?UTF-8?q?=EF=BC=8C=E5=85=81=E8=AE=B8=E7=A6=81=E7=94=A8=E5=A4=8D=E7=94=A8?=
 =?UTF-8?q?=E9=87=8D=E7=BD=AE=E6=B5=81=E7=A8=8B=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                 |  2 +-
 doc/mysql.php                  |  1 +
 src/mysql/_connection_pool.cpp | 38 +++++++++++++++++++++-------------
 src/mysql/_connection_pool.h   | 11 ++++++++--
 src/mysql/client.cpp           |  2 +-
 5 files changed, 36 insertions(+), 18 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index a8f1b27..f8aaf2e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,7 +25,7 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
 set(VENDOR_PHPEXT /data/vendor/phpext-1.1.3)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
-set(VENDOR_MYSQL /data/vendor/mysqlc-6.1.11)
+set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
diff --git a/doc/mysql.php b/doc/mysql.php
index 609a05d..2f52cfb 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -16,6 +16,7 @@
  *  目前可用的选项如下:
  *      * "charset" => 字符集
  *      * "ssl" => SSL 模式，可用值 "disabled" (默认) / "preferred" / "verify_ca" / "verify_identity"
+ *      * "proxy" => 当前地址是否是代理服务（已提供连接复用机制） "1" - 是，已提供连接复用机制 (禁用框架提供的复用重置机制)， "0" - 否，未提供（默认）
  * 注意：若服务端配置默认字符集与上述字符集不同，每次连接使用（复用）均会进行字符集设置，这里可能会产生部分额外消耗；
  * @return client 客户端对象
  */
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index e7f6bba..9b286d6 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -5,8 +5,7 @@ namespace flame::mysql
 {
     _connection_pool::_connection_pool(url u)
         : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
-        , reset_(boost::logic::indeterminate)
-        , charset_(boost::logic::indeterminate)
+        , flag_(FLAG_UNKNOWN)
     {
 
     }
@@ -72,6 +71,9 @@ namespace flame::mysql
         MYSQL* c = mysql_init(nullptr);
         // 这里的 CHARSET 设定会被 reset_connection 重置为系统值
         mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
+        // 8.0.15 默认使用 caching_sha2_password 进行认证，底版本不支持
+        mysql_options(c, MYSQL_DEFAULT_AUTH, "mysql_native_password");
+        // SSL 参数
         unsigned int ssl = SSL_MODE_DISABLED;
         if(url_.query.count("ssl") > 0) {
             std::string sstr = url_.query["ssl"];
@@ -103,18 +105,20 @@ namespace flame::mysql
             // 每次连接复用前，需要清理状态;
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
-            if(boost::logic::indeterminate(reset_)) query_version(c);
-            if(reset_) {
+            if(flag_ == FLAG_UNKNOWN) {
+                query_version(c);
+                query_charset(c); // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
+            }
+            if(flag_ & FLAG_REUSE_BY_RESET) {
                 mysql_reset_connection(c);
-            }else{
-                // FIXME: 8.0.15 驱动使用下面函数回出现错误
-                // (Access denied for user 'abc'@'172.30.20.2' (using password: NO)
+            }else if(flag_ & FLAG_REUSE_BY_CUSER) {
                 mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+            }else if(flag_ & FLAG_REUSE_BY_PROXY) {
+                // PROXY 已处理复用流程，此处无需处理
+            }
+            if(flag_ & FLAG_CHARSET_DIFFER) {
+                mysql_set_character_set(c, url_.query["charset"].c_str());
             }
-            // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
-            if(boost::logic::indeterminate(charset_)) query_charset(c);
-            if(charset_) mysql_set_character_set(c, url_.query["charset"].c_str());
-
             std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
             await_.pop_front();
             auto self = this->shared_from_this();
@@ -136,14 +140,20 @@ namespace flame::mysql
             if(!row) return;
             unsigned long* len = mysql_fetch_lengths(rst.get());
             std::string charset(row[1], len[1]);
-            charset_ = charset != url_.query["charset"];
+            flag_ |= charset == url_.query["charset"] ? FLAG_CHARSET_EQUAL : FLAG_CHARSET_DIFFER;
         }else{ // 这里忽略了 charset 查询的错误
-            charset_ = false;
+            flag_ |= FLAG_CHARSET_DIFFER;
         }
     }
 
     void _connection_pool::query_version(MYSQL* c) {
-        reset_ = mysql_get_server_version(c) >= 50700;
+        if(url_.query.count("proxy") > 0) {
+            if(std::stoi(url_.query["proxy"]) > 0) {
+                flag_ |= FLAG_REUSE_BY_PROXY;
+                return;
+            }
+        }
+        flag_ |= mysql_get_server_version(c) >= 50700 ? FLAG_REUSE_BY_RESET : FLAG_REUSE_BY_CUSER;
     }
 
     void _connection_pool::sweep() {
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index 304ba3b..7b31501 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -30,8 +30,15 @@ namespace flame::mysql
         std::list<connection_t> conn_;
         boost::asio::steady_timer tm_;
 
-        boost::logic::tribool reset_;
-        boost::logic::tribool charset_;
+        int flag_;
+        enum {
+            FLAG_UNKNOWN        = 0x00,
+            FLAG_REUSE_BY_RESET = 0x01,
+            FLAG_REUSE_BY_CUSER = 0x02,
+            FLAG_REUSE_BY_PROXY = 0x04,
+            FLAG_CHARSET_EQUAL  = 0x10,
+            FLAG_CHARSET_DIFFER = 0x20,
+        };
 
         MYSQL* create();
         void release(MYSQL *c);
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index ba4560a..436c60c 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -163,6 +163,6 @@ namespace flame::mysql
     {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
-        return php::string(mysql_get_host_info(conn.get()));
+        return php::string(mysql_get_server_info(conn.get()));
     }
 } // namespace flame::mysql

From f191d83669e2c4b5929bb84c1b20bc7284730543 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 21 Mar 2019 12:49:49 +0800
Subject: [PATCH 081/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E9=80=80?=
 =?UTF-8?q?=E5=87=BA=E6=97=B6=E9=97=B4=E8=8C=83=E5=9B=B4=E6=8E=A7=E5=88=B6?=
 =?UTF-8?q?=E5=A4=B1=E6=95=88=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/controller.cpp b/src/controller.cpp
index bc45cc8..546ee53 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -45,7 +45,7 @@ namespace flame {
 	void controller::initialize(const std::string& title, const php::array& options) {
 		// 多进程模式退出超时时间，默认 1s 超时强制退出
 		if(options.exists("timeout")) {
-			worker_quit = std::max(static_cast<int>(options.get("timeout")), 64);
+			worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 1), 128);
 		}else{
 			worker_quit = 1;
 		}

From 837fb74f2b362712f432f4a66a944e8a144dda0c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 3 Apr 2019 15:51:35 +0800
Subject: [PATCH 082/146] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20=E5=AE=8C?=
 =?UTF-8?q?=E5=96=84=20doc=20=E6=96=87=E6=A1=A3,=20=E9=98=B2=E6=AD=A2?=
 =?UTF-8?q?=E5=9C=A8=E6=9F=90=E4=BA=9B=E8=A7=A3=E9=87=8A=E7=A8=8B=E5=BA=8F?=
 =?UTF-8?q?=E4=B8=8B=E6=8A=A5=E9=94=99;=20=E4=BC=98=E5=8C=96:=20=E5=BD=93?=
 =?UTF-8?q?=20RabbitMQ=20=E8=BF=9E=E6=8E=A5=E9=80=9A=E9=81=93=E5=BC=82?=
 =?UTF-8?q?=E5=B8=B8=E6=97=B6=E6=8A=9B=E5=87=BA=E5=BC=82=E5=B8=B8;=20?=
 =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E5=9C=A8=20RabbitMQ=20=E8=BF=9E=E6=8E=A5?=
 =?UTF-8?q?=E5=90=8E=E6=8C=89=E5=91=A8=E6=9C=9F=E5=8F=91=E9=80=81=E5=BF=83?=
 =?UTF-8?q?=E8=B7=B3;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt              |   2 +-
 doc/compress.php            |   8 +-
 doc/core.php                |  16 ++-
 doc/encoding.php            |   8 +-
 doc/hash.php                |   6 +-
 doc/http.php                |  80 ++++++++++----
 doc/kafka.php               |   8 +-
 doc/mongodb.php             | 142 +++++++++++++++++++------
 doc/mysql.php               |  85 +++++++++++----
 doc/os.php                  |  20 +++-
 doc/rabbitmq.php            |  17 ++-
 doc/redis.php               |  12 ++-
 doc/tcp.php                 |   4 +-
 doc/time.php                |   8 +-
 doc/udp.php                 |   8 +-
 src/controller.cpp          |  61 +++--------
 src/http/_handler.cpp       |   7 +-
 src/http/server_request.cpp |   6 +-
 src/mongodb/collection.cpp  |  15 ++-
 src/mysql/client.cpp        |   1 -
 src/mysql/mysql.cpp         |   2 +-
 src/rabbitmq/_client.cpp    | 205 +++++++++++++++++++-----------------
 src/rabbitmq/_client.h      |  22 ++--
 src/rabbitmq/consumer.cpp   |   9 +-
 src/rabbitmq/consumer.h     |   2 +-
 src/rabbitmq/rabbitmq.cpp   |   8 +-
 src/vendor.h                |   2 +-
 27 files changed, 488 insertions(+), 276 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f8aaf2e..639a9ab 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,7 +17,7 @@ add_library(FLAME SHARED ${SOURCES})
 # -----------------------------------------------------------------------------
 # 依赖项目录
 # -----------------------------------------------------------------------------
-set(VENDOR_PHP /data/vendor/php-7.2.15)
+set(VENDOR_PHP /data/vendor/php-7.2.16)
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
diff --git a/doc/compress.php b/doc/compress.php
index fcbdb2f..460f9b4 100644
--- a/doc/compress.php
+++ b/doc/compress.php
@@ -6,8 +6,12 @@
 /**
  * snappy 压缩（与 https://github.com/kjdev/php-ext-snappy 兼容）
  */
-function snappy_compress(string $data):string {}
+function snappy_compress(string $data):string {
+    return "compressed data";
+}
 /**
  * snappy 解压（与 https://github.com/kjdev/php-ext-snappy 兼容）
  */
-function snappy_uncompress(string $data):string {}
+function snappy_uncompress(string $data):string {
+    return "original string";
+}
diff --git a/doc/core.php b/doc/core.php
index 9488547..0af585b 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -30,11 +30,15 @@ function go(callable $cb) {}
 /**
  * 获取一个当前协程 ID 标识
  */
-function co_id():int {}
+function co_id():int {
+    return 123456;
+}
 /**
  * 获取运行中的协程数量
  */
-function co_count():int {}
+function co_count():int {
+    return 8;
+}
 /**
  * 框架调度, 上述协程会在框架开始调度运行后启动
  * 注意：协程异步调度需要 run() 才能启动执行；
@@ -44,7 +48,9 @@ function run() {}
  * 从若干个队列中选择(等待)一个有数据队列
  * @return 若所有通道已关闭, 返回 null; 否则返回一个有数据的通道, 即: 可以无等待 pop()
  */
-function select(queue $q1, $q2/*, ...*/):queue {}
+function select(queue $q1, $q2/*, ...*/):queue {
+    return new queue();
+}
 /**
  * 监听框架的通知
  * @param string $event 目前消息存在以下两种:
@@ -85,7 +91,9 @@ function close() {}
     /**
      * 队列是否已关闭
      */
-    function is_closed(): boolean {}
+    function is_closed(): bool {
+        return false;
+    }
 }
 /**
  * 协程互斥量（锁）
diff --git a/doc/encoding.php b/doc/encoding.php
index 974aa35..8d8f37f 100644
--- a/doc/encoding.php
+++ b/doc/encoding.php
@@ -6,8 +6,12 @@
 /**
  * 将 PHP 数组转换为 BSON 格式，并返回实际 BSON 的原始二进制数据
  */
-function bson_encode(array $data):string {}
+function bson_encode(array $data):string {
+    return "encoded string";
+}
 /**
  * 还原由上述 `bson_encode` 生成的原始二进制数据，返回对应的数组
  */
-function bson_decode(string $data):array {}
+function bson_decode(string $data):array {
+    return [];
+}
diff --git a/doc/hash.php b/doc/hash.php
index 2ff9919..080d30a 100644
--- a/doc/hash.php
+++ b/doc/hash.php
@@ -7,15 +7,15 @@
  * 默认情况 kafka 内部使用的哈希算法（返回 )
  * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
  */
-function murmur2(string $data, bool $raw = false): mixed {}
+function murmur2(string $data, bool $raw = false) {}
 /**
  * 一种效率极高且哈希结果理想度高的哈希算法（返回 uint64)
  * @param int $seed 用于对哈希结果进行预期内的调整
  * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
  */
-function xxh64(string $data, int $seed = 0, bool $raw = false): mixed {}
+function xxh64(string $data, int $seed = 0, bool $raw = false) {}
 /**
  * 根据 EMCA 定义实现的 crc64 哈希（与 Golang 对应 hash/crc64 ECMA 计算值一直）
  * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
  */
-function crc64(string $data, bool $raw = false): mixed {}
+function crc64(string $data, bool $raw = false) {}
diff --git a/doc/http.php b/doc/http.php
index d97f5b2..15536ba 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -10,23 +10,33 @@
 /**
  * 使用默认客户端, 执行请求
  */
-function exec(client_request $req): client_response {}
+function exec(client_request $req): client_response {
+    return new client_response();
+}
 /**
  * 使用默认客户端, 构建并执行一个 GET 请求
  */
-function get(string $url, int $timeout = 3000): client_response {}
+function get(string $url, int $timeout = 3000): client_response {
+    return new client_response();
+}
 /**
  * 使用默认客户端, 构建并执行一个 POST 请求
  */
-function post(string $url, mixed $data, int $timeout = 3000): client_response {}
+function post(string $url, mixed $data, int $timeout = 3000): client_response {
+    return new client_response();
+}
 /**
  * 使用默认客户端, 构建并执行一个 PUT 请求
  */
-function put(string $url, mixed $data, int $timeout = 3000): client_response {}
+function put(string $url, mixed $data, int $timeout = 3000): client_response {
+    return new client_response();
+}
 /**
  * 使用默认客户端, 构建并执行一个 DELETE 请求
  */
-function delete(string $url, int $timeout = 3000): client_response {}
+function delete(string $url, int $timeout = 3000): client_response {
+    return new client_response();
+}
 
 /**
  * 客户端，用于执行请求
@@ -41,23 +51,33 @@ function __construct(array $options = []) {}
     /**
      * 执行请求, 返回响应
      */
-    function exec(client_request $req): client_response {}
+    function exec(client_request $req): client_response {
+        return new client_response();
+    }
     /**
      * 构建并执行一个 GET 请求
      */
-    function get(string $url, int $timeout = 3000): client_response {}
+    function get(string $url, int $timeout = 3000): client_response {
+        return new client_response();
+    }
     /**
      * 构建并执行一个 POST 请求
      */
-    function post(string $url, mixed $data, int $timeout = 3000) {}
+    function post(string $url, mixed $data, int $timeout = 3000): client_response {
+        return new client_response();
+    }
     /**
      * 构建并执行一个 PUT 请求
      */
-    function put(string $url, mixed $data, int $timeout = 3000): client_response {}
+    function put(string $url, mixed $data, int $timeout = 3000): client_response {
+        return new client_response();
+    }
     /**
      * 构建并执行一个 DELETE 请求
      */
-    function delete(string $url, int $timeout = 3000): client_response {}
+    function delete(string $url, int $timeout = 3000): client_response {
+        return new client_response();
+    }
 }
 /**
  * 客户端请求对象，用于拼装生成一个客户端请求（待执行）
@@ -112,14 +132,14 @@ class client_response {
      *  * "application/json"
      *  * "application/x-www-form-urlencoded"
      *  * "multipart/form-data"
-     * 其他类型保持文本的原始数据(与 $rawBody 相同);
+     * 其他类型保持文本的原始数据(与 $raw_body 相同);
      */
     public $body;
     /**
      * 原始请求体数据
      * @property string
      */
-    public $rawBody;
+    public $raw_body;
 }
 /**
  * HTTP 服务器
@@ -140,48 +160,66 @@ function __construct(string $address) {}
      * 其中 $match 参数标识此请求是否匹配了定义的 path 处理器;
      * 当 callback 函数返回 false 时, 将**不再**执行后续处理器
      */
-    function before(callable $cb):server {}
+    function before(callable $cb):server {
+        return $this;
+    }
     /**
      * 每次 HTTP 请求在执行对应 path 处理器后, 会执行下述设置的回调;
      * 回调形式同 before();
      * 注意: 若 before 回调或 path 回调返回了 false, 此 after 回调将不再执行;
      */
-    function after(callable $cb):server {}
+    function after(callable $cb):server {
+        return $this;
+    }
     /**
      * 设置一个处理 "PUT $path HTTP/1.1" 请求的路径 path 处理器;
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
-    function put(string $path, callable $cb):server {}
+    function put(string $path, callable $cb):server {
+        return $this;
+    }
     /**
      * 设置一个处理 "DELETE $path HTTP/1.1" 请求的路径 path 处理器;
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
-    function delete(string $path, callable $cb):server {}
+    function delete(string $path, callable $cb):server {
+        return $this;
+    }
     /**
      * 设置一个处理 "POST $path HTTP/1.1" 请求的路径 path 处理器;
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
-    function post(string $path, callable $cb):server {}
+    function post(string $path, callable $cb):server {
+        return $this;
+    }
     /**
      * 设置一个处理 "PATCH $path HTTP/1.1" 请求的路径 path 处理器;
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
-    function patch(string $path, callable $cb):server {}
+    function patch(string $path, callable $cb):server {
+        return $this;
+    }
     /**
      * 设置一个处理 "GET $path HTTP/1.1" 请求的路径 path 处理器;
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
-    function get(string $path, callable $cb):server {}
+    function get(string $path, callable $cb):server {
+        return $this;
+    }
     /**
      * 设置一个处理 "HEAD $path HTTP/1.1" 请求的路径 path 处理器;
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
-    function head(string $path, callable $cb):server {}
+    function head(string $path, callable $cb):server {
+        return $this;
+    }
     /**
      * 设置一个处理 "OPTIONS $path HTTP/1.1" 请求的路径 path 处理器;
      * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
      */
-    function options(string $path, callable $cb):server {}
+    function options(string $path, callable $cb):server {
+        return $this;
+    }
     /**
      * 启动服务器, 监听请求并执行对应回调
      * 注意: 运行服务器将阻塞当前协程;
diff --git a/doc/kafka.php b/doc/kafka.php
index d3e5c04..a641352 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -13,7 +13,9 @@
  *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
  * @param array $topics 待消费的数据源 TOPIC 名称列表
  */
-function consume(array $config, array $topics) {}
+function consume(array $config, array $topics):consumer {
+    return new consumer();
+}
 
 /**
  * @param array $config 基本 Kafka 配置, 一下两个选项必要:
@@ -23,7 +25,9 @@ function consume(array $config, array $topics) {}
  *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
  * @param array $topics 待生产的目标 TOPIC 名称列表
  */
-function produce(array $config, array $topics) {}
+function produce(array $config, array $topics):producer {
+    return new producer();
+}
 /**
  * 消费者
  */
diff --git a/doc/mongodb.php b/doc/mongodb.php
index d8bb57e..2114f9a 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -14,25 +14,38 @@
  * @param string $url 连接地址, 形如:
  *  mongodb://{username}:{password}@{host1}:{port1},{host2}:{port2}/{database}?{option1}={rs1}&{option2}=...
  */
-function connect(string $url):client {}
+function connect(string $url):client {
+    //....
+    return new client();
+}
 /**
  * MongoDB 客户端（内部使用连接池）
  */
 class client {
+    const COMMAND_RAW        = 0;
+    const COMMAND_READ       = 1;
+    const COMMAND_WRITE      = 2;
+    const COMMAND_READ_WRITE = 3;
     /**
      * 执行以 $command 描述的命令;
      * 请参考：https://docs.mongodb.com/master/reference/command/ 编写指令；
-     * 注意: 更新型指令需要自行指定 $write = true
+     * $type
+     * 注意: 更新型指令需要自行指定 $write =
+     * @return mixed array/cursor
      */
-    function execute(array $command, bool $write = false):mixed {}
+    function execute(array $command, int $type = 0) {}
     /**
      * 获取指定名称的 colleciton 对象
      */
-    function collection($name):collection {}
+    function collection($name):collection {
+        return new collection();
+    }
     /**
      * 与 `collection()` 相同, 返回指定的 collection 对象
      */
-    function __get($name):collection {}
+    function __get($name):collection {
+        return new collection();
+    }
     /**
      * @return bool 永远为 true ;
      */
@@ -52,9 +65,15 @@ class collection {
      *      ...
      *  )
      */
-    function insert(array $data, bool $ordered = true):array {}
-    function insert_many(array $data, bool $ordered = true):array {}
-    function insert_one(array $data):array {}
+    function insert(array $data, bool $ordered = true):array {
+        return [];
+    }
+    function insert_many(array $data, bool $ordered = true):array {
+        return [];
+    }
+    function insert_one(array $data):array {
+        return [];
+    }
     /**
      * 执行删除
      * @return array 形式同 insert() 类似：
@@ -64,9 +83,15 @@ function insert_one(array $data):array {}
      *      ...
      *  )
      */
-    function delete(array $query, int $limit = 0):array {}
-    function delete_many(array $query, int $limit = 0):array {}
-    function delete_one(array $query):array {}
+    function delete(array $query, int $limit = 0):array {
+        return [];
+    }
+    function delete_many(array $query, int $limit = 0):array {
+        return [];
+    }
+    function delete_one(array $query):array {
+        return [];
+    }
     /**
      * 执行更新动作
      * @param array $update 一般使用类似如下形式进行更新设置：
@@ -78,49 +103,74 @@ function delete_one(array $query):array {}
      *      ...
      *  )
      */
-    function update(array $query, array $update, $upsert = false):array {}
-    function update_many(array $query, array $update, $upsert = false):array {}
-    function update_one(array $query, array $update, $upsert = false):array {}
+    function update(array $query, array $update, $upsert = false):array {
+        return [];
+    }
+    function update_many(array $query, array $update, $upsert = false):array {
+        return [];
+    }
+    function update_one(array $query, array $update, $upsert = false):array {
+        return [];
+    }
     /**
      * 执行指定查询，返回游标对象
      * @param array $sort 一般使用如下形式表达排序：
      *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
      * @param mixed $limit 可以为 int 设置 `limit` 值，或 array 设置 `[$skip, $limit]`
      */
-    function find(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {}
-    function find_many(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {}
+    function find(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {
+        return new cursor();
+    }
+    function find_many(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {
+        return new cursor();
+    }
 
     /**
      * 查询并返回单个文档
      * @param array $sort 一般使用如下形式表达排序：
      *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
      */
-    function find_one(array $query, array $sort = null): array {}
-    function one(array $query, array $sort = null): array {}
+    function find_one(array $query, array $sort = null): array {
+        return [];
+    }
+    function one(array $query, array $sort = null): array {
+        return [];
+    }
     /**
      * 查询并返回单个文档的指定字段值
      * @param array $sort 一般使用如下形式表达排序：
      *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
+     * @return mixed 字段类型映射请参见顶部说明；
      */
-    function get(array $query, string $field, array $sort = null): mixed {}
+    function get(array $query, string $field, array $sort = null) {
+        return null;
+    }
     /**
      * 查询并返回匹配文档数量
      */
-    function count(array $query): int {}
+    function count(array $query): int {
+        return 0;
+    }
     /**
      * 执行指定聚合操作，返回游标对象；
      * 请参考 https://docs.mongodb.com/master/reference/operator/aggregation-pipeline/ 编写；
      */
-    function aggregate(array $pipeline): cursor {}
+    function aggregate(array $pipeline, array $options = []): cursor {
+        return new cursor();
+    }
     /**
      * 查找满足条件的第一个文档，删除并将其返回
      */
-    function find_and_delete(array $query, array $sort = null, $upsert = false, $fields = null) {}
+    function find_and_delete(array $query, array $sort = null, $upsert = false, $fields = null): array {
+        return [];
+    }
     /**
      * 查找满足条件的第一个文档，对其进行更新，并返回
      * @param $new boolean 返回更新后的文档
      */
-    function find_and_update(array $query, array $update, array $sort = null, $upsert = false, array $fields = null, $new = false) {}
+    function find_and_update(array $query, array $update, array $sort = null, $upsert = false, array $fields = null, $new = false) {
+        return [];
+    }
 }
 /**
  * 游标对象
@@ -128,12 +178,18 @@ function find_and_update(array $query, array $update, array $sort = null, $upser
 class cursor {
     /**
      * 读取当前 cursor 返回一个文档; 若读取已完成, 返回 null;
+     * @return mixed array/null
      */
-    function fetch_row():mixed {}
+    function fetch_row():array {
+        return [];
+    }
     /**
      * 读取当前 cursor 返回所有文档 (二维); 若读取已完成, 返回 null;
+     * @return mixed array/null
      */
-    function fetch_all(): array {}
+    function fetch_all(): array {
+        return [];
+    }
 }
 /**
  * 对应 MongoDB 日期时间字段，毫秒级精度
@@ -145,21 +201,31 @@ class date_time implements \JsonSerializable {
      */
     function __construct(int $milliseconds = null) {}
     function __toString() {}
-    function __toDateTime(): \DateTime {}
+    function __toDateTime(): \DateTime {
+        return new \DateTime();
+    }
     function __debugInfo() {}
-    function jsonSerialize(): array {}
+    function jsonSerialize(): array {
+        return [];
+    }
     /**
      * 返回秒级时间戳
      */
-    function unix(): int {}
+    function unix(): int {
+        return time();
+    }
     /**
      * 返回毫秒级时间戳
      */
-    function unix_ms(): int {}
+    function unix_ms(): int {
+        return intval(microtime(true) * 1000);
+    }
     /**
      * 返回标准的 YYYY-MM-DD hh:mm:ss 文本形式的时间
      */
-    function iso(): string {}
+    function iso(): string {
+        return "2019-03-31 21:40:25";
+    }
 }
 /**
  * 对应 MongoDB 对象ID
@@ -171,15 +237,23 @@ class object_id implements \JsonSerializable {
      */
     function __construct(string $object_id = null) {}
     function __toString() {}
-    function __toDateTime(): \DateTime {}
+    function __toDateTime(): \DateTime {
+        return new \DateTime();
+    }
     function __debugInfo() {}
-    function jsonSerialize(): array {}
+    function jsonSerialize(): array {
+        return [];
+    }
     /**
      * 秒级时间戳
      */
-    function unix(): int {}
+    function unix(): int {
+        return time();
+    }
     /**
      * 当前对象与另一 object_id 对象是否**值相等**
      */
-    function equal(object_id $oid): bool {}
+    function equal(object_id $oid): bool {
+        return false;
+    }
 }
diff --git a/doc/mysql.php b/doc/mysql.php
index 2f52cfb..801f42e 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -20,7 +20,9 @@
  * 注意：若服务端配置默认字符集与上述字符集不同，每次连接使用（复用）均会进行字符集设置，这里可能会产生部分额外消耗；
  * @return client 客户端对象
  */
-function connect($url): client {}
+function connect($url): client {
+    return new client();
+}
 
 /**
  * WHERE 字句语法规则:
@@ -88,29 +90,39 @@ class client {
     /**
      * 将数据进行转移，防止可能出现的 SQL 注入等问题；
      */
-    function escape($value):string {}
+    function escape($value):string {
+        return "escaped string";
+    }
     /**
      * 返回事务对象 (绑定在一个连接上)
      */
-    function begin_tx(): ?tx {}
+    function begin_tx(): ?tx {
+        return new tx();
+    }
     /**
      * 执行制定的 SQL 查询, 并返回结果
      * @param string $sql
      * @return mixed SELECT 型语句返回结果集对象 `result`; 否则返回关联数组, 一般包含 affected_rows / insert_id 两项;
      */
-    function query(string $sql): result {}
+    function query(string $sql): result {
+        return new result();
+    }
     /**
      * 返回最近一次执行的 SQL 语句
      * 注意：由于协程的“并行”（穿插）执行，本函数返回的语句可能与当前上下问代码不对应；
      */
-    function last_query(): string {}
+    function last_query(): string {
+        return "last executed sql";
+    }
     /**
      * 向指定表插入一行或多行数据 (自动进行 ESCAPE 转义)
      * @param string $table 表名
      * @param array $data 待插入数据, 多行关联数组插入多行数据(以首行 KEY 做字段名); 普通关联数组插入一行数据;
      * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
-    function insert(string $table, array $data): result {}
+    function insert(string $table, array $data): result {
+        return new result();
+    }
     /**
      * 从指定表格删除匹配的数据
      * @param string $table 表名
@@ -119,7 +131,9 @@ function insert(string $table, array $data): result {}
      * @param mixed $limit LIMIT 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
      * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
-    function delete(string $table, mixed $where, mixed $order, mixed $limit): result {}
+    function delete(string $table, mixed $where, mixed $order, mixed $limit): result {
+        return new result();
+    }
     /**
      * 更新指定表
      * @param string $table 表名
@@ -129,7 +143,9 @@ function delete(string $table, mixed $where, mixed $order, mixed $limit): result
      * @param mixed $limit LIMIT 子句, 请参考上文;
      * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
-    function update(string $table, mixed $where, mixed $modify, mixed $order, mixed $limit): result {}
+    function update(string $table, mixed $where, mixed $modify, mixed $order, mixed $limit): result {
+        return new result();
+    }
     /**
      * 从指定表筛选获取数据
      * @param string $table 表名
@@ -142,19 +158,28 @@ function update(string $table, mixed $where, mixed $modify, mixed $order, mixed
      * @param mixed $order ORDER 子句, 请参考上文;
      * @param mixed $limit LIMIT 子句, 请参考上文;
      */
-    function select(string $table, mixed $fields, array $where, mixed $order, mixed $limit): result {}
+    function select(string $table, mixed $fields, array $where, mixed $order, mixed $limit): result {
+        return new result();
+    }
     /**
      * 从指定表筛选获取一条数据, 并立即返回该行数据
      */
-    function one(string $table, mixed $where, mixed $order): array {}
+    function one(string $table, mixed $where, mixed $order): array {
+        return [];
+    }
     /**
      * 从指定表获取一行数据的指定字段值
+     * @return mixed 字段类型映射请参见顶部说明
      */
-    function get(string $table, string $field, array $where, mixed $order): mixed {}
+    function get(string $table, string $field, array $where, mixed $order) {
+        return null;
+    }
     /**
      * 获取服务端版本
      */
-    function server_version():string {}
+    function server_version():string {
+        return "5.5.5";
+    }
 };
 
 /**
@@ -171,32 +196,46 @@ function commit() {}
     function rollback() {}
     /**
      * @see client::query()
+     * @return mixed cursor/array
      */
-    function query(string $sql): mixed {}
+    function query(string $sql) {}
     /**
      * @see client::insert()
      */
-    function insert(string $table, array $data): array {}
+    function insert(string $table, array $data): array {
+        return [];
+    }
     /**
      * @see client::delete()
      */
-    function delete(string $table, mixed $where, mixed $order, mixed $limit): array {}
+    function delete(string $table, mixed $where, mixed $order, mixed $limit): array {
+        return [];
+    }
     /**
      * @see client::update()
      */
-    function update(string $table, array $where, mixed $modify, mixed $order = null, mixed $limit = null): array {}
+    function update(string $table, array $where, mixed $modify, mixed $order = null, mixed $limit = null): array {
+        return [];
+    }
     /**
      * @see client::select()
      */
-    function select(string $table, mixed $fields, array $where, mixed $order = null, mixed $limit = null): result {}
+    function select(string $table, mixed $fields, array $where, mixed $order = null, mixed $limit = null): result {
+        return new result();
+    }
     /**
      * @see client::one()
      */
-    function one(string $table, array $where, mixed $order = null): array {}
+    function one(string $table, array $where, mixed $order = null): array {
+        return [];
+    }
     /**
      * @see client::get()
+     * @return mixed 字段类型映射参见顶部说明；
      */
-    function get(string $table, string $field, array $where, mixed $order = null): mixed {}
+    function get(string $table, string $field, rray $where, mixed $order = null) {
+        return null;
+    }
 };
 /**
  * 结果集
@@ -211,11 +250,15 @@ class result {
      * @return 下一行数据关联数组;
      *  若读取已完成, 返回 NULL
      */
-    function fetch_row():?array {}
+    function fetch_row():?array {
+        return [];
+    }
     /**
      * 读取 (剩余) 全部行
      * @return 二维数组, 可能为空数组;
      *  若读取已完成, 返回 NULL
      */
-    function fetch_all():?array {}
+    function fetch_all():?array {
+        return [];
+    }
 };
diff --git a/doc/os.php b/doc/os.php
index 73b2a6c..75c889d 100644
--- a/doc/os.php
+++ b/doc/os.php
@@ -50,12 +50,16 @@
  *      }
  *      }
  */
-function interfaces():array {}
+function interfaces():array {
+    return [];
+}
 /**
  * 异步启动进程
  * @return 进程对象
  */
-function spawn(string $command, array $argv = [], array $options = []):process {}
+function spawn(string $command, array $argv = [], array $options = []):process {
+    return new process();
+}
 /**
  * 调用上述 spawn() 异步启动进程, 并等待其结束, 返回进程标准输出
  * @param array $options 目前可用的选项如下：
@@ -63,7 +67,9 @@ function spawn(string $command, array $argv = [], array $options = []):process {
  *  * "env" - array - 环境变量，K/V 结构文本;
  * @return string 进程标准输出内容
  */
-function exec(string $command, array $argv, array $options = []):string {}
+function exec(string $command, array $argv, array $options = []):string {
+    return "output of the process";
+}
 
 /**
  * 进程对象
@@ -80,9 +86,13 @@ function wait() {}
     /**
      * 获取进程标准输出（若进程还未结束需要等待）
      */
-    function stdout():string {}
+    function stdout():string {
+        return "stdout output";
+    }
     /**
      * 获取进程错误输出（若进程还未结束需要等待）
      */
-    function stderr():string {}
+    function stderr():string {
+        return "stderr output";
+    }
 }
\ No newline at end of file
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
index 1d2d988..4e9a80d 100644
--- a/doc/rabbitmq.php
+++ b/doc/rabbitmq.php
@@ -11,11 +11,15 @@
  * 可使用参数:
  *  * `prefetch` - int - 用于指定未 ack 时最大读取数量
  */
-function consume(string $url, string $queue): consumer {}
+function consume(string $url, string $queue): consumer {
+    return new consumer();
+}
 /**
  * 通过 RabbitMQ 连接地址连接并启动生产
  */
-function produce(string $url): producer {}
+function produce(string $url): producer {
+    return new producer();
+}
 
 /**
  * 生产者
@@ -25,6 +29,7 @@ class consumer {
      * 开始消费, 框架将按照一定数量启动并行消费协程, 并使用消息对象调用 $cb 回调函数;
      * @param callable $cb 消费回调, 形如:
      *  function callback($message) {}
+     * 当消费过程失败，将抛出异常（结束消费）
      */
     function run(callable $cb) {}
     /**
@@ -92,9 +97,13 @@ function __construct($body = "", $routing_key = "") {}
     /**
      * 返回消息体
      */
-    function __toString() {}
+    function __toString(): string {
+        return "body of message";
+    }
     /**
      * 自定义 JSON 序列化, 返回包含 routing_key / body / timestamp 字段的数组;
      */
-    function jsonSerialize() {}
+    function jsonSerialize(): array {
+        return [];
+    }
 }
diff --git a/doc/redis.php b/doc/redis.php
index d8ac01f..314edc5 100644
--- a/doc/redis.php
+++ b/doc/redis.php
@@ -7,7 +7,9 @@
  */
 namespace flame\redis;
 
-function connect(string $url):client {}
+function connect(string $url):client {
+    return new client();
+}
 /**
  * Redis 客户端
  */
@@ -45,7 +47,9 @@ function __isset($name) {
     /**
      * 批量执行
      */
-    function multi(): tx {}
+    function multi(): tx {
+        return new tx();
+    }
     /**
      * @uimplement 暂未实现
      */
@@ -79,5 +83,7 @@ function __call($name, $argv): tx {
      * 执行目前提交的命令并带回所有响应返回
      * @return array 返回数据项与命令一一对应
      */
-    function exec():array {}
+    function exec():array {
+        return [];
+    }
 }
diff --git a/doc/tcp.php b/doc/tcp.php
index 6b94460..5d66c77 100644
--- a/doc/tcp.php
+++ b/doc/tcp.php
@@ -8,7 +8,9 @@
  * 连接指定地址, 并返回 socket 对象
  * @param string $address 连接地址, 例如: "127.0.0.1:8687"
  */
-function connect(string $address):socket {}
+function connect(string $address):socket {
+    return new socket();
+}
 
 class socket {
     /**
diff --git a/doc/time.php b/doc/time.php
index e235953..f6c110c 100644
--- a/doc/time.php
+++ b/doc/time.php
@@ -12,9 +12,13 @@ function sleep(int $ms) {}
  * 获取毫秒时间戳
  * (此时间戳采用框架缓存机制，较默认获取系统时间函数消耗较小)
  */
-function now():int {}
+function now():int {
+    return intval(microtime(true) * 1000);
+}
 /**
  * 获取标准时间 "YYYY-mm-dd HH:ii:ss"
  * (按标准格式输出上述 `now()` 对应时间)
  */
-function iso():string {}
+function iso():string {
+    return "2019-03-31 21:40:25";
+}
diff --git a/doc/udp.php b/doc/udp.php
index 3994d74..78599ed 100644
--- a/doc/udp.php
+++ b/doc/udp.php
@@ -9,7 +9,9 @@
  * @param string $address 连接地址, 例如: "127.0.0.1:8687"
  * 注意: UDP "链接" 能在一定程度上提高效率;
  */
-function connect(string $address):socket {}
+function connect(string $address):socket {
+    return new socket();
+}
 
 class socket {
     /**
@@ -48,7 +50,9 @@ function send_to(string $data, string $to) {}
      * 接收并返回一个 UDP 包
      * @return mixed 返回接收到的数据包(文本 string); 若 socket 被关闭则返回 null;
      */
-    function recv():mixed {}
+    function recv():string {
+        return "payload from udp packet";
+    }
     /**
      * 关闭连接
      * 注意: 正在进行的发送 send(_to) 或 recv(_from) 接收动作将被终止;
diff --git a/src/controller.cpp b/src/controller.cpp
index 546ee53..584d1fd 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -11,34 +11,24 @@ namespace flame {
 		: type(process_type::UNKNOWN)
 		, env(boost::this_process::environment())
 		, status(STATUS_UNKNOWN)
-		, cbmap(new std::multimap<std::string, php::callable>())
-	{
+		, cbmap(new std::multimap<std::string, php::callable>()) {
 
 		worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
 		worker_size = std::min(std::max((int)worker_size, 1), 512);
 		// FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
-		if (env.count("FLAME_CUR_WORKER") > 0)
-		{
-			type = process_type::WORKER;
-		}
-		else if (env.count("FLAME_MAX_WORKERS") > 0)
-		{
-			type = process_type::MASTER;
-		}
-		else
-		{
+		if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
+		else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
+		else {
 			worker_size = 0;
 			type = process_type::WORKER;
 		}
 		mthread_id = std::this_thread::get_id();
 	}
-	controller *controller::on_init(std::function<void (const php::array &options)> fn)
-	{
+	controller *controller::on_init(std::function<void (const php::array &options)> fn) {
 		init_cb.push_back(fn);
 		return this;
 	}
-	controller* controller::on_stop(std::function<void ()> fn)
-	{
+	controller* controller::on_stop(std::function<void ()> fn) {
 		stop_cb.push_back(fn);
 		return this;
 	}
@@ -50,14 +40,9 @@ namespace flame {
 			worker_quit = 1;
 		}
 		status |= STATUS_INITIALIZED;
-		for (auto fn : init_cb)
-		{
-			fn(options);
-		}
-		if(type == process_type::WORKER)
-		{
-			if (worker_size > 0)
-			{
+		for (auto fn : init_cb) fn(options);
+		if(type == process_type::WORKER) {
+			if (worker_size > 0) {
 				std::string index = env["FLAME_CUR_WORKER"].to_string();
 				php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
 			}else{
@@ -65,8 +50,7 @@ namespace flame {
 			}
 			worker_.reset(new controller_worker());
 			worker_->initialize(options);
-		}else/* if(type == process_type::MASTER)*/
-		{
+		}else/* if(type == process_type::MASTER)*/ {
 			php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
 			master_.reset(new controller_master());
 			master_->initialize(options);
@@ -75,29 +59,16 @@ namespace flame {
 
 	void controller::run() {
 		status &= controller_status::STATUS_RUN;
-		if(type == process_type::WORKER)
-		{
-			worker_->run();
-		}
-		else /* if(type == process_type::MASTER)*/
-		{
-			master_->run();
-		}
+		if(type == process_type::WORKER) worker_->run();
+		else /* if(type == process_type::MASTER)*/ master_->run();
 		delete cbmap;
 		// 运行完毕
-		if(status & controller_status::STATUS_EXCEPTION) {
-			exit(-1);
-		} else {
-			exit(0);
-		}
+		if(status & controller_status::STATUS_EXCEPTION) exit(-1);
+		else exit(0);
 		// 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
 	}
 
-	void controller::stop()
-	{
-		for (auto fn : stop_cb)
-		{
-			fn();
-		}
+	void controller::stop() {
+		for (auto fn : stop_cb) fn();
 	}
 }
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 9b94907..7bf574e 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -117,8 +117,9 @@ namespace flame::http
                     //     res_ptr->set("status", 404);
                     //     res_ptr->set("body", nullptr);
                     // }
-                    if(gcontroller->status & controller::controller_status::STATUS_SHUTDOWN) {
+                    if(gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_) {
                         res_->keep_alive(false);
+                        res_->set(boost::beast::http::field::connection, "close");
                     }
                     res_ptr->build_ex(*res_);
                     // Content 方式, 待结束
@@ -163,9 +164,11 @@ namespace flame::http
         res_ptr->build_ex(*res_);
         res_->chunked(true);
         res_->set(boost::beast::http::field::transfer_encoding, "chunked");
-        if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN)
+        
+        if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_)
         {
             res_->keep_alive(false);
+            res_->set(boost::beast::http::field::connection, "close");
         }
         boost::system::error_code error;
 
diff --git a/src/http/server_request.cpp b/src/http/server_request.cpp
index b8a541d..86b2dad 100644
--- a/src/http/server_request.cpp
+++ b/src/http/server_request.cpp
@@ -15,7 +15,7 @@ namespace http {
 			.property({"header", nullptr})
 			.property({"cookie", nullptr})
 			.property({"body", nullptr})
-			.property({"rawBody", ""})
+			.property({"raw_body", ""})
 			.property({"file", nullptr})
 			.property({"data", nullptr})
 			.method<&server_request::__construct>("__construct", {}, php::PRIVATE);
@@ -73,13 +73,13 @@ namespace http {
 		if(body.typeof(php::TYPE::STRING)) {
 			auto ctype = ctr_.find(boost::beast::http::field::content_type);
 			if(ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0) {
-				set("rawBody", body); // 不在 multipart 时保留原始数据
+				set("raw_body", body); // 不在 multipart 时保留原始数据
 			}
 			if(ctype != ctr_.end()) { // 存在时按照类型进行解析
 				php::array files(4);
 				set("body", ctype_decode(ctype->value(), body, &files));
 				set("file", files);
-			}else{ // 不存在与 rawBody 相同
+			}else{ // 不存在与 raw_body 相同
 				set("body", body);
 			}
 		}
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index 072bb58..ea28c53 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -309,12 +309,21 @@ namespace flame::mongodb
         php::array pipeline = params[0];
         if (!pipeline.exists(0))
             throw php::exception(zend_ce_type_error, "aggregate pipeline must be a array of stages (array)");
-        cmd.set("pipeline", params[0]);
+        cmd.set("pipeline", pipeline);
         cmd.set("cursor", php::object(php::CLASS(zend_standard_class_def)));
-
+        if(params.size() > 1) {
+            php::array options = params[1];
+            for(auto i=options.begin(); i!=options.end(); ++i) {
+                cmd.set(i->first.to_string(), i->second);
+            }
+        }
+        int execute_type = _connection_base::COMMAND_READ;
+        if(params.size() > 2) {
+            execute_type = params[2].to_integer();
+        }
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
-        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
+        return cp_->exec(conn_, cmd, execute_type, ch);
     }
     php::value collection::find_and_delete(php::parameters& params) {
         php::array cmd(8);
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 436c60c..6f77bda 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -11,7 +11,6 @@ namespace flame::mysql
     {
         php::class_entry<client> class_client("flame\\mysql\\client");
         class_client
-            .constant({"AAAA",123})
             .method<&client::__construct>("__construct", {}, php::PRIVATE)
             .method<&client::__destruct>("__destruct")
             .method<&client::escape>("escape",
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 0e4f40d..3099d09 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -35,7 +35,7 @@ namespace flame::mysql {
         url u(params[0]);
         if(u.port < 10) u.port = 3306;
         if(!u.query.count("charset")) {
-            u.query["charset"] = "utf8";
+            u.query["charset"] = "utf8mb4";
         }
         if(!u.query.count("ssl")) {
             u.query["ssl"] = "disabled";
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
index fc254be..6706367 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/rabbitmq/_client.cpp
@@ -10,76 +10,91 @@ namespace flame::rabbitmq
             strncasecmp(str, "yes", 3) == 0 || strncasecmp(str, "true", 4);
     }
 
-#define CHECK_AND_SET_FLAG(xflag, yflag)                  \
-    for (auto i = u.query.find(#xflag); i != u.query.end();)    \
-    {                                                           \
+#define CHECK_AND_SET_FLAG(xflag, yflag)                        \
+    for (auto i = u.query.find(#xflag); i != u.query.end();) {  \
         const char *str = i->second.c_str();                    \
-        if (str2bool(str))                                      \
-        {                                                       \
-            fl_ |= yflag;                                      \
-        }                                                       \
+        if (str2bool(str)) fl_ |= yflag;                      \
     }
 
     _client::_client(url u, coroutine_handler& ch)
-        : hd_(gcontroller->context_x), cc_(&hd_, AMQP::Address(u.str(true, false).c_str(), u.str().size())), ch_(&cc_), consumer_closed_(false)
-    {
-        const char *err = nullptr;
-        ch_.onReady([this, &ch]() {
-            ch_.onError(nullptr);
+    : chl_(gcontroller->context_x)
+    , con_(&chl_, AMQP::Address(u.str(true, false).c_str(), u.str().size()))
+    , chn_(&con_)
+    , tm_(gcontroller->context_x)
+    , producer_cb_(false) {
+        chn_.onReady([this, &ch]() {
+            // ch_.onError([] (const char* message) {});
+            chn_.onError([this] (const char* message) {
+                error_.assign(message);
+                if(!consumer_tg_.empty()) consumer_close();
+            });
             ch.resume();
         });
-        ch_.onError([this, &err, &ch](const char *message) {
-            err = message;
-            ch_.onReady(nullptr);
+        chn_.onError([this, &ch](const char *message) {
+            error_ = message;
+            chn_.onReady(nullptr);
             ch.resume();
         });
         ch.suspend();
 
+        if(!error_.empty()) {
+            std::string err = std::move(error_);
+            throw php::exception(zend_ce_exception,
+                (boost::format("failed to connect RabbitMQ server: %1%") % err).str(), -1);
+        }
+
         auto i = u.query.find("prefetch");
         if(i == u.query.end()) {
             pf_ = 8;
         }else{
             pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 256);
         }
-        ch_.setQos(pf_);
+        chn_.setQos(pf_);
 
         CHECK_AND_SET_FLAG(nolocal, AMQP::nolocal);
         CHECK_AND_SET_FLAG(noack, AMQP::noack);
         CHECK_AND_SET_FLAG(exclusive, AMQP::noack);
         CHECK_AND_SET_FLAG(mandatory, AMQP::mandatory);
         CHECK_AND_SET_FLAG(immediate, AMQP::immediate);
-        
-        if(err) {
-            throw php::exception(zend_ce_exception,
-                (boost::format("failed to connect RabbitMQ server: %1%") % err).str(), -1);
-        }
+
+        // 启动心跳
+        heartbeat();
     }
 
-    void _client::connect(coroutine_handler& ch)
-    {
-        
+    void _client::heartbeat() {
+        tm_.expires_after(std::chrono::seconds(45));
+        tm_.async_wait([this] (const boost::system::error_code& error) {
+            if(error) return;
+            con_.heartbeat();
+            heartbeat();
+        });
     }
-    void _client::consume(const std::string &queue, coroutine_queue<php::object>& q, coroutine_handler &ch)
-    {
+
+    bool _client::has_error() {
+        return !error_.empty();
+    }
+
+    const std::string& _client::error() {
+        return error_;
+    }
+
+    void _client::consume(const std::string &queue, coroutine_queue<php::object>& q, coroutine_handler &ch) {
         php::object obj = nullptr;
         const char* err = nullptr;
-        ch_.consume(queue)
-            .onReceived([this, &q, &obj, &ch](const AMQP::Message &m, std::uint64_t tag, bool redelivered)
-            {
+        chn_.consume(queue)
+            .onReceived([this, &q, &obj, &ch](const AMQP::Message &m, std::uint64_t tag, bool redelivered) {
                 obj = php::object(php::class_entry<message>::entry());
                 message* ptr = static_cast<message*>(php::native(obj));
                 ptr->build_ex(m, tag);
-                ch.resume();
+                ch.resume(); // ----> 2
             })
-            .onSuccess([this, &ch](const std::string &t)
-            {
-                tag_ = t;
-                ch.resume();
+            .onSuccess([this, &ch](const std::string &t) {
+                consumer_tg_ = t;
+                ch.resume(); // ----> 2
             })
-            .onError([&err, &ch](const char *message)
-            {
+            .onError([&err, &ch](const char *message) {
                 err = message;
-                ch.resume();
+                ch.resume(); // ----> 2
             });
         ch.suspend();
         if(err) {
@@ -89,83 +104,83 @@ namespace flame::rabbitmq
         consumer_ch_ = ch;
         ch.suspend();
         // 非关闭恢复执行, 消息对象一定存在
-        while(!consumer_closed_/* && !obj.empty()*/)
-        {
+        while(!consumer_tg_.empty()) {
             q.push(std::move(obj), ch);
-            ch.suspend();
+            ch.suspend(); // 2 <----
+        }
+        if(err) {
+            std::cerr << "[rabbitmq] error: " << err << std::endl;
         }
         q.close();
         consumer_ch_.reset();
     }
-    void _client::confirm(std::uint64_t tag, coroutine_handler &ch)
-    {
-        ch_.ack(tag, 0);
+    void _client::confirm(std::uint64_t tag, coroutine_handler &ch) {
+        chn_.ack(tag, 0);
     }
-    void _client::reject(std::uint64_t tag, int flags, coroutine_handler &ch)
-    {
-        ch_.reject(tag, flags);
+    void _client::reject(std::uint64_t tag, int flags, coroutine_handler &ch) {
+        chn_.reject(tag, flags);
     }
-    void _client::close_consumer(coroutine_handler& ch)
-    {
+    void _client::consumer_close() {
+        const char* err = nullptr;
+        chn_.cancel(consumer_tg_);
+        consumer_tg_.clear();
+        // 标记结束后，消费流程队将自行关闭
+        if(consumer_ch_) consumer_ch_.resume();
+    }
+    void _client::consumer_close(coroutine_handler& ch) {
         const char* err = nullptr;
-        ch_.cancel(tag_)
+        chn_.cancel(consumer_tg_)
             .onSuccess([&ch] () {
                 ch.resume();
             })
-            .onError([&err, &ch] (const char* message)
-            {
+            .onError([&err, &ch] (const char* message) {
                 err = message;
                 ch.resume();
             });
         ch.suspend();
-        // TODO 关闭消费流程队列
-        if (err)
-        {
-            throw php::exception(zend_ce_error,
+        consumer_tg_.clear();
+        if(consumer_ch_) consumer_ch_.resume(); // ----> 2  标记结束后，消费流程队将自行关闭
+        if (err) throw php::exception(zend_ce_error,
                                  (boost::format("failed to close RabbitMQ consumer: %1%") % err).str(), -1);
-        }
-        consumer_closed_ = true;
-        if(consumer_ch_) consumer_ch_.resume();
     }
-    void _client::publish(const std::string &ex, const std::string &rk, const AMQP::Envelope &env, coroutine_handler& ch)
-    {
-        const char* err = nullptr;
-        ch_.publish(ex, rk, env, fl_);
-        // TODO 目前的流程, 暂时未实现消息发送的确认逻辑
-        //     .onSuccess([&ch] ()
-        //     {
-        //         ch.resume();
-        //     })
-        //     .onError([&err, &ch] (const char* message)
-        //     {
-        //         err = message;
-        //         ch.resume();
-        //     });
-        // ch.suspend();
-        // if(err) {
-        //     throw php::exception(zend_ce_error,
-        //                          (boost::format("failed to publish RabbitMQ message: %1%") % err).str(), -1);
-        // }
+    void _client::publish(const std::string &ex, const std::string &rk, const AMQP::Envelope &env, coroutine_handler& ch) {
+        /*auto& defer = */chn_.publish(ex, rk, env, fl_);
+        // producer_cb(defer, ch);
+        if(!error_.empty()) {
+            std::string err = std::move(error_);
+            throw php::exception(zend_ce_error,
+                (boost::format("failed to publish to RabbitMQ: %1%") % err).str(), -1);
+        }
     }
+
+    // void _client::producer_cb(AMQP::DeferredPublisher& defer, coroutine_handler& ch) {
+    //     producer_ch_ = ch;
+    //     if(!producer_cb_) {
+    //         producer_cb_ = true;
+    //         const char* err = nullptr;
+    //         defer.onSuccess([this]() {
+    //             if(producer_ch_) producer_ch_.resume();
+    //         }).onError([this](const char *message) {
+    //             error_ = message;
+    //             if(producer_ch_) producer_ch_.resume();
+    //         });
+    //     }
+    //     ch.suspend();
+    //     producer_ch_.reset();
+    //     if (!error_.empty()) {
+    //         std::string err = std::move(error_);
+    //         throw php::exception(zend_ce_error,
+    //                         (boost::format("failed to publish RabbitMQ message: %1%") % err).str(), -1);
+    //     }
+    // }
     void _client::publish(const std::string &ex, const std::string &rk, const char *msg, size_t len, coroutine_handler& ch)
     {
-        const char *err = nullptr;
-        ch_.publish(ex, rk, msg, len, fl_);
-        // TODO 目前的流程, 暂时未实现消息发送的确认逻辑
-            // .onSuccess([&ch]() {
-            //     std::cout << "success\n";
-            //     ch.resume();
-            // })
-            // .onError([&err, &ch](const char *message) {
-            //     std::cout << "error\n";
-            //     err = message;
-            //     ch.resume();
-            // });
-        // ch.suspend();
-        // if (err)
-        // {
-        //     throw php::exception(zend_ce_error,
-        //                          (boost::format("failed to publish RabbitMQ message: %1%") % err).str(), -1);
-        // }
+        /*auto& defer = */chn_.publish(ex, rk, msg, len, fl_);
+        // producer_cb(defer, ch);
+        if(!error_.empty()) {
+            std::string err = std::move(error_);
+            throw php::exception(zend_ce_error,
+                (boost::format("failed to publish to RabbitMQ: %1%") % err).str(), -1);
+        }
     }
 }
diff --git a/src/rabbitmq/_client.h b/src/rabbitmq/_client.h
index ecde057..47d3817 100644
--- a/src/rabbitmq/_client.h
+++ b/src/rabbitmq/_client.h
@@ -11,23 +11,29 @@ namespace flame::rabbitmq
     public:
         _client(url u, coroutine_handler& ch);
         
-        void connect(coroutine_handler &ch);
         void consume(const std::string& queue, coroutine_queue<php::object>& q, coroutine_handler& ch);
         void confirm(std::uint64_t tag, coroutine_handler& ch);
         void reject(std::uint64_t tag, int flags, coroutine_handler& ch);
-        void close_consumer(coroutine_handler& ch);
+        void consumer_close(coroutine_handler& ch);
+        void consumer_close();
         void publish(const std::string& ex, const std::string& rk, const AMQP::Envelope& env, coroutine_handler& ch);
         void publish(const std::string& ex, const std::string& rk, const char* msg, size_t len, coroutine_handler& ch);
+        // void producer_cb(AMQP::DeferredPublisher& defer, coroutine_handler& ch);
+        void heartbeat();
+        bool has_error();
+        const std::string& error();
     private:
-        AMQP::LibBoostAsioHandler hd_;
-        AMQP::TcpConnection       cc_;
-        AMQP::TcpChannel          ch_;
+        AMQP::LibBoostAsioHandler chl_;
+        AMQP::TcpConnection       con_;
+        AMQP::TcpChannel          chn_;
         int                       pf_;
         int                       fl_;
-        std::string              tag_;
-
+        boost::asio::steady_timer tm_;
+        std::string       consumer_tg_;
         coroutine_handler consumer_ch_;
-        bool          consumer_closed_;
+        bool              producer_cb_;
+        coroutine_handler producer_ch_;
+        std::string       error_;
 
         friend class consumer;
     };
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index c477250..2ef683d 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -65,14 +65,15 @@ namespace flame::rabbitmq
                 }
                 if(--count == 0)
                 {
-                    ch.resume();
+                    ch.resume(); // ----> 1
                 }
                 return nullptr;
             }));
         }
-        cc_->consume(cq_, q, ch);
-        ch.suspend();
+        cc_->consume(qn_, q, ch);
+        ch.suspend(); // 1 <----
         cb_ = nullptr;
+        if(cc_->has_error()) throw php::exception(zend_ce_exception, (boost::format("failed to consume RabbitMQ queue: %1%") % cc_->error()).str());
         return nullptr;
     }
     php::value consumer::confirm(php::parameters &params)
@@ -96,7 +97,7 @@ namespace flame::rabbitmq
     php::value consumer::close(php::parameters &params)
     {
         coroutine_handler ch {coroutine::current};
-        cc_->close_consumer(ch);
+        cc_->consumer_close(ch);
         return nullptr;
     }
 } // namespace flame::rabbitmq
diff --git a/src/rabbitmq/consumer.h b/src/rabbitmq/consumer.h
index 3c0d165..6fd206c 100644
--- a/src/rabbitmq/consumer.h
+++ b/src/rabbitmq/consumer.h
@@ -18,7 +18,7 @@ namespace flame::rabbitmq
         // 实际的客户端对象可能超过当前对象的生存期
         std::shared_ptr<_client> cc_;
         php::callable            cb_;
-        std::string              cq_;
+        std::string              qn_;
         int              concurrent_;
 
         friend php::value consume(php::parameters &params);
diff --git a/src/rabbitmq/rabbitmq.cpp b/src/rabbitmq/rabbitmq.cpp
index 3d1148a..8a1dc16 100644
--- a/src/rabbitmq/rabbitmq.cpp
+++ b/src/rabbitmq/rabbitmq.cpp
@@ -12,13 +12,11 @@ namespace flame::rabbitmq
     void declare(php::extension_entry &ext)
     {
         ext
-            .function<consume>("flame\\rabbitmq\\consume",
-            {
+            .function<consume>("flame\\rabbitmq\\consume", {
                 {"address", php::TYPE::STRING},
                 {"queue", php::TYPE::STRING},
             })
-            .function<produce>("flame\\rabbitmq\\produce",
-            {
+            .function<produce>("flame\\rabbitmq\\produce", {
                 {"address", php::TYPE::STRING},
             });
 
@@ -39,7 +37,7 @@ namespace flame::rabbitmq
         php::object obj(php::class_entry<consumer>::entry());
         consumer* ptr = static_cast<consumer*>(php::native(obj));
         ptr->cc_ = cc;
-        ptr->cq_ = static_cast<std::string>(params[1]);
+        ptr->qn_ = static_cast<std::string>(params[1]);
         return std::move(obj);
     }
     php::value produce(php::parameters &params)
diff --git a/src/vendor.h b/src/vendor.h
index 4db7e08..f63a7a8 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.13"
+#define EXTENSION_VERSION "0.12.14"
 
 #include <sys/types.h>
 #include <sys/socket.h>

From d8e4908ea0440b0ccae26a0f4046dbd028470260 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 4 Apr 2019 15:37:51 +0800
Subject: [PATCH 083/146] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20Redis=20=E8=BF=9E?=
 =?UTF-8?q?=E6=8E=A5=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF;=20=E4=BF=AE?=
 =?UTF-8?q?=E6=AD=A3:=20=E6=9F=90=E4=BA=9B=E5=BC=82=E5=B8=B8=E9=80=80?=
 =?UTF-8?q?=E5=87=BA=E6=B5=81=E7=A8=8B=E6=9C=AA=E8=83=BD=E7=BB=93=E6=9D=9F?=
 =?UTF-8?q?=E8=BF=9B=E7=A8=8B;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp             |  4 ++--
 src/core.cpp                   | 10 ++++------
 src/redis/_connection_pool.cpp | 36 +++++++++++++++++-----------------
 src/redis/_connection_pool.h   |  2 +-
 4 files changed, 25 insertions(+), 27 deletions(-)

diff --git a/src/controller.cpp b/src/controller.cpp
index 584d1fd..917bc02 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -63,8 +63,8 @@ namespace flame {
 		else /* if(type == process_type::MASTER)*/ master_->run();
 		delete cbmap;
 		// 运行完毕
-		if(status & controller_status::STATUS_EXCEPTION) exit(-1);
-		else exit(0);
+		if(status & controller_status::STATUS_EXCEPTION) _exit(-1);
+		else _exit(0);
 		// 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
 	}
 
diff --git a/src/core.cpp b/src/core.cpp
index 18c8d43..7372de5 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -33,12 +33,10 @@ namespace flame::core {
     }
     void declare(php::extension_entry &ext) {
         ext
-            .on_request_shutdown([] (php::extension_entry& ext) -> bool
-            {
-                if(coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0)
-                {
-                    std::cerr << "[FATAL] process exited prematurely (missing 'flame\\run();'?)\n";
-                    exit(-1);
+            .on_request_shutdown([] (php::extension_entry& ext) -> bool {
+                if(coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0) {
+                    if(!php::error::exists()) std::cerr << "[FATAL] process exited prematurely (missing 'flame\\run();'?)\n";
+                    _exit(-1);
                 }
                 return true;
             })
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index abf0bc6..e8c14c3 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -22,8 +22,9 @@ namespace flame::redis
     std::shared_ptr<redisContext> _connection_pool::acquire(coroutine_handler &ch)
     {
         std::shared_ptr<redisContext> conn;
+        std::string err;
         // 提交异步任务
-        boost::asio::post(guard_, [this, &conn, &ch]() {
+        boost::asio::post(guard_, [this, &conn, &err, &ch]() {
             // 设置对应的回调, 在获取连接后恢复协程
             await_.push_back([&conn, &ch](std::shared_ptr<redisContext> c) {
                 conn = c;
@@ -33,16 +34,13 @@ namespace flame::redis
             auto now = std::chrono::steady_clock::now();
             while (!conn_.empty())
             {
-                if(now - conn_.front().ttl < std::chrono::seconds(15) || ping(conn_.front().conn))
-                {
+                if(now - conn_.front().ttl < std::chrono::seconds(15) || ping(conn_.front().conn)) {
                     // 可用连接
                     redisContext *c = conn_.front().conn;
                     conn_.pop_front();
                     release(c);
                     return;
-                }
-                else
-                { // 连接已丢失，回收资源
+                } else { // 连接已丢失，回收资源
                     redisFree(conn_.front().conn);
                     conn_.pop_front();
                     --size_;
@@ -51,24 +49,19 @@ namespace flame::redis
             if (size_ >= max_)
                 return; // 已建立了足够多的连接, 需要等待已分配连接释放
 
-            redisContext *c = create();
-            if (c == nullptr) // 创建新连接失败
-            {
+            redisContext *c = create(err);
+            if (c == nullptr) { // 创建新连接失败
                 await_.pop_back();
                 ch.resume();
-            }
-            else
-            {
+            } else {
                 ++size_; // 当前还存在的连接数量
                 release(c);
             }
         });
         // 暂停, 等待连接获取(异步任务)
         ch.suspend();
-        if (!conn)
-        {
-            throw php::exception(zend_ce_exception, "failed to connect to Redis server", -1);
-        }
+        if (!conn) throw php::exception(zend_ce_exception,
+            (boost::format("failed to connect to Redis server: %1%") % err).str());
         // 恢复, 已经填充连接
         return conn;
     }
@@ -99,17 +92,23 @@ namespace flame::redis
         return exec(rc, name, data, rt, ch);
     }
 
-    redisContext *_connection_pool::create()
+    redisContext *_connection_pool::create(std::string& err)
     {
         struct timeval tv {5, 0};
         redisContext* c = redisConnectWithTimeout(url_.host.c_str(), url_.port, tv);
-        if(!c || c->err) {
+        if(!c) {
+            err.assign("connection failed");
+            return nullptr;
+        }else if(c->err) {
+            err.assign(c->errstr);
+            redisFree(c);
             return nullptr;
         }
         if(!url_.pass.empty()) { // 认证
             std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply*)redisCommand(c, "AUTH %s", url_.pass.c_str()),
                 (void (*)(redisReply*))freeReplyObject);
             if(!rp || rp->type == REDIS_REPLY_ERROR) {
+                err.assign(rp->str, rp->len);
                 redisFree(c);
                 return nullptr;
             }
@@ -118,6 +117,7 @@ namespace flame::redis
             std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "SELECT %d", std::atoi(url_.path.c_str() + 1)),
                 (void (*)(redisReply*))freeReplyObject);
             if(!rp || rp->type == REDIS_REPLY_ERROR) {
+                err.assign(rp->str, rp->len);
                 redisFree(c);
                 return nullptr;
             }
diff --git a/src/redis/_connection_pool.h b/src/redis/_connection_pool.h
index b270e63..6898a13 100644
--- a/src/redis/_connection_pool.h
+++ b/src/redis/_connection_pool.h
@@ -33,7 +33,7 @@ namespace flame::redis
         boost::asio::steady_timer tm_;
 
         bool ping(redisContext* c);
-        redisContext *create();
+        redisContext *create(std::string& err);
         void release(redisContext *c);
     };
 

From 7f7bb7000b1a17dff84ae7dcf1fc0cb888b5c5af Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 8 Apr 2019 16:15:06 +0800
Subject: [PATCH 084/146] =?UTF-8?q?=E9=87=8D=E6=9E=84=20HTTP(S)=20?=
 =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=20CURL=20=E5=BA=95=E5=B1=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                |  14 +-
 README.md                     |  32 ++++-
 doc/http.php                  |  54 ++++----
 doc/kafka.php                 |  14 +-
 doc/mysql.php                 |   2 +-
 doc/rabbitmq.php              |   2 +-
 doc/tcp.php                   |   4 +-
 doc/udp.php                   |   4 +-
 src/flame.cpp                 |   8 +-
 src/http/_connection_pool.cpp | 247 ----------------------------------
 src/http/_connection_pool.h   |  60 ---------
 src/http/client.cpp           | 162 +++++++++++++++++++---
 src/http/client.h             |  15 ++-
 src/http/client_body.cpp      |   8 ++
 src/http/client_body.h        |   9 ++
 src/http/client_request.cpp   |  90 +++++++------
 src/http/client_request.h     |  10 +-
 src/http/client_response.cpp  |  65 +++++----
 src/http/client_response.h    |  19 ++-
 src/http/http.cpp             |   9 +-
 src/http/http.h               |   9 +-
 src/http/server_response.cpp  |   2 +-
 src/vendor.h                  |   4 +
 23 files changed, 385 insertions(+), 458 deletions(-)
 delete mode 100644 src/http/_connection_pool.cpp
 delete mode 100644 src/http/_connection_pool.h
 create mode 100644 src/http/client_body.cpp
 create mode 100644 src/http/client_body.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 639a9ab..657d986 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,8 @@ set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.0)
 set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.9.0)
+set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.37.0)
+set(VENDOR_CURL /data/vendor/curl-7.64.1)
 # -----------------------------------------------------------------------------
 # 编译选项
 # -----------------------------------------------------------------------------
@@ -60,6 +62,9 @@ target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_AMQP}/include
     ${VENDOR_RDKAFKA}/include
     ${VENDOR_HTTPPARSER}/include
+    ${VENDOR_NGHTTP2}/include
+    ${VENDOR_CURL}/include
+    /usr/include/openssl-1.0
 )
 target_link_directories(FLAME PUBLIC /usr/lib/openssl-1.0)
 # 链接库
@@ -77,9 +82,16 @@ target_link_libraries(FLAME
     ${VENDOR_BOOST}/lib/libboost_system.a
     ${VENDOR_BOOST}/lib/libboost_thread.a
     ${VENDOR_BOOST}/lib/libboost_filesystem.a
+    ${VENDOR_CURL}/lib/libcurl.a
+    ${VENDOR_NGHTTP2}/lib/libnghttp2.a
+    ldap
     sasl2
-    ssl
+    lber
+    brotlidec
     crypto
+    ssl
+    z
+    dl
     pthread
     rt
 )
diff --git a/README.md b/README.md
index 8d23eab..99b5578 100644
--- a/README.md
+++ b/README.md
@@ -71,9 +71,11 @@ https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
 
 ### 依赖库
 
+> 注意：由于 mysqlc 驱动使用官方预编译版本，其对应 openssl 版本为 1.0.x ；故，其他依赖需对其版本依赖；
+
 #### PHP
 ``` Bash
-./configure --prefix=/data/vendor/php-7.2.15 --with-config-file-path=/data/vendor/php-7.2.15/etc --disable-fpm --disable-phar --disable-dom --disable-libxml --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-openssl --with-readline --enable-mbstring --without-pear --with-curl --enable-mbstring --host=x86_64-linux-gnu --target=x86_64-linux-gnu
+./configure --prefix=/data/vendor/php-7.2.16 --with-config-file-path=/data/vendor/php-7.2.16/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --host=x86_64-linux-gnu --target=x86_64-linux-gnu
 ```
 #### Boost
 
@@ -95,6 +97,7 @@ make install
 
 #### mysql-connector-c
 ``` Bash
+
 mkdir -p /data/vendor/mysqlc-8.0.15/lib
 cp -R mysql-8.0.15-linux-glibc2.12-x86_64/include /data/vendor/mysqlc-8.0.15
 cp mysql-8.0.15-linux-glibc2.12-x86_64/lib/libmysqlclient.a /data/vendor/mysqlc-8.0.15/lib
@@ -104,7 +107,6 @@ cp mysql-8.0.15-linux-glibc2.12-x86_64/lib/libmysqlclient.a /data/vendor/mysqlc-
 ``` Bash
 mkdir stage && cd stage
 CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
-# 由于 mysql-connector-c 官方提供的版本需要 openssl-1.0 此处也须统一版本
 # -DOPENSSL_INCLUDE_DIR=/usr/include/openssl-1.0 -DOPENSSL_SSL_LIBRARY=/usr/lib/openssl-1.0/libssl.so -DOPENSSL_CRYPTO_LIBRARY=/usr/lib/openssl-1.0/libcrypto.so
 make
 make install
@@ -114,12 +116,12 @@ rm /data/vendor/mongoc-1.14.0/lib/*.so*
 #### AMQP-CPP
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.3 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.4 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
 make
 make install
 ```
 
-#### Rdkafka
+#### rdkafka
 ``` Bash
 ./configure --prefix=/data/vendor/rdkafka-1.0.0
 make
@@ -130,7 +132,7 @@ cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
 cp src/xxhash.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
 ```
 
-#### HttpParser
+#### http-parser
 ``` Bash
 mkdir -p /data/vendor/http-parser-2.9.0/lib
 mkdir -p /data/vendor/http-parser-2.9.0/include
@@ -139,9 +141,27 @@ cp libhttp_parser.o /data/vendor/http-parser-2.9.0/lib
 cp http_parser.h /data/vendor/http-parser-2.9.0/include
 ```
 
-#### HiRedis
+#### hiredis
 ``` Bash
 CC=gcc make
 PREFIX=/data/vendor/hiredis-0.14.0 make install
 rm /data/vendor/hiredis-0.14.0/lib/*.so*
 ```
+
+#### nghttp2
+```
+# 需要统一 openssl 版本
+# PKG_CONFIG_PATH=/usr/lib/openssl-1.0/pkgconfig/
+CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/nghttp2-1.37.0 --enable-shared=no --enable-lib-only --with-boost=/data/vendor/boost-1.69.0 --enable-asio-lib
+make
+make install
+```
+
+#### curl
+```
+# 需要统一 openssl 版本
+# PKG_CONFIG_PATH=/usr/lib/openssl-1.0/pkgconfig/
+CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --prefix=/data/vendor/curl-7.64.1 --with-nghttp2=/data/vendor/nghttp2-1.37.0 --with-pic=pic --enable-ipv6 --enable-shared=no
+make
+make install
+```
diff --git a/doc/http.php b/doc/http.php
index 15536ba..6191a8e 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -1,9 +1,8 @@
 <?php
 /**
- * 提供基本的 HTTP 客户端、服务端封装；
+ * 提供基本的 HTTP(S) 客户端、服务端封装；
  * 注意：
- * 1. 暂不支持作为 HTTPS 客户端、服务端；请使用 CURL 库或 NGINX 反向代理；
- * 2. 默认状态客户端对请求连接进行 KeepAlive 复用；若需要使用短连接，请自行制定 Connection: close 头；
+ * * 默认状态客户端对请求连接进行 KeepAlive 复用；若需要使用短连接，请自行制定 Connection: close 头；
  */
 namespace flame\http;
 
@@ -84,28 +83,28 @@ function delete(string $url, int $timeout = 3000): client_response {
  */
 class client_request {
     /**
-     * @property int
+     * @var int
      */
     public $timeout;
     /**
-     * @property string
+     * @var string
      */
     public $method;
     /**
-     * @property string
+     * @var string
      */
     public $url;
     /**
      * 请求头；可通过指定 "Connection" => "close" 防止连接复用；
-     * @property array
+     * @var array
      */
     public $header;
     /**
-     * @property array
+     * @var array
      */
     public $cookie;
     /**
-     * @property mixed
+     * @var mixed
      */
     public $body;
     /**
@@ -117,18 +116,23 @@ function __construct(string $url, mixed $body = null, int $timeout = 3000) {}
  * 客户端响应对象，执行请求后得到
  */
 class client_response {
+    /**
+     * 对应请求实际的 HTTP 版本
+     * @var string
+     */
+    public $version;
     /**
      * 响应码
-     * @property int
+     * @var int
      */
     public $status;
     /**
      * 注意: 目前使用了数组形式, 切暂不支持多个同名 Header 数据的访问;
-     * @property array
+     * @var array
      */
     public $header;
     /**
-     * @property mixed 以下几种 Content-Type 将自动进行解析并生成关联数组:
+     * @var mixed 以下几种 Content-Type 将自动进行解析并生成关联数组:
      *  * "application/json"
      *  * "application/x-www-form-urlencoded"
      *  * "multipart/form-data"
@@ -137,7 +141,7 @@ class client_response {
     public $body;
     /**
      * 原始请求体数据
-     * @property string
+     * @var string
      */
     public $raw_body;
 }
@@ -237,28 +241,28 @@ function close() {}
 class server_request {
     /**
      * 请求方法
-     * @property string
+     * @var string
      */
     public $method = "GET";
     /**
      * 请求路径
-     * @property string
+     * @var string
      */
     public $path = "/";
     /**
      * 请求参数, 使用 PHP 内置 parse_str() 函数解析得到;
-     * @property array
+     * @var array
      */
     public $query;
     /**
      * 请求头; 所有头信息字段名被转换为**小写**形式;
      * 注意：暂不支持多项同名 Header 字段;
-     * @property array
+     * @var array
      */
     public $header;
     /**
      * 请求 Cookie
-     * @property array
+     * @var array
      */
     public $cookie;
     /**
@@ -267,12 +271,12 @@ class server_request {
      *  * "application/x-www-form-urlencoded"
      *  * "multipart/form-data"
      * 其他类型时与 $rawBody 相同;
-     * @property mixed
+     * @var mixed
      */
     public $body;
     /**
      * 原始请求体
-     * @property string
+     * @var string
      */
     public $rawBody;
     /**
@@ -290,13 +294,13 @@ class server_request {
      *      ...
      *  ]
      * 若无上传文件时可能为空
-     * @property array
+     * @var array
      */
     public $file;
     /**
      * 用户可设置的请求关联数据;
      * 例如: 在 before 回调中从 Cookie 中提取用户信息放在 $req->data["user"] 中, 后续处理器即可访问 $req->data["user"] 获取户信息;
-     * @property array
+     * @var array
      */
     public $data;
 }
@@ -307,18 +311,18 @@ class server_request {
 class server_response {
     /**
      * 响应状态码, 可以直接设置; 也可以在 Transfer-Encoding: chunked 模式时使用 write_header 指定;
-     * @property int
+     * @var int
      */
     public $status = 200;
     /**
      * 响应头信息, Key/Val 形式
-     * @property array
+     * @var array
      */
     public $header;
     // public $cookie;
     /**
      * 响应体, 用于在 Content-Length: xxx 响应模式下返回数据;
-     * @property mixed 任意数据, 实际响应会序列化此数据; 以下几种 Content-Type 存在内置的序列化:
+     * @var mixed 任意数据, 实际响应会序列化此数据; 以下几种 Content-Type 存在内置的序列化:
      *  * "application/json" - 使用 json_encode 进行序列化;
      *  * "application/x-www-form-urlencoded" - 使用 http_build_query 进行序列化;
      * 其他类型强制转换为文本类型后响应;
diff --git a/doc/kafka.php b/doc/kafka.php
index a641352..c2c764e 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -77,31 +77,31 @@ function flush() {}
  */
 class message implements \JsonSerializable {
     /**
-     * @property string $topic
+     * @var string $topic
      */
     public $topic;
     /**
-     * @property int $partition
+     * @var int $partition
      */
     public $partition;
     /**
-     * @property string $key
+     * @var string $key
      */
     public $key;
     /**
-     * @property int $offset
+     * @var int $offset
      */
     public $offset;
     /**
-     * @property array $header
+     * @var array $header
      */
     public $header;
     /**
-     * @property string $payload 消息内容
+     * @var string $payload 消息内容
      */
     public $payload;
     /**
-     * @property $timestamp 消息时间戳(一般为毫秒)
+     * @var $timestamp 消息时间戳(一般为毫秒)
      */
     public $timestamp;
     /**
diff --git a/doc/mysql.php b/doc/mysql.php
index 801f42e..01da9cf 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -242,7 +242,7 @@ function get(string $table, string $field, rray $where, mixed $order = null) {
  */
 class result {
     /**
-     * @property 结果集包含的记录行数
+     * @var 结果集包含的记录行数
      */
     public $fetched_rows;
     /**
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
index 4e9a80d..eca0387 100644
--- a/doc/rabbitmq.php
+++ b/doc/rabbitmq.php
@@ -76,7 +76,7 @@ class message implements JsonSerializable {
     public $priority;
     public $delivery_mode;
     /**
-     * @property array
+     * @var array
      */
     public $header;
     public $content_encoding;
diff --git a/doc/tcp.php b/doc/tcp.php
index 5d66c77..ef53c5b 100644
--- a/doc/tcp.php
+++ b/doc/tcp.php
@@ -15,12 +15,12 @@ function connect(string $address):socket {
 class socket {
     /**
      * 本地地址（含端口）
-     * @property string
+     * @var string
      */
     public $local_address;
     /**
      * 远端地址（含端口）
-     * @property string
+     * @var string
      */
     public $remote_address;
     /**
diff --git a/doc/udp.php b/doc/udp.php
index 78599ed..49a8538 100644
--- a/doc/udp.php
+++ b/doc/udp.php
@@ -27,12 +27,12 @@ class socket {
     function __construct(mixed $opt_or_address = null, array $option = []) {}
     /**
      * 本地地址（含端口）
-     * @property string
+     * @var string
      */
     public $local_address;
     /**
      * 远端地址（含端口）
-     * @property string
+     * @var string
      */
     public $remote_address;
     /**
diff --git a/src/flame.cpp b/src/flame.cpp
index 90489af..0cda7bb 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -115,12 +115,14 @@ extern "C"
 
         ext
             .desc({"vendor/boost", BOOST_LIB_VERSION})
-            .desc({"vendor/libphpext", PHPEXT_LIB_VERSION})
+            .desc({"vendor/phpext", PHPEXT_LIB_VERSION})
             .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
             .desc({"vendor/mysqlc", mysql_get_client_info()})
             .desc({"vendor/amqpcpp", "4.1.4"})
-            .desc({"vendor/librdkafka", rd_kafka_version_str()})
-            .desc({"vendor/mongoc", MONGOC_VERSION_S});
+            .desc({"vendor/rdkafka", rd_kafka_version_str()})
+            .desc({"vendor/mongoc", MONGOC_VERSION_S})
+            .desc({"vendor/nghttp2", NGHTTP2_VERSION})
+            .desc({"vendor/curl", curl_version()});
 
         flame::gcontroller.reset(new flame::controller());
         ext
diff --git a/src/http/_connection_pool.cpp b/src/http/_connection_pool.cpp
deleted file mode 100644
index a5fa285..0000000
--- a/src/http/_connection_pool.cpp
+++ /dev/null
@@ -1,247 +0,0 @@
-#include "../controller.h"
-#include "../coroutine.h"
-#include "value_body.h"
-#include "client_request.h"
-#include "client_response.h"
-#include "_connection_pool.h"
-#include "../url.h"
-
-namespace flame::http
-{
-    _connection_pool::_connection_pool(uint32_t cph)
-    : max_(cph), tm_(gcontroller->context_x), resolver_(gcontroller->context_x) {
-    }
-
-    _connection_pool::~_connection_pool() {
-    }
-
-    void _connection_pool::acquire(std::shared_ptr<url> url, std::shared_ptr<_connection_pool::context_t> ctx) {
-        auto type = url->schema + url->host + std::to_string(url->port);
-        while(p_.count(type) > 0) {
-            auto it = p_.find(type);
-            if(it != p_.end()) {
-                auto s = it->second.first;
-                p_.erase(it);
-                s->async_wait(tcp::socket::wait_write, [ctx](const boost::system::error_code& ec) {
-                    if(ec == boost::asio::error::operation_aborted || (ctx->timeout())) return ;
-                    ctx->ec_ = ec;
-                    ctx->ch_.resume();
-                });
-                ctx->ch_.suspend();
-                if(ctx->timeout())
-                    goto DONE;
-                if(!ctx->ec_) {
-                    ctx->c_ = s;
-                    goto DONE;
-                }
-                // 连接异常，直接释放
-                release(type, nullptr);
-            }
-            ctx->ec_ = boost::system::error_code{};
-        }
-
-        // 未获取到连接，且连接数达到最大
-        if(!ctx->c_ && ps_[type] >= max_) {
-            // 此处可能由于释放的连接已关闭
-            // 导致此处连接为空，而需要重建连接
-            ctx->wait();
-            // 在此处release连接会导致连接数只增不减
-            // 此处超时不应该减连接数
-            if(!ctx->c_ && !ctx->timeout())
-                --ps_[type];
-        }
-DONE:
-        // 引处不由于超时，并未获取到连接，所以无需释放连接
-        if(ctx->timeout())
-            throw php::exception(zend_ce_exception, (boost::format("timeout to acquire new connection, host %1%, connection limit %2%") % (url->host + std::to_string(url->port)) % max_).str());
-        if(!ctx->c_)
-            create(url, ctx);
-    }
-
-    void _connection_pool::create(std::shared_ptr<url> u, std::shared_ptr<_connection_pool::context_t> ctx) {
-        auto c    = std::make_shared<tcp::socket>(gcontroller->context_x);
-        auto type = u->schema + u->host + std::to_string(u->port);
-        tcp::resolver::results_type eps_;
-        ++ps_[type];
-        resolver_.async_resolve(
-            u->host, 
-            std::to_string(u->port), 
-            [&eps_, ctx, c] (const boost::system::error_code& ec, tcp::resolver::results_type eps) {
-                if(ctx->timeout()) return ;
-                ctx->ec_ = ec;
-                eps_ = std::move(eps);
-                ctx->ch_.resume();
-        });
-        ctx->ch_.suspend();
-        if(ctx->ec_ || ctx->timeout())
-            goto END;
-
-        ctx->to_ |= context_t::CONNECT;
-        boost::asio::async_connect(*c, eps_, [c, ctx] (const boost::system::error_code& ec, const tcp::endpoint& ep) {
-            if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
-            ctx->ec_ = ec;
-            ctx->ch_.resume();
-        });
-        ctx->ch_.suspend();
-END:
-        if(ctx->timeout()) {
-            if(ctx->to_ & context_t::CONNECT)
-                c->cancel();
-            release(type, nullptr);
-            throw php::exception(zend_ce_exception, "timeout to create new connection");
-        } 
-        if(ctx->ec_) {
-            release(type, nullptr);
-            ctx->tm_.cancel();
-            throw php::exception(zend_ce_exception, (boost::format("failed to create new connection: (%1%) %2%") % ctx->ec_.value() % ctx->ec_.message()).str(), ctx->ec_.value());
-        }
-        ctx->c_ = c;
-    }
-
-    php::value _connection_pool::execute(client_request* req, int32_t timeout, coroutine_handler &ch) {
-        auto type = req->url_->schema + req->url_->host + std::to_string(req->url_->port);
-        auto ctx  = std::make_shared<_connection_pool::context_t>(ch, wl_[type], timeout);
-        req->build_ex(ctx->req_);
-        
-        ctx->expire();
-        acquire(req->url_, ctx);
-        // 构建并发送请求
-        boost::beast::http::async_write(*ctx->c_, ctx->req_, [ctx](const boost::system::error_code& ec, std::size_t n) {
-            if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
-            ctx->ec_ = ec;
-            ctx->ch_.resume();
-        });
-        ctx->ch_.suspend();
-        if(ctx->timeout()) {
-            ctx->c_->cancel();
-            release(type, nullptr);
-            throw php::exception(zend_ce_exception, "failed to write request: timeout");
-        }
-        if(ctx->ec_) {
-            ctx->tm_.cancel();
-            release(type, nullptr);
-            throw php::exception(zend_ce_exception, (boost::format("failed to write request: (%1%) %2%") % ctx->ec_.value() % ctx->ec_.message()).str(), ctx->ec_.value());
-        }
-
-        // 构建并读取响应
-        auto res_ref = php::object(php::class_entry<client_response>::entry());
-        auto res_    = static_cast<client_response*>(php::native(res_ref));
-        boost::beast::http::async_read(*ctx->c_, ctx->buf_, ctx->res_, [ctx] (const boost::system::error_code& ec, std::size_t n) {
-            if(ec == boost::asio::error::operation_aborted || ctx->timeout()) return ;
-            ctx->ec_  = ec;
-            ctx->to_ |= context_t::DONE;
-            ctx->ch_.resume();
-        });
-
-        ctx->ch_.suspend();
-        if(ctx->timeout()) {
-            ctx->c_->cancel();
-            release(type, nullptr);
-            throw php::exception(zend_ce_exception, "failed to read response: timeout");
-        }
-        if(ctx->ec_) {
-            ctx->tm_.cancel();
-            release(type, nullptr);
-            throw php::exception(zend_ce_exception, (boost::format("failed to read response: (%1%) %2%") % ctx->ec_.value() % ctx->ec_.message()).str(), ctx->ec_.value());
-        }
-
-        res_->build_ex(ctx->res_);
-        ctx->tm_.cancel();
-
-        if(ctx->req_.keep_alive() && ctx->res_.keep_alive())
-            release(type, ctx->c_);
-        else
-            release(type, nullptr);
-        return res_ref;
-    }
-
-    void _connection_pool::release(std::string type, std::shared_ptr<tcp::socket> s) {
-        // read / write timeout，释放空连接
-        // 队列中有等待，导致callback->resume会在io run 排队，
-        // 导致其他协程通过 connections < max 
-        if (wl_[type].empty()) {
-            // 无等待分配的请求
-            if(s)
-                p_.insert(std::make_pair(type, std::make_pair(s, std::chrono::steady_clock::now())));
-            else
-                --ps_[type];
-        } else {
-            // 立刻分配使用
-            auto ctx = wl_[type].front();
-            wl_[type].pop_front();
-            // 释放空连接时，连接池计数在cb->resume后减小
-            (*ctx)(s);
-        }
-    }
-
-    void _connection_pool::sweep() {
-        tm_.expires_from_now(std::chrono::seconds(30));
-        tm_.async_wait([this] (const boost::system::error_code &ec) {
-            auto now = std::chrono::steady_clock::now();
-            if(ec) return; // 当前对象销毁时会发生对应的 abort 错误
-
-            for (auto i = p_.begin(); i != p_.end();) {
-                auto itvl = std::chrono::duration_cast<std::chrono::seconds>(now - i->second.second);
-                if(itvl.count() > 30) {
-                    //--ps_[i->first];
-                    release(i->first, nullptr);
-                    i = p_.erase(i);
-                } else
-                    ++i;
-            }
-            for(auto i = wl_.begin(); i != wl_.end(); ) {
-                if(i->second.empty())
-                    i = wl_.erase(i);
-                else
-                    ++i;
-            }
-            // 再次启动
-            sweep();
-        });
-    }
-
-    _connection_pool::context_t::context_t(coroutine_handler &ch, _connection_pool::queue_t& q, int32_t to) 
-    : ch_(ch)
-    , to_(NORMAL)
-    , tm_(gcontroller->context_x)
-    , q_(q)
-    , tp_(std::chrono::steady_clock::now() + std::chrono::milliseconds(to) ){
-    }
-    void _connection_pool::context_t::expire() {
-        boost::system::error_code ec;
-        tm_.expires_at(tp_);
-        tm_.async_wait([this, ctx = shared_from_this()](const boost::system::error_code &ec) {
-            if(ec == boost::asio::error::operation_aborted || (to_ & DONE)) return;
-            if(to_ & WAIT) {
-                for(auto i = q_.begin(); i != q_.end(); ++i ) {
-                    if( i->get() == this) {
-                        q_.erase(i);
-                        break;
-                    }
-                }
-                to_ &= ~WAIT;
-            }
-            to_ |= TIMEOUT;
-            ec_  = ec;
-            ch_.resume();
-        });
-    }
-    void _connection_pool::context_t::wait() {
-        to_ |= WAIT;
-        q_.push_back(shared_from_this());
-        ch_.suspend();
-    }
-    bool _connection_pool::context_t::timeout() {
-        return to_ & TIMEOUT;
-    }
-    void _connection_pool::context_t::operator()(std::shared_ptr<tcp::socket> c) {
-        // timeout的resume与此处的resume可能会在io_context上排队
-        // 在timeout时，需要先清理queue，再做resume
-        if(timeout()) return ;
-        to_ &= ~WAIT;
-        c_ = c;
-        ch_.resume();
-    }
-}
-
-
diff --git a/src/http/_connection_pool.h b/src/http/_connection_pool.h
deleted file mode 100644
index de8c5d3..0000000
--- a/src/http/_connection_pool.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#pragma once
-#include "../vendor.h"
-#include "../url.h"
-
-namespace flame::http
-{
-    class _connection_pool : public std::enable_shared_from_this<_connection_pool>
-    {
-        struct context_t;
-    public:
-        _connection_pool(uint32_t cph);
-        ~_connection_pool();
-        void acquire(std::shared_ptr<url> url, std::shared_ptr<context_t> ctx);
-        void create(std::shared_ptr<url> u, std::shared_ptr<context_t> ctx);
-        php::value execute(client_request* req, int32_t timeout, coroutine_handler &ch);
-        void release(std::string type, std::shared_ptr<tcp::socket> s);
-        void sweep();
-    private:
-
-        using time_t_ = std::chrono::time_point<std::chrono::steady_clock>;
-        using queue_t = std::list<std::shared_ptr<context_t>>;
-
-        struct context_t : public std::enable_shared_from_this<context_t>{
-            context_t(coroutine_handler &ch, queue_t& q, int32_t to);
-            void expire();
-            void wait();
-            bool timeout();
-            void operator()(std::shared_ptr<tcp::socket>);
-
-            // enum status  normal canceled
-            enum {
-                NORMAL  = 0x0,
-                TIMEOUT = 0x1,
-                WAIT    = 0x1 << 1,
-                CONNECT = 0x1 << 2,
-                DONE    = 0x1 << 3
-            };
-            std::shared_ptr<tcp::socket> c_;
-            boost::asio::steady_timer    tm_;
-            boost::system::error_code    ec_;
-            coroutine_handler            ch_;
-            queue_t                      &q_;
-            int32_t                      to_;
-            time_t_                      tp_;
-
-            boost::beast::http::message<true, value_body<true>> req_;
-            boost::beast::http::message<false, value_body<false>> res_;
-            boost::beast::flat_buffer buf_;
-        };
-
-        const std::uint32_t max_;
-
-        boost::asio::ip::tcp::resolver resolver_;
-        std::map<std::string, int32_t> ps_; // pool_size
-        std::multimap<std::string, std::pair<std::shared_ptr<tcp::socket>, time_t_>> p_; // connection pool
-        std::map<std::string, queue_t> wl_; // await list
-        boost::asio::steady_timer tm_;
-    };
-
-}
diff --git a/src/http/client.cpp b/src/http/client.cpp
index c017da5..d820ad7 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -1,15 +1,15 @@
+#include "../controller.h"
 #include "../coroutine.h"
 #include "client.h"
 #include "value_body.h"
 #include "client_request.h"
 #include "client_response.h"
-#include "_connection_pool.h"
+#include "../time/time.h"
 
 namespace flame::http {
     void client::declare(php::extension_entry& ext) {
         php::class_entry<client> class_client("flame\\http\\client");
         class_client
-            .property({"connection_per_host", 4})
             .method<&client::__construct>("__construct", {
                 {"options", php::TYPE::ARRAY, false, true},
             })
@@ -36,31 +36,161 @@ namespace flame::http {
             });
             ext.add(std::move(class_client));
     }
-	client::client()
-    : cp_(std::make_shared<_connection_pool>(16)) {
+
+    void client::check_done() {
+        CURLMsg *msg;
+        int left;
+        client_response* res_;
+        while((msg = curl_multi_info_read(c_multi_, &left))) {
+            if(msg->msg == CURLMSG_DONE) {
+                curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &res_);
+                res_->c_final_ = msg->data.result;
+                res_->c_coro_.resume();
+            }
+        }
+    }
+
+    void client::c_socket_ready_cb(const boost::system::error_code& error, curl_socket_t fd, int action, int* expect) {
+        if(error == boost::asio::error::operation_aborted || action != *expect) return; // 取消监听或已设置新的监听
+        // 不在处理已经关闭的等待
+        auto i = c_socks_.find(fd);
+        if(i == c_socks_.end()) return;
+        // 本次 SOCKET 动作执行
+        if(error) action = CURL_CSELECT_ERR;
+        curl_multi_socket_action(c_multi_, fd, action, &c_still_);
+        check_done();
+        if(c_still_ <= 0) c_timer_.cancel();
+        if(error || action != *expect) return; // 发生错误或监听被变更
+        action = *expect;
+        // 未变更时继续监听
+        if(action == CURL_POLL_IN || action == CURL_POLL_INOUT) 
+            i->second->async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client::c_socket_ready_cb, this, std::placeholders::_1, fd, action, expect));
+        if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT) 
+            i->second->async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client::c_socket_ready_cb, this, std::placeholders::_1, fd, action, expect));
+    }
+
+    int client::c_socket_cb(CURL* e, curl_socket_t fd, int action, void* data, void* sock_data) {
+        client* self = reinterpret_cast<client*>(data);
+        if(action == CURL_POLL_REMOVE) {
+            delete (int*)sock_data;
+            curl_multi_assign(self->c_multi_, fd, nullptr);
+            return 0;
+        }
+        int* expect = reinterpret_cast<int*>(sock_data);
+        boost::asio::ip::tcp::socket* sock = self->c_socks_[fd];
+        if(!sock_data) {
+            expect = new int { action };
+            curl_multi_assign(self->c_multi_, fd, expect);
+        }else{
+            *expect = action;
+        }
+        // 根据新设置重新设置监听
+        if(action == CURL_POLL_IN || action == CURL_POLL_INOUT)
+            sock->async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client::c_socket_ready_cb, self, std::placeholders::_1, fd, action, expect));
+        if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
+            sock->async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client::c_socket_ready_cb, self, std::placeholders::_1, fd, action, expect));
+        return 0;
+    }
+
+    int client::c_timer_cb(CURLM *m, long timeout_ms, void* data) {
+        client* self = reinterpret_cast<client*>(data);
+
+        self->c_timer_.cancel();
+        if(timeout_ms >= 0) {
+            self->c_timer_.expires_after(std::chrono::milliseconds(timeout_ms));
+            self->c_timer_.async_wait([self] (const boost::system::error_code& err) {
+                if(err) return;
+                curl_multi_socket_action(self->c_multi_, CURL_SOCKET_TIMEOUT, 0, &self->c_still_);
+                self->check_done();
+            });
+        }
+        return 0;
+    }
+
+    client::client()
+    : c_timer_(gcontroller->context_x) {
+        c_multi_ = curl_multi_init();
+        curl_multi_setopt(c_multi_, CURLMOPT_SOCKETDATA, this);
+        curl_multi_setopt(c_multi_, CURLMOPT_SOCKETFUNCTION, client::c_socket_cb);
+        curl_multi_setopt(c_multi_, CURLMOPT_TIMERDATA, this);
+        curl_multi_setopt(c_multi_, CURLMOPT_TIMERFUNCTION, client::c_timer_cb);
+
+        curl_multi_setopt(c_multi_, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX | CURLPIPE_HTTP1);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAX_PIPELINE_LENGTH, 4);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, 16);
+    }
+
+    client::~client() {
+        curl_multi_cleanup(c_multi_);
     }
+
     php::value client::__construct(php::parameters& params) {
         if(params.size() > 0) {
             php::array opts = params[0];
             if(opts.exists("connection_per_host")) {
-                int cph = opts.get("connection_per_host");
-                if(cph > 0 && cph < 512)
-                    cp_ = std::make_shared<_connection_pool>(cph);
+                int connection_per_host = opts.get("connection_per_host");
+                if(connection_per_host < 1 || connection_per_host > 512) 
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, connection_per_host);
             }
         }
-        cp_->sweep();
         return nullptr;
     }
+
+    curl_socket_t client::c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* address) {
+        boost::system::error_code error;
+        boost::asio::ip::tcp::socket* sock = new boost::asio::ip::tcp::socket(gcontroller->context_x);
+        if(purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET) {
+            sock->open(boost::asio::ip::tcp::v4(), error);
+        }else if(purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET6) {
+            sock->open(boost::asio::ip::tcp::v6(), error);
+        }
+        if(error) {
+            std::cerr << "[" << flame::time::iso() << "] (ERROR) failed to open socket: " << error.message() << "\n";
+            return CURL_SOCKET_BAD;
+        }
+        curl_socket_t fd = sock->native_handle();
+        client* self = reinterpret_cast<client*>(data);
+        self->c_socks_.insert({fd, sock});
+        return fd;
+    }
+
+    int client::c_socket_close_cb(void* data, curl_socket_t fd) {
+        client* self = reinterpret_cast<client*>(data);
+        auto i = self->c_socks_.find(fd);
+        if(i != self->c_socks_.end()) {
+            delete i->second;
+            self->c_socks_.erase(i);
+        }
+        return 0;
+    }
+
     php::value client::exec_ex(const php::object& req) {
-        coroutine_handler ch{coroutine::current};
         auto req_ = static_cast<client_request*>(php::native(req));
-        req_->build_url();
-        if(req_->url_->schema.compare("http") == 0) {
-            return cp_->execute(req_, static_cast<int>(req_->get("timeout")), ch);
-        } /*else if(req_->url_->schema.compare("https") == 0) {*/
-            //return cp_->execute(req_, timeout, ch);
-        /*}*/
-        throw php::exception(zend_ce_exception, (boost::format("HTTP schema \'%1%\' not implemented)") % req_->url_->schema).str());
+        if(req_->c_easy_ == nullptr) req_->c_easy_ = curl_easy_init();
+        curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETFUNCTION, c_socket_open_cb);
+        curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETDATA, this);
+        curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETFUNCTION, c_socket_close_cb);
+        curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETDATA, this);
+        req_->build_ex();
+
+        php::object res(php::class_entry<client_response>::entry());
+        auto res_ = static_cast<client_response*>(php::native(res));
+        res_->c_easy_ = req_->c_easy_;
+        res_->c_coro_.reset(coroutine::current);
+        res_->c_head_ = php::array(4);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_WRITEFUNCTION, client_response::c_write_cb);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_WRITEDATA, res_);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_HEADERFUNCTION, client_response::c_header_cb);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_HEADERDATA, res_);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_ERRORBUFFER, res_->c_error_);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_PRIVATE, res_);
+
+        curl_multi_add_handle(c_multi_, res_->c_easy_);
+        res_->c_coro_.suspend(); // <----- check_done
+
+        res_->build_ex();
+        curl_multi_remove_handle(c_multi_, res_->c_easy_);
+        return std::move(res);
     }
     php::value client::exec(php::parameters& params) {
         return exec_ex(params[0]);
diff --git a/src/http/client.h b/src/http/client.h
index 732fe2a..9fa6132 100644
--- a/src/http/client.h
+++ b/src/http/client.h
@@ -1,11 +1,12 @@
 #pragma once
+#include "../vendor.h"
 
 namespace flame::http {
-    class _connection_pool;
     class client: public php::class_base {
     public:
         static void declare(php::extension_entry& ext);
         client();
+        ~client();
         php::value __construct(php::parameters& params);
         php::value exec(php::parameters& params);
         php::value get(php::parameters& params);
@@ -13,7 +14,17 @@ namespace flame::http {
         php::value put(php::parameters& params);
         php::value delete_(php::parameters& params);
     private:
+        CURLM *c_multi_ = nullptr;
+        int    c_still_ = 0;
+        std::map<curl_socket_t, boost::asio::ip::tcp::socket*> c_socks_;
+        boost::asio::steady_timer c_timer_;
+
         php::value exec_ex(const php::object& req);
-        std::shared_ptr<_connection_pool> cp_;
+        static int c_socket_cb(CURL* e, curl_socket_t fd, int action, void* cbp, void* data);
+        static int c_timer_cb(CURLM *m, long timeout_ms, void* data);
+        static curl_socket_t c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* address);
+        static int c_socket_close_cb(void* data, curl_socket_t fd);
+        void c_socket_ready_cb(const boost::system::error_code& error, curl_socket_t fd, int action, int* expect);
+        void check_done();
     };
 }
diff --git a/src/http/client_body.cpp b/src/http/client_body.cpp
new file mode 100644
index 0000000..5a2f5fb
--- /dev/null
+++ b/src/http/client_body.cpp
@@ -0,0 +1,8 @@
+#include "client_body.h"
+
+namespace flame::http {
+	void client_body::declare(php::extension_entry& ext) {
+		php::class_entry<client_body> class_client_body("flame\\http\\client_body");
+        ext.add(std::move(class_client_body));
+	}
+}
\ No newline at end of file
diff --git a/src/http/client_body.h b/src/http/client_body.h
new file mode 100644
index 0000000..808e56e
--- /dev/null
+++ b/src/http/client_body.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::http {
+	class client_body: public php::class_base {
+	public:
+		static void declare(php::extension_entry& ext);
+	};
+}
\ No newline at end of file
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 4b493dd..6cad9f5 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -1,6 +1,7 @@
 #include "../coroutine.h"
 #include "value_body.h"
 #include "client_request.h"
+#include "client_body.h"
 #include "http.h"
 
 namespace flame {
@@ -19,16 +20,16 @@ namespace http {
 				{"url", php::TYPE::STRING},
 				{"body", php::TYPE::UNDEFINED, false, true},
 				{"timeout", php::TYPE::INTEGER, false, true},
-			});
+			})
+			.method<&client_request::__destruct>("__destruct");
 
 		ext.add(std::move(class_client_request));
 	}
+
 	php::value client_request::__construct(php::parameters& params) {
-		if (params.length() > 2) {
-			set("timeout", params[2]);
-		}else{
-			set("timeout", 3000);
-		}
+		if (params.length() > 2) set("timeout", params[2]);
+		else set("timeout", 3000);
+
 		if (params.length() > 1 && !params[1].empty()) {
 			set("body", params[1]);
 			set("method", "POST");
@@ -38,47 +39,43 @@ namespace http {
 		set("url", params[0]);
 		set("header", php::array(0));
 		set("cookie", php::array(0));
+
+		c_easy_ = curl_easy_init();
 		return nullptr;
 	}
-	void client_request::build_url()
-	{
-		if(url_) return; // 防止重复 build
+
+	php::value client_request::__destruct(php::parameters &params) {
+		if(c_head_) curl_slist_free_all(c_head_);
+		if(c_easy_) curl_easy_cleanup(c_easy_);
+		return nullptr;
+	}
+
+	void client_request::build_ex() {
 		// 目标请求地址
+		// ---------------------------------------------------------------------------
         php::string u = get("url", true);
 		if(!u.typeof(php::TYPE::STRING)) {
 			throw php::exception(zend_ce_exception, "request 'url' must be a string");
 		}
-
-		url_ = std::make_shared<url>(u, false);
-		if(!url_->port && !url_->schema.compare(0, 5, "https")) {
-			url_->port = 443;
-		}else if(!url_->port && !url_->schema.compare(0, 4, "http")) {
-			url_->port = 80;
-		}
-	}
-	void client_request::build_ex(boost::beast::http::message<true, value_body<true>>& ctr_)
-	{
-		// if(url_) return; // 防止重复 build
-		// 消息容器
-		ctr_.method(boost::beast::http::string_to_verb(get("method").to_string()));
-		std::string target(url_->path.length() > 0? url_->path : "/");
-		if(!url_->query_raw.empty()) {
-			target.append("?");
-			target.append(url_->query_raw);
-		}
-		ctr_.target(target);
+		curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
+		php::string m = get("method", true);
+		curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
 		// 头
+		// ---------------------------------------------------------------------------
+		if(c_head_ != nullptr) curl_slist_free_all(c_head_);
+		std::string ctype;
 		php::array header = get("header");
 		for(auto i=header.begin(); i!=header.end(); ++i) {
-			php::string key = i->first;
-			php::string val = i->second;
-			val.to_string();
-			ctr_.set(boost::string_view(key.c_str(), key.size()), val);
-		}
-		if(ctr_.find(boost::beast::http::field::host) == ctr_.end()) {
-			ctr_.set(boost::beast::http::field::host, url_->host);
+			std::string key = i->first;
+			std::string val = i->second;
+			if(strncasecmp(key.c_str(), "content-type", 12) == 0) {
+				ctype = val;
+			}
+			c_head_ = curl_slist_append(c_head_, (boost::format("%s: %s") % key % val).str().c_str());
 		}
+		curl_easy_setopt(c_easy_, CURLOPT_HTTPHEADER, c_head_);
 		// COOKIE
+		// ---------------------------------------------------------------------------
 		php::array cookie = get("cookie");
 		php::buffer cookies;
 		for(auto i=cookie.begin(); i!=cookie.end(); ++i) {
@@ -91,17 +88,22 @@ namespace http {
 			cookies.push_back(';');
 			cookies.push_back(' ');
 		}
-		ctr_.set(boost::beast::http::field::cookie, php::string(std::move(cookies)));
+		php::string cookie_str = std::move(cookies);
+		curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
+		// 体
+		// ---------------------------------------------------------------------------
 		php::string body = get("body");
-CTYPE_AGAIN:
-		auto ctype = ctr_.find(boost::beast::http::field::content_type);
-		if(ctype == ctr_.end()) {
-			ctr_.set(boost::beast::http::field::content_type, "application/x-www-form-urlencoded");
-			goto CTYPE_AGAIN;
+		if(ctype.empty()) ctype.assign("application/x-www-form-urlencoded", 33);
+		if(body.empty()) {
+
+		}else if(body.instanceof(php::class_entry<client_body>::entry())) {
+			// TODO multipart support
+		}else{
+			body = ctype_encode(ctype, body);
+			// 注意: CURLOPT_POSTFIELDS 仅"引用" body 数据
+			set("body", body);
+			curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDS, body.c_str());
 		}
-		if(body.empty()) return;
-		ctr_.body() = ctype_encode(ctype->value(), body);
-		ctr_.prepare_payload();
 	}
 }
 }
diff --git a/src/http/client_request.h b/src/http/client_request.h
index 0e94b06..79af184 100644
--- a/src/http/client_request.h
+++ b/src/http/client_request.h
@@ -1,4 +1,6 @@
 #pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
 #include "../url.h"
 
 namespace flame::http {
@@ -6,10 +8,12 @@ namespace flame::http {
 	public:
 		static void declare(php::extension_entry& ext);
 		php::value __construct(php::parameters& params);
+		php::value __destruct(php::parameters& params);
 	private:
-		void build_url();
-		void build_ex(boost::beast::http::message<true, value_body<true>>& ctr_);
-		std::shared_ptr<url> url_;
+		void build_ex();
+		CURL*       c_easy_ = nullptr;
+		curl_slist* c_head_ = nullptr;
+		
 		friend class client;
 		friend class _connection_pool;
     };
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index a6905b5..d6ff8cb 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -1,4 +1,3 @@
-#include "../coroutine.h"
 #include "http.h"
 #include "value_body.h"
 #include "client_response.h"
@@ -8,10 +7,11 @@ namespace http {
 	void client_response::declare(php::extension_entry& ext) {
 		php::class_entry<client_response>  class_client_response("flame\\http\\client_response");
 		class_client_response
+			.property({"version", "HTTP/1.0"})
 			.property({"status", 3000})
 			.property({"header", nullptr})
 			.property({"body", nullptr})
-			.property({"rawBody", ""})
+			.property({"raw_body", ""})
 			.method<&client_response::__construct>("__construct", {}, php::PRIVATE)
 			.method<&client_response::to_string>("__toString");
 		ext.add(std::move(class_client_response));
@@ -21,32 +21,51 @@ namespace http {
 		return nullptr;	
 	}
 	php::value client_response::to_string(php::parameters& params) {
-		return get("rawBody");
+		return get("raw_body");
 	}
-	void client_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
-		set("status", ctr_.result_int());
-		php::array header = get("header", true);
-		if(header.typeof(php::TYPE::NULLABLE)) {
-			header = php::array(4);
+	void client_response::build_ex() {
+		// 响应码
+		long rv = 0;
+		curl_easy_getinfo(c_easy_, CURLINFO_HTTP_VERSION, &rv);
+        set("version", rv == 3 ? "HTTP/2" : rv == 2 ? "HTTP/1.1" : "HTTP/1.0");
+		curl_easy_getinfo(c_easy_, CURLINFO_RESPONSE_CODE, &rv);
+		set("status", rv);
+		// 响应头
+		set("header", c_head_);
+		// 响应体
+		php::string rbody = std::move(c_body_);
+		set("raw_body", rbody);
+		if(c_head_.exists("content_type")) {
+			php::string ctype = c_head_.get("content-type");
+			std::string_view sv {ctype.c_str(), ctype.size()};
+			set("body", ctype_decode(sv, rbody));
+		}else{
+			set("body", rbody);
 		}
-		for(auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
-			php::string key {i->name_string().data(), i->name_string().size()};
-			php::string val {i->value().data(), i->value().size()};
+	}
 
-			// TODO 多个同名 HEADER 的处理
-			php::lowercase_inplace(key.data(), key.size());
-			header.set(key, val);
-		}
-		php::string body = ctr_.body();
-		if(body.typeof(php::TYPE::STRING)) {
-			set("rawBody", body);
-			auto ctype = ctr_.find(boost::beast::http::field::content_type);
-			if(ctype == ctr_.end()) {
-				set("body", body);
+	size_t client_response::c_write_cb(char *ptr, size_t size, size_t nmemb, void *data) {
+		client_response* self = static_cast<client_response*>(data);
+		// assert(size == 1);
+		self->c_body_.append(ptr, nmemb);
+		return size;
+	}
+	size_t client_response::c_header_cb(char *buffer, size_t size, size_t nitems, void *data) {
+		client_response* self = static_cast<client_response*>(data);
+		size = size * nitems;		
+
+		std::string_view sv{ buffer, size };
+		auto psep = sv.find_first_of(':', 1);
+		if(psep != sv.npos) {
+			auto pval = sv.find_first_not_of(' ', psep + 1);
+			php::lowercase_inplace(buffer, psep);
+			if(pval == sv.npos) {
+				self->c_head_.set( php::string(buffer, psep), php::string(0) );
 			}else{
-				set("body", ctype_decode(ctype->value(), body));
-			}	
+				self->c_head_.set( php::string(buffer, psep), php::string( buffer + pval, size - pval - 2)); // trailing \r\n
+			}
 		}
+		return size;
 	}
 }
 }
diff --git a/src/http/client_response.h b/src/http/client_response.h
index 787db39..fa45d87 100644
--- a/src/http/client_response.h
+++ b/src/http/client_response.h
@@ -1,7 +1,8 @@
 #pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
 
-namespace flame {
-namespace http {
+namespace flame::http {
 	class client_response: public php::class_base {
 	public:
 		static void declare(php::extension_entry& ext);
@@ -9,8 +10,16 @@ namespace http {
 		// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
 		php::value __construct(php::parameters& params);
 	private:
-		void build_ex(boost::beast::http::message<false, value_body<false>>& ctr_);
-		friend class _connection_pool;
+		CURL*       c_easy_;
+		php::array  c_head_;
+		php::buffer c_body_;
+		CURLcode c_final_ = CURLE_OK;
+		char c_error_[CURL_ERROR_SIZE];
+		coroutine_handler c_coro_;
+		void build_ex();
+		// 接收 curl 回调(响应数据)
+		static size_t c_write_cb(char *ptr, size_t size, size_t nmemb, void *data);
+		static size_t c_header_cb(char *buffer, size_t size, size_t nitems, void *data);
+		friend class client;
 	};
 }
-}
diff --git a/src/http/http.cpp b/src/http/http.cpp
index 1c8931b..bc1ca82 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -5,7 +5,7 @@
 #include "value_body.h"
 #include "client_request.h"
 #include "client_response.h"
-#include "_connection_pool.h"
+#include "client_body.h"
 #include "server.h"
 #include "server_request.h"
 #include "server_response.h"
@@ -39,6 +39,7 @@ namespace flame::http {
 
 		client_request::declare(ext);
 		client_response::declare(ext);
+		client_body::declare(ext);
 		client::declare(ext);
 		server::declare(ext);
 		server_request::declare(ext);
@@ -69,7 +70,7 @@ namespace flame::http {
 		init_guard();
 		return client_->exec(params);
 	}
-	php::string ctype_encode(boost::string_view ctype, const php::value& v) {
+	php::string ctype_encode(std::string_view ctype, const php::value& v) {
 		if(v.typeof(php::TYPE::STRING)) return v;
 
 		php::value r = v;
@@ -86,7 +87,7 @@ namespace flame::http {
 		}
 		return r;
 	}
-	php::value ctype_decode(boost::string_view ctype, const php::string& v, php::array* files) {
+	php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* files) {
 		if(ctype.compare(0, 16, "application/json") == 0) {
 			return php::json_decode(v);
 		}else if(ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
@@ -130,7 +131,7 @@ namespace flame::http {
 					meta = php::array(4);
 				}else{ // Part Header
 					php::lowercase_inplace(entry.first.data(), entry.first.size());
-					std::size_t end = boost::string_view(entry.second.data(), entry.second.size()).find_first_of(';');
+					std::size_t end = std::string_view(entry.second.data(), entry.second.size()).find_first_of(';');
 					if(end == std::string::npos) {
 						meta[entry.first] = std::move(entry.second);
 					}else if(entry.first == "content-disposition") {
diff --git a/src/http/http.h b/src/http/http.h
index 9eafcc0..704c9a1 100644
--- a/src/http/http.h
+++ b/src/http/http.h
@@ -1,7 +1,7 @@
 #pragma once
+#include "../vendor.h"
 
-namespace flame {
-namespace http {
+namespace flame::http {
     class _connection_pool;
 	class client;
 	extern client* client_;
@@ -14,7 +14,6 @@ namespace http {
 	php::value delete_(php::parameters& params);
 	php::value exec(php::parameters& params);
 
-	php::string ctype_encode(boost::string_view ctype, const php::value& v);
-	php::value ctype_decode(boost::string_view ctype, const php::string& v, php::array* file = nullptr);
-}
+	php::string ctype_encode(std::string_view ctype, const php::value& v);
+	php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* file = nullptr);
 }
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index 3cf6bcf..417793f 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -186,7 +186,7 @@ namespace flame::http {
 			for(auto i=headers.begin(); i!=headers.end(); ++i) {
 				php::string key { i->first };
 				php::string val { i->second };
-				ctr_.set(boost::string_view(key.c_str(), key.size()), val);
+				ctr_.set(std::string_view(key.c_str(), key.size()), val);
 			}
 		}
 		php::array cookies = get("cookie", true);
diff --git a/src/vendor.h b/src/vendor.h
index f63a7a8..9df8942 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -15,6 +15,7 @@
 #include <utility>
 #include <memory>
 #include <stack>
+#include <string_view>
 #include <optional>
 #include <unordered_set>
 #include <phpext/phpext.h>
@@ -39,6 +40,7 @@
 using boost::asio::ip::tcp;
 using boost::asio::ip::udp;
 namespace ssl = boost::asio::ssl;
+#define BOOST_BEAST_USE_STD_STRING_VIEW
 #include <boost/beast/core.hpp>
 #include <boost/beast/http.hpp>
 #include <boost/beast/websocket.hpp>
@@ -54,3 +56,5 @@ extern "C" {
     #include <librdkafka/rdmurmur2.h>
     #include <librdkafka/xxhash.h>
 }
+#include <nghttp2/nghttp2ver.h>
+#include <curl/curl.h>

From 739b670360f8d6463ca4e3b05c6dcd6f1b132aa8 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 8 Apr 2019 17:03:17 +0800
Subject: [PATCH 085/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9AHTTP=20?=
 =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=20SSL=20=E7=9B=B8=E5=85=B3=E8=AE=BE?=
 =?UTF-8?q?=E7=BD=AE=E6=94=AF=E6=8C=81=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/http.php                | 56 +++++++++++++++++++++++++++++++++
 src/http/client_request.cpp | 62 +++++++++++++++++++++++++++++++++++++
 src/http/client_request.h   |  3 ++
 src/vendor.h                |  2 +-
 4 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/doc/http.php b/doc/http.php
index 6191a8e..424d904 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -82,6 +82,48 @@ function delete(string $url, int $timeout = 3000): client_response {
  * 客户端请求对象，用于拼装生成一个客户端请求（待执行）
  */
 class client_request {
+    /**
+     * 由框架自行选择版本
+     */
+    const HTTP_VERSION_NONE = 0;
+    /**
+     * 强制使用 HTTP/1.0 协议
+     */
+    const HTTP_VERSION_1_0   = 1;
+    /**
+     * 强制使用 HTTP/1.1 协议
+     */
+    const HTTP_VERSION_1_1   = 2;
+    /**
+     * 尝试使用 HTTP/2 协议，自动退回 HTTP/1.1 版本
+     */
+    const HTTP_VERSION_2     = 3;
+    /**
+     * 尝试在 TLS (HTTPS) 使用 HTTP/2 协议，自动退化 HTTP/1.1 版本
+     */
+    const HTTP_VERSION_2_TLS = 4;
+    /**
+     * 强制在 non-TLS 状态进行 HTTP/2 请求，TLS (HTTPS) 时同 HTTP_VERSION_2_TLS
+     */
+    const HTTP_VERSION_2_PRI = 5;
+
+    const SSL_VERIFY_NONE    = 0;
+    /**
+     * 验证对端证书
+     */
+    const SSL_VERIFY_PEER    = 1;
+    /**
+     * 验证证书域名
+     */
+    const SSL_VERIFY_HOST    = 2;
+    /*
+     * 验证证书状态
+     */
+    const SSL_VERIFY_STATUS  = 4;
+    /**
+     * 相当于 SSL_VERIFY_PEER | SSL_VERIFY_HOST | SSL_VERIFY_STATUS
+     */
+    const SSL_VERIFY_ALL     = 7;
     /**
      * @var int
      */
@@ -111,6 +153,20 @@ class client_request {
      * 构建请求, 可选的指定请求体 `$body` (将自动设置为 POST 请求方法)及超时时间 `$timeout`
      */
     function __construct(string $url, mixed $body = null, int $timeout = 3000) {}
+    /**
+     * @param string $cert_file 证书文件路径
+     * @param string $pkey_file 密钥文件路径
+     * @param string $pkey_pass 密钥密码
+     */
+    function ssl_pem(string $cert_file, string $pkey_file = "", string $pkey_pass = "") {}
+    /**
+     * 请使用 client_request::SSL_VERIFY_* 相关常量进行设置（可按位组合）
+     */
+    function ssl_verify(int $verify) {}
+    /**
+     * 请选择使用 client_request::HTTP_VERSION_* 常量设置版本
+     */
+    function http_version(int $version) {}
 }
 /**
  * 客户端响应对象，执行请求后得到
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 6cad9f5..e558885 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -10,6 +10,17 @@ namespace http {
 		php::class_entry<client_request> class_client_request(
 			"flame\\http\\client_request");
 		class_client_request
+			.constant({"HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0})
+			.constant({"HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1})
+			.constant({"HTTP_VERSION_2", CURL_HTTP_VERSION_2})
+			.constant({"HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0})
+			.constant({"HTTP_VERSION_2_TLS", CURL_HTTP_VERSION_2TLS})
+			.constant({"HTTP_VERSION_2_PRI", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE})
+			.constant({"SSL_VERIFY_NONE",   0})
+			.constant({"SSL_VERIFY_PEER",   1}) // CURLOPT_SSL_VERIFYPEER
+			.constant({"SSL_VERIFY_HOST",   2}) // CURLOPT_SSL_VERIFYHOST
+			.constant({"SSL_VERIFY_STATUS", 4}) // CURLOPT_SSL_VERIFYSTATUS
+			.constant({"SSL_VERIFY_ALL",    7})
 			.property({"timeout", 3000})
 			.property({"method", "GET"})
 			.property({"url", nullptr})
@@ -21,6 +32,16 @@ namespace http {
 				{"body", php::TYPE::UNDEFINED, false, true},
 				{"timeout", php::TYPE::INTEGER, false, true},
 			})
+			.method<&client_request::__construct>("http_version", {
+				{"version", php::TYPE::INTEGER},
+			})
+			.method<&client_request::ssl_pem>("ssl_pem", {
+				{"cert", php::TYPE::STRING},
+				{"pkey", php::TYPE::STRING, false, true},
+			})
+			.method<&client_request::ssl_verify>("ssl_verify", {
+				{"verify", php::TYPE::INTEGER},
+			})
 			.method<&client_request::__destruct>("__destruct");
 
 		ext.add(std::move(class_client_request));
@@ -50,6 +71,47 @@ namespace http {
 		return nullptr;
 	}
 
+	php::value client_request::http_version(php::parameters& params) {
+		long v = static_cast<int>(params[0]);
+		if(v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST) {
+			throw php::exception("failed to set http version: illegal version");
+		}
+		curl_easy_setopt(c_easy_, CURLOPT_HTTP_VERSION, v);
+		return nullptr;
+	}
+
+	php::value client_request::ssl_pem(php::parameters& params) {
+		curl_easy_setopt(c_easy_, CURLOPT_SSLCERTTYPE, "PEM");
+		php::string cert = params[0];
+		curl_easy_setopt(c_easy_, CURLOPT_SSLCERT, cert.c_str());
+		if(params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
+			php::string pkey = params[1];
+			curl_easy_setopt(c_easy_, CURLOPT_SSLKEY, pkey.c_str());
+		}
+		if(params.size() > 2 && params[2].typeof(php::TYPE::STRING)) {
+			php::string pass = params[2];
+			curl_easy_setopt(c_easy_, CURLOPT_KEYPASSWD, pass.c_str());
+		}
+		return nullptr;
+	}
+
+	php::value client_request::ssl_verify(php::parameters& params) {
+		long v = static_cast<int>(params[0]), YES = 1, NO = 0;
+		if(v | CURLOPT_SSL_VERIFYPEER) 
+			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, YES);
+		else
+			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, NO);
+		if(v | CURLOPT_SSL_VERIFYHOST)
+			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, YES);
+		else
+			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, NO);
+		if(v | CURLOPT_SSL_VERIFYSTATUS) 
+			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, YES);
+		else 
+			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, NO);
+		return nullptr;
+	}
+
 	void client_request::build_ex() {
 		// 目标请求地址
 		// ---------------------------------------------------------------------------
diff --git a/src/http/client_request.h b/src/http/client_request.h
index 79af184..21431a1 100644
--- a/src/http/client_request.h
+++ b/src/http/client_request.h
@@ -9,6 +9,9 @@ namespace flame::http {
 		static void declare(php::extension_entry& ext);
 		php::value __construct(php::parameters& params);
 		php::value __destruct(php::parameters& params);
+		php::value http_version(php::parameters& params);
+		php::value ssl_pem(php::parameters& params);
+		php::value ssl_verify(php::parameters& params);
 	private:
 		void build_ex();
 		CURL*       c_easy_ = nullptr;
diff --git a/src/vendor.h b/src/vendor.h
index 9df8942..e93682e 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.12.14"
+#define EXTENSION_VERSION "0.13.0"
 
 #include <sys/types.h>
 #include <sys/socket.h>

From e4e5c0e10bd16227fba62bdb63ea7d0761d37f7b Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 8 Apr 2019 17:11:34 +0800
Subject: [PATCH 086/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E8=87=AA?=
 =?UTF-8?q?=E5=8A=A8=E8=AE=BE=E7=BD=AE=E4=BF=9D=E6=8C=81=E8=BF=9E=E6=8E=A5?=
 =?UTF-8?q?=E6=95=B0=E9=87=8F=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/client.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/http/client.cpp b/src/http/client.cpp
index d820ad7..09c4aa9 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -118,6 +118,7 @@ namespace flame::http {
         curl_multi_setopt(c_multi_, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX | CURLPIPE_HTTP1);
         curl_multi_setopt(c_multi_, CURLMOPT_MAX_PIPELINE_LENGTH, 4);
         curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, 16);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, 4);
     }
 
     client::~client() {
@@ -129,8 +130,11 @@ namespace flame::http {
             php::array opts = params[0];
             if(opts.exists("connection_per_host")) {
                 int connection_per_host = opts.get("connection_per_host");
-                if(connection_per_host < 1 || connection_per_host > 512) 
+                if(connection_per_host < 1 || connection_per_host > 512) {
                     curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, connection_per_host);
+                    int keepalive = std::ceil(connection_per_host / 4.0);
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, keepalive);
+                }
             }
         }
         return nullptr;

From 246509a0f897ac3360aef50e85c016002fd4321e Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 8 Apr 2019 21:13:02 +0800
Subject: [PATCH 087/146] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=B9=B6=E5=AE=8C?=
 =?UTF-8?q?=E5=96=84=E7=BC=96=E8=AF=91=E7=8E=AF=E5=A2=83=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt | 15 +++++------
 README.md      | 69 +++++++++++++++++++++++---------------------------
 src/flame.cpp  |  2 +-
 3 files changed, 39 insertions(+), 47 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 657d986..565e663 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,7 @@ set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.0)
 set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.9.0)
 set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.37.0)
 set(VENDOR_CURL /data/vendor/curl-7.64.1)
+set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1)
 # -----------------------------------------------------------------------------
 # 编译选项
 # -----------------------------------------------------------------------------
@@ -42,9 +43,11 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND WITH_GCC_TOOLCHAIN)
 endif()
 target_compile_options(FLAME BEFORE
 	PRIVATE "-std=c++17"
+    PRIVATE "-pthread"
 	PRIVATE "${CLANG_EXTRA_COMPILE_FLAGS}")
 target_link_options(FLAME BEFORE
 	PRIVATE "-static-libstdc++"
+    PRIVATE "-pthread"
 	PRIVATE "${CLANG_EXTRA_COMPILE_FLAGS}")
 set_target_properties(FLAME PROPERTIES
     PREFIX ""
@@ -64,9 +67,8 @@ target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_HTTPPARSER}/include
     ${VENDOR_NGHTTP2}/include
     ${VENDOR_CURL}/include
-    /usr/include/openssl-1.0
+    ${VENDOR_OPENSSL}/include
 )
-target_link_directories(FLAME PUBLIC /usr/lib/openssl-1.0)
 # 链接库
 target_link_libraries(FLAME
     ${VENDOR_HTTPPARSER}/lib/libhttp_parser.o
@@ -84,14 +86,11 @@ target_link_libraries(FLAME
     ${VENDOR_BOOST}/lib/libboost_filesystem.a
     ${VENDOR_CURL}/lib/libcurl.a
     ${VENDOR_NGHTTP2}/lib/libnghttp2.a
-    ldap
     sasl2
-    lber
-    brotlidec
-    crypto
-    ssl
+    cares
+    ${VENDOR_OPENSSL}/lib/libssl.a
+    ${VENDOR_OPENSSL}/lib/libcrypto.a
     z
     dl
-    pthread
     rt
 )
diff --git a/README.md b/README.md
index 99b5578..01ba852 100644
--- a/README.md
+++ b/README.md
@@ -71,62 +71,53 @@ https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
 
 ### 依赖库
 
-> 注意：由于 mysqlc 驱动使用官方预编译版本，其对应 openssl 版本为 1.0.x ；故，其他依赖需对其版本依赖；
-
-#### PHP
+#### openssl
 ``` Bash
-./configure --prefix=/data/vendor/php-7.2.16 --with-config-file-path=/data/vendor/php-7.2.16/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --host=x86_64-linux-gnu --target=x86_64-linux-gnu
+CC=gcc CXX=g++ ./Configure no-shared --prefix=/data/vendor/openssl-1.1.1 linux-x86_64
+make && make install
 ```
-#### Boost
 
+#### boost
 ``` Bash
 ./bootstrap.sh --prefix=/data/vendor/boost-1.69.0
 ./b2 -j4 --prefix=/data/vendor/boost-1.69.0 cxxflags="-fPIC" variant=release link=static threading=multi install
 ```
 
-#### libphpext
+#### PHP
 ``` Bash
-CXXFLAGS="-O2" make -j4
-make install
+CC=gcc CXX=g++ ./configure --prefix=/data/vendor/php-7.2.16 --with-config-file-path=/data/vendor/php-7.2.16/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --host=x86_64-linux-gnu --target=x86_64-linux-gnu
 ```
 
-#### cpp-parser
+#### libphpext
 ``` Bash
+CXXFLAGS="-O2" make
 make install
 ```
 
-#### mysql-connector-c
+#### cpp-parser
 ``` Bash
-
-mkdir -p /data/vendor/mysqlc-8.0.15/lib
-cp -R mysql-8.0.15-linux-glibc2.12-x86_64/include /data/vendor/mysqlc-8.0.15
-cp mysql-8.0.15-linux-glibc2.12-x86_64/lib/libmysqlclient.a /data/vendor/mysqlc-8.0.15/lib
+make install
 ```
 
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
-# -DOPENSSL_INCLUDE_DIR=/usr/include/openssl-1.0 -DOPENSSL_SSL_LIBRARY=/usr/lib/openssl-1.0/libssl.so -DOPENSSL_CRYPTO_LIBRARY=/usr/lib/openssl-1.0/libcrypto.so
-make
-make install
-rm /data/vendor/mongoc-1.14.0/lib/*.so*
+CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+make && make install
+# rm /data/vendor/mongoc-1.14.0/lib/*.so*
 ```
 
 #### AMQP-CPP
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.4 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=on ../
-make
-make install
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.4 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
+make && make install
 ```
 
 #### rdkafka
 ``` Bash
-./configure --prefix=/data/vendor/rdkafka-1.0.0
-make
-make install
-rm /data/vendor/rdkafka-1.0.0/lib/*.so*
+CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.0.0
+# rm /data/vendor/rdkafka-1.0.0/lib/*.so*
 cp src/snappy.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
 cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
 cp src/xxhash.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
@@ -145,23 +136,25 @@ cp http_parser.h /data/vendor/http-parser-2.9.0/include
 ``` Bash
 CC=gcc make
 PREFIX=/data/vendor/hiredis-0.14.0 make install
-rm /data/vendor/hiredis-0.14.0/lib/*.so*
+# rm /data/vendor/hiredis-0.14.0/lib/*.so*
 ```
 
 #### nghttp2
-```
-# 需要统一 openssl 版本
-# PKG_CONFIG_PATH=/usr/lib/openssl-1.0/pkgconfig/
-CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/nghttp2-1.37.0 --enable-shared=no --enable-lib-only --with-boost=/data/vendor/boost-1.69.0 --enable-asio-lib
-make
-make install
+``` Bash
+PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/nghttp2-1.37.0 --enable-shared=no --enable-lib-only --with-boost=/data/vendor/boost-1.69.0 --enable-asio-lib
+make && make install
 ```
 
 #### curl
+``` Bash
+CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --prefix=/data/vendor/curl-7.64.1 --with-nghttp2=/data/vendor/nghttp2-1.37.0 --with-ssl=/data/vendor/openssl-1.1.1 --with-pic=pic --enable-ipv6 --enable-shared=no --without-libidn2 --disable-ldap --without-libpsl --without-lber --enable-ares
+make && make install
 ```
-# 需要统一 openssl 版本
-# PKG_CONFIG_PATH=/usr/lib/openssl-1.0/pkgconfig/
-CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --prefix=/data/vendor/curl-7.64.1 --with-nghttp2=/data/vendor/nghttp2-1.37.0 --with-pic=pic --enable-ipv6 --enable-shared=no
-make
-make install
+
+#### mysql-connector-c
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ cmake3 -DCMAKE_BUILD_TYPE=Release -DWITHOUT_SERVER=ON -DWITH_BOOST=../boost/boost_1_68_0 -DWITH_SSL=/data/vendor/openssl-1.1.1 -DOPENSSL_EXECUTABLE=/data/vendor/openssl-1.1.1/bin/openssl -DWITH_CURL=/data/vendor/curl-7.64.1 -DCMAKE_C_FLAGS=-pthread -DCMAKE_CXX_FLAGS=-pthread ../
+make && make install
 ```
+注意：需要下载内含 boost 头文件的版本（与框架依赖版本不同）；
diff --git a/src/flame.cpp b/src/flame.cpp
index 0cda7bb..806d669 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -122,7 +122,7 @@ extern "C"
             .desc({"vendor/rdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S})
             .desc({"vendor/nghttp2", NGHTTP2_VERSION})
-            .desc({"vendor/curl", curl_version()});
+            .desc({"vendor/curl", LIBCURL_VERSION});
 
         flame::gcontroller.reset(new flame::controller());
         ext

From 5ff53e1700065d8b40175fd9354ba2106a997e8c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 9 Apr 2019 22:07:48 +0800
Subject: [PATCH 088/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=20=E8=BF=9E?=
 =?UTF-8?q?=E6=8E=A5=E5=A4=8D=E7=94=A8=E5=8F=8A=E5=BC=82=E6=AD=A5=E7=AD=89?=
 =?UTF-8?q?=E5=BE=85=E6=9C=BA=E5=88=B6;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                    |   2 +-
 src/http/client.cpp          | 122 ++++++++++++++---------------------
 src/http/client.h            |   8 ++-
 src/http/client_poll.cpp     |  89 +++++++++++++++++++++++++
 src/http/client_poll.h       |  42 ++++++++++++
 src/http/client_request.cpp  |  13 +++-
 src/http/client_response.cpp |  11 ++--
 src/tcp/server.cpp           |  37 +++--------
 8 files changed, 215 insertions(+), 109 deletions(-)
 create mode 100644 src/http/client_poll.cpp
 create mode 100644 src/http/client_poll.h

diff --git a/README.md b/README.md
index 01ba852..f510507 100644
--- a/README.md
+++ b/README.md
@@ -154,7 +154,7 @@ make && make install
 #### mysql-connector-c
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake3 -DCMAKE_BUILD_TYPE=Release -DWITHOUT_SERVER=ON -DWITH_BOOST=../boost/boost_1_68_0 -DWITH_SSL=/data/vendor/openssl-1.1.1 -DOPENSSL_EXECUTABLE=/data/vendor/openssl-1.1.1/bin/openssl -DWITH_CURL=/data/vendor/curl-7.64.1 -DCMAKE_C_FLAGS=-pthread -DCMAKE_CXX_FLAGS=-pthread ../
+CC=gcc CXX=g++ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/mysqlc-8.0.15 -DWITHOUT_SERVER=ON -DWITH_BOOST=../boost/boost_1_68_0 -DWITH_SSL=/data/vendor/openssl-1.1.1 -DOPENSSL_EXECUTABLE=/data/vendor/openssl-1.1.1/bin/openssl -DWITH_CURL=/data/vendor/curl-7.64.1 -DCMAKE_C_FLAGS=-pthread -DCMAKE_CXX_FLAGS=-pthread ../
 make && make install
 ```
 注意：需要下载内含 boost 头文件的版本（与框架依赖版本不同）；
diff --git a/src/http/client.cpp b/src/http/client.cpp
index 09c4aa9..c373f44 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -1,7 +1,7 @@
 #include "../controller.h"
 #include "../coroutine.h"
 #include "client.h"
-#include "value_body.h"
+#include "client_poll.h"
 #include "client_request.h"
 #include "client_response.h"
 #include "../time/time.h"
@@ -50,45 +50,31 @@ namespace flame::http {
         }
     }
 
-    void client::c_socket_ready_cb(const boost::system::error_code& error, curl_socket_t fd, int action, int* expect) {
-        if(error == boost::asio::error::operation_aborted || action != *expect) return; // 取消监听或已设置新的监听
-        // 不在处理已经关闭的等待
-        auto i = c_socks_.find(fd);
-        if(i == c_socks_.end()) return;
-        // 本次 SOCKET 动作执行
+    static const char* action_str[] = { "UNKNOWN", "CURL_POLL_IN", "CURL_POLL_OUT" , "CURL_POLL_INOUT", "CURL_POLL_REMOVE" };
+
+    void client::c_socket_ready_cb(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action) {
+        client* self = reinterpret_cast<client*>(poll->data);
+        std::cout << "client_on_ready: " << poll << " action: " << action_str[action] << "\n";
         if(error) action = CURL_CSELECT_ERR;
-        curl_multi_socket_action(c_multi_, fd, action, &c_still_);
-        check_done();
-        if(c_still_ <= 0) c_timer_.cancel();
-        if(error || action != *expect) return; // 发生错误或监听被变更
-        action = *expect;
-        // 未变更时继续监听
-        if(action == CURL_POLL_IN || action == CURL_POLL_INOUT) 
-            i->second->async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client::c_socket_ready_cb, this, std::placeholders::_1, fd, action, expect));
-        if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT) 
-            i->second->async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client::c_socket_ready_cb, this, std::placeholders::_1, fd, action, expect));
+        curl_multi_socket_action(self->c_multi_, fd, action, &self->c_still_);
+        self->check_done();
+        if(self->c_still_ <= 0) self->c_timer_.cancel();
     }
-
+    
     int client::c_socket_cb(CURL* e, curl_socket_t fd, int action, void* data, void* sock_data) {
+        std::cout << "POLL: " << fd << " action: " << action_str[action] << "\n"; 
         client* self = reinterpret_cast<client*>(data);
+        client_poll* poll = reinterpret_cast<client_poll*>(sock_data);
+
         if(action == CURL_POLL_REMOVE) {
-            delete (int*)sock_data;
             curl_multi_assign(self->c_multi_, fd, nullptr);
-            return 0;
-        }
-        int* expect = reinterpret_cast<int*>(sock_data);
-        boost::asio::ip::tcp::socket* sock = self->c_socks_[fd];
-        if(!sock_data) {
-            expect = new int { action };
-            curl_multi_assign(self->c_multi_, fd, expect);
+        }else if(poll == nullptr) {
+            poll = client_poll::create_poll(gcontroller->context_x, fd, c_socket_ready_cb, self);
+            curl_multi_assign(self->c_multi_, fd, poll);
         }else{
-            *expect = action;
+            // 
         }
-        // 根据新设置重新设置监听
-        if(action == CURL_POLL_IN || action == CURL_POLL_INOUT)
-            sock->async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client::c_socket_ready_cb, self, std::placeholders::_1, fd, action, expect));
-        if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
-            sock->async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client::c_socket_ready_cb, self, std::placeholders::_1, fd, action, expect));
+        poll->async_wait(action);
         return 0;
     }
 
@@ -96,7 +82,8 @@ namespace flame::http {
         client* self = reinterpret_cast<client*>(data);
 
         self->c_timer_.cancel();
-        if(timeout_ms >= 0) {
+        if(timeout_ms == 0) timeout_ms = 1;
+        if(timeout_ms > 0) {
             self->c_timer_.expires_after(std::chrono::milliseconds(timeout_ms));
             self->c_timer_.async_wait([self] (const boost::system::error_code& err) {
                 if(err) return;
@@ -109,19 +96,23 @@ namespace flame::http {
 
     client::client()
     : c_timer_(gcontroller->context_x) {
+        std::cout << "+client\n";
         c_multi_ = curl_multi_init();
+
         curl_multi_setopt(c_multi_, CURLMOPT_SOCKETDATA, this);
         curl_multi_setopt(c_multi_, CURLMOPT_SOCKETFUNCTION, client::c_socket_cb);
         curl_multi_setopt(c_multi_, CURLMOPT_TIMERDATA, this);
         curl_multi_setopt(c_multi_, CURLMOPT_TIMERFUNCTION, client::c_timer_cb);
 
-        curl_multi_setopt(c_multi_, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX | CURLPIPE_HTTP1);
-        curl_multi_setopt(c_multi_, CURLMOPT_MAX_PIPELINE_LENGTH, 4);
-        curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, 16);
-        curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, 4);
+        curl_multi_setopt(c_multi_, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX/* | CURLPIPE_HTTP1*/);
+        // curl_multi_setopt(c_multi_, CURLMOPT_MAX_PIPELINE_LENGTH, 4L);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS, 64L);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, 32L);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, 16L);
     }
 
     client::~client() {
+        std::cout << "~client\n";
         curl_multi_cleanup(c_multi_);
     }
 
@@ -129,52 +120,39 @@ namespace flame::http {
         if(params.size() > 0) {
             php::array opts = params[0];
             if(opts.exists("connection_per_host")) {
-                int connection_per_host = opts.get("connection_per_host");
-                if(connection_per_host < 1 || connection_per_host > 512) {
-                    curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, connection_per_host);
-                    int keepalive = std::ceil(connection_per_host / 4.0);
-                    curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, keepalive);
+                long c = opts.get("connection_per_host");
+                if(c < 1 || c > 512) {
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS, c * 4);
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, c * 2);
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, c);
                 }
             }
         }
         return nullptr;
     }
 
-    curl_socket_t client::c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* address) {
-        boost::system::error_code error;
-        boost::asio::ip::tcp::socket* sock = new boost::asio::ip::tcp::socket(gcontroller->context_x);
-        if(purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET) {
-            sock->open(boost::asio::ip::tcp::v4(), error);
-        }else if(purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET6) {
-            sock->open(boost::asio::ip::tcp::v6(), error);
-        }
-        if(error) {
-            std::cerr << "[" << flame::time::iso() << "] (ERROR) failed to open socket: " << error.message() << "\n";
-            return CURL_SOCKET_BAD;
-        }
-        curl_socket_t fd = sock->native_handle();
-        client* self = reinterpret_cast<client*>(data);
-        self->c_socks_.insert({fd, sock});
-        return fd;
-    }
+    // curl_socket_t client::c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* addr) {
+    //     client* self = reinterpret_cast<client*>(data);
+    //     curl_socket_t fd = ::socket(addr->family, addr->socktype, addr->protocol);
+    //     std::cout << time::iso() << " > c_socket_open_cb: " << fd << " purpose: " << purpose << " addr->family: " << addr->family << "\n";
+    //     return fd;
+    // }
 
-    int client::c_socket_close_cb(void* data, curl_socket_t fd) {
-        client* self = reinterpret_cast<client*>(data);
-        auto i = self->c_socks_.find(fd);
-        if(i != self->c_socks_.end()) {
-            delete i->second;
-            self->c_socks_.erase(i);
-        }
-        return 0;
-    }
+    // int client::c_socket_close_cb(void* data, curl_socket_t fd) {
+    //     client* self = reinterpret_cast<client*>(data);
+    //     std::cout << time::iso() << " > c_socket_close_cb: " << fd << "\n";
+    //     return ::close(fd);
+    //     return 0;
+    // }
 
     php::value client::exec_ex(const php::object& req) {
         auto req_ = static_cast<client_request*>(php::native(req));
         if(req_->c_easy_ == nullptr) req_->c_easy_ = curl_easy_init();
-        curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETFUNCTION, c_socket_open_cb);
-        curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETDATA, this);
-        curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETFUNCTION, c_socket_close_cb);
-        curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETDATA, this);
+        // curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETFUNCTION, c_socket_open_cb);
+        // curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETDATA, this);
+        // curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETFUNCTION, c_socket_close_cb);
+        // curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETDATA, this);
+        curl_easy_setopt(req_->c_easy_, CURLOPT_PIPEWAIT, 1);
         req_->build_ex();
 
         php::object res(php::class_entry<client_response>::entry());
diff --git a/src/http/client.h b/src/http/client.h
index 9fa6132..6c999c3 100644
--- a/src/http/client.h
+++ b/src/http/client.h
@@ -2,6 +2,7 @@
 #include "../vendor.h"
 
 namespace flame::http {
+    class client_poll;
     class client: public php::class_base {
     public:
         static void declare(php::extension_entry& ext);
@@ -13,18 +14,21 @@ namespace flame::http {
         php::value post(php::parameters& params);
         php::value put(php::parameters& params);
         php::value delete_(php::parameters& params);
+
+        std::map<curl_socket_t, boost::asio::ip::tcp::socket*> c_socks_;
+
     private:
         CURLM *c_multi_ = nullptr;
         int    c_still_ = 0;
-        std::map<curl_socket_t, boost::asio::ip::tcp::socket*> c_socks_;
         boost::asio::steady_timer c_timer_;
+        ;
 
         php::value exec_ex(const php::object& req);
         static int c_socket_cb(CURL* e, curl_socket_t fd, int action, void* cbp, void* data);
         static int c_timer_cb(CURLM *m, long timeout_ms, void* data);
         static curl_socket_t c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* address);
         static int c_socket_close_cb(void* data, curl_socket_t fd);
-        void c_socket_ready_cb(const boost::system::error_code& error, curl_socket_t fd, int action, int* expect);
+        static void c_socket_ready_cb(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action);
         void check_done();
     };
 }
diff --git a/src/http/client_poll.cpp b/src/http/client_poll.cpp
new file mode 100644
index 0000000..2efb7c7
--- /dev/null
+++ b/src/http/client_poll.cpp
@@ -0,0 +1,89 @@
+#include "client_poll.h"
+#include "http.h"
+#include "client.h"
+
+namespace flame::http {
+	client_poll* client_poll::create_poll(boost::asio::io_context &io, curl_socket_t fd, client_poll::poll_callback_t cb, void* data) {
+		int       type;
+		socklen_t size;
+		struct sockaddr addr;
+		int x = ::getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &size);
+		assert(x == 0);
+		// std::cout << "socket_type: " << "x=" << x << " type=" << type << "(" << size << " SOCK_STREAM="<<SOCK_STREAM << " SOCK_DGRAM=" << SOCK_DGRAM << ")\n";
+		::getsockname(fd, &addr, &size);
+		client_poll* poll;
+		if(type == SOCK_STREAM) {
+			poll = new client_poll_tcp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::tcp::v6() : boost::asio::ip::tcp::v4(), ::dup(fd));
+		}else if(type == SOCK_DGRAM) {
+			poll = new client_poll_udp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
+		}else{
+			return nullptr;
+		}
+		poll->data = data;
+		poll->fd_ = fd;
+		poll->cb_ = cb;
+		return poll;
+	}
+
+	client_poll::client_poll()
+	: action_(CURL_POLL_NONE) {
+		// std::cout << "+client_poll: " << this << " action: " << action_ << "\n";
+	}
+
+	client_poll::~client_poll() {
+		// std::cout << "~client_poll: " << this << "\n";
+	}
+
+	void client_poll::on_ready(const boost::system::error_code& error, int action) {
+		if(error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
+		// std::cout << "on_ready: " << this << "\n";
+		cb_(error, this, fd_, action);
+		if(action_ == CURL_POLL_REMOVE) delete this;
+		else if(action_ == action) async_wait();
+	}
+
+	void client_poll::async_wait(int action) {
+		if(action_ != action) {
+			action_ = action;
+			async_wait();
+		}
+	}
+
+	client_poll_tcp::client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd)
+	: sock_(io, proto, fd) {
+	}
+
+	client_poll_tcp::~client_poll_tcp() {
+		// sock_.release();
+	}
+
+	void client_poll_tcp::async_wait() {
+		std::cout << "async_wait: " << this << " action: " << action_ << "\n";
+		sock_.cancel();
+		// 重新监听
+		if(action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+			sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
+		if(action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+			sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
+	}
+	
+	client_poll_udp::client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd)
+	: client_poll(), sock_(io, proto, fd) {
+		// sock_.assign(proto, fd);
+	}
+
+	client_poll_udp::~client_poll_udp() {
+		// sock_.release();
+	}
+
+	void client_poll_udp::async_wait() {
+		std::cout << "async_wait: " << this << " action: " << action_ << "\n";
+		sock_.cancel();
+		// 重新监听
+		if(action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+			sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
+		if(action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+			sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
+	}
+
+}
\ No newline at end of file
diff --git a/src/http/client_poll.h b/src/http/client_poll.h
new file mode 100644
index 0000000..9454918
--- /dev/null
+++ b/src/http/client_poll.h
@@ -0,0 +1,42 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::http {
+	class client_poll {
+	public:
+		client_poll();
+		typedef void (*poll_callback_t)(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action);
+		virtual ~client_poll();
+		static client_poll* create_poll(boost::asio::io_context& io, curl_socket_t fd, poll_callback_t cb, void* data = nullptr);
+		virtual void async_wait() = 0;
+		void async_wait(int action);
+		void* data;
+	protected:
+		curl_socket_t   fd_;
+		int         action_;
+		poll_callback_t cb_;
+		void on_ready(const boost::system::error_code& error, int action);
+	};
+
+	class client_poll_tcp:public client_poll {
+	public:
+		// client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
+		client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
+		// client_poll_tcp(curl_socket_t fd);
+		~client_poll_tcp();
+		void async_wait() override;
+	private:
+		boost::asio::ip::tcp::socket sock_;
+		// boost::asio::ip::tcp::socket& sock_;
+		// boost::asio::posix::stream_descriptor sock_;
+	};
+
+	class client_poll_udp:public client_poll {
+	public:
+		client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd);
+		~client_poll_udp();
+		void async_wait() override;
+	private:
+		boost::asio::ip::udp::socket sock_;
+	};
+}
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index e558885..73e1480 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -113,6 +113,8 @@ namespace http {
 	}
 
 	void client_request::build_ex() {
+		long timeout = static_cast<long>(get("timeout"));
+		curl_easy_setopt(c_easy_, CURLOPT_TIMEOUT_MS, timeout);
 		// 目标请求地址
 		// ---------------------------------------------------------------------------
         php::string u = get("url", true);
@@ -121,21 +123,26 @@ namespace http {
 		}
 		curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
 		php::string m = get("method", true);
-		curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
+		// curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
 		// 头
 		// ---------------------------------------------------------------------------
 		if(c_head_ != nullptr) curl_slist_free_all(c_head_);
 		std::string ctype;
+		long keepalive = 1;
 		php::array header = get("header");
 		for(auto i=header.begin(); i!=header.end(); ++i) {
 			std::string key = i->first;
 			std::string val = i->second;
 			if(strncasecmp(key.c_str(), "content-type", 12) == 0) {
 				ctype = val;
+			}else if(strncasecmp(key.c_str(), "connection", 10) == 0 && strncasecmp(val.c_str(), "close", 5) == 0) {
+				keepalive = 0;
 			}
 			c_head_ = curl_slist_append(c_head_, (boost::format("%s: %s") % key % val).str().c_str());
 		}
+		c_head_ = curl_slist_append(c_head_, "Expect: ");
 		curl_easy_setopt(c_easy_, CURLOPT_HTTPHEADER, c_head_);
+		curl_easy_setopt(c_easy_, CURLOPT_TCP_KEEPALIVE, keepalive);
 		// COOKIE
 		// ---------------------------------------------------------------------------
 		php::array cookie = get("cookie");
@@ -151,7 +158,7 @@ namespace http {
 			cookies.push_back(' ');
 		}
 		php::string cookie_str = std::move(cookies);
-		curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
+		if(cookie_str.size() > 0) curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
 		// 体
 		// ---------------------------------------------------------------------------
 		php::string body = get("body");
@@ -164,6 +171,8 @@ namespace http {
 			body = ctype_encode(ctype, body);
 			// 注意: CURLOPT_POSTFIELDS 仅"引用" body 数据
 			set("body", body);
+			// curl_easy_setopt(c_easy_, CURLOPT_POST, 1);
+			curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDSIZE, body.size());
 			curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDS, body.c_str());
 		}
 	}
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index d6ff8cb..201b68b 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -46,10 +46,11 @@ namespace http {
 
 	size_t client_response::c_write_cb(char *ptr, size_t size, size_t nmemb, void *data) {
 		client_response* self = static_cast<client_response*>(data);
-		// assert(size == 1);
+		assert(size == 1);
 		self->c_body_.append(ptr, nmemb);
-		return size;
+		return nmemb;
 	}
+	
 	size_t client_response::c_header_cb(char *buffer, size_t size, size_t nitems, void *data) {
 		client_response* self = static_cast<client_response*>(data);
 		size = size * nitems;		
@@ -58,11 +59,11 @@ namespace http {
 		auto psep = sv.find_first_of(':', 1);
 		if(psep != sv.npos) {
 			auto pval = sv.find_first_not_of(' ', psep + 1);
-			php::lowercase_inplace(buffer, psep);
+			php::string key = php::lowercase(buffer, psep);
 			if(pval == sv.npos) {
-				self->c_head_.set( php::string(buffer, psep), php::string(0) );
+				self->c_head_.set( key, php::string(0) );
 			}else{
-				self->c_head_.set( php::string(buffer, psep), php::string( buffer + pval, size - pval - 2)); // trailing \r\n
+				self->c_head_.set( key, php::string( buffer + pval, size - pval - 2)); // trailing \r\n
 			}
 		}
 		return size;
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index c0a1c50..5e06bba 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -31,14 +31,12 @@ namespace flame::tcp
     }
     typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
 
-    php::value server::__construct(php::parameters &params)
-    {
+    php::value server::__construct(php::parameters &params) {
+        closed_ = false;
         std::string str_addr = params[0];
         auto pair = udp::addr2pair(str_addr);
         if(pair.first.empty() || pair.second.empty())
-        {
             throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
-        }
         boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
         addr_.address(addr);
         addr_.port(std::atoi(pair.second.c_str()));
@@ -52,36 +50,24 @@ namespace flame::tcp
 
         boost::system::error_code err;
         acceptor_.bind(addr_, err);
-        if(err)
-        {
-            throw php::exception(zend_ce_error,
+        if(err) throw php::exception(zend_ce_error,
                 (boost::format("failed to bind tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
-        }
+
         acceptor_.listen(boost::asio::socket_base::max_listen_connections, err);
-        if (err)
-        {
-            throw php::exception(zend_ce_error,
+        if (err) throw php::exception(zend_ce_error,
                 (boost::format("failed to listen tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
-        }
         return nullptr;
     }
-    php::value server::run(php::parameters &params)
-    {
+    php::value server::run(php::parameters &params) {
         cb_ = params[0];
         coroutine_handler ch {coroutine::current};
         boost::system::error_code err;
         while(!closed_)
         {
             acceptor_.async_accept(socket_, ch[err]);
-            if(err == boost::asio::error::operation_aborted)
-            {
-                break;
-            }
-            else if(err)
-            {
-                throw php::exception(zend_ce_error,
+            if(err == boost::asio::error::operation_aborted) break;
+            else if(err) throw php::exception(zend_ce_error,
                                      (boost::format("failed to accept tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
-            }
             else{
                 php::object obj(php::class_entry<socket>::entry());
                 socket* ptr = static_cast<socket*>(php::native(obj));
@@ -92,12 +78,9 @@ namespace flame::tcp
                     (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
 
                 coroutine::start(php::callable([obj, cb = cb_] (php::parameters& params) -> php::value {
-                    try
-                    {
+                    try {
                         cb.call({obj});
-                    }
-                    catch(const php::exception& ex)
-                    {
+                    } catch(const php::exception& ex) {
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) "<< obj.call("__toString") << "\n";
                         // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;

From c55bf842591dc5f04c773657a87be98307d13d17 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 10 Apr 2019 13:35:02 +0800
Subject: [PATCH 089/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=20HTTP=20=E5=AE=A2?=
 =?UTF-8?q?=E6=88=B7=E7=AB=AF=E8=B6=85=E6=97=B6=E5=8F=8A=E6=8A=A5=E9=94=99?=
 =?UTF-8?q?=E6=B5=81=E7=A8=8B;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/client.cpp          | 10 +---------
 src/http/client_poll.cpp     |  8 --------
 src/http/client_request.cpp  | 12 ++++++------
 src/http/client_response.cpp |  5 ++++-
 4 files changed, 11 insertions(+), 24 deletions(-)

diff --git a/src/http/client.cpp b/src/http/client.cpp
index c373f44..60d2624 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -54,7 +54,6 @@ namespace flame::http {
 
     void client::c_socket_ready_cb(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action) {
         client* self = reinterpret_cast<client*>(poll->data);
-        std::cout << "client_on_ready: " << poll << " action: " << action_str[action] << "\n";
         if(error) action = CURL_CSELECT_ERR;
         curl_multi_socket_action(self->c_multi_, fd, action, &self->c_still_);
         self->check_done();
@@ -62,7 +61,6 @@ namespace flame::http {
     }
     
     int client::c_socket_cb(CURL* e, curl_socket_t fd, int action, void* data, void* sock_data) {
-        std::cout << "POLL: " << fd << " action: " << action_str[action] << "\n"; 
         client* self = reinterpret_cast<client*>(data);
         client_poll* poll = reinterpret_cast<client_poll*>(sock_data);
 
@@ -96,14 +94,13 @@ namespace flame::http {
 
     client::client()
     : c_timer_(gcontroller->context_x) {
-        std::cout << "+client\n";
         c_multi_ = curl_multi_init();
 
         curl_multi_setopt(c_multi_, CURLMOPT_SOCKETDATA, this);
         curl_multi_setopt(c_multi_, CURLMOPT_SOCKETFUNCTION, client::c_socket_cb);
         curl_multi_setopt(c_multi_, CURLMOPT_TIMERDATA, this);
         curl_multi_setopt(c_multi_, CURLMOPT_TIMERFUNCTION, client::c_timer_cb);
-
+        // 7.62.x + HTTP1 PIPELINE 功能已无效
         curl_multi_setopt(c_multi_, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX/* | CURLPIPE_HTTP1*/);
         // curl_multi_setopt(c_multi_, CURLMOPT_MAX_PIPELINE_LENGTH, 4L);
         curl_multi_setopt(c_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS, 64L);
@@ -112,7 +109,6 @@ namespace flame::http {
     }
 
     client::~client() {
-        std::cout << "~client\n";
         curl_multi_cleanup(c_multi_);
     }
 
@@ -134,15 +130,12 @@ namespace flame::http {
     // curl_socket_t client::c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* addr) {
     //     client* self = reinterpret_cast<client*>(data);
     //     curl_socket_t fd = ::socket(addr->family, addr->socktype, addr->protocol);
-    //     std::cout << time::iso() << " > c_socket_open_cb: " << fd << " purpose: " << purpose << " addr->family: " << addr->family << "\n";
     //     return fd;
     // }
 
     // int client::c_socket_close_cb(void* data, curl_socket_t fd) {
     //     client* self = reinterpret_cast<client*>(data);
-    //     std::cout << time::iso() << " > c_socket_close_cb: " << fd << "\n";
     //     return ::close(fd);
-    //     return 0;
     // }
 
     php::value client::exec_ex(const php::object& req) {
@@ -169,7 +162,6 @@ namespace flame::http {
 
         curl_multi_add_handle(c_multi_, res_->c_easy_);
         res_->c_coro_.suspend(); // <----- check_done
-
         res_->build_ex();
         curl_multi_remove_handle(c_multi_, res_->c_easy_);
         return std::move(res);
diff --git a/src/http/client_poll.cpp b/src/http/client_poll.cpp
index 2efb7c7..1ca1ab8 100644
--- a/src/http/client_poll.cpp
+++ b/src/http/client_poll.cpp
@@ -9,7 +9,6 @@ namespace flame::http {
 		struct sockaddr addr;
 		int x = ::getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &size);
 		assert(x == 0);
-		// std::cout << "socket_type: " << "x=" << x << " type=" << type << "(" << size << " SOCK_STREAM="<<SOCK_STREAM << " SOCK_DGRAM=" << SOCK_DGRAM << ")\n";
 		::getsockname(fd, &addr, &size);
 		client_poll* poll;
 		if(type == SOCK_STREAM) {
@@ -27,16 +26,13 @@ namespace flame::http {
 
 	client_poll::client_poll()
 	: action_(CURL_POLL_NONE) {
-		// std::cout << "+client_poll: " << this << " action: " << action_ << "\n";
 	}
 
 	client_poll::~client_poll() {
-		// std::cout << "~client_poll: " << this << "\n";
 	}
 
 	void client_poll::on_ready(const boost::system::error_code& error, int action) {
 		if(error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
-		// std::cout << "on_ready: " << this << "\n";
 		cb_(error, this, fd_, action);
 		if(action_ == CURL_POLL_REMOVE) delete this;
 		else if(action_ == action) async_wait();
@@ -58,7 +54,6 @@ namespace flame::http {
 	}
 
 	void client_poll_tcp::async_wait() {
-		std::cout << "async_wait: " << this << " action: " << action_ << "\n";
 		sock_.cancel();
 		// 重新监听
 		if(action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
@@ -69,15 +64,12 @@ namespace flame::http {
 	
 	client_poll_udp::client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd)
 	: client_poll(), sock_(io, proto, fd) {
-		// sock_.assign(proto, fd);
 	}
 
 	client_poll_udp::~client_poll_udp() {
-		// sock_.release();
 	}
 
 	void client_poll_udp::async_wait() {
-		std::cout << "async_wait: " << this << " action: " << action_ << "\n";
 		sock_.cancel();
 		// 重新监听
 		if(action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 73e1480..6cb88a3 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -73,9 +73,9 @@ namespace http {
 
 	php::value client_request::http_version(php::parameters& params) {
 		long v = static_cast<int>(params[0]);
-		if(v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST) {
-			throw php::exception("failed to set http version: illegal version");
-		}
+		if(v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST)
+			throw php::exception(zend_ce_type_error, "Failed to set http version: illegal HTTP version", -1);
+		
 		curl_easy_setopt(c_easy_, CURLOPT_HTTP_VERSION, v);
 		return nullptr;
 	}
@@ -118,9 +118,9 @@ namespace http {
 		// 目标请求地址
 		// ---------------------------------------------------------------------------
         php::string u = get("url", true);
-		if(!u.typeof(php::TYPE::STRING)) {
-			throw php::exception(zend_ce_exception, "request 'url' must be a string");
-		}
+		if(!u.typeof(php::TYPE::STRING)) 
+			throw php::exception(zend_ce_type_error, "request 'url' must be a string");
+		
 		curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
 		php::string m = get("method", true);
 		// curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index 201b68b..350d8f2 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -24,6 +24,9 @@ namespace http {
 		return get("raw_body");
 	}
 	void client_response::build_ex() {
+		if(c_final_ != 0) throw php::exception(zend_ce_exception
+			, (boost::format("Failed to execute HTTP request: %s") % c_final_ % c_error_).str()
+			, c_final_);
 		// 响应码
 		long rv = 0;
 		curl_easy_getinfo(c_easy_, CURLINFO_HTTP_VERSION, &rv);
@@ -50,7 +53,7 @@ namespace http {
 		self->c_body_.append(ptr, nmemb);
 		return nmemb;
 	}
-	
+
 	size_t client_response::c_header_cb(char *buffer, size_t size, size_t nitems, void *data) {
 		client_response* self = static_cast<client_response*>(data);
 		size = size * nitems;		

From c89a730c52c48f8e343c394b40cd7c5f6c647938 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 10 Apr 2019 14:08:59 +0800
Subject: [PATCH 090/146] =?UTF-8?q?=E8=A7=84=E6=95=B4=E9=94=99=E8=AF=AF?=
 =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=8F=8A=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F?=
 =?UTF-8?q?;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/http.php                     |   4 +-
 src/controller_master.cpp        | 104 +++----
 src/core.cpp                     |  18 +-
 src/coroutine_queue.h            |  92 ++-----
 src/encoding/encoding.cpp        |   6 +-
 src/flame.cpp                    |  94 +++----
 src/http/_handler.cpp            | 207 ++++++--------
 src/http/client_request.cpp      |   8 +-
 src/http/http.cpp                |   7 +-
 src/http/server.cpp              | 107 ++++----
 src/http/server_response.cpp     |  55 ++--
 src/kafka/_consumer.cpp          | 124 ++++-----
 src/kafka/_producer.cpp          |  62 ++---
 src/kafka/consumer.cpp           |  34 +--
 src/kafka/kafka.cpp              |  58 ++--
 src/log/log.cpp                  |  51 ++--
 src/mongodb/_connection_base.cpp |  34 +--
 src/mongodb/_connection_lock.cpp |  19 +-
 src/mongodb/collection.cpp       | 199 +++++---------
 src/mysql/_connection_base.cpp   |  71 ++---
 src/mysql/_connection_lock.cpp   |  44 ++-
 src/mysql/_connection_pool.cpp   | 128 ++++-----
 src/mysql/_connection_pool.h     |   2 +-
 src/mysql/mysql.cpp              | 452 +++++++++++--------------------
 src/os/os.cpp                    | 100 +++----
 src/rabbitmq/_client.cpp         |  41 +--
 src/rabbitmq/consumer.cpp        |  63 ++---
 src/rabbitmq/rabbitmq.cpp        |  87 ++----
 src/redis/_connection_pool.cpp   | 106 +++-----
 src/redis/client.cpp             |   2 +-
 src/redis/tx.cpp                 |   2 +-
 src/tcp/server.cpp               |  48 ++--
 src/tcp/socket.cpp               |  58 ++--
 src/tcp/tcp.cpp                  |  31 ++-
 src/udp/server.cpp               |  55 ++--
 src/udp/socket.cpp               |  75 +++--
 src/udp/udp.cpp                  |  29 +-
 37 files changed, 1083 insertions(+), 1594 deletions(-)

diff --git a/doc/http.php b/doc/http.php
index 424d904..3562304 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -326,7 +326,7 @@ class server_request {
      *  * "application/json"
      *  * "application/x-www-form-urlencoded"
      *  * "multipart/form-data"
-     * 其他类型时与 $rawBody 相同;
+     * 其他类型时与 $raw_body 相同;
      * @var mixed
      */
     public $body;
@@ -334,7 +334,7 @@ class server_request {
      * 原始请求体
      * @var string
      */
-    public $rawBody;
+    public $raw_body;
     /**
      * 上传文件，形如：
      *  [
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 1bd81d9..6df3d96 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -50,26 +50,18 @@ namespace flame
             boost::process::std_err > *eout_[i],
             // boost::process::std_out > boost::process::null,
             boost::process::on_exit = [this, i](int exit_code, const std::error_code &error) {
-                if (error.value() == static_cast<int>(std::errc::no_child_process))
-                {
-                    return;
-                }
-                if (exit_code != 0)
-                {
+                if (error.value() == static_cast<int>(std::errc::no_child_process)) return;
+
+                if (exit_code != 0) {
                     // 日志记录
                     int sec = std::rand() % 3 + 3;
-                    std::cerr << "(FATAL) worker unexpected exit (" << exit_code << "), restart in " << sec << " ...\n";
+                    std::cerr << "(FATAL) worker unexpected exit: (" << exit_code << "), restart in " << sec << " ...\n";
 
                     auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
                     tm->async_wait([this, tm, i](const boost::system::error_code &error) {
                         if (error)
-                        {
                             std::cerr << "(FATAL) failed to restart worker: (" << error.value() << ") " << error.message() << std::endl;
-                        }
-                        else
-                        {
-                            spawn_worker(i);
-                        }
+                        else spawn_worker(i);
                     });
                 }
                 worker_[i].reset();
@@ -79,19 +71,14 @@ namespace flame
         redirect_sout(i);
         redirect_eout(i);
     }
-    void controller_master::redirect_sout(int i)
-    {
+
+    void controller_master::redirect_sout(int i) {
         boost::asio::async_read_until(*sout_[i], sbuf_[i], '\n', [this, i](const boost::system::error_code &error, std::size_t nread) {
-            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
-            {
+            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof) ;
                 // std::cerr << "(INFO) IGNORE sout\n";
-            }
             else if(error)
-            {
                 std::cerr << "(ERROR) failed to read from worker process: (" << error.value() << ") " << error.message() << std::endl;
-            }
-            else
-            {
+            else  {
                 auto y = sbuf_[i].data();
                 *offile_ << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
                 sbuf_[i].consume(nread);
@@ -101,19 +88,13 @@ namespace flame
             }
         });
     }
-    void controller_master::redirect_eout(int i)
-    {
+
+    void controller_master::redirect_eout(int i) {
         boost::asio::async_read_until(*eout_[i], ebuf_[i], '\n', [this, i](const boost::system::error_code &error, std::size_t nread) {
-            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
-            {
-                // std::cerr << "(INFO) IGNORE eout\n";
-            }
+            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof) ;
             else if (error)
-            {
                 std::cerr << "(ERROR) failed to read from worker process: (" << error.value() << ") " << error.message() << std::endl;
-            }
-            else
-            {
+            else {
                 auto y = ebuf_[i].data();
                 *offile_ << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
                 ebuf_[i].consume(nread);
@@ -123,35 +104,25 @@ namespace flame
             }
         });
     }
-    void controller_master::await_signal()
-    {
+
+    void controller_master::await_signal() {
         signal_->async_wait([this](const boost::system::error_code &error, int sig) {
             if (error) return;
             await_signal();
             boost::asio::post(gcontroller->context_x, [this, sig]() {
-                if (sig == SIGUSR2)
-                {
-                    reload_output();
-                }
-                else
-                {
-                    close_worker();
-                    // 似乎主进程与子进程 signal 处理之间有干扰
-                    // 需要后置清理
-                    // signal_.reset();
-                }
+                if (sig == SIGUSR2) reload_output();
+                else close_worker();
+                // 似乎主进程与子进程 signal 处理之间有干扰
+                // 需要后置清理
+                // signal_.reset();
             });
         });
     }
     // !!! run 实际上是在 initialize 中执行 (防止实际启用了功能)
-    void controller_master::run()
-    {
+    void controller_master::run() {
         // 主进程的启动过程:
         // 1. 新增环境变量及命令行参数, 启动子进程;
-        for(int i=0;i<gcontroller->worker_size;++i)
-        {
-            spawn_worker(i);
-        }
+        for(int i=0;i<gcontroller->worker_size;++i) spawn_worker(i);
         // 2. 监听信号进行日志重载或停止
         signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2));
         await_signal();
@@ -166,42 +137,33 @@ namespace flame
         thread_.join();
         // 5. 强制进程结束
         for(auto i=worker_.begin();i!=worker_.end();++i) {
-            if(*i) {
-                (*i)->terminate();
-            }
+            if(*i) (*i)->terminate();
         }
         // if(!group_.wait_for(std::chrono::seconds(1))) group_.terminate();
     }
-    void controller_master::reload_output()
-    {
+
+    void controller_master::reload_output() {
         if(ofpath_[0] == '/') {
             offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
             // 文件打开失败时不会抛出异常，需要额外的状态检查
-			if(!(*offile_)) {
-                std::cerr << "(ERROR) failed to create/open logger target file, fallback to standard output\n";
-            }else{
-                return;
-            }
+			if(!(*offile_))
+                std::cerr << "(ERROR) failed to create/open logger target file: fallback to standard output\n";
+            else return;
 		}
         offile_.reset(&std::clog, boost::null_deleter());
     }
-    void controller_master::close_worker()
-    {
+
+    void controller_master::close_worker() {
         // 第二次进行 “强制关闭”
-        if(close_)
-        {
+        if(close_) {
             timer_.reset();
             gcontroller->context_x.stop();
             return;
         }
         // 第一次设置超时
         close_ = true;
-        for (auto i = worker_.begin(); i != worker_.end(); ++i)
-        {
-            if (*i)
-            {
-                ::kill((*i)->id(), SIGTERM);
-            }
+        for (auto i = worker_.begin(); i != worker_.end(); ++i) {
+            if (*i) ::kill((*i)->id(), SIGTERM);
         }
         timer_.reset(new boost::asio::steady_timer(gcontroller->context_y));
         timer_->expires_after(std::chrono::seconds(gcontroller->worker_quit));
diff --git a/src/core.cpp b/src/core.cpp
index 7372de5..af9c778 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -6,16 +6,13 @@
 #include "mutex.h"
 
 namespace flame::core {
-    php::value select(php::parameters& params)
-    {
+    php::value select(php::parameters& params) {
         std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
         std::map< std::shared_ptr<coroutine_queue<php::value>>, php::object > mm;
-        for (auto i = 0; i < params.size(); ++i)
-        {
-            if(!params[i].instanceof(php::class_entry<queue>::entry()))
-            {
-                throw php::exception(zend_ce_type_error, "only flame\\queue can be selected", -1);
-            }
+        for (auto i = 0; i < params.size(); ++i) {
+            if(!params[i].instanceof(php::class_entry<queue>::entry())) throw php::exception(zend_ce_type_error
+                , "Failed to select: instanceof flame\\queue required"
+                , -1);
             php::object obj = params[i];
             queue* ptr = static_cast<queue*>(php::native(obj));
             qs.push_back( ptr->q_ );
@@ -25,17 +22,20 @@ namespace flame::core {
         std::shared_ptr<coroutine_queue<php::value>> q = select_queue(qs, ch);
         return mm[q];
     }
+
     php::value co_id(php::parameters& params) {
         return reinterpret_cast<uintptr_t>(coroutine::current.get());
     }
+
     php::value co_count(php::parameters& params) {
         return coroutine::count;
     }
+
     void declare(php::extension_entry &ext) {
         ext
             .on_request_shutdown([] (php::extension_entry& ext) -> bool {
                 if(coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0) {
-                    if(!php::error::exists()) std::cerr << "[FATAL] process exited prematurely (missing 'flame\\run();'?)\n";
+                    if(!php::error::exists()) std::cerr << "[FATAL] process exited prematurely: exception or missing 'flame\\run();' ?\n";
                     _exit(-1);
                 }
                 return true;
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
index f442b7f..604603b 100644
--- a/src/coroutine_queue.h
+++ b/src/coroutine_queue.h
@@ -1,39 +1,32 @@
 #pragma once
 #include "coroutine.h"
 
-namespace flame
-{
+namespace flame {
     template <class T>
-    class coroutine_queue: private boost::noncopyable
-    {
+    class coroutine_queue: private boost::noncopyable {
     public:
         coroutine_queue(std::size_t n = 1)
         : n_(n)
-        , closed_(false)
-        {
+        , closed_(false) {
         }
+
         // !!!! 生产者进行关闭 !!!!
-        void close()
-        {
+        void close() {
             closed_ = true;
-            while (!c_.empty())
-            {
+            while (!c_.empty()) {
                 auto ch = c_.extract(c_.begin()).value();
                 ch.resume();
             }
         }
-        bool is_closed()
-        {
+        bool is_closed() {
             return q_.empty() && closed_;
         }
-        void push(const T& t, coroutine_handler& ch)
-        {
-            if(closed_)
-            {
-                throw php::exception(zend_ce_exception, "queue already closed", -1);
-            }
-            if(q_.size() >= n_)
-            {
+
+        void push(const T& t, coroutine_handler& ch) {
+            if(closed_) throw php::exception(zend_ce_error_exception
+                , "Failed to push queue: already closed"
+                , -1);
+            if(q_.size() >= n_) {
                 p_.insert(ch);
                 ch.suspend();
             }
@@ -43,23 +36,11 @@ namespace flame
                 ch.resume();
             }
         }
-        std::optional<T> pop(coroutine_handler& ch)
-        {
-            for(;;)
-            {
-                if(!q_.empty())
-                {
-                    // 有数据消费
-                    break;
-                }
-                else if(closed_)
-                {
-                    // 无数据关闭
-                    return std::optional<T>();
-                }
-                else
-                {
-                    // 无数据等待
+        std::optional<T> pop(coroutine_handler& ch) {
+            for(;;) {
+                if(!q_.empty()) break; // 有数据消费
+                else if(closed_) return std::optional<T>();  // 无数据关闭
+                else { // 无数据等待
                     c_.insert(ch);
                     ch.suspend();
                 }
@@ -67,12 +48,10 @@ namespace flame
             T t = q_.front();
             q_.pop_front();
 
-            while (!p_.empty())
-            {
+            while (!p_.empty()) {
                 auto ch = p_.extract(p_.begin()).value();
                 ch.resume();
             }
-
             return std::optional<T>(t);
         }
 
@@ -85,34 +64,17 @@ namespace flame
     };
 
     template <class T>
-    std::shared_ptr < coroutine_queue<T> > select_queue(std::vector < std::shared_ptr<coroutine_queue<T>> > queues, coroutine_handler &ch)
-    {
-        TRY_ALL:
+    std::shared_ptr < coroutine_queue<T> > select_queue(std::vector < std::shared_ptr<coroutine_queue<T>> > queues, coroutine_handler &ch) {
+TRY_ALL:
         bool all_closed = true;
-        for(auto i=queues.begin();i!=queues.end();++i)
-        {
-            if(!(*i)->q_.empty())
-            {
-                return *i;
-            }
-            else if (!(*i)->closed_)
-            {
-                all_closed = false;
-            }
-        }
-        if(all_closed)
-        {
-            return std::shared_ptr< coroutine_queue<T> >(nullptr);
-        }
-        for(auto i=queues.begin();i!=queues.end();++i)
-        {
-            (*i)->c_.insert(ch);
+        for(auto i=queues.begin();i!=queues.end();++i) {
+            if(!(*i)->q_.empty()) return *i;
+            else if (!(*i)->closed_) all_closed = false;
         }
+        if(all_closed) return std::shared_ptr< coroutine_queue<T> >(nullptr);
+        for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(ch);
         ch.suspend();
-        for (auto i = queues.begin(); i != queues.end(); ++i)
-        {
-            (*i)->c_.erase(ch);
-        }
+        for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(ch);
         goto TRY_ALL;
     }
 
diff --git a/src/encoding/encoding.cpp b/src/encoding/encoding.cpp
index f3f29de..713c9c4 100644
--- a/src/encoding/encoding.cpp
+++ b/src/encoding/encoding.cpp
@@ -6,9 +6,9 @@ namespace flame::encoding {
         if(params[0].typeof(php::TYPE::ARRAY)) {
             auto doc = flame::mongodb::array2bson(params[0]);
             return php::string((const char*)bson_get_data(doc.get()), doc->len);
-        }else{
-            throw php::exception(zend_ce_exception, "Unable to encode type other than Array");
-        }
+        }else throw php::exception(zend_ce_type_error
+            , "Failed to encode: typeof 'array' required"
+            , -1);
     }
     static php::value bson_decode(php::parameters &params) {
         php::string data = params[0].to_string();
diff --git a/src/flame.cpp b/src/flame.cpp
index 806d669..73797db 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -19,38 +19,26 @@
 #include "encoding/encoding.h"
 #include "compress/compress.h"
 
-namespace flame
-{
-    static php::value init(php::parameters& params)
-    {
+namespace flame {
+    static php::value init(php::parameters& params) {
         php::array options(0);
-        if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY))
-        {
-            options = params[1];
-        }
+        if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) options = params[1];
         gcontroller->initialize(params[0], options);
         return nullptr;
     }
-    static php::value go(php::parameters& params)
-    {
+
+    static php::value go(php::parameters& params) {
         php::callable fn = params[0];
         coroutine::start(php::callable([fn] (php::parameters& params) -> php::value {
             php::value rv;
             try {
                 rv = fn.call();
-            }catch (const php::exception &ex) {
-                int x = 0;
+            } catch (const php::exception &ex) {
                 auto ft = gcontroller->cbmap->equal_range("exception");
-                for(auto i=ft.first; i!=ft.second; ++i) {
-                    ++x;
-                    i->second.call({ex});
-                }
+                for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
                 php::object obj = ex;
-                if(x==0) {
-                    std::cerr << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
-                }else {
-                    std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
-                }
+                std::cerr << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
+
                 boost::asio::post(gcontroller->context_x, [] () {
                     gcontroller->status |= controller::controller_status::STATUS_EXCEPTION;
                     gcontroller->context_x.stop();
@@ -61,33 +49,31 @@ namespace flame
         }));
         return nullptr;
     }
-    static php::value fake_fn(php::parameters& params)
-    {
+
+    static php::value fake_fn(php::parameters& params) {
         return nullptr;
     }
-    static php::value on(php::parameters &params)
-    {
+
+    static php::value on(php::parameters &params) {
         std::string event = params[0].to_string();
-        if (!params[1].typeof(php::TYPE::CALLABLE))
-        {
-            throw php::exception(zend_ce_type_error, "callable is required", -1);
-        }
+        if (!params[1].typeof(php::TYPE::CALLABLE)) throw php::exception(zend_ce_type_error
+            , "Failed to set callback: callable required"
+            , -1);
         gcontroller->cbmap->insert({event, params[1]});
         return nullptr;
     }
-    static php::value run(php::parameters& params)
-    {
-        if(gcontroller->status & controller::STATUS_INITIALIZED)
-        {
+
+    static php::value run(php::parameters& params) {
+        if(gcontroller->status & controller::STATUS_INITIALIZED) {
             gcontroller->default_execute_data = EG(current_execute_data);
             gcontroller->run();
-        }else{
-            throw php::exception(zend_ce_type_error, "failed to run flame: not yet initialized (forget to call 'flame\\init()' ?)");
-        }
+        }else throw php::exception(zend_ce_error_exception
+            , "Failed to run flame: exception or missing 'flame\\init()' ?"
+            , -1);
         return nullptr;
     }
-    static php::value quit(php::parameters& params)
-    {
+
+    static php::value quit(php::parameters& params) {
         gcontroller->context_x.stop();
         gcontroller->context_y.stop();
         return nullptr;
@@ -97,15 +83,12 @@ namespace flame
 #define VERSION_MACRO(major, minor, patch) VERSION_JOIN(major, minor, patch)
 #define VERSION_JOIN(major, minor, patch) #major"."#minor"."#patch
 
-extern "C"
-{
-    ZEND_DLEXPORT zend_module_entry *get_module()
-    {
+extern "C" {
+    ZEND_DLEXPORT zend_module_entry *get_module()  {
         static php::extension_entry ext(EXTENSION_NAME, EXTENSION_VERSION);
         std::string sapi = php::constant("PHP_SAPI");
-        if (sapi != "cli")
-        {
-            std::cerr << "Flame can only be using in SAPI='cli' mode\n";
+        if (sapi != "cli") {
+            std::cerr << "[WARNING] FLAME disabled: SAPI='cli' mode only\n";
             return ext;
         }
 
@@ -126,25 +109,22 @@ extern "C"
 
         flame::gcontroller.reset(new flame::controller());
         ext
-            .function<flame::init>("flame\\init",
-            {
+            .function<flame::init>("flame\\init", {
                 {"process_name", php::TYPE::STRING},
                 {"options", php::TYPE::ARRAY, false, true},
             })
             .function<flame::run>("flame\\run");
-        if(flame::gcontroller->type == flame::controller::process_type::WORKER)
-        {
+        if(flame::gcontroller->type == flame::controller::process_type::WORKER) {
             ext
-            .function<flame::go>("flame\\go",
-            {
+            .function<flame::go>("flame\\go", {
                 {"coroutine", php::TYPE::CALLABLE},
             })
-            .function<flame::on>("flame\\on",
-            {
+            .function<flame::on>("flame\\on", {
                 {"event", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
             .function<flame::quit>("flame\\quit");
+
             flame::core::declare(ext);
             flame::log::declare(ext);
             flame::os::declare(ext);
@@ -160,12 +140,10 @@ extern "C"
             flame::hash::declare(ext);
             flame::encoding::declare(ext);
             flame::compress::declare(ext);
-        }
-        else
-        {
+        } else {
             ext
-            .function<flame::fake_fn>("flame\\go")
-            .function<flame::fake_fn>("flame\\on");
+                .function<flame::fake_fn>("flame\\go")
+                .function<flame::fake_fn>("flame\\on");
         }
         return ext;
     }
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 7bf574e..45bf15f 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -7,26 +7,22 @@
 #include "server_request.h"
 #include "server_response.h"
 
-namespace flame::http
-{
+namespace flame::http {
+
     _handler::_handler(server *svr, tcp::socket &&sock)
     : svr_ptr(svr)
     , svr_obj(svr)
-    , socket_(std::move(sock))
-    {
-
+    , socket_(std::move(sock)) {
     }
-    _handler::~_handler()
-    {
 
+    _handler::~_handler() {
     }
-    void _handler::start()
-    {
+
+    void _handler::start() {
         // 第一次请求或允许复用
         if ((gcontroller->status & controller::controller_status::STATUS_SHUTDOWN)
                 || svr_ptr->closed_
-                || (req_ && !req_->keep_alive()) || (res_ && !res_->keep_alive()))
-        {
+                || (req_ && !req_->keep_alive()) || (res_ && !res_->keep_alive())) {
             req_.reset();
             res_.reset();
             return;
@@ -37,24 +33,24 @@ namespace flame::http
         }
     }
 
-    void _handler::read_request()
-    {
+    void _handler::read_request() {
         // 读取请求
         req_.reset(new boost::beast::http::request_parser<value_body<true>>());
         req_->body_limit(body_max_size);
-        boost::beast::http::async_read(socket_, buffer_, *req_, [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n)
+        boost::beast::http::async_read(socket_, buffer_, *req_
+            , [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n)
         {
-            if(error == boost::beast::http::error::end_of_stream || error == boost::asio::error::operation_aborted || error == boost::asio::error::connection_reset)
-            {
+            if(error == boost::beast::http::error::end_of_stream
+                || error == boost::asio::error::operation_aborted
+                || error == boost::asio::error::connection_reset) {
+
                 res_.reset();
                 req_.reset();
                 return;
-            }
-            else if (error)
-            {
+            } else if (error) {
                 res_.reset();
                 req_.reset();
-                std::cerr << "[" << time::iso() << "] (WARNING) failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
+                // std::cerr << "[" << time::iso() << "] (WARNING) Failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
                 return;
             }
             res_.reset(new boost::beast::http::message<false, value_body<false>>());
@@ -66,8 +62,7 @@ namespace flame::http
         });
     }
 
-    void _handler::call_handler()
-    {
+    void _handler::call_handler() {
         php::object req = php::object(php::class_entry<server_request>::entry());
         php::object res = php::object(php::class_entry<server_response>::entry());
         server_request *req_ptr = static_cast<server_request *>(php::native(req));
@@ -76,8 +71,7 @@ namespace flame::http
         req_ptr->build_ex(req_->get()); // 将 C++ 请求对象展开到 PHP 中
         res_ptr->handler_ = shared_from_this();
 
-        coroutine::start(php::callable([self = shared_from_this(), this, req, req_ptr, res, res_ptr] (php::parameters& params) -> php::value
-        {
+        coroutine::start(php::callable([self = shared_from_this(), this, req, req_ptr, res, res_ptr] (php::parameters& params) -> php::value {
             coroutine_handler ch{coroutine::current};
             std::string route = req.get("method");
             route.push_back(':');
@@ -87,32 +81,15 @@ namespace flame::http
                 ih = svr_ptr->cb_.find(route),
                 ia = svr_ptr->cb_.find("after");
 
-            try
-            {
+            try {
                 php::value rv = true;
-                if(ib != svr_ptr->cb_.end())
-                {
-                    rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
-                }
-                if(rv.typeof(php::TYPE::NO))
-                {
-                    goto HANDLE_BREAK;
-                }
-                if(ih != svr_ptr->cb_.end())
-                {
-                    rv = ih->second.call({req, res});
-                }
-                if(rv.typeof(php::TYPE::NO))
-                {
-                    goto HANDLE_BREAK;
-                }
-                if (ia != svr_ptr->cb_.end())
-                {
-                    rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
-                }
+                if(ib != svr_ptr->cb_.end()) rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
+                if(rv.typeof(php::TYPE::NO)) goto HANDLE_BREAK;
+                if(ih != svr_ptr->cb_.end()) rv = ih->second.call({req, res});
+                if(rv.typeof(php::TYPE::NO)) 
+                if (ia != svr_ptr->cb_.end()) rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
 HANDLE_BREAK:
-                if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/)
-                {
+                if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/) {
                     // if(!(res_ptr->status_ & server_response::STATUS_HEAD_SENT)) {
                     //     res_ptr->set("status", 404);
                     //     res_ptr->set("body", nullptr);
@@ -125,9 +102,7 @@ namespace flame::http
                     // Content 方式, 待结束
                     php::string body = res.get("body");
                     if (!body.empty())
-                    {
                         body = ctype_encode(res_->find(boost::beast::http::field::content_type)->value(), body);
-                    }
                     res_->body() = body;
                     res_->prepare_payload();
 
@@ -136,37 +111,31 @@ namespace flame::http
                     if(error) {
                         res_.reset();
                         req_.reset();
-                        throw php::exception(zend_ce_exception,
-                                            (boost::format("failed to write_head: (%1%) %2%") % error.value() % error.message()).str(), error.value());
-                    }else{
-                        start();
-                    }
+                        throw php::exception(zend_ce_exception
+                            , (boost::format("Failed to write response: %s") % error.message()).str()
+                            , error.value());
+                    } else start();
                 }/*else
                 {
                     // Chunked 未结束 -> server_response::__destruct() -> finish()
                 }*/
-            }
-            catch(const php::exception& ex)
-            {
+            } catch(const php::exception& ex) {
                 res_.reset();
                 req_.reset();
                 php::object obj = ex;
-                std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
-                // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
             }
             return nullptr;
         }));
     }
 
-    void _handler::write_head(server_response *res_ptr, coroutine_handler &ch)
-    {
+    void _handler::write_head(server_response *res_ptr, coroutine_handler &ch) {
         if(!res_) return;
         res_ptr->build_ex(*res_);
         res_->chunked(true);
         res_->set(boost::beast::http::field::transfer_encoding, "chunked");
         
-        if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_)
-        {
+        if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_) {
             res_->keep_alive(false);
             res_->set(boost::beast::http::field::connection, "close");
         }
@@ -177,111 +146,97 @@ namespace flame::http
         if(error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception,
-                (boost::format("failed to write_head: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to write header: %s") % error.message()).str()
+                , error.value());
         }
     }
-    void _handler::write_body(server_response *res_ptr, php::string data, coroutine_handler &ch)
-    {
+
+    void _handler::write_body(server_response *res_ptr, php::string data, coroutine_handler &ch) {
         if(!res_) return;
-        if(!res_->chunked()) {
-            throw php::exception(zend_ce_error, "failed to write_body: Transfer-Encoding: chunked required");
-        }
+        if(!res_->chunked())
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write_body: 'chunked' encoding required"
+                , -1);
+
         boost::system::error_code error;
         boost::asio::async_write(socket_,
             boost::beast::http::make_chunk(boost::asio::const_buffer(data.c_str(), data.size())), ch[error]);
-        if (error)
-        {
+        if (error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception,
-                (boost::format("failed to write_body: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to write body: %s") % error.message()).str()
+                , error.value());
         }
     }
-    void _handler::write_end(server_response* res_ptr, coroutine_handler &ch)
-    {
+
+    void _handler::write_end(server_response* res_ptr, coroutine_handler &ch) {
         if(!res_) return;
-        if (!res_->chunked())
-        {
-            throw php::exception(zend_ce_error, "failed to write_body: Transfer-Encoding: chunked required");
-        }
+        if (!res_->chunked()) throw php::exception(zend_ce_error_exception
+            , "Failed to write body: 'chunked' encoding required"
+            , -1);
         boost::system::error_code error;
         boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
-        if (error)
-        {
+        if (error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to write_end: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to write body: %s") % error.message()).str()
+                , error.value());
         }
     }
-    void _handler::write_file(server_response *res_ptr, std::string path, coroutine_handler &ch)
-    {
-        if(!res_) return;
+
+    void _handler::write_file(server_response *res_ptr, std::string path, coroutine_handler &ch) {
+        if (!res_) return;
         std::ifstream file(path);
         char buffer[2048];
         std::size_t size;
-        std::string err;
         boost::system::error_code error;
 
-        while(!file.eof()) {
-            boost::asio::post(gcontroller->context_y, [&file, &ch, &buffer, &size, &err] () {
-                try
-                {
+        while (!file.eof()) {
+            boost::asio::post(gcontroller->context_y, [&file, &ch, &buffer, &size, &error] () {
+                try {
                     file.read(buffer, sizeof(buffer));
                     size = file.gcount();
-                }
-                catch(const std::exception& ex)
-                {
-                    err.assign(ex.what());
+                } catch(const std::ios_base::failure& ex) {
+                    error.assign(ex.code().value(), boost::system::generic_category());
                 }
                 ch.resume();
             });
             ch.suspend();
-            if (err.empty())
-            {
-                boost::asio::async_write(socket_, boost::beast::http::make_chunk( boost::asio::buffer(buffer, size) ), ch[error]);
-                if (error)
-                {
-                    goto WRITE_ERROR;
-                }
-            }
-            else
-            {
+            if (error) {
                 res_.reset();
                 req_.reset();
-                throw php::exception(zend_ce_exception, "failed to write_file: " + err);
+                throw php::exception(zend_ce_exception
+                    , (boost::format("Failed to write file: %s") % error.message()).str()
+                    , error.value());
             }
+            boost::asio::async_write(socket_, boost::beast::http::make_chunk( boost::asio::buffer(buffer, size) ), ch[error]);
+            if (error) goto WRITE_ERROR;
         }
         boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
 WRITE_ERROR:
-        if (error)
-        {
+        if (error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to write_file: (%1%) %2%") % error.value() % error.message()).str(), error.value());
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to write file: %s") % error.message()).str()
+                , error.value());
         }
         // file.close();
     }
-    void _handler::finish(server_response *res_ptr, coroutine_handler &ch)
-    {
-        if(res_)
-        {
-            if(res_->chunked() && !(res_ptr->status_ & server_response::STATUS_BODY_END))
-            {
+
+    void _handler::finish(server_response *res_ptr, coroutine_handler &ch) {
+        if (res_) {
+            if (res_->chunked() && !(res_ptr->status_ & server_response::STATUS_BODY_END)) {
                 boost::system::error_code error;
                 boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
                 res_.reset();
                 req_.reset();
-                if (error)
-                {
-                    std::cerr << "[" << time::iso() << "] (ERROR) failed to write_end: (" << error.value() << ") " << error.message() << std::endl;
-                }
-                else
-                {
-                    start();
-                }
+                if (error) std::cerr << "[" << time::iso() << "] (ERROR) Failed to write body: (" 
+                    << error.value() << ") " << error.message() << "\n";
+                else start();
             }
         }
     }
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 6cb88a3..d1a600e 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -74,7 +74,9 @@ namespace http {
 	php::value client_request::http_version(php::parameters& params) {
 		long v = static_cast<int>(params[0]);
 		if(v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST)
-			throw php::exception(zend_ce_type_error, "Failed to set http version: illegal HTTP version", -1);
+			throw php::exception(zend_ce_error_exception
+				, "Failed to set HTTP version: value out of range"
+				, -1);
 		
 		curl_easy_setopt(c_easy_, CURLOPT_HTTP_VERSION, v);
 		return nullptr;
@@ -119,7 +121,9 @@ namespace http {
 		// ---------------------------------------------------------------------------
         php::string u = get("url", true);
 		if(!u.typeof(php::TYPE::STRING)) 
-			throw php::exception(zend_ce_type_error, "request 'url' must be a string");
+			throw php::exception(zend_ce_type_error
+				, "Failed to build client request: 'url' typeof 'string' required"
+				, -1);
 		
 		curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
 		php::string m = get("method", true);
diff --git a/src/http/http.cpp b/src/http/http.cpp
index bc1ca82..a24189b 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -46,9 +46,10 @@ namespace flame::http {
 		server_response::declare(ext);
 	}
 	static void init_guard() {
-		if(!client_) {
-			throw php::exception(zend_ce_parse_error, "flame not initialized");
-		}
+		if(!client_)
+			throw php::exception(zend_ce_error_exception
+				, "Failed to execute HTTP request: exception or missing 'flame\\init()' ?"
+				, -1);
 	}
 	php::value get(php::parameters& params) {
 		init_guard();
diff --git a/src/http/server.cpp b/src/http/server.cpp
index 71b88d7..9101ce5 100644
--- a/src/http/server.cpp
+++ b/src/http/server.cpp
@@ -11,50 +11,40 @@ namespace flame::http
         php::class_entry<server> class_server("flame\\http\\server");
         class_server
             .property({"address", "127.0.0.1:7678"})
-            .method<&server::__construct>("__construct",
-            {
+            .method<&server::__construct>("__construct", {
                 {"address", php::TYPE::STRING},
             })
-            .method<&server::before>("before",
-            {
+            .method<&server::before>("before", {
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::after>("after",
-            {
+            .method<&server::after>("after", {
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::put>("put",
-            {
+            .method<&server::put>("put", {
                 {"path", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::delete_>("delete",
-            {
+            .method<&server::delete_>("delete", {
                 {"path", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::post>("post",
-            {
+            .method<&server::post>("post", {
                 {"path", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::patch>("patch",
-            {
+            .method<&server::patch>("patch", {
                 {"path", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::get>("get",
-            {
+            .method<&server::get>("get", {
                 {"path", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::head>("head",
-            {
+            .method<&server::head>("head", {
                 {"path", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
-            .method<&server::options>("options",
-            {
+            .method<&server::options>("options", {
                 {"path", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
@@ -66,14 +56,18 @@ namespace flame::http
     server::server()
     : accp_(gcontroller->context_x)
     , sock_(gcontroller->context_x)
-    , closed_(false)
-    {}
+    , closed_(false) {
+
+    }
+
     typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
-    php::value server::__construct(php::parameters &params)
-    {
+    php::value server::__construct(php::parameters &params) {
         php::string str = params[0];
         auto pair = udp::addr2pair(str);
-        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
+        if (pair.first.empty() || pair.second.empty())
+            throw php::exception(zend_ce_error_exception
+                , "Failed to bind tcp socket: address malformed"
+                , -1);
         boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
         addr_.address(addr);
         addr_.port(std::atoi(pair.second.c_str()));
@@ -87,77 +81,70 @@ namespace flame::http
         set("address", params[0]);
         return nullptr;
     }
-    php::value server::before(php::parameters &params)
-    {
+
+    php::value server::before(php::parameters &params) {
         cb_["before"] = params[0];
         return this;
     }
-    php::value server::after(php::parameters &params)
-    {
+
+    php::value server::after(php::parameters &params) {
         cb_["after"] = params[0];
         return this;
     }
-    php::value server::put(php::parameters &params)
-    {
+
+    php::value server::put(php::parameters &params) {
         cb_[std::string("PUT:") + params[0].to_string()] = params[1];
         return this;
     }
-    php::value server::delete_(php::parameters &params)
-    {
+
+    php::value server::delete_(php::parameters &params) {
         cb_[std::string("DELETE:") + params[0].to_string()] = params[1];
         return this;
     }
-    php::value server::post(php::parameters &params)
-    {
+
+    php::value server::post(php::parameters &params) {
         cb_[std::string("POST:") + params[0].to_string()] = params[1];
         return this;
     }
-    php::value server::get(php::parameters &params)
-    {
+
+    php::value server::get(php::parameters &params) {
         cb_[std::string("GET:") + params[0].to_string()] = params[1];
         return this;
     }
-    php::value server::head(php::parameters &params)
-    {
+
+    php::value server::head(php::parameters &params) {
         cb_[std::string("HEAD:") + params[0].to_string()] = params[1];
         return this;
     }
-    php::value server::options(php::parameters &params)
-    {
+
+    php::value server::options(php::parameters &params) {
         cb_[std::string("OPTIONS:") + params[0].to_string()] = params[1];
         return this;
     }
-    php::value server::patch(php::parameters &params)
-    {
+
+    php::value server::patch(php::parameters &params) {
         cb_[std::string("PATCH:") + params[0].to_string()] = params[1];
         return this;
     }
-    php::value server::run(php::parameters &params)
-    {
+
+    php::value server::run(php::parameters &params) {
         accp_.bind(addr_);
         accp_.listen(boost::asio::socket_base::max_listen_connections);
 
         coroutine_handler ch{coroutine::current};
-        while(!closed_)
-        {
+        while(!closed_) {
             boost::system::error_code err;
             accp_.async_accept(sock_, ch[err]);
-            if (err == boost::asio::error::operation_aborted)
-            {
-                break;
-            }
-            else if(err)
-            {
-                throw php::exception(zend_ce_exception,
-                                   (boost::format("failed to accept connection: (%1%) %2%") % err.value() % err.message()).str(), err.value());
-            }else{
-                std::make_shared<_handler>(this, std::move(sock_))->start();
-            }
+            if (err == boost::asio::error::operation_aborted) break; 
+            else if(err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to accept connection: %s") % err.message()).str()
+                , err.value());
+            else std::make_shared<_handler>(this, std::move(sock_))->start();
         }
         return nullptr;
     }
-    php::value server::close(php::parameters &params)
-    {
+
+    php::value server::close(php::parameters &params) {
         if(closed_) return nullptr;
         closed_ = true;
         accp_.cancel();
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index 417793f..7b27ea6 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -105,26 +105,25 @@ namespace flame::http {
 	}
 	// chunked encoding 用法
 	php::value server_response::write_header(php::parameters& params) {
-		if((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT)) {
-			throw php::exception(zend_ce_exception, "header already sent");
-		}
+		if((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT))
+			throw php::exception(zend_ce_error_exception
+				, "Failed to write header: already sent"
+				, -1);
+		
 		status_ |= STATUS_HEAD_SENT;
-
-		if(params.size() > 0) {
-			set("status", params[0].to_integer());
-		}
+		if(params.size() > 0) set("status", params[0].to_integer());
 		coroutine_handler ch{coroutine::current};
 		handler_->write_head(this, ch);
 		return nullptr;
 	}
 	php::value server_response::write(php::parameters& params) {
 		if(status_ & STATUS_BODY_END)
-		{
-			throw php::exception(zend_ce_exception, "response already done");
-		}
+			throw php::exception(zend_ce_error_exception
+				, "Failed to write body: response already done"
+				, -1);
+
 		coroutine_handler ch{coroutine::current};
-		if (!(status_ & STATUS_HEAD_SENT))
-		{
+		if (!(status_ & STATUS_HEAD_SENT)) {
 			status_ |= STATUS_HEAD_SENT;
 			handler_->write_head(this, ch);
 		}
@@ -135,20 +134,20 @@ namespace flame::http {
 	}
 	php::value server_response::end(php::parameters& params) {
 		if (status_ & STATUS_BODY_END)
-		{
-			throw php::exception(zend_ce_exception, "response already done");
-		}
+			throw php::exception(zend_ce_error_exception
+				, "Failed to write body: response already done"
+				, -1);
+		
 		coroutine_handler ch{coroutine::current};
-		if (!(status_ & STATUS_HEAD_SENT))
-		{
+		if (!(status_ & STATUS_HEAD_SENT)) {
 			status_ |= STATUS_HEAD_SENT;
 			handler_->write_head(this, ch);
 		}
-		if(params.size() > 0) {
+		if (params.size() > 0) {
 			status_ |= STATUS_BODY_SENT;
 			php::string chunk = params[0].to_string();
 			handler_->write_body(this, chunk, ch);
-		}else{
+		} else {
 			status_ |= STATUS_BODY_END;
 			handler_->write_end(this, ch);
 			handler_.reset();
@@ -157,12 +156,12 @@ namespace flame::http {
 	}
 	php::value server_response::file(php::parameters& params) {
 		if ((status_ & STATUS_BODY_END) || (status_ & STATUS_BODY_SENT))
-		{
-			throw php::exception(zend_ce_exception, "response already done");
-		}
+			throw php::exception(zend_ce_error_exception
+				, "Failed to write file: response already done"
+				, -1);
+
 		coroutine_handler ch{coroutine::current};
-		if (!(status_ & STATUS_HEAD_SENT))
-		{
+		if (!(status_ & STATUS_HEAD_SENT)) {
 			status_ |= STATUS_HEAD_SENT;
 			handler_->write_head(this, ch);
 		}
@@ -226,12 +225,8 @@ namespace flame::http {
 					buffer.append("; Domain=", 9);
 					buffer.append(cookie.get("domain"));
 				}
-				if(!cookie.get("secure").empty()) {
-					buffer.append("; Secure", 8);
-				}
-				if(!cookie.get("http_only").empty()) {
-					buffer.append("; HttpOnly", 10);
-				}
+				if(!cookie.get("secure").empty()) buffer.append("; Secure", 8);
+				if(!cookie.get("http_only").empty()) buffer.append("; HttpOnly", 10);
 				ctr_.insert("Set-Cookie", buffer);
 			}
 		}
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index d962364..481cb24 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -6,8 +6,8 @@
 
 namespace flame::kafka
 {
-    static rd_kafka_topic_partition_list_t *array2topics(const php::array &topics)
-    { // 目标订阅的 TOPIC
+    static rd_kafka_topic_partition_list_t *array2topics(const php::array &topics) {
+        // 目标订阅的 TOPIC
         rd_kafka_topic_partition_list_t *t = rd_kafka_topic_partition_list_new(topics.size());
         for (auto i = topics.begin(); i != topics.end(); ++i)
         {
@@ -15,64 +15,63 @@ namespace flame::kafka
         }
         return t;
     }
+
     _consumer::_consumer(php::array &config, php::array &topics)
-    : close_(false)
-    {
+    : close_(false) {
         if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
-        {
-            throw php::exception(zend_ce_type_error, "kafka conf 'bootstrap.servers' is required");
-        }
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka consumer: 'bootstrap.servers' required"
+                , -1);
+        
         if (!config.exists("group.id"))
-        {
-            throw php::exception(zend_ce_type_error, "kafka conf 'group.id' is required");
-        }
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka consumer: 'group.id' required"
+                , -1);
+        
+        if (topics.size() == 0)
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka consumer: target topics missing"
+                , -1);
+
         rd_kafka_conf_t *conf = array2conf(config);
         char err[256];
 
         conn_ = rd_kafka_new(RD_KAFKA_CONSUMER, conf, err, sizeof(err));
         if (!conn_)
-        {
-            throw php::exception(zend_ce_exception,
-                                (boost::format("failed to create Kafka Consumer: %1%") % err).str(), -1);
-        }
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to create Kafka consumer: %s") % err).str()/*, 0*/);
+        
         auto r = rd_kafka_poll_set_consumer(conn_);
         if (r != RD_KAFKA_RESP_ERR_NO_ERROR)
-        {
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to create Kafka Consumer: (%1%) %2%") % r % rd_kafka_err2str(r)).str(), r);
-        }
-        if (topics.size() == 0)
-        {
-            throw php::exception(zend_ce_type_error, "failed to create Kafka Consumer: target topics missing", -1);
-        }
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to create Kafka Consumer: %s") % rd_kafka_err2str(r)).str()
+                , r);
+        
         tops_ = array2topics(topics);
     }
-    _consumer::~_consumer()
-    {
+
+    _consumer::~_consumer() {
         if(conn_) {
             // rd_kafka_consumer_close(conn_);
             rd_kafka_topic_partition_list_destroy(tops_);
             rd_kafka_destroy(conn_);
         }
     }
-    void _consumer::subscribe(coroutine_handler &ch)
-    {
+
+    void _consumer::subscribe(coroutine_handler &ch) {
         rd_kafka_resp_err_t err;
-        boost::asio::post(gcontroller->context_y, [this, &err, &ch] ()
-        {
+        boost::asio::post(gcontroller->context_y, [this, &err, &ch] ()  {
             err = rd_kafka_subscribe(conn_, tops_);
             ch.resume();
         });
         ch.suspend();
-        if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
-        {
-            throw php::exception(zend_ce_exception,
-                (boost::format("failed to subcribe Kafka topics: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
-                err);
-        }
+        if(err != RD_KAFKA_RESP_ERR_NO_ERROR) 
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to subcribe Kafka topics: %s") % rd_kafka_err2str(err)).str()
+                , err);
     }
-    void _consumer::consume(coroutine_queue<php::object>& q, coroutine_handler& ch)
-    {
+
+    void _consumer::consume(coroutine_queue<php::object>& q, coroutine_handler& ch) {
         rd_kafka_resp_err_t err;
         rd_kafka_message_t* msg = nullptr;
 POLL_MESSAGE:
@@ -82,8 +81,7 @@ namespace flame::kafka
         {
             msg = rd_kafka_consumer_poll(conn_, 500);
             if(!msg) err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
-            else if (msg->err)
-            {
+            else if (msg->err) {
                 err = msg->err;
                 rd_kafka_message_destroy(msg);
             }
@@ -91,23 +89,17 @@ namespace flame::kafka
             ch.resume();
         });
         ch.suspend();
-        if(err == RD_KAFKA_RESP_ERR__PARTITION_EOF)
-        {
-            if(close_)
-            {
+        if (err == RD_KAFKA_RESP_ERR__PARTITION_EOF) {
+            if (close_)  {
                 q.close(); // 关闭队列使对应消费协程自行退出
                 return;
             } // 消费已被关闭
             else goto POLL_MESSAGE;
-        }
-        else if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
-        {
-            throw php::exception(zend_ce_exception,
-                (boost::format("failed to consume Kafka message: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
-                err);
-        }
-        else
-        {
+        } else if(err != RD_KAFKA_RESP_ERR_NO_ERROR) {
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to consume Kafka message: %s") % rd_kafka_err2str(err)).str()
+                , err);
+        } else {
             php::object obj(php::class_entry<message>::entry());
             message* ptr = static_cast<message*>(php::native(obj));
             ptr->build_ex(msg); // msg 交由 message 对象管理
@@ -115,25 +107,22 @@ namespace flame::kafka
             goto POLL_MESSAGE;
         }
     }
-    void _consumer::commit(const php::object& obj, coroutine_handler& ch)
-    {
+
+    void _consumer::commit(const php::object& obj, coroutine_handler& ch) {
         rd_kafka_resp_err_t err;
         rd_kafka_message_t *msg = static_cast<message*>(php::native(obj))->msg_;
-        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]()
-        {
+        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]() {
             err = rd_kafka_commit_message(conn_, msg, 0);
             ch.resume();
         });
         ch.suspend();
-        if(err != RD_KAFKA_RESP_ERR_NO_ERROR)
-        {
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to commit Kafka message: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
-                                 err);
-        }
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to commit Kafka message: %s") % err % rd_kafka_err2str(err)).str()
+                , err);
     }
-    void _consumer::close(coroutine_handler& ch)
-    {
+
+    void _consumer::close(coroutine_handler& ch) {
         rd_kafka_resp_err_t err;
         boost::asio::post(gcontroller->context_y, [this, &err, &ch] () {
             err = rd_kafka_consumer_close(conn_);
@@ -141,11 +130,10 @@ namespace flame::kafka
         });
         ch.suspend();
         if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
-        {
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to close Kafka consumer: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
-                                 err);
-        }
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to close Kafka consumer: %s") % rd_kafka_err2str(err)).str()
+                , err);
+        
         close_ = true;
     }
 
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index ca75ef4..9eded7c 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -4,42 +4,45 @@
 
 namespace flame::kafka
 {
-    _producer::_producer(php::array& config, php::array& topics)
-    {
+    _producer::_producer(php::array& config, php::array& topics) {
         if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
-        {
-            throw php::exception(zend_ce_type_error, "kafka conf 'bootstrap.servers' is required");
-        }
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka producer: 'bootstrap.servers' required"
+                , -1);
+        if (topics.size() == 0)
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka producer: target topics missing"
+                , -1);
+
         rd_kafka_conf_t *conf = array2conf(config);
         char err[256];
-
         conn_ = rd_kafka_new(RD_KAFKA_PRODUCER, conf, err, sizeof(err));
         if (!conn_)
-        {
-            throw php::exception(zend_ce_type_error,
-                                 (boost::format("failed to create Kafka Consumer: %1%") % err).str(), -1);
-        }
-        for (auto i = topics.begin(); i != topics.end(); ++i)
-        {
+            throw php::exception(zend_ce_type_error
+                , (boost::format("Failed to create Kafka Consumer: %s") % err).str()
+                , -1);
+        
+        for (auto i = topics.begin(); i != topics.end(); ++i) {
             php::string topic = i->second.to_string();
             tops_[topic] = rd_kafka_topic_new(conn_, topic.c_str(), nullptr);
         }
     }
-    _producer::~_producer()
-    {
-        if(conn_) {
+
+    _producer::~_producer() {
+        if (conn_) {
             for(auto i=tops_.begin(); i!=tops_.end(); ++i) {
                 rd_kafka_topic_destroy(i->second);
             }
             rd_kafka_destroy(conn_);
         }
     }
-    void _producer::publish(const php::string &topic, const php::string &key, const php::string &payload, const php::array &headers, coroutine_handler &ch)
-    {
+
+    void _producer::publish(const php::string &topic, const php::string &key
+        , const php::string &payload, const php::array &headers, coroutine_handler &ch) {
         rd_kafka_resp_err_t err;
         rd_kafka_headers_t* hdrs = array2hdrs(headers);
         boost::asio::post(gcontroller->context_y, [this, &err, &topic, &key, &payload, &hdrs, &ch]() {
-            if(key.size() > 0) {
+            if (key.size() > 0) {
                 err = rd_kafka_producev(conn_,
                     RD_KAFKA_V_TOPIC(topic.c_str()),
                     RD_KAFKA_V_KEY(key.c_str(), key.size()),
@@ -48,7 +51,7 @@ namespace flame::kafka
                     RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
                     RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
                     RD_KAFKA_V_END);
-            }else{ // 无 KEY 时默认 partitioner 会随机分配
+            } else { // 无 KEY 时默认 partitioner 会随机分配
                 err = rd_kafka_producev(conn_,
                     RD_KAFKA_V_TOPIC(topic.c_str()),
                     RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
@@ -57,23 +60,20 @@ namespace flame::kafka
                     RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
                     RD_KAFKA_V_END);
             }
-            if(err == RD_KAFKA_RESP_ERR_NO_ERROR) {
-                // TODO 优化: 是否可以考虑按周期间隔进行调用?
-                rd_kafka_poll(conn_, 5);
-            }
+             // TODO 优化: 是否可以考虑按周期间隔进行调用?
+            if (err == RD_KAFKA_RESP_ERR_NO_ERROR) rd_kafka_poll(conn_, 5);
             ch.resume();
         });
         ch.suspend();
-        if(err != RD_KAFKA_RESP_ERR_NO_ERROR) {
-            // 发生错误时, 需要手动销毁
-            if(hdrs) rd_kafka_headers_destroy(hdrs);
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to publish Kafka message: (%1%) %2%") % err % rd_kafka_err2str(err)).str(),
-                                 err);
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR) {
+            if(hdrs) rd_kafka_headers_destroy(hdrs);  // 发生错误时, 需要手动销毁
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to publish Kafka message: %s") % rd_kafka_err2str(err)).str()
+                , err);
         }
     }
-    void _producer::flush(coroutine_handler& ch)
-    {
+
+    void _producer::flush(coroutine_handler& ch) {
         if(conn_) rd_kafka_flush(conn_, 10000);
     }
 } // namespace flame::kafka
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index 4aa7609..0206763 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -12,52 +12,43 @@ namespace flame::kafka {
 		php::class_entry<consumer> class_consumer("flame\\kafka\\consumer");
 		class_consumer
 			.method<&consumer::__construct>("__construct", {}, php::PRIVATE)
-			.method<&consumer::run>("run",
-            {
+			.method<&consumer::run>("run", {
 				{"callable", php::TYPE::CALLABLE},
 			})
-            .method<&consumer::commit>("commit",
-            {
+            .method<&consumer::commit>("commit", {
                 {"message", "flame\\kafka\\message"}
             })
 			.method<&consumer::close>("close");
 		ext.add(std::move(class_consumer));
 	}
 
-	php::value consumer::__construct(php::parameters& params)
-    {
+	php::value consumer::__construct(php::parameters& params) {
 		return nullptr;
 	}
-	php::value consumer::run(php::parameters& params)
-    {
+
+	php::value consumer::run(php::parameters& params) {
         cb_ = params[0];
         coroutine_handler ch_run {coroutine::current};
         coroutine_queue<php::object> q;
         // 启动若干协程, 然后进行"并行"消费
         int count = cc_;
-        for (int i = 0; i < count; ++i)
-        {
+        for (int i = 0; i < count; ++i) {
             // 启动协程开始消费
             coroutine::start(php::value([this, &ch_run, &q, &count](php::parameters &params) -> php::value {
                 coroutine_handler ch {coroutine::current};
-                while(true)
-                {
-                    try
-                    {
+                while(true) {
+                    try {
                         // consume 本身可能出现异常，不应导致进程停止
                         std::optional<php::object> m = q.pop(ch);
                         if(m) cb_.call({m.value()});
                         else break;
-                    }
-                    catch(const php::exception& ex)
-                    {
+                    } catch(const php::exception& ex) {
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
+                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
                         // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
                 if(--count == 0) ch_run.resume();
-
                 return nullptr;
             }));
         }
@@ -65,14 +56,15 @@ namespace flame::kafka {
         ch_run.suspend();
         return nullptr;
 	}
-    php::value consumer::commit(php::parameters& params)
-    {
+
+    php::value consumer::commit(php::parameters& params) {
         php::object obj = params[0];
         coroutine_handler ch {coroutine::current};
 
         cs_->commit(obj, ch);
         return nullptr;
     }
+
 	php::value consumer::close(php::parameters& params) {
         coroutine_handler ch {coroutine::current};
         cs_->close(ch);
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index 14093fe..af69a66 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -5,10 +5,8 @@
 #include "producer.h"
 #include "_producer.h"
 
-namespace flame::kafka
-{
-    void declare(php::extension_entry &ext)
-    {
+namespace flame::kafka {
+    void declare(php::extension_entry &ext) {
         ext
 			.function<consume>("flame\\kafka\\consume", {
 				{"options", php::TYPE::ARRAY},
@@ -23,85 +21,77 @@ namespace flame::kafka
 		producer::declare(ext);
     }
     
-    php::value consume(php::parameters& params)
-    {
+    php::value consume(php::parameters& params) {
         php::array config = params[0];
         php::array topics = params[1];
 
         php::object obj(php::class_entry<consumer>::entry());
         consumer* ptr = static_cast<consumer*>(php::native(obj));
-        if (config.exists("concurrent"))
-        {
+        if (config.exists("concurrent")) {
             ptr->cc_ = std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 256);
             config.erase("concurrent");
         }
-        if (!config.exists("log.connection.close")) {
-            config.set("log.connection.close", "false");
-        }
+        if (!config.exists("log.connection.close")) config.set("log.connection.close", "false");
+
         ptr->cs_.reset(new _consumer(config, topics));
         coroutine_handler ch {coroutine::current};
         // 订阅
         ptr->cs_->subscribe(ch);
         return std::move(obj);
     }
-    php::value produce(php::parameters& params)
-    {
+
+    php::value produce(php::parameters& params) {
         php::array config = params[0];
         php::array topics = params[1];
 
         php::object obj(php::class_entry<producer>::entry());
         producer* ptr = static_cast<producer*>(php::native(obj));
-        if (!config.exists("log.connection.close")) {
-            config.set("log.connection.close", "false");
-        }
+
+        if (!config.exists("log.connection.close")) config.set("log.connection.close", "false");
+
         ptr->pd_.reset(new _producer(config, topics));
         // TODO 优化: 确认首次连接已建立
         return std::move(obj);
     }
 
-
     rd_kafka_conf_t* array2conf(const php::array &config) {
-        if(config.empty()) return nullptr;
+        if (config.empty()) return nullptr;
         char err[256];
         rd_kafka_conf_t *conf = rd_kafka_conf_new();
         for (auto i = config.begin(); i != config.end(); ++i) {
             php::string key = i->first.to_string();
             php::string val = i->second.to_string();
 
-            if(RD_KAFKA_CONF_OK != rd_kafka_conf_set(conf, key.data(), val.data(), err, sizeof(err)))
-                throw php::exception(zend_ce_type_error,
-                        (boost::format("unable to set Kafka config: %1%") % err).str(), -1);
+            if (RD_KAFKA_CONF_OK != rd_kafka_conf_set(conf, key.data(), val.data(), err, sizeof(err)))
+                throw php::exception(zend_ce_type_error
+                    , (boost::format("Failed to set Kafka config: %s") % err).str()
+                    , -1);
         }
         return conf;
     }
-    rd_kafka_headers_t* array2hdrs(const php::array& data)
-    {
+
+    rd_kafka_headers_t* array2hdrs(const php::array& data) {
         rd_kafka_headers_t *hdrs = nullptr;
-        if (!data.empty() && data.typeof(php::TYPE::ARRAY) && data.size() > 0)
-        {
+        if (!data.empty() && data.typeof(php::TYPE::ARRAY) && data.size() > 0) {
             hdrs = rd_kafka_headers_new(data.size());
-            for (auto i = data.begin(); i != data.end(); ++i)
-            {
+            for (auto i = data.begin(); i != data.end(); ++i) {
                 php::string key = i->first.to_string(),
                             val = i->second.to_string();
                 rd_kafka_header_add(hdrs,
-                                    key.c_str(), key.size(),
-                                    val.c_str(), val.size());
+                    key.c_str(), key.size(),
+                    val.c_str(), val.size());
             }
         }
         return hdrs;
     }
-    php::array hdrs2array(rd_kafka_headers_t* hdrs)
-    {
-        if(hdrs == nullptr) return php::array(0);
+    php::array hdrs2array(rd_kafka_headers_t* hdrs) {
+        if (hdrs == nullptr) return php::array(0);
         php::array header(rd_kafka_header_cnt(hdrs));
         const char *key;
         const void *val;
         std::size_t len;
         for (std::size_t i = 0; rd_kafka_header_get_all(hdrs, i, &key, &val, &len) == RD_KAFKA_RESP_ERR_NO_ERROR; ++i)
-        {
             header.set(key, php::string((const char *)val, len));
-        }
         return header;
     }
 }
diff --git a/src/log/log.cpp b/src/log/log.cpp
index 67a8257..4c53d52 100644
--- a/src/log/log.cpp
+++ b/src/log/log.cpp
@@ -2,8 +2,8 @@
 #include "log.h"
 #include "../time/time.h"
 
-namespace flame::log
-{
+namespace flame::log {
+
     static std::string LEVEL_S[] = {
         "TRACE",
         "DEBUG",
@@ -12,7 +12,9 @@ namespace flame::log
         "ERROR",
         "FATAL",
     };
+
     static std::map<std::string, int> LEVEL_I {};
+
     enum {
         LEVEL_TRACE,
         LEVEL_DEBUG,
@@ -21,64 +23,55 @@ namespace flame::log
         LEVEL_ERROR,
         LEVEL_FATAL,
     };
+
     static int level = 0;
     
-    
-    static void write_ex(int lv, php::parameters& params)
-    {
+    static void write_ex(int lv, php::parameters& params) {
         if(lv < level) return;
         std::ostream& os = lv > LEVEL_WARNING ? std::cout : std::cerr;
         os << '[' << time::iso() << "] (";
         os << LEVEL_S[lv];
         os << ")";
         for (int i = 0; i < params.size(); ++i)
-        {
             os << ' ' << params[i].ptr();
-        }
+        
         os << std::endl;
     }
-    static php::value trace(php::parameters &params)
-    {
+
+    static php::value trace(php::parameters &params) {
         write_ex(LEVEL_TRACE, params);
         return nullptr;
     }
-    static php::value debug(php::parameters &params)
-    {
+
+    static php::value debug(php::parameters &params) {
         write_ex(LEVEL_DEBUG, params);
         return nullptr;
     }
-    static php::value info(php::parameters &params)
-    {
+
+    static php::value info(php::parameters &params) {
         write_ex(LEVEL_INFO, params);
         return nullptr;
     }
-    static php::value warning(php::parameters &params)
-    {
+
+    static php::value warning(php::parameters &params) {
         write_ex(LEVEL_WARNING, params);
         return nullptr;
     }
-    static php::value error(php::parameters &params)
-    {
+
+    static php::value error(php::parameters &params) {
         write_ex(LEVEL_ERROR, params);
         return nullptr;
     }
-    static php::value fatal(php::parameters &params)
-    {
+
+    static php::value fatal(php::parameters &params) {
         write_ex(LEVEL_FATAL, params);
         return nullptr;
     }
 
-    void declare(php::extension_entry &ext)
-    {
+    void declare(php::extension_entry &ext) {
         gcontroller->on_init([] (const php::array& options) {
-            if(options.exists("level"))
-            {
-                level = options.get("level").to_integer();
-            }
-            else
-            {
-                level = 0;
-            }
+            if(options.exists("level")) level = options.get("level").to_integer();
+            else level = 0;
         });
         ext
             .function<trace>("flame\\log\\trace")
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index 56c81ae..c66298e 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -7,8 +7,7 @@
 namespace flame::mongodb
 {
     void _connection_base::fake_deleter(bson_t *doc) {}
-    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, int type, coroutine_handler &ch)
-    {
+    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, int type, coroutine_handler &ch) {
         std::shared_ptr<bson_t> cmd = array2bson(pcmd);
         // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏
         //（实际对 reply 的使用流程会重新初始化 reply 导致其值被至于 stack 上，导致此处 heap 内存泄漏）
@@ -34,29 +33,29 @@ namespace flame::mongodb
             bson_append_int32(opt.get(), "serverId", 8, server_id);
             switch(type) {
             case COMMAND_READ:
-                rok = mongoc_client_read_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+                rok = mongoc_client_read_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
                 break;
             case COMMAND_WRITE:
-                rok = mongoc_client_write_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), opt.get(), rep.get(), err.get());
+                rok = mongoc_client_write_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), opt.get(), rep.get(), err.get());
                 break;
             case COMMAND_READ_WRITE:
-                rok = mongoc_client_read_write_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+                rok = mongoc_client_read_write_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
                 break;
             case COMMAND_RAW:
             default:
-                rok = mongoc_client_command_with_opts(conn.get(), mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+                rok = mongoc_client_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
             }
             ch.resume();
         });
         ch.suspend();
-        if(!rok)
-        {
-            throw php::exception(zend_ce_exception,
-                    (boost::format("failed to execute MongoDB command: (%1%) %2%") % err->code % err->message).str(),
-                    err->code);
-        }
-        else if(bson_has_field(rep.get(), "cursor"))
-        {
+        if (!rok) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to execute MongoDB command: %s") % err->message).str()
+            , err->code);
+        else if (bson_has_field(rep.get(), "cursor")) {
             php::object obj{php::class_entry<cursor>::entry()};
             auto ptr = static_cast<cursor *>(php::native(obj));
             ptr->cl_.reset(new _connection_lock(conn));
@@ -69,16 +68,13 @@ namespace flame::mongodb
             // 参考: http://mongoc.org/libmongoc/current/mongoc_cursor_new_from_command_reply_with_opts.html
             *std::get_deleter<void (*)(bson_t*)>(rep) = fake_deleter;
             return std::move(obj);
-        }
-        else
-        {
+        } else {
             php::array r(4);
 
             bson_iter_t i;
             bson_oid_t oid;
             bson_iter_init(&i, rep.get());
-            while (bson_iter_next(&i))
-            {
+            while (bson_iter_next(&i)) {
                 const char* key = bson_iter_key(&i);
                 std::uint32_t len = bson_iter_key_len(&i);
                 if(len > 0 && key[0] == '$') continue;
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
index bb1c9da..3e21ced 100644
--- a/src/mongodb/_connection_lock.cpp
+++ b/src/mongodb/_connection_lock.cpp
@@ -6,19 +6,18 @@ namespace flame::mongodb
 {
     _connection_lock::_connection_lock(std::shared_ptr<mongoc_client_t> c)
     : conn_(c)
-    , guard_(gcontroller->context_y)
-    {
+    , guard_(gcontroller->context_y) {
 
     }
+
     _connection_lock::~_connection_lock() {
-        
     }
-    std::shared_ptr<mongoc_client_t> _connection_lock::acquire(coroutine_handler &ch)
-    {
+    
+    std::shared_ptr<mongoc_client_t> _connection_lock::acquire(coroutine_handler &ch) {
         return conn_;
     }
-    php::array _connection_lock::fetch(std::shared_ptr<mongoc_cursor_t> cs, coroutine_handler &ch)
-    {
+
+    php::array _connection_lock::fetch(std::shared_ptr<mongoc_cursor_t> cs, coroutine_handler &ch) {
         bool has = false;
         auto err = std::make_shared<bson_error_t>();
         const bson_t *doc;
@@ -28,9 +27,9 @@ namespace flame::mongodb
         });
         ch.suspend();
         // 发生了错误
-        if (has) throw php::exception(zend_ce_exception,
-                    (boost::format("failed to fetch document: (%1%) %2%") % err->code % err->message).str(),
-                    err->code);
+        if (has) throw php::exception(zend_ce_exception
+                    , (boost::format("Failed to fetch document: %s") % err->message).str()
+                    , err->code);
         // 文档 doc 仅为 "引用" 流程
         return bson2array(const_cast<bson_t*>(doc));
     }
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index ea28c53..fa12c61 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -3,100 +3,81 @@
 #include "collection.h"
 #include "cursor.h"
 
-namespace flame::mongodb
-{
-    void collection::declare(php::extension_entry &ext)
-    {
+namespace flame::mongodb {
+    void collection::declare(php::extension_entry &ext) {
         php::class_entry<collection> class_collection("flame\\mongodb\\collection");
         class_collection
             .property({"name", ""})
             .method<&collection::__construct>("__construct", {}, php::PRIVATE)
-            .method<&collection::insert>("insert",
-            {
+            .method<&collection::insert>("insert",{
                 {"data", php::TYPE::ARRAY},
                 {"ordered", php::TYPE::BOOLEAN, false, true} // true
             })
-            .method<&collection::insert>("insert_many",
-            {
+            .method<&collection::insert>("insert_many", {
                 {"data", php::TYPE::ARRAY},
                 {"ordered", php::TYPE::BOOLEAN, false, true} // true
             })
-            .method<&collection::insert>("insert_one",
-            {
+            .method<&collection::insert>("insert_one", {
                 {"data", php::TYPE::ARRAY},
             })
-            .method<&collection::delete_>("delete",
-            {
+            .method<&collection::delete_>("delete", {
                 {"query", php::TYPE::ARRAY},
                 {"limit", php::TYPE::INTEGER, false, true}, // 0
             })
-            .method<&collection::delete_>("delete_many",
-            {
+            .method<&collection::delete_>("delete_many", {
                 {"query", php::TYPE::ARRAY},
                 {"limit", php::TYPE::INTEGER, false, true}, // 0
             })
-            .method<&collection::delete_>("delete_one",
-            {
+            .method<&collection::delete_>("delete_one", {
                 {"query", php::TYPE::ARRAY},
             })
-            .method<&collection::update>("update",
-            {
+            .method<&collection::update>("update", {
                 {"query", php::TYPE::ARRAY},
                 {"update", php::TYPE::ARRAY},
                 {"upsert", php::TYPE::BOOLEAN, false, true}, // false
             })
-            .method<&collection::update>("update_many",
-            {
+            .method<&collection::update>("update_many", {
                 {"query", php::TYPE::ARRAY},
                 {"update", php::TYPE::ARRAY},
                 {"upsert", php::TYPE::BOOLEAN, false, true}, // false
             })
-            .method<&collection::update_one>("update_one",
-            {
+            .method<&collection::update_one>("update_one", {
                 {"query", php::TYPE::ARRAY},
                 {"update", php::TYPE::ARRAY},
                 {"upsert", php::TYPE::BOOLEAN, false, true}, // false
             })
-            .method<&collection::find>("find",
-            {
+            .method<&collection::find>("find", {
                 {"filter", php::TYPE::ARRAY},
                 {"projection", php::TYPE::ARRAY, false, true},
                 {"sort", php::TYPE::ARRAY, false, true},
                 {"limit", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&collection::find_one>("one", // 兼容原 API 名称定义
-            {
+            .method<&collection::find_one>("one", { 
                 {"filter", php::TYPE::ARRAY},
                 {"sort", php::TYPE::ARRAY, false, true},
             })
-            .method<&collection::find_one>("find_one",
-            {
+            .method<&collection::find_one>("find_one", { // 兼容原 API 名称定义
                 {"filter", php::TYPE::ARRAY},
                 {"sort", php::TYPE::ARRAY, false, true},
             })
-            .method<&collection::get>("get",
-            {
+            .method<&collection::get>("get", {
                 {"filter", php::TYPE::ARRAY},
                 {"field", php::TYPE::STRING},
                 {"sort", php::TYPE::ARRAY, false, true},
             })
-            .method<&collection::count>("count",
-            {
+            .method<&collection::count>("count", {
                 {"query", php::TYPE::ARRAY},
             })
-            .method<&collection::aggregate>("aggregate",
-            {
+            .method<&collection::aggregate>("aggregate", {
                 {"pipeline", php::TYPE::ARRAY},
             })
-            .method<&collection::find_and_delete>("find_and_delete",
-            {
+            .method<&collection::find_and_delete>("find_and_delete", {
                 {"query", php::TYPE::ARRAY},
                 {"sort", php::TYPE::ARRAY, false, true},
                 {"upsert", php::TYPE::BOOLEAN, false, true},
                 {"fields", php::TYPE::ARRAY, false, true},
             })
-            .method<&collection::find_and_update>("find_and_update",
-            {
+            .method<&collection::find_and_update>("find_and_update", {
                 {"query", php::TYPE::ARRAY},
                 {"update", php::TYPE::ARRAY},
                 {"sort", php::TYPE::ARRAY, false, true},
@@ -106,59 +87,52 @@ namespace flame::mongodb
             });
         ext.add(std::move(class_collection));
     }
-    php::value collection::insert(php::parameters &params)
-    {
+
+    php::value collection::insert(php::parameters &params) {
         php::array cmd(8);
         cmd.set("insert", name_);
         php::array docs = params[0];
-        if (!docs.exists(0))
-        { // 单项插入的情况
+        if (!docs.exists(0)) { // 单项插入的情况
             docs = php::array(2);
             docs.set(0, params[0]);
         }
         cmd.set("documents", docs);
-        if (params.size() > 1)
-        {
-            cmd.set("ordered", params[1].to_boolean());
-        }
+        if (params.size() > 1) cmd.set("ordered", params[1].to_boolean());
+
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
     }
-    php::value collection::delete_(php::parameters &params)
-    {
+
+    php::value collection::delete_(php::parameters &params) {
         php::array cmd(8);
         cmd.set("delete", name_);
         php::array deletes(2), deleteo(2);
         deleteo.set("q", params[0]);
         if (params.size() > 1 && params[1].typeof(php::TYPE::INTEGER))
-        {
             deleteo.set("limit", params[1].to_integer());
-        }
-        else
-        {
-            deleteo.set("limit", 0);
-        }
+        else deleteo.set("limit", 0);
+        
         deletes.set(0, deleteo);
         cmd.set("deletes", deletes);
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
     }
+
     php::value collection::delete_one(php::parameters& params) {
         return call("delete", {params[0], 1});
     }
-    php::value collection::update(php::parameters &params)
-    {
+
+    php::value collection::update(php::parameters &params) {
         php::array cmd(8);
         cmd.set("update", name_);
         php::array updates(2), updateo(4);
         updateo.set("q", params[0]);
         updateo.set("u", params[1]);
         if (params.size() > 2)
-        {
             updateo.set("upsert", params[2].to_boolean());
-        }
+
         updateo.set("multi", true);
         updates.set(0, updateo);
         cmd.set("updates", updates);
@@ -166,6 +140,7 @@ namespace flame::mongodb
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
     }
+
     php::value collection::update_one(php::parameters &params) {
         php::array cmd(8);
         cmd.set("update", name_);
@@ -173,9 +148,8 @@ namespace flame::mongodb
         updateo.set("q", params[0]);
         updateo.set("u", params[1]);
         if (params.size() > 2)
-        {
             updateo.set("upsert", params[2].to_boolean());
-        }
+
         // updateo.set("multi", false);
         updates.set(0, updateo);
         cmd.set("updates", updates);
@@ -183,81 +157,54 @@ namespace flame::mongodb
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
     }
-    php::value collection::find(php::parameters &params)
-    {
+
+    php::value collection::find(php::parameters &params) {
         php::array cmd(8);
         cmd.set("find", name_);
         if (params.size() > 0)
-        {
             cmd.set("filter", params[0]);
-        }
-        if (params.size() > 1)
-        {
+
+        if (params.size() > 1) {
             php::array project;
-            if (params[1].typeof(php::TYPE::ARRAY))
-            {
+            if (params[1].typeof(php::TYPE::ARRAY)) {
                 php::array fields = params[1];
-                if (fields.exists(0))
-                { // 将纯字段列表形式转换为K/V形式
+                if (fields.exists(0)) { // 将纯字段列表形式转换为K/V形式
                     project = php::array(8);
                     for (auto i = fields.begin(); i != fields.end(); ++i)
-                    {
                         project.set(php::string(i->second), 1);
-                    }
-                }
-                else
-                {
-                    project = fields;
-                }
-            }
-            else if (params[1].typeof(php::TYPE::STRING))
-            { // 单个字段
+                } else project = fields;
+            } else if (params[1].typeof(php::TYPE::STRING)) { // 单个字段
                 php::array project(2);
                 project.set(php::string(params[1]), 1);
-            }
-            else
-            {
-                goto PROJECTION_ALL;
-            }
-            if (!project.exists("_id"))
-            {
-                project.set("_id", 0);
-            }
+            } else goto PROJECTION_ALL;
+
+            if (!project.exists("_id")) project.set("_id", 0);
             cmd.set("projection", project);
         }
     PROJECTION_ALL:
         if (params.size() > 2 && params[2].typeof(php::TYPE::ARRAY))
-        {
             cmd.set("sort", params[2]);
-        }
-        if (params.size() > 3)
-        {
+        if (params.size() > 3) {
             if (params[3].typeof(php::TYPE::INTEGER))
-            {
                 cmd.set("limit", params[3]);
-            }
-            else if (params[3].typeof(php::TYPE::ARRAY))
-            {
+            else if (params[3].typeof(php::TYPE::ARRAY)) {
                 php::array limits = params[3];
-                if (limits.size() > 0)
-                    cmd.set("skip", limits[0].to_integer());
-                if (limits.size() > 1)
-                    cmd.set("limit", limits[1].to_integer());
+                if (limits.size() > 0) cmd.set("skip", limits[0].to_integer());
+                if (limits.size() > 1) cmd.set("limit", limits[1].to_integer());
             }
         }
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
     }
-    php::value collection::find_one(php::parameters &params)
-    {
+
+    php::value collection::find_one(php::parameters &params) {
         php::array cmd(8);
         cmd.set("find", name_);
         cmd.set("filter", params[0]);
         if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY))
-        {
             cmd.set("sort", params[1]);
-        }
+
         cmd.set("limit", 1);
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
@@ -275,9 +222,7 @@ namespace flame::mongodb
         cmd.set("projection", project);
         cmd.set("filter", params[0]);
         if (params.size() > 2 && params[2].typeof(php::TYPE::ARRAY))
-        {
             cmd.set("sort", params[2]);
-        }
         cmd.set("limit", 1);
 
         coroutine_handler ch{coroutine::current};
@@ -285,14 +230,11 @@ namespace flame::mongodb
         php::object cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
         assert(cs.instanceof (php::class_entry<cursor>::entry()));
         php::array row = cs.call("fetch_row");
-        if(row.empty()) {
-            return nullptr;
-        }else{
-            return row.get(field);
-        }
+        if (row.empty()) return nullptr;
+        else return row.get(field);
     }
-    php::value collection::count(php::parameters &params)
-    {
+
+    php::value collection::count(php::parameters &params) {
         php::array cmd(8);
         cmd.set("count", name_);
         cmd.set("query", params[0]);
@@ -302,29 +244,30 @@ namespace flame::mongodb
         php::array cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
         return cs.get("n");
     }
-    php::value collection::aggregate(php::parameters &params)
-    {
+
+    php::value collection::aggregate(php::parameters &params) {
         php::array cmd(8);
         cmd.set("aggregate", name_);
         php::array pipeline = params[0];
-        if (!pipeline.exists(0))
-            throw php::exception(zend_ce_type_error, "aggregate pipeline must be a array of stages (array)");
+        if (!pipeline.exists(0)) throw php::exception(zend_ce_type_error
+            , "Failed to aggregate: 'pipeline' typeof array required"
+            , -1);
         cmd.set("pipeline", pipeline);
         cmd.set("cursor", php::object(php::CLASS(zend_standard_class_def)));
         if(params.size() > 1) {
             php::array options = params[1];
-            for(auto i=options.begin(); i!=options.end(); ++i) {
+            for(auto i=options.begin(); i!=options.end(); ++i)
                 cmd.set(i->first.to_string(), i->second);
-            }
         }
         int execute_type = _connection_base::COMMAND_READ;
-        if(params.size() > 2) {
+        if(params.size() > 2)
             execute_type = params[2].to_integer();
-        }
+        
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, execute_type, ch);
     }
+
     php::value collection::find_and_delete(php::parameters& params) {
         php::array cmd(8);
         cmd.set("query", params[0]);
@@ -333,9 +276,7 @@ namespace flame::mongodb
             php::value f = params[1];
             if(f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
         }
-        if(params.size() > 2) {
-            cmd.set("upsert", params[2].to_boolean());
-        }
+        if(params.size() > 2) cmd.set("upsert", params[2].to_boolean());
         if(params.size() > 3) {
             php::value f = params[3];
             if(f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
@@ -344,6 +285,7 @@ namespace flame::mongodb
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ_WRITE, ch);
     }
+
     php::value collection::find_and_update(php::parameters& params) {
         php::array cmd(8);
         cmd.set("query", params[0]);
@@ -355,16 +297,15 @@ namespace flame::mongodb
             php::value f = params[2];
             if(f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
         }
-        if(params.size() > 3) {
+        if(params.size() > 3)
             cmd.set("upsert", params[3].to_boolean());
-        }
         if(params.size() > 4) {
             php::value f = params[4];
             if(f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
         }
-        if(params.size() > 5) {
+        if(params.size() > 5)
             cmd.set("new", params[5].to_boolean());
-        }
+        
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ_WRITE, ch);
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index d976ae3..d8bf83b 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -3,12 +3,9 @@
 #include "_connection_lock.h"
 #include "result.h"
 
-namespace flame::mysql
-{
-    void _connection_base::escape(std::shared_ptr<MYSQL> conn, php::buffer &b, const php::value &v, char quote)
-    {
-        switch (Z_TYPE_P(static_cast<zval *>(v)))
-        {
+namespace flame::mysql {
+    void _connection_base::escape(std::shared_ptr<MYSQL> conn, php::buffer &b, const php::value &v, char quote) {
+        switch (Z_TYPE_P(static_cast<zval *>(v))) {
         case IS_NULL:
             b.append("NULL", 4);
             break;
@@ -25,17 +22,13 @@ namespace flame::mysql
         // 	b.append(str);
         // 	break;
         // }
-        case IS_STRING:
-        {
+        case IS_STRING: {
             php::string str = v;
             // 支持字段名 aaa.bbb 进行 ESCAPE 变为 `aaa`.`bbb`
-            if (quote == '`')
-            {
+            if (quote == '`') {
                 const char *s = str.c_str(), *c, *e = str.c_str() + str.size();
-                for (c = s; c < e; ++c)
-                {
-                    if (*c == '.')
-                    {
+                for (c = s; c < e; ++c) {
+                    if (*c == '.') {
                         escape(conn, b, php::string(s, c - s), quote);
                         b.push_back('.');
                         escape(conn, b, php::string(c + 1, e - c - 1), quote);
@@ -51,15 +44,12 @@ namespace flame::mysql
             b.commit(n);
             break;
         }
-        case IS_ARRAY:
-        {
+        case IS_ARRAY: {
             php::array arr = v;
             int index = 0;
             b.push_back('(');
-            for (auto i = arr.begin(); i != arr.end(); ++i)
-            {
-                if (++index > 1)
-                    b.push_back(',');
+            for (auto i = arr.begin(); i != arr.end(); ++i) {
+                if (++index > 1) b.push_back(',');
                 escape(conn, b, i->second, quote);
             }
             b.push_back(')');
@@ -68,17 +58,13 @@ namespace flame::mysql
         case IS_OBJECT: {
             php::object obj = v;
             php::string str;
-            if(obj.instanceof(php_date_get_date_ce())) {
-                // DateTime 类型的 SQL 拼接
+            if (obj.instanceof(php_date_get_date_ce()))  // DateTime 类型的 SQL 拼接
                 str = obj.call("format", {php::string("Y-m-d H:i:s")});
-            }else{
-                str = obj.to_string();
-            }
+            else str = obj.to_string();
             escape(conn, b, str, quote);
             break;
         }
-        default:
-        {
+        default: {
             php::string str = v;
             str.to_string();
             escape(conn, b, str, quote);
@@ -106,9 +92,9 @@ ESCAPE_FINISHED:;
         last_query_ = sql;
         if(err != 0) {
             int err = mysql_errno(conn.get());
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to query MySQL server: (%1%) %2%") % err % mysql_error(conn.get())).str(),
-                                 err);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to query MySQL server: %s") % mysql_error(conn.get())).str()
+                , err);
         }
         if(rst) { // 存在结果集
             php::object obj(php::class_entry<result>::entry());
@@ -127,30 +113,29 @@ ESCAPE_FINISHED:;
             return std::move(data);
         }
     }
+
     const std::string& _connection_base::last_query() {
         return last_query_;
     }
-    php::array _connection_base::fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch)
-    {
+
+    php::array _connection_base::fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst
+        , MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch) {
+
         MYSQL_ROW row;
         unsigned long* len;
         boost::asio::post(gcontroller->context_y, [&rst, &ch, &row, &len] () {
             row = mysql_fetch_row(rst.get());
-            if(row) {
-                len = mysql_fetch_lengths(rst.get());
-            }
+            if(row) len = mysql_fetch_lengths(rst.get());
             ch.resume();
         });
         ch.suspend();
-        if(!row) {
+        if (!row) {
             int err = mysql_errno(conn.get());
-            if(err != 0) {
-                throw php::exception(zend_ce_error,
-                                     (boost::format("failed to fetch MySQL row: (%1%) %2%") % err % mysql_error(conn.get())).str(),
-                                     err);
-            }else{
-                return nullptr;
-            }
+            if (err != 0) 
+                throw php::exception(zend_ce_error
+                    , (boost::format("Failed to fetch MySQL row: %s") % mysql_error(conn.get())).str()
+                    , err);
+            else return nullptr;
         }
         php::array php_row {std::size_t(n)};
         for(int i=0;i<n;++i) {
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
index 88c4ff4..04fae10 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/mysql/_connection_lock.cpp
@@ -7,22 +7,18 @@ namespace flame::mysql
 {
 
     _connection_lock::_connection_lock(std::shared_ptr<MYSQL> c)
-        : conn_(c)
-    {
+    : conn_(c) {
         
     }
 
-    _connection_lock::~_connection_lock()
-    {
+    _connection_lock::~_connection_lock() {
     }
 
-    std::shared_ptr<MYSQL> _connection_lock::acquire(coroutine_handler &ch)
-    {
+    std::shared_ptr<MYSQL> _connection_lock::acquire(coroutine_handler &ch) {
         return conn_;
     }
 
-    void _connection_lock::begin_tx(coroutine_handler &ch)
-    {
+    void _connection_lock::begin_tx(coroutine_handler &ch) {
         int err = 0;
         boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
             err = mysql_real_query(conn_.get(), "START TRANSACTION", 17);
@@ -31,43 +27,39 @@ namespace flame::mysql
         ch.suspend();
         if(err != 0) {
             err = mysql_errno(conn_.get());
-            throw php::exception(zend_ce_error,
-                                 (boost::format("failed to begin transaction: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
-                                 err);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to start transaction: %s") % mysql_error(conn_.get())).str()
+                , err);
         }
-        
     }
 
-    void _connection_lock::commit(coroutine_handler &ch)
-    {
+    void _connection_lock::commit(coroutine_handler &ch) {
         int err = 0;
         boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
             err = mysql_real_query(conn_.get(), "COMMIT", 6);
             ch.resume();
         });
         ch.suspend();
-        if (err != 0)
-        {
+        if (err != 0) {
             err = mysql_errno(conn_.get());
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to commit MySQL tx: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
-                                 err);
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to commit transaction: %s") % mysql_error(conn_.get())).str()
+                , err);
         }
     }
-    void _connection_lock::rollback(coroutine_handler &ch) 
-    {
+
+    void _connection_lock::rollback(coroutine_handler &ch) {
         int err = 0;
         boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
             err = mysql_real_query(conn_.get(), "ROLLBACK", 8);
             ch.resume();
         });
         ch.suspend();
-        if (err != 0)
-        {
+        if (err != 0) {
             err = mysql_errno(conn_.get());
-            throw php::exception(zend_ce_error,
-                                 (boost::format("failed to rollback MySQL tx: (%1%) %2%") % err % mysql_error(conn_.get())).str(),
-                                 err);
+            throw php::exception(zend_ce_error
+                , (boost::format("failed to rollback transaction: %s") % mysql_error(conn_.get())).str()
+                , err);
         }
     }
 
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 9b286d6..f517c71 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -4,136 +4,122 @@
 namespace flame::mysql
 {
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
-        , flag_(FLAG_UNKNOWN)
-    {
+    : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+    , flag_(FLAG_UNKNOWN) {
 
     }
 
-    _connection_pool::~_connection_pool()
-    {
-        while (!conn_.empty())
-        {
+    _connection_pool::~_connection_pool() {
+        while (!conn_.empty()) {
             mysql_close(conn_.front().conn);
             conn_.pop_front();
         }
     }
 
-    std::shared_ptr<MYSQL> _connection_pool::acquire(coroutine_handler& ch)
-    {
+    std::shared_ptr<MYSQL> _connection_pool::acquire(coroutine_handler& ch) {
         std::shared_ptr<MYSQL> conn;
+        int         errnum = 0;
+        std::string errmsg;
         // 提交异步任务
-        boost::asio::post(guard_, [this, &conn, &ch] () {
+        boost::asio::post(guard_, [this, &errnum, &errmsg, &conn, &ch] () {
             // 设置对应的回调, 在获取连接后恢复协程
-            await_.push_back([&conn, &ch] (std::shared_ptr<MYSQL> c) {
-                conn = c;
+            await_.push_back([&conn, &ch] (std::shared_ptr<MYSQL> cc) {
+                conn = cc;
                 // RESUME 需要在主线程进行
                 ch.resume();
             });
             auto now = std::chrono::steady_clock::now();
             while (!conn_.empty()) {
-                if (now - conn_.front().ttl < std::chrono::seconds(15) || mysql_ping(conn_.front().conn) == 0)
-                { // 可用连接
-                    MYSQL *c = conn_.front().conn;
+                if (now - conn_.front().ttl < std::chrono::seconds(15)
+                    || mysql_ping(conn_.front().conn) == 0) {  // 可用连接
+                    
+                    MYSQL* c = conn_.front().conn;
                     conn_.pop_front();
                     release(c);
                     return;
-                }
-                else
-                { // 连接已丢失，回收资源
+                } else { // 连接已丢失，回收资源
                     mysql_close(conn_.front().conn);
                     conn_.pop_front();
                     --size_;
                 }
             }
             if (size_ >= max_) return; // 已建立了足够多的连接, 需要等待已分配连接释放
+            MYSQL* c = mysql_init(nullptr);
+            init_options(c);
 
-            MYSQL* c = create();
-            if(c == nullptr) {
-                await_.pop_back();
-                ch.resume();
-            }else{
+            if(mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
                 ++size_; // 当前还存在的连接数量
                 release(c);
+            }else{
+                errnum = mysql_errno(c);
+                errmsg = mysql_error(c);
+                mysql_close(c);
+                await_.pop_back();
+                ch.resume();
             }
         });
         // 暂停, 等待连接获取(异步任务)
         ch.suspend();
-        if(!conn) {
-            throw php::exception(zend_ce_exception, "failed to connect to MySQL server", -1);
+        if (!conn) {
+            if (errnum)
+                throw php::exception(zend_ce_exception
+                    , (boost::format("Failed to connect to MySQL server: %s") % errmsg).str()
+                    , errnum);
+            else
+                throw php::exception(zend_ce_exception, "Failed to connect to MySQL server", 0);
         }
         // 恢复, 已经填充连接
         return conn;
     }
 
-    MYSQL* _connection_pool::create()
-    {
-        MYSQL* c = mysql_init(nullptr);
+    void _connection_pool::init_options(MYSQL* c) {
         // 这里的 CHARSET 设定会被 reset_connection 重置为系统值
         mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
         // 8.0.15 默认使用 caching_sha2_password 进行认证，底版本不支持
         mysql_options(c, MYSQL_DEFAULT_AUTH, "mysql_native_password");
         // SSL 参数
         unsigned int ssl = SSL_MODE_DISABLED;
-        if(url_.query.count("ssl") > 0) {
+        if (url_.query.count("ssl") > 0) {
             std::string sstr = url_.query["ssl"];
-            if(strncasecmp(sstr.c_str(), "preferred", 9) == 0) {
-                ssl = SSL_MODE_PREFERRED;
-            }else if(strncasecmp(sstr.c_str(), "required", 8) == 0) {
-                ssl = SSL_MODE_REQUIRED;
-            }else if(strncasecmp(sstr.c_str(), "verify_ca", 9) == 0) {
-                ssl = SSL_MODE_VERIFY_CA;
-            }else if(strncasecmp(sstr.c_str(), "verify_identity", 15) == 0) {
-                ssl = SSL_MODE_VERIFY_IDENTITY;
-            }
+            if (strncasecmp(sstr.c_str(), "preferred", 9) == 0) ssl = SSL_MODE_PREFERRED;
+            else if (strncasecmp(sstr.c_str(), "required", 8) == 0) ssl = SSL_MODE_REQUIRED;
+            else if (strncasecmp(sstr.c_str(), "verify_ca", 9) == 0) ssl = SSL_MODE_VERIFY_CA;
+            else if (strncasecmp(sstr.c_str(), "verify_identity", 15) == 0) ssl = SSL_MODE_VERIFY_IDENTITY;
         }
         mysql_options(c, MYSQL_OPT_SSL_MODE, &ssl);
         unsigned int timeout = 5; // 连接超时
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
-        if (!mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
-            mysql_close(c);
-            return nullptr;
-        }
-        return c;
     }
 
-    void _connection_pool::release(MYSQL *c)
-    {
-        if (await_.empty()) { // 无等待分配的请求
-            conn_.push_back({c, std::chrono::steady_clock::now()});
-        }else { // 立刻分配使用
+    void _connection_pool::release(MYSQL *c) {
+        // 无等待分配的请求, 保存连接(待后续分配)
+        if (await_.empty()) conn_.push_back({c, std::chrono::steady_clock::now()});
+        else { // 立刻分配使用
             // 每次连接复用前，需要清理状态;
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
-            if(flag_ == FLAG_UNKNOWN) {
+            if (flag_ == FLAG_UNKNOWN) {
                 query_version(c);
                 query_charset(c); // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
             }
-            if(flag_ & FLAG_REUSE_BY_RESET) {
-                mysql_reset_connection(c);
-            }else if(flag_ & FLAG_REUSE_BY_CUSER) {
-                mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
-            }else if(flag_ & FLAG_REUSE_BY_PROXY) {
-                // PROXY 已处理复用流程，此处无需处理
-            }
-            if(flag_ & FLAG_CHARSET_DIFFER) {
-                mysql_set_character_set(c, url_.query["charset"].c_str());
-            }
+            if (flag_ & FLAG_REUSE_BY_RESET) mysql_reset_connection(c);
+            else if (flag_ & FLAG_REUSE_BY_CUSER) mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+            else if (flag_ & FLAG_REUSE_BY_PROXY) ;// PROXY 已处理复用流程，此处无需处理
+            
+            if (flag_ & FLAG_CHARSET_DIFFER) mysql_set_character_set(c, url_.query["charset"].c_str());
             std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
             await_.pop_front();
             auto self = this->shared_from_this();
-            cb(
-                // 释放回调函数须持有当前对象引用 self
-                // (否则连接池可能先于连接归还被销毁)
-                std::shared_ptr<MYSQL>(c, [this, self](MYSQL *c) {
-                    boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
-                }));
+            // 释放回调函数须持有当前对象引用 self (否则连接池可能先于连接归还被销毁)
+            cb(std::shared_ptr<MYSQL>(c, [this, self](MYSQL *c) {
+                boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+            }));
         }
     }
 
     void _connection_pool::query_charset(MYSQL* c) {
         // 使用 mysql_get_charactor_set_name() 获取到的字符集与下述查询不同
-        if(0 == mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59)) {
+        if (0 == mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59)) {
             std::unique_ptr<MYSQL_RES, void (*)(MYSQL_RES*)> rst(mysql_store_result(c), mysql_free_result);
             if (!rst) return;
             MYSQL_ROW row = mysql_fetch_row(rst.get());
@@ -141,9 +127,7 @@ namespace flame::mysql
             unsigned long* len = mysql_fetch_lengths(rst.get());
             std::string charset(row[1], len[1]);
             flag_ |= charset == url_.query["charset"] ? FLAG_CHARSET_EQUAL : FLAG_CHARSET_DIFFER;
-        }else{ // 这里忽略了 charset 查询的错误
-            flag_ |= FLAG_CHARSET_DIFFER;
-        }
+        } else flag_ |= FLAG_CHARSET_DIFFER; // 这里忽略了 charset 查询的错误
     }
 
     void _connection_pool::query_version(MYSQL* c) {
@@ -162,16 +146,14 @@ namespace flame::mysql
         tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
             if(error) return; // 当前对象销毁时会发生对应的 abort 错误
             auto now = std::chrono::steady_clock::now();
+            // 超低水位，关闭不活跃或已丢失的连接
             for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;) {
-                // 超低水位，关闭不活跃或已丢失的连接
                 auto duration = now - (*i).ttl;
                 if (duration > std::chrono::seconds(60) || mysql_ping((*i).conn) != 0) {
                     mysql_close((*i).conn);
                     --size_;
                     i = conn_.erase(i);
-                } else {
-                    ++i;
-                } // 所有连接还活跃（即使超过低水位）
+                } else  ++i;
             }
             // 再次启动
             sweep();
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index 7b31501..5b26fcf 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -40,7 +40,7 @@ namespace flame::mysql
             FLAG_CHARSET_DIFFER = 0x20,
         };
 
-        MYSQL* create();
+        void init_options(MYSQL *c);
         void release(MYSQL *c);
         void query_charset(MYSQL* c);
         void query_version(MYSQL* c);
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 3099d09..83dfd14 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -9,20 +9,16 @@
 
 namespace flame::mysql {
 
-    void declare(php::extension_entry &ext)
-    {
+    void declare(php::extension_entry &ext) {
         gcontroller
-            ->on_init([] (const php::array& options)
-            {
+            ->on_init([] (const php::array& options) {
                 mysql_library_init(0, nullptr, nullptr);
             })
-            ->on_stop([] ()
-            {
+            ->on_stop([] () {
                 mysql_library_end();
             });
         ext
-            .function<connect>("flame\\mysql\\connect",
-            {
+            .function<connect>("flame\\mysql\\connect", {
                 {"url", php::TYPE::STRING},
             });
         client::declare(ext);
@@ -30,16 +26,11 @@ namespace flame::mysql {
         tx::declare(ext);
     }
 
-    php::value connect(php::parameters& params)
-    {
+    php::value connect(php::parameters& params) {
         url u(params[0]);
         if(u.port < 10) u.port = 3306;
-        if(!u.query.count("charset")) {
-            u.query["charset"] = "utf8mb4";
-        }
-        if(!u.query.count("ssl")) {
-            u.query["ssl"] = "disabled";
-        }
+        if(!u.query.count("charset")) u.query["charset"] = "utf8mb4";
+        if(!u.query.count("ssl")) u.query["ssl"] = "disabled";
 
         php::object obj(php::class_entry<client>::entry());
         client *ptr = static_cast<client *>(php::native(obj));
@@ -49,14 +40,9 @@ namespace flame::mysql {
         return std::move(obj);
 	}
     // 相等
-    static void where_eq(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
-        if (cond.typeof(php::TYPE::NULLABLE))
-        {
-            buf.append(" IS NULL", 8);
-        }
-        else
-        {
+    static void where_eq(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(" IS NULL", 8);
+        else {
             php::string str = cond;
             str.to_string();
             buf.push_back('=');
@@ -64,14 +50,9 @@ namespace flame::mysql {
         }
     }
     // 不等 {!=}
-    static void where_ne(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
-        if (cond.typeof(php::TYPE::NULLABLE))
-        {
-            buf.append(" IS NOT NULL", 12);
-        }
-        else
-        {
+    static void where_ne(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(" IS NOT NULL", 12);
+        else {
             php::string str = cond;
             str.to_string();
             buf.append("!=", 2);
@@ -79,14 +60,9 @@ namespace flame::mysql {
         }
     }
     // 大于 {>}
-    static void where_gt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
-        if (cond.typeof(php::TYPE::NULLABLE))
-        {
-            buf.append(">0", 2);
-        }
-        else
-        {
+    static void where_gt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(">0", 2);
+        else {
             php::string str = cond;
             str.to_string();
             buf.push_back('>');
@@ -94,14 +70,10 @@ namespace flame::mysql {
         }
     }
     // 小于 {<}
-    static void where_lt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
-        if (cond.typeof(php::TYPE::NULLABLE))
-        {
+    static void where_lt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.typeof(php::TYPE::NULLABLE)) {
             buf.append("<0", 2);
-        }
-        else
-        {
+        } else {
             php::string str = cond;
             str.to_string();
             buf.push_back('<');
@@ -109,14 +81,9 @@ namespace flame::mysql {
         }
     }
     // 大于等于 {>=}
-    static void where_gte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
-        if (cond.typeof(php::TYPE::NULLABLE))
-        {
-            buf.append(">=0", 3);
-        }
-        else
-        {
+    static void where_gte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)  {
+        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(">=0", 3);
+        else {
             php::string str = cond;
             str.to_string();
             buf.append(">=", 2);
@@ -124,14 +91,9 @@ namespace flame::mysql {
         }
     }
     // 小于等于 {<=}
-    static void where_lte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
-        if (cond.typeof(php::TYPE::NULLABLE))
-        {
-            buf.append("<=0", 3);
-        }
-        else
-        {
+    static void where_lte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.typeof(php::TYPE::NULLABLE)) buf.append("<=0", 3);
+        else {
             php::string str = cond;
             str.to_string();
             buf.append("<=", 2);
@@ -139,17 +101,12 @@ namespace flame::mysql {
         }
     }
     // 某个 IN 无对应符号
-    static void where_in(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond)
-    {
+    static void where_in(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
         assert(cond.typeof(php::TYPE::ARRAY) && "目标格式错误");
 
         buf.append(" IN (", 5);
-        for (auto i = cond.begin(); i != cond.end(); ++i)
-        {
-            if (static_cast<int>(i->first) > 0)
-            {
-                buf.push_back(',');
-            }
+        for (auto i = cond.begin(); i != cond.end(); ++i) {
+            if (static_cast<int>(i->first) > 0) buf.push_back(',');
             php::string v = i->second;
             v.to_string();
             _connection_base::escape(cc, buf, v);
@@ -157,362 +114,263 @@ namespace flame::mysql {
         buf.push_back(')');
     }
     // WITH IN RANGE
-    static void where_wir(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond)
-    {
+    static void where_wir(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
         assert(cond.typeof(php::TYPE::ARRAY) && "目标格式错误");
 
         buf.append(" BETWEEN ", 9);
         std::int64_t min = std::numeric_limits<std::int64_t>::max(), max = std::numeric_limits<std::int64_t>::min();
-        for (auto i = cond.begin(); i != cond.end(); ++i)
-        {
+        for (auto i = cond.begin(); i != cond.end(); ++i) {
             std::int64_t x = i->second.to_integer();
-            if (x > max)
-                max = x;
-            if (x < min)
-                min = x;
+            if (x > max) max = x;
+            if (x < min) min = x;
         }
         buf.append(std::to_string(min));
         buf.append(" AND ", 5);
         buf.append(std::to_string(max));
     }
     // OUT OF RANGE
-    static void where_oor(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond)
-    {
+    static void where_oor(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
         buf.append(" NOT", 4);
         where_wir(cc, buf, cond);
     }
     // LINK
-    static void where_lk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
+    static void where_lk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
         buf.append(" LIKE ", 6);
         _connection_base::escape(cc, buf, cond);
     }
     // NOT LIKE
-    static void where_nlk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)
-    {
+    static void where_nlk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
         buf.append(" NOT LIKE ", 10);
         _connection_base::escape(cc, buf, cond);
     }
-    static void where_ex(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond, const php::string &field, const php::string &separator)
-    {
-        if (cond.size() > 1)
-            buf.push_back('(');
+    static void where_ex(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond
+        , const php::string &field, const php::string &separator) {
+
+        if (cond.size() > 1) buf.push_back('(');
 
         int j = -1;
-        for (auto i = cond.begin(); i != cond.end(); ++i)
-        {
-            if (++j > 0)
-                buf.append(separator);
+        for (auto i = cond.begin(); i != cond.end(); ++i) {
+            if (++j > 0) buf.append(separator);
 
-            if (i->first.typeof(php::TYPE::INTEGER))
-            {
-                if (i->second.typeof(php::TYPE::ARRAY))
-                {
-                    where_ex(cc, buf, i->second, i->first, " AND ");
-                }
+            if (i->first.typeof(php::TYPE::INTEGER))  {
+                if (i->second.typeof(php::TYPE::ARRAY)) where_ex(cc, buf, i->second, i->first, " AND ");
                 else
                 {
                     php::string str = i->second;
                     str.to_string();
                     buf.append(str);
                 }
-            }
-            else
-            {
+            } else {
                 php::string key = i->first;
-                if (key.c_str()[0] == '{')
-                { // OPERATOR (php::TYPE::STRING)
-                    if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0) || (key.size() == 3 && strncasecmp(key.c_str(), "{!}", 3) == 0))
-                    {
+                if (key.c_str()[0] == '{') { // OPERATOR (php::TYPE::STRING)
+                    if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0) 
+                        || (key.size() == 3 && strncasecmp(key.c_str(), "{!}", 3) == 0)) {
+
                         assert(i->second.typeof(php::TYPE::ARRAY));
                         buf.append(" NOT ", 5);
                         where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
-                    }
-                    else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0 || strncasecmp(key.c_str(), "{||}", 4) == 0))
-                    {
+                    } else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0
+                        || strncasecmp(key.c_str(), "{||}", 4) == 0)) {
+
                         assert(i->second.typeof(php::TYPE::ARRAY));
                         where_ex(cc, buf, i->second, php::string(nullptr), " OR ");
-                    }
-                    else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0) || (key.size() == 4 && strncasecmp(key.c_str(), "{&&}", 4) == 0))
-                    {
+                    } else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0)
+                        || (key.size() == 4 && strncasecmp(key.c_str(), "{&&}", 4) == 0)) {
                         assert(i->second.typeof(php::TYPE::ARRAY));
                         where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
-                    }
-                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0)
-                    {
+                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_ne(cc, buf, i->second);
-                    }
-                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{>}", 3) == 0)
-                    {
+                    } else if (key.size() == 3 && strncasecmp(key.c_str(), "{>}", 3) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_gt(cc, buf, i->second);
-                    }
-                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{<}", 2) == 0)
-                    {
+                    } else if (key.size() == 3 && strncasecmp(key.c_str(), "{<}", 2) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_lt(cc, buf, i->second);
-                    }
-                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{>=}", 4) == 0)
-                    {
+                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{>=}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_gte(cc, buf, i->second);
-                    }
-                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<=}", 4) == 0)
-                    {
+                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{<=}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_lte(cc, buf, i->second);
-                    }
-                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<>}", 4) == 0)
-                    {
+                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{<>}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_wir(cc, buf, i->second);
-                    }
-                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{><}", 4) == 0)
-                    {
+                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{><}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_oor(cc, buf, i->second);
-                    }
-                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{~}", 3) == 0)
-                    {
+                    } else if (key.size() == 3 && strncasecmp(key.c_str(), "{~}", 3) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_lk(cc, buf, i->second);
-                    }
-                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!~}", 4) == 0)
-                    {
+                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{!~}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_nlk(cc, buf, i->second);
                     }
-                }
-                else
-                { // php::TYPE::STRING
-                    if (i->second.typeof(php::TYPE::ARRAY))
-                    {
+                } else { // php::TYPE::STRING
+                    if (i->second.typeof(php::TYPE::ARRAY)) {
                         php::array cond = i->second;
-                        if (cond.exists(0))
-                        {
+                        if (cond.exists(0)) {
                             _connection_base::escape(cc, buf, key, '`');
                             buf.push_back(' ');
                             where_in(cc, buf, i->second);
-                        }
-                        else
-                        {
-                            where_ex(cc, buf, cond, key, " AND ");
-                        }
-                    }
-                    else
-                    {
+                        } else where_ex(cc, buf, cond, key, " AND ");
+                    } else {
                         _connection_base::escape(cc, buf, key, '`');
                         where_eq(cc, buf, i->second);
                     }
                 }
             }
         }
-        if (cond.size() > 1)
-            buf.push_back(')');
+        if (cond.size() > 1) buf.push_back(')');
     }
-    void build_where(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data)
-    {
+
+    void build_where(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
         buf.append(" WHERE ", 7);
-        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER))
-        {
+        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER)) {
             buf.push_back(' ');
             buf.append(data);
-        }
-        else if (data.typeof(php::TYPE::ARRAY))
-        {
+        } else if (data.typeof(php::TYPE::ARRAY)) {
             where_ex(cc, buf, data, php::string(0), " AND ");
-        }
-        else
-        {
-            buf.push_back('1');
-        }
+        } else buf.push_back('1');
     }
-    void build_order(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data)
-    {
-        if (data.typeof(php::TYPE::STRING))
-        {
+
+    void build_order(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
+        if (data.typeof(php::TYPE::STRING)) {
             buf.append(" ORDER BY ", 10);
             buf.append(data);
-        }
-        else if (data.typeof(php::TYPE::ARRAY))
-        {
+        } else if (data.typeof(php::TYPE::ARRAY)) {
             buf.append(" ORDER BY", 9);
             php::array order = data;
             int j = -1;
-            for (auto i = order.begin(); i != order.end(); ++i)
-            {
-                if (++j > 0)
-                    buf.push_back(',');
-                if (i->first.typeof(php::TYPE::INTEGER))
-                {
-                    if (i->second.typeof(php::TYPE::STRING))
-                    {
+            for (auto i = order.begin(); i != order.end(); ++i) {
+                if (++j > 0) buf.push_back(',');
+                if (i->first.typeof(php::TYPE::INTEGER)) {
+                    if (i->second.typeof(php::TYPE::STRING)) {
                         buf.push_back(' ');
                         buf.append(i->second);
                     }
-                }
-                else
-                {
+                } else {
                     buf.push_back(' ');
                     _connection_base::escape(cc, buf, i->first, '`');
-                    if (i->second.typeof(php::TYPE::STRING))
-                    {
+                    if (i->second.typeof(php::TYPE::STRING)) {
                         buf.push_back(' ');
                         buf.append(i->second);
-                    }
-                    else if (i->second.typeof(php::TYPE::YES) || (i->second.typeof(php::TYPE::INTEGER) && static_cast<int>(i->second) >= 0))
-                    {
+                    } else if (i->second.typeof(php::TYPE::YES)
+                        || (i->second.typeof(php::TYPE::INTEGER) && static_cast<int>(i->second) >= 0))
                         buf.append(" ASC", 4);
-                    }
-                    else
-                    {
-                        buf.append(" DESC", 5);
-                    }
+                    else buf.append(" DESC", 5);
                 }
             }
-        }
-        else
-        {
-            // throw php::exception(zend_ce_type_error, "failed to build 'ORDER BY' clause: unsupported type");
-        }
+        } else ; // throw php::exception(zend_ce_type_error, "Failed to build 'ORDER BY' clause: unsupported type specified for 'order'", -1);
     }
-    void build_limit(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data)
-    {
-        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER))
-        {
+
+    void build_limit(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
+        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER)) {
             buf.append(" LIMIT ", 7);
             buf.append(data);
-        }
-        else if (data.typeof(php::TYPE::ARRAY))
-        {
+        } else if (data.typeof(php::TYPE::ARRAY)) {
             buf.append(" LIMIT", 6);
             php::array limit = data;
             int j = -1;
-            for (auto i = limit.begin(); i != limit.end(); ++i)
-            {
-                if (++j > 0)
-                    buf.push_back(',');
+            for (auto i = limit.begin(); i != limit.end(); ++i) {
+                if (++j > 0) buf.push_back(',');
                 buf.push_back(' ');
                 buf.append(i->second);
-                if (j > 0)
-                    break;
+                if (j > 0) break;
             }
-        }
-        else
-        {
-            throw php::exception(zend_ce_type_error, "failed to build 'LIMIT' clause: unsupported type specified for `limit`");
-        }
+        } else throw php::exception(zend_ce_type_error
+            , "Failed to build 'LIMIT' clause: unsupported type specified for `limit`"
+            , -1);
     }
-    static void insert_row(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &row)
-    {
+
+    static void insert_row(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &row) {
         int j = -1;
-        for (auto i = row.begin(); i != row.end(); ++i)
-        {
-            if (++j > 0)
-                buf.append(", ", 2);
+        for (auto i = row.begin(); i != row.end(); ++i)  {
+            if (++j > 0) buf.append(", ", 2);
             _connection_base::escape(cc, buf, i->second);
         }
     }
-    void build_insert(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
-    {
+
+    void build_insert(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
         buf.append("INSERT INTO ", 12);
         // 表名
         _connection_base::escape(cc, buf, params[0], '`');
         // 字段
         buf.push_back('(');
         php::array rows = params[1], row;
-        if (rows.exists(0))
-        {
+        if (rows.exists(0)) {
             row = rows.get(0);
             assert(!row.exists(0) && "二级行无字段");
-        }
-        else
-        {
+        } else {
             row = rows;
             rows = nullptr;
         }
         assert(row.typeof(php::TYPE::ARRAY) && "行非数组");
         int j = -1;
-        for (auto i = row.begin(); i != row.end(); ++i)
-        {
-            if (++j > 0)
-                buf.append(", ", 2);
+        for (auto i = row.begin(); i != row.end(); ++i) {
+            if (++j > 0) buf.append(", ", 2);
             _connection_base::escape(cc, buf, i->first, '`');
         }
         // 数据
         buf.append(") VALUES", 8);
-        if (rows.typeof(php::TYPE::ARRAY))
-        {
+        if (rows.typeof(php::TYPE::ARRAY)) {
             buf.push_back('(');
             int j = -1;
-            for (auto i = rows.begin(); i != rows.end(); ++i)
-            {
-                if (++j > 0)
-                    buf.append("), (", 4);
+            for (auto i = rows.begin(); i != rows.end(); ++i) {
+                if (++j > 0) buf.append("), (", 4);
                 insert_row(cc, buf, i->second);
             }
             buf.push_back(')');
-        }
-        else
-        {
+        } else {
             buf.push_back('(');
             insert_row(cc, buf, row);
             buf.push_back(')');
         }
     }
-    void build_delete(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
-    {
+
+    void build_delete(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
         buf.append("DELETE FROM ", 12);
         // 表名
         _connection_base::escape(cc, buf, params[0], '`');
         // 条件
         build_where(cc, buf, params[1]);
         // 排序
-        if (params.size() > 2)
-            build_order(cc, buf, params[2]);
+        if (params.size() > 2) build_order(cc, buf, params[2]);
         // 限制
-        if (params.size() > 3)
-            build_limit(cc, buf, params[3]);
+        if (params.size() > 3) build_limit(cc, buf, params[3]);
     }
-    void build_update(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
-    {
+
+    void build_update(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
         buf.append("UPDATE ", 7);
         // 表名
         _connection_base::escape(cc, buf, params[0], '`');
         // 数据
         buf.append(" SET", 4);
         php::array data = params[2];
-        if (data.typeof(php::TYPE::STRING))
-        {
+        if (data.typeof(php::TYPE::STRING)) {
             buf.push_back(' ');
             buf.append(data);
-        }
-        else if (data.typeof(php::TYPE::ARRAY))
-        {
+        } else if (data.typeof(php::TYPE::ARRAY)) {
             int j = -1;
-            for (auto i = data.begin(); i != data.end(); ++i)
-            {
-                if (++j > 0)
-                    buf.push_back(',');
+            for (auto i = data.begin(); i != data.end(); ++i) {
+                if (++j > 0) buf.push_back(',');
                 buf.push_back(' ');
                 _connection_base::escape(cc, buf, i->first, '`');
                 buf.push_back('=');
                 _connection_base::escape(cc, buf, i->second);
             }
-        }
-        else throw php::exception(zend_ce_type_error, "failed to build 'UPDATE' clause: unsupported type specified for `update`");
+        }else throw php::exception(zend_ce_type_error
+            , "Failed to build 'UPDATE' clause: unsupported type specified for `update`"
+            , -1);
         // 条件
         build_where(cc, buf, params[1]);
         // 排序
-        if (params.size() > 3)
-            build_order(cc, buf, params[3]);
+        if (params.size() > 3) build_order(cc, buf, params[3]);
         // 限制
-        if (params.size() > 4)
-            build_limit(cc, buf, params[4]);
+        if (params.size() > 4) build_limit(cc, buf, params[4]);
     }
-    void build_select(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
-    {
+
+    void build_select(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
         buf.append("SELECT ", 7);
         // 字段
         php::value fields = params[1];
@@ -520,16 +378,10 @@ namespace flame::mysql {
         else if (fields.typeof(php::TYPE::ARRAY)) {
             php::array a = fields;
             int j = -1;
-            for (auto i = a.begin(); i != a.end(); ++i)
-            {
-                if (++j > 0)
-                    buf.append(", ", 2);
-                if (i->first.typeof(php::TYPE::INTEGER))
-                {
-                    _connection_base::escape(cc, buf, i->second, '`');
-                }
-                else
-                {
+            for (auto i = a.begin(); i != a.end(); ++i) {
+                if (++j > 0) buf.append(", ", 2);
+                if (i->first.typeof(php::TYPE::INTEGER)) _connection_base::escape(cc, buf, i->second, '`');
+                else {
                     buf.append(i->first);
                     buf.push_back('(');
                     _connection_base::escape(cc, buf, i->second, '`');
@@ -537,49 +389,47 @@ namespace flame::mysql {
                 }
             }
         }
-        else if(fields.typeof(php::TYPE::NULLABLE)) buf.push_back('*');
-        else throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type specified for `fields`");
+        else if (fields.typeof(php::TYPE::NULLABLE)) buf.push_back('*');
+        else throw php::exception(zend_ce_type_error
+            , "Failed to build 'SELECT' clause: unsupported type specified for `fields`"
+            , -1);
         // 表名
         buf.append(" FROM ", 6);
         _connection_base::escape(cc, buf, params[0], '`');
         // 条件
-        if (params.size() > 2)
-            build_where(cc, buf, params[2]);
+        if (params.size() > 2) build_where(cc, buf, params[2]);
         // 排序
-        if (params.size() > 3)
-            build_order(cc, buf, params[3]);
+        if (params.size() > 3) build_order(cc, buf, params[3]);
         // 限制
-        if (params.size() > 4)
-            build_limit(cc, buf, params[4]);
+        if (params.size() > 4) build_limit(cc, buf, params[4]);
     }
-    void build_one(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
-    {
+
+    void build_one(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
         buf.append("SELECT * FROM ", 14);
         // 表名
         _connection_base::escape(cc, buf, params[0], '`');
         // 条件
-        if (params.size() > 1)
-            build_where(cc, buf, params[1]);
+        if (params.size() > 1) build_where(cc, buf, params[1]);
         // 排序
-        if (params.size() > 2)
-            build_order(cc, buf, params[2]);
+        if (params.size() > 2) build_order(cc, buf, params[2]);
         buf.append(" LIMIT 1", 8);
     }
-    void build_get(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params)
-    {
+
+    void build_get(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
         buf.append("SELECT ", 7);
         // 字段
         if (params[1].typeof(php::TYPE::STRING)) _connection_base::escape(cc, buf, params[1], '`');
-        else throw php::exception(zend_ce_type_error, "failed to build 'SELECT' clause: unsupported type specified for `field`");
+        else throw php::exception(zend_ce_type_error
+            , "Failed to build 'SELECT' clause: unsupported type specified for `field`"
+            , -1);
         // 表名
         buf.append(" FROM ", 6);
         _connection_base::escape(cc, buf, params[0], '`');
         // 条件
-        if (params.size() > 2)
-            build_where(cc, buf, params[2]);
+        if (params.size() > 2) build_where(cc, buf, params[2]);
         // 排序
-        if (params.size() > 3)
-            build_order(cc, buf, params[3]);
+        if (params.size() > 3) build_order(cc, buf, params[3]);
         buf.append(" LIMIT 1", 8);
     }
+
 }
diff --git a/src/os/os.cpp b/src/os/os.cpp
index 4badf99..721723b 100644
--- a/src/os/os.cpp
+++ b/src/os/os.cpp
@@ -3,20 +3,16 @@
 #include "os.h"
 #include "process.h"
 
-namespace flame::os
-{
-    void declare(php::extension_entry &ext)
-    {
+namespace flame::os {
+    void declare(php::extension_entry &ext) {
         ext
             .function<interfaces>("flame\\os\\interfaces")
-            .function<spawn>("flame\\os\\spawn",
-            {
+            .function<spawn>("flame\\os\\spawn", {
                 {"executable", php::TYPE::STRING},
                 {"arguments", php::TYPE::ARRAY, false, true},
                 {"options", php::TYPE::ARRAY, false, true},
             })
-            .function<exec>("flame\\os\\exec",
-            {
+            .function<exec>("flame\\os\\exec", {
                 {"executable", php::TYPE::STRING},
                 {"arguments", php::TYPE::ARRAY, false, true},
                 {"options", php::TYPE::ARRAY, false, true},
@@ -24,56 +20,44 @@ namespace flame::os
 
         process::declare(ext);
     }
-    static void interface(php::array& data, const struct ifaddrs *addr)
-    {
+
+    static void interface(php::array& data, const struct ifaddrs *addr) {
         php::string name(addr->ifa_name);
         php::array info(2);
         info.set("family", addr->ifa_addr->sa_family == AF_INET ? "IPv4" : "IPv6");
         char address[NI_MAXHOST];
         if (getnameinfo(addr->ifa_addr,
-                        addr->ifa_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
-                        address, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST) != 0)
-        {
-            throw php::exception(zend_ce_error, gai_strerror(errno));
-        }
+                addr->ifa_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
+                address, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST) != 0)
+            throw php::exception(zend_ce_exception, gai_strerror(errno), errno);
+        
         info.set("address", address);
-        if (data.exists(name))
-        {
+        if (data.exists(name)) {
             php::array iface = data.get(name);
             iface.set(iface.size(), info);
-        }
-        else
-        {
+        } else {
             php::array iface(2);
             iface.set(iface.size(), info);
             data.set(name, iface);
         }
     }
-    php::value interfaces(php::parameters &params)
-    {
+
+    php::value interfaces(php::parameters &params) {
         struct ifaddrs *addr;
-        if (getifaddrs(&addr) != 0)
-        {
-            throw php::exception(zend_ce_error, std::strerror(errno));
-        }
-        else if (!addr)
-        {
-            return php::array(0);
-        }
+        if (getifaddrs(&addr) != 0) throw php::exception(zend_ce_exception, std::strerror(errno), errno);
+        else if (!addr) return php::array(0);
+       
         php::array data(4);
         // 用 shared_ptr 作自动释放保证
         std::shared_ptr<struct ifaddrs> autofree(addr, freeifaddrs);
-        do
-        {
-            if (addr->ifa_addr->sa_family != AF_INET && addr->ifa_addr->sa_family != AF_INET6)
-                continue;
+        do {
+            if (addr->ifa_addr->sa_family != AF_INET && addr->ifa_addr->sa_family != AF_INET6) continue;
             interface(data, addr);
         } while ((addr = addr->ifa_next) != nullptr);
-
         return std::move(data);
     }
-    php::value spawn(php::parameters &params)
-    {
+
+    php::value spawn(php::parameters &params) {
         php::object proc(php::class_entry<process>::entry());
         process *proc_ = static_cast<process *>(php::native(proc));
         proc_->exit_ = false;
@@ -81,54 +65,40 @@ namespace flame::os
         auto env = boost::this_process::environment();
         std::string exec = params[0].to_string();
         if (exec[0] != '.' && exec[0] != '/')
-        {
             exec = boost::process::search_path(exec).native();
-        }
+        
         std::vector<std::string> argv;
         std::string cwdv = boost::filesystem::current_path().native();
-        if (params.size() > 1)
-        {
+        if (params.size() > 1) {
             php::array args = params[1];
             for (auto i = args.begin(); i != args.end(); ++i)
-            {
                 argv.push_back(i->second.to_string());
-            }
         }
-        if (params.size() > 2)
-        {
+        if (params.size() > 2) {
             php::array opts = params[2];
             php::array envs = opts.get("env");
-            if (envs.typeof(php::TYPE::ARRAY))
-            {
+            if (envs.typeof(php::TYPE::ARRAY)) {
                 for (auto i = envs.begin(); i != envs.end(); ++i)
-                {
                     env[i->first.to_string()] = i->second.to_string();
-                }
             }
             php::string cwds = opts.get("cwd");
-            if (cwds.typeof(php::TYPE::STRING))
-            {
-                cwdv = cwds.to_string();
-            }
+            if (cwds.typeof(php::TYPE::STRING)) cwdv = cwds.to_string();
         }
 
         boost::process::child c(exec, boost::process::args = argv, env, gcontroller->context_x,
-                boost::process::start_dir = cwdv,
-                boost::process::std_out > proc_->out_,
-                boost::process::std_err > proc_->err_,
-                boost::process::on_exit = [proc, proc_] (int exit_code, const std::error_code &) {
-                    proc_->exit_ = true;
-                    if (proc_->ch_)
-                    {
-                        proc_->ch_.resume();
-                    }
-                });
+            boost::process::start_dir = cwdv,
+            boost::process::std_out > proc_->out_,
+            boost::process::std_err > proc_->err_,
+            boost::process::on_exit = [proc, proc_] (int exit_code, const std::error_code &) {
+                proc_->exit_ = true;
+                if (proc_->ch_) proc_->ch_.resume();
+            });
         proc.set("pid", c.id());
         proc_->c_ = std::move(c);
         return proc;
     }
-    php::value exec(php::parameters &params)
-    {
+
+    php::value exec(php::parameters &params) {
         php::object proc = spawn(params);
         process *proc_ = static_cast<process *>(php::native(proc));
         proc.call("wait");
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
index 6706367..04737f5 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/rabbitmq/_client.cpp
@@ -39,12 +39,13 @@ namespace flame::rabbitmq
 
         if(!error_.empty()) {
             std::string err = std::move(error_);
-            throw php::exception(zend_ce_exception,
-                (boost::format("failed to connect RabbitMQ server: %1%") % err).str(), -1);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to connect RabbitMQ server: %s") % err).str()
+                , -1);
         }
 
         auto i = u.query.find("prefetch");
-        if(i == u.query.end()) {
+        if (i == u.query.end()) {
             pf_ = 8;
         }else{
             pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 256);
@@ -97,10 +98,9 @@ namespace flame::rabbitmq
                 ch.resume(); // ----> 2
             });
         ch.suspend();
-        if(err) {
-            throw php::exception(zend_ce_error,
-                (boost::format("failed to consume RabbitMQ queue: %1%") % err).str(), -1);
-        }
+        if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to consume RabbitMQ queue: %s") % err).str()
+            , -1);
         consumer_ch_ = ch;
         ch.suspend();
         // 非关闭恢复执行, 消息对象一定存在
@@ -108,18 +108,21 @@ namespace flame::rabbitmq
             q.push(std::move(obj), ch);
             ch.suspend(); // 2 <----
         }
-        if(err) {
-            std::cerr << "[rabbitmq] error: " << err << std::endl;
-        }
+        if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to consume RabbitMQ queue: %s") % err).str()
+            , -1);
         q.close();
         consumer_ch_.reset();
     }
+
     void _client::confirm(std::uint64_t tag, coroutine_handler &ch) {
         chn_.ack(tag, 0);
     }
+
     void _client::reject(std::uint64_t tag, int flags, coroutine_handler &ch) {
         chn_.reject(tag, flags);
     }
+
     void _client::consumer_close() {
         const char* err = nullptr;
         chn_.cancel(consumer_tg_);
@@ -127,6 +130,7 @@ namespace flame::rabbitmq
         // 标记结束后，消费流程队将自行关闭
         if(consumer_ch_) consumer_ch_.resume();
     }
+
     void _client::consumer_close(coroutine_handler& ch) {
         const char* err = nullptr;
         chn_.cancel(consumer_tg_)
@@ -140,16 +144,19 @@ namespace flame::rabbitmq
         ch.suspend();
         consumer_tg_.clear();
         if(consumer_ch_) consumer_ch_.resume(); // ----> 2  标记结束后，消费流程队将自行关闭
-        if (err) throw php::exception(zend_ce_error,
-                                 (boost::format("failed to close RabbitMQ consumer: %1%") % err).str(), -1);
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to close RabbitMQ consumer: %s") % err).str()
+            , -1);
     }
+
     void _client::publish(const std::string &ex, const std::string &rk, const AMQP::Envelope &env, coroutine_handler& ch) {
         /*auto& defer = */chn_.publish(ex, rk, env, fl_);
         // producer_cb(defer, ch);
         if(!error_.empty()) {
             std::string err = std::move(error_);
-            throw php::exception(zend_ce_error,
-                (boost::format("failed to publish to RabbitMQ: %1%") % err).str(), -1);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to publish to RabbitMQ: %s") % err).str()
+                , -1);
         }
     }
 
@@ -173,14 +180,16 @@ namespace flame::rabbitmq
     //                         (boost::format("failed to publish RabbitMQ message: %1%") % err).str(), -1);
     //     }
     // }
+
     void _client::publish(const std::string &ex, const std::string &rk, const char *msg, size_t len, coroutine_handler& ch)
     {
         /*auto& defer = */chn_.publish(ex, rk, msg, len, fl_);
         // producer_cb(defer, ch);
         if(!error_.empty()) {
             std::string err = std::move(error_);
-            throw php::exception(zend_ce_error,
-                (boost::format("failed to publish to RabbitMQ: %1%") % err).str(), -1);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to publish RabbitMQ message: %s") % err).str()
+                , -1);
         }
     }
 }
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index 2ef683d..9b778af 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -4,40 +4,36 @@
 #include "message.h"
 #include "../time/time.h"
 
-namespace flame::rabbitmq
+namespace flame::rabbitmq 
 {
-    void consumer::declare(php::extension_entry &ext)
-    {
+    void consumer::declare(php::extension_entry &ext) {
         php::class_entry<consumer> class_consumer("flame\\rabbitmq\\consumer");
         class_consumer
             .method<&consumer::__construct>("__construct", {}, php::PRIVATE)
             .method<&consumer::__destruct>("__destruct")
-            .method<&consumer::run>("run",
-            {
+            .method<&consumer::run>("run", {
                 {"callable", php::TYPE::CALLABLE},
             })
-            .method<&consumer::confirm>("confirm",
-            {
+            .method<&consumer::confirm>("confirm", {
                 {"message", "flame\\rabbitmq\\message"}
             })
-            .method<&consumer::reject>("reject",
-            {
+            .method<&consumer::reject>("reject", {
                 {"message", "flame\\rabbitmq\\message"},
                 {"requeue", php::TYPE::BOOLEAN, false, true},
             })
             .method<&consumer::close>("close");
         ext.add(std::move(class_consumer));
     }
-    php::value consumer::__construct(php::parameters &params)
-    {
+
+    php::value consumer::__construct(php::parameters &params) {
         return nullptr;
     }
-    php::value consumer::__destruct(php::parameters& params)
-    {
+
+    php::value consumer::__destruct(php::parameters& params) {
         return nullptr;
     }
-    php::value consumer::run(php::parameters &params)
-    {
+
+    php::value consumer::run(php::parameters &params) {
         cb_ = params[0];
 
         coroutine_handler ch {coroutine::current};
@@ -45,47 +41,40 @@ namespace flame::rabbitmq
         coroutine_queue<php::object> q(cc_->pf_);
         // 启动若干协程, 然后进行"并行>"消费
         int count = (int)std::ceil(cc_->pf_ / 2.0);
-        for (int i = 0; i < count; ++i)
-        {
+        for (int i = 0; i < count; ++i) {
             // 启动协程开始消费
             coroutine::start(php::value([this, &q, &count, &ch, i] (php::parameters &params) -> php::value {
                 coroutine_handler cch {coroutine::current};
-                while(auto x = q.pop(cch))
-                {
-                    try
-                    {
+                while(auto x = q.pop(cch)) {
+                    try {
                         cb_.call( {x.value()} );
-                    }
-                    catch(const php::exception& ex)
-                    {
+                    } catch(const php::exception& ex) {
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
-                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
                     }
                 }
-                if(--count == 0)
-                {
-                    ch.resume(); // ----> 1
-                }
+                if(--count == 0)  ch.resume(); // ----> 1
                 return nullptr;
             }));
         }
         cc_->consume(qn_, q, ch);
         ch.suspend(); // 1 <----
         cb_ = nullptr;
-        if(cc_->has_error()) throw php::exception(zend_ce_exception, (boost::format("failed to consume RabbitMQ queue: %1%") % cc_->error()).str());
+        if(cc_->has_error()) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to consume RabbitMQ queue: %s") % cc_->error()).str()
+            , -1);
         return nullptr;
     }
-    php::value consumer::confirm(php::parameters &params)
-    {
+
+    php::value consumer::confirm(php::parameters &params) {
         php::object obj = params[0];
         message *ptr = static_cast<message *>(php::native(obj));
         coroutine_handler ch {coroutine::current};
         cc_->confirm(ptr->tag_, ch);
         return nullptr;
     }
-    php::value consumer::reject(php::parameters &params)
-    {
+
+    php::value consumer::reject(php::parameters &params) {
         php::object obj = params[0];
         message *ptr = static_cast<message *>(php::native(obj));
         int flags = 0;
@@ -94,8 +83,8 @@ namespace flame::rabbitmq
         cc_->reject(ptr->tag_, flags, ch);
         return nullptr;
     }
-    php::value consumer::close(php::parameters &params)
-    {
+
+    php::value consumer::close(php::parameters &params) {
         coroutine_handler ch {coroutine::current};
         cc_->consumer_close(ch);
         return nullptr;
diff --git a/src/rabbitmq/rabbitmq.cpp b/src/rabbitmq/rabbitmq.cpp
index 8a1dc16..609fa85 100644
--- a/src/rabbitmq/rabbitmq.cpp
+++ b/src/rabbitmq/rabbitmq.cpp
@@ -7,10 +7,8 @@
 #include "producer.h"
 #include "message.h"
 
-namespace flame::rabbitmq
-{
-    void declare(php::extension_entry &ext)
-    {
+namespace flame::rabbitmq {
+    void declare(php::extension_entry &ext) {
         ext
             .function<consume>("flame\\rabbitmq\\consume", {
                 {"address", php::TYPE::STRING},
@@ -25,8 +23,7 @@ namespace flame::rabbitmq
         message::declare(ext);
     }
 
-    php::value consume(php::parameters &params)
-    {
+    php::value consume(php::parameters &params) {
         coroutine_handler ch {coroutine::current};
         url u(params[0]);
         //  u.query.clear();
@@ -40,8 +37,8 @@ namespace flame::rabbitmq
         ptr->qn_ = static_cast<std::string>(params[1]);
         return std::move(obj);
     }
-    php::value produce(php::parameters &params)
-    {
+
+    php::value produce(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         url u(params[0]);
         // u.query.clear();
@@ -55,70 +52,32 @@ namespace flame::rabbitmq
         return std::move(obj);
     }
     // 仅支持一维数组
-    php::array table2array(const AMQP::Table& table)
-    {
+    php::array table2array(const AMQP::Table& table) {
         php::array data(table.size());
-        for (auto key : table.keys())
-        {
+        for (auto key : table.keys())  {
             const AMQP::Field &field = table.get(key);
-            if (field.isBoolean())
-            {
-                data.set(key, static_cast<uint8_t>(field) ? true : false);
-            }
-            else if (field.isInteger())
-            {
-                data.set(key, static_cast<int64_t>(field));
-            }
-            else if (field.isDecimal())
-            {
-                data.set(key, static_cast<double>(field));
-            }
-            else if (field.isString())
-            {
-                data.set(key, (const std::string &)field);
-            }
-            else if (field.typeID() == 'd')
-            {
-                data.set(key, static_cast<double>(field));
-            }
-            else if (field.typeID() == 'f')
-            {
-                data.set(key, static_cast<float>(field));
-            }
+            if (field.isBoolean()) data.set(key, static_cast<uint8_t>(field) ? true : false);
+            else if (field.isInteger()) data.set(key, static_cast<int64_t>(field));
+            else if (field.isDecimal()) data.set(key, static_cast<double>(field));
+            else if (field.isString()) data.set(key, (const std::string &)field);
+            else if (field.typeID() == 'd') data.set(key, static_cast<double>(field));
+            else if (field.typeID() == 'f') data.set(key, static_cast<float>(field));
             // TODO 记录警告信息?
         }
         return std::move(data);
     }
     // 仅支持一维数组
-    AMQP::Table array2table(const php::array& table)
-    {
+    AMQP::Table array2table(const php::array& table) {
         AMQP::Table data;
-        if (!table.typeof(php::TYPE::ARRAY) || table.empty())
-        {
-            return std::move(data);
-        }
-        for (auto i = table.begin(); i != table.end(); ++i)
-        {
-            if (i->second.typeof(php::TYPE::BOOLEAN))
-            {
-                data.set(i->first.to_string(), i->second.to_boolean());
-            }
-            else if (i->second.typeof(php::TYPE::INTEGER))
-            {
-                data.set(i->first.to_string(), i->second.to_integer());
-            }
-            else if (i->second.typeof(php::TYPE::STRING))
-            {
-                data.set(i->first.to_string(), i->second.to_string());
-            }
-            else if (i->second.typeof(php::TYPE::FLOAT))
-            {
-                data.set(i->first.to_string(), AMQP::Double{i->second.to_float()});
-            }
-            else
-            {
-                throw php::exception(zend_ce_type_error, "table conversion failed: unsupported type");
-            }
+        if (!table.typeof(php::TYPE::ARRAY) || table.empty()) return std::move(data);
+        for (auto i = table.begin(); i != table.end(); ++i) {
+            if (i->second.typeof(php::TYPE::BOOLEAN)) data.set(i->first.to_string(), i->second.to_boolean());
+            else if (i->second.typeof(php::TYPE::INTEGER)) data.set(i->first.to_string(), i->second.to_integer());
+            else if (i->second.typeof(php::TYPE::STRING)) data.set(i->first.to_string(), i->second.to_string());
+            else if (i->second.typeof(php::TYPE::FLOAT)) data.set(i->first.to_string(), AMQP::Double{i->second.to_float()});
+            else throw php::exception(zend_ce_type_error
+                , (boost::format("Failed to convert array: unsupported type '%s'") % i->second.typeof().name()).str()
+                , -1);
         }
         return std::move(data);
     }
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index e8c14c3..34fbddc 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -2,25 +2,24 @@
 #include "redis.h"
 #include "_connection_pool.h"
 
-namespace flame::redis
-{
+namespace flame::redis {
 
     _connection_pool::_connection_pool(url u)
-        : url_(std::move(u)), min_(1), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
-    {
+    : url_(std::move(u)), min_(1), max_(6)
+    , size_(0)
+    , guard_(gcontroller->context_y)
+    , tm_(gcontroller->context_y) {
         if(url_.port < 10) url_.port = 6379;
     }
-    _connection_pool::~_connection_pool()
-    {
-        while (!conn_.empty())
-        {
+
+    _connection_pool::~_connection_pool() {
+        while (!conn_.empty()) {
             redisFree(conn_.front().conn);
             conn_.pop_front();
         }
     }
 
-    std::shared_ptr<redisContext> _connection_pool::acquire(coroutine_handler &ch)
-    {
+    std::shared_ptr<redisContext> _connection_pool::acquire(coroutine_handler &ch) {
         std::shared_ptr<redisContext> conn;
         std::string err;
         // 提交异步任务
@@ -32,8 +31,7 @@ namespace flame::redis
                 ch.resume();
             });
             auto now = std::chrono::steady_clock::now();
-            while (!conn_.empty())
-            {
+            while (!conn_.empty())  {
                 if(now - conn_.front().ttl < std::chrono::seconds(15) || ping(conn_.front().conn)) {
                     // 可用连接
                     redisContext *c = conn_.front().conn;
@@ -46,8 +44,7 @@ namespace flame::redis
                     --size_;
                 }
             }
-            if (size_ >= max_)
-                return; // 已建立了足够多的连接, 需要等待已分配连接释放
+            if (size_ >= max_) return; // 已建立了足够多的连接, 需要等待已分配连接释放
 
             redisContext *c = create(err);
             if (c == nullptr) { // 创建新连接失败
@@ -60,14 +57,14 @@ namespace flame::redis
         });
         // 暂停, 等待连接获取(异步任务)
         ch.suspend();
-        if (!conn) throw php::exception(zend_ce_exception,
-            (boost::format("failed to connect to Redis server: %1%") % err).str());
+        if (!conn) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to connect to Redis server: %s") % err).str()
+            , -1);
         // 恢复, 已经填充连接
         return conn;
     }
 
-    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler& ch)
-    {
+    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler& ch) {
         std::string ss = format(name, argv);
         redisReply *rp = nullptr;
         boost::asio::post(gcontroller->context_y, [&rc, &ss, &ch, &rp]() {
@@ -80,20 +77,18 @@ namespace flame::redis
             php::value rv = reply2value(rp, argv, rt);
             freeReplyObject(rp);
             return std::move(rv);
-        }else if(rc->err) {
-            throw php::exception(zend_ce_exception, (boost::format("failed to exec Redis command: (%d) %s") % rc->err % rc->errstr).str(), rc->err);
-        }else{
-            return nullptr;
-        }
+        } else if(rc->err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to exec Redis command: %s") % rc->errstr).str()
+            , rc->err);
+        else return nullptr;
     }
-    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler& ch)
-    {
+
+    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler& ch) {
         php::array data {argv};
         return exec(rc, name, data, rt, ch);
     }
 
-    redisContext *_connection_pool::create(std::string& err)
-    {
+    redisContext *_connection_pool::create(std::string& err) {
         struct timeval tv {5, 0};
         redisContext* c = redisConnectWithTimeout(url_.host.c_str(), url_.port, tv);
         if(!c) {
@@ -124,65 +119,44 @@ namespace flame::redis
         }
         return c;
     }
-    void _connection_pool::release(redisContext *c)
-    {
-        if(c->err)
-        {
-            // 出现上下文异常的连接直接抛弃
-            --size_;
-        }
+
+    void _connection_pool::release(redisContext *c) {
+        if(c->err) --size_;  // 出现上下文异常的连接直接抛弃
         else if (await_.empty())
-        { // 无等待分配的请求
-            conn_.push_back({c, std::chrono::steady_clock::now()});
-        }
-        else
-        { // 立刻分配使用
+            conn_.push_back({c, std::chrono::steady_clock::now()}); // 无等待分配的请求
+        else { // 立刻分配使用
             std::function<void(std::shared_ptr<redisContext> c)> cb = await_.front();
             await_.pop_front();
             auto self = this->shared_from_this();
-            cb(
-                // 释放回调函数须持有当前对象引用 self
-                // (否则连接池可能先于连接归还被销毁)
-                std::shared_ptr<redisContext>(c, [this, self](redisContext *c)
-                {
-                    boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
-                }));
+            // 释放回调函数须持有当前对象引用 self (否则连接池可能先于连接归还被销毁)
+            cb(std::shared_ptr<redisContext>(c, [this, self](redisContext *c) {
+                boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+            }));
         }
     }
+
     bool _connection_pool::ping(redisContext* c) {
         std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "PING"), (void (*)(redisReply*))freeReplyObject);
-        if (!rp || rp->type == REDIS_REPLY_ERROR)
-        {
-            return false;
-        }
-        return true;
+        if (!rp || rp->type == REDIS_REPLY_ERROR) return false;
+        else return true;
     }
 
-    void _connection_pool::sweep()
-    {
+    void _connection_pool::sweep() {
         tm_.expires_from_now(std::chrono::seconds(60));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
         tm_.async_wait(boost::asio::bind_executor(guard_, [this](const boost::system::error_code &error) {
-            if (error)
-                return; // 当前对象销毁时会发生对应的 abort 错误
+            if (error) return; // 当前对象销毁时会发生对应的 abort 错误
             auto now = std::chrono::steady_clock::now();
-            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)
-            {
+            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)  {
                 // 超低水位，关闭不活跃连接
                 auto duration = now - (*i).ttl;
-                if (duration > std::chrono::seconds(60) || !ping((*i).conn))
-                {
+                if (duration > std::chrono::seconds(60) || !ping((*i).conn)) {
                     redisFree((*i).conn);
                     --size_;
                     i = conn_.erase(i);
-                }
-                else
-                {
-                    ++i;
-                } // 所有连接还活跃（即使超过低水位）
+                } else ++i;
             }
-            // 再次启动
-            sweep();
+            sweep(); // 再次启动
         }));
     }
 
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index e719f30..1c0dbdd 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -178,6 +178,6 @@ namespace flame::redis {
 		return std::move(obj);
 	}
 	php::value client::unimplement(php::parameters& params) {
-        throw php::exception(zend_ce_type_error, "This redis command is NOT yet implemented");
+        throw php::exception(zend_ce_error_exception, "This redis command is NOT yet implemented");
     }
 } // namespace flame::redis
diff --git a/src/redis/tx.cpp b/src/redis/tx.cpp
index 2a984bb..b618c07 100644
--- a/src/redis/tx.cpp
+++ b/src/redis/tx.cpp
@@ -170,6 +170,6 @@ namespace flame::redis {
         return this;
     }
 	php::value tx::unimplement(php::parameters& params) {
-        throw php::exception(zend_ce_type_error, "This redis command is NOT yet implemented");
+        throw php::exception(zend_ce_error_exception, "This redis command is NOT yet implemented");
     }
 } // namespace flame::redis
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index 5e06bba..1707a39 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -6,29 +6,27 @@
 #include "tcp.h"
 #include "socket.h"
 
-namespace flame::tcp
-{
-    void server::declare(php::extension_entry &ext)
-    {
+namespace flame::tcp {
+
+    void server::declare(php::extension_entry &ext) {
         php::class_entry<server> class_server("flame\\tcp\\server");
 		class_server
-			.method<&server::__construct>("__construct",
-            {
+			.method<&server::__construct>("__construct", {
 				{"bind", php::TYPE::STRING},
 			})
-			.method<&server::run>("run",
-            {
+			.method<&server::run>("run", {
 				{"cb", php::TYPE::CALLABLE},
 			})
 			.method<&server::close>("close");
 		ext.add(std::move(class_server));
     }
+
     server::server()
     : acceptor_(gcontroller->context_x)
-    , socket_(gcontroller->context_x)
-    {
+    , socket_(gcontroller->context_x) {
 
     }
+
     typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
 
     php::value server::__construct(php::parameters &params) {
@@ -36,7 +34,9 @@ namespace flame::tcp
         std::string str_addr = params[0];
         auto pair = udp::addr2pair(str_addr);
         if(pair.first.empty() || pair.second.empty())
-            throw php::exception(zend_ce_type_error, "failed to bind tcp socket: address malformed");
+            throw php::exception(zend_ce_error_exception
+                , "Failed to bind TCP socket: address malformed"
+                , -1);
         boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
         addr_.address(addr);
         addr_.port(std::atoi(pair.second.c_str()));
@@ -50,24 +50,27 @@ namespace flame::tcp
 
         boost::system::error_code err;
         acceptor_.bind(addr_, err);
-        if(err) throw php::exception(zend_ce_error,
-                (boost::format("failed to bind tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to bind TCP socket: %s") % err.message()).str()
+            , err.value());
 
         acceptor_.listen(boost::asio::socket_base::max_listen_connections, err);
-        if (err) throw php::exception(zend_ce_error,
-                (boost::format("failed to listen tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to listen TCP socket: %s") % err.message()).str()
+            , err.value());
         return nullptr;
     }
+
     php::value server::run(php::parameters &params) {
         cb_ = params[0];
         coroutine_handler ch {coroutine::current};
         boost::system::error_code err;
-        while(!closed_)
-        {
+        while(!closed_) {
             acceptor_.async_accept(socket_, ch[err]);
             if(err == boost::asio::error::operation_aborted) break;
-            else if(err) throw php::exception(zend_ce_error,
-                                     (boost::format("failed to accept tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+            else if(err) throw php::exception(zend_ce_error_exception
+                , (boost::format("Failed to accept TCP socket: %s") % err.message()).str()
+                , err.value());
             else{
                 php::object obj(php::class_entry<socket>::entry());
                 socket* ptr = static_cast<socket*>(php::native(obj));
@@ -82,8 +85,7 @@ namespace flame::tcp
                         cb.call({obj});
                     } catch(const php::exception& ex) {
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) "<< obj.call("__toString") << "\n";
-                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
+                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
                     }
                     return nullptr;
                 }));
@@ -92,8 +94,8 @@ namespace flame::tcp
         cb_ = nullptr;
         return nullptr;
     }
-    php::value server::close(php::parameters &params)
-    {
+    
+    php::value server::close(php::parameters &params) {
         if(!closed_) {
             closed_ = true;
             acceptor_.cancel();
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
index 1b6748a..f472d09 100644
--- a/src/tcp/socket.cpp
+++ b/src/tcp/socket.cpp
@@ -3,23 +3,22 @@
 #include "socket.h"
 
 namespace flame::tcp {
-	void socket::declare(php::extension_entry& ext)
-    {
+
+	void socket::declare(php::extension_entry& ext) {
 		php::class_entry<socket> class_socket("flame\\tcp\\socket");
 		class_socket
 			.property({"local_address", ""})
 			.property({"remote_address", ""})
-			.method<&socket::read>("read",
-            {
+			.method<&socket::read>("read", {
 				{"completion", php::TYPE::UNDEFINED, false, true}
 			})
-			.method<&socket::write>("write",
-            {
+			.method<&socket::write>("write", {
 				{"data", php::TYPE::STRING}
 			})
 			.method<&socket::close>("close");
 		ext.add(std::move(class_socket));
 	}
+
 	socket::socket()
 	: socket_(gcontroller->context_x) {
 
@@ -32,18 +31,15 @@ namespace flame::tcp {
 
 		boost::system::error_code err;
         std::size_t len;
-        if (params.size() == 0) // 1. 随意读取一段数据
-        {
+        if (params.size() == 0) {// 1. 随意读取一段数据
             len = socket_.async_read_some(boost::asio::buffer(buffer_.prepare(8192), 8192), ch[err]);
             buffer_.commit(len);
         }
-        else if (params[0].typeof(php::TYPE::STRING)) // 2. 读取到指定的结束符
-        {
+        else if (params[0].typeof(php::TYPE::STRING)) { // 2. 读取到指定的结束符
 			std::string delim = params[0];
             len = boost::asio::async_read_until(socket_, buffer_, delim, ch[err]);
         }
-        else if (params[0].typeof(php::TYPE::INTEGER)) // 3. 读取指定长度
-        {
+        else if (params[0].typeof(php::TYPE::INTEGER)) { // 3. 读取指定长度
 			std::size_t want = params[0].to_integer();
 			if(buffer_.size() >= want) {
                 len = want;
@@ -53,29 +49,23 @@ namespace flame::tcp {
             len = boost::asio::async_read(socket_, buffer_, boost::asio::transfer_exactly(want - buffer_.size()), ch[err]);
             len = want;
 		}
-        else // 未知读取方式
-        {
-            throw php::exception(zend_ce_error, "failed to read tcp socket: unknown completion");
-        }
+        else throw php::exception(zend_ce_type_error
+            , "Failed to read TCP socket: unknown completion type"
+            , -1); // 未知读取方式
 RETURN_DATA:
         // 数据返回
-        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof)
-        {
-            return nullptr;
-        }
-        else if (err)
-        {
-            throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to read tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
-        }
-        else
-        {
+        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to read TCP socket: %s") % err.message()).str()
+            , err.value());
+        else {
             php::string data(len);
             boost::asio::buffer_copy(boost::asio::buffer(data.data(), len), buffer_.data(), len);
             buffer_.consume(len);
             return std::move(data);
         }
     }
+
 	php::value socket::write(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
         // 使用下面锁保证不会同时写入
@@ -85,16 +75,12 @@ namespace flame::tcp {
 		std::string data = params[0];
         boost::asio::async_write(socket_, boost::asio::buffer(data), ch);
 
-        if (!err || err == boost::asio::error::operation_aborted)
-        {
-            return nullptr;
-        }
-        else
-        {
-            throw php::exception(zend_ce_exception,
-                (boost::format("failed to write tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
-        }
+        if (!err || err == boost::asio::error::operation_aborted) return nullptr;
+        else throw php::exception(zend_ce_exception
+            , (boost::format("Failed to write TCP socket: %S") % err.message()).str()
+            , err.value());
 	}
+
 	php::value socket::close(php::parameters& params) {
 		socket_.shutdown(boost::asio::socket_base::shutdown_both);
 		return nullptr;
diff --git a/src/tcp/tcp.cpp b/src/tcp/tcp.cpp
index dbe51cd..9fa17f1 100644
--- a/src/tcp/tcp.cpp
+++ b/src/tcp/tcp.cpp
@@ -9,16 +9,13 @@ namespace flame::tcp {
 	static std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
 	void declare(php::extension_entry& ext)
     {
-		gcontroller->on_init([] (const php::array& options)
-        {
+		gcontroller->on_init([] (const php::array& options) {
             resolver_.reset(new boost::asio::ip::tcp::resolver(gcontroller->context_x));
-        })->on_stop([] ()
-        {
+        })->on_stop([] () {
             resolver_.reset();
         });
 		ext
-			.function<connect>("flame\\tcp\\connect",
-            {
+			.function<connect>("flame\\tcp\\connect", {
 				{"address", php::TYPE::STRING},
 			});
 		socket::declare(ext);
@@ -31,7 +28,9 @@ namespace flame::tcp {
 
         std::string str = params[0];
         auto pair = udp::addr2pair(str);
-        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to connect tcp socket: illegal address format");
+        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+        	, "Failed to connect TCP socket: illegal address format"
+        	, -1);
 
 		coroutine_handler ch{coroutine::current};
         boost::system::error_code err;
@@ -43,21 +42,27 @@ namespace flame::tcp {
             ch.resume();
 		});
 		ch.suspend();
-		if(err) throw php::exception(zend_ce_exception,
-                (boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to resolve address: %s") % err.message()).str()
+            , err.value());
 
 		// 连接
         boost::asio::async_connect(ptr->socket_, eps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::tcp::endpoint &edp) {
             if (error) err = error;
 			else {
-	            obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
-				obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+	            obj.set("local_address", (boost::format("%s:%d")
+	            	% ptr->socket_.local_endpoint().address().to_string()
+	            	% ptr->socket_.local_endpoint().port() ).str());
+				obj.set("remote_address", (boost::format("%s:%d")
+					% ptr->socket_.remote_endpoint().address().to_string()
+					% ptr->socket_.remote_endpoint().port() ).str());
 			}
             ch.resume();
         });
         ch.suspend();
-        if(err) throw php::exception(zend_ce_exception,
-                (boost::format("failed to connect tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to connect TCP socket: %s") % err.message()).str()
+            , err.value());
         return std::move(obj);
 	}
 } // namespace flame::tcp
diff --git a/src/udp/server.cpp b/src/udp/server.cpp
index f2b48f5..589a47d 100644
--- a/src/udp/server.cpp
+++ b/src/udp/server.cpp
@@ -6,8 +6,8 @@
 #include "server.h"
 
 namespace flame::udp {
-    void server::declare(php::extension_entry& ext)
-    {
+    
+    void server::declare(php::extension_entry& ext) {
 		php::class_entry<server> class_server("flame\\udp\\server");
 		class_server
 			.method<&server::__construct>("__construct", {
@@ -31,14 +31,16 @@ namespace flame::udp {
     , max_(64 * 1024) {
 
 	}
+
     typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
 
     php::value server::__construct(php::parameters& params) {
         auto pair = addr2pair(params[0]);
 		boost::system::error_code err;
 		boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
-		if(err) throw php::exception(zend_ce_exception,
-            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to bind: %s") % err.message()).str()
+            , err.value());
 		std::uint16_t port = std::stoi(pair.second);
         addr_.address(address);
         addr_.port(port);
@@ -49,17 +51,16 @@ namespace flame::udp {
         reuse_port opt2(true);
         socket_.set_option(opt2);
 		socket_.bind(addr_, err);
-		if(err) throw php::exception(zend_ce_exception,
-            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to bind: %s") % err.message()).str()
+            , err.value());
 
         if(params.size() > 1) {
             php::array options = params[1];
-            if(options.exists("concurrent")) {
+            if(options.exists("concurrent"))
                 concurrent_ = std::max(std::min(static_cast<int>(options.get("concurrent")), 256), 1);
-            }
-            if(options.exists("max")) {
-                max_ = std::min(std::max(static_cast<int>(options.get("concurrent")) , 1), 64 * 1024);
-            }
+            if(options.exists("max"))
+                max_ = std::min(std::max(static_cast<int>(options.get("max")) , 1), 64 * 1024);
         }
         return nullptr;
     }
@@ -80,7 +81,7 @@ namespace flame::udp {
                         cb_.call({x->first, x->second});
                     } catch(const php::exception& ex) {
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) " << obj.call("__toString") << "\n";
+                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
                         // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
@@ -94,7 +95,9 @@ namespace flame::udp {
 		boost::asio::ip::udp::endpoint ep;
         php::buffer buffer;
         while(!closed_) {
-    		socket_.async_receive_from(boost::asio::buffer(buffer.prepare(max_), max_), ep, [&len, &err, &ch] (const boost::system::error_code& error, std::size_t nread) {
+    		socket_.async_receive_from(boost::asio::buffer(buffer.prepare(max_), max_), ep
+                , [&len, &err, &ch] (const boost::system::error_code& error, std::size_t nread) {
+
                 if(error) err = error;
                 else len = nread;
                 ch.resume();
@@ -102,10 +105,12 @@ namespace flame::udp {
             ch.suspend();
             // 存在可能被关闭, 直接返回
             if(err == boost::asio::error::operation_aborted) goto CLOSING;
-            else if(err) throw php::exception(zend_ce_exception,
-                                     (boost::format("failed to read tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+            else if(err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to read TCP socket: %s") % err.message()).str()
+                , err.value());
             buffer.commit(len);
-            q.push(std::make_pair<php::string, php::string>(php::string(std::move(buffer)), (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str()), ch);
+            q.push(std::make_pair<php::string, php::string>(php::string(std::move(buffer))
+                , (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str()), ch);
         }
 CLOSING:
         q.close();
@@ -119,18 +124,23 @@ namespace flame::udp {
         boost::system::error_code err;
 		php::string data = params[0];
 		auto pair = addr2pair(params[1]);
-		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to send udp packet: illegal address format");
+		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+            , "Failed to send udp packet: illegal address format"
+            , -1);
 
 		boost::asio::ip::udp::resolver::results_type eps;
-		resolver_->async_resolve(pair.first, pair.second, [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+		resolver_->async_resolve(pair.first, pair.second
+            , [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+
 			if(error) err = error;
 			else eps = results;
 			ch.resume();
 		});
 		ch.suspend();
 		if (err == boost::asio::error::operation_aborted) return nullptr;
-		else if(err) throw php::exception(zend_ce_exception,
-			(boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		else if(err) throw php::exception(zend_ce_exception
+			, (boost::format("Failed to resolve address: %s") % err.message()).str()
+            , err.value());
 
 		// 发送
 		int sent = 0;
@@ -139,8 +149,9 @@ namespace flame::udp {
 			if(!err) return nullptr;
 		}
 
-		throw php::exception(zend_ce_exception,
-			(boost::format("failed to send: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		throw php::exception(zend_ce_exception
+			, (boost::format("Failed to send UDP packet: %s") % err.message()).str()
+            , err.value());
 	}
 
     php::value server::close(php::parameters& params) {
diff --git a/src/udp/socket.cpp b/src/udp/socket.cpp
index a0ca186..391d10e 100644
--- a/src/udp/socket.cpp
+++ b/src/udp/socket.cpp
@@ -4,8 +4,8 @@
 #include "socket.h"
 
 namespace flame::udp {
-	void socket::declare(php::extension_entry& ext)
-    {
+
+	void socket::declare(php::extension_entry& ext)  {
 		php::class_entry<socket> class_socket("flame\\udp\\socket");
 		class_socket
 			.property({"local_address", ""})
@@ -25,6 +25,7 @@ namespace flame::udp {
 			.method<&socket::close>("close");
 		ext.add(std::move(class_socket));
 	}
+
 	socket::socket()
 	: socket_(gcontroller->context_x)
 	, connected_(false)
@@ -37,11 +38,12 @@ namespace flame::udp {
 	php::value socket::__construct(php::parameters& params) {
 		boost::system::error_code err;
 		php::array option;
-		if(params.size() > 0 && params[0].typeof(php::TYPE::STRING)) {
+		if (params.size() > 0 && params[0].typeof(php::TYPE::STRING)) {
 			auto pair = addr2pair(params[0]);
 			boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
-			if(err) throw php::exception(zend_ce_exception,
-	            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+			if(err) throw php::exception(zend_ce_exception
+	            , (boost::format("Failed to bind: %s") % err.message()).str()
+	            , err.value());
 			std::uint16_t port = std::stoi(pair.second);
 			auto ep = boost::asio::ip::udp::endpoint(address, port);
 			socket_.open(ep.protocol());
@@ -50,17 +52,19 @@ namespace flame::udp {
 	        reuse_port opt2(true);
 	        socket_.set_option(opt2);
 			socket_.bind(ep, err);
-			if(err) throw php::exception(zend_ce_exception,
-	            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+			if(err) throw php::exception(zend_ce_exception
+	            , (boost::format("failed to bind: %s") % err.message()).str()
+	            , err.value());
 			if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
 				option = params[0];
 				goto PARSE_OPTION;
 			}
-		}else{
+		} else {
 			socket_.open(boost::asio::ip::udp::v6(), err);
-			if(err) socket_.open(boost::asio::ip::udp::v4(), err);
-			if(err) throw php::exception(zend_ce_exception,
-	            (boost::format("failed to bind: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+			if (err) socket_.open(boost::asio::ip::udp::v4(), err);
+			if (err) throw php::exception(zend_ce_exception
+				, (boost::format("Failed to bind: %s") % err.message()).str()
+				, err.value());
 			if(params.size() > 0 && params[0].typeof(php::TYPE::ARRAY)) {
 				option = params[0];
 				goto PARSE_OPTION;
@@ -68,13 +72,15 @@ namespace flame::udp {
 		}
 		return nullptr;
 PARSE_OPTION:
-		if(option.exists("max")) {
+		if(option.exists("max"))
 			max_ = std::min(std::max(static_cast<int>(option.get("max")), 64), 64 * 1024);
-		}
 		return nullptr;
 	}
+
 	php::value socket::recv(php::parameters& params) {
-		if(!connected_) throw php::exception(zend_ce_exception, "failed to send: socket not connected");
+		if(!connected_) throw php::exception(zend_ce_error_exception
+			, "Failed to send: socket not connected"
+			, -1);
         coroutine_handler ch{coroutine::current};
         // 使用下面锁保证不会同时进行读取
         coroutine_guard guard(rmutex_, ch);
@@ -84,10 +90,12 @@ namespace flame::udp {
 		buffer_.commit(len);
         // 数据返回
         if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
-        else if (err) throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to recv from socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to recv UDP packet: %s") % err.message()).str()
+            , err.value());
         else return php::string(std::move(buffer_));
     }
+
 	php::value socket::recv_from(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
         // 使用下面锁保证不会同时进行读取
@@ -100,13 +108,17 @@ namespace flame::udp {
 		buffer_.commit(len);
         // 数据返回
         if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
-        else if (err) throw php::exception(zend_ce_exception,
-                                 (boost::format("failed to read tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("failed to read TCP socket: %s") % err.message()).str()
+            , err.value());
 		if(params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
 		return php::string(std::move(buffer_));
     }
+
 	php::value socket::send(php::parameters& params) {
-		if(!connected_) throw php::exception(zend_ce_exception, "failed to send: connected socket required");
+		if(!connected_) throw php::exception(zend_ce_error_exception
+			, "Failed to send: connected socket required"
+			, -1);
         coroutine_handler ch{coroutine::current};
 
         boost::system::error_code err;
@@ -114,28 +126,35 @@ namespace flame::udp {
         socket_.async_send(boost::asio::buffer(data), ch[err]);
 
         if (!err || err == boost::asio::error::operation_aborted) return nullptr;
-        else throw php::exception(zend_ce_exception,
-                (boost::format("failed to write tcp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        else throw php::exception(zend_ce_exception
+            , (boost::format("Failed to write TCP socket: %s") % err.message()).str()
+            , err.value());
         return nullptr;
 	}
+
 	php::value socket::send_to(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
 
         boost::system::error_code err;
 		php::string data = params[0];
 		auto pair = addr2pair(params[1]);
-		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to send udp packet: illegal address format");
+		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+			, "Failed to send udp packet: illegal address format"
+			, -1);
 
 		boost::asio::ip::udp::resolver::results_type eps;
-		resolver_->async_resolve(pair.first, pair.second, [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+		resolver_->async_resolve(pair.first, pair.second, 
+			[&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+
 			if(error) err = error;
 			else eps = results;
 			ch.resume();
 		});
 		ch.suspend();
 		if (err == boost::asio::error::operation_aborted) return nullptr;
-		else if(err) throw php::exception(zend_ce_exception,
-			(boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		else if(err) throw php::exception(zend_ce_exception
+			, (boost::format("Failed to resolve address: %s") % err.message()).str()
+			, err.value());
 
 		// 发送
 		int sent = 0;
@@ -144,9 +163,11 @@ namespace flame::udp {
 			if(!err) return nullptr;
 		}
 
-		throw php::exception(zend_ce_exception,
-			(boost::format("failed to send: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		throw php::exception(zend_ce_exception
+			, (boost::format("Failed to send UDP packet: %s") % err.message()).str()
+			, err.value());
 	}
+	
 	php::value socket::close(php::parameters& params) {
 		connected_ = false;
 		socket_.shutdown(boost::asio::socket_base::shutdown_both);
diff --git a/src/udp/udp.cpp b/src/udp/udp.cpp
index 2f56e38..e0fc212 100644
--- a/src/udp/udp.cpp
+++ b/src/udp/udp.cpp
@@ -5,9 +5,10 @@
 #include "server.h"
 
 namespace flame::udp {
+
 	std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
-	void declare(php::extension_entry& ext)
-    {
+
+	void declare(php::extension_entry& ext) {
 		gcontroller->on_init([] (const php::array& options) {
             resolver_.reset(new boost::asio::ip::udp::resolver(gcontroller->context_x));
         })->on_stop([] () {
@@ -20,27 +21,35 @@ namespace flame::udp {
 		socket::declare(ext);
 		server::declare(ext);
 	}
+
 	php::value connect(php::parameters& params) {
 		php::object obj(php::class_entry<socket>::entry());
         socket *ptr = static_cast<socket *>(php::native(obj));
 
         std::string str = params[0];
         auto pair = addr2pair(str);
-        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error, "failed to connect udp socket: illegal address format");
+        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+            , "Failed to connect UDP socket: illegal address format"
+            , -1);
 
 		coroutine_handler ch{coroutine::current};
         boost::system::error_code err;
 		boost::asio::ip::udp::resolver::results_type eps;
-		resolver_->async_resolve(pair.first, pair.second, [&err, &obj, &ptr, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) { // DNS 地址解析
+		resolver_->async_resolve(pair.first, pair.second
+            , [&err, &obj, &ptr, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) { // DNS 地址解析
+
 			if(error) err = error;
 			else eps = results;
 			ch.resume();
 		});
 		ch.suspend();
-		if(err) throw php::exception(zend_ce_exception,
-                (boost::format("failed to resolve address: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+		if(err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to resolve address: %s") % err.message()).str()
+                , err.value());
+
+        boost::asio::async_connect(ptr->socket_, eps
+            , [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::udp::endpoint &edp) { // 连接
 
-        boost::asio::async_connect(ptr->socket_, eps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::udp::endpoint &edp) { // 连接
             if (error) err = error;
 			else {
             	obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
@@ -49,11 +58,13 @@ namespace flame::udp {
             ch.resume();
         });
         ch.suspend();
-        if(err) throw php::exception(zend_ce_exception,
-                (boost::format("failed to connect udp socket: (%1%) %2%") % err.value() % err.message()).str(), err.value());
+        if(err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to connect UDP socket: %s") % err.message()).str()
+            , err.value());
 		ptr->connected_ = true;
         return std::move(obj);
 	}
+    
     std::pair<std::string, std::string> addr2pair(const std::string &addr) {
         char *s = const_cast<char*>(addr.data()), *p, *e = s + addr.size();
         for (p = e - 1; p > s; --p) if (*p == ':') break; // 分离 地址与端口

From d7b43dc862df63330c7b690619d8d37458e73a56 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 10 Apr 2019 14:13:29 +0800
Subject: [PATCH 091/146] =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=BA=94=E4=BA=A4?=
 =?UTF-8?q?=E7=94=B1=E7=94=A8=E6=88=B7=E5=A4=84=E7=90=86=E5=87=BD=E6=95=B0?=
 =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E5=A4=84=E7=90=86(=E8=AE=B0=E5=BD=95)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/_handler.cpp     | 4 ++++
 src/kafka/consumer.cpp    | 5 ++++-
 src/rabbitmq/consumer.cpp | 4 ++++
 src/tcp/server.cpp        | 3 +++
 src/udp/server.cpp        | 4 +++-
 5 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 45bf15f..b918194 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -122,6 +122,10 @@ namespace flame::http {
             } catch(const php::exception& ex) {
                 res_.reset();
                 req_.reset();
+                
+                auto ft = gcontroller->cbmap->equal_range("exception");
+                for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
+
                 php::object obj = ex;
                 std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
             }
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index 0206763..7dc4c9d 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -1,3 +1,4 @@
+#include "../controller.h"
 #include "../coroutine.h"
 #include "../time/time.h"
 #include "consumer.h"
@@ -43,9 +44,11 @@ namespace flame::kafka {
                         if(m) cb_.call({m.value()});
                         else break;
                     } catch(const php::exception& ex) {
+                        auto ft = gcontroller->cbmap->equal_range("exception");
+                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
+
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
-                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
                 if(--count == 0) ch_run.resume();
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index 9b778af..eb8506f 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -1,3 +1,4 @@
+#include "../controller.h"
 #include "../coroutine.h"
 #include "consumer.h"
 #include "_client.h"
@@ -49,6 +50,9 @@ namespace flame::rabbitmq
                     try {
                         cb_.call( {x.value()} );
                     } catch(const php::exception& ex) {
+                        auto ft = gcontroller->cbmap->equal_range("exception");
+                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
+
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
                     }
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index 1707a39..80be987 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -84,6 +84,9 @@ namespace flame::tcp {
                     try {
                         cb.call({obj});
                     } catch(const php::exception& ex) {
+                        auto ft = gcontroller->cbmap->equal_range("exception");
+                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
+
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
                     }
diff --git a/src/udp/server.cpp b/src/udp/server.cpp
index 589a47d..6ff1002 100644
--- a/src/udp/server.cpp
+++ b/src/udp/server.cpp
@@ -80,9 +80,11 @@ namespace flame::udp {
                     try {
                         cb_.call({x->first, x->second});
                     } catch(const php::exception& ex) {
+                        auto ft = gcontroller->cbmap->equal_range("exception");
+                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
+
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
-                        // std::clog << "[" << time::iso() << "] (ERROR) " << ex.what() << std::endl;
                     }
                 }
                 if(--count == 0) ch.resume();

From 9c01877b385a636b6c7d0cbaf8689d9a7e12ad45 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 10 Apr 2019 14:21:10 +0800
Subject: [PATCH 092/146] =?UTF-8?q?HTTP(S)/2=20=E7=9B=B8=E5=85=B3=E6=96=87?=
 =?UTF-8?q?=E6=A1=A3=E8=AF=B4=E6=98=8E;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md    | 39 +++++++++++++++++++++++++++++----------
 doc/http.php |  2 +-
 2 files changed, 30 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index f510507..ada6d9f 100644
--- a/README.md
+++ b/README.md
@@ -49,19 +49,38 @@ flame\run();
 
 * **core** - 核心，框架初始化设置，协程启动，协程队列，协程锁等；
 * **time** - 时间相关，协程休眠，当前时间等；
-* **log** - 简单日志记录功能，多进程支持，支持日志登记过滤设置；
+> 毫秒级时间戳及缓存机制;  
+* **log** - 简单日志记录功能:
+> 多进程统一日志记录;  
+> 支持日志登记过滤设置;  
+> 支持通过信号进行日志重载;  
 * **os** - 操作系统相关信息获取，异步进程启停操作等；
+> 获取本地网卡地址;
 * **tcp** - 封装 TCP 相关服务端客户端功能；
 * **udp** - 封装 UDP 相关服务端客户端功能；
-* **http** - 简单的 HTTP 客户端，支持长连 Keep-Alive 及相关连接数控制；HTTP 服务端，简单 PATH 处理器；（暂不支持 HTTPS 相关）
-* **mysql** - 简单 MySQL 客户端（连接池），提供部分简化方法，如 `insert/delete/one` 等；
-* **redis** - 简单 Redis 客户端（连接池）；
-* **mongodb** - 简单 MongoDB 客户端（连接池），提供部分简化方法，如 `insert/delete/one/count` 等；
-* **rabbitmq** - 简单 RabbitMQ 生产消费支持；
-* **kafka** - 简单 Kafka 生产消费支持（仅支持 Kafka 0.10+ 群组消费）；
-* **hash** - 提供了若干哈希算法, 例如 murmur2 / crc64 / xxhash64 等；
-* **encoding** - 提供了若干编码、序列化函数，例如 bson 等；
-* **compress** - 提供了若干压缩算法，例如 snappy 等；
+* **http** - 简单的 HTTP 客户端/服务端封装;
+> 客户端支持长连 Keep-Alive 及相关连接数控制;  
+> 客户端支持 HTTPS 及 HTTP/2 协议部分功能;  
+> 客户端暂不支持 form-data/multipart 形式的请求;  
+> 服务器**不支持** HTTPS 可考虑使用 NGINX 等进行反向代理;  
+* **mysql** - 简单 MySQL 客户端:
+> 提供部分简化方法，如 `insert/delete/one` 等, 自动进行 ESCAPE 转义;  
+> 内部使用连接池形式自动连接复用;  
+> 支持事务;  
+* **redis** - 简单 Redis 客户端:
+> 内部使用连接池形式自动连接复用;  
+> 暂不支持 SUBSCRIBE 相关指令;  
+* **mongodb** - 简单 MongoDB 客户端:
+> 提供部分简化方法，如 `insert/delete/one/count` 等;  
+> 内部使用连接池自动连接复用;  
+* **rabbitmq** - 简单 RabbitMQ 生产消费支持:
+> 支持使用协程进行"并行"消费;
+* **kafka** - 简单 Kafka 生产消费支持:
+> 仅支持 Kafka 0.10+ 群组消费形式;  
+> 支持使用协程进行"并行"消费;  
+* **hash** - 提供了若干哈希算法；
+* **encoding** - 提供了若干编码、序列化函数；
+* **compress** - 提供了若干压缩算法；
 
 请参看 `/doc` `/test` 目录下相关 PHP 文件的文档注释；也可将 `/doc` 挂接在 IDE 内方便提供 **自动完成** 等；
 
diff --git a/doc/http.php b/doc/http.php
index 3562304..f00ea52 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * 提供基本的 HTTP(S) 客户端、服务端封装；
+ * 提供基本的 HTTP(S) 客户端、服务端封装；服务端仅支持 HTTP/1.1 协议; 客户端支持 (TLS) HTTP/1.x HTTP/2 协议;
  * 注意：
  * * 默认状态客户端对请求连接进行 KeepAlive 复用；若需要使用短连接，请自行制定 Connection: close 头；
  */

From b04da2a1935d4995f0cbeacc5b1c30fc5614d0ee Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 10 Apr 2019 14:54:56 +0800
Subject: [PATCH 093/146] =?UTF-8?q?=E5=B1=95=E7=A4=BA=E5=86=85=E7=BD=AE=20?=
 =?UTF-8?q?OPENSSL=20=E7=89=88=E6=9C=AC;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                    |  3 +++
 src/flame.cpp                | 12 ++++++++++++
 src/http/client_response.cpp |  2 +-
 src/rabbitmq/_client.cpp     |  5 +++--
 src/tcp/socket.cpp           |  2 +-
 5 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index ada6d9f..7f8f77c 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,8 @@ https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
 
 ### 依赖库
 
+> 需要基础编译组建, 及 c-ares / sasl2 等库;
+
 #### openssl
 ``` Bash
 CC=gcc CXX=g++ ./Configure no-shared --prefix=/data/vendor/openssl-1.1.1 linux-x86_64
@@ -175,5 +177,6 @@ make && make install
 mkdir stage && cd stage
 CC=gcc CXX=g++ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/mysqlc-8.0.15 -DWITHOUT_SERVER=ON -DWITH_BOOST=../boost/boost_1_68_0 -DWITH_SSL=/data/vendor/openssl-1.1.1 -DOPENSSL_EXECUTABLE=/data/vendor/openssl-1.1.1/bin/openssl -DWITH_CURL=/data/vendor/curl-7.64.1 -DCMAKE_C_FLAGS=-pthread -DCMAKE_CXX_FLAGS=-pthread ../
 make && make install
+# rm /data/vendor/mysqlc-8.0.15/lib/*.so*
 ```
 注意：需要下载内含 boost 头文件的版本（与框架依赖版本不同）；
diff --git a/src/flame.cpp b/src/flame.cpp
index 73797db..dfc62f8 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -80,6 +80,17 @@ namespace flame {
     }
 }
 
+static std::string openssl_version_str() {
+    std::string version = (boost::format("%d.%d.%d")
+        % ((OPENSSL_VERSION_NUMBER & 0xff0000000L) >> 28)
+        % ((OPENSSL_VERSION_NUMBER & 0x00ff00000L) >> 20)
+        % ((OPENSSL_VERSION_NUMBER & 0x0000ff000L) >> 12) ).str();
+    char status = (OPENSSL_VERSION_NUMBER & 0x000000ff0L) >>  4;
+    if(status > 0) {
+        version.push_back('a' + status - 1);
+    }
+    return version;
+}
 #define VERSION_MACRO(major, minor, patch) VERSION_JOIN(major, minor, patch)
 #define VERSION_JOIN(major, minor, patch) #major"."#minor"."#patch
 
@@ -97,6 +108,7 @@ extern "C" {
         ext.add(std::move(class_closure));
 
         ext
+            .desc({"vendor/openssl", openssl_version_str()})
             .desc({"vendor/boost", BOOST_LIB_VERSION})
             .desc({"vendor/phpext", PHPEXT_LIB_VERSION})
             .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index 350d8f2..84b50cf 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -25,7 +25,7 @@ namespace http {
 	}
 	void client_response::build_ex() {
 		if(c_final_ != 0) throw php::exception(zend_ce_exception
-			, (boost::format("Failed to execute HTTP request: %s") % c_final_ % c_error_).str()
+			, (boost::format("Failed to execute HTTP request: %s") % c_error_).str()
 			, c_final_);
 		// 响应码
 		long rv = 0;
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
index 04737f5..943da7f 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/rabbitmq/_client.cpp
@@ -176,8 +176,9 @@ namespace flame::rabbitmq
     //     producer_ch_.reset();
     //     if (!error_.empty()) {
     //         std::string err = std::move(error_);
-    //         throw php::exception(zend_ce_error,
-    //                         (boost::format("failed to publish RabbitMQ message: %1%") % err).str(), -1);
+    //         throw php::exception(zend_ce_error
+    //             , (boost::format("Failed to publish RabbitMQ message: %s") % err).str()
+    //             , -1);
     //     }
     // }
 
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
index f472d09..f68d765 100644
--- a/src/tcp/socket.cpp
+++ b/src/tcp/socket.cpp
@@ -77,7 +77,7 @@ namespace flame::tcp {
 
         if (!err || err == boost::asio::error::operation_aborted) return nullptr;
         else throw php::exception(zend_ce_exception
-            , (boost::format("Failed to write TCP socket: %S") % err.message()).str()
+            , (boost::format("Failed to write TCP socket: %s") % err.message()).str()
             , err.value());
 	}
 

From 920c5b6ea4de06e391690289a5eb3ad2a11aff9a Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 10 Apr 2019 16:29:22 +0800
Subject: [PATCH 094/146] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=B8=B8=E9=87=8F?=
 =?UTF-8?q?=E8=AE=BE=E5=AE=9A,=20=E5=B0=BD=E9=87=8F=E5=85=BC=E5=AE=B9?=
 =?UTF-8?q?=E5=8E=9F=20BOOL=20=E7=94=A8=E6=B3=95;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mongodb.php                | 4 ++--
 src/mongodb/_connection_base.h | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/mongodb.php b/doc/mongodb.php
index 2114f9a..0b2bc3f 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -23,8 +23,8 @@ function connect(string $url):client {
  */
 class client {
     const COMMAND_RAW        = 0;
-    const COMMAND_READ       = 1;
-    const COMMAND_WRITE      = 2;
+    const COMMAND_WRITE      = 1;
+    const COMMAND_READ       = 2;
     const COMMAND_READ_WRITE = 3;
     /**
      * 执行以 $command 描述的命令;
diff --git a/src/mongodb/_connection_base.h b/src/mongodb/_connection_base.h
index 1a3c6aa..9fc6706 100644
--- a/src/mongodb/_connection_base.h
+++ b/src/mongodb/_connection_base.h
@@ -8,8 +8,8 @@ namespace flame::mongodb {
     public:
         enum command_type_t {
             COMMAND_RAW,
-            COMMAND_READ,
             COMMAND_WRITE,
+            COMMAND_READ,
             COMMAND_READ_WRITE,
         };
         virtual std::shared_ptr<mongoc_client_t> acquire(coroutine_handler& ch) = 0;

From 779e6ee4d4ca854b46b5ebaf5aa7c9714dea1ab0 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 11 Apr 2019 12:23:22 +0800
Subject: [PATCH 095/146] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E8=A7=84=E6=95=B4?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/core.cpp                     |   6 +-
 src/coroutine.cpp                | 160 ++++++++++++++----------------
 src/coroutine_mutex.h            |  33 +++----
 src/coroutine_queue.h            |  12 +--
 src/encoding/encoding.cpp        |  11 ++-
 src/flame.cpp                    |  14 +--
 src/hash/hash.cpp                |   8 +-
 src/http/_handler.cpp            |  65 ++++++------
 src/http/client.cpp              |  45 +++++----
 src/http/client_poll.cpp         |  24 ++---
 src/http/client_request.cpp      |  57 ++++++-----
 src/http/client_response.cpp     |  15 +--
 src/http/http.cpp                |  61 +++++++-----
 src/http/server.cpp              |   4 +-
 src/http/server_request.cpp      |  28 +++---
 src/http/server_request.h        |   4 +-
 src/http/server_response.cpp     |  79 +++++++--------
 src/http/value_body.cpp          |   8 +-
 src/kafka/_consumer.cpp          |  12 ++-
 src/kafka/_producer.cpp          |   7 +-
 src/kafka/consumer.cpp           |   4 +-
 src/kafka/message.cpp            |   8 +-
 src/kafka/producer.cpp           |  49 ++++------
 src/log/log.cpp                  |   4 +-
 src/mongodb/_connection_base.cpp |   5 +-
 src/mongodb/_connection_lock.cpp |   2 +-
 src/mongodb/_connection_pool.cpp |  23 +++--
 src/mongodb/collection.cpp       |  41 ++++----
 src/mongodb/cursor.cpp           |  26 +++--
 src/mongodb/date_time.cpp        |   2 +-
 src/mongodb/mongodb.cpp          | 163 +++++++++++++------------------
 src/mysql/_connection_base.cpp   |   8 +-
 src/mysql/_connection_lock.cpp   |   2 +-
 src/mysql/_connection_pool.cpp   |  29 +++---
 src/mysql/client.cpp             |  63 ++++++------
 src/mysql/mysql.cpp              |  91 +++++++++++------
 src/mysql/result.cpp             |  24 ++---
 src/os/os.cpp                    |   3 +-
 src/os/process.cpp               |  44 ++++-----
 src/queue.cpp                    |  45 ++++-----
 src/rabbitmq/_client.cpp         |  31 +++---
 src/rabbitmq/consumer.cpp        |   4 +-
 src/redis/_connection_base.cpp   |  50 ++++------
 src/redis/_connection_pool.cpp   |  45 +++++----
 src/redis/client.cpp             |  10 +-
 src/redis/tx.cpp                 |   8 +-
 src/tcp/server.cpp               |  10 +-
 src/tcp/socket.cpp               |   2 +-
 src/tcp/tcp.cpp                  |   8 +-
 src/time/time.cpp                |   2 +-
 src/udp/server.cpp               |  26 ++---
 src/udp/socket.cpp               |  27 ++---
 src/udp/udp.cpp                  |   8 +-
 src/url.cpp                      |  38 +++----
 54 files changed, 769 insertions(+), 789 deletions(-)

diff --git a/src/core.cpp b/src/core.cpp
index af9c778..622a096 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -10,7 +10,7 @@ namespace flame::core {
         std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
         std::map< std::shared_ptr<coroutine_queue<php::value>>, php::object > mm;
         for (auto i = 0; i < params.size(); ++i) {
-            if(!params[i].instanceof(php::class_entry<queue>::entry())) throw php::exception(zend_ce_type_error
+            if (!params[i].instanceof(php::class_entry<queue>::entry())) throw php::exception(zend_ce_type_error
                 , "Failed to select: instanceof flame\\queue required"
                 , -1);
             php::object obj = params[i];
@@ -34,8 +34,8 @@ namespace flame::core {
     void declare(php::extension_entry &ext) {
         ext
             .on_request_shutdown([] (php::extension_entry& ext) -> bool {
-                if(coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0) {
-                    if(!php::error::exists()) std::cerr << "[FATAL] process exited prematurely: exception or missing 'flame\\run();' ?\n";
+                if (coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0) {
+                    if (!php::error::exists()) std::cerr << "[FATAL] process exited prematurely: exception or missing 'flame\\run();' ?\n";
                     _exit(-1);
                 }
                 return true;
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 2342dca..5491e30 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -1,15 +1,17 @@
 #include "controller.h"
 #include "coroutine.h"
 
-namespace flame
-{
+namespace flame {
+
     boost::context::fixedsize_stack coroutine::stack_allocator(64 * 1024);
+
     std::size_t coroutine::count = 0;
+
     coroutine::php_context_t coroutine::global_context;
     // 当前协程
     std::shared_ptr<coroutine> coroutine::current(nullptr);
-    void coroutine::save_context(php_context_t &ctx)
-    {
+
+    void coroutine::save_context(php_context_t &ctx) {
         ctx.vm_stack = EG(vm_stack);
         ctx.vm_stack_top = EG(vm_stack_top);
         ctx.vm_stack_end = EG(vm_stack_end);
@@ -19,8 +21,8 @@ namespace flame
         ctx.exception_class = EG(exception_class);
         ctx.error_handling = EG(error_handling);
     }
-    void coroutine::restore_context(php_context_t &ctx)
-    {
+
+    void coroutine::restore_context(php_context_t &ctx) {
         EG(vm_stack) = ctx.vm_stack;
         EG(vm_stack_top) = ctx.vm_stack_top;
         EG(vm_stack_end) = ctx.vm_stack_end;
@@ -31,8 +33,7 @@ namespace flame
         EG(error_handling) = ctx.error_handling;
     }
     // 参考 zend_execute.c 
-    static zend_vm_stack zend_vm_stack_new_page(size_t size, zend_vm_stack prev)
-    {
+    static zend_vm_stack zend_vm_stack_new_page(size_t size, zend_vm_stack prev) {
         zend_vm_stack page = (zend_vm_stack)emalloc(size);
 
         page->top = ZEND_VM_STACK_ELEMENTS(page);
@@ -41,16 +42,14 @@ namespace flame
         return page;
     }
     // 参考 zend_execute.c
-    static void zend_vm_stack_init(void)
-    {
+    static void zend_vm_stack_init(void) {
         EG(vm_stack) = zend_vm_stack_new_page(4 * sizeof(zval) * 1024, NULL);
         EG(vm_stack)->top++;
         EG(vm_stack_top) = EG(vm_stack)->top;
         EG(vm_stack_end) = EG(vm_stack)->end;
     }
-    std::shared_ptr<coroutine> coroutine::start(php::callable fn)
-    {
-        
+
+    std::shared_ptr<coroutine> coroutine::start(php::callable fn) {
         ++coroutine::count;
         auto co_ = std::make_shared<coroutine>(fn);
         // 事实上的协程启动会稍后
@@ -58,53 +57,50 @@ namespace flame
             co_->c1_ = boost::context::fiber(
                 std::allocator_arg, coroutine::stack_allocator,
                 [co_ /*, ag = std::move(ag)*/](boost::context::fiber &&cc) {
-                    co_->c2_ = std::move(cc);
-                    auto work = boost::asio::make_work_guard(gcontroller->context_x);
-
-                    save_context(coroutine::global_context);
-                    // 启动进入协程
-                    coroutine::current = co_;
-
-                    EG(current_execute_data) = nullptr;
-                    EG(error_handling) = EH_NORMAL;
-                    EG(exception_class) = nullptr;
-                    EG(exception) = nullptr;
-                    zend_vm_stack_init();
-                    // 协程运行;
-                    co_->fn_.call(/*ag*/);
-                    // 实际协程销毁可能会较晚, 保证在 Zend 引擎前释放
-                    co_->fn_ = nullptr;
-                    // 协程运行完毕
-                    zend_vm_stack_destroy();
-                    coroutine::current = nullptr;
-                    
-                    restore_context(coroutine::global_context);
-                    boost::asio::post(gcontroller->context_x, [co_]()
-                    {
-                        co_->c1_ = boost::context::fiber();
-                        if (--coroutine::count == 0)
-                        {
-                            // 所有协程结束后退出
-                            gcontroller->stop();
-                        }
-                    });
-                    return std::move(co_->c2_);
+
+                co_->c2_ = std::move(cc);
+                auto work = boost::asio::make_work_guard(gcontroller->context_x);
+
+                save_context(coroutine::global_context);
+                // 启动进入协程
+                coroutine::current = co_;
+
+                EG(current_execute_data) = nullptr;
+                EG(error_handling) = EH_NORMAL;
+                EG(exception_class) = nullptr;
+                EG(exception) = nullptr;
+                zend_vm_stack_init();
+                // 协程运行;
+                co_->fn_.call(/*ag*/);
+                // 实际协程销毁可能会较晚, 保证在 Zend 引擎前释放
+                co_->fn_ = nullptr;
+                // 协程运行完毕
+                zend_vm_stack_destroy();
+                coroutine::current = nullptr;
+                
+                restore_context(coroutine::global_context);
+                boost::asio::post(gcontroller->context_x, [co_]() {
+                    co_->c1_ = boost::context::fiber();
+                    if (--coroutine::count == 0) gcontroller->stop(); // 所有协程结束后退出
                 });
+                return std::move(co_->c2_);
+            });
 
             co_->c1_ = std::move(co_->c1_).resume();
         });
         return co_;
     }
+
     coroutine::coroutine(php::callable fn)
-        : fn_(std::move(fn)), c1_(), c2_() {
-            // std::cout << "coroutine\n";
-        }
-    coroutine::~coroutine()
-    {
+    : fn_(std::move(fn)), c1_(), c2_() {
+        // std::cout << "coroutine\n";
+    }
+
+    coroutine::~coroutine() {
         // std::cout << "~coroutine\n";
     }
-    void coroutine::suspend()
-    {
+
+    void coroutine::suspend() {
         // 保存 PHP 堆栈
         coroutine::save_context(php_);
         coroutine::restore_context(coroutine::global_context);
@@ -113,8 +109,7 @@ namespace flame
         c2_ = std::move(c2_).resume();
     }
 
-    void coroutine::resume()
-    {
+    void coroutine::resume() {
         // 恢复 PHP 堆栈
         coroutine::save_context(coroutine::global_context);
         coroutine::restore_context(php_);
@@ -123,61 +118,58 @@ namespace flame
         c1_ = std::move(c1_).resume();
     }
     coroutine_handler::coroutine_handler()
-        : co_(nullptr)
-        , er_(nullptr)
-        , stat_(new std::atomic<int>(0))
-    {
+    : co_(nullptr)
+    , er_(nullptr)
+    , stat_(new std::atomic<int>(0)) {
     }
+
     coroutine_handler::coroutine_handler(std::shared_ptr<coroutine> co)
-        : co_(co)
-        , er_(nullptr)
-        , stat_(new std::atomic<int>(0))
-    {
+    : co_(co)
+    , er_(nullptr)
+    , stat_(new std::atomic<int>(0)) {
     }
-    coroutine_handler::~coroutine_handler()
-    {
+
+    coroutine_handler::~coroutine_handler() {
         // std::cout << "~coroutine_handler\n";
     }
-    void coroutine_handler::reset()
-    {
+
+    void coroutine_handler::reset() {
         co_.reset();
         *stat_ = 0;
     }
-    void coroutine_handler::reset(std::shared_ptr<coroutine> co)
-    {
+
+    void coroutine_handler::reset(std::shared_ptr<coroutine> co) {
         assert(co_ == nullptr);
         co_   = co;
         *stat_ = 0;
     }
-    coroutine_handler::operator bool() const
-    {
+
+    coroutine_handler::operator bool() const {
         return co_ != nullptr;
     }
-    void coroutine_handler::operator() (const boost::system::error_code &e, std::size_t n)
-    {
-        if(er_) *er_ = e;
+
+    void coroutine_handler::operator() (const boost::system::error_code &e, std::size_t n) {
+        if (er_) *er_ = e;
         co_->len_ = n;
 
         resume();
     }
-    coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e)
-    {
+
+    coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e) {
         er_ = &e;
         return *this;
     }
-    void coroutine_handler::resume()
-    {
-        if(--*stat_ == 0)
-            boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
+
+    void coroutine_handler::resume() {
+        if (--*stat_ == 0) boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
-    void coroutine_handler::suspend()
-    {
+
+    void coroutine_handler::suspend() {
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
-        if(++*stat_ == 1)
-            co_->suspend();
+        if (++*stat_ == 1) co_->suspend();
     }
-    bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2)
-    {
+    
+    bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2) {
         return ch1.co_ < ch2.co_;
     }
 }
diff --git a/src/coroutine_mutex.h b/src/coroutine_mutex.h
index 8b5a499..264d540 100644
--- a/src/coroutine_mutex.h
+++ b/src/coroutine_mutex.h
@@ -1,38 +1,32 @@
 #pragma once
 #include "coroutine.h"
 
-namespace flame 
-{
-    class coroutine_mutex
-    {
+namespace flame {
+    class coroutine_mutex {
     public:
         coroutine_mutex()
-        : mt_(false)
-        {
+        : mt_(false) {
 
         }
-        void lock(coroutine_handler& ch)
-        {  
-            while(mt_)
-            {
+
+        void lock(coroutine_handler& ch) {  
+            while(mt_) {
                 cm_.insert(ch);
                 ch.suspend();
             }
             mt_ = true;
         }
-        bool try_lock()
-        {
-            if(!mt_)
-            {
+
+        bool try_lock() {
+            if (!mt_) {
                 mt_ = true;
                 return true;
             }
             return false;
         }
-        void unlock()
-        {
-            while(!cm_.empty())
-            {
+
+        void unlock() {
+            while(!cm_.empty()) {
                 auto ch = cm_.extract(cm_.begin()).value();
                 ch.resume();
             }
@@ -45,8 +39,7 @@ namespace flame
     class coroutine_guard {
     public:
         coroutine_guard(coroutine_mutex& cm, coroutine_handler& ch)
-        : cm_(cm)
-        {
+        : cm_(cm) {
             cm_.lock(ch);
         }
         ~coroutine_guard() {
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
index 604603b..87de4f9 100644
--- a/src/coroutine_queue.h
+++ b/src/coroutine_queue.h
@@ -23,10 +23,10 @@ namespace flame {
         }
 
         void push(const T& t, coroutine_handler& ch) {
-            if(closed_) throw php::exception(zend_ce_error_exception
+            if (closed_) throw php::exception(zend_ce_error_exception
                 , "Failed to push queue: already closed"
                 , -1);
-            if(q_.size() >= n_) {
+            if (q_.size() >= n_) {
                 p_.insert(ch);
                 ch.suspend();
             }
@@ -38,8 +38,8 @@ namespace flame {
         }
         std::optional<T> pop(coroutine_handler& ch) {
             for(;;) {
-                if(!q_.empty()) break; // 有数据消费
-                else if(closed_) return std::optional<T>();  // 无数据关闭
+                if (!q_.empty()) break; // 有数据消费
+                else if (closed_) return std::optional<T>();  // 无数据关闭
                 else { // 无数据等待
                     c_.insert(ch);
                     ch.suspend();
@@ -68,10 +68,10 @@ namespace flame {
 TRY_ALL:
         bool all_closed = true;
         for(auto i=queues.begin();i!=queues.end();++i) {
-            if(!(*i)->q_.empty()) return *i;
+            if (!(*i)->q_.empty()) return *i;
             else if (!(*i)->closed_) all_closed = false;
         }
-        if(all_closed) return std::shared_ptr< coroutine_queue<T> >(nullptr);
+        if (all_closed) return std::shared_ptr< coroutine_queue<T> >(nullptr);
         for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(ch);
         ch.suspend();
         for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(ch);
diff --git a/src/encoding/encoding.cpp b/src/encoding/encoding.cpp
index 713c9c4..e7b091b 100644
--- a/src/encoding/encoding.cpp
+++ b/src/encoding/encoding.cpp
@@ -3,23 +3,24 @@
 
 namespace flame::encoding {
     static php::value bson_encode(php::parameters &params) {
-        if(params[0].typeof(php::TYPE::ARRAY)) {
+        if (params[0].typeof(php::TYPE::ARRAY)) {
             auto doc = flame::mongodb::array2bson(params[0]);
             return php::string((const char*)bson_get_data(doc.get()), doc->len);
-        }else throw php::exception(zend_ce_type_error
+        }
+        else throw php::exception(zend_ce_type_error
             , "Failed to encode: typeof 'array' required"
             , -1);
     }
     static php::value bson_decode(php::parameters &params) {
         php::string data = params[0].to_string();
         bson_t* doc = bson_new_from_data((const uint8_t*)data.data(), data.size());
-        if(doc == nullptr) return nullptr;
+        if (doc == nullptr) return nullptr;
         php::array rv = flame::mongodb::bson2array(doc);
         bson_destroy(doc);
         return std::move(rv);
     }
-    void declare(php::extension_entry &ext)
-    {
+    
+    void declare(php::extension_entry &ext) {
         ext
             .function<bson_encode>("flame\\encoding\\bson_encode", {
                 {"data", php::TYPE::ARRAY},
diff --git a/src/flame.cpp b/src/flame.cpp
index dfc62f8..c35ada8 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -22,7 +22,7 @@
 namespace flame {
     static php::value init(php::parameters& params) {
         php::array options(0);
-        if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) options = params[1];
+        if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) options = params[1];
         gcontroller->initialize(params[0], options);
         return nullptr;
     }
@@ -64,10 +64,11 @@ namespace flame {
     }
 
     static php::value run(php::parameters& params) {
-        if(gcontroller->status & controller::STATUS_INITIALIZED) {
+        if (gcontroller->status & controller::STATUS_INITIALIZED) {
             gcontroller->default_execute_data = EG(current_execute_data);
             gcontroller->run();
-        }else throw php::exception(zend_ce_error_exception
+        }
+        else throw php::exception(zend_ce_error_exception
             , "Failed to run flame: exception or missing 'flame\\init()' ?"
             , -1);
         return nullptr;
@@ -86,7 +87,7 @@ static std::string openssl_version_str() {
         % ((OPENSSL_VERSION_NUMBER & 0x00ff00000L) >> 20)
         % ((OPENSSL_VERSION_NUMBER & 0x0000ff000L) >> 12) ).str();
     char status = (OPENSSL_VERSION_NUMBER & 0x000000ff0L) >>  4;
-    if(status > 0) {
+    if (status > 0) {
         version.push_back('a' + status - 1);
     }
     return version;
@@ -126,7 +127,7 @@ extern "C" {
                 {"options", php::TYPE::ARRAY, false, true},
             })
             .function<flame::run>("flame\\run");
-        if(flame::gcontroller->type == flame::controller::process_type::WORKER) {
+        if (flame::gcontroller->type == flame::controller::process_type::WORKER) {
             ext
             .function<flame::go>("flame\\go", {
                 {"coroutine", php::TYPE::CALLABLE},
@@ -152,7 +153,8 @@ extern "C" {
             flame::hash::declare(ext);
             flame::encoding::declare(ext);
             flame::compress::declare(ext);
-        } else {
+        }
+        else {
             ext
                 .function<flame::fake_fn>("flame\\go")
                 .function<flame::fake_fn>("flame\\on");
diff --git a/src/hash/hash.cpp b/src/hash/hash.cpp
index d37667e..6d96d2a 100644
--- a/src/hash/hash.cpp
+++ b/src/hash/hash.cpp
@@ -11,17 +11,17 @@ namespace flame::hash {
     static php::value murmur2(php::parameters& params) {
         php::string data = params[0].to_string();
         std::uint32_t raw = rd_murmur2(data.c_str(), data.size());
-        if(params.size() > 1 && params[1].to_boolean()) return raw;
+        if (params.size() > 1 && params[1].to_boolean()) return raw;
         return dec2hex<std::uint32_t>(raw);
     }
     static php::value xxh64(php::parameters& params) {
         php::string data = params[0].to_string();
         unsigned long long seed = 0;
-        if(params.size() > 1) {
+        if (params.size() > 1) {
             seed = params[1].to_integer();
         }
         std::uint64_t raw = XXH64(data.c_str(), data.size(), seed);
-        if(params.size() > 2 && params[2].to_boolean()) return raw;
+        if (params.size() > 2 && params[2].to_boolean()) return raw;
         return dec2hex<std::uint64_t>(raw);
     }
     static php::value crc64(php::parameters& params) {
@@ -29,7 +29,7 @@ namespace flame::hash {
         boost::crc_optimal<64, 0x42F0E1EBA9EA3693, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true, true>  crc64;
         crc64.process_bytes(data.c_str(), data.size());
         std::uint64_t raw = crc64.checksum();
-        if(params.size() > 1 && params[1].to_boolean()) return raw;
+        if (params.size() > 1 && params[1].to_boolean()) return raw;
         return dec2hex<std::uint64_t>(raw);
     }
     void declare(php::extension_entry &ext)
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index b918194..19a795f 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -19,35 +19,30 @@ namespace flame::http {
     }
 
     void _handler::start() {
+        req_.reset();
+        res_.reset();
+        if ((req_ && !req_->keep_alive()) || (res_ && !res_->keep_alive()))  return;
         // 第一次请求或允许复用
-        if ((gcontroller->status & controller::controller_status::STATUS_SHUTDOWN)
-                || svr_ptr->closed_
-                || (req_ && !req_->keep_alive()) || (res_ && !res_->keep_alive())) {
-            req_.reset();
-            res_.reset();
-            return;
-        }else{
-            req_.reset();
-            res_.reset();
-            read_request();
-        }
+        read_request();
     }
 
     void _handler::read_request() {
         // 读取请求
         req_.reset(new boost::beast::http::request_parser<value_body<true>>());
+        req_->header_limit(16 * 1024);
         req_->body_limit(body_max_size);
         boost::beast::http::async_read(socket_, buffer_, *req_
-            , [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n)
-        {
-            if(error == boost::beast::http::error::end_of_stream
+            , [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n) {
+
+            if (error == boost::beast::http::error::end_of_stream
                 || error == boost::asio::error::operation_aborted
                 || error == boost::asio::error::connection_reset) {
 
                 res_.reset();
                 req_.reset();
                 return;
-            } else if (error) {
+            }
+            else if (error) {
                 res_.reset();
                 req_.reset();
                 // std::cerr << "[" << time::iso() << "] (WARNING) Failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
@@ -83,18 +78,19 @@ namespace flame::http {
 
             try {
                 php::value rv = true;
-                if(ib != svr_ptr->cb_.end()) rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
-                if(rv.typeof(php::TYPE::NO)) goto HANDLE_BREAK;
-                if(ih != svr_ptr->cb_.end()) rv = ih->second.call({req, res});
-                if(rv.typeof(php::TYPE::NO)) 
+                if (ib != svr_ptr->cb_.end()) rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
+                if (rv.typeof(php::TYPE::NO)) goto HANDLE_BREAK;
+                if (ih != svr_ptr->cb_.end()) rv = ih->second.call({req, res});
+                if (rv.typeof(php::TYPE::NO)) goto HANDLE_BREAK;
                 if (ia != svr_ptr->cb_.end()) rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
 HANDLE_BREAK:
-                if(!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/) {
-                    // if(!(res_ptr->status_ & server_response::STATUS_HEAD_SENT)) {
+                if (!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/) {
+                    // if (!(res_ptr->status_ & server_response::STATUS_HEAD_SENT)) {
                     //     res_ptr->set("status", 404);
                     //     res_ptr->set("body", nullptr);
                     // }
-                    if(gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_) {
+                    // 服务及将停止或服务器以进行关闭后，禁止连接复用
+                    if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_) {
                         res_->keep_alive(false);
                         res_->set(boost::beast::http::field::connection, "close");
                     }
@@ -108,15 +104,16 @@ namespace flame::http {
 
                     boost::system::error_code error;
                     boost::beast::http::async_write(socket_, *res_, ch[error]);
-                    if(error) {
+                    if (error) {
                         res_.reset();
                         req_.reset();
                         throw php::exception(zend_ce_exception
                             , (boost::format("Failed to write response: %s") % error.message()).str()
                             , error.value());
-                    } else start();
-                }/*else
-                {
+                    }
+                    else start();
+                }
+                /*else {
                     // Chunked 未结束 -> server_response::__destruct() -> finish()
                 }*/
             } catch(const php::exception& ex) {
@@ -134,11 +131,11 @@ namespace flame::http {
     }
 
     void _handler::write_head(server_response *res_ptr, coroutine_handler &ch) {
-        if(!res_) return;
+        if (!res_) return;
         res_ptr->build_ex(*res_);
         res_->chunked(true);
         res_->set(boost::beast::http::field::transfer_encoding, "chunked");
-        
+        // 服务及将停止或服务器以进行关闭后，禁止连接复用
         if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_) {
             res_->keep_alive(false);
             res_->set(boost::beast::http::field::connection, "close");
@@ -147,7 +144,7 @@ namespace flame::http {
 
         boost::beast::http::serializer<false, value_body<false>> sr(*res_);
         boost::beast::http::async_write_header(socket_, sr, ch[error]);
-        if(error) {
+        if (error) {
             res_.reset();
             req_.reset();
             throw php::exception(zend_ce_exception
@@ -157,8 +154,8 @@ namespace flame::http {
     }
 
     void _handler::write_body(server_response *res_ptr, php::string data, coroutine_handler &ch) {
-        if(!res_) return;
-        if(!res_->chunked())
+        if (!res_) return;
+        if (!res_->chunked())
             throw php::exception(zend_ce_error_exception
                 , "Failed to write_body: 'chunked' encoding required"
                 , -1);
@@ -176,7 +173,7 @@ namespace flame::http {
     }
 
     void _handler::write_end(server_response* res_ptr, coroutine_handler &ch) {
-        if(!res_) return;
+        if (!res_) return;
         if (!res_->chunked()) throw php::exception(zend_ce_error_exception
             , "Failed to write body: 'chunked' encoding required"
             , -1);
@@ -193,6 +190,10 @@ namespace flame::http {
 
     void _handler::write_file(server_response *res_ptr, std::string path, coroutine_handler &ch) {
         if (!res_) return;
+        if (!res_->chunked())
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write_body: 'chunked' encoding required"
+                , -1);
         std::ifstream file(path);
         char buffer[2048];
         std::size_t size;
diff --git a/src/http/client.cpp b/src/http/client.cpp
index 60d2624..ebf6280 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -42,7 +42,7 @@ namespace flame::http {
         int left;
         client_response* res_;
         while((msg = curl_multi_info_read(c_multi_, &left))) {
-            if(msg->msg == CURLMSG_DONE) {
+            if (msg->msg == CURLMSG_DONE) {
                 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &res_);
                 res_->c_final_ = msg->data.result;
                 res_->c_coro_.resume();
@@ -54,22 +54,24 @@ namespace flame::http {
 
     void client::c_socket_ready_cb(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action) {
         client* self = reinterpret_cast<client*>(poll->data);
-        if(error) action = CURL_CSELECT_ERR;
+        if (error) action = CURL_CSELECT_ERR;
         curl_multi_socket_action(self->c_multi_, fd, action, &self->c_still_);
         self->check_done();
-        if(self->c_still_ <= 0) self->c_timer_.cancel();
+        if (self->c_still_ <= 0) self->c_timer_.cancel();
     }
     
     int client::c_socket_cb(CURL* e, curl_socket_t fd, int action, void* data, void* sock_data) {
         client* self = reinterpret_cast<client*>(data);
         client_poll* poll = reinterpret_cast<client_poll*>(sock_data);
 
-        if(action == CURL_POLL_REMOVE) {
+        if (action == CURL_POLL_REMOVE) {
             curl_multi_assign(self->c_multi_, fd, nullptr);
-        }else if(poll == nullptr) {
+        }
+        else if (poll == nullptr) {
             poll = client_poll::create_poll(gcontroller->context_x, fd, c_socket_ready_cb, self);
             curl_multi_assign(self->c_multi_, fd, poll);
-        }else{
+        }
+        else {
             // 
         }
         poll->async_wait(action);
@@ -80,11 +82,11 @@ namespace flame::http {
         client* self = reinterpret_cast<client*>(data);
 
         self->c_timer_.cancel();
-        if(timeout_ms == 0) timeout_ms = 1;
-        if(timeout_ms > 0) {
+        if (timeout_ms == 0) timeout_ms = 1;
+        if (timeout_ms > 0) {
             self->c_timer_.expires_after(std::chrono::milliseconds(timeout_ms));
             self->c_timer_.async_wait([self] (const boost::system::error_code& err) {
-                if(err) return;
+                if (err) return;
                 curl_multi_socket_action(self->c_multi_, CURL_SOCKET_TIMEOUT, 0, &self->c_still_);
                 self->check_done();
             });
@@ -113,11 +115,11 @@ namespace flame::http {
     }
 
     php::value client::__construct(php::parameters& params) {
-        if(params.size() > 0) {
+        if (params.size() > 0) {
             php::array opts = params[0];
-            if(opts.exists("connection_per_host")) {
+            if (opts.exists("connection_per_host")) {
                 long c = opts.get("connection_per_host");
-                if(c < 1 || c > 512) {
+                if (c < 1 || c > 512) {
                     curl_multi_setopt(c_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS, c * 4);
                     curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, c * 2);
                     curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, c);
@@ -140,7 +142,7 @@ namespace flame::http {
 
     php::value client::exec_ex(const php::object& req) {
         auto req_ = static_cast<client_request*>(php::native(req));
-        if(req_->c_easy_ == nullptr) req_->c_easy_ = curl_easy_init();
+        if (req_->c_easy_ == nullptr) req_->c_easy_ = curl_easy_init();
         // curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETFUNCTION, c_socket_open_cb);
         // curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETDATA, this);
         // curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETFUNCTION, c_socket_close_cb);
@@ -176,9 +178,10 @@ namespace flame::http {
         req.set("header", php::array(0));
         req.set("cookie", php::array(0));
         req.set("body", nullptr);
-        if(params.length() > 1) {
+        if (params.length() > 1) {
             req.set("timeout", params[1]);
-        }else{
+        }
+        else {
             req.set("timeout", 3000);
         }
         return exec_ex(req);
@@ -190,7 +193,7 @@ namespace flame::http {
         req.set("header", php::array(0));
         req.set("cookie", php::array(0));
         req.set("body",       params[1]);
-        if(params.length() > 2) {
+        if (params.length() > 2) {
             req.set("timeout",params[2]);
         }else{
             req.set("timeout",     3000);
@@ -204,9 +207,10 @@ namespace flame::http {
         req.set("header", php::array(0));
         req.set("cookie", php::array(0));
         req.set("body",       params[1]);
-        if(params.length() > 2) {
+        if (params.length() > 2) {
             req.set("timeout",params[2]);
-        }else{
+        }
+        else {
             req.set("timeout",     3000);
         }
         return exec_ex(req);
@@ -218,9 +222,10 @@ namespace flame::http {
         req.set("header", php::array(0));
         req.set("cookie", php::array(0));
         req.set("body",         nullptr);
-        if(params.length() > 1) {
+        if (params.length() > 1) {
             req.set("timeout", params[1]);
-        }else{
+        }
+        else {
             req.set("timeout", 3000);
         }
         return exec_ex(req);
diff --git a/src/http/client_poll.cpp b/src/http/client_poll.cpp
index 1ca1ab8..a2877a2 100644
--- a/src/http/client_poll.cpp
+++ b/src/http/client_poll.cpp
@@ -11,11 +11,13 @@ namespace flame::http {
 		assert(x == 0);
 		::getsockname(fd, &addr, &size);
 		client_poll* poll;
-		if(type == SOCK_STREAM) {
+		if (type == SOCK_STREAM) {
 			poll = new client_poll_tcp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::tcp::v6() : boost::asio::ip::tcp::v4(), ::dup(fd));
-		}else if(type == SOCK_DGRAM) {
+		}
+		else if (type == SOCK_DGRAM) {
 			poll = new client_poll_udp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
-		}else{
+		}
+		else {
 			return nullptr;
 		}
 		poll->data = data;
@@ -32,14 +34,14 @@ namespace flame::http {
 	}
 
 	void client_poll::on_ready(const boost::system::error_code& error, int action) {
-		if(error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
+		if (error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
 		cb_(error, this, fd_, action);
-		if(action_ == CURL_POLL_REMOVE) delete this;
-		else if(action_ == action) async_wait();
+		if (action_ == CURL_POLL_REMOVE) delete this;
+		else if (action_ == action) async_wait();
 	}
 
 	void client_poll::async_wait(int action) {
-		if(action_ != action) {
+		if (action_ != action) {
 			action_ = action;
 			async_wait();
 		}
@@ -56,9 +58,9 @@ namespace flame::http {
 	void client_poll_tcp::async_wait() {
 		sock_.cancel();
 		// 重新监听
-		if(action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+		if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
 			sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
-		if(action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+		if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
 			sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
 	}
 	
@@ -72,9 +74,9 @@ namespace flame::http {
 	void client_poll_udp::async_wait() {
 		sock_.cancel();
 		// 重新监听
-		if(action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+		if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
 			sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
-		if(action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+		if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
 			sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
 	}
 
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index d1a600e..b13148d 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -32,7 +32,7 @@ namespace http {
 				{"body", php::TYPE::UNDEFINED, false, true},
 				{"timeout", php::TYPE::INTEGER, false, true},
 			})
-			.method<&client_request::__construct>("http_version", {
+			.method<&client_request::http_version>("http_version", {
 				{"version", php::TYPE::INTEGER},
 			})
 			.method<&client_request::ssl_pem>("ssl_pem", {
@@ -54,9 +54,8 @@ namespace http {
 		if (params.length() > 1 && !params[1].empty()) {
 			set("body", params[1]);
 			set("method", "POST");
-		}else{
-			set("method", "GET");
 		}
+		else set("method", "GET");
 		set("url", params[0]);
 		set("header", php::array(0));
 		set("cookie", php::array(0));
@@ -66,14 +65,14 @@ namespace http {
 	}
 
 	php::value client_request::__destruct(php::parameters &params) {
-		if(c_head_) curl_slist_free_all(c_head_);
-		if(c_easy_) curl_easy_cleanup(c_easy_);
+		if (c_head_) curl_slist_free_all(c_head_);
+		if (c_easy_) curl_easy_cleanup(c_easy_);
 		return nullptr;
 	}
 
 	php::value client_request::http_version(php::parameters& params) {
 		long v = static_cast<int>(params[0]);
-		if(v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST)
+		if (v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST)
 			throw php::exception(zend_ce_error_exception
 				, "Failed to set HTTP version: value out of range"
 				, -1);
@@ -86,11 +85,11 @@ namespace http {
 		curl_easy_setopt(c_easy_, CURLOPT_SSLCERTTYPE, "PEM");
 		php::string cert = params[0];
 		curl_easy_setopt(c_easy_, CURLOPT_SSLCERT, cert.c_str());
-		if(params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
+		if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
 			php::string pkey = params[1];
 			curl_easy_setopt(c_easy_, CURLOPT_SSLKEY, pkey.c_str());
 		}
-		if(params.size() > 2 && params[2].typeof(php::TYPE::STRING)) {
+		if (params.size() > 2 && params[2].typeof(php::TYPE::STRING)) {
 			php::string pass = params[2];
 			curl_easy_setopt(c_easy_, CURLOPT_KEYPASSWD, pass.c_str());
 		}
@@ -99,15 +98,15 @@ namespace http {
 
 	php::value client_request::ssl_verify(php::parameters& params) {
 		long v = static_cast<int>(params[0]), YES = 1, NO = 0;
-		if(v | CURLOPT_SSL_VERIFYPEER) 
+		if (v | CURLOPT_SSL_VERIFYPEER) 
 			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, YES);
 		else
 			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, NO);
-		if(v | CURLOPT_SSL_VERIFYHOST)
+		if (v | CURLOPT_SSL_VERIFYHOST)
 			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, YES);
 		else
 			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, NO);
-		if(v | CURLOPT_SSL_VERIFYSTATUS) 
+		if (v | CURLOPT_SSL_VERIFYSTATUS) 
 			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, YES);
 		else 
 			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, NO);
@@ -119,29 +118,27 @@ namespace http {
 		curl_easy_setopt(c_easy_, CURLOPT_TIMEOUT_MS, timeout);
 		// 目标请求地址
 		// ---------------------------------------------------------------------------
-        php::string u = get("url", true);
-		if(!u.typeof(php::TYPE::STRING)) 
+        php::string u = get("url");
+		if (!u.typeof(php::TYPE::STRING)) 
 			throw php::exception(zend_ce_type_error
 				, "Failed to build client request: 'url' typeof 'string' required"
 				, -1);
 		
 		curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
-		php::string m = get("method", true);
-		// curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
+		php::string m = get("method");
+		curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
 		// 头
 		// ---------------------------------------------------------------------------
-		if(c_head_ != nullptr) curl_slist_free_all(c_head_);
+		if (c_head_ != nullptr) curl_slist_free_all(c_head_);
 		std::string ctype;
 		long keepalive = 1;
 		php::array header = get("header");
-		for(auto i=header.begin(); i!=header.end(); ++i) {
+		for (auto i=header.begin(); i!=header.end(); ++i) {
 			std::string key = i->first;
 			std::string val = i->second;
-			if(strncasecmp(key.c_str(), "content-type", 12) == 0) {
-				ctype = val;
-			}else if(strncasecmp(key.c_str(), "connection", 10) == 0 && strncasecmp(val.c_str(), "close", 5) == 0) {
-				keepalive = 0;
-			}
+			if (strncasecmp(key.c_str(), "content-type", 12) == 0) ctype = val;
+			else if (strncasecmp(key.c_str(), "connection", 10) == 0
+				&& strncasecmp(val.c_str(), "close", 5) == 0) keepalive = 0;
 			c_head_ = curl_slist_append(c_head_, (boost::format("%s: %s") % key % val).str().c_str());
 		}
 		c_head_ = curl_slist_append(c_head_, "Expect: ");
@@ -151,7 +148,7 @@ namespace http {
 		// ---------------------------------------------------------------------------
 		php::array cookie = get("cookie");
 		php::buffer cookies;
-		for(auto i=cookie.begin(); i!=cookie.end(); ++i) {
+		for (auto i=cookie.begin(); i!=cookie.end(); ++i) {
 			php::string key = i->first;
 			php::string val = i->second.to_string();
 			val = php::url_encode(val.c_str(), val.size());
@@ -162,16 +159,18 @@ namespace http {
 			cookies.push_back(' ');
 		}
 		php::string cookie_str = std::move(cookies);
-		if(cookie_str.size() > 0) curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
+		if (cookie_str.size() > 0) curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
 		// 体
 		// ---------------------------------------------------------------------------
 		php::string body = get("body");
-		if(ctype.empty()) ctype.assign("application/x-www-form-urlencoded", 33);
-		if(body.empty()) {
-
-		}else if(body.instanceof(php::class_entry<client_body>::entry())) {
+		if (ctype.empty()) ctype.assign("application/x-www-form-urlencoded", 33);
+		if (body.empty()) {
+			// TODO 空 BODY 的处理流程？
+		}
+		else if (body.instanceof(php::class_entry<client_body>::entry())) {
 			// TODO multipart support
-		}else{
+		}
+		else {
 			body = ctype_encode(ctype, body);
 			// 注意: CURLOPT_POSTFIELDS 仅"引用" body 数据
 			set("body", body);
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index 84b50cf..1e5e471 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -24,7 +24,7 @@ namespace http {
 		return get("raw_body");
 	}
 	void client_response::build_ex() {
-		if(c_final_ != 0) throw php::exception(zend_ce_exception
+		if (c_final_ != 0) throw php::exception(zend_ce_exception
 			, (boost::format("Failed to execute HTTP request: %s") % c_error_).str()
 			, c_final_);
 		// 响应码
@@ -38,11 +38,13 @@ namespace http {
 		// 响应体
 		php::string rbody = std::move(c_body_);
 		set("raw_body", rbody);
-		if(c_head_.exists("content_type")) {
+		if (c_head_.exists("content-type")) {
 			php::string ctype = c_head_.get("content-type");
+			std::cout << "content-type: " << ctype << "\n";
 			std::string_view sv {ctype.c_str(), ctype.size()};
 			set("body", ctype_decode(sv, rbody));
-		}else{
+		}
+		else {
 			set("body", rbody);
 		}
 	}
@@ -60,12 +62,13 @@ namespace http {
 
 		std::string_view sv{ buffer, size };
 		auto psep = sv.find_first_of(':', 1);
-		if(psep != sv.npos) {
+		if (psep != sv.npos) {
 			auto pval = sv.find_first_not_of(' ', psep + 1);
 			php::string key = php::lowercase(buffer, psep);
-			if(pval == sv.npos) {
+			if (pval == sv.npos) {
 				self->c_head_.set( key, php::string(0) );
-			}else{
+			}
+			else {
 				self->c_head_.set( key, php::string( buffer + pval, size - pval - 2)); // trailing \r\n
 			}
 		}
diff --git a/src/http/http.cpp b/src/http/http.cpp
index a24189b..5248bfa 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -46,7 +46,7 @@ namespace flame::http {
 		server_response::declare(ext);
 	}
 	static void init_guard() {
-		if(!client_)
+		if (!client_)
 			throw php::exception(zend_ce_error_exception
 				, "Failed to execute HTTP request: exception or missing 'flame\\init()' ?"
 				, -1);
@@ -72,43 +72,51 @@ namespace flame::http {
 		return client_->exec(params);
 	}
 	php::string ctype_encode(std::string_view ctype, const php::value& v) {
-		if(v.typeof(php::TYPE::STRING)) return v;
+		if (v.typeof(php::TYPE::STRING)) return v;
 
 		php::value r = v;
-		if(ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
-			if(r.typeof(php::TYPE::ARRAY)) {
+		if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+			if (r.typeof(php::TYPE::ARRAY)) {
 				r = php::callable("http_build_query")({v});
-			}else{
+			}
+			else {
 				r.to_string();
 			}
-		}else if(ctype.compare(0, 16, "application/json") == 0) {
+		}
+		else if (ctype.compare(0, 16, "application/json") == 0) {
 			r = php::json_encode(r);
-		}else{
+		}
+		else {
 			r.to_string();
 		}
 		return r;
 	}
 	php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* files) {
-		if(ctype.compare(0, 16, "application/json") == 0) {
+		if (ctype.compare(0, 16, "application/json") == 0) {
 			return php::json_decode(v);
-		}else if(ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+		}
+		else if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
 			php::array data(4);
 			php::callable("parse_str")({v, data.make_ref()});
 			return data;
-		}else if(ctype.compare(0, 19, "multipart/form-data") == 0) {
+		}
+		else if (ctype.compare(0, 19, "multipart/form-data") == 0) {
 			std::string boundary;
 			std::string field;
 			php::array meta(4);
 			parser::separator_parser<std::string, std::string> p1('\0','\0','=','"','"',';', [&boundary, &field, &meta] (std::pair<std::string, std::string> entry) {
-				if(entry.first == "boundary") {
+				if (entry.first == "boundary") {
 					boundary = std::move(entry.second);
-				}else if(entry.first == "content-disposition") {
+				}
+				else if (entry.first == "content-disposition") {
 					// 此项头信息没有用途
-				}else if(entry.first == "name") {
+				}
+				else if (entry.first == "name") {
 					// 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
 					// 表单字段名
 					field = std::move(entry.second);
-				}else{
+				}
+				else {
 					meta.set(entry.first, entry.second);
 				}
 			});
@@ -118,31 +126,35 @@ namespace flame::http {
 
 			php::array data(8);
 			parser::multipart_parser<std::string, php::buffer> p2(boundary, [&p1, &field, &meta, &data, &files] (std::pair<std::string, php::buffer> entry) {
-				if(entry.first.size() == 0) { // Part Data
-					if(meta.exists("filename") > 0) {
-						if(files) { // 接收对应文件
+				if (entry.first.size() == 0) { // Part Data
+					if (meta.exists("filename") > 0) {
+						if (files) { // 接收对应文件
 							// 上传文件 meta 
 							meta["size"] = entry.second.size();
 							meta["data"] = std::move(entry.second);
 							files->set(field, meta);
 						}
-					}else{
+					}
+					else {
 						data.set(field, std::move(entry.second));
 					}
 					meta = php::array(4);
-				}else{ // Part Header
+				}
+				else { // Part Header
 					php::lowercase_inplace(entry.first.data(), entry.first.size());
 					std::size_t end = std::string_view(entry.second.data(), entry.second.size()).find_first_of(';');
-					if(end == std::string::npos) {
+					if (end == std::string::npos) {
 						meta[entry.first] = std::move(entry.second);
-					}else if(entry.first == "content-disposition") {
+					}
+					else if (entry.first == "content-disposition") {
 						// 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
 						std::size_t begin = end + 1;
-						if(entry.second.size() > begin + 2) {
+						if (entry.second.size() > begin + 2) {
 							p1.parse(entry.second.data() + begin, entry.second.size() - begin);
 							p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
 						}
-					}else{
+					}
+					else {
 						meta[entry.first] = php::string(entry.second.data(), end);
 					}
 				}
@@ -150,8 +162,7 @@ namespace flame::http {
 			p2.parse(v.data(), v.size());
 
 			return data;
-		}else{
-			return v;
 		}
+		else return v;
 	}
 }
diff --git a/src/http/server.cpp b/src/http/server.cpp
index 9101ce5..01549d1 100644
--- a/src/http/server.cpp
+++ b/src/http/server.cpp
@@ -136,7 +136,7 @@ namespace flame::http
             boost::system::error_code err;
             accp_.async_accept(sock_, ch[err]);
             if (err == boost::asio::error::operation_aborted) break; 
-            else if(err) throw php::exception(zend_ce_exception
+            else if (err) throw php::exception(zend_ce_exception
                 , (boost::format("Failed to accept connection: %s") % err.message()).str()
                 , err.value());
             else std::make_shared<_handler>(this, std::move(sock_))->start();
@@ -145,7 +145,7 @@ namespace flame::http
     }
 
     php::value server::close(php::parameters &params) {
-        if(closed_) return nullptr;
+        if (closed_) return nullptr;
         closed_ = true;
         accp_.cancel();
         accp_.close();
diff --git a/src/http/server_request.cpp b/src/http/server_request.cpp
index 86b2dad..f7435b7 100644
--- a/src/http/server_request.cpp
+++ b/src/http/server_request.cpp
@@ -21,6 +21,9 @@ namespace http {
 			.method<&server_request::__construct>("__construct", {}, php::PRIVATE);
 
 		ext.add(std::move(class_server_request));
+	}
+	server_request::server_request() {
+		
 	}
 	server_request::~server_request() {
 
@@ -34,28 +37,30 @@ namespace http {
 		// 目标请求地址
 		auto target = ctr_.target();
 		std::shared_ptr<php::url> url_ = php::parse_url(target.data(), target.size());
-		if(url_->path) {
+		if (url_->path) {
 			set("path", php::string(url_->path));
-		}else{
+		}
+		else {
 			set("path", php::string("/", 1));
 		}
-		if(url_->query) {
+		if (url_->query) {
 			php::array query(4);
 			php::callable("parse_str")({php::string(url_->query), query.make_ref()});
 			set("query", query);
 		}
 		php::array header(4);
-		for(auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
+		for (auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
 			php::string key {i->name_string().data(), i->name_string().size()};
 			php::string val {i->value().data(), i->value().size()};
 
-			if(key.size() == 6 && strncasecmp(key.c_str(), "cookie", 6) == 0) {
+			if (key.size() == 6 && strncasecmp(key.c_str(), "cookie", 6) == 0) {
 				php::array cookie(4);
 				parser::separator_parser<std::string, php::buffer> p1('\0','\0','=','\0','\0',';', [&cookie] (std::pair<std::string, php::buffer> entry) {
-					if(entry.second.size() > 0) {
+					if (entry.second.size() > 0) {
 						entry.second.shrink( php::url_decode_inplace(entry.second.data(), entry.second.size()) );
 						cookie.set(entry.first, php::string(std::move(entry.second)));
-					}else{
+					}
+					else {
 						cookie.set(entry.first, php::string(0));
 					}
 				});
@@ -70,16 +75,17 @@ namespace http {
 		set("header", header);
 
 		php::string body = ctr_.body();
-		if(body.typeof(php::TYPE::STRING)) {
+		if (body.typeof(php::TYPE::STRING)) {
 			auto ctype = ctr_.find(boost::beast::http::field::content_type);
-			if(ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0) {
+			if (ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0) {
 				set("raw_body", body); // 不在 multipart 时保留原始数据
 			}
-			if(ctype != ctr_.end()) { // 存在时按照类型进行解析
+			if (ctype != ctr_.end()) { // 存在时按照类型进行解析
 				php::array files(4);
 				set("body", ctype_decode(ctype->value(), body, &files));
 				set("file", files);
-			}else{ // 不存在与 raw_body 相同
+			}
+			else { // 不存在与 raw_body 相同
 				set("body", body);
 			}
 		}
diff --git a/src/http/server_request.h b/src/http/server_request.h
index 0f462a5..aefdece 100644
--- a/src/http/server_request.h
+++ b/src/http/server_request.h
@@ -1,12 +1,14 @@
 #pragma once
+#include "../vendor.h"
 
 namespace flame::http {
 	class handler;
 	class server_request: public php::class_base {
 	public:
 		static void declare(php::extension_entry& ext);
-		php::value __construct(php::parameters& params);
+		server_request();
 		~server_request();
+		php::value __construct(php::parameters& params);
 	private:
 		void build_ex(const boost::beast::http::message<true, value_body<true>>& ctr);
 
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index 7b27ea6..26d97be 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -4,6 +4,7 @@
 #include "server_response.h"
 
 namespace flame::http {
+	
 	void server_response::declare(php::extension_entry& ext) {
 		php::class_entry<server_response> class_server_response("flame\\http\\server_response");
 		class_server_response
@@ -38,86 +39,84 @@ namespace flame::http {
 
 		ext.add(std::move(class_server_response));
 	}
+
 	server_response::server_response()
 	: status_(0) {
 
 	}
-	server_response::~server_response()
-	{
+
+	server_response::~server_response() {
 
 	}
 	// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
-	php::value server_response::__construct(php::parameters& params)
-	{
+	php::value server_response::__construct(php::parameters& params) {
 		return nullptr;
 	}
-	php::value server_response::__destruct(php::parameters& params)
-	{
+
+	php::value server_response::__destruct(php::parameters& params) {
 		// 响应还未完全结束, 由 handler 代理 last_chunk 响应
-		if(handler_) 
-		{
+		if (handler_)  {
 			coroutine_handler ch{coroutine::current};
 			handler_->finish(this, ch);
 		}
 		return nullptr;
 	}
+
 	php::value server_response::set_cookie(php::parameters& params) {
 		php::array cookie(4);
 		php::string name = params[0], val;
 		name.to_string();
 		cookie.set("name", name);
-		if(params.size() > 1) {
+		if (params.size() > 1) {
 			val = params[1];
 			val.to_string();
-		}else{
-			val = php::string("");
 		}
+		else val = php::string("");
 		cookie.set("value", val);
-		if(params.size() > 2) {
+		if (params.size() > 2) {
 			// php::object expire = php::datetime(time::now() +  * 1000);
 			cookie.set("expire", params[2].to_integer());
 		}
-		if(params.size() > 3) {
+		if (params.size() > 3) {
 			php::string path = params[3];
 			path.to_string();
 			cookie.set("path", path);
 		}
-		if(params.size() > 4) {
+		if (params.size() > 4) {
 			php::string domain = params[4];
 			domain.to_string();
 			cookie.set("domain", domain);
 		}
-		if(params.size() > 5) {
+		if (params.size() > 5) {
 			bool secure = params[5];
 			cookie.set("secure", secure);
 		}
-		if(params.size() > 6) {
+		if (params.size() > 6) {
 			bool http_only = params[5];
 			cookie.set("http_only", http_only);
 		}
 
-		php::array cookies = get("cookie", true);
-		if(cookies.typeof(php::TYPE::NULLABLE)) {
-			cookies = php::array(4);
-		}
+		php::array cookies = get("cookie");
+		if (cookies.typeof(php::TYPE::NULLABLE)) cookies = php::array(4);
 		cookies.set(cookies.size(), cookie);
 		return nullptr;
 	}
 	// chunked encoding 用法
 	php::value server_response::write_header(php::parameters& params) {
-		if((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT))
+		if ((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT))
 			throw php::exception(zend_ce_error_exception
 				, "Failed to write header: already sent"
 				, -1);
 		
 		status_ |= STATUS_HEAD_SENT;
-		if(params.size() > 0) set("status", params[0].to_integer());
+		if (params.size() > 0) set("status", params[0].to_integer());
 		coroutine_handler ch{coroutine::current};
 		handler_->write_head(this, ch);
 		return nullptr;
 	}
+
 	php::value server_response::write(php::parameters& params) {
-		if(status_ & STATUS_BODY_END)
+		if (status_ & STATUS_BODY_END)
 			throw php::exception(zend_ce_error_exception
 				, "Failed to write body: response already done"
 				, -1);
@@ -147,7 +146,8 @@ namespace flame::http {
 			status_ |= STATUS_BODY_SENT;
 			php::string chunk = params[0].to_string();
 			handler_->write_body(this, chunk, ch);
-		} else {
+		}
+		else {
 			status_ |= STATUS_BODY_END;
 			handler_->write_end(this, ch);
 			handler_.reset();
@@ -177,62 +177,63 @@ namespace flame::http {
 	
 	// 支持 content-length 形式的用法
 	void server_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
-		if(status_ & STATUS_BUILT) return; // 重复使用
+		if (status_ & STATUS_BUILT) return; // 重复使用
 		status_ |= STATUS_BUILT; // 在 chunked_writer 中设置
 		ctr_.result( get("status").to_integer() ); // 非法 status_code 会抛出异常
-		php::array headers = get("header", true);
-		if(headers.typeof(php::TYPE::ARRAY)) {
-			for(auto i=headers.begin(); i!=headers.end(); ++i) {
+		php::array headers = get("header");
+		if (headers.typeof(php::TYPE::ARRAY)) {
+			for (auto i=headers.begin(); i!=headers.end(); ++i) {
 				php::string key { i->first };
 				php::string val { i->second };
 				ctr_.set(std::string_view(key.c_str(), key.size()), val);
 			}
 		}
-		php::array cookies = get("cookie", true);
-		if(cookies.typeof(php::TYPE::ARRAY)) {
+		php::array cookies = get("cookie");
+		if (cookies.typeof(php::TYPE::ARRAY)) {
 			for(auto i=cookies.begin(); i!=cookies.end(); ++i) {
 				php::array  cookie = i->second;
 				std::string buffer;
 				buffer.append(cookie.get("name"));
 				buffer.push_back('=');
 				php::string value = cookie.get("value");
-				if(!value.empty()) {
+				if (!value.empty()) {
 					buffer.push_back('"');
 					buffer.append(php::url_encode(value.c_str(), value.size()));
 					buffer.push_back('"');
 				}
-				if(cookie.exists("expire")) {
+				if (cookie.exists("expire")) {
 					std::int64_t expire = cookie.get("expire");
 					// 范围 30 天内的数值, 按时间长度计算
-					if(expire < 30 * 86400) {
+					if (expire < 30 * 86400) {
 						buffer.append("; Max-Age=", 10);
 						buffer.append(std::to_string(expire));
 						buffer.append("; Expire=", 9);
 						php::object date = php::datetime(expire * 1000 + std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count());
 						buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
-					}else{
+					}
+					else {
 					// 超过上面范围, 按时间点计算
 						buffer.append("; Expire=", 9);
 						php::object date = php::datetime(expire * 1000);
 						buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
 					}
 				}
-				if(cookie.exists("path")) {
+				if (cookie.exists("path")) {
 					buffer.append("; Path=", 7);
 					buffer.append(cookie.get("path"));
 				}
-				if(cookie.exists("domain")) {
+				if (cookie.exists("domain")) {
 					buffer.append("; Domain=", 9);
 					buffer.append(cookie.get("domain"));
 				}
-				if(!cookie.get("secure").empty()) buffer.append("; Secure", 8);
-				if(!cookie.get("http_only").empty()) buffer.append("; HttpOnly", 10);
+				if (!cookie.get("secure").empty()) buffer.append("; Secure", 8);
+				if (!cookie.get("http_only").empty()) buffer.append("; HttpOnly", 10);
 				ctr_.insert("Set-Cookie", buffer);
 			}
 		}
 CTYPE_AGAIN:
 		auto ctype = ctr_.find(boost::beast::http::field::content_type);
-		if(ctype == ctr_.end()) {
+		if (ctype == ctr_.end()) {
 			ctr_.set(boost::beast::http::field::content_type, "text/plain");
 			goto CTYPE_AGAIN;
 		}
diff --git a/src/http/value_body.cpp b/src/http/value_body.cpp
index 40d6062..b5fd426 100644
--- a/src/http/value_body.cpp
+++ b/src/http/value_body.cpp
@@ -6,17 +6,21 @@ namespace http {
     void value_body_reader::init(boost::optional<std::uint64_t> n, boost::system::error_code& error) {
         error.assign(0, error.category());
     }
+
     void value_body_reader::finish(boost::system::error_code& error) {
         v_ = std::move(b_);
         error.assign(0, error.category());
     }
+
     void value_body_writer::init(boost::system::error_code& error) {
         error.assign(0, error.category());
     }
+
     boost::optional<std::pair<value_body_writer::const_buffers_type, bool>> value_body_writer::get(boost::system::error_code& error) {
-        if(v_.empty()) {
+        if (v_.empty()) {
             return {{boost::asio::const_buffer(nullptr, 0), false}};
-        }else{
+        }
+        else {
             php::string data = v_;
             error.assign(0, error.category());
             return {{boost::asio::const_buffer(data.data(), data.size()), false}};
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index 481cb24..6bf57e7 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -51,7 +51,7 @@ namespace flame::kafka
     }
 
     _consumer::~_consumer() {
-        if(conn_) {
+        if (conn_) {
             // rd_kafka_consumer_close(conn_);
             rd_kafka_topic_partition_list_destroy(tops_);
             rd_kafka_destroy(conn_);
@@ -65,7 +65,7 @@ namespace flame::kafka
             ch.resume();
         });
         ch.suspend();
-        if(err != RD_KAFKA_RESP_ERR_NO_ERROR) 
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR) 
             throw php::exception(zend_ce_exception
                 , (boost::format("failed to subcribe Kafka topics: %s") % rd_kafka_err2str(err)).str()
                 , err);
@@ -80,7 +80,7 @@ namespace flame::kafka
         boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]()
         {
             msg = rd_kafka_consumer_poll(conn_, 500);
-            if(!msg) err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
+            if (!msg) err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
             else if (msg->err) {
                 err = msg->err;
                 rd_kafka_message_destroy(msg);
@@ -95,11 +95,13 @@ namespace flame::kafka
                 return;
             } // 消费已被关闭
             else goto POLL_MESSAGE;
-        } else if(err != RD_KAFKA_RESP_ERR_NO_ERROR) {
+        }
+        else if (err != RD_KAFKA_RESP_ERR_NO_ERROR) {
             throw php::exception(zend_ce_exception
                 , (boost::format("failed to consume Kafka message: %s") % rd_kafka_err2str(err)).str()
                 , err);
-        } else {
+        }
+        else {
             php::object obj(php::class_entry<message>::entry());
             message* ptr = static_cast<message*>(php::native(obj));
             ptr->build_ex(msg); // msg 交由 message 对象管理
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index 9eded7c..d8f7e85 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -51,7 +51,8 @@ namespace flame::kafka
                     RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
                     RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
                     RD_KAFKA_V_END);
-            } else { // 无 KEY 时默认 partitioner 会随机分配
+            }
+            else { // 无 KEY 时默认 partitioner 会随机分配
                 err = rd_kafka_producev(conn_,
                     RD_KAFKA_V_TOPIC(topic.c_str()),
                     RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
@@ -66,7 +67,7 @@ namespace flame::kafka
         });
         ch.suspend();
         if (err != RD_KAFKA_RESP_ERR_NO_ERROR) {
-            if(hdrs) rd_kafka_headers_destroy(hdrs);  // 发生错误时, 需要手动销毁
+            if (hdrs) rd_kafka_headers_destroy(hdrs);  // 发生错误时, 需要手动销毁
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to publish Kafka message: %s") % rd_kafka_err2str(err)).str()
                 , err);
@@ -74,6 +75,6 @@ namespace flame::kafka
     }
 
     void _producer::flush(coroutine_handler& ch) {
-        if(conn_) rd_kafka_flush(conn_, 10000);
+        if (conn_) rd_kafka_flush(conn_, 10000);
     }
 } // namespace flame::kafka
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index 7dc4c9d..ef29e8f 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -41,7 +41,7 @@ namespace flame::kafka {
                     try {
                         // consume 本身可能出现异常，不应导致进程停止
                         std::optional<php::object> m = q.pop(ch);
-                        if(m) cb_.call({m.value()});
+                        if (m) cb_.call({m.value()});
                         else break;
                     } catch(const php::exception& ex) {
                         auto ft = gcontroller->cbmap->equal_range("exception");
@@ -51,7 +51,7 @@ namespace flame::kafka {
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
                     }
                 }
-                if(--count == 0) ch_run.resume();
+                if (--count == 0) ch_run.resume();
                 return nullptr;
             }));
         }
diff --git a/src/kafka/message.cpp b/src/kafka/message.cpp
index 1efc91a..c294583 100644
--- a/src/kafka/message.cpp
+++ b/src/kafka/message.cpp
@@ -27,7 +27,7 @@ namespace flame::kafka {
     }
     message::~message()
     {
-        if(msg_) rd_kafka_message_destroy(msg_);
+        if (msg_) rd_kafka_message_destroy(msg_);
     }
     void message::build_ex(rd_kafka_message_t* msg)
     {
@@ -39,7 +39,7 @@ namespace flame::kafka {
         set("key", php::string((const char*)msg->key, msg->key_len));
         set("offset", msg->offset);
         rd_kafka_headers_t* hdrs;
-        if(rd_kafka_message_headers(msg, &hdrs) == RD_KAFKA_RESP_ERR_NO_ERROR)
+        if (rd_kafka_message_headers(msg, &hdrs) == RD_KAFKA_RESP_ERR_NO_ERROR)
         {
             set("header", hdrs2array(hdrs));
         }
@@ -49,11 +49,11 @@ namespace flame::kafka {
     }
     php::value message::__construct(php::parameters& params)
     {
-        if(params.size() > 0)
+        if (params.size() > 0)
         {
             set("payload", params[0].to_string());
         }
-        if(params.size() > 1)
+        if (params.size() > 1)
         {
             set("key", params[1].to_string());
         }
diff --git a/src/kafka/producer.cpp b/src/kafka/producer.cpp
index 691cc8d..31e3578 100644
--- a/src/kafka/producer.cpp
+++ b/src/kafka/producer.cpp
@@ -4,10 +4,9 @@
 #include "_producer.h"
 #include "producer.h"
 
-namespace flame::kafka
-{
-    void producer::declare(php::extension_entry &ext)
-    {
+namespace flame::kafka {
+
+    void producer::declare(php::extension_entry &ext) {
         php::class_entry<producer> class_producer("flame\\kafka\\producer");
         class_producer
             .method<&producer::__construct>("__construct", {}, php::PRIVATE)
@@ -19,24 +18,23 @@ namespace flame::kafka
             .method<&producer::flush>("flush");
         ext.add(std::move(class_producer));
     }
-    php::value producer::__construct(php::parameters &params)
-    {
+
+    php::value producer::__construct(php::parameters &params) {
         return nullptr;
     }
-    php::value producer::__destruct(php::parameters &params)
-    {
-        if(pd_) {
+
+    php::value producer::__destruct(php::parameters &params) {
+        if (pd_) {
             coroutine_handler ch {coroutine::current};
             pd_->flush(ch);
         }
         return nullptr;
     }
-    php::value producer::publish(php::parameters &params)
-    {
+
+    php::value producer::publish(php::parameters &params) {
         php::string topic = params[0].to_string(), key, payload;
         php::array header;
-        if (params[1].instanceof (php::class_entry<message>::entry()))
-        {
+        if (params[1].instanceof (php::class_entry<message>::entry())) {
             php::object msg = params[1];
             key = msg.get("key").to_string();
             payload = msg.get("payload").to_string();
@@ -46,26 +44,17 @@ namespace flame::kafka
                 header = php::array(0);
             }
         }
-        else
-        {
+        else {
             payload = params[1].to_string();
-            if (params.size() > 2)
-            {
+            if (params.size() > 2) {
                 key = params[2].to_string();
             }
-            else
-            {
+            else {
                 key = php::string(0);
             }
-            if (params.size() > 3)
-            {
-                if (params[3].typeof(php::TYPE::ARRAY))
-                {
-                    header = params[4];
-                }else
-                {
-                    header = php::array(0);
-                }
+            if (params.size() > 3) {
+                if (params[3].typeof(php::TYPE::ARRAY)) header = params[4];
+                else header = php::array(0);
             }
         }
 
@@ -73,8 +62,8 @@ namespace flame::kafka
         pd_->publish(topic, key, payload, header, ch);
         return nullptr;
     }
-    php::value producer::flush(php::parameters &params)
-    {
+    
+    php::value producer::flush(php::parameters &params) {
         coroutine_handler ch {coroutine::current};
         pd_->flush(ch);
         return nullptr;
diff --git a/src/log/log.cpp b/src/log/log.cpp
index 4c53d52..0e4d531 100644
--- a/src/log/log.cpp
+++ b/src/log/log.cpp
@@ -27,7 +27,7 @@ namespace flame::log {
     static int level = 0;
     
     static void write_ex(int lv, php::parameters& params) {
-        if(lv < level) return;
+        if (lv < level) return;
         std::ostream& os = lv > LEVEL_WARNING ? std::cout : std::cerr;
         os << '[' << time::iso() << "] (";
         os << LEVEL_S[lv];
@@ -70,7 +70,7 @@ namespace flame::log {
 
     void declare(php::extension_entry &ext) {
         gcontroller->on_init([] (const php::array& options) {
-            if(options.exists("level")) level = options.get("level").to_integer();
+            if (options.exists("level")) level = options.get("level").to_integer();
             else level = 0;
         });
         ext
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index c66298e..b364857 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -68,7 +68,8 @@ namespace flame::mongodb
             // 参考: http://mongoc.org/libmongoc/current/mongoc_cursor_new_from_command_reply_with_opts.html
             *std::get_deleter<void (*)(bson_t*)>(rep) = fake_deleter;
             return std::move(obj);
-        } else {
+        }
+        else {
             php::array r(4);
 
             bson_iter_t i;
@@ -77,7 +78,7 @@ namespace flame::mongodb
             while (bson_iter_next(&i)) {
                 const char* key = bson_iter_key(&i);
                 std::uint32_t len = bson_iter_key_len(&i);
-                if(len > 0 && key[0] == '$') continue;
+                if (len > 0 && key[0] == '$') continue;
                 r.set(php::string(key, len), iter2value(&i));
             }
             return std::move(r);
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
index 3e21ced..b0a3a1f 100644
--- a/src/mongodb/_connection_lock.cpp
+++ b/src/mongodb/_connection_lock.cpp
@@ -22,7 +22,7 @@ namespace flame::mongodb
         auto err = std::make_shared<bson_error_t>();
         const bson_t *doc;
         boost::asio::post(guard_, [this, &cs, &ch, &has, &err, &doc]() {
-            if(!mongoc_cursor_next(cs.get(), &doc)) has = mongoc_cursor_error(cs.get(), err.get());
+            if (!mongoc_cursor_next(cs.get(), &doc)) has = mongoc_cursor_error(cs.get(), err.get());
             ch.resume();
         });
         ch.suspend();
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index df13879..f9bfe44 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -2,15 +2,13 @@
 #include "mongodb.h"
 #include "_connection_pool.h"
 
-namespace flame::mongodb
-{
+namespace flame::mongodb {
+    
     _connection_pool::_connection_pool(const std::string& url)
-    : guard_(gcontroller->context_y)
-    {
+    : guard_(gcontroller->context_y) {
         std::unique_ptr<mongoc_uri_t, void (*)(mongoc_uri_t *)> uri(mongoc_uri_new(url.c_str()), mongoc_uri_destroy);
         const bson_t* options = mongoc_uri_get_options(uri.get());
-        if (!bson_has_field(options, MONGOC_URI_READPREFERENCE))
-        {
+        if (!bson_has_field(options, MONGOC_URI_READPREFERENCE)) {
 			mongoc_read_prefs_t* pref = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); // secondaryPreferred
 			mongoc_uri_set_read_prefs_t(uri.get(), pref);
 			mongoc_read_prefs_destroy(pref);
@@ -20,11 +18,12 @@ namespace flame::mongodb
 
         p_ = mongoc_client_pool_new(uri.get());
     }
+
     _connection_pool::~_connection_pool() {
         mongoc_client_pool_destroy(p_);
     }
-    std::shared_ptr<mongoc_client_t> _connection_pool::acquire(coroutine_handler &ch)
-    {
+
+    std::shared_ptr<mongoc_client_t> _connection_pool::acquire(coroutine_handler &ch) {
         std::shared_ptr<mongoc_client_t> conn;
         auto self = shared_from_this();
         boost::asio::post(guard_, [this, self, &conn, &ch] () {
@@ -36,15 +35,15 @@ namespace flame::mongodb
                 ch.resume();
             });
             mongoc_client_t* c = mongoc_client_pool_try_pop(p_);
-            if(c) release(c);
+            if (c) release(c);
         });
         ch.suspend();
         return conn;
     }
+
     void _connection_pool::release(mongoc_client_t* c) {
-        if(await_.empty()) {
-            mongoc_client_pool_push(p_, c);
-        }else{
+        if (await_.empty()) mongoc_client_pool_push(p_, c);
+        else {
             auto cb = await_.front();
             await_.pop_front();
             cb(c);
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index fa12c61..ceb1f81 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -172,11 +172,14 @@ namespace flame::mongodb {
                     project = php::array(8);
                     for (auto i = fields.begin(); i != fields.end(); ++i)
                         project.set(php::string(i->second), 1);
-                } else project = fields;
-            } else if (params[1].typeof(php::TYPE::STRING)) { // 单个字段
+                }
+                else project = fields;
+            }
+            else if (params[1].typeof(php::TYPE::STRING)) { // 单个字段
                 php::array project(2);
                 project.set(php::string(params[1]), 1);
-            } else goto PROJECTION_ALL;
+            }
+            else goto PROJECTION_ALL;
 
             if (!project.exists("_id")) project.set("_id", 0);
             cmd.set("projection", project);
@@ -254,13 +257,13 @@ namespace flame::mongodb {
             , -1);
         cmd.set("pipeline", pipeline);
         cmd.set("cursor", php::object(php::CLASS(zend_standard_class_def)));
-        if(params.size() > 1) {
+        if (params.size() > 1) {
             php::array options = params[1];
             for(auto i=options.begin(); i!=options.end(); ++i)
                 cmd.set(i->first.to_string(), i->second);
         }
         int execute_type = _connection_base::COMMAND_READ;
-        if(params.size() > 2)
+        if (params.size() > 2)
             execute_type = params[2].to_integer();
         
         coroutine_handler ch {coroutine::current};
@@ -272,14 +275,14 @@ namespace flame::mongodb {
         php::array cmd(8);
         cmd.set("query", params[0]);
         cmd.set("remove", true);
-        if(params.size() > 1) {
+        if (params.size() > 1) {
             php::value f = params[1];
-            if(f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
+            if (f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
         }
-        if(params.size() > 2) cmd.set("upsert", params[2].to_boolean());
-        if(params.size() > 3) {
+        if (params.size() > 2) cmd.set("upsert", params[2].to_boolean());
+        if (params.size() > 3) {
             php::value f = params[3];
-            if(f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
+            if (f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
         }
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
@@ -289,22 +292,20 @@ namespace flame::mongodb {
     php::value collection::find_and_update(php::parameters& params) {
         php::array cmd(8);
         cmd.set("query", params[0]);
-        if(params.size() > 1) {
+        if (params.size() > 1) {
             php::value f = params[1];
-            if(f.typeof(php::TYPE::ARRAY)) cmd.set("update", f);
+            if (f.typeof(php::TYPE::ARRAY)) cmd.set("update", f);
         }
-        if(params.size() > 2) {
+        if (params.size() > 2) {
             php::value f = params[2];
-            if(f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
+            if (f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
         }
-        if(params.size() > 3)
-            cmd.set("upsert", params[3].to_boolean());
-        if(params.size() > 4) {
+        if (params.size() > 3) cmd.set("upsert", params[3].to_boolean());
+        if (params.size() > 4) {
             php::value f = params[4];
-            if(f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
+            if (f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
         }
-        if(params.size() > 5)
-            cmd.set("new", params[5].to_boolean());
+        if (params.size() > 5) cmd.set("new", params[5].to_boolean());
         
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
diff --git a/src/mongodb/cursor.cpp b/src/mongodb/cursor.cpp
index 55133e7..4fda35c 100644
--- a/src/mongodb/cursor.cpp
+++ b/src/mongodb/cursor.cpp
@@ -2,10 +2,9 @@
 #include "cursor.h"
 #include "_connection_lock.h"
 
-namespace flame::mongodb
-{
-    void cursor::declare(php::extension_entry &ext)
-    {
+namespace flame::mongodb {
+
+    void cursor::declare(php::extension_entry &ext) {
         php::class_entry<cursor> class_cursor("flame\\mongodb\\cursor");
         class_cursor
             .method<&cursor::__construct>("__construct", {}, php::PRIVATE)
@@ -14,35 +13,32 @@ namespace flame::mongodb
             .method<&cursor::fetch_all>("fetch_all");
         ext.add(std::move(class_cursor));
     }
-    php::value cursor::fetch_row(php::parameters &params)
-    {
+
+    php::value cursor::fetch_row(php::parameters &params) {
         php::array row(nullptr);
-        if(cs_ && cl_) {
+        if (cs_ && cl_) {
             coroutine_handler ch{coroutine::current};
             row = cl_->fetch(cs_, ch);
         }
-        if(row.typeof(php::TYPE::NULLABLE)) {
+        if (row.typeof(php::TYPE::NULLABLE)) {
             // 尽早释放连接
             cs_.reset();
             cl_.reset();
         }
         return row;
     }
-    php::value cursor::fetch_all(php::parameters &params)
-    {
-        if(cs_ && cl_) {
+
+    php::value cursor::fetch_all(php::parameters &params) {
+        if (cs_ && cl_) {
             coroutine_handler ch{coroutine::current};
             php::array data(8);
             for(php::array row = cl_->fetch(cs_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
-            {
                 data.set(data.size(), row);
-            }
             // 尽早释放连接
             cs_.reset();
             cl_.reset();
             return data;
-        }else{
-            return nullptr;
         }
+        else return nullptr;
     }
 } // namespace flame::mongodb
diff --git a/src/mongodb/date_time.cpp b/src/mongodb/date_time.cpp
index d841d6c..4f47e79 100644
--- a/src/mongodb/date_time.cpp
+++ b/src/mongodb/date_time.cpp
@@ -21,7 +21,7 @@ namespace mongodb {
 		ext.add(std::move(class_date_time));
 	}
 	php::value date_time::__construct(php::parameters& params) {
-		if(params.size() > 0)
+		if (params.size() > 0)
         {
 			tm_ = params[0].to_integer();
 		}
diff --git a/src/mongodb/mongodb.cpp b/src/mongodb/mongodb.cpp
index 815249c..6ab95c5 100644
--- a/src/mongodb/mongodb.cpp
+++ b/src/mongodb/mongodb.cpp
@@ -8,11 +8,9 @@
 #include "object_id.h"
 #include "date_time.h"
 
+namespace flame::mongodb {
 
-namespace flame::mongodb
-{
-    void declare(php::extension_entry &ext)
-    {
+    void declare(php::extension_entry &ext) {
         gcontroller->on_init([] (const php::array& options) {
                 mongoc_init();
             })->on_stop([]() {
@@ -26,8 +24,8 @@ namespace flame::mongodb
         cursor::declare(ext);
         collection::declare(ext);
     }
-    php::value connect(php::parameters &params)
-    {
+
+    php::value connect(php::parameters &params) {
         php::object obj(php::class_entry<client>::entry());
         client *ptr = static_cast<client *>(php::native(obj));
         std::string url = params[0];
@@ -36,39 +34,31 @@ namespace flame::mongodb
         // TODO 优化: 实际确认建立第一个连接?
         return std::move(obj);
     }
-    php::value iter2value(bson_iter_t *i)
-    {
-        switch (bson_iter_type(i))
-        {
+
+    php::value iter2value(bson_iter_t *i) {
+        switch (bson_iter_type(i)) {
         case BSON_TYPE_DOUBLE:
             return bson_iter_double(i);
-        case BSON_TYPE_UTF8:
-        {
+        case BSON_TYPE_UTF8: {
             std::uint32_t size = 0;
             const char *data = bson_iter_utf8(i, &size);
             return php::string(data, size);
         }
         case BSON_TYPE_DOCUMENT:
-        case BSON_TYPE_ARRAY:
-        {
+        case BSON_TYPE_ARRAY: {
             bson_iter_t j;
             bson_iter_recurse(i, &j);
             php::array a(4);
-            while (bson_iter_next(&j))
-            {
-                a.set(bson_iter_key(&j), iter2value(&j));
-            }
+            while (bson_iter_next(&j)) a.set(bson_iter_key(&j), iter2value(&j));
             return std::move(a);
         }
-        case BSON_TYPE_BINARY:
-        {
+        case BSON_TYPE_BINARY: {
             std::uint32_t size = 0;
             const unsigned char *data;
             bson_iter_binary(i, nullptr, &size, &data);
             return php::string((const char *)data, size);
         }
-        case BSON_TYPE_OID:
-        {
+        case BSON_TYPE_OID: {
             php::object o(php::class_entry<object_id>::entry());
             object_id *o_ = static_cast<object_id *>(php::native(o));
             bson_oid_copy(bson_iter_oid(i), &o_->oid_);
@@ -76,8 +66,7 @@ namespace flame::mongodb
         }
         case BSON_TYPE_BOOL:
             return bson_iter_bool(i);
-        case BSON_TYPE_DATE_TIME:
-        {
+        case BSON_TYPE_DATE_TIME: {
             php::object o(php::class_entry<date_time>::entry());
             date_time *o_ = static_cast<date_time *>(php::native(o));
             o_->tm_ = bson_iter_date_time(i);
@@ -91,98 +80,80 @@ namespace flame::mongodb
             return nullptr;
         }
     }
-    php::array bson2array(std::shared_ptr<bson_t> v)
-    {
+
+    php::array bson2array(std::shared_ptr<bson_t> v) {
         return bson2array(v.get());
     }
-    php::array bson2array(bson_t* v)
-    {
-        if(v == nullptr) return nullptr;
+
+    php::array bson2array(bson_t* v) {
+        if (v == nullptr) return nullptr;
 
         php::array doc(4);
 
         bson_iter_t i;
         bson_oid_t oid;
         bson_iter_init(&i, v);
-        while (bson_iter_next(&i))
-        {
-            doc.set(bson_iter_key(&i), iter2value(&i));
-        }
+        while (bson_iter_next(&i)) doc.set(bson_iter_key(&i), iter2value(&i));
         return std::move(doc);
     }
 
-    std::shared_ptr<bson_t> array2bson(const php::array &v)
-    {
+    std::shared_ptr<bson_t> array2bson(const php::array &v) {
         assert(v.typeof(php::TYPE::ARRAY));
         bson_t *doc = bson_new();
-        for (auto i = v.begin(); i != v.end(); ++i)
-        {
+        for (auto i = v.begin(); i != v.end(); ++i) {
             php::string key = i->first;
             key.to_string();
             php::value val = i->second;
-            switch (Z_TYPE_P(static_cast<zval *>(val)))
-            {
-                case IS_UNDEF:
-                    break;
-                case IS_NULL:
-                    bson_append_null(doc, key.c_str(), key.size());
-                    break;
-                case IS_TRUE:
-                    bson_append_bool(doc, key.c_str(), key.size(), true);
-                    break;
-                case IS_FALSE:
-                    bson_append_bool(doc, key.c_str(), key.size(), false);
-                    break;
-                case IS_LONG:
-                    bson_append_int64(doc, key.c_str(), key.size(), static_cast<std::int64_t>(val));
-                    break;
-                case IS_DOUBLE:
-                    bson_append_double(doc, key.c_str(), key.size(), static_cast<double>(val));
-                    break;
-                case IS_STRING:
-                {
-                    php::string str = val;
-                    bson_append_utf8(doc, key.c_str(), key.size(), str.c_str(), str.size());
-                    break;
+            switch (Z_TYPE_P(static_cast<zval *>(val))) {
+            case IS_UNDEF:
+                break;
+            case IS_NULL:
+                bson_append_null(doc, key.c_str(), key.size());
+                break;
+            case IS_TRUE:
+                bson_append_bool(doc, key.c_str(), key.size(), true);
+                break;
+            case IS_FALSE:
+                bson_append_bool(doc, key.c_str(), key.size(), false);
+                break;
+            case IS_LONG:
+                bson_append_int64(doc, key.c_str(), key.size(), static_cast<std::int64_t>(val));
+                break;
+            case IS_DOUBLE:
+                bson_append_double(doc, key.c_str(), key.size(), static_cast<double>(val));
+                break;
+            case IS_STRING: {
+                php::string str = val;
+                bson_append_utf8(doc, key.c_str(), key.size(), str.c_str(), str.size());
+                break;
+            }
+            case IS_ARRAY: {
+                auto a = array2bson(val);
+                if (bson_has_field(a.get(), "0") || bson_count_keys(a.get()) == 0)
+                    bson_append_array(doc, key.c_str(), key.size(), a.get());
+                else
+                    bson_append_document(doc, key.c_str(), key.size(), a.get());
+                break;
+            }
+            case IS_OBJECT: {
+                php::object o = val;
+                if (o.instanceof (php_date_get_date_ce())) // PHP 内置的 DateTime 类型
+                    bson_append_date_time(doc, key.c_str(), key.size(), static_cast<std::int64_t>(o.call("getTimestamp")) * 1000);
+                else if (o.instanceof (php::class_entry<date_time>::entry())) {
+                    date_time *o_ = static_cast<date_time *>(php::native(o));
+                    bson_append_date_time(doc, key.c_str(), key.size(), o_->tm_);
                 }
-                case IS_ARRAY:
-                {
-                    auto a = array2bson(val);
-                    if (bson_has_field(a.get(), "0") || bson_count_keys(a.get()) == 0)
-                    {
-                        bson_append_array(doc, key.c_str(), key.size(), a.get());
-                    }
-                    else
-                    {
-                        bson_append_document(doc, key.c_str(), key.size(), a.get());
-                    }
-                    break;
+                else if (o.instanceof (php::class_entry<object_id>::entry())) {
+                    object_id *o_ = static_cast<object_id *>(php::native(o));
+                    bson_append_oid(doc, key.c_str(), key.size(), &o_->oid_);
                 }
-                case IS_OBJECT:
-                {
-                    php::object o = val;
-                    if (o.instanceof (php_date_get_date_ce()))
-                    { // PHP 内置的 DateTime 类型
-                        bson_append_date_time(doc, key.c_str(), key.size(), static_cast<std::int64_t>(o.call("getTimestamp")) * 1000);
-                    }
-                    else if (o.instanceof (php::class_entry<date_time>::entry()))
-                    {
-                        date_time *o_ = static_cast<date_time *>(php::native(o));
-                        bson_append_date_time(doc, key.c_str(), key.size(), o_->tm_);
-                    }
-                    else if (o.instanceof (php::class_entry<object_id>::entry()))
-                    {
-                        object_id *o_ = static_cast<object_id *>(php::native(o));
-                        bson_append_oid(doc, key.c_str(), key.size(), &o_->oid_);
-                    }
-                    else
-                    {
-                        bson_t uninitialized;
-                        bson_append_document_begin(doc, key.c_str(), key.size(), &uninitialized);
-                        bson_append_document_end(doc, &uninitialized);
-                    }
+                else {
+                    bson_t uninitialized;
+                    bson_append_document_begin(doc, key.c_str(), key.size(), &uninitialized);
+                    bson_append_document_end(doc, &uninitialized);
                 }
             }
+            }
         }
         return std::shared_ptr<bson_t>(doc, bson_destroy);
     }
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index d8bf83b..6e9eb5d 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -90,13 +90,13 @@ ESCAPE_FINISHED:;
         });
         ch.suspend();
         last_query_ = sql;
-        if(err != 0) {
+        if (err != 0) {
             int err = mysql_errno(conn.get());
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to query MySQL server: %s") % mysql_error(conn.get())).str()
                 , err);
         }
-        if(rst) { // 存在结果集
+        if (rst) { // 存在结果集
             php::object obj(php::class_entry<result>::entry());
             result* ptr = static_cast<result*>(php::native(obj));
             ptr->cl_.reset(new _connection_lock(conn));
@@ -125,7 +125,7 @@ ESCAPE_FINISHED:;
         unsigned long* len;
         boost::asio::post(gcontroller->context_y, [&rst, &ch, &row, &len] () {
             row = mysql_fetch_row(rst.get());
-            if(row) len = mysql_fetch_lengths(rst.get());
+            if (row) len = mysql_fetch_lengths(rst.get());
             ch.resume();
         });
         ch.suspend();
@@ -141,7 +141,7 @@ ESCAPE_FINISHED:;
         for(int i=0;i<n;++i) {
             php::string field(f[i].name, f[i].name_length);
             php::value  value;
-            if(row[i] == nullptr) {
+            if (row[i] == nullptr) {
                 value = nullptr;
                 php_row.set(field, value);
                 continue;
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
index 04fae10..5e9cb4c 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/mysql/_connection_lock.cpp
@@ -25,7 +25,7 @@ namespace flame::mysql
             ch.resume();
         });
         ch.suspend();
-        if(err != 0) {
+        if (err != 0) {
             err = mysql_errno(conn_.get());
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to start transaction: %s") % mysql_error(conn_.get())).str()
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index f517c71..e962b40 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -37,7 +37,8 @@ namespace flame::mysql
                     conn_.pop_front();
                     release(c);
                     return;
-                } else { // 连接已丢失，回收资源
+                }
+                else { // 连接已丢失，回收资源
                     mysql_close(conn_.front().conn);
                     conn_.pop_front();
                     --size_;
@@ -47,10 +48,11 @@ namespace flame::mysql
             MYSQL* c = mysql_init(nullptr);
             init_options(c);
 
-            if(mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
+            if (mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
                 ++size_; // 当前还存在的连接数量
                 release(c);
-            }else{
+            }
+            else {
                 errnum = mysql_errno(c);
                 errmsg = mysql_error(c);
                 mysql_close(c);
@@ -61,12 +63,11 @@ namespace flame::mysql
         // 暂停, 等待连接获取(异步任务)
         ch.suspend();
         if (!conn) {
-            if (errnum)
-                throw php::exception(zend_ce_exception
+            if (errnum) throw php::exception(zend_ce_exception
                     , (boost::format("Failed to connect to MySQL server: %s") % errmsg).str()
                     , errnum);
-            else
-                throw php::exception(zend_ce_exception, "Failed to connect to MySQL server", 0);
+            else throw php::exception(zend_ce_exception
+                , "Failed to connect to MySQL server", 0);
         }
         // 恢复, 已经填充连接
         return conn;
@@ -123,16 +124,17 @@ namespace flame::mysql
             std::unique_ptr<MYSQL_RES, void (*)(MYSQL_RES*)> rst(mysql_store_result(c), mysql_free_result);
             if (!rst) return;
             MYSQL_ROW row = mysql_fetch_row(rst.get());
-            if(!row) return;
+            if (!row) return;
             unsigned long* len = mysql_fetch_lengths(rst.get());
             std::string charset(row[1], len[1]);
             flag_ |= charset == url_.query["charset"] ? FLAG_CHARSET_EQUAL : FLAG_CHARSET_DIFFER;
-        } else flag_ |= FLAG_CHARSET_DIFFER; // 这里忽略了 charset 查询的错误
+        }
+        else flag_ |= FLAG_CHARSET_DIFFER; // 这里忽略了 charset 查询的错误
     }
 
     void _connection_pool::query_version(MYSQL* c) {
-        if(url_.query.count("proxy") > 0) {
-            if(std::stoi(url_.query["proxy"]) > 0) {
+        if (url_.query.count("proxy") > 0) {
+            if (std::stoi(url_.query["proxy"]) > 0) {
                 flag_ |= FLAG_REUSE_BY_PROXY;
                 return;
             }
@@ -144,7 +146,7 @@ namespace flame::mysql
         tm_.expires_from_now(std::chrono::seconds(60));
         // 注意, 实际的清理流程需要保证 guard_ 串行流程
         tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
-            if(error) return; // 当前对象销毁时会发生对应的 abort 错误
+            if (error) return; // 当前对象销毁时会发生对应的 abort 错误
             auto now = std::chrono::steady_clock::now();
             // 超低水位，关闭不活跃或已丢失的连接
             for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;) {
@@ -153,7 +155,8 @@ namespace flame::mysql
                     mysql_close((*i).conn);
                     --size_;
                     i = conn_.erase(i);
-                } else  ++i;
+                }
+                else  ++i;
             }
             // 再次启动
             sweep();
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 6f77bda..15166ed 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -5,10 +5,9 @@
 #include "mysql.h"
 #include "tx.h"
 
-namespace flame::mysql
-{
-    void client::declare(php::extension_entry &ext)
-    {
+namespace flame::mysql {
+    
+    void client::declare(php::extension_entry &ext) {
         php::class_entry<client> class_client("flame\\mysql\\client");
         class_client
             .method<&client::__construct>("__construct", {}, php::PRIVATE)
@@ -60,31 +59,31 @@ namespace flame::mysql
             .method<&client::server_version>("server_version");
         ext.add(std::move(class_client));
     }
+
     php::value client::__construct(php::parameters& params) {
         return nullptr;
     }
-    php::value client::__destruct(php::parameters &params)
-    {
+
+    php::value client::__destruct(php::parameters &params) {
         // std::cout << "client destruct\n";
         return nullptr;
     }
-    php::value client::escape(php::parameters &params)
-    {
+
+    php::value client::escape(php::parameters &params) {
         coroutine_handler ch {coroutine::current};
         std::shared_ptr<MYSQL> conn = cp_->acquire(ch);
 
         php::buffer buffer;
         char quote = '\'';
-        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING))
-        {
+        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
             php::string q = params[1];
             if (q.data()[0] == '`') quote = '`';
         }
         _connection_base::escape(conn, buffer, params[0], quote);
         return std::move(buffer);
     }
-    php::value client::begin_tx(php::parameters &params)
-    {
+
+    php::value client::begin_tx(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         auto cl = std::make_shared<_connection_lock>(conn);
@@ -95,45 +94,45 @@ namespace flame::mysql
         ptr->cl_ = cl; // 继续持有当前连接
         return std::move(obj);
     }
-    php::value client::query(php::parameters &params)
-    {
+
+    php::value client::query(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         return cp_->query(cp_->acquire(ch), params[0], ch);
     }
-    php::value client::insert(php::parameters &params)
-    {
+
+    php::value client::insert(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         php::buffer buf;
         build_insert(conn, buf, params);
         return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value client::delete_(php::parameters &params)
-    {
+
+    php::value client::delete_(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         php::buffer buf;
         build_delete(conn, buf, params);
         return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value client::update(php::parameters &params)
-    {
+
+    php::value client::update(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         php::buffer buf;
         build_update(conn, buf, params);
         return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value client::select(php::parameters &params)
-    {
+
+    php::value client::select(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         php::buffer buf;
         build_select(conn, buf, params);
         return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value client::one(php::parameters &params)
-    {
+
+    php::value client::one(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         php::buffer buf;
@@ -141,25 +140,23 @@ namespace flame::mysql
         php::object rst = cp_->query(conn, std::string(buf.data(), buf.size()), ch);
         return rst.call("fetch_row");
     }
-    php::value client::get(php::parameters &params)
-    {
+
+    php::value client::get(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         php::buffer buf;
         build_get(conn, buf, params);
         php::object rst = cp_->query(conn, std::string(buf.data(), buf.size()), ch);
         php::array row = rst.call("fetch_row");
-        if(!row.empty()) {
-            return row.get( static_cast<php::string>(params[1]) );
-        }else{
-            return nullptr;
-        }
+        if (!row.empty()) return row.get( static_cast<php::string>(params[1]) );
+        else return nullptr;
     }
+
     php::value client::last_query(php::parameters& params) {
         return cp_->last_query();
     }
-    php::value client::server_version(php::parameters &params)
-    {
+
+    php::value client::server_version(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cp_->acquire(ch);
         return php::string(mysql_get_server_info(conn.get()));
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 83dfd14..fd02cde 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -28,9 +28,9 @@ namespace flame::mysql {
 
     php::value connect(php::parameters& params) {
         url u(params[0]);
-        if(u.port < 10) u.port = 3306;
-        if(!u.query.count("charset")) u.query["charset"] = "utf8mb4";
-        if(!u.query.count("ssl")) u.query["ssl"] = "disabled";
+        if (u.port < 10) u.port = 3306;
+        if (!u.query.count("charset")) u.query["charset"] = "utf8mb4";
+        if (!u.query.count("ssl")) u.query["ssl"] = "disabled";
 
         php::object obj(php::class_entry<client>::entry());
         client *ptr = static_cast<client *>(php::native(obj));
@@ -73,7 +73,8 @@ namespace flame::mysql {
     static void where_lt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
         if (cond.typeof(php::TYPE::NULLABLE)) {
             buf.append("<0", 2);
-        } else {
+        } 
+        else {
             php::string str = cond;
             str.to_string();
             buf.push_back('<');
@@ -143,6 +144,7 @@ namespace flame::mysql {
         buf.append(" NOT LIKE ", 10);
         _connection_base::escape(cc, buf, cond);
     }
+
     static void where_ex(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond
         , const php::string &field, const php::string &separator) {
 
@@ -160,7 +162,8 @@ namespace flame::mysql {
                     str.to_string();
                     buf.append(str);
                 }
-            } else {
+            }
+            else {
                 php::string key = i->first;
                 if (key.c_str()[0] == '{') { // OPERATOR (php::TYPE::STRING)
                     if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0) 
@@ -169,52 +172,66 @@ namespace flame::mysql {
                         assert(i->second.typeof(php::TYPE::ARRAY));
                         buf.append(" NOT ", 5);
                         where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
-                    } else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0
+                    }
+                    else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0
                         || strncasecmp(key.c_str(), "{||}", 4) == 0)) {
 
                         assert(i->second.typeof(php::TYPE::ARRAY));
                         where_ex(cc, buf, i->second, php::string(nullptr), " OR ");
-                    } else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0)
+                    }
+                    else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0)
                         || (key.size() == 4 && strncasecmp(key.c_str(), "{&&}", 4) == 0)) {
                         assert(i->second.typeof(php::TYPE::ARRAY));
                         where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
-                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0) {
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_ne(cc, buf, i->second);
-                    } else if (key.size() == 3 && strncasecmp(key.c_str(), "{>}", 3) == 0) {
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{>}", 3) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_gt(cc, buf, i->second);
-                    } else if (key.size() == 3 && strncasecmp(key.c_str(), "{<}", 2) == 0) {
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{<}", 2) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_lt(cc, buf, i->second);
-                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{>=}", 4) == 0) {
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{>=}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_gte(cc, buf, i->second);
-                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{<=}", 4) == 0) {
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<=}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_lte(cc, buf, i->second);
-                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{<>}", 4) == 0) {
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<>}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_wir(cc, buf, i->second);
-                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{><}", 4) == 0) {
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{><}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_oor(cc, buf, i->second);
-                    } else if (key.size() == 3 && strncasecmp(key.c_str(), "{~}", 3) == 0) {
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{~}", 3) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_lk(cc, buf, i->second);
-                    } else if (key.size() == 4 && strncasecmp(key.c_str(), "{!~}", 4) == 0) {
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!~}", 4) == 0) {
                         _connection_base::escape(cc, buf, field, '`');
                         where_nlk(cc, buf, i->second);
                     }
-                } else { // php::TYPE::STRING
+                }
+                else { // php::TYPE::STRING
                     if (i->second.typeof(php::TYPE::ARRAY)) {
                         php::array cond = i->second;
                         if (cond.exists(0)) {
                             _connection_base::escape(cc, buf, key, '`');
                             buf.push_back(' ');
                             where_in(cc, buf, i->second);
-                        } else where_ex(cc, buf, cond, key, " AND ");
-                    } else {
+                        }
+                        else where_ex(cc, buf, cond, key, " AND ");
+                    }
+                    else {
                         _connection_base::escape(cc, buf, key, '`');
                         where_eq(cc, buf, i->second);
                     }
@@ -229,16 +246,19 @@ namespace flame::mysql {
         if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER)) {
             buf.push_back(' ');
             buf.append(data);
-        } else if (data.typeof(php::TYPE::ARRAY)) {
+        }
+        else if (data.typeof(php::TYPE::ARRAY)) {
             where_ex(cc, buf, data, php::string(0), " AND ");
-        } else buf.push_back('1');
+        }
+        else buf.push_back('1');
     }
 
     void build_order(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
         if (data.typeof(php::TYPE::STRING)) {
             buf.append(" ORDER BY ", 10);
             buf.append(data);
-        } else if (data.typeof(php::TYPE::ARRAY)) {
+        }
+        else if (data.typeof(php::TYPE::ARRAY)) {
             buf.append(" ORDER BY", 9);
             php::array order = data;
             int j = -1;
@@ -249,26 +269,30 @@ namespace flame::mysql {
                         buf.push_back(' ');
                         buf.append(i->second);
                     }
-                } else {
+                }
+                else {
                     buf.push_back(' ');
                     _connection_base::escape(cc, buf, i->first, '`');
                     if (i->second.typeof(php::TYPE::STRING)) {
                         buf.push_back(' ');
                         buf.append(i->second);
-                    } else if (i->second.typeof(php::TYPE::YES)
+                    }
+                    else if (i->second.typeof(php::TYPE::YES)
                         || (i->second.typeof(php::TYPE::INTEGER) && static_cast<int>(i->second) >= 0))
                         buf.append(" ASC", 4);
                     else buf.append(" DESC", 5);
                 }
             }
-        } else ; // throw php::exception(zend_ce_type_error, "Failed to build 'ORDER BY' clause: unsupported type specified for 'order'", -1);
+        }
+        else ; // throw php::exception(zend_ce_type_error, "Failed to build 'ORDER BY' clause: unsupported type specified for 'order'", -1);
     }
 
     void build_limit(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
         if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER)) {
             buf.append(" LIMIT ", 7);
             buf.append(data);
-        } else if (data.typeof(php::TYPE::ARRAY)) {
+        }
+        else if (data.typeof(php::TYPE::ARRAY)) {
             buf.append(" LIMIT", 6);
             php::array limit = data;
             int j = -1;
@@ -278,7 +302,8 @@ namespace flame::mysql {
                 buf.append(i->second);
                 if (j > 0) break;
             }
-        } else throw php::exception(zend_ce_type_error
+        }
+        else throw php::exception(zend_ce_type_error
             , "Failed to build 'LIMIT' clause: unsupported type specified for `limit`"
             , -1);
     }
@@ -301,7 +326,8 @@ namespace flame::mysql {
         if (rows.exists(0)) {
             row = rows.get(0);
             assert(!row.exists(0) && "二级行无字段");
-        } else {
+        }
+        else {
             row = rows;
             rows = nullptr;
         }
@@ -321,7 +347,8 @@ namespace flame::mysql {
                 insert_row(cc, buf, i->second);
             }
             buf.push_back(')');
-        } else {
+        }
+        else {
             buf.push_back('(');
             insert_row(cc, buf, row);
             buf.push_back(')');
@@ -350,7 +377,8 @@ namespace flame::mysql {
         if (data.typeof(php::TYPE::STRING)) {
             buf.push_back(' ');
             buf.append(data);
-        } else if (data.typeof(php::TYPE::ARRAY)) {
+        }
+        else if (data.typeof(php::TYPE::ARRAY)) {
             int j = -1;
             for (auto i = data.begin(); i != data.end(); ++i) {
                 if (++j > 0) buf.push_back(',');
@@ -359,7 +387,8 @@ namespace flame::mysql {
                 buf.push_back('=');
                 _connection_base::escape(cc, buf, i->second);
             }
-        }else throw php::exception(zend_ce_type_error
+        }
+        else throw php::exception(zend_ce_type_error
             , "Failed to build 'UPDATE' clause: unsupported type specified for `update`"
             , -1);
         // 条件
diff --git a/src/mysql/result.cpp b/src/mysql/result.cpp
index 923bf64..d7b8b0f 100644
--- a/src/mysql/result.cpp
+++ b/src/mysql/result.cpp
@@ -2,10 +2,9 @@
 #include "result.h"
 #include "_connection_lock.h"
 
-namespace flame::mysql
-{
-    void result::declare(php::extension_entry &ext)
-    {
+namespace flame::mysql {
+
+    void result::declare(php::extension_entry &ext) {
         php::class_entry<result> class_result("flame\\mysql\\result");
         class_result
             .property({"stored_rows", 0})
@@ -14,23 +13,23 @@ namespace flame::mysql
 
         ext.add(std::move(class_result));
     }
-    php::value result::fetch_row(php::parameters &params)
-    {
+    
+    php::value result::fetch_row(php::parameters &params) {
         php::array row(nullptr);
-        if(cl_ && rs_) {
+        if (cl_ && rs_) {
             coroutine_handler ch{coroutine::current};
             row = cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch);
         }
-        if(row.typeof(php::TYPE::NULLABLE)) {
+        if (row.typeof(php::TYPE::NULLABLE)) {
             // 尽早的释放连接
             rs_.reset();
             cl_.reset();
         }
         return row;
     }
-    php::value result::fetch_all(php::parameters &params)
-    {
-        if(cl_ && rs_) {
+
+    php::value result::fetch_all(php::parameters &params) {
+        if (cl_ && rs_) {
             coroutine_handler ch{coroutine::current};
             auto conn = cl_->acquire(ch);
             php::array data {4}, row;
@@ -42,7 +41,8 @@ namespace flame::mysql
             rs_.reset();
             cl_.reset();
             return data;
-        }else{
+        }
+        else {
             return nullptr;
         }
     }
diff --git a/src/os/os.cpp b/src/os/os.cpp
index 721723b..1efbd55 100644
--- a/src/os/os.cpp
+++ b/src/os/os.cpp
@@ -35,7 +35,8 @@ namespace flame::os {
         if (data.exists(name)) {
             php::array iface = data.get(name);
             iface.set(iface.size(), info);
-        } else {
+        }
+        else {
             php::array iface(2);
             iface.set(iface.size(), info);
             data.set(name, iface);
diff --git a/src/os/process.cpp b/src/os/process.cpp
index b6e7584..17ae03d 100644
--- a/src/os/process.cpp
+++ b/src/os/process.cpp
@@ -1,10 +1,9 @@
 #include "../coroutine.h"
 #include "process.h"
 
-namespace flame::os
-{
-    void process::declare(php::extension_entry &ext)
-    {
+namespace flame::os {
+
+    void process::declare(php::extension_entry &ext) {
         ext
             .constant({"flame\\os\\SIGTERM", SIGTERM})
             .constant({"flame\\os\\SIGKILL", SIGKILL})
@@ -17,8 +16,7 @@ namespace flame::os
             .property({"pid", 0})
             .method<&process::__construct>("__construct", {}, php::PRIVATE)
             .method<&process::__destruct>("__destruct")
-            .method<&process::kill>("kill",
-            {
+            .method<&process::kill>("kill", {
                 {"signal", php::TYPE::INTEGER, false, true}
             })
             .method<&process::detach>("detach")
@@ -27,32 +25,32 @@ namespace flame::os
             .method<&process::stderr>("stderr");
         ext.add(std::move(class_process));
     }
-    php::value process::__construct(php::parameters& params)
-    {
+
+    php::value process::__construct(php::parameters& params) {
         return nullptr;
     }
-    php::value process::__destruct(php::parameters& params)
-    {
-        if(!detach_) {
-            if(!c_.wait_for(std::chrono::milliseconds(10000))) c_.terminate();
-            if(c_.joinable()) c_.join();
+
+    php::value process::__destruct(php::parameters& params) {
+        if (!detach_) {
+            if (!c_.wait_for(std::chrono::milliseconds(10000))) c_.terminate();
+            if (c_.joinable()) c_.join();
         }
         return nullptr;
     }
-    php::value process::kill(php::parameters &params)
-    {
+
+    php::value process::kill(php::parameters &params) {
         if (params.size() > 0) ::kill(get("pid"), params[0].to_integer());
         else ::kill(get("pid"), SIGTERM);
         return nullptr;
     }
-    php::value process::detach(php::parameters& params) 
-    {
+
+    php::value process::detach(php::parameters& params) {
         detach_ = true;
         c_.detach();
         return nullptr;
     }
-    php::value process::wait(php::parameters &params)
-    {
+
+    php::value process::wait(php::parameters &params) {
         if (!exit_) {
             ch_.reset(coroutine::current);
             ch_.suspend();
@@ -60,13 +58,13 @@ namespace flame::os
         }
         return nullptr;
     }
-    php::value process::stdout(php::parameters &params)
-    {
+
+    php::value process::stdout(php::parameters &params) {
         wait(params);
         return out_.get();
     }
-    php::value process::stderr(php::parameters &params)
-    {
+
+    php::value process::stderr(php::parameters &params) {
         wait(params);
         return out_.get();
     }
diff --git a/src/queue.cpp b/src/queue.cpp
index 00fca99..0535b62 100644
--- a/src/queue.cpp
+++ b/src/queue.cpp
@@ -1,18 +1,14 @@
 #include "coroutine.h"
 #include "queue.h"
 
-namespace flame
-{
-    void queue::declare(php::extension_entry &ext)
-    {
+namespace flame {
+    void queue::declare(php::extension_entry &ext) {
         php::class_entry<queue> class_queue("flame\\queue");
         class_queue
-            .method<&queue::__construct>("__construct",
-            {
+            .method<&queue::__construct>("__construct", {
                 {"n", php::TYPE::INTEGER, false, true},
             })
-            .method<&queue::push>("push",
-            {
+            .method<&queue::push>("push", {
                 {"value", php::TYPE::UNDEFINED}
             })
             .method<&queue::pop>("pop")
@@ -20,40 +16,33 @@ namespace flame
             .method<&queue::is_closed>("is_closed");
         ext.add(std::move(class_queue));
     }
-    php::value queue::__construct(php::parameters& params)
-    {
-        if(params.size() > 0)
-        {
+
+    php::value queue::__construct(php::parameters& params) {
+        if (params.size() > 0)
             q_.reset(new coroutine_queue<php::value>(static_cast<int>(params[0])));
-        }else{
+        else
             q_.reset(new coroutine_queue<php::value>(1));
-        }
         return nullptr;
     }
-    php::value queue::push(php::parameters &params)
-    {
+
+    php::value queue::push(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         q_->push(params[0], ch);
         return nullptr;
     }
-    php::value queue::pop(php::parameters &params)
-    {
+
+    php::value queue::pop(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto x = q_->pop(ch);
-        if(x)
-        {
-            return x.value();
-        }
-        else
-        {
-            return nullptr;
-        }
+        if (x) return x.value();
+        else return nullptr;
     }
-    php::value queue::close(php::parameters &params)
-    {
+
+    php::value queue::close(php::parameters &params) {
         q_->close();
         return nullptr;
     }
+
     php::value queue::is_closed(php::parameters& params) {
         return q_->is_closed();
     }
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
index 943da7f..825b542 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/rabbitmq/_client.cpp
@@ -26,7 +26,7 @@ namespace flame::rabbitmq
             // ch_.onError([] (const char* message) {});
             chn_.onError([this] (const char* message) {
                 error_.assign(message);
-                if(!consumer_tg_.empty()) consumer_close();
+                if (!consumer_tg_.empty()) consumer_close();
             });
             ch.resume();
         });
@@ -37,7 +37,7 @@ namespace flame::rabbitmq
         });
         ch.suspend();
 
-        if(!error_.empty()) {
+        if (!error_.empty()) {
             std::string err = std::move(error_);
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to connect RabbitMQ server: %s") % err).str()
@@ -45,11 +45,8 @@ namespace flame::rabbitmq
         }
 
         auto i = u.query.find("prefetch");
-        if (i == u.query.end()) {
-            pf_ = 8;
-        }else{
-            pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 256);
-        }
+        if (i == u.query.end()) pf_ = 8;
+        else pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 256);
         chn_.setQos(pf_);
 
         CHECK_AND_SET_FLAG(nolocal, AMQP::nolocal);
@@ -65,7 +62,7 @@ namespace flame::rabbitmq
     void _client::heartbeat() {
         tm_.expires_after(std::chrono::seconds(45));
         tm_.async_wait([this] (const boost::system::error_code& error) {
-            if(error) return;
+            if (error) return;
             con_.heartbeat();
             heartbeat();
         });
@@ -98,7 +95,7 @@ namespace flame::rabbitmq
                 ch.resume(); // ----> 2
             });
         ch.suspend();
-        if(err) throw php::exception(zend_ce_exception
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to consume RabbitMQ queue: %s") % err).str()
             , -1);
         consumer_ch_ = ch;
@@ -108,7 +105,7 @@ namespace flame::rabbitmq
             q.push(std::move(obj), ch);
             ch.suspend(); // 2 <----
         }
-        if(err) throw php::exception(zend_ce_exception
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to consume RabbitMQ queue: %s") % err).str()
             , -1);
         q.close();
@@ -128,7 +125,7 @@ namespace flame::rabbitmq
         chn_.cancel(consumer_tg_);
         consumer_tg_.clear();
         // 标记结束后，消费流程队将自行关闭
-        if(consumer_ch_) consumer_ch_.resume();
+        if (consumer_ch_) consumer_ch_.resume();
     }
 
     void _client::consumer_close(coroutine_handler& ch) {
@@ -143,7 +140,7 @@ namespace flame::rabbitmq
             });
         ch.suspend();
         consumer_tg_.clear();
-        if(consumer_ch_) consumer_ch_.resume(); // ----> 2  标记结束后，消费流程队将自行关闭
+        if (consumer_ch_) consumer_ch_.resume(); // ----> 2  标记结束后，消费流程队将自行关闭
         if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to close RabbitMQ consumer: %s") % err).str()
             , -1);
@@ -152,7 +149,7 @@ namespace flame::rabbitmq
     void _client::publish(const std::string &ex, const std::string &rk, const AMQP::Envelope &env, coroutine_handler& ch) {
         /*auto& defer = */chn_.publish(ex, rk, env, fl_);
         // producer_cb(defer, ch);
-        if(!error_.empty()) {
+        if (!error_.empty()) {
             std::string err = std::move(error_);
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to publish to RabbitMQ: %s") % err).str()
@@ -162,14 +159,14 @@ namespace flame::rabbitmq
 
     // void _client::producer_cb(AMQP::DeferredPublisher& defer, coroutine_handler& ch) {
     //     producer_ch_ = ch;
-    //     if(!producer_cb_) {
+    //     if (!producer_cb_) {
     //         producer_cb_ = true;
     //         const char* err = nullptr;
     //         defer.onSuccess([this]() {
-    //             if(producer_ch_) producer_ch_.resume();
+    //             if (producer_ch_) producer_ch_.resume();
     //         }).onError([this](const char *message) {
     //             error_ = message;
-    //             if(producer_ch_) producer_ch_.resume();
+    //             if (producer_ch_) producer_ch_.resume();
     //         });
     //     }
     //     ch.suspend();
@@ -186,7 +183,7 @@ namespace flame::rabbitmq
     {
         /*auto& defer = */chn_.publish(ex, rk, msg, len, fl_);
         // producer_cb(defer, ch);
-        if(!error_.empty()) {
+        if (!error_.empty()) {
             std::string err = std::move(error_);
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to publish RabbitMQ message: %s") % err).str()
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index eb8506f..0c62de2 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -57,14 +57,14 @@ namespace flame::rabbitmq
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
                     }
                 }
-                if(--count == 0)  ch.resume(); // ----> 1
+                if (--count == 0)  ch.resume(); // ----> 1
                 return nullptr;
             }));
         }
         cc_->consume(qn_, q, ch);
         ch.suspend(); // 1 <----
         cb_ = nullptr;
-        if(cc_->has_error()) throw php::exception(zend_ce_exception
+        if (cc_->has_error()) throw php::exception(zend_ce_exception
             , (boost::format("Failed to consume RabbitMQ queue: %s") % cc_->error()).str()
             , -1);
         return nullptr;
diff --git a/src/redis/_connection_base.cpp b/src/redis/_connection_base.cpp
index 1613a52..de052cd 100644
--- a/src/redis/_connection_base.cpp
+++ b/src/redis/_connection_base.cpp
@@ -1,12 +1,10 @@
 #include "_connection_base.h"
 
 namespace flame::redis {
+    
     static php::value simple2value(redisReply* rp) {
-        if(!rp) {
-            return nullptr;
-        }
-        switch (rp->type)
-        {
+        if (!rp) return nullptr;
+        switch (rp->type) {
         case REDIS_REPLY_STATUS:
         case REDIS_REPLY_ERROR:
         case REDIS_REPLY_STRING:
@@ -25,47 +23,40 @@ namespace flame::redis {
             return nullptr;
         }
     }
-    php::value _connection_base::reply2value(redisReply* rp, php::array &argv, reply_type rt)
-    {
-        switch(rt)
-        {
-            case reply_type::ASSOC_ARRAY_1:
-            {
+
+    php::value _connection_base::reply2value(redisReply* rp, php::array &argv, reply_type rt) {
+        switch(rt) {
+            case reply_type::ASSOC_ARRAY_1: {
                 php::array data(rp->elements/2 + 1);
-                for(int i = 0; i < rp->elements; i += 2) {
+                for (int i = 0; i < rp->elements; i += 2) {
                     php::string key = simple2value(rp->element[i]).to_string();
                     data.set(key, simple2value(rp->element[i+1]));
                 }
                 return data;
             }
-            case reply_type::ASSOC_ARRAY_2:
-            {
+            case reply_type::ASSOC_ARRAY_2: {
                 assert(rp->elements == 2 && rp->element[0]->type == REDIS_REPLY_STRING && rp->element[1]->type == REDIS_REPLY_ARRAY);
                 php::array wrap(2);
                 php::array data(rp->element[1]->elements/2 + 1);
                 wrap.set(0, simple2value(rp->element[0]));
-                for(int i = 0; i < rp->element[1]->elements; i += 2) {
+                for (int i = 0; i < rp->element[1]->elements; i += 2) {
                     php::string key = simple2value(rp->element[1]->element[i]).to_string();
                     data.set(key, simple2value(rp->element[1]->element[i+1]));
                 }
                 wrap.set(1, data); 
                 return wrap;
             }
-            case reply_type::COMBINE_1:
-            {
+            case reply_type::COMBINE_1: {
                 php::array data(rp->elements);
-                for(int i = 0; i < rp->elements; ++i)
-                {
+                for (int i = 0; i < rp->elements; ++i) {
                     php::string key = argv[i].to_string();
                     data.set(key, simple2value(rp->element[i]));
                 }
                 return data;
             }
-            case reply_type::COMBINE_2:
-            {
+            case reply_type::COMBINE_2: {
                 php::array data(rp->elements);
-                for (int i = 0; i < rp->elements; ++i)
-                {
+                for (int i = 0; i < rp->elements; ++i) {
                     php::string key = argv[i+1].to_string();
                     data.set(key, simple2value(rp->element[i]));
                 }
@@ -77,18 +68,13 @@ namespace flame::redis {
         }
     }
 
-    std::string _connection_base::format(php::string& name, php::array& argv)
-    {
+    std::string _connection_base::format(php::string& name, php::array& argv) {
         std::ostringstream ss;
         ss << "*" << 1 + argv.size() << "\r\n$" << name.size() << "\r\n" << name << "\r\n";
-        for (auto i = argv.begin(); i != argv.end(); ++i)
-        {
+        for (auto i = argv.begin(); i != argv.end(); ++i) {
             php::string v = i->second.to_string();
-            if(v.size() > 0) {
-                ss << "$" << v.size() << "\r\n" << v << "\r\n";;
-            }else{
-                ss << "$0\r\n\r\n";
-            }
+            if (v.size() > 0) ss << "$" << v.size() << "\r\n" << v << "\r\n";
+            else ss << "$0\r\n\r\n";
         }
         return ss.str();
     }
diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index 34fbddc..c5de726 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -9,7 +9,7 @@ namespace flame::redis {
     , size_(0)
     , guard_(gcontroller->context_y)
     , tm_(gcontroller->context_y) {
-        if(url_.port < 10) url_.port = 6379;
+        if (url_.port < 10) url_.port = 6379;
     }
 
     _connection_pool::~_connection_pool() {
@@ -32,13 +32,14 @@ namespace flame::redis {
             });
             auto now = std::chrono::steady_clock::now();
             while (!conn_.empty())  {
-                if(now - conn_.front().ttl < std::chrono::seconds(15) || ping(conn_.front().conn)) {
+                if (now - conn_.front().ttl < std::chrono::seconds(15) || ping(conn_.front().conn)) {
                     // 可用连接
                     redisContext *c = conn_.front().conn;
                     conn_.pop_front();
                     release(c);
                     return;
-                } else { // 连接已丢失，回收资源
+                }
+                else { // 连接已丢失，回收资源
                     redisFree(conn_.front().conn);
                     conn_.pop_front();
                     --size_;
@@ -50,7 +51,8 @@ namespace flame::redis {
             if (c == nullptr) { // 创建新连接失败
                 await_.pop_back();
                 ch.resume();
-            } else {
+            }
+            else {
                 ++size_; // 当前还存在的连接数量
                 release(c);
             }
@@ -73,11 +75,12 @@ namespace flame::redis {
             ch.resume();
         });
         ch.suspend();
-        if(rp) {
+        if (rp) {
             php::value rv = reply2value(rp, argv, rt);
             freeReplyObject(rp);
             return std::move(rv);
-        } else if(rc->err) throw php::exception(zend_ce_exception
+        }
+        else if (rc->err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to exec Redis command: %s") % rc->errstr).str()
             , rc->err);
         else return nullptr;
@@ -91,27 +94,32 @@ namespace flame::redis {
     redisContext *_connection_pool::create(std::string& err) {
         struct timeval tv {5, 0};
         redisContext* c = redisConnectWithTimeout(url_.host.c_str(), url_.port, tv);
-        if(!c) {
+        if (!c) {
             err.assign("connection failed");
             return nullptr;
-        }else if(c->err) {
+        }
+        else if (c->err) {
             err.assign(c->errstr);
             redisFree(c);
             return nullptr;
         }
-        if(!url_.pass.empty()) { // 认证
-            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply*)redisCommand(c, "AUTH %s", url_.pass.c_str()),
-                (void (*)(redisReply*))freeReplyObject);
-            if(!rp || rp->type == REDIS_REPLY_ERROR) {
+        if (!url_.pass.empty()) { // 认证
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply*)redisCommand(c, "AUTH %s"
+                , url_.pass.c_str())
+                , (void (*)(redisReply*))freeReplyObject);
+
+            if (!rp || rp->type == REDIS_REPLY_ERROR) {
                 err.assign(rp->str, rp->len);
                 redisFree(c);
                 return nullptr;
             }
         }
-        if(url_.path.length() > 1) { // 指定数据库
-            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "SELECT %d", std::atoi(url_.path.c_str() + 1)),
-                (void (*)(redisReply*))freeReplyObject);
-            if(!rp || rp->type == REDIS_REPLY_ERROR) {
+        if (url_.path.length() > 1) { // 指定数据库
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "SELECT %d"
+                , std::atoi(url_.path.c_str() + 1))
+                , (void (*)(redisReply*))freeReplyObject);
+
+            if (!rp || rp->type == REDIS_REPLY_ERROR) {
                 err.assign(rp->str, rp->len);
                 redisFree(c);
                 return nullptr;
@@ -121,7 +129,7 @@ namespace flame::redis {
     }
 
     void _connection_pool::release(redisContext *c) {
-        if(c->err) --size_;  // 出现上下文异常的连接直接抛弃
+        if (c->err) --size_;  // 出现上下文异常的连接直接抛弃
         else if (await_.empty())
             conn_.push_back({c, std::chrono::steady_clock::now()}); // 无等待分配的请求
         else { // 立刻分配使用
@@ -154,7 +162,8 @@ namespace flame::redis {
                     redisFree((*i).conn);
                     --size_;
                     i = conn_.erase(i);
-                } else ++i;
+                }
+                else ++i;
             }
             sweep(); // 再次启动
         }));
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index 1c0dbdd..de9732d 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -145,11 +145,10 @@ namespace flame::redis {
 		php::string name("ZRANGEBYSCORE", 13);
 
 		for(int i=3; i<params.size(); ++i) {
-			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
 				php::string arg = params[i];
-				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
 					return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
-				}
 			}
 		}
 		return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
@@ -160,11 +159,10 @@ namespace flame::redis {
 		php::string name("ZREVRANGEBYSCORE", 16);
 
 		for(int i=3; i<params.size(); ++i) {
-			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
 				php::string arg = params[i];
-				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
 					return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
-				}
 			}
 		}
 		return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
diff --git a/src/redis/tx.cpp b/src/redis/tx.cpp
index b618c07..2bb7434 100644
--- a/src/redis/tx.cpp
+++ b/src/redis/tx.cpp
@@ -143,9 +143,9 @@ namespace flame::redis {
 		php::string name("ZRANGEBYSCORE", 13);
 
 		for(int i=3; i<params.size(); ++i) {
-			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
 				php::string arg = params[i];
-				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
                     cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
                     return this;
                 }
@@ -158,9 +158,9 @@ namespace flame::redis {
 		php::string name("ZREVRANGEBYSCORE", 16);
 
 		for(int i=3; i<params.size(); ++i) {
-			if(params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
 				php::string arg = params[i];
-				if(strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
                     cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
                     return this;
                 }
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index 80be987..b7edcf5 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -33,7 +33,7 @@ namespace flame::tcp {
         closed_ = false;
         std::string str_addr = params[0];
         auto pair = udp::addr2pair(str_addr);
-        if(pair.first.empty() || pair.second.empty())
+        if (pair.first.empty() || pair.second.empty())
             throw php::exception(zend_ce_error_exception
                 , "Failed to bind TCP socket: address malformed"
                 , -1);
@@ -50,7 +50,7 @@ namespace flame::tcp {
 
         boost::system::error_code err;
         acceptor_.bind(addr_, err);
-        if(err) throw php::exception(zend_ce_exception
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to bind TCP socket: %s") % err.message()).str()
             , err.value());
 
@@ -67,8 +67,8 @@ namespace flame::tcp {
         boost::system::error_code err;
         while(!closed_) {
             acceptor_.async_accept(socket_, ch[err]);
-            if(err == boost::asio::error::operation_aborted) break;
-            else if(err) throw php::exception(zend_ce_error_exception
+            if (err == boost::asio::error::operation_aborted) break;
+            else if (err) throw php::exception(zend_ce_error_exception
                 , (boost::format("Failed to accept TCP socket: %s") % err.message()).str()
                 , err.value());
             else{
@@ -99,7 +99,7 @@ namespace flame::tcp {
     }
     
     php::value server::close(php::parameters &params) {
-        if(!closed_) {
+        if (!closed_) {
             closed_ = true;
             acceptor_.cancel();
         }
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
index f68d765..96de57a 100644
--- a/src/tcp/socket.cpp
+++ b/src/tcp/socket.cpp
@@ -41,7 +41,7 @@ namespace flame::tcp {
         }
         else if (params[0].typeof(php::TYPE::INTEGER)) { // 3. 读取指定长度
 			std::size_t want = params[0].to_integer();
-			if(buffer_.size() >= want) {
+			if (buffer_.size() >= want) {
                 len = want;
                 goto RETURN_DATA;
 			}
diff --git a/src/tcp/tcp.cpp b/src/tcp/tcp.cpp
index 9fa17f1..075ba5d 100644
--- a/src/tcp/tcp.cpp
+++ b/src/tcp/tcp.cpp
@@ -28,7 +28,7 @@ namespace flame::tcp {
 
         std::string str = params[0];
         auto pair = udp::addr2pair(str);
-        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
         	, "Failed to connect TCP socket: illegal address format"
         	, -1);
 
@@ -37,12 +37,12 @@ namespace flame::tcp {
 		boost::asio::ip::tcp::resolver::results_type eps;
 		// DNS 地址解析
 		resolver_->async_resolve(pair.first, pair.second, [&err, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type results) {
-			if(error) err = error;
+			if (error) err = error;
 			else eps = results;
             ch.resume();
 		});
 		ch.suspend();
-		if(err) throw php::exception(zend_ce_exception
+		if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to resolve address: %s") % err.message()).str()
             , err.value());
 
@@ -60,7 +60,7 @@ namespace flame::tcp {
             ch.resume();
         });
         ch.suspend();
-        if(err) throw php::exception(zend_ce_exception
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to connect TCP socket: %s") % err.message()).str()
             , err.value());
         return std::move(obj);
diff --git a/src/time/time.cpp b/src/time/time.cpp
index f763bef..7bafc13 100644
--- a/src/time/time.cpp
+++ b/src/time/time.cpp
@@ -13,7 +13,7 @@ namespace flame::time
         coroutine_handler ch {coroutine::current};
         boost::asio::steady_timer tm(gcontroller->context_x);
         int ms = static_cast<int>(params[0]);
-        if(ms < 0) ms = 1;
+        if (ms < 0) ms = 1;
         tm.expires_from_now(std::chrono::milliseconds(ms));
         tm.async_wait(ch);
         return nullptr;
diff --git a/src/udp/server.cpp b/src/udp/server.cpp
index 6ff1002..6f9dba8 100644
--- a/src/udp/server.cpp
+++ b/src/udp/server.cpp
@@ -38,7 +38,7 @@ namespace flame::udp {
         auto pair = addr2pair(params[0]);
 		boost::system::error_code err;
 		boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
-		if(err) throw php::exception(zend_ce_exception
+		if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to bind: %s") % err.message()).str()
             , err.value());
 		std::uint16_t port = std::stoi(pair.second);
@@ -51,15 +51,15 @@ namespace flame::udp {
         reuse_port opt2(true);
         socket_.set_option(opt2);
 		socket_.bind(addr_, err);
-		if(err) throw php::exception(zend_ce_exception
+		if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to bind: %s") % err.message()).str()
             , err.value());
 
-        if(params.size() > 1) {
+        if (params.size() > 1) {
             php::array options = params[1];
-            if(options.exists("concurrent"))
+            if (options.exists("concurrent"))
                 concurrent_ = std::max(std::min(static_cast<int>(options.get("concurrent")), 256), 1);
-            if(options.exists("max"))
+            if (options.exists("max"))
                 max_ = std::min(std::max(static_cast<int>(options.get("max")) , 1), 64 * 1024);
         }
         return nullptr;
@@ -87,7 +87,7 @@ namespace flame::udp {
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
                     }
                 }
-                if(--count == 0) ch.resume();
+                if (--count == 0) ch.resume();
                 return nullptr;
             }));
         }
@@ -100,14 +100,14 @@ namespace flame::udp {
     		socket_.async_receive_from(boost::asio::buffer(buffer.prepare(max_), max_), ep
                 , [&len, &err, &ch] (const boost::system::error_code& error, std::size_t nread) {
 
-                if(error) err = error;
+                if (error) err = error;
                 else len = nread;
                 ch.resume();
             });
             ch.suspend();
             // 存在可能被关闭, 直接返回
-            if(err == boost::asio::error::operation_aborted) goto CLOSING;
-            else if(err) throw php::exception(zend_ce_exception
+            if (err == boost::asio::error::operation_aborted) goto CLOSING;
+            else if (err) throw php::exception(zend_ce_exception
                 , (boost::format("Failed to read TCP socket: %s") % err.message()).str()
                 , err.value());
             buffer.commit(len);
@@ -126,7 +126,7 @@ namespace flame::udp {
         boost::system::error_code err;
 		php::string data = params[0];
 		auto pair = addr2pair(params[1]);
-		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+		if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
             , "Failed to send udp packet: illegal address format"
             , -1);
 
@@ -134,13 +134,13 @@ namespace flame::udp {
 		resolver_->async_resolve(pair.first, pair.second
             , [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
 
-			if(error) err = error;
+			if (error) err = error;
 			else eps = results;
 			ch.resume();
 		});
 		ch.suspend();
 		if (err == boost::asio::error::operation_aborted) return nullptr;
-		else if(err) throw php::exception(zend_ce_exception
+		else if (err) throw php::exception(zend_ce_exception
 			, (boost::format("Failed to resolve address: %s") % err.message()).str()
             , err.value());
 
@@ -148,7 +148,7 @@ namespace flame::udp {
 		int sent = 0;
 		for(auto i=eps.begin(); i!=eps.end(); ++i) {
 			socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
-			if(!err) return nullptr;
+			if (!err) return nullptr;
 		}
 
 		throw php::exception(zend_ce_exception
diff --git a/src/udp/socket.cpp b/src/udp/socket.cpp
index 391d10e..959f23f 100644
--- a/src/udp/socket.cpp
+++ b/src/udp/socket.cpp
@@ -41,7 +41,7 @@ namespace flame::udp {
 		if (params.size() > 0 && params[0].typeof(php::TYPE::STRING)) {
 			auto pair = addr2pair(params[0]);
 			boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
-			if(err) throw php::exception(zend_ce_exception
+			if (err) throw php::exception(zend_ce_exception
 	            , (boost::format("Failed to bind: %s") % err.message()).str()
 	            , err.value());
 			std::uint16_t port = std::stoi(pair.second);
@@ -52,33 +52,34 @@ namespace flame::udp {
 	        reuse_port opt2(true);
 	        socket_.set_option(opt2);
 			socket_.bind(ep, err);
-			if(err) throw php::exception(zend_ce_exception
+			if (err) throw php::exception(zend_ce_exception
 	            , (boost::format("failed to bind: %s") % err.message()).str()
 	            , err.value());
-			if(params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
+			if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
 				option = params[0];
 				goto PARSE_OPTION;
 			}
-		} else {
+		}
+		else {
 			socket_.open(boost::asio::ip::udp::v6(), err);
 			if (err) socket_.open(boost::asio::ip::udp::v4(), err);
 			if (err) throw php::exception(zend_ce_exception
 				, (boost::format("Failed to bind: %s") % err.message()).str()
 				, err.value());
-			if(params.size() > 0 && params[0].typeof(php::TYPE::ARRAY)) {
+			if (params.size() > 0 && params[0].typeof(php::TYPE::ARRAY)) {
 				option = params[0];
 				goto PARSE_OPTION;
 			}
 		}
 		return nullptr;
 PARSE_OPTION:
-		if(option.exists("max"))
+		if (option.exists("max"))
 			max_ = std::min(std::max(static_cast<int>(option.get("max")), 64), 64 * 1024);
 		return nullptr;
 	}
 
 	php::value socket::recv(php::parameters& params) {
-		if(!connected_) throw php::exception(zend_ce_error_exception
+		if (!connected_) throw php::exception(zend_ce_error_exception
 			, "Failed to send: socket not connected"
 			, -1);
         coroutine_handler ch{coroutine::current};
@@ -111,12 +112,12 @@ namespace flame::udp {
         else if (err) throw php::exception(zend_ce_exception
             , (boost::format("failed to read TCP socket: %s") % err.message()).str()
             , err.value());
-		if(params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
+		if (params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
 		return php::string(std::move(buffer_));
     }
 
 	php::value socket::send(php::parameters& params) {
-		if(!connected_) throw php::exception(zend_ce_error_exception
+		if (!connected_) throw php::exception(zend_ce_error_exception
 			, "Failed to send: connected socket required"
 			, -1);
         coroutine_handler ch{coroutine::current};
@@ -138,7 +139,7 @@ namespace flame::udp {
         boost::system::error_code err;
 		php::string data = params[0];
 		auto pair = addr2pair(params[1]);
-		if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+		if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
 			, "Failed to send udp packet: illegal address format"
 			, -1);
 
@@ -146,13 +147,13 @@ namespace flame::udp {
 		resolver_->async_resolve(pair.first, pair.second, 
 			[&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
 
-			if(error) err = error;
+			if (error) err = error;
 			else eps = results;
 			ch.resume();
 		});
 		ch.suspend();
 		if (err == boost::asio::error::operation_aborted) return nullptr;
-		else if(err) throw php::exception(zend_ce_exception
+		else if (err) throw php::exception(zend_ce_exception
 			, (boost::format("Failed to resolve address: %s") % err.message()).str()
 			, err.value());
 
@@ -160,7 +161,7 @@ namespace flame::udp {
 		int sent = 0;
 		for(auto i=eps.begin(); i!=eps.end(); ++i) {
 			socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
-			if(!err) return nullptr;
+			if (!err) return nullptr;
 		}
 
 		throw php::exception(zend_ce_exception
diff --git a/src/udp/udp.cpp b/src/udp/udp.cpp
index e0fc212..c58d721 100644
--- a/src/udp/udp.cpp
+++ b/src/udp/udp.cpp
@@ -28,7 +28,7 @@ namespace flame::udp {
 
         std::string str = params[0];
         auto pair = addr2pair(str);
-        if(pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
             , "Failed to connect UDP socket: illegal address format"
             , -1);
 
@@ -38,12 +38,12 @@ namespace flame::udp {
 		resolver_->async_resolve(pair.first, pair.second
             , [&err, &obj, &ptr, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) { // DNS 地址解析
 
-			if(error) err = error;
+			if (error) err = error;
 			else eps = results;
 			ch.resume();
 		});
 		ch.suspend();
-		if(err) throw php::exception(zend_ce_exception
+		if (err) throw php::exception(zend_ce_exception
                 , (boost::format("Failed to resolve address: %s") % err.message()).str()
                 , err.value());
 
@@ -58,7 +58,7 @@ namespace flame::udp {
             ch.resume();
         });
         ch.suspend();
-        if(err) throw php::exception(zend_ce_exception
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to connect UDP socket: %s") % err.message()).str()
             , err.value());
 		ptr->connected_ = true;
diff --git a/src/url.cpp b/src/url.cpp
index 9b53e2a..2eb1781 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -1,50 +1,44 @@
 #include "vendor.h"
 #include "url.h"
 
-namespace flame
-{
-
+namespace flame {
     typedef ::parser::separator_parser<std::string, std::string> parser_t;
+
     url::url(const php::string &str, bool parse_query)
-        : raw_(str)
-    {
+    : raw_(str) {
         const char* s = str.c_str();
 
         struct http_parser_url x;
         http_parser_parse_url(s, str.size(), 0, &x);
-        if(x.field_set & (1 << UF_SCHEMA)) {
+        if (x.field_set & (1 << UF_SCHEMA)) {
             auto y = x.field_data[UF_SCHEMA];
             schema.assign(s + y.off, y.len);
         }
-        if(x.field_set & (1 << UF_USERINFO)) {
+        if (x.field_set & (1 << UF_USERINFO)) {
             auto y = x.field_data[UF_USERINFO];
             int i = 0;
             for(;i<y.len;++i) {
-                if(s[y.off + i] == ':') {
-                    break;
-                }else{
-                    user.push_back(s[y.off + i]);
-                }
+                if (s[y.off + i] == ':') break;
+                else user.push_back(s[y.off + i]);
             }
             ++i;
-            for(;i<y.len;++i) {
+            for (;i<y.len;++i) {
                 pass.push_back(s[y.off + i]);
             }
         }
-        if(x.field_set & (1 << UF_HOST)) {
+        if (x.field_set & (1 << UF_HOST)) {
             auto y = x.field_data[UF_HOST];
             host.assign(s + y.off, y.len);
         }
         port = x.port;
-        if (x.field_set & (1 << UF_PATH))
-        {
+        if (x.field_set & (1 << UF_PATH)) {
             auto y = x.field_data[UF_PATH];
             path.assign(s + y.off, y.len);
         }
         if (x.field_set & (1 << UF_QUERY)) {
             auto y = x.field_data[UF_QUERY];
             query_raw.assign(s + y.off, y.len);
-            if(parse_query) {
+            if (parse_query) {
                 parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
                     std::string key = et.first;
                     std::string val = et.second;
@@ -59,15 +53,11 @@ namespace flame
         }
     }
 
-    std::string url::str(bool with_query, bool update)
-    {
-        if(update) {
+    std::string url::str(bool with_query, bool update) {
+        if (update) {
             std::ostringstream ss;
             ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path;
-            if(with_query)
-            {
-                ss << "?" << query_raw;
-            }
+            if (with_query) ss << "?" << query_raw;
             raw_ = ss.str();
         }
         return raw_;

From 3ed4bbb729972088a75cb6186b99bbebcb847d03 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 11 Apr 2019 14:30:21 +0800
Subject: [PATCH 096/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E4=BD=BF?=
 =?UTF-8?q?=E7=94=A8=20USR1=20=E4=BF=A1=E5=8F=B7=E8=BF=9B=E8=A1=8C?=
 =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E9=87=8D=E8=BD=BD=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php              |   2 +-
 src/controller.cpp        |  22 ++-
 src/controller_master.cpp | 317 ++++++++++++++++++++++++--------------
 src/controller_master.h   |  63 ++++++--
 src/controller_worker.cpp |  58 ++++---
 src/controller_worker.h   |   3 +-
 src/vendor.h              |   1 +
 7 files changed, 287 insertions(+), 179 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index 0af585b..731e434 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -15,7 +15,7 @@
  *      "warning"
  *      "error"
  *      "fatal"
- *  * "timeout" - 多进程退出超时（超时后会被强制杀死）(v0.12.12+ 默认 1s 超时 / v0.12.11- 默认 10s 超时）
+ *  * "timeout" - 多进程退出超时, 单位毫秒( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
  *  @see flame\log
  *
  * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式
diff --git a/src/controller.cpp b/src/controller.cpp
index 917bc02..437cea4 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -34,23 +34,21 @@ namespace flame {
 	}
 	void controller::initialize(const std::string& title, const php::array& options) {
 		// 多进程模式退出超时时间，默认 1s 超时强制退出
-		if(options.exists("timeout")) {
-			worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 1), 128);
-		}else{
-			worker_quit = 1;
-		}
+		if (options.exists("timeout"))
+			worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
+		else worker_quit = 3000;
 		status |= STATUS_INITIALIZED;
 		for (auto fn : init_cb) fn(options);
-		if(type == process_type::WORKER) {
+		if (type == process_type::WORKER) {
 			if (worker_size > 0) {
 				std::string index = env["FLAME_CUR_WORKER"].to_string();
 				php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
-			}else{
-				php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
 			}
+			else php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
 			worker_.reset(new controller_worker());
 			worker_->initialize(options);
-		}else/* if(type == process_type::MASTER)*/ {
+		}
+		else/* if (type == process_type::MASTER)*/ {
 			php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
 			master_.reset(new controller_master());
 			master_->initialize(options);
@@ -59,11 +57,11 @@ namespace flame {
 
 	void controller::run() {
 		status &= controller_status::STATUS_RUN;
-		if(type == process_type::WORKER) worker_->run();
-		else /* if(type == process_type::MASTER)*/ master_->run();
+		if (type == process_type::WORKER) worker_->run();
+		else /* if (type == process_type::MASTER)*/ master_->run();
 		delete cbmap;
 		// 运行完毕
-		if(status & controller_status::STATUS_EXCEPTION) _exit(-1);
+		if (status & controller_status::STATUS_EXCEPTION) _exit(-1);
 		else _exit(0);
 		// 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
 	}
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 6df3d96..f2e94af 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -1,119 +1,225 @@
 #include "controller_master.h"
 #include "controller.h"
 
-namespace flame
-{
-    controller_master::controller_master()
-        : worker_(gcontroller->worker_size)
-        , sout_(gcontroller->worker_size)
-        , sbuf_(gcontroller->worker_size)
-        , eout_(gcontroller->worker_size)
-        , ebuf_(gcontroller->worker_size)
-    {
-
-    }
-    void controller_master::initialize(const php::array& options)
-    {
-        if(options.exists("logger"))
-        {
-            ofpath_ = options.get("logger").to_string();
-        }
-        else
-        {
-            ofpath_ = "stdout";
-        }
-        reload_output();
-    }
-    static std::string start_command_line()
-    {
+namespace flame {
+
+    static std::string start_command_line() {
         std::ostringstream ss;
         ss << php::constant("PHP_BINARY");
         php::array argv = php::server("argv");
-        for (auto i = argv.begin(); i != argv.end(); ++i)
-        {
-            ss << " " << i->second;
-        }
+        for (auto i = argv.begin(); i != argv.end(); ++i) ss << " " << i->second;
         return ss.str();
     }
-    void controller_master::spawn_worker(int i)
-    {
-        sout_[i].reset( new boost::process::async_pipe(gcontroller->context_x) );
-        eout_[i].reset( new boost::process::async_pipe(gcontroller->context_x) );
 
-        ++count_;
+    controller_master_worker::controller_master_worker(controller_master* m, int i)
+    : m_(m), i_(i)
+    , sout_(gcontroller->context_x), eout_(gcontroller->context_x) {
+        // 准备命令行及环境变量
         std::string cmd = start_command_line();
         boost::process::environment env = gcontroller->env;
         env["FLAME_CUR_WORKER"] = std::to_string(i + 1);
-
-        worker_[i].reset( new boost::process::child(cmd, env/*, group_*/, gcontroller->context_x,
-            boost::process::std_out > *sout_[i],
-            boost::process::std_err > *eout_[i],
-            // boost::process::std_out > boost::process::null,
+        // 构造进程
+        proc_ = boost::process::child(gcontroller->context_x, cmd, env, 
+            boost::process::std_out > sout_, boost::process::std_err > eout_,
+            // 结束回调
             boost::process::on_exit = [this, i](int exit_code, const std::error_code &error) {
                 if (error.value() == static_cast<int>(std::errc::no_child_process)) return;
-
-                if (exit_code != 0) {
-                    // 日志记录
-                    int sec = std::rand() % 3 + 3;
-                    std::cerr << "(FATAL) worker unexpected exit: (" << exit_code << "), restart in " << sec << " ...\n";
-
-                    auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
-                    tm->async_wait([this, tm, i](const boost::system::error_code &error) {
-                        if (error)
-                            std::cerr << "(FATAL) failed to restart worker: (" << error.value() << ") " << error.message() << std::endl;
-                        else spawn_worker(i);
-                    });
-                }
-                worker_[i].reset();
-                sout_[i].reset();
-                eout_[i].reset();
-            }) );
-        redirect_sout(i);
-        redirect_eout(i);
-    }
-
-    void controller_master::redirect_sout(int i) {
-        boost::asio::async_read_until(*sout_[i], sbuf_[i], '\n', [this, i](const boost::system::error_code &error, std::size_t nread) {
-            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof) ;
-                // std::cerr << "(INFO) IGNORE sout\n";
-            else if(error)
-                std::cerr << "(ERROR) failed to read from worker process: (" << error.value() << ") " << error.message() << std::endl;
-            else  {
-                auto y = sbuf_[i].data();
-                *offile_ << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
-                sbuf_[i].consume(nread);
-                offile_->flush();
-
-                redirect_sout(i);
-            }
-        });
+                m_->notify_exit(i, this, exit_code);
+            });
+        redirect_output(sout_, sbuf_);
+        redirect_output(eout_, ebuf_);
     }
 
-    void controller_master::redirect_eout(int i) {
-        boost::asio::async_read_until(*eout_[i], ebuf_[i], '\n', [this, i](const boost::system::error_code &error, std::size_t nread) {
-            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof) ;
+    void controller_master_worker::redirect_output(boost::process::async_pipe& pipe, boost::asio::streambuf& buffer) {
+        boost::asio::async_read_until(pipe, buffer, '\n', [this, &pipe, &buffer] (const boost::system::error_code &error, std::size_t nread) {
+            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
+                ; // std::cerr << "(INFO) IGNORE sout\n";
             else if (error)
-                std::cerr << "(ERROR) failed to read from worker process: (" << error.value() << ") " << error.message() << std::endl;
+                std::cerr << "(ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
             else {
-                auto y = ebuf_[i].data();
-                *offile_ << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
-                ebuf_[i].consume(nread);
-                offile_->flush();
-                
-                redirect_eout(i);
+                auto y = buffer.data();
+                *(m_->offile_) << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
+                buffer.consume(nread);
+                m_->offile_->flush();
+
+                redirect_output(pipe, buffer);
             }
         });
     }
 
+    void controller_master_worker::close() {
+        ::kill(proc_.id(), SIGTERM);
+    }
+
+    void controller_master_worker::close_now() {
+        proc_.terminate();
+        sout_.cancel();
+        eout_.cancel();
+        // 注意: 上面强制进程终止过程 proc_.terminate() 流程不会触发 on_exit 回调
+        // 注意：不能在当前流程直接调用 notify_exit 否则会导致错误的状态使用: RESTART + SINGLE FORCEFUL CLOSE
+        // m_->notify_exit(i_, this, 0);
+        boost::asio::post(std::bind(&controller_master::notify_exit, m_, i_, this, 0));
+    }
+
+    controller_master::controller_master()
+    : worker_spawn_(gcontroller->worker_size, nullptr)
+    , worker_close_(gcontroller->worker_size, nullptr)
+    , worker_spawn_count_(0)
+    , worker_count_(0)
+    , tm_inter_(gcontroller->context_x)
+    , tm_force_(gcontroller->context_x) {
+
+    }
+
+    void controller_master::initialize(const php::array& options) {
+        if (options.exists("logger")) ofpath_ = options.get("logger").to_string();
+        else ofpath_ = "stdout";
+        reload_output();
+    }
+
+    void controller_master::spawn_worker_next(int i) {
+        if (i >= gcontroller->worker_size) return;
+        if (i == 0) worker_spawn_count_ = 0;
+        spawn_worker(i);
+        
+        // 防止启动过快，按间隔进行启动
+        tm_inter_.cancel();
+        tm_inter_.expires_after(std::chrono::milliseconds(125));
+        tm_inter_.async_wait([this, i] (const boost::system::error_code& error) {
+            if (error) return;
+            spawn_worker_next(i + 1);
+        });
+    }
+
+    void controller_master::spawn_worker(int i) {
+        worker_spawn_[i] = new controller_master_worker(this, i);
+        ++worker_count_;
+        if(++worker_spawn_count_ == gcontroller->worker_size) { // 所有进程进行了启动或重启
+            if((close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_MASTER) close_ = CLOSE_NOTHING; // 重启流程完毕, 重置状态
+        }
+    }
+
+    void controller_master::close_worker(int i) {
+        std::cout << "close_worker: " << i << "\n";
+        if (worker_close_[i] == nullptr) return;
+        if ((close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_TERMINATE) worker_close_[i]->close_now();
+        else worker_close_[i]->close();
+    }
+
+    void controller_master::close_worker_next(int i) {
+        if (i == 0) worker_spawn_count_ = 0;
+        if (i >= gcontroller->worker_size) {
+            // 非重启模式, 所有进程都进行了关闭动作, 统一进行停止超时
+            if((close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_RESTART
+                && (close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_SIGNALING) {
+
+                tm_force_.cancel();
+                tm_force_.expires_after(std::chrono::milliseconds(gcontroller->worker_quit));
+                tm_force_.async_wait([this] (const boost::system::error_code& error) {
+                    if(error) return;
+                    // 切换关闭模式
+                    close_ =  (close_ & CLOSE_FLAG_MASK) | CLOSE_WORKER_TERMINATE;
+                    close_worker_next();
+                });
+            }
+            return;
+        }
+
+        close_worker(i);
+        // 重启模式, 下一个进程的关闭将在本进程结束后开始
+        // 重启模式, 按单个进程设置超时
+        // if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_RESTART
+            // && (close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_SIGNALING) {
+        if (close_ == (CLOSE_FLAG_RESTART | CLOSE_WORKER_SIGNALING)) {
+            tm_force_.cancel();
+            tm_force_.expires_after(std::chrono::milliseconds(gcontroller->worker_quit));
+            tm_force_.async_wait([this, i] (const boost::system::error_code& error) {
+                if(error) return;
+                // 切换关闭模式, 立即结束进程
+                close_ = (close_ & CLOSE_FLAG_MASK) | CLOSE_WORKER_TERMINATE;
+                close_worker(i);
+                // 恢复关闭模式, 本进程结束后继续下一个进程关闭
+                close_ = (close_ & CLOSE_FLAG_MASK) | CLOSE_WORKER_SIGNALING;
+            });
+        }
+        else { // 非重启模式, 继续下一个进程
+            tm_inter_.cancel();
+            tm_inter_.expires_after(std::chrono::milliseconds(125));
+            tm_inter_.async_wait([this, i] (const boost::system::error_code& error) {
+                if (error) return;
+                close_worker_next(i + 1);
+            });
+        }
+    }
+
+    void controller_master::notify_exit(int i, controller_master_worker* w, int exit_code) {
+        // 异常退出
+        if (exit_code != 0) { 
+            int sec = std::rand() % 3 + 3;
+            std::cerr << "(FATAL) worker unexpected exit: (" << exit_code << "), restart in " << sec << " ...\n";
+            // 少许延迟进行重启
+            auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
+            tm->async_wait([this, tm, i](const boost::system::error_code &error) {
+                if (error) std::cerr << "(FATAL) failed to restart worker: ("
+                    << error.value() << ") " << error.message() << std::endl;
+                else spawn_worker(i); // 重启进程
+            });
+        }
+        // 下面容器理论上仅会命中一个
+        if (worker_spawn_[i] == w) worker_spawn_[i] = nullptr;
+        if (worker_close_[i] == w) worker_close_[i] = nullptr;
+        delete w;
+        --worker_count_;
+        // 正常退出
+        if (exit_code == 0) {
+            // 所有进程全部关闭, 同时计划关闭主进程
+            if (worker_count_ == 0 && (close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) close(); 
+            // 重启模式, 关闭了一个进程
+            // if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_RESTART
+            // && (close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_SIGNALING) {
+            if (close_ == (CLOSE_FLAG_RESTART | CLOSE_WORKER_SIGNALING)) {
+                tm_force_.cancel(); // 取消超时强制
+                spawn_worker(i); // 重启子进程
+                // 继续关闭下一个进程
+                tm_inter_.cancel();
+                tm_inter_.expires_after(std::chrono::milliseconds(125));
+                tm_inter_.async_wait([this, i] (const boost::system::error_code& error) {
+                    if (error) return;
+                    close_worker_next(i + 1);
+                });
+            }
+        }
+    }
+
+    void controller_master::close() {
+        // 强制模式，子进程已经全部被结束了 -> 主进退出
+        gcontroller->context_x.stop(); 
+    }
+
     void controller_master::await_signal() {
         signal_->async_wait([this](const boost::system::error_code &error, int sig) {
             if (error) return;
+             // 主进程与子进程 signal 处理之间有干扰
             await_signal();
             boost::asio::post(gcontroller->context_x, [this, sig]() {
-                if (sig == SIGUSR2) reload_output();
-                else close_worker();
-                // 似乎主进程与子进程 signal 处理之间有干扰
-                // 需要后置清理
+                if (sig == SIGUSR1) {
+                    if (close_ != CLOSE_NOTHING) return;
+                    close_ = CLOSE_FLAG_RESTART | CLOSE_WORKER_SIGNALING;
+                    worker_close_.swap(worker_spawn_); // 准备关闭的进程
+                    close_worker_next(); // 关闭现有进程
+                }
+                else if (sig == SIGUSR2) reload_output();
+                else {
+                    if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) {
+                        close_ = CLOSE_FLAG_MASTER | CLOSE_WORKER_TERMINATE;
+                        close_worker_next();
+                    }
+                    else if (close_ == CLOSE_NOTHING) {
+                        close_ = CLOSE_FLAG_MASTER | CLOSE_WORKER_SIGNALING;
+                        worker_close_.swap(worker_spawn_); // 准备关闭的进程
+                        close_worker_next();
+                    }
+                }
                 // signal_.reset();
             });
         });
@@ -124,7 +230,9 @@ namespace flame
         // 1. 新增环境变量及命令行参数, 启动子进程;
         for(int i=0;i<gcontroller->worker_size;++i) spawn_worker(i);
         // 2. 监听信号进行日志重载或停止
-        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM, SIGUSR2));
+        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
+        signal_->add(SIGUSR1); // 进程重载
+        signal_->add(SIGUSR2); // 日志重载
         await_signal();
         // 3. 启动运行
         thread_ = std::thread([this] {
@@ -133,43 +241,18 @@ namespace flame
         gcontroller->context_x.run();
         // 4. 等待工作线程结束
         if (signal_) signal_.reset();
-        if (timer_) timer_.reset();
         thread_.join();
-        // 5. 强制进程结束
-        for(auto i=worker_.begin();i!=worker_.end();++i) {
-            if(*i) (*i)->terminate();
-        }
-        // if(!group_.wait_for(std::chrono::seconds(1))) group_.terminate();
     }
 
     void controller_master::reload_output() {
-        if(ofpath_[0] == '/') {
+        if (ofpath_[0] == '/') {
             offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
             // 文件打开失败时不会抛出异常，需要额外的状态检查
-			if(!(*offile_))
+			if (!(*offile_))
                 std::cerr << "(ERROR) failed to create/open logger target file: fallback to standard output\n";
             else return;
 		}
         offile_.reset(&std::clog, boost::null_deleter());
     }
 
-    void controller_master::close_worker() {
-        // 第二次进行 “强制关闭”
-        if(close_) {
-            timer_.reset();
-            gcontroller->context_x.stop();
-            return;
-        }
-        // 第一次设置超时
-        close_ = true;
-        for (auto i = worker_.begin(); i != worker_.end(); ++i) {
-            if (*i) ::kill((*i)->id(), SIGTERM);
-        }
-        timer_.reset(new boost::asio::steady_timer(gcontroller->context_y));
-        timer_->expires_after(std::chrono::seconds(gcontroller->worker_quit));
-        timer_->async_wait([] (const boost::system::error_code& error) {
-            if(error) return;
-            gcontroller->context_x.stop();
-        });
-    }
 }
diff --git a/src/controller_master.h b/src/controller_master.h
index 14ccdab..fe5d6d2 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -1,35 +1,66 @@
 #pragma once
 #include "vendor.h"
 
-namespace flame
-{
+namespace flame {
+
+    class controller_master;
+    class controller_master_worker {
+    public:
+        controller_master_worker(controller_master* m, int i);
+        void close();
+        void close_now();
+    private:
+        controller_master*            m_;
+        int                           i_;
+        boost::process::child      proc_;
+        boost::process::async_pipe sout_;
+        boost::process::async_pipe eout_;
+        boost::asio::streambuf     sbuf_;
+        boost::asio::streambuf     ebuf_;
+        void redirect_output(boost::process::async_pipe& pipe, boost::asio::streambuf& buffer);
+    };
+
     class controller_master {
 	public:
 		controller_master();
         void initialize(const php::array& options);
 		void run();
     private:
-		std::vector<std::unique_ptr<boost::process::child>>    worker_;
-        std::vector<std::unique_ptr<boost::process::async_pipe>> sout_;
-        std::vector<std::unique_ptr<boost::process::async_pipe>> eout_;
-        std::vector<boost::asio::streambuf> sbuf_;
-        std::vector<boost::asio::streambuf> ebuf_;
-        // boost::process::group               group_;
-		std::unique_ptr<boost::asio::signal_set>  signal_;
-        std::unique_ptr<boost::asio::steady_timer> timer_;
+		std::vector<controller_master_worker*>   worker_spawn_;
+        std::vector<controller_master_worker*>   worker_close_;
+        int                                      worker_spawn_count_;
+        int                                      worker_count_;
+		std::unique_ptr<boost::asio::signal_set> signal_;
+        boost::asio::steady_timer tm_inter_;
+        boost::asio::steady_timer tm_force_;
 		std::thread                        thread_;
-        std::size_t                        count_;
 
         std::string                        ofpath_;
         std::shared_ptr<std::ostream>      offile_;
 
         void spawn_worker(int i);
-        void reload_output();
-        void redirect_sout(int i);
-        void redirect_eout(int i);
+        void spawn_worker_next(int i = 0);
+        void notify_exit(int i, controller_master_worker* w, int exit_code);
+        enum {
+            CLOSE_NOTHING, 
+            CLOSE_WORKER_SIGNALING = 0x0001, // 信号通知 + 超时
+            CLOSE_WORKER_TERMINATE = 0x0002, // 立即停止
+            CLOSE_WORKER_MASK      = 0x000f, 
+            CLOSE_FLAG_RESTART     = 0x0010, // 重启
+            CLOSE_FLAG_MASTER      = 0x0020, // 停止主进程
+            CLOSE_FLAG_MASK        = 0x00f0,
+        };
+        int close_ = CLOSE_NOTHING;
+        void close_worker(int i);
+        void close_worker_next(int i = 0);
+
+        void close();
 
+        void reload_worker();
+        
+
+        void reload_output();
         void await_signal();
-        bool close_ = false;
-        void close_worker();
+        friend class controller_master_worker;
     };
 }
\ No newline at end of file
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index ddf62d9..067e646 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -2,49 +2,46 @@
 #include "controller_worker.h"
 #include "controller.h"
 
-namespace flame
-{
-    controller_worker::controller_worker()
-    {
+namespace flame {
+    
+    controller_worker::controller_worker() {
 
     }
-    void controller_worker::initialize(const php::array &options)
-    {
+
+    void controller_worker::initialize(const php::array &options) {
 
     }
-    void controller_worker::await_signal() {
-        signal_->async_wait([this] (const boost::system::error_code &error, int sig)
-        {
-            if(error || sig == SIGUSR2) return;
-            // 似乎主进程与子进程 signal 处理之间有干扰
-            // 需要后置清理
-            if(gcontroller->worker_size == 0) signal_.reset();
-            else await_signal();
 
-            boost::asio::post(gcontroller->context_x, [this] () {
-                gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
-                auto ft = gcontroller->cbmap->equal_range("quit");
-                for (auto i = ft.first; i != ft.second; ++i)
-                {
-                    // 需要在协程调用退出回调
-                    coroutine::start(i->second);
-                }
-            });
+    void controller_worker::await_signal() {
+        signal_->async_wait([this] (const boost::system::error_code &error, int sig) {
+            // 主进程与子进程 signal 处理之间有干扰
+            if (error) return; 
+            if (gcontroller->worker_size == 0) signal_.reset(); // 单进程模式
+            else await_signal(); // 多进程模式的工作进程需要持续监听
+            
+            if(sig == SIGTERM) { // 工作进程仅处理退出信号
+                boost::asio::post(gcontroller->context_x, [this] () {
+                    gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
+                    auto ft = gcontroller->cbmap->equal_range("quit"); // 需要在协程调用退出回调
+                    for (auto i = ft.first; i != ft.second; ++i) coroutine::start(i->second);
+                });
+            }
         });
     }
-    void controller_worker::run()
-    {
+
+    void controller_worker::run() {
         // 工作线程的使用是随机的, 需要保持其一直存在
         auto work = boost::asio::make_work_guard(gcontroller->context_y);
         // 1. 停止信号调用退出通知
-        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGUSR2, SIGINT, SIGTERM));
+        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
+        signal_->add(SIGUSR1);
+        signal_->add(SIGUSR2);
         await_signal();
 
         // 子进程的启动过程:
         // 2. 启动线程池, 并使用线程池运行 context_y
         thread_.resize(4);
-        for (int i = 0; i < thread_.size(); ++i)
-        {
+        for (int i = 0; i < thread_.size(); ++i) {
             thread_[i] = new std::thread([this] {
                 gcontroller->context_y.run();
             });
@@ -52,10 +49,9 @@ namespace flame
         // 3. 启动 context_x 运行
         gcontroller->context_x.run();
         // 4. 确认工作线程停止
-        if(signal_) signal_.reset();
+        if (signal_) signal_.reset();
         work.reset();
-        for (int i = 0; i < thread_.size(); ++i)
-        {
+        for (int i = 0; i < thread_.size(); ++i) {
             thread_[i]->join();
             delete thread_[i];
         }
diff --git a/src/controller_worker.h b/src/controller_worker.h
index f781da0..98b9a0c 100644
--- a/src/controller_worker.h
+++ b/src/controller_worker.h
@@ -1,8 +1,7 @@
 #pragma once
 #include "vendor.h"
 
-namespace flame
-{
+namespace flame {
     class controller_worker {
 	public:
 		controller_worker();
diff --git a/src/vendor.h b/src/vendor.h
index e93682e..19efb41 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -17,6 +17,7 @@
 #include <stack>
 #include <string_view>
 #include <optional>
+#include <set>
 #include <unordered_set>
 #include <phpext/phpext.h>
 #include <http_parser.h>

From 8484f0cfa6ddd7915d1a6e2566cb3c21137b2563 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 11 Apr 2019 20:08:41 +0800
Subject: [PATCH 097/146] =?UTF-8?q?=E8=A7=84=E6=95=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/compress/compress.cpp        |  20 +-
 src/controller.cpp               | 122 ++++-----
 src/controller.h                 |  90 ++++---
 src/controller_master.cpp        |   4 +-
 src/controller_master.h          |  12 +-
 src/controller_worker.h          |  16 +-
 src/coroutine.h                  | 146 +++++-----
 src/hash/hash.cpp                |   8 +-
 src/http/_handler.h              |   6 +-
 src/http/client.cpp              |   3 +-
 src/http/client.h                |   1 +
 src/http/client_body.cpp         |   6 +-
 src/http/client_body.h           |   9 +-
 src/http/client_poll.cpp         | 135 +++++-----
 src/http/client_poll.h           |  71 ++---
 src/http/client_request.cpp      | 332 +++++++++++------------
 src/http/client_request.h        |  31 +--
 src/http/client_response.cpp     | 136 +++++-----
 src/http/client_response.h       |  39 +--
 src/http/http.cpp                | 279 ++++++++++----------
 src/http/http.h                  |  23 +-
 src/http/server.cpp              |   7 +-
 src/http/server.h                |   8 +-
 src/http/server_request.cpp      | 164 ++++++------
 src/http/server_request.h        |  19 +-
 src/http/server_response.cpp     | 440 +++++++++++++++----------------
 src/http/server_response.h       |  53 ++--
 src/http/value_body.cpp          |   7 +-
 src/http/value_body.h            |  11 +-
 src/http/value_body.ipp          |  12 +-
 src/kafka/_consumer.cpp          |   8 +-
 src/kafka/_consumer.h            |   8 +-
 src/kafka/_producer.cpp          |   4 +-
 src/kafka/_producer.h            |   4 +-
 src/kafka/consumer.cpp           |  32 +--
 src/kafka/consumer.h             |   7 +-
 src/kafka/kafka.cpp              |  14 +-
 src/kafka/kafka.h                |   3 +-
 src/kafka/message.cpp            |  59 ++---
 src/kafka/message.h              |  32 +--
 src/kafka/producer.cpp           |  16 +-
 src/kafka/producer.h             |   7 +-
 src/log/log.h                    |   4 +-
 src/mongodb/_connection_base.cpp |   5 +-
 src/mongodb/_connection_base.h   |   4 +-
 src/mongodb/_connection_lock.cpp |   4 +-
 src/mongodb/_connection_lock.h   |   7 +-
 src/mongodb/_connection_pool.cpp |   8 +-
 src/mongodb/_connection_pool.h   |   3 +-
 src/mongodb/client.cpp           |  39 ++-
 src/mongodb/client.h             |   7 +-
 src/mongodb/collection.cpp       |   8 +-
 src/mongodb/collection.h         |  13 +-
 src/mongodb/cursor.h             |  28 +-
 src/mongodb/date_time.cpp        | 106 ++++----
 src/mongodb/date_time.h          |  32 +--
 src/mongodb/mongodb.h            |   3 +-
 src/mongodb/object_id.cpp        |  55 ++--
 src/mongodb/object_id.h          |  30 +--
 src/mutex.cpp                    |  38 ++-
 src/mutex.h                      |  10 +-
 src/mysql/_connection_base.cpp   |   8 +-
 src/mysql/_connection_base.h     |   6 +-
 src/mysql/_connection_lock.cpp   |   4 +-
 src/mysql/_connection_lock.h     |   6 +-
 src/mysql/_connection_pool.cpp   |   3 +-
 src/mysql/_connection_pool.h     |  10 +-
 src/mysql/client.cpp             |   3 +-
 src/mysql/client.h               |   7 +-
 src/mysql/mysql.cpp              |   5 +-
 src/mysql/mysql.h                |   5 +-
 src/mysql/result.h               |   8 +-
 src/mysql/tx.cpp                 |  91 +++----
 src/mysql/tx.h                   |   7 +-
 src/os/os.h                      |   4 +-
 src/os/process.h                 |  38 +--
 src/queue.h                      |  11 +-
 src/rabbitmq/_client.cpp         |   7 +-
 src/rabbitmq/_client.h           |   7 +-
 src/rabbitmq/consumer.cpp        |   4 +-
 src/rabbitmq/consumer.h          |   7 +-
 src/rabbitmq/message.cpp         | 125 ++++-----
 src/rabbitmq/message.h           |   8 +-
 src/rabbitmq/producer.cpp        |  38 ++-
 src/rabbitmq/producer.h          |  24 +-
 src/rabbitmq/rabbitmq.h          |   8 +-
 src/redis/_connection_base.h     |  11 +-
 src/redis/_connection_lock.cpp   |  30 +--
 src/redis/_connection_lock.h     |  41 ++-
 src/redis/_connection_pool.h     |  10 +-
 src/redis/client.cpp             | 322 +++++++++++-----------
 src/redis/client.h               |   6 +-
 src/redis/redis.cpp              |  25 +-
 src/redis/redis.h                |   3 +-
 src/redis/tx.cpp                 | 230 ++++++++--------
 src/redis/tx.h                   |  10 +-
 src/tcp/server.cpp               |  18 +-
 src/tcp/server.h                 |   7 +-
 src/tcp/socket.cpp               |  62 ++---
 src/tcp/socket.h                 |   9 +-
 src/tcp/tcp.cpp                  |  65 +++--
 src/tcp/tcp.h                    |   3 +-
 src/time/time.cpp                |  46 ++--
 src/time/time.h                  |   3 +-
 src/udp/server.cpp               |  98 +++----
 src/udp/socket.cpp               | 260 +++++++++---------
 src/udp/socket.h                 |   4 +-
 src/udp/udp.cpp                  |  54 ++--
 108 files changed, 2191 insertions(+), 2378 deletions(-)

diff --git a/src/compress/compress.cpp b/src/compress/compress.cpp
index c3e5405..66a895d 100644
--- a/src/compress/compress.cpp
+++ b/src/compress/compress.cpp
@@ -1,18 +1,19 @@
 #include "compress.h"
 
 namespace flame::compress {
+    
     static php::value snappy_compress(php::parameters& params) {
         php::string data = params[0].to_string();
         struct iovec iov_in = {
-    		.iov_base = data.data(),
-    		.iov_len = data.size(),
-    	};
+            .iov_base = data.data(),
+            .iov_len = data.size(),
+        };
         std::size_t len = rd_kafka_snappy_max_compressed_length(data.size());
         php::string out(len);
-    	struct iovec iov_out = {
-    		.iov_base = out.data(),
-    		.iov_len = 0xffffffff,
-    	};
+        struct iovec iov_out = {
+            .iov_base = out.data(),
+            .iov_len = 0xffffffff,
+        };
         struct snappy_env env;
         rd_kafka_snappy_init_env(&env);
         rd_kafka_snappy_compress_iov(&env, &iov_in, 1, data.size(), &iov_out);
@@ -20,6 +21,7 @@ namespace flame::compress {
         out.shrink(iov_out.iov_len);
         return std::move(out);
     }
+
     static php::value snappy_uncompress(php::parameters& params) {
         php::string data = params[0].to_string();
         std::size_t len;
@@ -28,8 +30,8 @@ namespace flame::compress {
         rd_kafka_snappy_uncompress(data.c_str(), data.size(), out.data());
         return std::move(out);
     }
-    void declare(php::extension_entry &ext)
-    {
+
+    void declare(php::extension_entry &ext) {
         ext
             .function<snappy_compress>("flame\\compress\\snappy_compress", {
                 {"data", php::TYPE::STRING},
diff --git a/src/controller.cpp b/src/controller.cpp
index 437cea4..6418bf4 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -4,69 +4,69 @@
 #include "controller_worker.h"
 
 namespace flame {
-	// 全局控制器
-	std::unique_ptr<controller> gcontroller;
+    // 全局控制器
+    std::unique_ptr<controller> gcontroller;
 
-	controller::controller()
-		: type(process_type::UNKNOWN)
-		, env(boost::this_process::environment())
-		, status(STATUS_UNKNOWN)
-		, cbmap(new std::multimap<std::string, php::callable>()) {
+    controller::controller()
+        : type(process_type::UNKNOWN)
+        , env(boost::this_process::environment())
+        , status(STATUS_UNKNOWN)
+        , cbmap(new std::multimap<std::string, php::callable>()) {
 
-		worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
-		worker_size = std::min(std::max((int)worker_size, 1), 512);
-		// FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
-		if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
-		else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
-		else {
-			worker_size = 0;
-			type = process_type::WORKER;
-		}
-		mthread_id = std::this_thread::get_id();
-	}
-	controller *controller::on_init(std::function<void (const php::array &options)> fn) {
-		init_cb.push_back(fn);
-		return this;
-	}
-	controller* controller::on_stop(std::function<void ()> fn) {
-		stop_cb.push_back(fn);
-		return this;
-	}
-	void controller::initialize(const std::string& title, const php::array& options) {
-		// 多进程模式退出超时时间，默认 1s 超时强制退出
-		if (options.exists("timeout"))
-			worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
-		else worker_quit = 3000;
-		status |= STATUS_INITIALIZED;
-		for (auto fn : init_cb) fn(options);
-		if (type == process_type::WORKER) {
-			if (worker_size > 0) {
-				std::string index = env["FLAME_CUR_WORKER"].to_string();
-				php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
-			}
-			else php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
-			worker_.reset(new controller_worker());
-			worker_->initialize(options);
-		}
-		else/* if (type == process_type::MASTER)*/ {
-			php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
-			master_.reset(new controller_master());
-			master_->initialize(options);
-		}
-	}
+        worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
+        worker_size = std::min(std::max((int)worker_size, 1), 512);
+        // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
+        if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
+        else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
+        else {
+            worker_size = 0;
+            type = process_type::WORKER;
+        }
+        mthread_id = std::this_thread::get_id();
+    }
+    controller *controller::on_init(std::function<void (const php::array &options)> fn) {
+        init_cb.push_back(fn);
+        return this;
+    }
+    controller* controller::on_stop(std::function<void ()> fn) {
+        stop_cb.push_back(fn);
+        return this;
+    }
+    void controller::initialize(const std::string& title, const php::array& options) {
+        // 多进程模式退出超时时间，默认 1s 超时强制退出
+        if (options.exists("timeout"))
+            worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
+        else worker_quit = 3000;
+        status |= STATUS_INITIALIZED;
+        for (auto fn : init_cb) fn(options);
+        if (type == process_type::WORKER) {
+            if (worker_size > 0) {
+                std::string index = env["FLAME_CUR_WORKER"].to_string();
+                php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
+            }
+            else php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
+            worker_.reset(new controller_worker());
+            worker_->initialize(options);
+        }
+        else/* if (type == process_type::MASTER)*/ {
+            php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
+            master_.reset(new controller_master());
+            master_->initialize(options);
+        }
+    }
 
-	void controller::run() {
-		status &= controller_status::STATUS_RUN;
-		if (type == process_type::WORKER) worker_->run();
-		else /* if (type == process_type::MASTER)*/ master_->run();
-		delete cbmap;
-		// 运行完毕
-		if (status & controller_status::STATUS_EXCEPTION) _exit(-1);
-		else _exit(0);
-		// 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
-	}
+    void controller::run() {
+        status &= controller_status::STATUS_RUN;
+        if (type == process_type::WORKER) worker_->run();
+        else /* if (type == process_type::MASTER)*/ master_->run();
+        delete cbmap;
+        // 运行完毕
+        if (status & controller_status::STATUS_EXCEPTION) _exit(-1);
+        else _exit(0);
+        // 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
+    }
 
-	void controller::stop() {
-		for (auto fn : stop_cb) fn();
-	}
+    void controller::stop() {
+        for (auto fn : stop_cb) fn();
+    }
 }
diff --git a/src/controller.h b/src/controller.h
index 9c840bd..ceef3f8 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -2,51 +2,49 @@
 #include "vendor.h"
 
 namespace flame {
-	class controller_master;
-	class controller_worker;
-	class controller {
-	public:
-		boost::asio::io_context context_x;
-		boost::asio::io_context context_y;
-		zend_execute_data* default_execute_data;
-		enum class process_type
-		{
-			UNKNOWN = 0,
-			MASTER = 1,
-			WORKER = 2,
-		} type;
-		boost::process::environment env;
-		enum controller_status
-		{
-			STATUS_UNKNOWN     = 0x00,
-			STATUS_INITIALIZED = 0x01,
-			STATUS_SHUTDOWN    = 0x02,
-			STATUS_EXCEPTION   = 0x04,
-			STATUS_RUN         = 0x08,
-		};
-		int status;
-		std::size_t    worker_size;
-		std::size_t    worker_quit; // 多进程退出超时时间
-		std::thread::id mthread_id;
-		// 防止 PHP 提前回收, 使用堆容器
-		std::multimap<std::string, php::callable>* cbmap;
-	private:
-		// 核心进程对象
-		std::unique_ptr<controller_master> master_;
-		// 工作进程对象
-		std::unique_ptr<controller_worker> worker_;
-		// 公共
-		std::list<std::function<void (const php::array& options)>>  init_cb;
-		std::list<std::function<void ()>>  stop_cb;
-	public:
-		controller();
-		controller(const controller& c) = delete;
-		void initialize(const std::string& title, const php::array &options);
-		void run();
-		controller *on_init(std::function<void(const php::array &options)> fn);
-		controller* on_stop(std::function<void ()> fn);
-		void stop();
-	};
+    class controller_master;
+    class controller_worker;
+    class controller {
+    public:
+        boost::asio::io_context context_x;
+        boost::asio::io_context context_y;
+        zend_execute_data* default_execute_data;
+        enum class process_type {
+            UNKNOWN = 0,
+            MASTER = 1,
+            WORKER = 2,
+        } type;
+        boost::process::environment env;
+        enum controller_status {
+            STATUS_UNKNOWN     = 0x00,
+            STATUS_INITIALIZED = 0x01,
+            STATUS_SHUTDOWN    = 0x02,
+            STATUS_EXCEPTION   = 0x04,
+            STATUS_RUN         = 0x08,
+        };
+        int status;
+        std::size_t    worker_size;
+        std::size_t    worker_quit; // 多进程退出超时时间
+        std::thread::id mthread_id;
+        // 防止 PHP 提前回收, 使用堆容器
+        std::multimap<std::string, php::callable>* cbmap;
+    private:
+        // 核心进程对象
+        std::unique_ptr<controller_master> master_;
+        // 工作进程对象
+        std::unique_ptr<controller_worker> worker_;
+        // 公共
+        std::list<std::function<void (const php::array& options)>>  init_cb;
+        std::list<std::function<void ()>>  stop_cb;
+    public:
+        controller();
+        controller(const controller& c) = delete;
+        void initialize(const std::string& title, const php::array &options);
+        void run();
+        controller *on_init(std::function<void(const php::array &options)> fn);
+        controller* on_stop(std::function<void ()> fn);
+        void stop();
+    };
 
-	extern std::unique_ptr<controller> gcontroller;
+    extern std::unique_ptr<controller> gcontroller;
 }
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index f2e94af..55a65be 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -248,10 +248,10 @@ namespace flame {
         if (ofpath_[0] == '/') {
             offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
             // 文件打开失败时不会抛出异常，需要额外的状态检查
-			if (!(*offile_))
+            if (!(*offile_))
                 std::cerr << "(ERROR) failed to create/open logger target file: fallback to standard output\n";
             else return;
-		}
+        }
         offile_.reset(&std::clog, boost::null_deleter());
     }
 
diff --git a/src/controller_master.h b/src/controller_master.h
index fe5d6d2..7cf8e94 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -21,19 +21,19 @@ namespace flame {
     };
 
     class controller_master {
-	public:
-		controller_master();
+    public:
+        controller_master();
         void initialize(const php::array& options);
-		void run();
+        void run();
     private:
-		std::vector<controller_master_worker*>   worker_spawn_;
+        std::vector<controller_master_worker*>   worker_spawn_;
         std::vector<controller_master_worker*>   worker_close_;
         int                                      worker_spawn_count_;
         int                                      worker_count_;
-		std::unique_ptr<boost::asio::signal_set> signal_;
+        std::unique_ptr<boost::asio::signal_set> signal_;
         boost::asio::steady_timer tm_inter_;
         boost::asio::steady_timer tm_force_;
-		std::thread                        thread_;
+        std::thread                        thread_;
 
         std::string                        ofpath_;
         std::shared_ptr<std::ostream>      offile_;
diff --git a/src/controller_worker.h b/src/controller_worker.h
index 98b9a0c..e41f918 100644
--- a/src/controller_worker.h
+++ b/src/controller_worker.h
@@ -3,14 +3,14 @@
 
 namespace flame {
     class controller_worker {
-	public:
-		controller_worker();
+    public:
+        controller_worker();
         void initialize(const php::array& options);
-		void run();
-	private:
-		std::vector<std::thread*> thread_;
-		std::unique_ptr<boost::asio::signal_set> signal_;
+        void run();
+    private:
+        std::vector<std::thread*> thread_;
+        std::unique_ptr<boost::asio::signal_set> signal_;
 
-		void await_signal();
-	};
+        void await_signal();
+    };
 }
\ No newline at end of file
diff --git a/src/coroutine.h b/src/coroutine.h
index 336789c..05f1359 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -2,88 +2,82 @@
 #include "vendor.h"
 
 namespace flame {
-	class coroutine : public std::enable_shared_from_this<coroutine>
-	{
-	public:
-		struct php_context_t
-		{
-			zend_vm_stack vm_stack;
-			zval *vm_stack_top;
-			zval *vm_stack_end;
-			zend_class_entry *scope;
-			zend_execute_data *current_execute_data;
 
-			zend_object *         exception;
-			zend_error_handling_t error_handling;
-			zend_class_entry *    exception_class;
-		};
-		//
-		static boost::context::fixedsize_stack stack_allocator;
-		static std::size_t count;
-		// 当前协程
-		static std::shared_ptr<coroutine> current;
-		static void save_context(php_context_t &ctx);
-		static void restore_context(php_context_t& ctx);
-		static php_context_t global_context;
-		static std::shared_ptr<coroutine> start(php::callable fn);
-		coroutine(php::callable fn);
-		~coroutine();
+    class coroutine : public std::enable_shared_from_this<coroutine> {
+    public:
+        struct php_context_t {
+            zend_vm_stack vm_stack;
+            zval *vm_stack_top;
+            zval *vm_stack_end;
+            zend_class_entry *scope;
+            zend_execute_data *current_execute_data;
 
-		void suspend();
-		void resume();
-		php::callable fn_;
-		// boost::context::continuation c1_;
-		// boost::context::continuation c2_;
-		boost::context::fiber c1_;
-		boost::context::fiber c2_;
-		php_context_t php_;
-		// 用于部分异步请求返回长度信息
-		std::size_t len_;
-		
-	};
+            zend_object *         exception;
+            zend_error_handling_t error_handling;
+            zend_class_entry *    exception_class;
+        };
+        //
+        static boost::context::fixedsize_stack stack_allocator;
+        static std::size_t count;
+        // 当前协程
+        static std::shared_ptr<coroutine> current;
+        static void save_context(php_context_t &ctx);
+        static void restore_context(php_context_t& ctx);
+        static php_context_t global_context;
+        static std::shared_ptr<coroutine> start(php::callable fn);
+        coroutine(php::callable fn);
+        ~coroutine();
 
-	struct coroutine_handler
-	{
-	public:
-		coroutine_handler();
-		coroutine_handler(std::shared_ptr<coroutine> co);
-		~coroutine_handler();
-		// !!! 慎用: 目前仅用于空构造后指定协程
-		void reset(std::shared_ptr<coroutine> co);
-		void reset();
-		operator bool() const;
-		void operator()(const boost::system::error_code& e, std::size_t n = 0);
-		coroutine_handler& operator [](boost::system::error_code& e);
-		void resume();
-		void suspend();
-		boost::system::error_code* er_;
-		std::shared_ptr<coroutine> co_;
-		std::shared_ptr<std::atomic<int>> stat_;
+        void suspend();
+        void resume();
+        php::callable fn_;
+        // boost::context::continuation c1_;
+        // boost::context::continuation c2_;
+        boost::context::fiber c1_;
+        boost::context::fiber c2_;
+        php_context_t php_;
+        // 用于部分异步请求返回长度信息
+        std::size_t len_;
+        
+    };
 
-		friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
+    struct coroutine_handler {
+    public:
+        coroutine_handler();
+        coroutine_handler(std::shared_ptr<coroutine> co);
+        ~coroutine_handler();
+        // !!! 慎用: 目前仅用于空构造后指定协程
+        void reset(std::shared_ptr<coroutine> co);
+        void reset();
+        operator bool() const;
+        void operator()(const boost::system::error_code& e, std::size_t n = 0);
+        coroutine_handler& operator [](boost::system::error_code& e);
+        void resume();
+        void suspend();
+        boost::system::error_code* er_;
+        std::shared_ptr<coroutine> co_;
+        std::shared_ptr<std::atomic<int>> stat_;
 
-	  private:
-		
-	};
+        friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
+
+      private:
+        
+    };
 } // namespace flame
 
-namespace boost::asio
-{
+namespace boost::asio {
 
-	template <>
-	class async_result<::flame::coroutine_handler>
-	{
-	public:
-		explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
-			
-		}
-		using type = std::size_t;
-		type get()
-		{
-			ch_.suspend();
-			return ch_.co_->len_;
-		}
-	private:
-		::flame::coroutine_handler &ch_;
-	};
+    template <>
+    class async_result<::flame::coroutine_handler> {
+    public:
+        explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
+        }
+        using type = std::size_t;
+        type get() {
+            ch_.suspend();
+            return ch_.co_->len_;
+        }
+    private:
+        ::flame::coroutine_handler &ch_;
+    };
 } // namespace boost::asio
diff --git a/src/hash/hash.cpp b/src/hash/hash.cpp
index 6d96d2a..55ba653 100644
--- a/src/hash/hash.cpp
+++ b/src/hash/hash.cpp
@@ -2,18 +2,21 @@
 
 
 namespace flame::hash {
+    
     template <typename INTEGER_TYPE>
     static php::string dec2hex(INTEGER_TYPE dec) {
         php::string hex(sizeof(INTEGER_TYPE) * 2);
         sprintf(hex.data(), "%0*lx", sizeof(INTEGER_TYPE) * 2, dec);
         return std::move(hex);
     }
+
     static php::value murmur2(php::parameters& params) {
         php::string data = params[0].to_string();
         std::uint32_t raw = rd_murmur2(data.c_str(), data.size());
         if (params.size() > 1 && params[1].to_boolean()) return raw;
         return dec2hex<std::uint32_t>(raw);
     }
+
     static php::value xxh64(php::parameters& params) {
         php::string data = params[0].to_string();
         unsigned long long seed = 0;
@@ -24,6 +27,7 @@ namespace flame::hash {
         if (params.size() > 2 && params[2].to_boolean()) return raw;
         return dec2hex<std::uint64_t>(raw);
     }
+
     static php::value crc64(php::parameters& params) {
         php::string data = params[0].to_string();
         boost::crc_optimal<64, 0x42F0E1EBA9EA3693, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true, true>  crc64;
@@ -32,8 +36,8 @@ namespace flame::hash {
         if (params.size() > 1 && params[1].to_boolean()) return raw;
         return dec2hex<std::uint64_t>(raw);
     }
-    void declare(php::extension_entry &ext)
-    {
+
+    void declare(php::extension_entry &ext) {
         ext
             .function<murmur2>("flame\\hash\\murmur2", {
                 {"data", php::TYPE::STRING},
diff --git a/src/http/_handler.h b/src/http/_handler.h
index 822a6ea..6011ea5 100644
--- a/src/http/_handler.h
+++ b/src/http/_handler.h
@@ -2,16 +2,14 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 
-namespace flame::http
-{
+namespace flame::http {
     class server_response;
     class server_request;
 
     template <bool isRequest>
     class value_body;
     class server;
-    class _handler : public std::enable_shared_from_this<_handler>
-    {
+    class _handler : public std::enable_shared_from_this<_handler> {
     public:
         _handler(server *svr, boost::asio::ip::tcp::socket &&sock);
         ~_handler();
diff --git a/src/http/client.cpp b/src/http/client.cpp
index ebf6280..c5b40f4 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -7,6 +7,7 @@
 #include "../time/time.h"
 
 namespace flame::http {
+    
     void client::declare(php::extension_entry& ext) {
         php::class_entry<client> class_client("flame\\http\\client");
         class_client
@@ -171,7 +172,7 @@ namespace flame::http {
     php::value client::exec(php::parameters& params) {
         return exec_ex(params[0]);
     }
-	php::value client::get(php::parameters& params) {
+    php::value client::get(php::parameters& params) {
         php::object req(php::class_entry<client_request>::entry());
         req.set("method", "GET");
         req.set("url", params[0]);
diff --git a/src/http/client.h b/src/http/client.h
index 6c999c3..418360b 100644
--- a/src/http/client.h
+++ b/src/http/client.h
@@ -2,6 +2,7 @@
 #include "../vendor.h"
 
 namespace flame::http {
+    
     class client_poll;
     class client: public php::class_base {
     public:
diff --git a/src/http/client_body.cpp b/src/http/client_body.cpp
index 5a2f5fb..9b1cb28 100644
--- a/src/http/client_body.cpp
+++ b/src/http/client_body.cpp
@@ -1,8 +1,8 @@
 #include "client_body.h"
 
 namespace flame::http {
-	void client_body::declare(php::extension_entry& ext) {
-		php::class_entry<client_body> class_client_body("flame\\http\\client_body");
+    void client_body::declare(php::extension_entry& ext) {
+        php::class_entry<client_body> class_client_body("flame\\http\\client_body");
         ext.add(std::move(class_client_body));
-	}
+    }
 }
\ No newline at end of file
diff --git a/src/http/client_body.h b/src/http/client_body.h
index 808e56e..efab79c 100644
--- a/src/http/client_body.h
+++ b/src/http/client_body.h
@@ -2,8 +2,9 @@
 #include "../vendor.h"
 
 namespace flame::http {
-	class client_body: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-	};
+    
+    class client_body: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+    };
 }
\ No newline at end of file
diff --git a/src/http/client_poll.cpp b/src/http/client_poll.cpp
index a2877a2..67e3e5e 100644
--- a/src/http/client_poll.cpp
+++ b/src/http/client_poll.cpp
@@ -3,81 +3,82 @@
 #include "client.h"
 
 namespace flame::http {
-	client_poll* client_poll::create_poll(boost::asio::io_context &io, curl_socket_t fd, client_poll::poll_callback_t cb, void* data) {
-		int       type;
-		socklen_t size;
-		struct sockaddr addr;
-		int x = ::getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &size);
-		assert(x == 0);
-		::getsockname(fd, &addr, &size);
-		client_poll* poll;
-		if (type == SOCK_STREAM) {
-			poll = new client_poll_tcp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::tcp::v6() : boost::asio::ip::tcp::v4(), ::dup(fd));
-		}
-		else if (type == SOCK_DGRAM) {
-			poll = new client_poll_udp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
-		}
-		else {
-			return nullptr;
-		}
-		poll->data = data;
-		poll->fd_ = fd;
-		poll->cb_ = cb;
-		return poll;
-	}
+    
+    client_poll* client_poll::create_poll(boost::asio::io_context &io, curl_socket_t fd, client_poll::poll_callback_t cb, void* data) {
+        int       type;
+        socklen_t size;
+        struct sockaddr addr;
+        int x = ::getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &size);
+        assert(x == 0);
+        ::getsockname(fd, &addr, &size);
+        client_poll* poll;
+        if (type == SOCK_STREAM) {
+            poll = new client_poll_tcp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::tcp::v6() : boost::asio::ip::tcp::v4(), ::dup(fd));
+        }
+        else if (type == SOCK_DGRAM) {
+            poll = new client_poll_udp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
+        }
+        else {
+            return nullptr;
+        }
+        poll->data = data;
+        poll->fd_ = fd;
+        poll->cb_ = cb;
+        return poll;
+    }
 
-	client_poll::client_poll()
-	: action_(CURL_POLL_NONE) {
-	}
+    client_poll::client_poll()
+    : action_(CURL_POLL_NONE) {
+    }
 
-	client_poll::~client_poll() {
-	}
+    client_poll::~client_poll() {
+    }
 
-	void client_poll::on_ready(const boost::system::error_code& error, int action) {
-		if (error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
-		cb_(error, this, fd_, action);
-		if (action_ == CURL_POLL_REMOVE) delete this;
-		else if (action_ == action) async_wait();
-	}
+    void client_poll::on_ready(const boost::system::error_code& error, int action) {
+        if (error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
+        cb_(error, this, fd_, action);
+        if (action_ == CURL_POLL_REMOVE) delete this;
+        else if (action_ == action) async_wait();
+    }
 
-	void client_poll::async_wait(int action) {
-		if (action_ != action) {
-			action_ = action;
-			async_wait();
-		}
-	}
+    void client_poll::async_wait(int action) {
+        if (action_ != action) {
+            action_ = action;
+            async_wait();
+        }
+    }
 
-	client_poll_tcp::client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd)
-	: sock_(io, proto, fd) {
-	}
+    client_poll_tcp::client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd)
+    : sock_(io, proto, fd) {
+    }
 
-	client_poll_tcp::~client_poll_tcp() {
-		// sock_.release();
-	}
+    client_poll_tcp::~client_poll_tcp() {
+        // sock_.release();
+    }
 
-	void client_poll_tcp::async_wait() {
-		sock_.cancel();
-		// 重新监听
-		if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
-			sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
-		if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
-			sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
-	}
-	
-	client_poll_udp::client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd)
-	: client_poll(), sock_(io, proto, fd) {
-	}
+    void client_poll_tcp::async_wait() {
+        sock_.cancel();
+        // 重新监听
+        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
+        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
+    }
+    
+    client_poll_udp::client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd)
+    : client_poll(), sock_(io, proto, fd) {
+    }
 
-	client_poll_udp::~client_poll_udp() {
-	}
+    client_poll_udp::~client_poll_udp() {
+    }
 
-	void client_poll_udp::async_wait() {
-		sock_.cancel();
-		// 重新监听
-		if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
-			sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
-		if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
-			sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
-	}
+    void client_poll_udp::async_wait() {
+        sock_.cancel();
+        // 重新监听
+        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
+        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
+    }
 
 }
\ No newline at end of file
diff --git a/src/http/client_poll.h b/src/http/client_poll.h
index 9454918..39b81e0 100644
--- a/src/http/client_poll.h
+++ b/src/http/client_poll.h
@@ -2,41 +2,42 @@
 #include "../vendor.h"
 
 namespace flame::http {
-	class client_poll {
-	public:
-		client_poll();
-		typedef void (*poll_callback_t)(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action);
-		virtual ~client_poll();
-		static client_poll* create_poll(boost::asio::io_context& io, curl_socket_t fd, poll_callback_t cb, void* data = nullptr);
-		virtual void async_wait() = 0;
-		void async_wait(int action);
-		void* data;
-	protected:
-		curl_socket_t   fd_;
-		int         action_;
-		poll_callback_t cb_;
-		void on_ready(const boost::system::error_code& error, int action);
-	};
+    
+    class client_poll {
+    public:
+        client_poll();
+        typedef void (*poll_callback_t)(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action);
+        virtual ~client_poll();
+        static client_poll* create_poll(boost::asio::io_context& io, curl_socket_t fd, poll_callback_t cb, void* data = nullptr);
+        virtual void async_wait() = 0;
+        void async_wait(int action);
+        void* data;
+    protected:
+        curl_socket_t   fd_;
+        int         action_;
+        poll_callback_t cb_;
+        void on_ready(const boost::system::error_code& error, int action);
+    };
 
-	class client_poll_tcp:public client_poll {
-	public:
-		// client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
-		client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
-		// client_poll_tcp(curl_socket_t fd);
-		~client_poll_tcp();
-		void async_wait() override;
-	private:
-		boost::asio::ip::tcp::socket sock_;
-		// boost::asio::ip::tcp::socket& sock_;
-		// boost::asio::posix::stream_descriptor sock_;
-	};
+    class client_poll_tcp:public client_poll {
+    public:
+        // client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
+        client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
+        // client_poll_tcp(curl_socket_t fd);
+        ~client_poll_tcp();
+        void async_wait() override;
+    private:
+        boost::asio::ip::tcp::socket sock_;
+        // boost::asio::ip::tcp::socket& sock_;
+        // boost::asio::posix::stream_descriptor sock_;
+    };
 
-	class client_poll_udp:public client_poll {
-	public:
-		client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd);
-		~client_poll_udp();
-		void async_wait() override;
-	private:
-		boost::asio::ip::udp::socket sock_;
-	};
+    class client_poll_udp:public client_poll {
+    public:
+        client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd);
+        ~client_poll_udp();
+        void async_wait() override;
+    private:
+        boost::asio::ip::udp::socket sock_;
+    };
 }
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index b13148d..817ceb0 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -4,180 +4,180 @@
 #include "client_body.h"
 #include "http.h"
 
-namespace flame {
-namespace http {
-	void client_request::declare(php::extension_entry& ext) {
-		php::class_entry<client_request> class_client_request(
-			"flame\\http\\client_request");
-		class_client_request
-			.constant({"HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0})
-			.constant({"HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1})
-			.constant({"HTTP_VERSION_2", CURL_HTTP_VERSION_2})
-			.constant({"HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0})
-			.constant({"HTTP_VERSION_2_TLS", CURL_HTTP_VERSION_2TLS})
-			.constant({"HTTP_VERSION_2_PRI", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE})
-			.constant({"SSL_VERIFY_NONE",   0})
-			.constant({"SSL_VERIFY_PEER",   1}) // CURLOPT_SSL_VERIFYPEER
-			.constant({"SSL_VERIFY_HOST",   2}) // CURLOPT_SSL_VERIFYHOST
-			.constant({"SSL_VERIFY_STATUS", 4}) // CURLOPT_SSL_VERIFYSTATUS
-			.constant({"SSL_VERIFY_ALL",    7})
-			.property({"timeout", 3000})
-			.property({"method", "GET"})
-			.property({"url", nullptr})
-			.property({"header", nullptr})
-			.property({"cookie", nullptr})
-			.property({"body", ""})
-			.method<&client_request::__construct>("__construct", {
-				{"url", php::TYPE::STRING},
-				{"body", php::TYPE::UNDEFINED, false, true},
-				{"timeout", php::TYPE::INTEGER, false, true},
-			})
-			.method<&client_request::http_version>("http_version", {
-				{"version", php::TYPE::INTEGER},
-			})
-			.method<&client_request::ssl_pem>("ssl_pem", {
-				{"cert", php::TYPE::STRING},
-				{"pkey", php::TYPE::STRING, false, true},
-			})
-			.method<&client_request::ssl_verify>("ssl_verify", {
-				{"verify", php::TYPE::INTEGER},
-			})
-			.method<&client_request::__destruct>("__destruct");
+namespace flame::http {
 
-		ext.add(std::move(class_client_request));
-	}
+    void client_request::declare(php::extension_entry& ext) {
+        php::class_entry<client_request> class_client_request(
+            "flame\\http\\client_request");
+        class_client_request
+            .constant({"HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0})
+            .constant({"HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1})
+            .constant({"HTTP_VERSION_2", CURL_HTTP_VERSION_2})
+            .constant({"HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0})
+            .constant({"HTTP_VERSION_2_TLS", CURL_HTTP_VERSION_2TLS})
+            .constant({"HTTP_VERSION_2_PRI", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE})
+            .constant({"SSL_VERIFY_NONE",   0})
+            .constant({"SSL_VERIFY_PEER",   1}) // CURLOPT_SSL_VERIFYPEER
+            .constant({"SSL_VERIFY_HOST",   2}) // CURLOPT_SSL_VERIFYHOST
+            .constant({"SSL_VERIFY_STATUS", 4}) // CURLOPT_SSL_VERIFYSTATUS
+            .constant({"SSL_VERIFY_ALL",    7})
+            .property({"timeout", 3000})
+            .property({"method", "GET"})
+            .property({"url", nullptr})
+            .property({"header", nullptr})
+            .property({"cookie", nullptr})
+            .property({"body", ""})
+            .method<&client_request::__construct>("__construct", {
+                {"url", php::TYPE::STRING},
+                {"body", php::TYPE::UNDEFINED, false, true},
+                {"timeout", php::TYPE::INTEGER, false, true},
+            })
+            .method<&client_request::http_version>("http_version", {
+                {"version", php::TYPE::INTEGER},
+            })
+            .method<&client_request::ssl_pem>("ssl_pem", {
+                {"cert", php::TYPE::STRING},
+                {"pkey", php::TYPE::STRING, false, true},
+            })
+            .method<&client_request::ssl_verify>("ssl_verify", {
+                {"verify", php::TYPE::INTEGER},
+            })
+            .method<&client_request::__destruct>("__destruct");
 
-	php::value client_request::__construct(php::parameters& params) {
-		if (params.length() > 2) set("timeout", params[2]);
-		else set("timeout", 3000);
+        ext.add(std::move(class_client_request));
+    }
 
-		if (params.length() > 1 && !params[1].empty()) {
-			set("body", params[1]);
-			set("method", "POST");
-		}
-		else set("method", "GET");
-		set("url", params[0]);
-		set("header", php::array(0));
-		set("cookie", php::array(0));
+    php::value client_request::__construct(php::parameters& params) {
+        if (params.length() > 2) set("timeout", params[2]);
+        else set("timeout", 3000);
 
-		c_easy_ = curl_easy_init();
-		return nullptr;
-	}
+        if (params.length() > 1 && !params[1].empty()) {
+            set("body", params[1]);
+            set("method", "POST");
+        }
+        else set("method", "GET");
+        set("url", params[0]);
+        set("header", php::array(0));
+        set("cookie", php::array(0));
 
-	php::value client_request::__destruct(php::parameters &params) {
-		if (c_head_) curl_slist_free_all(c_head_);
-		if (c_easy_) curl_easy_cleanup(c_easy_);
-		return nullptr;
-	}
+        c_easy_ = curl_easy_init();
+        return nullptr;
+    }
 
-	php::value client_request::http_version(php::parameters& params) {
-		long v = static_cast<int>(params[0]);
-		if (v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST)
-			throw php::exception(zend_ce_error_exception
-				, "Failed to set HTTP version: value out of range"
-				, -1);
-		
-		curl_easy_setopt(c_easy_, CURLOPT_HTTP_VERSION, v);
-		return nullptr;
-	}
+    php::value client_request::__destruct(php::parameters &params) {
+        if (c_head_) curl_slist_free_all(c_head_);
+        if (c_easy_) curl_easy_cleanup(c_easy_);
+        return nullptr;
+    }
 
-	php::value client_request::ssl_pem(php::parameters& params) {
-		curl_easy_setopt(c_easy_, CURLOPT_SSLCERTTYPE, "PEM");
-		php::string cert = params[0];
-		curl_easy_setopt(c_easy_, CURLOPT_SSLCERT, cert.c_str());
-		if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
-			php::string pkey = params[1];
-			curl_easy_setopt(c_easy_, CURLOPT_SSLKEY, pkey.c_str());
-		}
-		if (params.size() > 2 && params[2].typeof(php::TYPE::STRING)) {
-			php::string pass = params[2];
-			curl_easy_setopt(c_easy_, CURLOPT_KEYPASSWD, pass.c_str());
-		}
-		return nullptr;
-	}
+    php::value client_request::http_version(php::parameters& params) {
+        long v = static_cast<int>(params[0]);
+        if (v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to set HTTP version: value out of range"
+                , -1);
+        
+        curl_easy_setopt(c_easy_, CURLOPT_HTTP_VERSION, v);
+        return nullptr;
+    }
 
-	php::value client_request::ssl_verify(php::parameters& params) {
-		long v = static_cast<int>(params[0]), YES = 1, NO = 0;
-		if (v | CURLOPT_SSL_VERIFYPEER) 
-			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, YES);
-		else
-			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, NO);
-		if (v | CURLOPT_SSL_VERIFYHOST)
-			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, YES);
-		else
-			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, NO);
-		if (v | CURLOPT_SSL_VERIFYSTATUS) 
-			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, YES);
-		else 
-			curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, NO);
-		return nullptr;
-	}
+    php::value client_request::ssl_pem(php::parameters& params) {
+        curl_easy_setopt(c_easy_, CURLOPT_SSLCERTTYPE, "PEM");
+        php::string cert = params[0];
+        curl_easy_setopt(c_easy_, CURLOPT_SSLCERT, cert.c_str());
+        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
+            php::string pkey = params[1];
+            curl_easy_setopt(c_easy_, CURLOPT_SSLKEY, pkey.c_str());
+        }
+        if (params.size() > 2 && params[2].typeof(php::TYPE::STRING)) {
+            php::string pass = params[2];
+            curl_easy_setopt(c_easy_, CURLOPT_KEYPASSWD, pass.c_str());
+        }
+        return nullptr;
+    }
 
-	void client_request::build_ex() {
-		long timeout = static_cast<long>(get("timeout"));
-		curl_easy_setopt(c_easy_, CURLOPT_TIMEOUT_MS, timeout);
-		// 目标请求地址
-		// ---------------------------------------------------------------------------
+    php::value client_request::ssl_verify(php::parameters& params) {
+        long v = static_cast<int>(params[0]), YES = 1, NO = 0;
+        if (v | CURLOPT_SSL_VERIFYPEER) 
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, YES);
+        else
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, NO);
+        if (v | CURLOPT_SSL_VERIFYHOST)
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, YES);
+        else
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, NO);
+        if (v | CURLOPT_SSL_VERIFYSTATUS) 
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, YES);
+        else 
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, NO);
+        return nullptr;
+    }
+
+    void client_request::build_ex() {
+        long timeout = static_cast<long>(get("timeout"));
+        curl_easy_setopt(c_easy_, CURLOPT_TIMEOUT_MS, timeout);
+        // 目标请求地址
+        // ---------------------------------------------------------------------------
         php::string u = get("url");
-		if (!u.typeof(php::TYPE::STRING)) 
-			throw php::exception(zend_ce_type_error
-				, "Failed to build client request: 'url' typeof 'string' required"
-				, -1);
-		
-		curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
-		php::string m = get("method");
-		curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
-		// 头
-		// ---------------------------------------------------------------------------
-		if (c_head_ != nullptr) curl_slist_free_all(c_head_);
-		std::string ctype;
-		long keepalive = 1;
-		php::array header = get("header");
-		for (auto i=header.begin(); i!=header.end(); ++i) {
-			std::string key = i->first;
-			std::string val = i->second;
-			if (strncasecmp(key.c_str(), "content-type", 12) == 0) ctype = val;
-			else if (strncasecmp(key.c_str(), "connection", 10) == 0
-				&& strncasecmp(val.c_str(), "close", 5) == 0) keepalive = 0;
-			c_head_ = curl_slist_append(c_head_, (boost::format("%s: %s") % key % val).str().c_str());
-		}
-		c_head_ = curl_slist_append(c_head_, "Expect: ");
-		curl_easy_setopt(c_easy_, CURLOPT_HTTPHEADER, c_head_);
-		curl_easy_setopt(c_easy_, CURLOPT_TCP_KEEPALIVE, keepalive);
-		// COOKIE
-		// ---------------------------------------------------------------------------
-		php::array cookie = get("cookie");
-		php::buffer cookies;
-		for (auto i=cookie.begin(); i!=cookie.end(); ++i) {
-			php::string key = i->first;
-			php::string val = i->second.to_string();
-			val = php::url_encode(val.c_str(), val.size());
-			cookies.append(key);
-			cookies.push_back('=');
-			cookies.append(val);
-			cookies.push_back(';');
-			cookies.push_back(' ');
-		}
-		php::string cookie_str = std::move(cookies);
-		if (cookie_str.size() > 0) curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
-		// 体
-		// ---------------------------------------------------------------------------
-		php::string body = get("body");
-		if (ctype.empty()) ctype.assign("application/x-www-form-urlencoded", 33);
-		if (body.empty()) {
-			// TODO 空 BODY 的处理流程？
-		}
-		else if (body.instanceof(php::class_entry<client_body>::entry())) {
-			// TODO multipart support
-		}
-		else {
-			body = ctype_encode(ctype, body);
-			// 注意: CURLOPT_POSTFIELDS 仅"引用" body 数据
-			set("body", body);
-			// curl_easy_setopt(c_easy_, CURLOPT_POST, 1);
-			curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDSIZE, body.size());
-			curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDS, body.c_str());
-		}
-	}
-}
+        if (!u.typeof(php::TYPE::STRING)) 
+            throw php::exception(zend_ce_type_error
+                , "Failed to build client request: 'url' typeof 'string' required"
+                , -1);
+        
+        curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
+        php::string m = get("method");
+        curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
+        // 头
+        // ---------------------------------------------------------------------------
+        if (c_head_ != nullptr) curl_slist_free_all(c_head_);
+        std::string ctype;
+        long keepalive = 1;
+        php::array header = get("header");
+        for (auto i=header.begin(); i!=header.end(); ++i) {
+            std::string key = i->first;
+            std::string val = i->second;
+            if (strncasecmp(key.c_str(), "content-type", 12) == 0) ctype = val;
+            else if (strncasecmp(key.c_str(), "connection", 10) == 0
+                && strncasecmp(val.c_str(), "close", 5) == 0) keepalive = 0;
+            c_head_ = curl_slist_append(c_head_, (boost::format("%s: %s") % key % val).str().c_str());
+        }
+        c_head_ = curl_slist_append(c_head_, "Expect: ");
+        curl_easy_setopt(c_easy_, CURLOPT_HTTPHEADER, c_head_);
+        curl_easy_setopt(c_easy_, CURLOPT_TCP_KEEPALIVE, keepalive);
+        // COOKIE
+        // ---------------------------------------------------------------------------
+        php::array cookie = get("cookie");
+        php::buffer cookies;
+        for (auto i=cookie.begin(); i!=cookie.end(); ++i) {
+            php::string key = i->first;
+            php::string val = i->second.to_string();
+            val = php::url_encode(val.c_str(), val.size());
+            cookies.append(key);
+            cookies.push_back('=');
+            cookies.append(val);
+            cookies.push_back(';');
+            cookies.push_back(' ');
+        }
+        php::string cookie_str = std::move(cookies);
+        if (cookie_str.size() > 0) curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
+        // 体
+        // ---------------------------------------------------------------------------
+        php::string body = get("body");
+        if (ctype.empty()) ctype.assign("application/x-www-form-urlencoded", 33);
+        if (body.empty()) {
+            // TODO 空 BODY 的处理流程？
+        }
+        else if (body.instanceof(php::class_entry<client_body>::entry())) {
+            // TODO multipart support
+        }
+        else {
+            body = ctype_encode(ctype, body);
+            // 注意: CURLOPT_POSTFIELDS 仅"引用" body 数据
+            set("body", body);
+            // curl_easy_setopt(c_easy_, CURLOPT_POST, 1);
+            curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDSIZE, body.size());
+            curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDS, body.c_str());
+        }
+    }
 }
+
diff --git a/src/http/client_request.h b/src/http/client_request.h
index 21431a1..da314af 100644
--- a/src/http/client_request.h
+++ b/src/http/client_request.h
@@ -4,20 +4,21 @@
 #include "../url.h"
 
 namespace flame::http {
-	class client_request: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value http_version(php::parameters& params);
-		php::value ssl_pem(php::parameters& params);
-		php::value ssl_verify(php::parameters& params);
-	private:
-		void build_ex();
-		CURL*       c_easy_ = nullptr;
-		curl_slist* c_head_ = nullptr;
-		
-		friend class client;
-		friend class _connection_pool;
+    
+    class client_request: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value __destruct(php::parameters& params);
+        php::value http_version(php::parameters& params);
+        php::value ssl_pem(php::parameters& params);
+        php::value ssl_verify(php::parameters& params);
+    private:
+        void build_ex();
+        CURL*       c_easy_ = nullptr;
+        curl_slist* c_head_ = nullptr;
+        
+        friend class client;
+        friend class _connection_pool;
     };
 }
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index 1e5e471..e7cac92 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -2,77 +2,75 @@
 #include "value_body.h"
 #include "client_response.h"
 
-namespace flame {
-namespace http {
-	void client_response::declare(php::extension_entry& ext) {
-		php::class_entry<client_response>  class_client_response("flame\\http\\client_response");
-		class_client_response
-			.property({"version", "HTTP/1.0"})
-			.property({"status", 3000})
-			.property({"header", nullptr})
-			.property({"body", nullptr})
-			.property({"raw_body", ""})
-			.method<&client_response::__construct>("__construct", {}, php::PRIVATE)
-			.method<&client_response::to_string>("__toString");
-		ext.add(std::move(class_client_response));
-	}
-	// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
-	php::value client_response::__construct(php::parameters& params) {
-		return nullptr;	
-	}
-	php::value client_response::to_string(php::parameters& params) {
-		return get("raw_body");
-	}
-	void client_response::build_ex() {
-		if (c_final_ != 0) throw php::exception(zend_ce_exception
-			, (boost::format("Failed to execute HTTP request: %s") % c_error_).str()
-			, c_final_);
-		// 响应码
-		long rv = 0;
-		curl_easy_getinfo(c_easy_, CURLINFO_HTTP_VERSION, &rv);
+namespace flame::http {
+    void client_response::declare(php::extension_entry& ext) {
+        php::class_entry<client_response>  class_client_response("flame\\http\\client_response");
+        class_client_response
+            .property({"version", "HTTP/1.0"})
+            .property({"status", 3000})
+            .property({"header", nullptr})
+            .property({"body", nullptr})
+            .property({"raw_body", ""})
+            .method<&client_response::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client_response::to_string>("__toString");
+        ext.add(std::move(class_client_response));
+    }
+    // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+    php::value client_response::__construct(php::parameters& params) {
+        return nullptr; 
+    }
+    php::value client_response::to_string(php::parameters& params) {
+        return get("raw_body");
+    }
+    void client_response::build_ex() {
+        if (c_final_ != 0) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to execute HTTP request: %s") % c_error_).str()
+            , c_final_);
+        // 响应码
+        long rv = 0;
+        curl_easy_getinfo(c_easy_, CURLINFO_HTTP_VERSION, &rv);
         set("version", rv == 3 ? "HTTP/2" : rv == 2 ? "HTTP/1.1" : "HTTP/1.0");
-		curl_easy_getinfo(c_easy_, CURLINFO_RESPONSE_CODE, &rv);
-		set("status", rv);
-		// 响应头
-		set("header", c_head_);
-		// 响应体
-		php::string rbody = std::move(c_body_);
-		set("raw_body", rbody);
-		if (c_head_.exists("content-type")) {
-			php::string ctype = c_head_.get("content-type");
-			std::cout << "content-type: " << ctype << "\n";
-			std::string_view sv {ctype.c_str(), ctype.size()};
-			set("body", ctype_decode(sv, rbody));
-		}
-		else {
-			set("body", rbody);
-		}
-	}
+        curl_easy_getinfo(c_easy_, CURLINFO_RESPONSE_CODE, &rv);
+        set("status", rv);
+        // 响应头
+        set("header", c_head_);
+        // 响应体
+        php::string rbody = std::move(c_body_);
+        set("raw_body", rbody);
+        if (c_head_.exists("content-type")) {
+            php::string ctype = c_head_.get("content-type");
+            std::cout << "content-type: " << ctype << "\n";
+            std::string_view sv {ctype.c_str(), ctype.size()};
+            set("body", ctype_decode(sv, rbody));
+        }
+        else {
+            set("body", rbody);
+        }
+    }
 
-	size_t client_response::c_write_cb(char *ptr, size_t size, size_t nmemb, void *data) {
-		client_response* self = static_cast<client_response*>(data);
-		assert(size == 1);
-		self->c_body_.append(ptr, nmemb);
-		return nmemb;
-	}
+    size_t client_response::c_write_cb(char *ptr, size_t size, size_t nmemb, void *data) {
+        client_response* self = static_cast<client_response*>(data);
+        assert(size == 1);
+        self->c_body_.append(ptr, nmemb);
+        return nmemb;
+    }
 
-	size_t client_response::c_header_cb(char *buffer, size_t size, size_t nitems, void *data) {
-		client_response* self = static_cast<client_response*>(data);
-		size = size * nitems;		
+    size_t client_response::c_header_cb(char *buffer, size_t size, size_t nitems, void *data) {
+        client_response* self = static_cast<client_response*>(data);
+        size = size * nitems;       
 
-		std::string_view sv{ buffer, size };
-		auto psep = sv.find_first_of(':', 1);
-		if (psep != sv.npos) {
-			auto pval = sv.find_first_not_of(' ', psep + 1);
-			php::string key = php::lowercase(buffer, psep);
-			if (pval == sv.npos) {
-				self->c_head_.set( key, php::string(0) );
-			}
-			else {
-				self->c_head_.set( key, php::string( buffer + pval, size - pval - 2)); // trailing \r\n
-			}
-		}
-		return size;
-	}
-}
+        std::string_view sv{ buffer, size };
+        auto psep = sv.find_first_of(':', 1);
+        if (psep != sv.npos) {
+            auto pval = sv.find_first_not_of(' ', psep + 1);
+            php::string key = php::lowercase(buffer, psep);
+            if (pval == sv.npos) {
+                self->c_head_.set( key, php::string(0) );
+            }
+            else {
+                self->c_head_.set( key, php::string( buffer + pval, size - pval - 2)); // trailing \r\n
+            }
+        }
+        return size;
+    }
 }
diff --git a/src/http/client_response.h b/src/http/client_response.h
index fa45d87..6e962b6 100644
--- a/src/http/client_response.h
+++ b/src/http/client_response.h
@@ -3,23 +3,24 @@
 #include "../coroutine.h"
 
 namespace flame::http {
-	class client_response: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		php::value to_string(php::parameters& params);
-		// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
-		php::value __construct(php::parameters& params);
-	private:
-		CURL*       c_easy_;
-		php::array  c_head_;
-		php::buffer c_body_;
-		CURLcode c_final_ = CURLE_OK;
-		char c_error_[CURL_ERROR_SIZE];
-		coroutine_handler c_coro_;
-		void build_ex();
-		// 接收 curl 回调(响应数据)
-		static size_t c_write_cb(char *ptr, size_t size, size_t nmemb, void *data);
-		static size_t c_header_cb(char *buffer, size_t size, size_t nitems, void *data);
-		friend class client;
-	};
+    
+    class client_response: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value to_string(php::parameters& params);
+        // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+        php::value __construct(php::parameters& params);
+    private:
+        CURL*       c_easy_;
+        php::array  c_head_;
+        php::buffer c_body_;
+        CURLcode c_final_ = CURLE_OK;
+        char c_error_[CURL_ERROR_SIZE];
+        coroutine_handler c_coro_;
+        void build_ex();
+        // 接收 curl 回调(响应数据)
+        static size_t c_write_cb(char *ptr, size_t size, size_t nmemb, void *data);
+        static size_t c_header_cb(char *buffer, size_t size, size_t nitems, void *data);
+        friend class client;
+    };
 }
diff --git a/src/http/http.cpp b/src/http/http.cpp
index 5248bfa..6916e55 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -12,15 +12,16 @@
 #include "_handler.h"
 
 namespace flame::http {
+    
     client* client_;
-	std::int64_t body_max_size = 1024 * 1024 * 1024;
+    std::int64_t body_max_size = 1024 * 1024 * 1024;
 
-	void declare(php::extension_entry& ext) {
-		client_ = nullptr;
-		ext.on_module_startup([] (php::extension_entry& ext) -> bool {
-			// 在框架初始化后创建全局HTTP客户端
+    void declare(php::extension_entry& ext) {
+        client_ = nullptr;
+        ext.on_module_startup([] (php::extension_entry& ext) -> bool {
+            // 在框架初始化后创建全局HTTP客户端
             gcontroller->on_init([] (const php::array& opts) {
-				body_max_size = std::max(php::ini("post_max_size").calc(), body_max_size);
+                body_max_size = std::max(php::ini("post_max_size").calc(), body_max_size);
                 client_ = new client();
                 php::parameters p(0, nullptr);
                 client_->__construct(p);
@@ -28,141 +29,141 @@ namespace flame::http {
                 delete client_;
                 client_ = nullptr;
             });
-			return true;
-		});
-		ext
-			.function<get>("flame\\http\\get")
-			.function<post>("flame\\http\\post")
-			.function<put>("flame\\http\\put")
-			.function<delete_>("flame\\http\\delete")
-			.function<exec>("flame\\http\\exec");
+            return true;
+        });
+        ext
+            .function<get>("flame\\http\\get")
+            .function<post>("flame\\http\\post")
+            .function<put>("flame\\http\\put")
+            .function<delete_>("flame\\http\\delete")
+            .function<exec>("flame\\http\\exec");
 
-		client_request::declare(ext);
-		client_response::declare(ext);
-		client_body::declare(ext);
-		client::declare(ext);
-		server::declare(ext);
-		server_request::declare(ext);
-		server_response::declare(ext);
-	}
-	static void init_guard() {
-		if (!client_)
-			throw php::exception(zend_ce_error_exception
-				, "Failed to execute HTTP request: exception or missing 'flame\\init()' ?"
-				, -1);
-	}
-	php::value get(php::parameters& params) {
-		init_guard();
-		return client_->get(params);
-	}
-	php::value post(php::parameters& params) {
-		init_guard();
-		return client_->post(params);
-	}
-	php::value put(php::parameters& params) {
-		init_guard();
-		return client_->put(params);
-	}
-	php::value delete_(php::parameters& params) {
-		init_guard();
-		return client_->delete_(params);
-	}
-	php::value exec(php::parameters& params) {
-		init_guard();
-		return client_->exec(params);
-	}
-	php::string ctype_encode(std::string_view ctype, const php::value& v) {
-		if (v.typeof(php::TYPE::STRING)) return v;
+        client_request::declare(ext);
+        client_response::declare(ext);
+        client_body::declare(ext);
+        client::declare(ext);
+        server::declare(ext);
+        server_request::declare(ext);
+        server_response::declare(ext);
+    }
+    static void init_guard() {
+        if (!client_)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to execute HTTP request: exception or missing 'flame\\init()' ?"
+                , -1);
+    }
+    php::value get(php::parameters& params) {
+        init_guard();
+        return client_->get(params);
+    }
+    php::value post(php::parameters& params) {
+        init_guard();
+        return client_->post(params);
+    }
+    php::value put(php::parameters& params) {
+        init_guard();
+        return client_->put(params);
+    }
+    php::value delete_(php::parameters& params) {
+        init_guard();
+        return client_->delete_(params);
+    }
+    php::value exec(php::parameters& params) {
+        init_guard();
+        return client_->exec(params);
+    }
+    php::string ctype_encode(std::string_view ctype, const php::value& v) {
+        if (v.typeof(php::TYPE::STRING)) return v;
 
-		php::value r = v;
-		if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
-			if (r.typeof(php::TYPE::ARRAY)) {
-				r = php::callable("http_build_query")({v});
-			}
-			else {
-				r.to_string();
-			}
-		}
-		else if (ctype.compare(0, 16, "application/json") == 0) {
-			r = php::json_encode(r);
-		}
-		else {
-			r.to_string();
-		}
-		return r;
-	}
-	php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* files) {
-		if (ctype.compare(0, 16, "application/json") == 0) {
-			return php::json_decode(v);
-		}
-		else if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
-			php::array data(4);
-			php::callable("parse_str")({v, data.make_ref()});
-			return data;
-		}
-		else if (ctype.compare(0, 19, "multipart/form-data") == 0) {
-			std::string boundary;
-			std::string field;
-			php::array meta(4);
-			parser::separator_parser<std::string, std::string> p1('\0','\0','=','"','"',';', [&boundary, &field, &meta] (std::pair<std::string, std::string> entry) {
-				if (entry.first == "boundary") {
-					boundary = std::move(entry.second);
-				}
-				else if (entry.first == "content-disposition") {
-					// 此项头信息没有用途
-				}
-				else if (entry.first == "name") {
-					// 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
-					// 表单字段名
-					field = std::move(entry.second);
-				}
-				else {
-					meta.set(entry.first, entry.second);
-				}
-			});
-			std::size_t begin = ctype.find_first_of(';', 19) + 1;
-			p1.parse(ctype.data() + begin, ctype.size() - begin);
-			p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
+        php::value r = v;
+        if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+            if (r.typeof(php::TYPE::ARRAY)) {
+                r = php::callable("http_build_query")({v});
+            }
+            else {
+                r.to_string();
+            }
+        }
+        else if (ctype.compare(0, 16, "application/json") == 0) {
+            r = php::json_encode(r);
+        }
+        else {
+            r.to_string();
+        }
+        return r;
+    }
+    php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* files) {
+        if (ctype.compare(0, 16, "application/json") == 0) {
+            return php::json_decode(v);
+        }
+        else if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+            php::array data(4);
+            php::callable("parse_str")({v, data.make_ref()});
+            return data;
+        }
+        else if (ctype.compare(0, 19, "multipart/form-data") == 0) {
+            std::string boundary;
+            std::string field;
+            php::array meta(4);
+            parser::separator_parser<std::string, std::string> p1('\0','\0','=','"','"',';', [&boundary, &field, &meta] (std::pair<std::string, std::string> entry) {
+                if (entry.first == "boundary") {
+                    boundary = std::move(entry.second);
+                }
+                else if (entry.first == "content-disposition") {
+                    // 此项头信息没有用途
+                }
+                else if (entry.first == "name") {
+                    // 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
+                    // 表单字段名
+                    field = std::move(entry.second);
+                }
+                else {
+                    meta.set(entry.first, entry.second);
+                }
+            });
+            std::size_t begin = ctype.find_first_of(';', 19) + 1;
+            p1.parse(ctype.data() + begin, ctype.size() - begin);
+            p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
 
-			php::array data(8);
-			parser::multipart_parser<std::string, php::buffer> p2(boundary, [&p1, &field, &meta, &data, &files] (std::pair<std::string, php::buffer> entry) {
-				if (entry.first.size() == 0) { // Part Data
-					if (meta.exists("filename") > 0) {
-						if (files) { // 接收对应文件
-							// 上传文件 meta 
-							meta["size"] = entry.second.size();
-							meta["data"] = std::move(entry.second);
-							files->set(field, meta);
-						}
-					}
-					else {
-						data.set(field, std::move(entry.second));
-					}
-					meta = php::array(4);
-				}
-				else { // Part Header
-					php::lowercase_inplace(entry.first.data(), entry.first.size());
-					std::size_t end = std::string_view(entry.second.data(), entry.second.size()).find_first_of(';');
-					if (end == std::string::npos) {
-						meta[entry.first] = std::move(entry.second);
-					}
-					else if (entry.first == "content-disposition") {
-						// 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
-						std::size_t begin = end + 1;
-						if (entry.second.size() > begin + 2) {
-							p1.parse(entry.second.data() + begin, entry.second.size() - begin);
-							p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
-						}
-					}
-					else {
-						meta[entry.first] = php::string(entry.second.data(), end);
-					}
-				}
-			});
-			p2.parse(v.data(), v.size());
+            php::array data(8);
+            parser::multipart_parser<std::string, php::buffer> p2(boundary, [&p1, &field, &meta, &data, &files] (std::pair<std::string, php::buffer> entry) {
+                if (entry.first.size() == 0) { // Part Data
+                    if (meta.exists("filename") > 0) {
+                        if (files) { // 接收对应文件
+                            // 上传文件 meta 
+                            meta["size"] = entry.second.size();
+                            meta["data"] = std::move(entry.second);
+                            files->set(field, meta);
+                        }
+                    }
+                    else {
+                        data.set(field, std::move(entry.second));
+                    }
+                    meta = php::array(4);
+                }
+                else { // Part Header
+                    php::lowercase_inplace(entry.first.data(), entry.first.size());
+                    std::size_t end = std::string_view(entry.second.data(), entry.second.size()).find_first_of(';');
+                    if (end == std::string::npos) {
+                        meta[entry.first] = std::move(entry.second);
+                    }
+                    else if (entry.first == "content-disposition") {
+                        // 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
+                        std::size_t begin = end + 1;
+                        if (entry.second.size() > begin + 2) {
+                            p1.parse(entry.second.data() + begin, entry.second.size() - begin);
+                            p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
+                        }
+                    }
+                    else {
+                        meta[entry.first] = php::string(entry.second.data(), end);
+                    }
+                }
+            });
+            p2.parse(v.data(), v.size());
 
-			return data;
-		}
-		else return v;
-	}
+            return data;
+        }
+        else return v;
+    }
 }
diff --git a/src/http/http.h b/src/http/http.h
index 704c9a1..eaaf58b 100644
--- a/src/http/http.h
+++ b/src/http/http.h
@@ -2,18 +2,19 @@
 #include "../vendor.h"
 
 namespace flame::http {
+    
     class _connection_pool;
-	class client;
-	extern client* client_;
-	extern std::int64_t body_max_size;
+    class client;
+    extern client* client_;
+    extern std::int64_t body_max_size;
 
-	void declare(php::extension_entry& ext);
-	php::value get(php::parameters& params);
-	php::value post(php::parameters& params);
-	php::value put(php::parameters& params);
-	php::value delete_(php::parameters& params);
-	php::value exec(php::parameters& params);
+    void declare(php::extension_entry& ext);
+    php::value get(php::parameters& params);
+    php::value post(php::parameters& params);
+    php::value put(php::parameters& params);
+    php::value delete_(php::parameters& params);
+    php::value exec(php::parameters& params);
 
-	php::string ctype_encode(std::string_view ctype, const php::value& v);
-	php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* file = nullptr);
+    php::string ctype_encode(std::string_view ctype, const php::value& v);
+    php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* file = nullptr);
 }
diff --git a/src/http/server.cpp b/src/http/server.cpp
index 01549d1..f8ffd38 100644
--- a/src/http/server.cpp
+++ b/src/http/server.cpp
@@ -4,10 +4,9 @@
 #include "server.h"
 #include "_handler.h"
 
-namespace flame::http
-{
-    void server::declare(php::extension_entry &ext)
-    {
+namespace flame::http {
+    
+    void server::declare(php::extension_entry &ext)  {
         php::class_entry<server> class_server("flame\\http\\server");
         class_server
             .property({"address", "127.0.0.1:7678"})
diff --git a/src/http/server.h b/src/http/server.h
index a010818..14a7dce 100644
--- a/src/http/server.h
+++ b/src/http/server.h
@@ -1,11 +1,9 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::http
-{
+namespace flame::http {
     class acceptor;
-    class server : public php::class_base
-    {
+    class server : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
 
@@ -33,4 +31,4 @@ namespace flame::http
 
         friend class _handler;
     };
-} // namespace flame::http
\ No newline at end of file
+} // namespace flame::http
diff --git a/src/http/server_request.cpp b/src/http/server_request.cpp
index f7435b7..792892c 100644
--- a/src/http/server_request.cpp
+++ b/src/http/server_request.cpp
@@ -3,92 +3,88 @@
 #include "value_body.h"
 #include "server_request.h"
 
-namespace flame {
-namespace http {
-	void server_request::declare(php::extension_entry& ext) {
-		php::class_entry<server_request> class_server_request(
-			"flame\\http\\server_request");
-		class_server_request
-			.property({"method", "GET"})
-			.property({"path", "/"})
-			.property({"query", nullptr})
-			.property({"header", nullptr})
-			.property({"cookie", nullptr})
-			.property({"body", nullptr})
-			.property({"raw_body", ""})
-			.property({"file", nullptr})
-			.property({"data", nullptr})
-			.method<&server_request::__construct>("__construct", {}, php::PRIVATE);
+namespace flame::http {
 
-		ext.add(std::move(class_server_request));
-	}
-	server_request::server_request() {
-		
-	}
-	server_request::~server_request() {
+    void server_request::declare(php::extension_entry& ext) {
+        php::class_entry<server_request> class_server_request(
+            "flame\\http\\server_request");
+        class_server_request
+            .property({"method", "GET"})
+            .property({"path", "/"})
+            .property({"query", nullptr})
+            .property({"header", nullptr})
+            .property({"cookie", nullptr})
+            .property({"body", nullptr})
+            .property({"raw_body", ""})
+            .property({"file", nullptr})
+            .property({"data", nullptr})
+            .method<&server_request::__construct>("__construct", {}, php::PRIVATE);
+        ext.add(std::move(class_server_request));
+    }
+    
+    server_request::server_request() {
+    }
 
-	}
-	php::value server_request::__construct(php::parameters& params) {
-		return nullptr;
-	}
-	void server_request::build_ex(const boost::beast::http::message<true, value_body<true>>& ctr_) {
-		auto method = boost::beast::http::to_string(ctr_.method());
-		set("method", php::string(method.data(), method.size()));
-		// 目标请求地址
-		auto target = ctr_.target();
-		std::shared_ptr<php::url> url_ = php::parse_url(target.data(), target.size());
-		if (url_->path) {
-			set("path", php::string(url_->path));
-		}
-		else {
-			set("path", php::string("/", 1));
-		}
-		if (url_->query) {
-			php::array query(4);
-			php::callable("parse_str")({php::string(url_->query), query.make_ref()});
-			set("query", query);
-		}
-		php::array header(4);
-		for (auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
-			php::string key {i->name_string().data(), i->name_string().size()};
-			php::string val {i->value().data(), i->value().size()};
+    server_request::~server_request() {
+    }
 
-			if (key.size() == 6 && strncasecmp(key.c_str(), "cookie", 6) == 0) {
-				php::array cookie(4);
-				parser::separator_parser<std::string, php::buffer> p1('\0','\0','=','\0','\0',';', [&cookie] (std::pair<std::string, php::buffer> entry) {
-					if (entry.second.size() > 0) {
-						entry.second.shrink( php::url_decode_inplace(entry.second.data(), entry.second.size()) );
-						cookie.set(entry.first, php::string(std::move(entry.second)));
-					}
-					else {
-						cookie.set(entry.first, php::string(0));
-					}
-				});
-				p1.parse(val.c_str(), val.size());
-				p1.parse(";", 1); // 结尾分隔符可能不存在(可以认为数据行可能不完整)
-				set("cookie", cookie);
-			}
-			// TODO 多个同名 HEADER 的处理
-			php::lowercase_inplace(key.data(), key.size());
-			header.set(key, val);
-		}
-		set("header", header);
+    php::value server_request::__construct(php::parameters& params) {
+        return nullptr;
+    }
 
-		php::string body = ctr_.body();
-		if (body.typeof(php::TYPE::STRING)) {
-			auto ctype = ctr_.find(boost::beast::http::field::content_type);
-			if (ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0) {
-				set("raw_body", body); // 不在 multipart 时保留原始数据
-			}
-			if (ctype != ctr_.end()) { // 存在时按照类型进行解析
-				php::array files(4);
-				set("body", ctype_decode(ctype->value(), body, &files));
-				set("file", files);
-			}
-			else { // 不存在与 raw_body 相同
-				set("body", body);
-			}
-		}
-	}
-}
+    void server_request::build_ex(const boost::beast::http::message<true, value_body<true>>& ctr_) {
+        auto method = boost::beast::http::to_string(ctr_.method());
+        set("method", php::string(method.data(), method.size()));
+        // 目标请求地址
+        auto target = ctr_.target();
+        std::shared_ptr<php::url> url_ = php::parse_url(target.data(), target.size());
+        if (url_->path) set("path", php::string(url_->path));
+        else set("path", php::string("/", 1));
+        if (url_->query) {
+            php::array query(4);
+            php::callable("parse_str")({php::string(url_->query), query.make_ref()});
+            set("query", query);
+        }
+        php::array header(4);
+        for (auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
+            php::string key {i->name_string().data(), i->name_string().size()};
+            php::string val {i->value().data(), i->value().size()};
+
+            if (key.size() == 6 && strncasecmp(key.c_str(), "cookie", 6) == 0) {
+                php::array cookie(4);
+                parser::separator_parser<std::string, php::buffer> p1('\0','\0','=','\0','\0',';', [&cookie] (std::pair<std::string, php::buffer> entry) {
+                    if (entry.second.size() > 0) {
+                        entry.second.shrink( php::url_decode_inplace(entry.second.data(), entry.second.size()) );
+                        cookie.set(entry.first, php::string(std::move(entry.second)));
+                    }
+                    else {
+                        cookie.set(entry.first, php::string(0));
+                    }
+                });
+                p1.parse(val.c_str(), val.size());
+                p1.parse(";", 1); // 结尾分隔符可能不存在(可以认为数据行可能不完整)
+                set("cookie", cookie);
+            }
+            // TODO 多个同名 HEADER 的处理
+            php::lowercase_inplace(key.data(), key.size());
+            header.set(key, val);
+        }
+        set("header", header);
+
+        php::string body = ctr_.body();
+        if (body.typeof(php::TYPE::STRING)) {
+            auto ctype = ctr_.find(boost::beast::http::field::content_type);
+            if (ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0)
+                set("raw_body", body); // 不在 multipart 时保留原始数据
+
+            if (ctype != ctr_.end()) { // 存在时按照类型进行解析
+                php::array files(4);
+                set("body", ctype_decode(ctype->value(), body, &files));
+                set("file", files);
+            }
+            else { // 不存在与 raw_body 相同
+                set("body", body);
+            }
+        }
+    }
 }
diff --git a/src/http/server_request.h b/src/http/server_request.h
index aefdece..454ca22 100644
--- a/src/http/server_request.h
+++ b/src/http/server_request.h
@@ -2,15 +2,16 @@
 #include "../vendor.h"
 
 namespace flame::http {
-	class handler;
-	class server_request: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		server_request();
-		~server_request();
-		php::value __construct(php::parameters& params);
-	private:
-		void build_ex(const boost::beast::http::message<true, value_body<true>>& ctr);
+    
+    class handler;
+    class server_request: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        server_request();
+        ~server_request();
+        php::value __construct(php::parameters& params);
+    private:
+        void build_ex(const boost::beast::http::message<true, value_body<true>>& ctr);
 
         friend class _handler;
     };
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index 26d97be..6948ab3 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -4,238 +4,238 @@
 #include "server_response.h"
 
 namespace flame::http {
-	
-	void server_response::declare(php::extension_entry& ext) {
-		php::class_entry<server_response> class_server_response("flame\\http\\server_response");
-		class_server_response
-			.property({"status", 200})
-			.property({"header", nullptr})
-			.property({"cookie", nullptr})
-			.property({"body",   nullptr})
-			.method<&server_response::__construct>("__construct", {}, php::PRIVATE)
-			.method<&server_response::__destruct>("__destruct")
-			.method<&server_response::set_cookie>("set_cookie", {
-				{"name", php::TYPE::STRING},
-				{"value", php::TYPE::STRING, false, true},
-				{"expire", php::TYPE::INTEGER, false, true},
-				{"path", php::TYPE::STRING, false, true},
-				{"domain", php::TYPE::STRING, false, true},
-				{"secure", php::TYPE::BOOLEAN, false, true},
-				{"httponly", php::TYPE::BOOLEAN, false, true},
-			})
-			.method<&server_response::write_header>("write_header", {
-				{"status", php::TYPE::INTEGER, false, true}
-			})
-			.method<&server_response::write>("write", {
-				{"chunk", php::TYPE::UNDEFINED}
-			})
-			.method<&server_response::end>("end", {
-				{"chunk", php::TYPE::UNDEFINED, false, true}
-			})
-			.method<&server_response::file>("file", {
-				{"root", php::TYPE::STRING},
-				{"path", php::TYPE::STRING},
-			});
+    
+    void server_response::declare(php::extension_entry& ext) {
+        php::class_entry<server_response> class_server_response("flame\\http\\server_response");
+        class_server_response
+            .property({"status", 200})
+            .property({"header", nullptr})
+            .property({"cookie", nullptr})
+            .property({"body",   nullptr})
+            .method<&server_response::__construct>("__construct", {}, php::PRIVATE)
+            .method<&server_response::__destruct>("__destruct")
+            .method<&server_response::set_cookie>("set_cookie", {
+                {"name", php::TYPE::STRING},
+                {"value", php::TYPE::STRING, false, true},
+                {"expire", php::TYPE::INTEGER, false, true},
+                {"path", php::TYPE::STRING, false, true},
+                {"domain", php::TYPE::STRING, false, true},
+                {"secure", php::TYPE::BOOLEAN, false, true},
+                {"httponly", php::TYPE::BOOLEAN, false, true},
+            })
+            .method<&server_response::write_header>("write_header", {
+                {"status", php::TYPE::INTEGER, false, true}
+            })
+            .method<&server_response::write>("write", {
+                {"chunk", php::TYPE::UNDEFINED}
+            })
+            .method<&server_response::end>("end", {
+                {"chunk", php::TYPE::UNDEFINED, false, true}
+            })
+            .method<&server_response::file>("file", {
+                {"root", php::TYPE::STRING},
+                {"path", php::TYPE::STRING},
+            });
 
-		ext.add(std::move(class_server_response));
-	}
+        ext.add(std::move(class_server_response));
+    }
 
-	server_response::server_response()
-	: status_(0) {
+    server_response::server_response()
+    : status_(0) {
 
-	}
+    }
 
-	server_response::~server_response() {
+    server_response::~server_response() {
 
-	}
-	// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
-	php::value server_response::__construct(php::parameters& params) {
-		return nullptr;
-	}
+    }
+    // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+    php::value server_response::__construct(php::parameters& params) {
+        return nullptr;
+    }
 
-	php::value server_response::__destruct(php::parameters& params) {
-		// 响应还未完全结束, 由 handler 代理 last_chunk 响应
-		if (handler_)  {
-			coroutine_handler ch{coroutine::current};
-			handler_->finish(this, ch);
-		}
-		return nullptr;
-	}
+    php::value server_response::__destruct(php::parameters& params) {
+        // 响应还未完全结束, 由 handler 代理 last_chunk 响应
+        if (handler_)  {
+            coroutine_handler ch{coroutine::current};
+            handler_->finish(this, ch);
+        }
+        return nullptr;
+    }
 
-	php::value server_response::set_cookie(php::parameters& params) {
-		php::array cookie(4);
-		php::string name = params[0], val;
-		name.to_string();
-		cookie.set("name", name);
-		if (params.size() > 1) {
-			val = params[1];
-			val.to_string();
-		}
-		else val = php::string("");
-		cookie.set("value", val);
-		if (params.size() > 2) {
-			// php::object expire = php::datetime(time::now() +  * 1000);
-			cookie.set("expire", params[2].to_integer());
-		}
-		if (params.size() > 3) {
-			php::string path = params[3];
-			path.to_string();
-			cookie.set("path", path);
-		}
-		if (params.size() > 4) {
-			php::string domain = params[4];
-			domain.to_string();
-			cookie.set("domain", domain);
-		}
-		if (params.size() > 5) {
-			bool secure = params[5];
-			cookie.set("secure", secure);
-		}
-		if (params.size() > 6) {
-			bool http_only = params[5];
-			cookie.set("http_only", http_only);
-		}
+    php::value server_response::set_cookie(php::parameters& params) {
+        php::array cookie(4);
+        php::string name = params[0], val;
+        name.to_string();
+        cookie.set("name", name);
+        if (params.size() > 1) {
+            val = params[1];
+            val.to_string();
+        }
+        else val = php::string("");
+        cookie.set("value", val);
+        if (params.size() > 2) {
+            // php::object expire = php::datetime(time::now() +  * 1000);
+            cookie.set("expire", params[2].to_integer());
+        }
+        if (params.size() > 3) {
+            php::string path = params[3];
+            path.to_string();
+            cookie.set("path", path);
+        }
+        if (params.size() > 4) {
+            php::string domain = params[4];
+            domain.to_string();
+            cookie.set("domain", domain);
+        }
+        if (params.size() > 5) {
+            bool secure = params[5];
+            cookie.set("secure", secure);
+        }
+        if (params.size() > 6) {
+            bool http_only = params[5];
+            cookie.set("http_only", http_only);
+        }
 
-		php::array cookies = get("cookie");
-		if (cookies.typeof(php::TYPE::NULLABLE)) cookies = php::array(4);
-		cookies.set(cookies.size(), cookie);
-		return nullptr;
-	}
-	// chunked encoding 用法
-	php::value server_response::write_header(php::parameters& params) {
-		if ((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT))
-			throw php::exception(zend_ce_error_exception
-				, "Failed to write header: already sent"
-				, -1);
-		
-		status_ |= STATUS_HEAD_SENT;
-		if (params.size() > 0) set("status", params[0].to_integer());
-		coroutine_handler ch{coroutine::current};
-		handler_->write_head(this, ch);
-		return nullptr;
-	}
+        php::array cookies = get("cookie");
+        if (cookies.typeof(php::TYPE::NULLABLE)) cookies = php::array(4);
+        cookies.set(cookies.size(), cookie);
+        return nullptr;
+    }
+    // chunked encoding 用法
+    php::value server_response::write_header(php::parameters& params) {
+        if ((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT))
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write header: already sent"
+                , -1);
+        
+        status_ |= STATUS_HEAD_SENT;
+        if (params.size() > 0) set("status", params[0].to_integer());
+        coroutine_handler ch{coroutine::current};
+        handler_->write_head(this, ch);
+        return nullptr;
+    }
 
-	php::value server_response::write(php::parameters& params) {
-		if (status_ & STATUS_BODY_END)
-			throw php::exception(zend_ce_error_exception
-				, "Failed to write body: response already done"
-				, -1);
+    php::value server_response::write(php::parameters& params) {
+        if (status_ & STATUS_BODY_END)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write body: response already done"
+                , -1);
 
-		coroutine_handler ch{coroutine::current};
-		if (!(status_ & STATUS_HEAD_SENT)) {
-			status_ |= STATUS_HEAD_SENT;
-			handler_->write_head(this, ch);
-		}
-		status_ |= STATUS_BODY_SENT;
-		php::string chunk = params[0].to_string();
-		handler_->write_body(this, chunk, ch);
-		return nullptr;
-	}
-	php::value server_response::end(php::parameters& params) {
-		if (status_ & STATUS_BODY_END)
-			throw php::exception(zend_ce_error_exception
-				, "Failed to write body: response already done"
-				, -1);
-		
-		coroutine_handler ch{coroutine::current};
-		if (!(status_ & STATUS_HEAD_SENT)) {
-			status_ |= STATUS_HEAD_SENT;
-			handler_->write_head(this, ch);
-		}
-		if (params.size() > 0) {
-			status_ |= STATUS_BODY_SENT;
-			php::string chunk = params[0].to_string();
-			handler_->write_body(this, chunk, ch);
-		}
-		else {
-			status_ |= STATUS_BODY_END;
-			handler_->write_end(this, ch);
-			handler_.reset();
-		}
-		return nullptr;
-	}
-	php::value server_response::file(php::parameters& params) {
-		if ((status_ & STATUS_BODY_END) || (status_ & STATUS_BODY_SENT))
-			throw php::exception(zend_ce_error_exception
-				, "Failed to write file: response already done"
-				, -1);
+        coroutine_handler ch{coroutine::current};
+        if (!(status_ & STATUS_HEAD_SENT)) {
+            status_ |= STATUS_HEAD_SENT;
+            handler_->write_head(this, ch);
+        }
+        status_ |= STATUS_BODY_SENT;
+        php::string chunk = params[0].to_string();
+        handler_->write_body(this, chunk, ch);
+        return nullptr;
+    }
+    php::value server_response::end(php::parameters& params) {
+        if (status_ & STATUS_BODY_END)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write body: response already done"
+                , -1);
+        
+        coroutine_handler ch{coroutine::current};
+        if (!(status_ & STATUS_HEAD_SENT)) {
+            status_ |= STATUS_HEAD_SENT;
+            handler_->write_head(this, ch);
+        }
+        if (params.size() > 0) {
+            status_ |= STATUS_BODY_SENT;
+            php::string chunk = params[0].to_string();
+            handler_->write_body(this, chunk, ch);
+        }
+        else {
+            status_ |= STATUS_BODY_END;
+            handler_->write_end(this, ch);
+            handler_.reset();
+        }
+        return nullptr;
+    }
+    php::value server_response::file(php::parameters& params) {
+        if ((status_ & STATUS_BODY_END) || (status_ & STATUS_BODY_SENT))
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write file: response already done"
+                , -1);
 
-		coroutine_handler ch{coroutine::current};
-		if (!(status_ & STATUS_HEAD_SENT)) {
-			status_ |= STATUS_HEAD_SENT;
-			handler_->write_head(this, ch);
-		}
-		status_ |= STATUS_BODY_END | STATUS_BODY_SENT;
+        coroutine_handler ch{coroutine::current};
+        if (!(status_ & STATUS_HEAD_SENT)) {
+            status_ |= STATUS_HEAD_SENT;
+            handler_->write_head(this, ch);
+        }
+        status_ |= STATUS_BODY_END | STATUS_BODY_SENT;
 
-		boost::filesystem::path root, file;
-		root += params[0].to_string();
-		file += params[1].to_string();
+        boost::filesystem::path root, file;
+        root += params[0].to_string();
+        file += params[1].to_string();
 
-		handler_->write_file(this, (root / file.lexically_normal()).string(), ch);
-		return nullptr;
-	}
-	
-	// 支持 content-length 形式的用法
-	void server_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
-		if (status_ & STATUS_BUILT) return; // 重复使用
-		status_ |= STATUS_BUILT; // 在 chunked_writer 中设置
-		ctr_.result( get("status").to_integer() ); // 非法 status_code 会抛出异常
-		php::array headers = get("header");
-		if (headers.typeof(php::TYPE::ARRAY)) {
-			for (auto i=headers.begin(); i!=headers.end(); ++i) {
-				php::string key { i->first };
-				php::string val { i->second };
-				ctr_.set(std::string_view(key.c_str(), key.size()), val);
-			}
-		}
-		php::array cookies = get("cookie");
-		if (cookies.typeof(php::TYPE::ARRAY)) {
-			for(auto i=cookies.begin(); i!=cookies.end(); ++i) {
-				php::array  cookie = i->second;
-				std::string buffer;
-				buffer.append(cookie.get("name"));
-				buffer.push_back('=');
-				php::string value = cookie.get("value");
-				if (!value.empty()) {
-					buffer.push_back('"');
-					buffer.append(php::url_encode(value.c_str(), value.size()));
-					buffer.push_back('"');
-				}
-				if (cookie.exists("expire")) {
-					std::int64_t expire = cookie.get("expire");
-					// 范围 30 天内的数值, 按时间长度计算
-					if (expire < 30 * 86400) {
-						buffer.append("; Max-Age=", 10);
-						buffer.append(std::to_string(expire));
-						buffer.append("; Expire=", 9);
-						php::object date = php::datetime(expire * 1000 + std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count());
-						buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
-					}
-					else {
-					// 超过上面范围, 按时间点计算
-						buffer.append("; Expire=", 9);
-						php::object date = php::datetime(expire * 1000);
-						buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
-					}
-				}
-				if (cookie.exists("path")) {
-					buffer.append("; Path=", 7);
-					buffer.append(cookie.get("path"));
-				}
-				if (cookie.exists("domain")) {
-					buffer.append("; Domain=", 9);
-					buffer.append(cookie.get("domain"));
-				}
-				if (!cookie.get("secure").empty()) buffer.append("; Secure", 8);
-				if (!cookie.get("http_only").empty()) buffer.append("; HttpOnly", 10);
-				ctr_.insert("Set-Cookie", buffer);
-			}
-		}
+        handler_->write_file(this, (root / file.lexically_normal()).string(), ch);
+        return nullptr;
+    }
+    
+    // 支持 content-length 形式的用法
+    void server_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
+        if (status_ & STATUS_BUILT) return; // 重复使用
+        status_ |= STATUS_BUILT; // 在 chunked_writer 中设置
+        ctr_.result( get("status").to_integer() ); // 非法 status_code 会抛出异常
+        php::array headers = get("header");
+        if (headers.typeof(php::TYPE::ARRAY)) {
+            for (auto i=headers.begin(); i!=headers.end(); ++i) {
+                php::string key { i->first };
+                php::string val { i->second };
+                ctr_.set(std::string_view(key.c_str(), key.size()), val);
+            }
+        }
+        php::array cookies = get("cookie");
+        if (cookies.typeof(php::TYPE::ARRAY)) {
+            for(auto i=cookies.begin(); i!=cookies.end(); ++i) {
+                php::array  cookie = i->second;
+                std::string buffer;
+                buffer.append(cookie.get("name"));
+                buffer.push_back('=');
+                php::string value = cookie.get("value");
+                if (!value.empty()) {
+                    buffer.push_back('"');
+                    buffer.append(php::url_encode(value.c_str(), value.size()));
+                    buffer.push_back('"');
+                }
+                if (cookie.exists("expire")) {
+                    std::int64_t expire = cookie.get("expire");
+                    // 范围 30 天内的数值, 按时间长度计算
+                    if (expire < 30 * 86400) {
+                        buffer.append("; Max-Age=", 10);
+                        buffer.append(std::to_string(expire));
+                        buffer.append("; Expire=", 9);
+                        php::object date = php::datetime(expire * 1000 + std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count());
+                        buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
+                    }
+                    else {
+                    // 超过上面范围, 按时间点计算
+                        buffer.append("; Expire=", 9);
+                        php::object date = php::datetime(expire * 1000);
+                        buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
+                    }
+                }
+                if (cookie.exists("path")) {
+                    buffer.append("; Path=", 7);
+                    buffer.append(cookie.get("path"));
+                }
+                if (cookie.exists("domain")) {
+                    buffer.append("; Domain=", 9);
+                    buffer.append(cookie.get("domain"));
+                }
+                if (!cookie.get("secure").empty()) buffer.append("; Secure", 8);
+                if (!cookie.get("http_only").empty()) buffer.append("; HttpOnly", 10);
+                ctr_.insert("Set-Cookie", buffer);
+            }
+        }
 CTYPE_AGAIN:
-		auto ctype = ctr_.find(boost::beast::http::field::content_type);
-		if (ctype == ctr_.end()) {
-			ctr_.set(boost::beast::http::field::content_type, "text/plain");
-			goto CTYPE_AGAIN;
-		}
-	}
+        auto ctype = ctr_.find(boost::beast::http::field::content_type);
+        if (ctype == ctr_.end()) {
+            ctr_.set(boost::beast::http::field::content_type, "text/plain");
+            goto CTYPE_AGAIN;
+        }
+    }
 }
diff --git a/src/http/server_response.h b/src/http/server_response.h
index 8bd8e20..9c7c069 100644
--- a/src/http/server_response.h
+++ b/src/http/server_response.h
@@ -3,33 +3,32 @@
 #include "value_body.h"
 
 namespace flame::http {
-	class _handler;
-	class server_response: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		// 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value set_cookie(php::parameters& params);
-		php::value write_header(php::parameters& params);
-		php::value write(php::parameters& params);
-		php::value end(php::parameters& params);
-		php::value file(php::parameters& params);
-		server_response();
-		~server_response();
-	private:
-		enum
-		{
-			STATUS_BUILT = 0x01,
-			STATUS_HEAD_SENT = 0x02,
-			STATUS_BODY_SENT = 0x04,
-			STATUS_BODY_END  = 0x08,
-		};
-		int status_;
-		std::shared_ptr<_handler> handler_; // 允许通过 $res 对象保留 handler 持续响应
 
-		void build_ex(boost::beast::http::message<false, value_body<false>> &ctr);
+    class _handler;
+    class server_response: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+        php::value __construct(php::parameters& params);
+        php::value __destruct(php::parameters& params);
+        php::value set_cookie(php::parameters& params);
+        php::value write_header(php::parameters& params);
+        php::value write(php::parameters& params);
+        php::value end(php::parameters& params);
+        php::value file(php::parameters& params);
+        server_response();
+        ~server_response();
+    private:
+        enum {
+            STATUS_BUILT = 0x01,
+            STATUS_HEAD_SENT = 0x02,
+            STATUS_BODY_SENT = 0x04,
+            STATUS_BODY_END  = 0x08,
+        };
+        int status_;
+        std::shared_ptr<_handler> handler_; // 允许通过 $res 对象保留 handler 持续响应
+        void build_ex(boost::beast::http::message<false, value_body<false>> &ctr);
 
-		friend class _handler;
-	};
+        friend class _handler;
+    };
 }
diff --git a/src/http/value_body.cpp b/src/http/value_body.cpp
index b5fd426..d03cb9d 100644
--- a/src/http/value_body.cpp
+++ b/src/http/value_body.cpp
@@ -1,8 +1,8 @@
 #include "../coroutine.h"
 #include "value_body.h"
 
-namespace flame {
-namespace http {
+namespace flame::http {
+
     void value_body_reader::init(boost::optional<std::uint64_t> n, boost::system::error_code& error) {
         error.assign(0, error.category());
     }
@@ -26,5 +26,4 @@ namespace http {
             return {{boost::asio::const_buffer(data.data(), data.size()), false}};
         }
     }
-} // http
-} // flame
+} // namespace flame::http
diff --git a/src/http/value_body.h b/src/http/value_body.h
index 1f5f350..9f193b7 100644
--- a/src/http/value_body.h
+++ b/src/http/value_body.h
@@ -1,7 +1,8 @@
 #pragma once
+#include "../vendor.h"
+
+namespace flame::http {
 
-namespace flame {
-namespace http {
     class value_body_reader;
     class value_body_writer;
     template <bool isRequest>
@@ -15,6 +16,7 @@ namespace http {
         // BodyWriter (Body)
         using writer = value_body_writer;
     };
+
     class value_body_reader {
     public:
         template <bool isRequest, class Fields>
@@ -27,6 +29,7 @@ namespace http {
         php::value& v_;
         php::buffer b_;
     };
+
     class value_body_writer {
     public:
         using const_buffers_type = boost::asio::const_buffer;
@@ -38,6 +41,6 @@ namespace http {
         // const boost::beast::http::header<true>& h_;
         const php::value& v_;
     };
-} // http
-} // flame
+} // namnespace flame::http
+
 #include "value_body.ipp"
diff --git a/src/http/value_body.ipp b/src/http/value_body.ipp
index 7874d43..63e9fce 100644
--- a/src/http/value_body.ipp
+++ b/src/http/value_body.ipp
@@ -1,16 +1,17 @@
 #pragma once
 
-namespace flame {
-namespace http {
+namespace flame::http {
+    
     template <bool isRequest, class Fields>
     value_body_reader::value_body_reader(boost::beast::http::header<isRequest, Fields>& h, php::value& v)
     : v_(v) {
 
     }
+
     template <typename ConstBufferSequence>
     std::size_t value_body_reader::put(ConstBufferSequence s, boost::system::error_code& error) {
         std::size_t n = 0;
-        for(auto i=boost::asio::buffer_sequence_begin(s); i!=boost::asio::buffer_sequence_end(s); ++i) {
+        for (auto i=boost::asio::buffer_sequence_begin(s); i!=boost::asio::buffer_sequence_end(s); ++i) {
             boost::asio::const_buffer cbuf(*i);
             b_.append((const char *)cbuf.data(), cbuf.size());
             n += cbuf.size();
@@ -18,13 +19,14 @@ namespace http {
         error.assign(0, error.category());
         return n;
     }
+
     template <bool isRequest, class Fields>
     value_body_writer::value_body_writer(const boost::beast::http::header<isRequest, Fields>& h, const php::value& v)
     : v_(v) {}
+
     template <bool isRequest>
     std::uint64_t value_body<isRequest>::size(const value_body<isRequest>::value_type& v) {
         return v.size();
     }
 
-} // http
-} // flame
+} // namespace flame::http
diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index 6bf57e7..75505f7 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -4,15 +4,12 @@
 #include "message.h"
 #include "../coroutine_queue.h"
 
-namespace flame::kafka
-{
+namespace flame::kafka {
     static rd_kafka_topic_partition_list_t *array2topics(const php::array &topics) {
         // 目标订阅的 TOPIC
         rd_kafka_topic_partition_list_t *t = rd_kafka_topic_partition_list_new(topics.size());
         for (auto i = topics.begin(); i != topics.end(); ++i)
-        {
             rd_kafka_topic_partition_list_add(t, i->second.to_string().c_str(), RD_KAFKA_PARTITION_UA);
-        }
         return t;
     }
 
@@ -77,8 +74,7 @@ namespace flame::kafka
 POLL_MESSAGE:
         // 由于 poll 动作本身阻塞, 为防止所有工作线被占用, 这里绑定到 strd_ 执行
         // (理论上仅占用一个协程)
-        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]()
-        {
+        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]() {
             msg = rd_kafka_consumer_poll(conn_, 500);
             if (!msg) err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
             else if (msg->err) {
diff --git a/src/kafka/_consumer.h b/src/kafka/_consumer.h
index fef9bd5..7329cfe 100644
--- a/src/kafka/_consumer.h
+++ b/src/kafka/_consumer.h
@@ -6,10 +6,10 @@ namespace flame {
     template <class T>
     class coroutine_queue;
 }
-namespace flame::kafka
-{
-    class _consumer
-    {
+
+namespace flame::kafka {
+
+    class _consumer {
     public:
         _consumer(php::array& config, php::array& topics);
         ~_consumer();
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index d8f7e85..c369a86 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -2,8 +2,8 @@
 #include "_producer.h"
 #include "kafka.h"
 
-namespace flame::kafka
-{
+namespace flame::kafka {
+    
     _producer::_producer(php::array& config, php::array& topics) {
         if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
             throw php::exception(zend_ce_type_error
diff --git a/src/kafka/_producer.h b/src/kafka/_producer.h
index ab98aad..a6c6d13 100644
--- a/src/kafka/_producer.h
+++ b/src/kafka/_producer.h
@@ -2,8 +2,8 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 
-namespace flame::kafka
-{
+namespace flame::kafka {
+    
     class _producer {
     public:
         _producer(php::array& config, php::array& topics);
diff --git a/src/kafka/consumer.cpp b/src/kafka/consumer.cpp
index ef29e8f..d4f95f0 100644
--- a/src/kafka/consumer.cpp
+++ b/src/kafka/consumer.cpp
@@ -10,24 +10,24 @@
 namespace flame::kafka {
 
     void consumer::declare(php::extension_entry& ext) {
-		php::class_entry<consumer> class_consumer("flame\\kafka\\consumer");
-		class_consumer
-			.method<&consumer::__construct>("__construct", {}, php::PRIVATE)
-			.method<&consumer::run>("run", {
-				{"callable", php::TYPE::CALLABLE},
-			})
+        php::class_entry<consumer> class_consumer("flame\\kafka\\consumer");
+        class_consumer
+            .method<&consumer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&consumer::run>("run", {
+                {"callable", php::TYPE::CALLABLE},
+            })
             .method<&consumer::commit>("commit", {
                 {"message", "flame\\kafka\\message"}
             })
-			.method<&consumer::close>("close");
-		ext.add(std::move(class_consumer));
-	}
+            .method<&consumer::close>("close");
+        ext.add(std::move(class_consumer));
+    }
 
-	php::value consumer::__construct(php::parameters& params) {
-		return nullptr;
-	}
+    php::value consumer::__construct(php::parameters& params) {
+        return nullptr;
+    }
 
-	php::value consumer::run(php::parameters& params) {
+    php::value consumer::run(php::parameters& params) {
         cb_ = params[0];
         coroutine_handler ch_run {coroutine::current};
         coroutine_queue<php::object> q;
@@ -58,7 +58,7 @@ namespace flame::kafka {
         cs_->consume(q, ch_run);
         ch_run.suspend();
         return nullptr;
-	}
+    }
 
     php::value consumer::commit(php::parameters& params) {
         php::object obj = params[0];
@@ -68,9 +68,9 @@ namespace flame::kafka {
         return nullptr;
     }
 
-	php::value consumer::close(php::parameters& params) {
+    php::value consumer::close(php::parameters& params) {
         coroutine_handler ch {coroutine::current};
         cs_->close(ch);
         return nullptr;
-	}
+    }
 } // namespace
diff --git a/src/kafka/consumer.h b/src/kafka/consumer.h
index fdcea60..db39685 100644
--- a/src/kafka/consumer.h
+++ b/src/kafka/consumer.h
@@ -2,11 +2,10 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 
-namespace flame::kafka
-{
+namespace flame::kafka {
+    
     class _consumer;
-    class consumer : public php::class_base
-    {
+    class consumer : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index af69a66..f711647 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -8,17 +8,17 @@
 namespace flame::kafka {
     void declare(php::extension_entry &ext) {
         ext
-			.function<consume>("flame\\kafka\\consume", {
-				{"options", php::TYPE::ARRAY},
+            .function<consume>("flame\\kafka\\consume", {
+                {"options", php::TYPE::ARRAY},
                 {"topics", php::TYPE::ARRAY},
-			})
+            })
             .function<produce>("flame\\kafka\\produce", {
-				{"options", php::TYPE::ARRAY},
+                {"options", php::TYPE::ARRAY},
                 {"topics", php::TYPE::ARRAY},
-			});;
-		message::declare(ext);
+            });;
+        message::declare(ext);
         consumer::declare(ext);
-		producer::declare(ext);
+        producer::declare(ext);
     }
     
     php::value consume(php::parameters& params) {
diff --git a/src/kafka/kafka.h b/src/kafka/kafka.h
index 3d868ab..f6ab8b2 100644
--- a/src/kafka/kafka.h
+++ b/src/kafka/kafka.h
@@ -1,8 +1,7 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::kafka
-{
+namespace flame::kafka {
     void declare(php::extension_entry &ext);
     php::value consume(php::parameters& params);
     php::value produce(php::parameters& params);
diff --git a/src/kafka/message.cpp b/src/kafka/message.cpp
index c294583..4d71b93 100644
--- a/src/kafka/message.cpp
+++ b/src/kafka/message.cpp
@@ -3,34 +3,31 @@
 #include "../time/time.h"
 
 namespace flame::kafka {
-    void message::declare(php::extension_entry& ext)
-    {
+    void message::declare(php::extension_entry& ext) {
         php::class_entry<message> class_message("flame\\kafka\\message");
-		class_message
+        class_message
             .implements(&php_json_serializable_ce)
-			.property({"topic", ""})
-			.property({"partition", -1})
-			.property({"key", ""})
+            .property({"topic", ""})
+            .property({"partition", -1})
+            .property({"key", ""})
             .property({"offset", -1})
             .property({"header", nullptr})
-			.property({"payload", nullptr})
-			.property({"timestamp", 0})
-			.method<&message::__construct>("__construct",
-            {
-				{"payload", php::TYPE::STRING, false, true},
+            .property({"payload", nullptr})
+            .property({"timestamp", 0})
+            .method<&message::__construct>("__construct", {
+                {"payload", php::TYPE::STRING, false, true},
                 {"key", php::TYPE::STRING, false, true},
-			})
-			.method<&message::to_string>("__toString")
+            })
+            .method<&message::to_string>("__toString")
             .method<&message::to_json>("jsonSerialize");
-
-		ext.add(std::move(class_message));
+        ext.add(std::move(class_message));
     }
-    message::~message()
-    {
+
+    message::~message()  {
         if (msg_) rd_kafka_message_destroy(msg_);
     }
-    void message::build_ex(rd_kafka_message_t* msg)
-    {
+
+    void message::build_ex(rd_kafka_message_t* msg) {
         // 用于单挑 message 的提交
         msg_ = msg;
         // ！！！ 是否必须复制出 PHP 的内容？
@@ -40,38 +37,30 @@ namespace flame::kafka {
         set("offset", msg->offset);
         rd_kafka_headers_t* hdrs;
         if (rd_kafka_message_headers(msg, &hdrs) == RD_KAFKA_RESP_ERR_NO_ERROR)
-        {
             set("header", hdrs2array(hdrs));
-        }
         // header
         set("payload", php::string((const char*)msg->payload, msg->len));
         set("timestamp", rd_kafka_message_timestamp(msg, nullptr));
     }
-    php::value message::__construct(php::parameters& params)
-    {
-        if (params.size() > 0)
-        {
-            set("payload", params[0].to_string());
-        }
-        if (params.size() > 1)
-        {
-            set("key", params[1].to_string());
-        }
+
+    php::value message::__construct(php::parameters& params) {
+        if (params.size() > 0) set("payload", params[0].to_string());
+        if (params.size() > 1) set("key", params[1].to_string());
         set("header", php::array(4));
         set("timestamp", 
             std::chrono::duration_cast<std::chrono::milliseconds>(flame::time::now().time_since_epoch()).count());
         return nullptr;
     }
-    php::value message::to_json(php::parameters& params)
-    {
+
+    php::value message::to_json(php::parameters& params) {
         php::array json(4);
         json.set("topic",  get("topic"));
         json.set("key",    get("key"));
         json.set("payload",get("payload"));
         return json;
     }
-    php::value message::to_string(php::parameters& params)
-    {
+
+    php::value message::to_string(php::parameters& params) {
         return get("payload");
     }
 } // namespace flame::kafka
diff --git a/src/kafka/message.h b/src/kafka/message.h
index a1d7604..5f82506 100644
--- a/src/kafka/message.h
+++ b/src/kafka/message.h
@@ -1,21 +1,21 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::kafka
-{
-	class _consumer;
-	class message: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		~message();
-		void build_ex(rd_kafka_message_t* msg);
-		php::value __construct(php::parameters& params); // 私有
-		php::value to_json(php::parameters& params);
-		php::value to_string(php::parameters& params);
-	private:
-	  rd_kafka_message_t *msg_ = nullptr;
+namespace flame::kafka {
+    
+    class _consumer;
+    class message: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        ~message();
+        void build_ex(rd_kafka_message_t* msg);
+        php::value __construct(php::parameters& params); // 私有
+        php::value to_json(php::parameters& params);
+        php::value to_string(php::parameters& params);
+    private:
+      rd_kafka_message_t *msg_ = nullptr;
 
-	  friend class _consumer;
-	//   friend class _producer;
-	};
+      friend class _consumer;
+    //   friend class _producer;
+    };
 }
diff --git a/src/kafka/producer.cpp b/src/kafka/producer.cpp
index 31e3578..e76c572 100644
--- a/src/kafka/producer.cpp
+++ b/src/kafka/producer.cpp
@@ -11,8 +11,7 @@ namespace flame::kafka {
         class_producer
             .method<&producer::__construct>("__construct", {}, php::PRIVATE)
             .method<&producer::__destruct>("__destruct")
-            .method<&producer::publish>("publish",
-            {
+            .method<&producer::publish>("publish", {
                 {"topic", php::TYPE::STRING},
             })
             .method<&producer::flush>("flush");
@@ -39,19 +38,12 @@ namespace flame::kafka {
             key = msg.get("key").to_string();
             payload = msg.get("payload").to_string();
             header = msg.get("header");
-            if (!header.typeof(php::TYPE::ARRAY))
-            {
-                header = php::array(0);
-            }
+            if (!header.typeof(php::TYPE::ARRAY)) header = php::array(0);
         }
         else {
             payload = params[1].to_string();
-            if (params.size() > 2) {
-                key = params[2].to_string();
-            }
-            else {
-                key = php::string(0);
-            }
+            if (params.size() > 2) key = params[2].to_string();
+            else key = php::string(0);
             if (params.size() > 3) {
                 if (params[3].typeof(php::TYPE::ARRAY)) header = params[4];
                 else header = php::array(0);
diff --git a/src/kafka/producer.h b/src/kafka/producer.h
index 476f420..90a21c6 100644
--- a/src/kafka/producer.h
+++ b/src/kafka/producer.h
@@ -1,11 +1,10 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::kafka
-{
+namespace flame::kafka {
+    
     class _producer;
-    class producer : public php::class_base
-    {
+    class producer : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
diff --git a/src/log/log.h b/src/log/log.h
index 8801bc2..86974a8 100644
--- a/src/log/log.h
+++ b/src/log/log.h
@@ -1,8 +1,6 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::log
-{
+namespace flame::log {
     void declare(php::extension_entry &ext);
 } // namespace flame::log
-
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index b364857..394ad6c 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -4,9 +4,10 @@
 #include "mongodb.h"
 #include "cursor.h"
 
-namespace flame::mongodb
-{
+namespace flame::mongodb {
+
     void _connection_base::fake_deleter(bson_t *doc) {}
+    
     php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, int type, coroutine_handler &ch) {
         std::shared_ptr<bson_t> cmd = array2bson(pcmd);
         // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏
diff --git a/src/mongodb/_connection_base.h b/src/mongodb/_connection_base.h
index 9fc6706..c652c54 100644
--- a/src/mongodb/_connection_base.h
+++ b/src/mongodb/_connection_base.h
@@ -3,8 +3,8 @@
 #include "../coroutine.h"
 
 namespace flame::mongodb {
-    class _connection_base
-    {
+    
+    class _connection_base {
     public:
         enum command_type_t {
             COMMAND_RAW,
diff --git a/src/mongodb/_connection_lock.cpp b/src/mongodb/_connection_lock.cpp
index b0a3a1f..88fada7 100644
--- a/src/mongodb/_connection_lock.cpp
+++ b/src/mongodb/_connection_lock.cpp
@@ -2,12 +2,10 @@
 #include "_connection_lock.h"
 #include "mongodb.h"
 
-namespace flame::mongodb
-{
+namespace flame::mongodb {
     _connection_lock::_connection_lock(std::shared_ptr<mongoc_client_t> c)
     : conn_(c)
     , guard_(gcontroller->context_y) {
-
     }
 
     _connection_lock::~_connection_lock() {
diff --git a/src/mongodb/_connection_lock.h b/src/mongodb/_connection_lock.h
index 573b8fc..bc48651 100644
--- a/src/mongodb/_connection_lock.h
+++ b/src/mongodb/_connection_lock.h
@@ -2,10 +2,9 @@
 #include "../vendor.h"
 #include "_connection_base.h"
 
-namespace flame::mongodb
-{
-    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock>
-    {
+namespace flame::mongodb {
+
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> {
     public:
         _connection_lock(std::shared_ptr<mongoc_client_t> c);
         ~_connection_lock();
diff --git a/src/mongodb/_connection_pool.cpp b/src/mongodb/_connection_pool.cpp
index f9bfe44..922af34 100644
--- a/src/mongodb/_connection_pool.cpp
+++ b/src/mongodb/_connection_pool.cpp
@@ -9,10 +9,10 @@ namespace flame::mongodb {
         std::unique_ptr<mongoc_uri_t, void (*)(mongoc_uri_t *)> uri(mongoc_uri_new(url.c_str()), mongoc_uri_destroy);
         const bson_t* options = mongoc_uri_get_options(uri.get());
         if (!bson_has_field(options, MONGOC_URI_READPREFERENCE)) {
-			mongoc_read_prefs_t* pref = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); // secondaryPreferred
-			mongoc_uri_set_read_prefs_t(uri.get(), pref);
-			mongoc_read_prefs_destroy(pref);
-		}
+            mongoc_read_prefs_t* pref = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); // secondaryPreferred
+            mongoc_uri_set_read_prefs_t(uri.get(), pref);
+            mongoc_read_prefs_destroy(pref);
+        }
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
         mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 6);
 
diff --git a/src/mongodb/_connection_pool.h b/src/mongodb/_connection_pool.h
index 7c18993..c33ba9f 100644
--- a/src/mongodb/_connection_pool.h
+++ b/src/mongodb/_connection_pool.h
@@ -4,8 +4,7 @@
 
 namespace flame::mongodb {
 
-    class _connection_pool: public _connection_base, public std::enable_shared_from_this<_connection_pool>
-    {
+    class _connection_pool: public _connection_base, public std::enable_shared_from_this<_connection_pool> {
     public:
         _connection_pool(const std::string& url);
         ~_connection_pool();
diff --git a/src/mongodb/client.cpp b/src/mongodb/client.cpp
index 77c6800..34c4905 100644
--- a/src/mongodb/client.cpp
+++ b/src/mongodb/client.cpp
@@ -4,10 +4,9 @@
 #include "client.h"
 #include "collection.h"
 
-namespace flame::mongodb
-{
-    void client::declare(php::extension_entry &ext)
-    {
+namespace flame::mongodb {
+
+    void client::declare(php::extension_entry &ext) {
         php::class_entry<client> class_client("flame\\mongodb\\client");
         class_client
             .constant({"COMMAND_RAW", _connection_base::COMMAND_RAW})
@@ -18,36 +17,32 @@ namespace flame::mongodb
             .method<&client::dump>("dump", {
                 {"data", php::TYPE::ARRAY},
             })
-            .method<&client::execute>("execute",
-            {
+            .method<&client::execute>("execute", {
                 {"command", php::TYPE::ARRAY},
                 {"write", php::TYPE::INTEGER, false, true},
             })
-            .method<&client::__get>("collection",
-            {
+            .method<&client::__get>("collection", {
                 {"name", php::TYPE::STRING}
             })
-            .method<&client::__get>("__get",
-            {
+            .method<&client::__get>("__get", {
                 {"name", php::TYPE::STRING}
             })
-            .method<&client::__isset>("__isset",
-            {
+            .method<&client::__isset>("__isset", {
                 {"name", php::TYPE::STRING}
             });
         ext.add(std::move(class_client));
     }
-    php::value client::__construct(php::parameters &params)
-    {
+
+    php::value client::__construct(php::parameters &params) {
         return nullptr;
     }
-    php::value client::dump(php::parameters& params)
-    {
+
+    php::value client::dump(php::parameters& params) {
         std::cout << bson_as_relaxed_extended_json(array2bson(params[0]).get(), nullptr) << std::endl;
         return nullptr;
     }
-    php::value client::execute(php::parameters &params)
-    {
+
+    php::value client::execute(php::parameters &params) {
         int write = _connection_base::COMMAND_RAW;
         if (params.size() > 1) write = params[1].to_integer();
         coroutine_handler ch {coroutine::current};
@@ -56,8 +51,8 @@ namespace flame::mongodb
         php::array cmd = params[0];
         return cp_->exec(conn_, cmd, write, ch);
     }
-    php::value client::__get(php::parameters &params)
-    {
+
+    php::value client::__get(php::parameters &params) {
         php::object obj(php::class_entry<collection>::entry());
         collection *ptr = static_cast<collection *>(php::native(obj));
         ptr->cp_ = cp_;
@@ -65,8 +60,8 @@ namespace flame::mongodb
         obj.set("name", params[0]);
         return std::move(obj);
     }
-    php::value client::__isset(php::parameters &params)
-    {
+
+    php::value client::__isset(php::parameters &params) {
         return true;
     }
 } // namespace flame::mongodb
diff --git a/src/mongodb/client.h b/src/mongodb/client.h
index 594a582..1be09ca 100644
--- a/src/mongodb/client.h
+++ b/src/mongodb/client.h
@@ -1,11 +1,10 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::mongodb
-{
+namespace flame::mongodb {
+
     class _connection_pool;
-    class client : public php::class_base
-    {
+    class client : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params);
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index ceb1f81..20ea059 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -88,6 +88,10 @@ namespace flame::mongodb {
         ext.add(std::move(class_collection));
     }
 
+    php::value collection::__construct(php::parameters &params) { // 私有
+        return nullptr;
+    }
+
     php::value collection::insert(php::parameters &params) {
         php::array cmd(8);
         cmd.set("insert", name_);
@@ -215,8 +219,8 @@ namespace flame::mongodb {
         assert(cs.instanceof(php::class_entry<cursor>::entry()));
         return cs.call("fetch_row");
     }
-    php::value collection::get(php::parameters &params)
-    {
+    
+    php::value collection::get(php::parameters &params) {
         php::array cmd(8);
         cmd.set("find", name_);
         php::array project(2);
diff --git a/src/mongodb/collection.h b/src/mongodb/collection.h
index ae553d3..a5dd2a4 100644
--- a/src/mongodb/collection.h
+++ b/src/mongodb/collection.h
@@ -1,16 +1,13 @@
 #pragma once
+#include "../vendor.h"
+
+namespace flame::mongodb {
 
-namespace flame::mongodb
-{
     class _connection_pool;
-    class collection : public php::class_base
-    {
+    class collection : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
-        php::value __construct(php::parameters &params)
-        { // 私有
-            return nullptr;
-        }
+        php::value __construct(php::parameters &params);
         php::value insert(php::parameters &params);
         php::value delete_(php::parameters &params);
         php::value delete_one(php::parameters& params);
diff --git a/src/mongodb/cursor.h b/src/mongodb/cursor.h
index fa73402..55e2ce8 100644
--- a/src/mongodb/cursor.h
+++ b/src/mongodb/cursor.h
@@ -2,22 +2,22 @@
 #include "../vendor.h"
 
 namespace flame::mongodb {
-	class _connection_lock;
-	class cursor: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		php::value __construct(php::parameters& params) { // 私有
-			return nullptr;
-		}
-		php::value __destruct(php::parameters& params) {
-			return nullptr;
-		}
-		php::value fetch_row(php::parameters& params);
-		php::value fetch_all(php::parameters& params);
-	private:
+    class _connection_lock;
+    class cursor: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params) { // 私有
+            return nullptr;
+        }
+        php::value __destruct(php::parameters& params) {
+            return nullptr;
+        }
+        php::value fetch_row(php::parameters& params);
+        php::value fetch_all(php::parameters& params);
+    private:
         // 须先销毁 指针 后归还 连接
         std::shared_ptr<_connection_lock> cl_;
         std::shared_ptr<mongoc_cursor_t>  cs_;
         friend class _connection_base;
-	};
+    };
 } // namespace flame::mongodb
diff --git a/src/mongodb/date_time.cpp b/src/mongodb/date_time.cpp
index 4f47e79..25e35bc 100644
--- a/src/mongodb/date_time.cpp
+++ b/src/mongodb/date_time.cpp
@@ -1,58 +1,58 @@
 #include "date_time.h"
 #include "../time/time.h"
 
-namespace flame {
-namespace mongodb {
-	void date_time::declare(php::extension_entry& ext) {
-		php::class_entry<date_time> class_date_time("flame\\mongodb\\date_time");
-		class_date_time
-			.implements(&php_json_serializable_ce)
-			.method<&date_time::__construct>("__construct", {
-				{"milliseconds", php::TYPE::INTEGER, false, true}
-			})
-			.method<&date_time::to_string>("__toString")
-			.method<&date_time::to_json>("__toJSON")
-			.method<&date_time::to_datetime>("__toDateTime")
-			.method<&date_time::unix>("unix")
-			.method<&date_time::unix_ms>("unix_ms")
-			.method<&date_time::to_json>("jsonSerialize")
-			.method<&date_time::to_json>("__debugInfo")
-			.method<&date_time::iso>("iso");
-		ext.add(std::move(class_date_time));
-	}
-	php::value date_time::__construct(php::parameters& params) {
-		if (params.size() > 0)
-        {
-			tm_ = params[0].to_integer();
-		}
-        else
-        {
-            // 默认以当前时间建立
-			tm_ = std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count();
-		}
-		return nullptr;
-	}
-	php::value date_time::to_string(php::parameters& params) {
-		return std::to_string(tm_);
-	}
-	php::value date_time::unix(php::parameters& params) {
-		return std::int64_t(tm_ / 1000);
-	}
-	php::value date_time::unix_ms(php::parameters& params) {
-		return tm_;
-	}
-	php::value date_time::to_datetime(php::parameters& params) {
-		return php::datetime(tm_);
-	}
-	php::value date_time::to_json(php::parameters& params) {
+namespace flame::mongodb {
+
+    void date_time::declare(php::extension_entry& ext) {
+        php::class_entry<date_time> class_date_time("flame\\mongodb\\date_time");
+        class_date_time
+            .implements(&php_json_serializable_ce)
+            .method<&date_time::__construct>("__construct", {
+                {"milliseconds", php::TYPE::INTEGER, false, true}
+            })
+            .method<&date_time::to_string>("__toString")
+            .method<&date_time::to_json>("__toJSON")
+            .method<&date_time::to_datetime>("__toDateTime")
+            .method<&date_time::unix>("unix")
+            .method<&date_time::unix_ms>("unix_ms")
+            .method<&date_time::to_json>("jsonSerialize")
+            .method<&date_time::to_json>("__debugInfo")
+            .method<&date_time::iso>("iso");
+        ext.add(std::move(class_date_time));
+    }
+
+    php::value date_time::__construct(php::parameters& params) {
+        if (params.size() > 0) tm_ = params[0].to_integer();
+        else // 默认以当前时间建立
+            tm_ = std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count();
+        return nullptr;
+    }
+
+    php::value date_time::to_string(php::parameters& params) {
+        return std::to_string(tm_);
+    }
+
+    php::value date_time::unix(php::parameters& params) {
+        return std::int64_t(tm_ / 1000);
+    }
+
+    php::value date_time::unix_ms(php::parameters& params) {
+        return tm_;
+    }
+
+    php::value date_time::to_datetime(php::parameters& params) {
+        return php::datetime(tm_);
+    }
+
+    php::value date_time::to_json(php::parameters& params) {
         // 自定义 JSON 形式 (保持和官方 MongoDB 驱动形式一致)
-		php::array ret(1), num(1);
-		num.set("$numberLong", std::to_string(tm_));
-		ret.set("$date", num);
-		return std::move(ret);
-	}
-	php::value date_time::iso(php::parameters& params) {
-		return time::iso(std::chrono::system_clock::time_point( std::chrono::milliseconds(tm_) ));
-	}
-}
+        php::array ret(1), num(1);
+        num.set("$numberLong", std::to_string(tm_));
+        ret.set("$date", num);
+        return std::move(ret);
+    }
+    
+    php::value date_time::iso(php::parameters& params) {
+        return time::iso(std::chrono::system_clock::time_point( std::chrono::milliseconds(tm_) ));
+    }
 }
diff --git a/src/mongodb/date_time.h b/src/mongodb/date_time.h
index 757e263..fa0f0f9 100644
--- a/src/mongodb/date_time.h
+++ b/src/mongodb/date_time.h
@@ -2,20 +2,20 @@
 #include "../vendor.h"
 
 namespace flame::mongodb {
-	class date_time: public php::class_base
-    {
-	public:
-		static void declare(php::extension_entry& ext);
-		php::value __construct(php::parameters& params);
-		php::value to_string(php::parameters& params);
-		php::value unix(php::parameters& params);
-		php::value unix_ms(php::parameters& params);
-		php::value to_datetime(php::parameters& params);
-		php::value to_json(php::parameters& params);
-		php::value iso(php::parameters& params);
-	private:
-		std::int64_t tm_;
-		friend php::value iter2value(bson_iter_t *i);
-		friend std::shared_ptr<bson_t> array2bson(const php::array &v);
-	};
+    
+    class date_time: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value to_string(php::parameters& params);
+        php::value unix(php::parameters& params);
+        php::value unix_ms(php::parameters& params);
+        php::value to_datetime(php::parameters& params);
+        php::value to_json(php::parameters& params);
+        php::value iso(php::parameters& params);
+    private:
+        std::int64_t tm_;
+        friend php::value iter2value(bson_iter_t *i);
+        friend std::shared_ptr<bson_t> array2bson(const php::array &v);
+    };
 }
diff --git a/src/mongodb/mongodb.h b/src/mongodb/mongodb.h
index 06a2f6a..8cd2bac 100644
--- a/src/mongodb/mongodb.h
+++ b/src/mongodb/mongodb.h
@@ -1,8 +1,7 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::mongodb
-{
+namespace flame::mongodb {
     void declare(php::extension_entry &ext);
     php::value connect(php::parameters &params);
     php::value iter2value(bson_iter_t *i);
diff --git a/src/mongodb/object_id.cpp b/src/mongodb/object_id.cpp
index 7160174..f48bf86 100644
--- a/src/mongodb/object_id.cpp
+++ b/src/mongodb/object_id.cpp
@@ -1,9 +1,8 @@
 #include "object_id.h"
 
-namespace flame::mongodb
-{
-    void object_id::declare(php::extension_entry &ext)
-    {
+namespace flame::mongodb {
+
+    void object_id::declare(php::extension_entry &ext) {
         php::class_entry<object_id> class_object_id("flame\\mongodb\\object_id");
         class_object_id
             .implements(&php_json_serializable_ce)
@@ -14,39 +13,36 @@ namespace flame::mongodb
             .method<&object_id::unix>("unix")
             .method<&object_id::to_json>("jsonSerialize")
             .method<&object_id::to_json>("__debugInfo")
-            .method<&object_id::equal>("equal",
-                                       {{"object_id", "?flame\\mongodb\\object_id"}});
+            .method<&object_id::equal>("equal",{
+                {"object_id", "?flame\\mongodb\\object_id"}
+            });
         ext.add(std::move(class_object_id));
     }
-    php::value object_id::__construct(php::parameters &params)
-    {
-        if (params.size() > 0)
-        {
+
+    php::value object_id::__construct(php::parameters &params) {
+        if (params.size() > 0) {
             php::string oid = params[0].to_string();
             bson_oid_init_from_string(&oid_, oid.c_str());
         }
-        else
-        {
-            bson_oid_init(&oid_, nullptr);
-        }
+        else bson_oid_init(&oid_, nullptr);
         return nullptr;
     }
-    php::value object_id::to_string(php::parameters &params)
-    {
+
+    php::value object_id::to_string(php::parameters &params) {
         php::string str(24);
         bson_oid_to_string(&oid_, str.data());
         return std::move(str);
     }
-    php::value object_id::unix(php::parameters &params)
-    {
+
+    php::value object_id::unix(php::parameters &params) {
         return bson_oid_get_time_t(&oid_);
     }
-    php::value object_id::to_datetime(php::parameters &params)
-    {
+
+    php::value object_id::to_datetime(php::parameters &params) {
         return php::datetime(bson_oid_get_time_t(&oid_) * 1000);
     }
-    php::value object_id::to_json(php::parameters &params)
-    {
+
+    php::value object_id::to_json(php::parameters &params) {
         // 定制 JSON 输出形式 (为何官方 MongoDB 驱动保持一致)
         php::array oid(1);
         php::string str(24);
@@ -54,24 +50,19 @@ namespace flame::mongodb
         oid.set("$oid", str);
         return std::move(oid);
     }
-    php::value object_id::equal(php::parameters &params)
-    {
-        if (params[0].typeof(php::TYPE::STRING))
-        {
+
+    php::value object_id::equal(php::parameters &params) {
+        if (params[0].typeof(php::TYPE::STRING)) {
             php::string data = params[0];
             bson_oid_t oid;
             bson_oid_init_from_string(&oid, data.c_str());
             return bson_oid_equal(&oid_, &oid);
         }
-        else if (params[0].instanceof (php::class_entry<object_id>::entry()))
-        {
+        else if (params[0].instanceof (php::class_entry<object_id>::entry())) {
             php::object obj = params[0];
             object_id *ptr = static_cast<object_id *>(php::native(obj));
             return bson_oid_equal(&oid_, &ptr->oid_);
         }
-        else
-        {
-            return false;
-        }
+        else return false;
     }
 } // namespace flame::mongodb
diff --git a/src/mongodb/object_id.h b/src/mongodb/object_id.h
index 459ac64..dddf6fb 100644
--- a/src/mongodb/object_id.h
+++ b/src/mongodb/object_id.h
@@ -2,19 +2,19 @@
 #include "../vendor.h"
 
 namespace flame::mongodb {
-	class object_id: public php::class_base
-    {
-	public:
-		static void declare(php::extension_entry& ext);
-		php::value __construct(php::parameters& params);
-		php::value to_string(php::parameters& params);
-		php::value unix(php::parameters& params);
-		php::value to_datetime(php::parameters& params);
-		php::value to_json(php::parameters& params);
-		php::value equal(php::parameters& params);
-	private:
-		bson_oid_t oid_;
-		friend php::value iter2value(bson_iter_t *i);
-		friend std::shared_ptr<bson_t> array2bson(const php::array &v);
-	};
+    
+    class object_id: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value to_string(php::parameters& params);
+        php::value unix(php::parameters& params);
+        php::value to_datetime(php::parameters& params);
+        php::value to_json(php::parameters& params);
+        php::value equal(php::parameters& params);
+    private:
+        bson_oid_t oid_;
+        friend php::value iter2value(bson_iter_t *i);
+        friend std::shared_ptr<bson_t> array2bson(const php::array &v);
+    };
 }
diff --git a/src/mutex.cpp b/src/mutex.cpp
index 996df58..518c8e1 100644
--- a/src/mutex.cpp
+++ b/src/mutex.cpp
@@ -2,58 +2,54 @@
 #include "coroutine_mutex.h"
 #include "mutex.h"
 
-namespace flame
-{
-    void mutex::declare(php::extension_entry &ext)
-    {
+namespace flame {
+    void mutex::declare(php::extension_entry &ext) {
         php::class_entry<mutex> class_mutex("flame\\mutex");
         class_mutex
-            .method<&mutex::__construct>("__construct",
-            {
+            .method<&mutex::__construct>("__construct", {
                 {"n", php::TYPE::INTEGER, false, true},
             })
             .method<&mutex::lock>("lock")
             .method<&mutex::unlock>("unlock");
         ext.add(std::move(class_mutex));
     }
-    php::value mutex::__construct(php::parameters& params)
-    {
+
+    php::value mutex::__construct(php::parameters& params) {
         mutex_.reset(new coroutine_mutex());
         return nullptr;
     }
-    php::value mutex::lock(php::parameters& params)
-    {
+
+    php::value mutex::lock(php::parameters& params) {
         coroutine_handler ch {coroutine::current};
         mutex_->lock(ch);
         return nullptr;
     }
-    php::value mutex::unlock(php::parameters& params)
-    {
+
+    php::value mutex::unlock(php::parameters& params) {
         mutex_->unlock();
         return nullptr;
     }
-    void guard::declare(php::extension_entry &ext)
-    {
+
+    void guard::declare(php::extension_entry &ext) {
         php::class_entry<guard> class_guard("flame\\guard");
         class_guard
-            .method<&guard::__construct>("__construct",
-            {
+            .method<&guard::__construct>("__construct", {
                 {"mutex", "flame\\mutex"},
             })
             .method<&guard::__destruct>("__destruct");
         ext.add(std::move(class_guard));
     }
-    php::value guard::__construct(php::parameters & params)
-    {
+
+    php::value guard::__construct(php::parameters & params) {
         php::object obj = params[0];
         mutex_ = static_cast<mutex*>(php::native(obj))->mutex_;
         coroutine_handler ch {coroutine::current};
         mutex_->lock(ch);
         return nullptr;
     }
-    php::value guard::__destruct(php::parameters & params)
-    {
+
+    php::value guard::__destruct(php::parameters & params) {
         mutex_->unlock();
         return nullptr;
     }
-}
\ No newline at end of file
+}
diff --git a/src/mutex.h b/src/mutex.h
index 66969d6..dc3c04e 100644
--- a/src/mutex.h
+++ b/src/mutex.h
@@ -1,11 +1,10 @@
 #pragma once
 #include "vendor.h"
 
-namespace flame
-{
+namespace flame {
+
     class coroutine_mutex;
-    class mutex: public php::class_base
-    {
+    class mutex: public php::class_base {
     public:
         static void declare(php::extension_entry& ext);
         php::value __construct(php::parameters& params);
@@ -17,8 +16,7 @@ namespace flame
         friend class guard;
     };
 
-    class guard: public php::class_base
-    {
+    class guard: public php::class_base {
     public:
         static void declare(php::extension_entry& ext);
         php::value __construct(php::parameters& params);
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 6e9eb5d..30fa651 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -17,10 +17,10 @@ namespace flame::mysql {
             break;
         // case IS_LONG:
         // case IS_DOUBLE: {
-        // 	php::string str = v;
-        // 	str.to_string();
-        // 	b.append(str);
-        // 	break;
+        //  php::string str = v;
+        //  str.to_string();
+        //  b.append(str);
+        //  break;
         // }
         case IS_STRING: {
             php::string str = v;
diff --git a/src/mysql/_connection_base.h b/src/mysql/_connection_base.h
index bf61930..b608287 100644
--- a/src/mysql/_connection_base.h
+++ b/src/mysql/_connection_base.h
@@ -2,11 +2,9 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 
-namespace flame::mysql
-{
+namespace flame::mysql {
 
-    class _connection_base
-    {
+    class _connection_base {
     public:
         // 此函数仅允许 escape 主线程调用
         static void escape(std::shared_ptr<MYSQL> c, php::buffer &b, const php::value &v, char quote = '\''); // 方便使用
diff --git a/src/mysql/_connection_lock.cpp b/src/mysql/_connection_lock.cpp
index 5e9cb4c..ffedba8 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/mysql/_connection_lock.cpp
@@ -3,12 +3,10 @@
 #include "_connection_base.h"
 #include "_connection_lock.h"
 
-namespace flame::mysql
-{
+namespace flame::mysql {
 
     _connection_lock::_connection_lock(std::shared_ptr<MYSQL> c)
     : conn_(c) {
-        
     }
 
     _connection_lock::~_connection_lock() {
diff --git a/src/mysql/_connection_lock.h b/src/mysql/_connection_lock.h
index 2a04ff3..6af7ac6 100644
--- a/src/mysql/_connection_lock.h
+++ b/src/mysql/_connection_lock.h
@@ -3,10 +3,8 @@
 #include "../coroutine.h"
 #include "_connection_base.h"
 
-namespace flame::mysql
-{
-    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock>
-    {
+namespace flame::mysql {
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> {
     public:
         _connection_lock(std::shared_ptr<MYSQL> c);
         ~_connection_lock();
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index e962b40..c5d12c0 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -1,8 +1,7 @@
 #include "../controller.h"
 #include "_connection_pool.h"
 
-namespace flame::mysql
-{
+namespace flame::mysql {
     _connection_pool::_connection_pool(url u)
     : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     , flag_(FLAG_UNKNOWN) {
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index 5b26fcf..6adb47e 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -4,11 +4,9 @@
 #include "../url.h"
 #include "_connection_base.h"
 
+namespace flame::mysql {
 
-namespace flame::mysql
-{
-    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool>
-    {
+    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool> {
     public:
         _connection_pool(url u);
         ~_connection_pool();
@@ -22,8 +20,7 @@ namespace flame::mysql
 
         boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题;
         std::list<std::function<void(std::shared_ptr<MYSQL>)>> await_;
-        struct connection_t
-        {
+        struct connection_t {
             MYSQL *conn;
             std::chrono::time_point<std::chrono::steady_clock> ttl;
         };
@@ -45,5 +42,4 @@ namespace flame::mysql
         void query_charset(MYSQL* c);
         void query_version(MYSQL* c);
     };
-
 } // namespace flame::mysql
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index 15166ed..cfa0f61 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -12,8 +12,7 @@ namespace flame::mysql {
         class_client
             .method<&client::__construct>("__construct", {}, php::PRIVATE)
             .method<&client::__destruct>("__destruct")
-            .method<&client::escape>("escape",
-            {
+            .method<&client::escape>("escape", {
                 {"data", php::TYPE::UNDEFINED},
             })
             .method<&client::begin_tx>("begin_tx")
diff --git a/src/mysql/client.h b/src/mysql/client.h
index 901936d..ad379bc 100644
--- a/src/mysql/client.h
+++ b/src/mysql/client.h
@@ -1,11 +1,10 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::mysql
-{
+namespace flame::mysql {
+
     class _connection_pool;
-    class client : public php::class_base
-    {
+    class client : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params);
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index fd02cde..546382f 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -38,7 +38,7 @@ namespace flame::mysql {
         ptr->cp_->sweep(); // 启动自动清理扫描
         // TODO 优化: 确认第一个连接建立 ?
         return std::move(obj);
-	}
+    }
     // 相等
     static void where_eq(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
         if (cond.typeof(php::TYPE::NULLABLE)) buf.append(" IS NULL", 8);
@@ -156,8 +156,7 @@ namespace flame::mysql {
 
             if (i->first.typeof(php::TYPE::INTEGER))  {
                 if (i->second.typeof(php::TYPE::ARRAY)) where_ex(cc, buf, i->second, i->first, " AND ");
-                else
-                {
+                else {
                     php::string str = i->second;
                     str.to_string();
                     buf.append(str);
diff --git a/src/mysql/mysql.h b/src/mysql/mysql.h
index 1786d99..8e65b48 100644
--- a/src/mysql/mysql.h
+++ b/src/mysql/mysql.h
@@ -1,7 +1,8 @@
 #pragma once
+#include "../vendor.h"
+
+namespace flame::mysql {
 
-namespace flame::mysql
-{
     void declare(php::extension_entry &ext);
     php::value connect(php::parameters& params);
 
diff --git a/src/mysql/result.h b/src/mysql/result.h
index 906da0f..9197656 100644
--- a/src/mysql/result.h
+++ b/src/mysql/result.h
@@ -1,10 +1,10 @@
 #pragma once
+#include "../vendor.h"
+
+namespace flame::mysql {
 
-namespace flame::mysql
-{
     class _connection_lock;
-    class result: public php::class_base
-    {
+    class result: public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value fetch_row(php::parameters &params);
diff --git a/src/mysql/tx.cpp b/src/mysql/tx.cpp
index 2a96b50..bd18a03 100644
--- a/src/mysql/tx.cpp
+++ b/src/mysql/tx.cpp
@@ -3,60 +3,50 @@
 #include "_connection_lock.h"
 #include "mysql.h"
 
-namespace flame::mysql
-{
-    void tx::declare(php::extension_entry &ext)
-    {
+namespace flame::mysql {
+    void tx::declare(php::extension_entry &ext) {
         php::class_entry<tx> class_tx("flame\\mysql\\tx");
         class_tx
             .constant({"AAAA", 123})
             .method<&tx::__construct>("__construct", {}, php::PRIVATE)
-            .method<&tx::escape>("escape",
-            {
+            .method<&tx::escape>("escape", {
                 {"data", php::TYPE::UNDEFINED},
             })
             .method<&tx::commit>("commit")
             .method<&tx::rollback>("rollback")
-            .method<&tx::query>("query",
-            {
+            .method<&tx::query>("query", {
                 {"sql", php::TYPE::STRING},
             })
-            .method<&tx::insert>("insert",
-            {
+            .method<&tx::insert>("insert", {
                 {"table", php::TYPE::STRING},
                 {"rows", php::TYPE::ARRAY},
             })
-            .method<&tx::delete_>("delete",
-            {
+            .method<&tx::delete_>("delete", {
                 {"table", php::TYPE::STRING},
                 {"where", php::TYPE::UNDEFINED},
                 {"order", php::TYPE::UNDEFINED, false, true},
                 {"limit", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&tx::update>("update",
-            {
+            .method<&tx::update>("update", {
                 {"table", php::TYPE::STRING},
                 {"where", php::TYPE::UNDEFINED},
                 {"set", php::TYPE::UNDEFINED},
                 {"order", php::TYPE::UNDEFINED, false, true},
                 {"limit", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&tx::select>("select",
-            {
+            .method<&tx::select>("select", {
                 {"table", php::TYPE::STRING},
                 {"fields", php::TYPE::UNDEFINED},
                 {"where", php::TYPE::UNDEFINED},
                 {"order", php::TYPE::UNDEFINED, false, true},
                 {"limit", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&tx::one>("one",
-            {
+            .method<&tx::one>("one", {
                 {"table", php::TYPE::STRING},
                 {"where", php::TYPE::UNDEFINED},
                 {"order", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&tx::get>("get",
-            {
+            .method<&tx::get>("get", {
                 {"table", php::TYPE::STRING},
                 {"field", php::TYPE::STRING},
                 {"where", php::TYPE::UNDEFINED},
@@ -64,78 +54,76 @@ namespace flame::mysql
             });
         ext.add(std::move(class_tx));
     }
-    php::value tx::__construct(php::parameters &params)
-    {
+
+    php::value tx::__construct(php::parameters &params) {
         return nullptr;
     }
-    php::value tx::escape(php::parameters &params)
-    {
+
+    php::value tx::escape(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         std::shared_ptr<MYSQL> conn = cl_->acquire(ch);
 
         php::buffer buffer;
         char quote = '\'';
-        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING))
-        {
+        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
             php::string q = params[1];
-            if (q.data()[0] == '`')
-                quote = '`';
+            if (q.data()[0] == '`') quote = '`';
         }
         _connection_base::escape(conn, buffer, params[0], quote);
         return std::move(buffer);
     }
-    php::value tx::commit(php::parameters &params)
-    {
+
+    php::value tx::commit(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         cl_->commit(ch);
         return nullptr;
     }
-    php::value tx::rollback(php::parameters &params)
-    {
+
+    php::value tx::rollback(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         cl_->rollback(ch);
         return nullptr;
     }
-    php::value tx::query(php::parameters &params)
-    {
+
+    php::value tx::query(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         std::shared_ptr<MYSQL> conn = cl_->acquire(ch);
         return cl_->query(conn, params[0], ch);
     }
-    php::value tx::insert(php::parameters &params)
-    {
+
+    php::value tx::insert(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cl_->acquire(ch);
         php::buffer buf;
         build_insert(conn, buf, params);
         return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value tx::delete_(php::parameters &params)
-    {
+
+    php::value tx::delete_(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cl_->acquire(ch);
         php::buffer buf;
         build_delete(conn, buf, params);
         return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value tx::update(php::parameters &params)
-    {
+
+    php::value tx::update(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cl_->acquire(ch);
         php::buffer buf;
         build_update(conn, buf, params);
         return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value tx::select(php::parameters &params)
-    {
+
+    php::value tx::select(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cl_->acquire(ch);
         php::buffer buf;
         build_select(conn, buf, params);
         return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
     }
-    php::value tx::one(php::parameters &params)
-    {
+
+    php::value tx::one(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cl_->acquire(ch);
         php::buffer buf;
@@ -143,22 +131,15 @@ namespace flame::mysql
         php::object rst = cl_->query(conn, std::string(buf.data(), buf.size()), ch);
         return rst.call("fetch_row");
     }
-    php::value tx::get(php::parameters &params)
-    {
+
+    php::value tx::get(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         auto conn = cl_->acquire(ch);
         php::buffer buf;
         build_get(conn, buf, params);
         php::object rst = cl_->query(conn, std::string(buf.data(), buf.size()), ch);
         php::array row = rst.call("fetch_row");
-        if (!row.empty())
-        {
-            return row.get( static_cast<php::string>(params[1]) );
-        }
-        else
-        {
-            return nullptr;
-        }
+        if (!row.empty()) return row.get( static_cast<php::string>(params[1]) );
+        else return nullptr;
     }
-
 } // namespace flame::mysql
diff --git a/src/mysql/tx.h b/src/mysql/tx.h
index 2c70952..d6088cf 100644
--- a/src/mysql/tx.h
+++ b/src/mysql/tx.h
@@ -1,12 +1,10 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::mysql
-{
+namespace flame::mysql {
 
     class _connection_lock;
-    class tx : public php::class_base
-    {
+    class tx : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
@@ -27,5 +25,4 @@ namespace flame::mysql
         std::shared_ptr<_connection_lock> cl_;
         friend class client;
     };
-
 } // namespace flame::mysql
diff --git a/src/os/os.h b/src/os/os.h
index aaf41ce..41f7ca6 100644
--- a/src/os/os.h
+++ b/src/os/os.h
@@ -1,8 +1,8 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::os
-{
+namespace flame::os {
+    
     void declare(php::extension_entry &ext);
     php::value interfaces(php::parameters &params);
     php::value spawn(php::parameters &params);
diff --git a/src/os/process.h b/src/os/process.h
index e24920f..03abff3 100644
--- a/src/os/process.h
+++ b/src/os/process.h
@@ -3,24 +3,24 @@
 #include "../coroutine.h"
 
 namespace flame::os {
-	class process: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value kill(php::parameters& params);
-		php::value wait(php::parameters& params);
-		php::value detach(php::parameters& params);
-		php::value stdout(php::parameters& params);
-		php::value stderr(php::parameters& params);
-	private:
-		boost::process::child      c_;
+    class process: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value __destruct(php::parameters& params);
+        php::value kill(php::parameters& params);
+        php::value wait(php::parameters& params);
+        php::value detach(php::parameters& params);
+        php::value stdout(php::parameters& params);
+        php::value stderr(php::parameters& params);
+    private:
+        boost::process::child      c_;
         coroutine_handler         ch_;
-		std::future<std::string> out_;
-		std::future<std::string> err_;
-		bool exit_ = false;
-		bool detach_ = false;
-		friend class php::value spawn(php::parameters& params);
-		friend class php::value exec(php::parameters& params);
-	};
+        std::future<std::string> out_;
+        std::future<std::string> err_;
+        bool exit_ = false;
+        bool detach_ = false;
+        friend class php::value spawn(php::parameters& params);
+        friend class php::value exec(php::parameters& params);
+    };
 }
diff --git a/src/queue.h b/src/queue.h
index 59fb0d5..8beae75 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -2,14 +2,13 @@
 #include "vendor.h"
 #include "coroutine_queue.h"
 
-namespace flame
-{
-    namespace core
-    {
+namespace flame {
+
+    namespace core {
         php::value select(php::parameters &params);
     }
-    class queue: public php::class_base
-    {
+
+    class queue: public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters& params);
diff --git a/src/rabbitmq/_client.cpp b/src/rabbitmq/_client.cpp
index 825b542..38adb46 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/rabbitmq/_client.cpp
@@ -3,8 +3,8 @@
 #include "_client.h"
 #include "message.h"
 
-namespace flame::rabbitmq
-{
+namespace flame::rabbitmq {
+    
     static bool str2bool(const char *str){
         return strncasecmp(str, "1", 1) == 0 || strncasecmp(str, "yes", 3) == 0 ||
             strncasecmp(str, "yes", 3) == 0 || strncasecmp(str, "true", 4);
@@ -179,8 +179,7 @@ namespace flame::rabbitmq
     //     }
     // }
 
-    void _client::publish(const std::string &ex, const std::string &rk, const char *msg, size_t len, coroutine_handler& ch)
-    {
+    void _client::publish(const std::string &ex, const std::string &rk, const char *msg, size_t len, coroutine_handler& ch) {
         /*auto& defer = */chn_.publish(ex, rk, msg, len, fl_);
         // producer_cb(defer, ch);
         if (!error_.empty()) {
diff --git a/src/rabbitmq/_client.h b/src/rabbitmq/_client.h
index 47d3817..6cc1edf 100644
--- a/src/rabbitmq/_client.h
+++ b/src/rabbitmq/_client.h
@@ -4,10 +4,9 @@
 #include "../url.h"
 #include "../coroutine_queue.h"
 
-namespace flame::rabbitmq
-{
-    class _client
-    {
+namespace flame::rabbitmq {
+
+    class _client {
     public:
         _client(url u, coroutine_handler& ch);
         
diff --git a/src/rabbitmq/consumer.cpp b/src/rabbitmq/consumer.cpp
index 0c62de2..8d9933e 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/rabbitmq/consumer.cpp
@@ -5,8 +5,8 @@
 #include "message.h"
 #include "../time/time.h"
 
-namespace flame::rabbitmq 
-{
+namespace flame::rabbitmq  {
+    
     void consumer::declare(php::extension_entry &ext) {
         php::class_entry<consumer> class_consumer("flame\\rabbitmq\\consumer");
         class_consumer
diff --git a/src/rabbitmq/consumer.h b/src/rabbitmq/consumer.h
index 6fd206c..5d4338e 100644
--- a/src/rabbitmq/consumer.h
+++ b/src/rabbitmq/consumer.h
@@ -1,11 +1,10 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::rabbitmq
-{
+namespace flame::rabbitmq {
+
     class _client;
-    class consumer : public php::class_base
-    {
+    class consumer : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
diff --git a/src/rabbitmq/message.cpp b/src/rabbitmq/message.cpp
index e925098..8ebe499 100644
--- a/src/rabbitmq/message.cpp
+++ b/src/rabbitmq/message.cpp
@@ -2,10 +2,9 @@
 #include "rabbitmq.h"
 #include "message.h"
 
-namespace flame::rabbitmq
-{
-    void message::declare(php::extension_entry &ext)
-    {
+namespace flame::rabbitmq {
+
+    void message::declare(php::extension_entry &ext) {
         php::class_entry<message> class_message("flame\\rabbitmq\\message");
         class_message
             .implements(&php_json_serializable_ce)
@@ -25,8 +24,7 @@ namespace flame::rabbitmq
             .property({"type_name", ""})
             .property({"timestamp", 0})
             .property({"message_id", ""})
-            .method<&message::__construct>("__construct",
-            {
+            .method<&message::__construct>("__construct", {
                 {"body", php::TYPE::STRING, false, true},
             })
             .method<&message::to_string>("__toString")
@@ -34,110 +32,75 @@ namespace flame::rabbitmq
 
         ext.add(std::move(class_message));
     }
-    php::value message::__construct(php::parameters &params)
-    {
-        if (params.size() > 0)
-        {
-            set("body", params[0]);
-        }
-        if (params.size() > 1)
-        {
-            set("routing_key", params[1]);
-        }
+
+    php::value message::__construct(php::parameters &params) {
+        if (params.size() > 0) set("body", params[0]);
+        if (params.size() > 1) set("routing_key", params[1]);
         return nullptr;
     }
-    php::value message::to_json(php::parameters &params)
-    {
+
+    php::value message::to_json(php::parameters &params) {
         php::array data(16);
         data.set("routing_key", get("routing_key"));
         data.set("body", get("body"));
         data.set("timestamp", get("timestamp"));
         return std::move(data);
     }
-    php::value message::to_string(php::parameters &params)
-    {
+
+    php::value message::to_string(php::parameters &params) {
         return get("body");
     }
-    void message::build_ex(const AMQP::Message &msg, std::uint64_t tag)
-    {
+
+    void message::build_ex(const AMQP::Message &msg, std::uint64_t tag) {
         tag_ = tag;
         set("routing_key", msg.routingkey());
         set("body", php::string(msg.body(), msg.bodySize()));
-        if (msg.hasExpiration())
-            set("expiration", msg.expiration());
-        if (msg.hasReplyTo())
-            set("reply_to", msg.replyTo());
-        if (msg.hasCorrelationID())
-            set("correlation_id", msg.correlationID());
-        if (msg.hasPriority())
-            set("priority", msg.priority());
-        if (msg.hasDeliveryMode())
-            set("delivery_mode", msg.deliveryMode());
-        if (msg.hasHeaders())
-            set("header", table2array(msg.headers()));
-        if (msg.hasContentEncoding())
-            set("content_encoding", msg.contentEncoding());
-        if (msg.hasContentType())
-            set("content_type", msg.contentType());
-        if (msg.hasClusterID())
-            set("cluster_id", msg.clusterID());
-        if (msg.hasAppID())
-            set("app_id", msg.appID());
-        if (msg.hasUserID())
-            set("user_id", msg.userID());
-        if (msg.hasTypeName())
-            set("type_name", msg.typeName());
-        if (msg.hasTimestamp())
-            set("timestamp", msg.timestamp());
-        if (msg.hasMessageID())
-            set("message_id", msg.messageID());
+        if (msg.hasExpiration()) set("expiration", msg.expiration());
+        if (msg.hasReplyTo()) set("reply_to", msg.replyTo());
+        if (msg.hasCorrelationID()) set("correlation_id", msg.correlationID());
+        if (msg.hasPriority()) set("priority", msg.priority());
+        if (msg.hasDeliveryMode()) set("delivery_mode", msg.deliveryMode());
+        if (msg.hasHeaders()) set("header", table2array(msg.headers()));
+        if (msg.hasContentEncoding()) set("content_encoding", msg.contentEncoding());
+        if (msg.hasContentType()) set("content_type", msg.contentType());
+        if (msg.hasClusterID()) set("cluster_id", msg.clusterID());
+        if (msg.hasAppID()) set("app_id", msg.appID());
+        if (msg.hasUserID()) set("user_id", msg.userID());
+        if (msg.hasTypeName()) set("type_name", msg.typeName());
+        if (msg.hasTimestamp()) set("timestamp", msg.timestamp());
+        if (msg.hasMessageID()) set("message_id", msg.messageID());
     }
-    void message::build_ex(AMQP::Envelope &env)
-    {
+
+    void message::build_ex(AMQP::Envelope &env) {
         php::string v;
 
         v = get("expiration");
-        if (!v.empty())
-            env.setExpiration(v.to_string());
+        if (!v.empty()) env.setExpiration(v.to_string());
         v = get("reply_to");
-        if (!v.empty())
-            env.setReplyTo(v.to_string());
+        if (!v.empty()) env.setReplyTo(v.to_string());
         v = get("correlation_id");
-        if (!v.empty())
-            env.setCorrelationID(v.to_string());
+        if (!v.empty()) env.setCorrelationID(v.to_string());
         v = get("priority");
-        if (!v.empty())
-            env.setPriority(v.to_integer());
+        if (!v.empty()) env.setPriority(v.to_integer());
         v = get("delivery_mode");
-        if (!v.empty())
-            env.setDeliveryMode(v.to_integer());
+        if (!v.empty()) env.setDeliveryMode(v.to_integer());
         v = get("header");
-        if (!v.empty())
-            env.setHeaders(array2table(v));
+        if (!v.empty()) env.setHeaders(array2table(v));
         v = get("content_encoding");
-        if (!v.empty())
-            env.setContentEncoding(v.to_string());
+        if (!v.empty()) env.setContentEncoding(v.to_string());
         v = get("content_type");
-        if (!v.empty())
-            env.setContentType(v.to_string());
+        if (!v.empty()) env.setContentType(v.to_string());
         v = get("cluster_id");
-        if (!v.empty())
-            env.setClusterID(v.to_string());
+        if (!v.empty()) env.setClusterID(v.to_string());
         v = get("app_id");
-        if (!v.empty())
-            env.setAppID(v.to_string());
+        if (!v.empty()) env.setAppID(v.to_string());
         v = get("user_id");
-        if (!v.empty())
-            env.setUserID(v.to_string());
+        if (!v.empty()) env.setUserID(v.to_string());
         v = get("type_name");
-        if (!v.empty())
-            env.setTypeName(v.to_string());
+        if (!v.empty()) env.setTypeName(v.to_string());
         v = get("timestamp");
-        if (!v.empty())
-            env.setTimestamp(v.to_integer());
+        if (!v.empty()) env.setTimestamp(v.to_integer());
         v = get("message_id");
-        if (!v.empty())
-            env.setMessageID(v.to_string());
+        if (!v.empty()) env.setMessageID(v.to_string());
     }
-
 } // namespace flame::rabbitmq
diff --git a/src/rabbitmq/message.h b/src/rabbitmq/message.h
index ad7e222..13ea716 100644
--- a/src/rabbitmq/message.h
+++ b/src/rabbitmq/message.h
@@ -1,10 +1,9 @@
 #pragma once
+#include "../vendor.h"
 
-namespace flame::rabbitmq
-{
+namespace flame::rabbitmq {
 
-    class message : public php::class_base
-    {
+    class message : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
@@ -15,5 +14,4 @@ namespace flame::rabbitmq
         void build_ex(AMQP::Envelope &env);
         std::uint64_t tag_;
     };
-    
 } // namespace flame::rabbitmq
diff --git a/src/rabbitmq/producer.cpp b/src/rabbitmq/producer.cpp
index 24fd158..440b24f 100644
--- a/src/rabbitmq/producer.cpp
+++ b/src/rabbitmq/producer.cpp
@@ -3,51 +3,43 @@
 #include "_client.h"
 #include "message.h"
 
-namespace flame::rabbitmq
-{
-    void producer::declare(php::extension_entry &ext)
-    {
+namespace flame::rabbitmq {
+
+    void producer::declare(php::extension_entry &ext) {
         php::class_entry<producer> class_producer("flame\\rabbitmq\\producer");
         class_producer
             .method<&producer::__construct>("__construct", {}, php::PRIVATE)
-            .method<&producer::publish>("publish",
-            {
+            .method<&producer::publish>("publish", {
                 {"exchange", php::TYPE::STRING},
                 {"message", php::TYPE::UNDEFINED},
             });
         ext.add(std::move(class_producer));
     }
-    php::value producer::__construct(php::parameters &params)
-    {
+
+    php::value producer::__construct(php::parameters &params) {
         return nullptr;
     }
-    php::value producer::publish(php::parameters &params)
-    {
+
+    php::value producer::publish(php::parameters &params) {
         std::string exch = params[0];
         std::string routing_key;
-        if (params.size() > 2)
-        {
-            routing_key = params[2].to_string();
-        }
-
+        if (params.size() > 2) routing_key = params[2].to_string();
+        
         coroutine_handler ch{coroutine::current};
-        if (params[1].instanceof(php::class_entry<message>::entry()))
-        {
+        if (params[1].instanceof(php::class_entry<message>::entry())) {
             php::object msg = params[1];
             message *msg_ = static_cast<message *>(php::native(msg));
             php::string body = msg.get("body");
             body.to_string();
             AMQP::Envelope env(body.c_str(), body.size());
             msg_->build_ex(env);
-            if (routing_key.empty())
-            { // 未指定时使用 message 默认的 routing_key
-                routing_key = msg.get("routing_key").to_string();
-            }
+            // 未指定时使用 message 默认的 routing_key
+            if (routing_key.empty()) routing_key = msg.get("routing_key").to_string();
+
             coroutine_handler ch {coroutine::current};
             cc_->publish(exch, routing_key, env, ch);
         }
-        else
-        {
+        else {
             php::string msg = params[1];
             msg.to_string();
             cc_->publish(exch, routing_key, msg.c_str(), msg.size(), ch);
diff --git a/src/rabbitmq/producer.h b/src/rabbitmq/producer.h
index 975b665..a66817d 100644
--- a/src/rabbitmq/producer.h
+++ b/src/rabbitmq/producer.h
@@ -3,16 +3,16 @@
 
 namespace flame::rabbitmq {
     class _client;
-	class producer: public php::class_base {
-	public:
-		static void declare(php::extension_entry& ext);
-		php::value __construct(php::parameters& params); // 私有
-		php::value publish(php::parameters& params);
-	private:
-		// 实际的客户端对象可能超过当前对象的生存期
-		std::shared_ptr<_client> cc_;
-		
-		friend php::value produce(php::parameters& params);
-		friend class _client;
-	};
+    class producer: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params); // 私有
+        php::value publish(php::parameters& params);
+    private:
+        // 实际的客户端对象可能超过当前对象的生存期
+        std::shared_ptr<_client> cc_;
+        
+        friend php::value produce(php::parameters& params);
+        friend class _client;
+    };
 } // namespace flame::rabbitmq
diff --git a/src/rabbitmq/rabbitmq.h b/src/rabbitmq/rabbitmq.h
index 5ab1147..6d008fe 100644
--- a/src/rabbitmq/rabbitmq.h
+++ b/src/rabbitmq/rabbitmq.h
@@ -1,12 +1,12 @@
 #pragma once
+#include "../vendor.h"
+
+namespace flame::rabbitmq {
 
-namespace flame::rabbitmq
-{
     void declare(php::extension_entry &ext);
     php::value consume(php::parameters &params);
     php::value produce(php::parameters &params);
 
     php::array  table2array(const AMQP::Table &table);
     AMQP::Table array2table(const php::array &table);
-    
-} // namespace flame::rabbitmq
\ No newline at end of file
+} // namespace flame::rabbitmq
diff --git a/src/redis/_connection_base.h b/src/redis/_connection_base.h
index d2b018e..92e61e7 100644
--- a/src/redis/_connection_base.h
+++ b/src/redis/_connection_base.h
@@ -2,10 +2,9 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 
-namespace flame::redis
-{
-    enum class reply_type
-    {
+namespace flame::redis {
+
+    enum class reply_type {
         SIMPLE        = 0x00,  // 默认返回处理
         ASSOC_ARRAY_1 = 0x01,  // 返回数据多项按 KEY VAL 生成关联数组
         ASSOC_ARRAY_2 = 0x02,  // 第一层普通数组, 第二层关联数组 (HSCAN/ZSCAN)
@@ -14,8 +13,8 @@ namespace flame::redis
         EXEC          = 0x100, // 事务特殊处理方式（需要结合上面几种方式）
         PIPE          = 0x200, // 与事务形式相同
     };
-    class _connection_base
-    {
+
+    class _connection_base {
     public:
         virtual std::shared_ptr<redisContext> acquire(coroutine_handler &ch) = 0;
         php::value reply2value(redisReply *rp, php::array &argv, reply_type rt);
diff --git a/src/redis/_connection_lock.cpp b/src/redis/_connection_lock.cpp
index d4807da..5caf500 100644
--- a/src/redis/_connection_lock.cpp
+++ b/src/redis/_connection_lock.cpp
@@ -3,45 +3,38 @@
 #include "_connection_base.h"
 #include "_connection_lock.h"
 
-namespace flame::redis
-{
+namespace flame::redis {
 
     _connection_lock::_connection_lock(std::shared_ptr<redisContext> c)
-        : conn_(c)
-    {
+    : conn_(c) {
     }
 
-    _connection_lock::~_connection_lock()
-    {
+    _connection_lock::~_connection_lock() {
     }
 
-    std::shared_ptr<redisContext> _connection_lock::acquire(coroutine_handler &ch)
-    {
+    std::shared_ptr<redisContext> _connection_lock::acquire(coroutine_handler &ch) {
         return conn_;
     }
 
-    void _connection_lock::push(php::string &name, php::array &argv, reply_type type)
-    {
+    void _connection_lock::push(php::string &name, php::array &argv, reply_type type) {
         // 暂未提交, 仅记录
         // TODO 优化: 是否可以在提交前在进行 format 操作 (减少内存占用的持续时间) ?
         cmds_.push_back({name, argv, type, format(name, argv)});
     }
+
     void _connection_lock::push(php::string &name, php::parameters& params, reply_type type) {
         php::array argv {params};
         push(name, argv, type);
     }
-    php::array _connection_lock::exec(coroutine_handler &ch) 
-    {
-        boost::asio::post(gcontroller->context_x, [this, &ch] ()
-        {
+
+    php::array _connection_lock::exec(coroutine_handler &ch)  {
+        boost::asio::post(gcontroller->context_x, [this, &ch] () {
             // 在工作线程中, 提交所有待执行命令
-            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) {
+            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i)
                 redisAppendFormattedCommand(conn_.get(), i->strs.c_str(), i->strs.size());
-            }
             // 读取对应的返回值
-            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) {
+            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) 
                 redisGetReply(conn_.get(), (void**)&i->reply);
-            }
             // 回到主线程
             ch.resume();
         });
@@ -57,5 +50,4 @@ namespace flame::redis
         cmds_.clear();
         return std::move(data);
     }
-
 } // namespace flame::redis
diff --git a/src/redis/_connection_lock.h b/src/redis/_connection_lock.h
index f71c8f9..aff88da 100644
--- a/src/redis/_connection_lock.h
+++ b/src/redis/_connection_lock.h
@@ -3,26 +3,25 @@
 #include "../coroutine.h"
 #include "_connection_base.h"
 
-namespace flame::redis
-{
-class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock>
-{
-  public:
-    _connection_lock(std::shared_ptr<redisContext> c);
-    ~_connection_lock();
-    std::shared_ptr<redisContext> acquire(coroutine_handler &ch) override;
-    void push(php::string &name, php::array &argv, reply_type rt);
-    void push(php::string &name, php::parameters &argv, reply_type rt);
-    php::array exec(coroutine_handler& ch);
-    struct command_t {
-        php::string name;
-        php::array  argv;
-        reply_type  type;
-        std::string strs;
-        redisReply* reply;
+namespace flame::redis {
+
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> {
+    public:
+        _connection_lock(std::shared_ptr<redisContext> c);
+        ~_connection_lock();
+        std::shared_ptr<redisContext> acquire(coroutine_handler &ch) override;
+        void push(php::string &name, php::array &argv, reply_type rt);
+        void push(php::string &name, php::parameters &argv, reply_type rt);
+        php::array exec(coroutine_handler& ch);
+        struct command_t {
+            php::string name;
+            php::array  argv;
+            reply_type  type;
+            std::string strs;
+            redisReply* reply;
+        };
+    private:
+        std::shared_ptr<redisContext> conn_;
+        std::list<command_t> cmds_;
     };
-  private:
-    std::shared_ptr<redisContext> conn_;
-    std::list<command_t> cmds_;
-};
 } // namespace flame::redis
diff --git a/src/redis/_connection_pool.h b/src/redis/_connection_pool.h
index 6898a13..65ab021 100644
--- a/src/redis/_connection_pool.h
+++ b/src/redis/_connection_pool.h
@@ -5,10 +5,8 @@
 #include "_connection_base.h"
 
 
-namespace flame::redis
-{
-    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool>
-    {
+namespace flame::redis {
+    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool> {
     public:
         _connection_pool(url u);
         ~_connection_pool();
@@ -24,8 +22,7 @@ namespace flame::redis
 
         boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题;
         std::list<std::function<void(std::shared_ptr<redisContext>)>> await_;
-        struct connection_t
-        {
+        struct connection_t {
             redisContext *conn;
             std::chrono::time_point<std::chrono::steady_clock> ttl;
         };
@@ -36,5 +33,4 @@ namespace flame::redis
         redisContext *create(std::string& err);
         void release(redisContext *c);
     };
-
 } // namespace flame::mysql
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index de9732d..bd86fb6 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -5,177 +5,177 @@
 #include "_connection_lock.h"
 
 namespace flame::redis {
-	void client::declare(php::extension_entry& ext) {
-		php::class_entry<client> class_client("flame\\redis\\client");
-		class_client
-			.method<&client::__construct>("__construct", {}, php::PRIVATE)
-			.method<&client::__call>("__call", {
-				{"cmd", php::TYPE::STRING},
-				{"arg", php::TYPE::ARRAY},
-			})
-			.method<&client::__isset>("__isset", {
-				{"cmd", php::TYPE::STRING},
-			})
-			.method<&client::mget>("mget", {
-				{"key", php::TYPE::STRING},
-			})
-			.method<&client::hmget>("hmget", {
-				{"hash", php::TYPE::STRING},
-			})
-			.method<&client::hgetall>("hgetall", {
-				{"hash", php::TYPE::STRING},
-			})
-			.method<&client::hscan>("hscan", {
-				{"hash", php::TYPE::STRING},
-				{"cursor", php::TYPE::INTEGER},
-			})
-			.method<&client::zscan>("zscan", {
-				{"zset", php::TYPE::STRING},
-				{"cursor", php::TYPE::INTEGER},
-			})
-			.method<&client::zrange>("zrange", {
-				{"zset", php::TYPE::STRING},
-				{"start", php::TYPE::INTEGER},
-				{"stop", php::TYPE::INTEGER},
-			})
-			.method<&client::zrevrange>("zrevrange", {
-				{"zset", php::TYPE::STRING},
-				{"start", php::TYPE::INTEGER},
-				{"stop", php::TYPE::INTEGER},
-			})
-			.method<&client::zrangebyscore>("zrangebyscore", {
-				{"zset", php::TYPE::STRING},
-				{"min", php::TYPE::STRING},
-				{"max", php::TYPE::STRING},
-			})
-			.method<&client::zrevrangebyscore>("zrevrangebyscore", {
-				{"zset", php::TYPE::STRING},
-				{"min", php::TYPE::STRING},
-				{"max", php::TYPE::STRING},
-			})
-			.method<&client::multi>("multi")
-			// 以下暂未实现
-			.method<&client::unimplement>("subscribe")
-			.method<&client::unimplement>("psubscribe")
+    void client::declare(php::extension_entry& ext) {
+        php::class_entry<client> class_client("flame\\redis\\client");
+        class_client
+            .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::__call>("__call", {
+                {"cmd", php::TYPE::STRING},
+                {"arg", php::TYPE::ARRAY},
+            })
+            .method<&client::__isset>("__isset", {
+                {"cmd", php::TYPE::STRING},
+            })
+            .method<&client::mget>("mget", {
+                {"key", php::TYPE::STRING},
+            })
+            .method<&client::hmget>("hmget", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&client::hgetall>("hgetall", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&client::hscan>("hscan", {
+                {"hash", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&client::zscan>("zscan", {
+                {"zset", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&client::zrange>("zrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&client::zrevrange>("zrevrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&client::zrangebyscore>("zrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&client::zrevrangebyscore>("zrevrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&client::multi>("multi")
+            // 以下暂未实现
+            .method<&client::unimplement>("subscribe")
+            .method<&client::unimplement>("psubscribe")
             .method<&client::unimplement>("unsubscribe")
-			.method<&client::unimplement>("punsubscribe");
+            .method<&client::unimplement>("punsubscribe");
 
-		ext.add(std::move(class_client));
-	}
+        ext.add(std::move(class_client));
+    }
+
+    php::value client::__construct(php::parameters& params) {
+        return nullptr;
+    }
 
-	php::value client::__construct(php::parameters& params)
-	{
-		return nullptr;
-	}
-	php::value client::__call(php::parameters& params)
-	{
+    php::value client::__call(php::parameters& params) {
         coroutine_handler ch {coroutine::current};
         auto rc = cp_->acquire(ch);
         php::string name = params[0];
         php::array  argv = params[1];
         return cp_->exec(rc, name, argv, reply_type::SIMPLE, ch);
-	}
-	php::value client::__isset(php::parameters& params) {
-		return true;
-	}
-	php::value client::mget(php::parameters& params)
-	{
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("MGET", 4);
-		return cp_->exec(rc, name, params, reply_type::COMBINE_1, ch);
-	}
-	php::value client::hmget(php::parameters& params)
-	{
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("HMGET", 5);
-		return cp_->exec(rc, name, params, reply_type::COMBINE_2, ch);
-	}
-	php::value client::hgetall(php::parameters& params)
-	{
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("HGETALL", 7);
-		return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
-	}
-	php::value client::hscan(php::parameters& params) {
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("HSCAN", 5);
-		return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
-	}
-	php::value client::zscan(php::parameters& params) {
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("ZSCAN", 5);
-		return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
-	}
-	php::value client::zrange(php::parameters& params) {
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("ZRANGE", 6);
-		php::string last = params[params.size()-1];
-		if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
-		{
-			return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
-		}
-		else
-		{
-			return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
-		}
-	}
-	php::value client::zrevrange(php::parameters& params) {
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("ZREVRANGE", 9);
-		php::string last = params[params.size() - 1];
-		if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
-		{
-			return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
-		}
-		else
-		{
-			return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
-		}
-	}
-	php::value client::zrangebyscore(php::parameters& params) {
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("ZRANGEBYSCORE", 13);
-
-		for(int i=3; i<params.size(); ++i) {
-			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
-				php::string arg = params[i];
-				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
-					return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
-			}
-		}
-		return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
-	}
-	php::value client::zrevrangebyscore(php::parameters& params) {
-		coroutine_handler ch{coroutine::current};
-		auto rc = cp_->acquire(ch);
-		php::string name("ZREVRANGEBYSCORE", 16);
-
-		for(int i=3; i<params.size(); ++i) {
-			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
-				php::string arg = params[i];
-				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
-					return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
-			}
-		}
-		return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
-	}
-	php::value client::multi(php::parameters& params) {
+    }
+
+    php::value client::__isset(php::parameters& params) {
+        return true;
+    }
+
+    php::value client::mget(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("MGET", 4);
+        return cp_->exec(rc, name, params, reply_type::COMBINE_1, ch);
+    }
+
+    php::value client::hmget(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("HMGET", 5);
+        return cp_->exec(rc, name, params, reply_type::COMBINE_2, ch);
+    }
+
+    php::value client::hgetall(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("HGETALL", 7);
+        return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+    }
+
+    php::value client::hscan(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("HSCAN", 5);
+        return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
+    }
+
+    php::value client::zscan(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZSCAN", 5);
+        return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
+    }
+
+    php::value client::zrange(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZRANGE", 6);
+        php::string last = params[params.size()-1];
+        if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+            return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+        else
+            return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::zrevrange(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZREVRANGE", 9);
+        php::string last = params[params.size() - 1];
+        if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+            return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+        else
+            return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::zrangebyscore(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZRANGEBYSCORE", 13);
+
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
+                    return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+            }
+        }
+        return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::zrevrangebyscore(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZREVRANGEBYSCORE", 16);
+
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
+                    return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+            }
+        }
+        return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::multi(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
-		php::object obj(php::class_entry<tx>::entry());
-		tx *ptr = static_cast<tx *>(php::native(obj));
-		ptr->cl_.reset(new _connection_lock(conn_));
-		return std::move(obj);
-	}
-	php::value client::unimplement(php::parameters& params) {
+        php::object obj(php::class_entry<tx>::entry());
+        tx *ptr = static_cast<tx *>(php::native(obj));
+        ptr->cl_.reset(new _connection_lock(conn_));
+        return std::move(obj);
+    }
+    
+    php::value client::unimplement(php::parameters& params) {
         throw php::exception(zend_ce_error_exception, "This redis command is NOT yet implemented");
     }
 } // namespace flame::redis
diff --git a/src/redis/client.h b/src/redis/client.h
index 98ccef8..37d6717 100644
--- a/src/redis/client.h
+++ b/src/redis/client.h
@@ -1,11 +1,9 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::redis
-{
+namespace flame::redis {
     class _connection_pool;
-    class client : public php::class_base
-    {
+    class client : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
diff --git a/src/redis/redis.cpp b/src/redis/redis.cpp
index c4caab0..82a40d6 100644
--- a/src/redis/redis.cpp
+++ b/src/redis/redis.cpp
@@ -3,20 +3,17 @@
 #include "client.h"
 #include "tx.h"
 
-namespace flame::redis
-{
-    void declare(php::extension_entry& ext)
-    {
-		ext.function<connect>("flame\\redis\\connect",
-        {
-			{"uri", php::TYPE::STRING}
-		});
-		client::declare(ext);
-		tx::declare(ext);
-	}
+namespace flame::redis {
+    
+    void declare(php::extension_entry& ext) {
+        ext.function<connect>("flame\\redis\\connect", {
+            {"uri", php::TYPE::STRING}
+        });
+        client::declare(ext);
+        tx::declare(ext);
+    }
 
-    php::value connect(php::parameters &params)
-    {
+    php::value connect(php::parameters &params) {
         url u(params[0]);
         php::object obj {php::class_entry<client>::entry()};
         client *ptr = static_cast<client *>(php::native(obj));
@@ -25,6 +22,4 @@ namespace flame::redis
         // TODO 优化: 确认第一个连接建立 ?
         return std::move(obj);
     }
-
-
 }
diff --git a/src/redis/redis.h b/src/redis/redis.h
index b8e920f..cd4d68f 100644
--- a/src/redis/redis.h
+++ b/src/redis/redis.h
@@ -1,8 +1,7 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::redis
-{
+namespace flame::redis {
     void declare(php::extension_entry &ext);
     php::value connect(php::parameters &params);
 } // namespace flame::redis
diff --git a/src/redis/tx.cpp b/src/redis/tx.cpp
index 2bb7434..f6c5b9f 100644
--- a/src/redis/tx.cpp
+++ b/src/redis/tx.cpp
@@ -3,173 +3,161 @@
 #include "_connection_lock.h"
 
 namespace flame::redis {
-	void tx::declare(php::extension_entry& ext) {
-		php::class_entry<tx> class_tx("flame\\redis\\tx");
-		class_tx
-			.method<&tx::__construct>("__construct", {}, php::PRIVATE)
+    void tx::declare(php::extension_entry& ext) {
+        php::class_entry<tx> class_tx("flame\\redis\\tx");
+        class_tx
+            .method<&tx::__construct>("__construct", {}, php::PRIVATE)
             .method<&tx::exec>("exec")
-			.method<&tx::__call>("__call",
-            {
-				{"cmd", php::TYPE::STRING},
-				{"arg", php::TYPE::ARRAY},
-			})
-			.method<&tx::mget>("mget",
-            {
-				{"key", php::TYPE::STRING},
-			})
-			.method<&tx::hmget>("hmget",
-            {
-				{"hash", php::TYPE::STRING},
-			})
-			.method<&tx::hgetall>("hgetall",
-            {
-				{"hash", php::TYPE::STRING},
-			})
-			.method<&tx::hscan>("hscan",
-            {
-				{"hash", php::TYPE::STRING},
-				{"cursor", php::TYPE::INTEGER},
-			})
-			.method<&tx::zscan>("zscan",
-            {
-				{"zset", php::TYPE::STRING},
-				{"cursor", php::TYPE::INTEGER},
-			})
-			.method<&tx::zrange>("zrange",
-            {
-				{"zset", php::TYPE::STRING},
-				{"start", php::TYPE::INTEGER},
-				{"stop", php::TYPE::INTEGER},
-			})
-			.method<&tx::zrevrange>("zrevrange",
-            {
-				{"zset", php::TYPE::STRING},
-				{"start", php::TYPE::INTEGER},
-				{"stop", php::TYPE::INTEGER},
-			})
-			.method<&tx::zrangebyscore>("zrangebyscore",
-            {
-				{"zset", php::TYPE::STRING},
-				{"min", php::TYPE::STRING},
-				{"max", php::TYPE::STRING},
-			})
-			.method<&tx::zrevrangebyscore>("zrevrangebyscore",
-            {
-				{"zset", php::TYPE::STRING},
-				{"min", php::TYPE::STRING},
-				{"max", php::TYPE::STRING},
-			})
-			.method<&tx::unimplement>("multi")
-			// 以下暂未实现
-			.method<&tx::unimplement>("subscribe")
-			.method<&tx::unimplement>("psubscribe")
+            .method<&tx::__call>("__call", {
+                {"cmd", php::TYPE::STRING},
+                {"arg", php::TYPE::ARRAY},
+            })
+            .method<&tx::mget>("mget", {
+                {"key", php::TYPE::STRING},
+            })
+            .method<&tx::hmget>("hmget", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&tx::hgetall>("hgetall", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&tx::hscan>("hscan", {
+                {"hash", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&tx::zscan>("zscan", {
+                {"zset", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&tx::zrange>("zrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&tx::zrevrange>("zrevrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&tx::zrangebyscore>("zrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&tx::zrevrangebyscore>("zrevrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&tx::unimplement>("multi")
+            // 以下暂未实现
+            .method<&tx::unimplement>("subscribe")
+            .method<&tx::unimplement>("psubscribe")
             .method<&tx::unimplement>("unsubscribe")
-			.method<&tx::unimplement>("punsubscribe");
+            .method<&tx::unimplement>("punsubscribe");
         
-		ext.add(std::move(class_tx));
-	}
+        ext.add(std::move(class_tx));
+    }
+
+    php::value tx::__construct(php::parameters& params) {
+        return nullptr;
+    }
 
-	php::value tx::__construct(php::parameters& params)
-	{
-		return nullptr;
-	}
-    php::value tx::exec(php::parameters& params)
-    {
+    php::value tx::exec(php::parameters& params) {
         coroutine_handler ch {coroutine::current};
         return cl_->exec(ch);
     }
-	php::value tx::__call(php::parameters& params)
-	{
+
+    php::value tx::__call(php::parameters& params) {
         php::string name = params[0];
         php::array  argv = params[1];
         cl_->push(name, argv, reply_type::SIMPLE);
         return this;
-	}
-	php::value tx::mget(php::parameters& params)
-	{
-		php::string name("MGET", 4);
-		cl_->push(name, params, reply_type::COMBINE_1);
+    }
+
+    php::value tx::mget(php::parameters& params) {
+        php::string name("MGET", 4);
+        cl_->push(name, params, reply_type::COMBINE_1);
         return this;
     }
-	php::value tx::hmget(php::parameters& params)
-	{
-		php::string name("HMGET", 5);
+
+    php::value tx::hmget(php::parameters& params) {
+        php::string name("HMGET", 5);
         cl_->push(name, params, reply_type::COMBINE_2);
         return this;
     }
-	php::value tx::hgetall(php::parameters& params)
-	{
-		php::string name("HGETALL", 7);
+
+    php::value tx::hgetall(php::parameters& params) {
+        php::string name("HGETALL", 7);
         cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
         return this;
     }
-	php::value tx::hscan(php::parameters& params) {
-		php::string name("HSCAN", 5);
-		cl_->push(name, params, reply_type::ASSOC_ARRAY_2);
+
+    php::value tx::hscan(php::parameters& params) {
+        php::string name("HSCAN", 5);
+        cl_->push(name, params, reply_type::ASSOC_ARRAY_2);
         return this;
     }
-	php::value tx::zscan(php::parameters& params) {
-		php::string name("ZSCAN", 5);
+
+    php::value tx::zscan(php::parameters& params) {
+        php::string name("ZSCAN", 5);
         cl_->push(name, params, reply_type::ASSOC_ARRAY_2);
         return this;
     }
-	php::value tx::zrange(php::parameters& params) {
-		php::string name("ZRANGE", 6);
-		php::string last = params[params.size()-1];
-		if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
-		{
+
+    php::value tx::zrange(php::parameters& params) {
+        php::string name("ZRANGE", 6);
+        php::string last = params[params.size()-1];
+        if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
             cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
-        }
-		else
-		{
+        else
             cl_->push(name, params, reply_type::SIMPLE);
-        }
         return this;
     } 
-	php::value tx::zrevrange(php::parameters& params) {
-		php::string name("ZREVRANGE", 9);
-		php::string last = params[params.size() - 1];
-		if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
-		{
+
+    php::value tx::zrevrange(php::parameters& params) {
+        php::string name("ZREVRANGE", 9);
+        php::string last = params[params.size() - 1];
+        if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
             cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
-        }
-		else
-		{
+        else
             cl_->push(name, params, reply_type::SIMPLE);
-        }
         return this;
     }
-	php::value tx::zrangebyscore(php::parameters& params) {
-		php::string name("ZRANGEBYSCORE", 13);
 
-		for(int i=3; i<params.size(); ++i) {
-			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
-				php::string arg = params[i];
-				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+    php::value tx::zrangebyscore(php::parameters& params) {
+        php::string name("ZRANGEBYSCORE", 13);
+
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
                     cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
                     return this;
                 }
-			}
-		}
+            }
+        }
         cl_->push(name, params, reply_type::SIMPLE);
         return this;
     }
-	php::value tx::zrevrangebyscore(php::parameters& params) {
-		php::string name("ZREVRANGEBYSCORE", 16);
+    
+    php::value tx::zrevrangebyscore(php::parameters& params) {
+        php::string name("ZREVRANGEBYSCORE", 16);
 
-		for(int i=3; i<params.size(); ++i) {
-			if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
-				php::string arg = params[i];
-				if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
                     cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
                     return this;
                 }
-			}
-		}
+            }
+        }
         cl_->push(name, params, reply_type::SIMPLE);
         return this;
     }
-	php::value tx::unimplement(php::parameters& params) {
+    
+    php::value tx::unimplement(php::parameters& params) {
         throw php::exception(zend_ce_error_exception, "This redis command is NOT yet implemented");
     }
 } // namespace flame::redis
diff --git a/src/redis/tx.h b/src/redis/tx.h
index 5f09c6a..38037de 100644
--- a/src/redis/tx.h
+++ b/src/redis/tx.h
@@ -1,11 +1,10 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::redis
-{
+namespace flame::redis {
+
     class _connection_lock;
-    class tx : public php::class_base
-    {
+    class tx : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
@@ -28,9 +27,8 @@ namespace flame::redis
         php::value multi(php::parameters &params);
         // 用于标记不实现的功能
         php::value unimplement(php::parameters &params);
-
     private:
         std::shared_ptr<_connection_lock> cl_;
         friend class client;
     };
-} // namespace flame::redis
\ No newline at end of file
+} // namespace flame::redis
diff --git a/src/tcp/server.cpp b/src/tcp/server.cpp
index b7edcf5..463984d 100644
--- a/src/tcp/server.cpp
+++ b/src/tcp/server.cpp
@@ -10,15 +10,15 @@ namespace flame::tcp {
 
     void server::declare(php::extension_entry &ext) {
         php::class_entry<server> class_server("flame\\tcp\\server");
-		class_server
-			.method<&server::__construct>("__construct", {
-				{"bind", php::TYPE::STRING},
-			})
-			.method<&server::run>("run", {
-				{"cb", php::TYPE::CALLABLE},
-			})
-			.method<&server::close>("close");
-		ext.add(std::move(class_server));
+        class_server
+            .method<&server::__construct>("__construct", {
+                {"bind", php::TYPE::STRING},
+            })
+            .method<&server::run>("run", {
+                {"cb", php::TYPE::CALLABLE},
+            })
+            .method<&server::close>("close");
+        ext.add(std::move(class_server));
     }
 
     server::server()
diff --git a/src/tcp/server.h b/src/tcp/server.h
index 865b324..4b463a8 100644
--- a/src/tcp/server.h
+++ b/src/tcp/server.h
@@ -1,9 +1,8 @@
 #pragma once
+#include "../vendor.h"
 
-namespace flame::tcp
-{
-    class server : public php::class_base
-    {
+namespace flame::tcp {
+    class server : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         server();
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
index 96de57a..0d85ee4 100644
--- a/src/tcp/socket.cpp
+++ b/src/tcp/socket.cpp
@@ -4,51 +4,51 @@
 
 namespace flame::tcp {
 
-	void socket::declare(php::extension_entry& ext) {
-		php::class_entry<socket> class_socket("flame\\tcp\\socket");
-		class_socket
-			.property({"local_address", ""})
-			.property({"remote_address", ""})
-			.method<&socket::read>("read", {
-				{"completion", php::TYPE::UNDEFINED, false, true}
-			})
-			.method<&socket::write>("write", {
-				{"data", php::TYPE::STRING}
-			})
-			.method<&socket::close>("close");
-		ext.add(std::move(class_socket));
-	}
+    void socket::declare(php::extension_entry& ext) {
+        php::class_entry<socket> class_socket("flame\\tcp\\socket");
+        class_socket
+            .property({"local_address", ""})
+            .property({"remote_address", ""})
+            .method<&socket::read>("read", {
+                {"completion", php::TYPE::UNDEFINED, false, true}
+            })
+            .method<&socket::write>("write", {
+                {"data", php::TYPE::STRING}
+            })
+            .method<&socket::close>("close");
+        ext.add(std::move(class_socket));
+    }
 
-	socket::socket()
-	: socket_(gcontroller->context_x) {
+    socket::socket()
+    : socket_(gcontroller->context_x) {
 
-	}
+    }
     
-	php::value socket::read(php::parameters& params) {
+    php::value socket::read(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
         // 使用下面锁保证不会同时进行读取
         coroutine_guard guard(rmutex_, ch);
 
-		boost::system::error_code err;
+        boost::system::error_code err;
         std::size_t len;
         if (params.size() == 0) {// 1. 随意读取一段数据
             len = socket_.async_read_some(boost::asio::buffer(buffer_.prepare(8192), 8192), ch[err]);
             buffer_.commit(len);
         }
         else if (params[0].typeof(php::TYPE::STRING)) { // 2. 读取到指定的结束符
-			std::string delim = params[0];
+            std::string delim = params[0];
             len = boost::asio::async_read_until(socket_, buffer_, delim, ch[err]);
         }
         else if (params[0].typeof(php::TYPE::INTEGER)) { // 3. 读取指定长度
-			std::size_t want = params[0].to_integer();
-			if (buffer_.size() >= want) {
+            std::size_t want = params[0].to_integer();
+            if (buffer_.size() >= want) {
                 len = want;
                 goto RETURN_DATA;
-			}
+            }
             // 读取指定长度 (剩余的缓存数据也算在长度中)
             len = boost::asio::async_read(socket_, buffer_, boost::asio::transfer_exactly(want - buffer_.size()), ch[err]);
             len = want;
-		}
+        }
         else throw php::exception(zend_ce_type_error
             , "Failed to read TCP socket: unknown completion type"
             , -1); // 未知读取方式
@@ -66,24 +66,24 @@ namespace flame::tcp {
         }
     }
 
-	php::value socket::write(php::parameters& params) {
+    php::value socket::write(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
         // 使用下面锁保证不会同时写入
         coroutine_guard guard(wmutex_, ch);
         
         boost::system::error_code err;
-		std::string data = params[0];
+        std::string data = params[0];
         boost::asio::async_write(socket_, boost::asio::buffer(data), ch);
 
         if (!err || err == boost::asio::error::operation_aborted) return nullptr;
         else throw php::exception(zend_ce_exception
             , (boost::format("Failed to write TCP socket: %s") % err.message()).str()
             , err.value());
-	}
+    }
 
-	php::value socket::close(php::parameters& params) {
-		socket_.shutdown(boost::asio::socket_base::shutdown_both);
-		return nullptr;
-	}
+    php::value socket::close(php::parameters& params) {
+        socket_.shutdown(boost::asio::socket_base::shutdown_both);
+        return nullptr;
+    }
 }
   
\ No newline at end of file
diff --git a/src/tcp/socket.h b/src/tcp/socket.h
index 11d3783..8463bd9 100644
--- a/src/tcp/socket.h
+++ b/src/tcp/socket.h
@@ -3,10 +3,9 @@
 #include "../coroutine.h"
 #include "../coroutine_mutex.h"
 
-namespace flame::tcp
-{
-    class socket : public php::class_base
-    {
+namespace flame::tcp {
+
+    class socket : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         socket();
@@ -23,4 +22,4 @@ namespace flame::tcp
         friend class server;
         friend php::value connect(php::parameters &params);
     };
-} // namespace flame::tcp
\ No newline at end of file
+} // namespace flame::tcp
diff --git a/src/tcp/tcp.cpp b/src/tcp/tcp.cpp
index 075ba5d..7a4946f 100644
--- a/src/tcp/tcp.cpp
+++ b/src/tcp/tcp.cpp
@@ -6,57 +6,56 @@
 #include "server.h"
 
 namespace flame::tcp {
-	static std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
-	void declare(php::extension_entry& ext)
-    {
-		gcontroller->on_init([] (const php::array& options) {
+    static std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
+    void declare(php::extension_entry& ext) {
+        gcontroller->on_init([] (const php::array& options) {
             resolver_.reset(new boost::asio::ip::tcp::resolver(gcontroller->context_x));
         })->on_stop([] () {
             resolver_.reset();
         });
-		ext
-			.function<connect>("flame\\tcp\\connect", {
-				{"address", php::TYPE::STRING},
-			});
-		socket::declare(ext);
-		server::declare(ext);
-	}
+        ext
+            .function<connect>("flame\\tcp\\connect", {
+                {"address", php::TYPE::STRING},
+            });
+        socket::declare(ext);
+        server::declare(ext);
+    }
 
     php::value connect(php::parameters& params) {
-		php::object obj(php::class_entry<socket>::entry());
+        php::object obj(php::class_entry<socket>::entry());
         socket *ptr = static_cast<socket *>(php::native(obj));
 
         std::string str = params[0];
         auto pair = udp::addr2pair(str);
         if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
-        	, "Failed to connect TCP socket: illegal address format"
-        	, -1);
+            , "Failed to connect TCP socket: illegal address format"
+            , -1);
 
-		coroutine_handler ch{coroutine::current};
+        coroutine_handler ch{coroutine::current};
         boost::system::error_code err;
-		boost::asio::ip::tcp::resolver::results_type eps;
-		// DNS 地址解析
-		resolver_->async_resolve(pair.first, pair.second, [&err, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type results) {
-			if (error) err = error;
-			else eps = results;
+        boost::asio::ip::tcp::resolver::results_type eps;
+        // DNS 地址解析
+        resolver_->async_resolve(pair.first, pair.second, [&err, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type results) {
+            if (error) err = error;
+            else eps = results;
             ch.resume();
-		});
-		ch.suspend();
-		if (err) throw php::exception(zend_ce_exception
+        });
+        ch.suspend();
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to resolve address: %s") % err.message()).str()
             , err.value());
 
-		// 连接
+        // 连接
         boost::asio::async_connect(ptr->socket_, eps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::tcp::endpoint &edp) {
             if (error) err = error;
-			else {
-	            obj.set("local_address", (boost::format("%s:%d")
-	            	% ptr->socket_.local_endpoint().address().to_string()
-	            	% ptr->socket_.local_endpoint().port() ).str());
-				obj.set("remote_address", (boost::format("%s:%d")
-					% ptr->socket_.remote_endpoint().address().to_string()
-					% ptr->socket_.remote_endpoint().port() ).str());
-			}
+            else {
+                obj.set("local_address", (boost::format("%s:%d")
+                    % ptr->socket_.local_endpoint().address().to_string()
+                    % ptr->socket_.local_endpoint().port() ).str());
+                obj.set("remote_address", (boost::format("%s:%d")
+                    % ptr->socket_.remote_endpoint().address().to_string()
+                    % ptr->socket_.remote_endpoint().port() ).str());
+            }
             ch.resume();
         });
         ch.suspend();
@@ -64,5 +63,5 @@ namespace flame::tcp {
             , (boost::format("Failed to connect TCP socket: %s") % err.message()).str()
             , err.value());
         return std::move(obj);
-	}
+    }
 } // namespace flame::tcp
diff --git a/src/tcp/tcp.h b/src/tcp/tcp.h
index 8da2a39..b2d61e2 100644
--- a/src/tcp/tcp.h
+++ b/src/tcp/tcp.h
@@ -1,8 +1,7 @@
 #pragma once
 #include "../vendor.h"
 
-namespace flame::tcp
-{
+namespace flame::tcp {
     void declare(php::extension_entry &ext);
     php::value connect(php::parameters &params);
 } // namespace flame::tcp
diff --git a/src/time/time.cpp b/src/time/time.cpp
index 7bafc13..bee3ccd 100644
--- a/src/time/time.cpp
+++ b/src/time/time.cpp
@@ -2,14 +2,12 @@
 #include "../controller.h"
 #include "../coroutine.h"
 
-namespace flame::time
-{
+namespace flame::time {
 
     static std::chrono::time_point<std::chrono::system_clock> time_system;
-	static std::chrono::time_point<std::chrono::steady_clock> time_steady;
+    static std::chrono::time_point<std::chrono::steady_clock> time_steady;
 
-    static php::value sleep(php::parameters& params)
-    {
+    static php::value sleep(php::parameters& params) {
         coroutine_handler ch {coroutine::current};
         boost::asio::steady_timer tm(gcontroller->context_x);
         int ms = static_cast<int>(params[0]);
@@ -19,13 +17,12 @@ namespace flame::time
         return nullptr;
     }
 
-
-    std::chrono::time_point<std::chrono::system_clock> now()
-    {
+    std::chrono::time_point<std::chrono::system_clock> now() {
         std::chrono::milliseconds diff = std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::steady_clock::now() - time_steady);
         return time_system + diff;
     }
+
     php::string iso(const std::chrono::time_point<std::chrono::system_clock>& now) {
         php::string data(19);
         std::time_t t = std::chrono::system_clock::to_time_t(now);
@@ -39,35 +36,30 @@ namespace flame::time
                 m->tm_sec);
         return std::move(data);
     }
-    php::string iso()
-    {
+
+    php::string iso() {
         return iso(now());
     }
 
-    static php::value now(php::parameters &params)
-    {
+    static php::value now(php::parameters &params) {
         return std::chrono::duration_cast<std::chrono::milliseconds>(now().time_since_epoch()).count();
     }
-    static php::value iso(php::parameters &params)
-    {
+
+    static php::value iso(php::parameters &params) {
         return iso();
     }
 
-    void declare(php::extension_entry& ext)
-    {
+    void declare(php::extension_entry& ext) {
         time_steady = std::chrono::steady_clock::now();
-		time_system = std::chrono::system_clock::now();
+        time_system = std::chrono::system_clock::now();
 
         ext
-        .function<sleep>("flame\\time\\sleep",
-        {
-            {"duration", php::TYPE::INTEGER},
-        })
-        // 毫秒时间戳
-        .function<now>("flame\\time\\now")
-        // 标准时间 YYYY-mm-dd HH:ii:ss
-        .function<iso>("flame\\time\\iso");
-
+            .function<sleep>("flame\\time\\sleep", {
+                {"duration", php::TYPE::INTEGER},
+            })
+            // 毫秒时间戳
+            .function<now>("flame\\time\\now")
+            // 标准时间 YYYY-mm-dd HH:ii:ss
+            .function<iso>("flame\\time\\iso");
     }
-
 }
diff --git a/src/time/time.h b/src/time/time.h
index f4a105f..12d3408 100644
--- a/src/time/time.h
+++ b/src/time/time.h
@@ -1,7 +1,6 @@
 #include "../vendor.h"
 
-namespace flame::time
-{
+namespace flame::time {
     void declare(php::extension_entry &ext);
     std::chrono::time_point<std::chrono::system_clock> now();
     php::string iso();
diff --git a/src/udp/server.cpp b/src/udp/server.cpp
index 6f9dba8..40d8117 100644
--- a/src/udp/server.cpp
+++ b/src/udp/server.cpp
@@ -8,40 +8,40 @@
 namespace flame::udp {
     
     void server::declare(php::extension_entry& ext) {
-		php::class_entry<server> class_server("flame\\udp\\server");
-		class_server
-			.method<&server::__construct>("__construct", {
-				{"address", php::TYPE::STRING, false, false},
-				{"options", php::TYPE::ARRAY, false, true},
-			})
-			.method<&server::run>("run", {
-				{"length", php::TYPE::CALLABLE, false, false},
-			})
+        php::class_entry<server> class_server("flame\\udp\\server");
+        class_server
+            .method<&server::__construct>("__construct", {
+                {"address", php::TYPE::STRING, false, false},
+                {"options", php::TYPE::ARRAY, false, true},
+            })
+            .method<&server::run>("run", {
+                {"length", php::TYPE::CALLABLE, false, false},
+            })
             .method<&server::send_to>("send_to", {
-				{"data", php::TYPE::STRING, false, false},
-				{"address", php::TYPE::STRING, false, false},
-			})
-			.method<&server::close>("close");
-		ext.add(std::move(class_server));
-	}
+                {"data", php::TYPE::STRING, false, false},
+                {"address", php::TYPE::STRING, false, false},
+            })
+            .method<&server::close>("close");
+        ext.add(std::move(class_server));
+    }
     server::server()
-	: socket_(gcontroller->context_x)
+    : socket_(gcontroller->context_x)
     , closed_(false)
     , concurrent_(8)
     , max_(64 * 1024) {
 
-	}
+    }
 
     typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
 
     php::value server::__construct(php::parameters& params) {
         auto pair = addr2pair(params[0]);
-		boost::system::error_code err;
-		boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
-		if (err) throw php::exception(zend_ce_exception
+        boost::system::error_code err;
+        boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to bind: %s") % err.message()).str()
             , err.value());
-		std::uint16_t port = std::stoi(pair.second);
+        std::uint16_t port = std::stoi(pair.second);
         addr_.address(address);
         addr_.port(port);
 
@@ -50,8 +50,8 @@ namespace flame::udp {
         socket_.set_option(opt1);
         reuse_port opt2(true);
         socket_.set_option(opt2);
-		socket_.bind(addr_, err);
-		if (err) throw php::exception(zend_ce_exception
+        socket_.bind(addr_, err);
+        if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to bind: %s") % err.message()).str()
             , err.value());
 
@@ -66,7 +66,7 @@ namespace flame::udp {
     }
 
     php::value server::run(php::parameters& params) {
-		php::callable cb_ = params[0];
+        php::callable cb_ = params[0];
 
         coroutine_handler ch {coroutine::current};
         coroutine_queue<std::pair<php::string, php::string>> q(128);
@@ -94,10 +94,10 @@ namespace flame::udp {
         // 生产
         std::size_t len = 0;
         boost::system::error_code err;
-		boost::asio::ip::udp::endpoint ep;
+        boost::asio::ip::udp::endpoint ep;
         php::buffer buffer;
         while(!closed_) {
-    		socket_.async_receive_from(boost::asio::buffer(buffer.prepare(max_), max_), ep
+            socket_.async_receive_from(boost::asio::buffer(buffer.prepare(max_), max_), ep
                 , [&len, &err, &ch] (const boost::system::error_code& error, std::size_t nread) {
 
                 if (error) err = error;
@@ -118,43 +118,43 @@ namespace flame::udp {
         q.close();
         ch.suspend();
         return nullptr;
-	}
+    }
 
     php::value server::send_to(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
 
         boost::system::error_code err;
-		php::string data = params[0];
-		auto pair = addr2pair(params[1]);
-		if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+        php::string data = params[0];
+        auto pair = addr2pair(params[1]);
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
             , "Failed to send udp packet: illegal address format"
             , -1);
 
-		boost::asio::ip::udp::resolver::results_type eps;
-		resolver_->async_resolve(pair.first, pair.second
+        boost::asio::ip::udp::resolver::results_type eps;
+        resolver_->async_resolve(pair.first, pair.second
             , [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
 
-			if (error) err = error;
-			else eps = results;
-			ch.resume();
-		});
-		ch.suspend();
-		if (err == boost::asio::error::operation_aborted) return nullptr;
-		else if (err) throw php::exception(zend_ce_exception
-			, (boost::format("Failed to resolve address: %s") % err.message()).str()
+            if (error) err = error;
+            else eps = results;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err == boost::asio::error::operation_aborted) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to resolve address: %s") % err.message()).str()
             , err.value());
 
-		// 发送
-		int sent = 0;
-		for(auto i=eps.begin(); i!=eps.end(); ++i) {
-			socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
-			if (!err) return nullptr;
-		}
+        // 发送
+        int sent = 0;
+        for(auto i=eps.begin(); i!=eps.end(); ++i) {
+            socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
+            if (!err) return nullptr;
+        }
 
-		throw php::exception(zend_ce_exception
-			, (boost::format("Failed to send UDP packet: %s") % err.message()).str()
+        throw php::exception(zend_ce_exception
+            , (boost::format("Failed to send UDP packet: %s") % err.message()).str()
             , err.value());
-	}
+    }
 
     php::value server::close(php::parameters& params) {
         closed_ = true;
diff --git a/src/udp/socket.cpp b/src/udp/socket.cpp
index 959f23f..9212232 100644
--- a/src/udp/socket.cpp
+++ b/src/udp/socket.cpp
@@ -5,90 +5,90 @@
 
 namespace flame::udp {
 
-	void socket::declare(php::extension_entry& ext)  {
-		php::class_entry<socket> class_socket("flame\\udp\\socket");
-		class_socket
-			.property({"local_address", ""})
-			.property({"remote_address", ""})
-			.method<&socket::__construct>("__construct", {
-				{"address", php::TYPE::STRING, false, true},
-			})
-			.method<&socket::recv_from>("recv_from", {
-				{"address", php::TYPE::STRING, true, true},
-			})
-			.method<&socket::recv>("recv")
-			.method<&socket::send_to>("send_to", {
-				{"data", php::TYPE::STRING, false, false},
-				{"address", php::TYPE::STRING, false, false},
-			})
-			.method<&socket::send>("send")
-			.method<&socket::close>("close");
-		ext.add(std::move(class_socket));
-	}
-
-	socket::socket()
-	: socket_(gcontroller->context_x)
-	, connected_(false)
-	, max_(64 * 1024) {
-
-	}
-
-	typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
-
-	php::value socket::__construct(php::parameters& params) {
-		boost::system::error_code err;
-		php::array option;
-		if (params.size() > 0 && params[0].typeof(php::TYPE::STRING)) {
-			auto pair = addr2pair(params[0]);
-			boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
-			if (err) throw php::exception(zend_ce_exception
-	            , (boost::format("Failed to bind: %s") % err.message()).str()
-	            , err.value());
-			std::uint16_t port = std::stoi(pair.second);
-			auto ep = boost::asio::ip::udp::endpoint(address, port);
-			socket_.open(ep.protocol());
-			boost::asio::socket_base::reuse_address opt1(true);
-	        socket_.set_option(opt1);
-	        reuse_port opt2(true);
-	        socket_.set_option(opt2);
-			socket_.bind(ep, err);
-			if (err) throw php::exception(zend_ce_exception
-	            , (boost::format("failed to bind: %s") % err.message()).str()
-	            , err.value());
-			if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
-				option = params[0];
-				goto PARSE_OPTION;
-			}
-		}
-		else {
-			socket_.open(boost::asio::ip::udp::v6(), err);
-			if (err) socket_.open(boost::asio::ip::udp::v4(), err);
-			if (err) throw php::exception(zend_ce_exception
-				, (boost::format("Failed to bind: %s") % err.message()).str()
-				, err.value());
-			if (params.size() > 0 && params[0].typeof(php::TYPE::ARRAY)) {
-				option = params[0];
-				goto PARSE_OPTION;
-			}
-		}
-		return nullptr;
+    void socket::declare(php::extension_entry& ext)  {
+        php::class_entry<socket> class_socket("flame\\udp\\socket");
+        class_socket
+            .property({"local_address", ""})
+            .property({"remote_address", ""})
+            .method<&socket::__construct>("__construct", {
+                {"address", php::TYPE::STRING, false, true},
+            })
+            .method<&socket::recv_from>("recv_from", {
+                {"address", php::TYPE::STRING, true, true},
+            })
+            .method<&socket::recv>("recv")
+            .method<&socket::send_to>("send_to", {
+                {"data", php::TYPE::STRING, false, false},
+                {"address", php::TYPE::STRING, false, false},
+            })
+            .method<&socket::send>("send")
+            .method<&socket::close>("close");
+        ext.add(std::move(class_socket));
+    }
+
+    socket::socket()
+    : socket_(gcontroller->context_x)
+    , connected_(false)
+    , max_(64 * 1024) {
+
+    }
+
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+
+    php::value socket::__construct(php::parameters& params) {
+        boost::system::error_code err;
+        php::array option;
+        if (params.size() > 0 && params[0].typeof(php::TYPE::STRING)) {
+            auto pair = addr2pair(params[0]);
+            boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
+            if (err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to bind: %s") % err.message()).str()
+                , err.value());
+            std::uint16_t port = std::stoi(pair.second);
+            auto ep = boost::asio::ip::udp::endpoint(address, port);
+            socket_.open(ep.protocol());
+            boost::asio::socket_base::reuse_address opt1(true);
+            socket_.set_option(opt1);
+            reuse_port opt2(true);
+            socket_.set_option(opt2);
+            socket_.bind(ep, err);
+            if (err) throw php::exception(zend_ce_exception
+                , (boost::format("failed to bind: %s") % err.message()).str()
+                , err.value());
+            if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
+                option = params[0];
+                goto PARSE_OPTION;
+            }
+        }
+        else {
+            socket_.open(boost::asio::ip::udp::v6(), err);
+            if (err) socket_.open(boost::asio::ip::udp::v4(), err);
+            if (err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to bind: %s") % err.message()).str()
+                , err.value());
+            if (params.size() > 0 && params[0].typeof(php::TYPE::ARRAY)) {
+                option = params[0];
+                goto PARSE_OPTION;
+            }
+        }
+        return nullptr;
 PARSE_OPTION:
-		if (option.exists("max"))
-			max_ = std::min(std::max(static_cast<int>(option.get("max")), 64), 64 * 1024);
-		return nullptr;
-	}
-
-	php::value socket::recv(php::parameters& params) {
-		if (!connected_) throw php::exception(zend_ce_error_exception
-			, "Failed to send: socket not connected"
-			, -1);
+        if (option.exists("max"))
+            max_ = std::min(std::max(static_cast<int>(option.get("max")), 64), 64 * 1024);
+        return nullptr;
+    }
+
+    php::value socket::recv(php::parameters& params) {
+        if (!connected_) throw php::exception(zend_ce_error_exception
+            , "Failed to send: socket not connected"
+            , -1);
         coroutine_handler ch{coroutine::current};
         // 使用下面锁保证不会同时进行读取
         coroutine_guard guard(rmutex_, ch);
-		boost::system::error_code err;
+        boost::system::error_code err;
         std::size_t len = 0;
         len = socket_.async_receive(boost::asio::buffer(buffer_.prepare(max_), max_), ch[err]);
-		buffer_.commit(len);
+        buffer_.commit(len);
         // 数据返回
         if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
         else if (err) throw php::exception(zend_ce_exception
@@ -97,33 +97,33 @@ namespace flame::udp {
         else return php::string(std::move(buffer_));
     }
 
-	php::value socket::recv_from(php::parameters& params) {
+    php::value socket::recv_from(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
         // 使用下面锁保证不会同时进行读取
         coroutine_guard guard(rmutex_, ch);
 
-		boost::system::error_code err;
-		std::size_t len = 0;
-		boost::asio::ip::udp::endpoint ep;
-		len = socket_.async_receive_from(boost::asio::buffer(buffer_.prepare(max_), max_), ep, ch[err]);
-		buffer_.commit(len);
+        boost::system::error_code err;
+        std::size_t len = 0;
+        boost::asio::ip::udp::endpoint ep;
+        len = socket_.async_receive_from(boost::asio::buffer(buffer_.prepare(max_), max_), ep, ch[err]);
+        buffer_.commit(len);
         // 数据返回
         if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
         else if (err) throw php::exception(zend_ce_exception
             , (boost::format("failed to read TCP socket: %s") % err.message()).str()
             , err.value());
-		if (params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
-		return php::string(std::move(buffer_));
+        if (params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
+        return php::string(std::move(buffer_));
     }
 
-	php::value socket::send(php::parameters& params) {
-		if (!connected_) throw php::exception(zend_ce_error_exception
-			, "Failed to send: connected socket required"
-			, -1);
+    php::value socket::send(php::parameters& params) {
+        if (!connected_) throw php::exception(zend_ce_error_exception
+            , "Failed to send: connected socket required"
+            , -1);
         coroutine_handler ch{coroutine::current};
 
         boost::system::error_code err;
-		std::string data = params[0];
+        std::string data = params[0];
         socket_.async_send(boost::asio::buffer(data), ch[err]);
 
         if (!err || err == boost::asio::error::operation_aborted) return nullptr;
@@ -131,47 +131,47 @@ namespace flame::udp {
             , (boost::format("Failed to write TCP socket: %s") % err.message()).str()
             , err.value());
         return nullptr;
-	}
+    }
 
-	php::value socket::send_to(php::parameters& params) {
+    php::value socket::send_to(php::parameters& params) {
         coroutine_handler ch{coroutine::current};
 
         boost::system::error_code err;
-		php::string data = params[0];
-		auto pair = addr2pair(params[1]);
-		if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
-			, "Failed to send udp packet: illegal address format"
-			, -1);
-
-		boost::asio::ip::udp::resolver::results_type eps;
-		resolver_->async_resolve(pair.first, pair.second, 
-			[&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
-
-			if (error) err = error;
-			else eps = results;
-			ch.resume();
-		});
-		ch.suspend();
-		if (err == boost::asio::error::operation_aborted) return nullptr;
-		else if (err) throw php::exception(zend_ce_exception
-			, (boost::format("Failed to resolve address: %s") % err.message()).str()
-			, err.value());
-
-		// 发送
-		int sent = 0;
-		for(auto i=eps.begin(); i!=eps.end(); ++i) {
-			socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
-			if (!err) return nullptr;
-		}
-
-		throw php::exception(zend_ce_exception
-			, (boost::format("Failed to send UDP packet: %s") % err.message()).str()
-			, err.value());
-	}
-	
-	php::value socket::close(php::parameters& params) {
-		connected_ = false;
-		socket_.shutdown(boost::asio::socket_base::shutdown_both);
-		return nullptr;
-	}
+        php::string data = params[0];
+        auto pair = addr2pair(params[1]);
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+            , "Failed to send udp packet: illegal address format"
+            , -1);
+
+        boost::asio::ip::udp::resolver::results_type eps;
+        resolver_->async_resolve(pair.first, pair.second, 
+            [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+
+            if (error) err = error;
+            else eps = results;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err == boost::asio::error::operation_aborted) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to resolve address: %s") % err.message()).str()
+            , err.value());
+
+        // 发送
+        int sent = 0;
+        for(auto i=eps.begin(); i!=eps.end(); ++i) {
+            socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
+            if (!err) return nullptr;
+        }
+
+        throw php::exception(zend_ce_exception
+            , (boost::format("Failed to send UDP packet: %s") % err.message()).str()
+            , err.value());
+    }
+    
+    php::value socket::close(php::parameters& params) {
+        connected_ = false;
+        socket_.shutdown(boost::asio::socket_base::shutdown_both);
+        return nullptr;
+    }
 }
diff --git a/src/udp/socket.h b/src/udp/socket.h
index aebddd6..294a455 100644
--- a/src/udp/socket.h
+++ b/src/udp/socket.h
@@ -4,8 +4,8 @@
 #include "../coroutine_mutex.h"
 
 namespace flame::udp {
-    class socket : public php::class_base
-    {
+    
+    class socket : public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
         socket();
diff --git a/src/udp/udp.cpp b/src/udp/udp.cpp
index c58d721..565143b 100644
--- a/src/udp/udp.cpp
+++ b/src/udp/udp.cpp
@@ -6,24 +6,24 @@
 
 namespace flame::udp {
 
-	std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
+    std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
 
-	void declare(php::extension_entry& ext) {
-		gcontroller->on_init([] (const php::array& options) {
+    void declare(php::extension_entry& ext) {
+        gcontroller->on_init([] (const php::array& options) {
             resolver_.reset(new boost::asio::ip::udp::resolver(gcontroller->context_x));
         })->on_stop([] () {
             resolver_.reset();
         });
-		ext
-			.function<connect>("flame\\udp\\connect", {
-				{"address", php::TYPE::STRING},
-			});
-		socket::declare(ext);
-		server::declare(ext);
-	}
+        ext
+            .function<connect>("flame\\udp\\connect", {
+                {"address", php::TYPE::STRING},
+            });
+        socket::declare(ext);
+        server::declare(ext);
+    }
 
-	php::value connect(php::parameters& params) {
-		php::object obj(php::class_entry<socket>::entry());
+    php::value connect(php::parameters& params) {
+        php::object obj(php::class_entry<socket>::entry());
         socket *ptr = static_cast<socket *>(php::native(obj));
 
         std::string str = params[0];
@@ -32,18 +32,18 @@ namespace flame::udp {
             , "Failed to connect UDP socket: illegal address format"
             , -1);
 
-		coroutine_handler ch{coroutine::current};
+        coroutine_handler ch{coroutine::current};
         boost::system::error_code err;
-		boost::asio::ip::udp::resolver::results_type eps;
-		resolver_->async_resolve(pair.first, pair.second
+        boost::asio::ip::udp::resolver::results_type eps;
+        resolver_->async_resolve(pair.first, pair.second
             , [&err, &obj, &ptr, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) { // DNS 地址解析
 
-			if (error) err = error;
-			else eps = results;
-			ch.resume();
-		});
-		ch.suspend();
-		if (err) throw php::exception(zend_ce_exception
+            if (error) err = error;
+            else eps = results;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err) throw php::exception(zend_ce_exception
                 , (boost::format("Failed to resolve address: %s") % err.message()).str()
                 , err.value());
 
@@ -51,19 +51,19 @@ namespace flame::udp {
             , [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::udp::endpoint &edp) { // 连接
 
             if (error) err = error;
-			else {
-            	obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
-				obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
-			}
+            else {
+                obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
+                obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+            }
             ch.resume();
         });
         ch.suspend();
         if (err) throw php::exception(zend_ce_exception
             , (boost::format("Failed to connect UDP socket: %s") % err.message()).str()
             , err.value());
-		ptr->connected_ = true;
+        ptr->connected_ = true;
         return std::move(obj);
-	}
+    }
     
     std::pair<std::string, std::string> addr2pair(const std::string &addr) {
         char *s = const_cast<char*>(addr.data()), *p, *e = s + addr.size();

From ef1db3c7b772ac3c9c76869f6e6d21882cfe01d4 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 11 Apr 2019 21:16:09 +0800
Subject: [PATCH 098/146] =?UTF-8?q?=E5=8F=96=E6=B6=88=20sasl=20=E5=87=8F?=
 =?UTF-8?q?=E5=B0=91=E4=BE=9D=E8=B5=96=E5=8A=A8=E6=80=81=E5=BA=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt |  2 +-
 README.md      | 12 +++++++++---
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 565e663..30ab49e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -86,7 +86,7 @@ target_link_libraries(FLAME
     ${VENDOR_BOOST}/lib/libboost_filesystem.a
     ${VENDOR_CURL}/lib/libcurl.a
     ${VENDOR_NGHTTP2}/lib/libnghttp2.a
-    sasl2
+#    sasl2
     cares
     ${VENDOR_OPENSSL}/lib/libssl.a
     ${VENDOR_OPENSSL}/lib/libcrypto.a
diff --git a/README.md b/README.md
index 7f8f77c..530bd67 100644
--- a/README.md
+++ b/README.md
@@ -119,11 +119,17 @@ make install
 ``` Bash
 make install
 ```
-
+<!--
+#### sasl2
+``` Bash
+PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/sasl2 --with-openssl=/data/vendor/openssl-1.1.1 --without-ldap --enable-shared=no
+make && make install
+```
+-->
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
 make && make install
 # rm /data/vendor/mongoc-1.14.0/lib/*.so*
 ```
@@ -175,7 +181,7 @@ make && make install
 #### mysql-connector-c
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/mysqlc-8.0.15 -DWITHOUT_SERVER=ON -DWITH_BOOST=../boost/boost_1_68_0 -DWITH_SSL=/data/vendor/openssl-1.1.1 -DOPENSSL_EXECUTABLE=/data/vendor/openssl-1.1.1/bin/openssl -DWITH_CURL=/data/vendor/curl-7.64.1 -DCMAKE_C_FLAGS=-pthread -DCMAKE_CXX_FLAGS=-pthread ../
+CC=gcc CXX=g++ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/mysqlc-8.0.15 -DWITHOUT_SERVER=ON -DWITH_BOOST=../boost/boost_1_68_0 -DWITH_SSL=/data/vendor/openssl-1.1.1 -DOPENSSL_EXECUTABLE=/data/vendor/openssl-1.1.1/bin/openssl -DWITH_CURL=/data/vendor/curl-7.64.1 -DWITH_SASL=OFF -DSASL_SYSTEM_LIBRARY=OFF -DCMAKE_C_FLAGS=-pthread -DCMAKE_CXX_FLAGS=-pthread ../
 make && make install
 # rm /data/vendor/mysqlc-8.0.15/lib/*.so*
 ```

From 1e1d168dc934b4d55a73b817c02bddcb9d2064d4 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 12 Apr 2019 12:49:56 +0800
Subject: [PATCH 099/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9AMongoDB=20?=
 =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E9=80=89=E6=8B=A9=E5=AF=BC=E8=87=B4?=
 =?UTF-8?q?=E5=86=85=E5=AD=98=E6=B3=84=E6=BC=8F=EF=BC=9B=E4=BC=98=E5=8C=96?=
 =?UTF-8?q?=EF=BC=9A=E8=87=AA=E5=8A=A8=E5=9B=9E=E9=80=80=20MySQL=20?=
 =?UTF-8?q?=E6=9C=AA=E6=8F=90=E4=BA=A4=E7=9A=84=E4=BA=8B=E5=8A=A1=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mongodb/_connection_base.cpp | 5 +++--
 src/mysql/tx.cpp                 | 9 ++++++++-
 src/mysql/tx.h                   | 2 ++
 src/vendor.h                     | 1 -
 4 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index 394ad6c..d592468 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -18,18 +18,19 @@ namespace flame::mongodb {
         std::uint32_t server_id;
         int  rok = 0;
 
-        boost::asio::post(gcontroller->context_y, [conn, type, &server_id, &cmd, &rep, &opt, &err, &rok, &ch] () {
+        boost::asio::post(gcontroller->context_y, [conn, type, &cmd, &rep, &opt, &err, &rok, &ch] () {
             mongoc_server_description_t* server;
             switch(type) {
             case COMMAND_READ:
                 server = mongoc_client_select_server(conn.get(), false, nullptr, nullptr);
+                break;
             case COMMAND_RAW:
             case COMMAND_WRITE:
             case COMMAND_READ_WRITE:
             default:
                 server = mongoc_client_select_server(conn.get(), true, nullptr, nullptr);
             }
-            server_id = mongoc_server_description_id(server);
+            uint32_t server_id = mongoc_server_description_id(server);
             mongoc_server_description_destroy(server);
             bson_append_int32(opt.get(), "serverId", 8, server_id);
             switch(type) {
diff --git a/src/mysql/tx.cpp b/src/mysql/tx.cpp
index bd18a03..fda0c10 100644
--- a/src/mysql/tx.cpp
+++ b/src/mysql/tx.cpp
@@ -7,8 +7,8 @@ namespace flame::mysql {
     void tx::declare(php::extension_entry &ext) {
         php::class_entry<tx> class_tx("flame\\mysql\\tx");
         class_tx
-            .constant({"AAAA", 123})
             .method<&tx::__construct>("__construct", {}, php::PRIVATE)
+            .method<&tx::__destruct>("__destruct")
             .method<&tx::escape>("escape", {
                 {"data", php::TYPE::UNDEFINED},
             })
@@ -59,6 +59,11 @@ namespace flame::mysql {
         return nullptr;
     }
 
+    php::value tx::__destruct(php::parameters &params) {
+        if (!done_) rollback(params);
+        return nullptr;
+    }
+
     php::value tx::escape(php::parameters &params) {
         coroutine_handler ch{coroutine::current};
         std::shared_ptr<MYSQL> conn = cl_->acquire(ch);
@@ -74,12 +79,14 @@ namespace flame::mysql {
     }
 
     php::value tx::commit(php::parameters &params) {
+        done_ = true;
         coroutine_handler ch{coroutine::current};
         cl_->commit(ch);
         return nullptr;
     }
 
     php::value tx::rollback(php::parameters &params) {
+        done_ = true;
         coroutine_handler ch{coroutine::current};
         cl_->rollback(ch);
         return nullptr;
diff --git a/src/mysql/tx.h b/src/mysql/tx.h
index d6088cf..c90572f 100644
--- a/src/mysql/tx.h
+++ b/src/mysql/tx.h
@@ -8,6 +8,7 @@ namespace flame::mysql {
     public:
         static void declare(php::extension_entry &ext);
         php::value __construct(php::parameters &params); // 私有
+        php::value __destruct(php::parameters& params);
         php::value commit(php::parameters &params);
         php::value rollback(php::parameters &params);
 
@@ -23,6 +24,7 @@ namespace flame::mysql {
 
     protected:
         std::shared_ptr<_connection_lock> cl_;
+        bool done_ = false;
         friend class client;
     };
 } // namespace flame::mysql
diff --git a/src/vendor.h b/src/vendor.h
index 19efb41..e93682e 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -17,7 +17,6 @@
 #include <stack>
 #include <string_view>
 #include <optional>
-#include <set>
 #include <unordered_set>
 #include <phpext/phpext.h>
 #include <http_parser.h>

From f606d5db2f070db24cb4ce58ef519219a107bf2a Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 12 Apr 2019 13:14:06 +0800
Subject: [PATCH 100/146] php -> v7.2.17

---
 CMakeLists.txt    |   2 +-
 README.md         |   2 +-
 test/http_1.php   | 121 +++++-----------------------------------------
 test/tcp_2.php    |  25 ++++++++++
 test/worker_1.php |   8 ++-
 5 files changed, 42 insertions(+), 116 deletions(-)
 create mode 100644 test/tcp_2.php

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 30ab49e..a930e39 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,7 +17,7 @@ add_library(FLAME SHARED ${SOURCES})
 # -----------------------------------------------------------------------------
 # 依赖项目录
 # -----------------------------------------------------------------------------
-set(VENDOR_PHP /data/vendor/php-7.2.16)
+set(VENDOR_PHP /data/vendor/php-7.2.17)
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
diff --git a/README.md b/README.md
index 530bd67..e75a0e9 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ make && make install
 
 #### PHP
 ``` Bash
-CC=gcc CXX=g++ ./configure --prefix=/data/vendor/php-7.2.16 --with-config-file-path=/data/vendor/php-7.2.16/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --host=x86_64-linux-gnu --target=x86_64-linux-gnu
+CC=gcc CXX=g++ ./configure --prefix=/data/vendor/php-7.2.17 --with-config-file-path=/data/vendor/php-7.2.17/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --build=x86_64-linux-gnu
 ```
 
 #### libphpext
diff --git a/test/http_1.php b/test/http_1.php
index 8e9778b..ad713a0 100644
--- a/test/http_1.php
+++ b/test/http_1.php
@@ -1,115 +1,18 @@
 <?php
 flame\init("http_1");
 
-/*flame\go(function() {*/
-    //flame\http\get("http://127.0.0.1:8082/", 3000);
-    //flame\http\post("http://127.0.0.1:8082/", "post", 3000);
-    //flame\http\put("http://127.0.0.1:8082/", "put", 3000);
-    //flame\http\delete("http://127.0.0.1:8082/", 3000);
-/*});*/
-// 使用独立连接测试
-flame\go(function() {
-    $opt["connection_per_host"] = 32;
-    $cli = new flame\http\client($opt);
-    for($i = 0; $i < 2000; $i++) {
-        flame\go(function() use($i, $cli) {
-            for($j = 0; $j < 5; ++$j) {
-                try {
-                    var_dump('------ ' . $i . ' --- ' . $j . ' 1000');
-                    //$port = rand(8081, 8082);
-                    $port = 8082;
-                    $req = new flame\http\client_request('http://127.0.0.1:' . $port . '/get', null, 1500);
-                    $req->method = "GET";
-                    $cli->exec($req);
-                } catch(Exception $e) {
-                    echo $e;
-                }
-            }
-        });
-    }
+for($i=0;$i<10;++$i) {
     flame\go(function() {
-        $opt2["connection_per_host"] = 16;
-        $cli2 = new flame\http\client($opt2);
-        for($i = 0; $i < 100; $i++) {
-            flame\go(function() use($i, $cli2) {
-                for($j = 0; $j < 50; ++$j) {
-                    try {
-                        var_dump('++++++ ' . $i . ' + ' . $j . ' 1000');
-                        //$port = rand(8081, 8082);
-                        $port = 8082;
-                        $req = new flame\http\client_request('http://127.0.0.1:' . $port . '/get', null, 2000);
-                        $req->method = "GET";
-                        $cli2->exec($req);
-                    } catch(Exception $e) {
-                        echo $e;
-                    }
-                }
-            });
-        }
+        flame\http\get("http://www.baidu.com/", 8000);
+        flame\http\get("http://www.baidu.com/", 8000);
+        flame\http\get("https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/static/protocol/https/home/img/qrcode/zbios_efde696.png", 3000);
+        var_dump( flame\http\put("http://127.0.0.1:8687/", "this is the data for put request", 800000) );
+        var_dump( flame\http\get("http://127.0.0.1:8687/") );
+        $req = new flame\http\client_request('https://www.baidu.com/');
+        $req->method = "POST";
+        $req->http_version(flame\http\client_request::HTTP_VERSION_2_PRI);
+        var_dump(flame\http\exec($req));
+        echo "done.\n";
     });
-});
-// 直接使用全局连接测试
-/*flame\go(function() {*/
-    //for($i = 0; $i < 50; $i++) {
-        //flame\go(function() use($i) {
-            //for($j = 0; $j < 40; ++$j) {
-                //var_dump('====== ' . $i . ' === ' . $j . ' 1000');
-                //flame\http\get("http://127.0.0.1:8081/get", 300);
-            //}
-        //});
-    //}
-    //flame\time\sleep(3000);
-//});
-
-//flame\go(function() {
-    //for($i = 1000; $i < 1500; $i++) {
-        //flame\go(function() use($i) {
-            //var_dump('****** ' . $i . ' 1000');
-            //$port = rand(8081, 8082);
-            //flame\http\get('http://127.0.0.1:' . $port . '/get', 300);
-        //});
-    //}
-/*});*/
-
- //连接超时测试
-/*flame\go(function() {*/
-    //$opt["connection_per_host"] = 1;
-    //$cli = new flame\http\client($opt);
-    //flame\go(function() use($cli) {
-        //try {
-            //var_dump("client ------ 0 4s " . microtime());
-            //$req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 4000);
-            //$req->method = "GET";
-            //$cli->exec($req);
-        //} catch(Exception $e) {
-            //echo $e;
-        //}
-        //var_dump("client ------- 0 4s " . microtime());
-    //});
-    //flame\go(function() use($cli) {
-        //try {
-            //var_dump("client ++++++ 1 1s " . microtime());
-            //$req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 1000);
-            //$req->method = "GET";
-            //$cli->exec($req);
-        //} catch(Exception $e) {
-            //echo $e;
-        //}
-        //var_dump("client ++++++ 1 1s " . microtime());
-    //});
-    //flame\go(function() use($cli) {
-        //try {
-            //var_dump("client ------ 2 15s " . microtime());
-            //$req = new flame\http\client_request("http://127.0.0.1:8081/get", null, 15000);
-            //$req->method = "GET";
-            //$cli->exec($req);
-        //} catch(Exception $e) {
-            //echo $e;
-        //}
-        //var_dump("client ------- 2 15s " . microtime());
-    //});
-
-    //flame\time\sleep(20000);
-/*});*/
-
+}
 flame\run();
diff --git a/test/tcp_2.php b/test/tcp_2.php
new file mode 100644
index 0000000..facb0bc
--- /dev/null
+++ b/test/tcp_2.php
@@ -0,0 +1,25 @@
+<?php
+flame\init("tcp_2");
+
+flame\go(function() {
+    $server = new flame\tcp\server("0.0.0.0:8687");
+    $server->run(function($socket) {
+        $length = 0;
+        while( ($line = $socket->read("\n")) !== null) {
+            echo $line;
+            if(substr($line, 0, 14) == "Content-Length") {
+                $length = intval(substr($line, 16));
+            }else if($line == "\r\n") {
+                if($length > 0) {
+                    $body = $socket->read($length);
+                    echo $body, "\n";
+                }
+                flame\time\sleep(4000);
+                $socket->write("HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nok");
+            }
+        }
+    });
+    echo "done.\n";
+});
+
+flame\run();
diff --git a/test/worker_1.php b/test/worker_1.php
index 0667be2..bf94519 100644
--- a/test/worker_1.php
+++ b/test/worker_1.php
@@ -1,16 +1,14 @@
 <?php
-flame\init("worker_1", [
-    // "logger" => "/tmp/output.txt",
-]);
+flame\init("worker_1");
 
 flame\go(function() {
     flame\time\sleep(1000);
-    echo getenv("FLAME_CUR_WORKER").": abc\n";
+    flame\log\trace("WORKING:", intval(getenv("FLAME_CUR_WORKER")) - 1);
     flame\time\sleep(60000);
 });
 
 flame\on("quit", function() {
-    echo "quit\n";
+    flame\log\trace("QUITING:", intval(getenv("FLAME_CUR_WORKER")) - 1);
     flame\quit();
 });
 

From 3a976bb5899399b3088019b92c760e71d48700e5 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 12 Apr 2019 14:25:11 +0800
Subject: [PATCH 101/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md    | 84 ++++++++++++++++++++++++++++++++--------------------
 doc/core.php |  7 +++--
 2 files changed, 56 insertions(+), 35 deletions(-)

diff --git a/README.md b/README.md
index e75a0e9..bfcb4c4 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,66 @@
 ### FLAME
-最初开发 FLAME 是为了给咱 PHP 争口气，开发者不能总是因为 “并行处理能力不足” 被各方高玩鄙视；最初试用了 [Swoole](https://www.swoole.com/) 但被其进程模型搞得一头雾水，而且可能也是因为早期版本，还出现了各式各样的问题；于是在各方支持下，自行开发了 FLAME 框架；
+为解决 PHP 被认为“并发处理能力不足”的问题，我最初在业务中试用 [Swoole](https://www.swoole.com/) 框架；估计由于使用的是 Swoole 相对早期的版本，在实际业务中出现了各式各样的问题（很多都是内存问题）；同时很多同学对 Swoole 中提供的进程使用形式，都感觉把握比较吃力；  
+于是在各方支持下，开发了 FLAME 框架：
 
-FLAME 能够在很大程度上提高 PHP 处理并行能力, 其定位在于简化开发常见互联网业务高性能服务程序的难度和代价：
-* 网络底层异步化；
-* 同步驱动连接池挂接线程池；
-* 使用 PHP 内置协程 (Generator) 机制；
-* 对等多进程工作模式；
+* 基于协程机制的异步化网络底层（ TCP / UDP / HTTP)；
+* 简单的多进程模式 （ SO_REUSEPORT ）；
+* 基于协程的队列机制（ 类似 Go 的 Channel）；
+* 多种驱动异步化支持（ MySQL / Redis / MongoDB / RabbitMQ / Kafka )；
 
-陆续接入了 MySQL / Kafka / Redis / MongoDB / RabbitMQ 等第三方驱动，满足日常大多数接口型开发需求；
+同时为了简化日常应用开发成本，及很多重复性工作，框架还提供了：
+* 平滑起停；
+* 多进程日志、登等级过滤、文件重载；
+* 数据库连接池机制；
+* 异步进程；
+* 等等（BSON / SNAPPY / INTERFACE）...
 
-为了简化框架的学习成本、使用难度，在后期使用内置协程机制代替 `yield` 显式协程，并补充完善了很多功能：
-* 协程队列，协程管道；
-* 协程、异步错误捕获；
-* 异步进程；网卡地址读取；毫秒、标准时间；...
-* 平滑起停控制；
-* 多进程日志、重载；
-* ...
+目前，FLAME 已经使用在了公司内各种业务线；实际使用中，框架在**并行处理性能**方面表现十分**突出**，并在持续的改进、完善下，框架在**稳定性**方面也有了**不错**的表现；  
+不过，由于时间、能力问题，未能详尽的测试整个框架的各项功能，可能还存在一些问题、缺陷；感谢大家的包容、反馈与帮助（大礼参拜）
 
-在适配到 PHP 7.2 版本以后，陆陆续续已经使用在了公司内各种业务线；在稳定性方面已经有了一定的底子；
+### 示例
+以下代码实现了几个简单的 HTTP 接口，展示了框架的基本使用方法：
 
-当然说的很高大上、白富美，但实在能力有限，同时各种业务压力，没有时间详尽的测试整个框架的各项组合功能，框架可能存在各类问题、缺陷；迫切期望大家能帮忙发现和完善，在此感谢~ 大礼参拜~
-
-以下代码实现了几个简单的 HTTP 接口：
 ``` PHP
+<?php
+// 框架初始化
 flame\init("http_server_demo");
+// 第一个（主）协程
 flame\go(function() {
+    // 创建 HTTP 服务器（监听）
     $server = new flame\http\server(":::56101");
-    $server->before(function($req, $res) {
-            $req->data["before"] = flame\time\now();
-        })->get("/hello", function($req, $res) {
+
+    $server
+        ->before(function($req, $res) { // 前置处理器（HOOK）
+            $req->data["before"] = flame\time\now(); // 记录请求开始时间
+        })
+        ->get("/hello", function($req, $res) { // 路径处理器
+            // 简单响应方式
+            $res->status = 200;
             $res->body = "world";
-        })->post("/path", function($req, $res) {
+        })
+        ->post("/hello/world", function($req, $res) { // 路径处理器
+            // Transfer-Encoding: Chunked
+            $res->write_header(200);
             $res->write("CHUNKED RESPONSE:")
             $res->write($res->body);
             $res->end();
-        })->after(function($req, $res, $r) {
+        })
+        ->after(function($req, $res, $r) {
+            // 后置处理器（HOOK）
+            flame\log\trace($req->method, $req->path // 请求时长日志记录
+                , "in", (flame\time\now() - $req->data["before"]), "ms");
             if(!$r) {
                 $res->status = 404;
-                $res->file(__DIR__."/404.html");
+                $res->file(__DIR__."/404.html"); // 响应文件
             }
         });
     $server->run();
 });
+// 启动（调度）
 flame\run();
 ```
 
-### 功能项
+### 功能
 
 * **core** - 核心，框架初始化设置，协程启动，协程队列，协程锁等；
 * **time** - 时间相关，协程休眠，当前时间等；
@@ -82,15 +97,17 @@ flame\run();
 * **encoding** - 提供了若干编码、序列化函数；
 * **compress** - 提供了若干压缩算法；
 
-请参看 `/doc` `/test` 目录下相关 PHP 文件的文档注释；也可将 `/doc` 挂接在 IDE 内方便提供 **自动完成** 等；
 
-### 常见问题
 
-https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
+### 文档
+* [API 文档](https://github.com/terrywh/php-flame/tree/master/doc)
+* [常见问题](https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
 
-### 依赖库
+> 将 `/doc` 挂接在 IDE 内可提供 **自动完成** 等功能
 
-> 需要基础编译组建, 及 c-ares / sasl2 等库;
+### 其他
+<details><summary>依赖项编译安装，仅供参考</summary>
+<p>
 
 #### openssl
 ``` Bash
@@ -129,7 +146,7 @@ make && make install
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake3 -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
 make && make install
 # rm /data/vendor/mongoc-1.14.0/lib/*.so*
 ```
@@ -143,7 +160,7 @@ make && make install
 
 #### rdkafka
 ``` Bash
-CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.0.0
+CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.0.0 --disable-sasl
 # rm /data/vendor/rdkafka-1.0.0/lib/*.so*
 cp src/snappy.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
 cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
@@ -186,3 +203,6 @@ make && make install
 # rm /data/vendor/mysqlc-8.0.15/lib/*.so*
 ```
 注意：需要下载内含 boost 头文件的版本（与框架依赖版本不同）；
+
+</p>
+</details>
\ No newline at end of file
diff --git a/doc/core.php b/doc/core.php
index 731e434..6cbc0b3 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -7,7 +7,7 @@
  * 初始化框架, 设置进程名称及相关配置;
  * @param string $process_name 进程名称
  * @param array  $options 选项配置, 目前可用如下:
- *  * "logger" - 日志输出重定向目标文件(完整路径, 若不提供使用标准输出);
+ *  * "logger" - 日志输出重定向目标文件 (完整路径, 若不提供使用标准输出);
  *      向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
  *  * "level" - 日志输出级别, 设置该级别下的日志将不被记录; 可用级别如下
  *      "debug"
@@ -15,11 +15,12 @@
  *      "warning"
  *      "error"
  *      "fatal"
- *  * "timeout" - 多进程退出超时, 单位毫秒( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
+ *  * "timeout" - 多进程退出超时, 单位毫秒 ( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
  *  @see flame\log
  *
- * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式
+ * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式；
  * 主进程将自动进行日志文件写入，子进程自动拉起；
+ * 向主进程发送 SIGUSR1 信号将进行进程重载；（陆续停止当前进程并启动新进程）
  */
 function init($process_name, $options = []) {}
 /**

From 3f2c3dd4763d088ddadb1a4395438f8dd7f79189 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 15 Apr 2019 11:08:22 +0800
Subject: [PATCH 102/146] =?UTF-8?q?=E4=B8=8D=E6=94=B9=E5=8F=98=20Query=20?=
 =?UTF-8?q?=E5=AE=9E=E9=99=85=E5=A4=A7=E5=B0=8F=E5=86=99=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                    |  6 +++---
 src/coroutine.cpp            | 20 +++++++++++++++----
 src/coroutine.h              | 37 ++++++++++++++++++++++++++----------
 src/coroutine_mutex.h        |  6 +++---
 src/coroutine_queue.h        | 18 +++++++++---------
 src/http/client_response.cpp |  1 -
 src/tcp/socket.cpp           |  2 +-
 src/url.cpp                  | 10 ++++++----
 src/url.h                    |  1 -
 9 files changed, 65 insertions(+), 36 deletions(-)

diff --git a/README.md b/README.md
index bfcb4c4..5b21291 100644
--- a/README.md
+++ b/README.md
@@ -117,8 +117,8 @@ make && make install
 
 #### boost
 ``` Bash
-./bootstrap.sh --prefix=/data/vendor/boost-1.69.0
-./b2 -j4 --prefix=/data/vendor/boost-1.69.0 cxxflags="-fPIC" variant=release link=static threading=multi install
+./bootstrap.sh --prefix=/data/vendor/boost-1.70.0
+./b2 --prefix=/data/vendor/boost-1.70.0 cxxflags="-fPIC" variant=release link=static threading=multi install
 ```
 
 #### PHP
@@ -185,7 +185,7 @@ PREFIX=/data/vendor/hiredis-0.14.0 make install
 
 #### nghttp2
 ``` Bash
-PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/nghttp2-1.37.0 --enable-shared=no --enable-lib-only --with-boost=/data/vendor/boost-1.69.0 --enable-asio-lib
+PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/nghttp2-1.37.0 --enable-shared=no --enable-lib-only --with-boost=/data/vendor/boost-1.70.0 --enable-asio-lib
 make && make install
 ```
 
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index 5491e30..b524950 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -119,16 +119,23 @@ namespace flame {
     }
     coroutine_handler::coroutine_handler()
     : co_(nullptr)
-    , er_(nullptr)
+    , err_(nullptr)
     , stat_(new std::atomic<int>(0)) {
     }
 
     coroutine_handler::coroutine_handler(std::shared_ptr<coroutine> co)
     : co_(co)
-    , er_(nullptr)
+    , err_(nullptr)
     , stat_(new std::atomic<int>(0)) {
     }
 
+    // coroutine_handler::coroutine_handler(coroutine_handler&& ch)
+    // : co_(std::move(ch.co_))
+    // , err_(ch.err_)
+    // , stat_(std::move(ch.stat_)) {
+    //     ch.err_ = nullptr;
+    // }
+
     coroutine_handler::~coroutine_handler() {
         // std::cout << "~coroutine_handler\n";
     }
@@ -149,14 +156,19 @@ namespace flame {
     }
 
     void coroutine_handler::operator() (const boost::system::error_code &e, std::size_t n) {
-        if (er_) *er_ = e;
+        if (err_) *err_ = e;
         co_->len_ = n;
+        resume();
+    }
 
+    void coroutine_handler::operator() (const boost::system::error_code &e) {
+        if (err_) *err_ = e;
+        // co_->len_ = 0;
         resume();
     }
 
     coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e) {
-        er_ = &e;
+        err_ = &e;
         return *this;
     }
 
diff --git a/src/coroutine.h b/src/coroutine.h
index 05f1359..7ca57fb 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -49,35 +49,52 @@ namespace flame {
         // !!! 慎用: 目前仅用于空构造后指定协程
         void reset(std::shared_ptr<coroutine> co);
         void reset();
-        operator bool() const;
-        void operator()(const boost::system::error_code& e, std::size_t n = 0);
-        coroutine_handler& operator [](boost::system::error_code& e);
         void resume();
         void suspend();
-        boost::system::error_code* er_;
+
+        operator bool() const;
+        void operator()(const boost::system::error_code& e, std::size_t n);
+        void operator()(const boost::system::error_code& e);
+        coroutine_handler& operator [](boost::system::error_code& e);
+        
+        boost::system::error_code* err_;
         std::shared_ptr<coroutine> co_;
         std::shared_ptr<std::atomic<int>> stat_;
-
         friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
-
       private:
         
     };
 } // namespace flame
 
 namespace boost::asio {
-
     template <>
-    class async_result<::flame::coroutine_handler> {
+    class async_result<::flame::coroutine_handler, void (boost::system::error_code error, std::size_t size)> {
     public:
         explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
         }
-        using type = std::size_t;
-        type get() {
+        using completion_handler_type = ::flame::coroutine_handler;
+        using return_type = std::size_t;
+        return_type get() {
             ch_.suspend();
             return ch_.co_->len_;
         }
     private:
         ::flame::coroutine_handler &ch_;
     };
+
+    template <>
+    class async_result<::flame::coroutine_handler, void (boost::system::error_code error)> {
+    public:
+        explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
+        }
+        using completion_handler_type = ::flame::coroutine_handler;
+        using return_type = void;
+        void get() {
+            ch_.suspend();
+        }
+    private:
+        ::flame::coroutine_handler &ch_;
+    };
+
+    
 } // namespace boost::asio
diff --git a/src/coroutine_mutex.h b/src/coroutine_mutex.h
index 264d540..bce30ee 100644
--- a/src/coroutine_mutex.h
+++ b/src/coroutine_mutex.h
@@ -11,7 +11,7 @@ namespace flame {
 
         void lock(coroutine_handler& ch) {  
             while(mt_) {
-                cm_.insert(ch);
+                cm_.insert(&ch);
                 ch.suspend();
             }
             mt_ = true;
@@ -28,12 +28,12 @@ namespace flame {
         void unlock() {
             while(!cm_.empty()) {
                 auto ch = cm_.extract(cm_.begin()).value();
-                ch.resume();
+                ch->resume();
             }
             mt_ = false;
         }
     private:
-        std::set<coroutine_handler> cm_;
+        std::set<coroutine_handler*> cm_;
         bool mt_;
     };
     class coroutine_guard {
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
index 87de4f9..e82ac70 100644
--- a/src/coroutine_queue.h
+++ b/src/coroutine_queue.h
@@ -15,7 +15,7 @@ namespace flame {
             closed_ = true;
             while (!c_.empty()) {
                 auto ch = c_.extract(c_.begin()).value();
-                ch.resume();
+                ch->resume();
             }
         }
         bool is_closed() {
@@ -27,13 +27,13 @@ namespace flame {
                 , "Failed to push queue: already closed"
                 , -1);
             if (q_.size() >= n_) {
-                p_.insert(ch);
+                p_.insert(&ch);
                 ch.suspend();
             }
             q_.push_back(t);
             while(!q_.empty() && !c_.empty()) {
                 auto ch = c_.extract(c_.begin()).value();
-                ch.resume();
+                ch->resume();
             }
         }
         std::optional<T> pop(coroutine_handler& ch) {
@@ -41,7 +41,7 @@ namespace flame {
                 if (!q_.empty()) break; // 有数据消费
                 else if (closed_) return std::optional<T>();  // 无数据关闭
                 else { // 无数据等待
-                    c_.insert(ch);
+                    c_.insert(&ch);
                     ch.suspend();
                 }
             }
@@ -50,7 +50,7 @@ namespace flame {
 
             while (!p_.empty()) {
                 auto ch = p_.extract(p_.begin()).value();
-                ch.resume();
+                ch->resume();
             }
             return std::optional<T>(t);
         }
@@ -58,8 +58,8 @@ namespace flame {
     /* private: */
         std::size_t                 n_;
         std::list<T>                q_;
-        std::set<coroutine_handler> c_; // 消费者
-        std::set<coroutine_handler> p_; // 生产者
+        std::set<coroutine_handler*> c_; // 消费者
+        std::set<coroutine_handler*> p_; // 生产者
         bool                   closed_;
     };
 
@@ -72,9 +72,9 @@ namespace flame {
             else if (!(*i)->closed_) all_closed = false;
         }
         if (all_closed) return std::shared_ptr< coroutine_queue<T> >(nullptr);
-        for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(ch);
+        for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(&ch);
         ch.suspend();
-        for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(ch);
+        for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(&ch);
         goto TRY_ALL;
     }
 
diff --git a/src/http/client_response.cpp b/src/http/client_response.cpp
index e7cac92..9d24990 100644
--- a/src/http/client_response.cpp
+++ b/src/http/client_response.cpp
@@ -39,7 +39,6 @@ namespace flame::http {
         set("raw_body", rbody);
         if (c_head_.exists("content-type")) {
             php::string ctype = c_head_.get("content-type");
-            std::cout << "content-type: " << ctype << "\n";
             std::string_view sv {ctype.c_str(), ctype.size()};
             set("body", ctype_decode(sv, rbody));
         }
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
index 0d85ee4..90ca85d 100644
--- a/src/tcp/socket.cpp
+++ b/src/tcp/socket.cpp
@@ -73,7 +73,7 @@ namespace flame::tcp {
         
         boost::system::error_code err;
         std::string data = params[0];
-        boost::asio::async_write(socket_, boost::asio::buffer(data), ch);
+        std::size_t len = boost::asio::async_write(socket_, boost::asio::buffer(data), ch);
 
         if (!err || err == boost::asio::error::operation_aborted) return nullptr;
         else throw php::exception(zend_ce_exception
diff --git a/src/url.cpp b/src/url.cpp
index 2eb1781..1dbbf87 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -37,13 +37,10 @@ namespace flame {
         }
         if (x.field_set & (1 << UF_QUERY)) {
             auto y = x.field_data[UF_QUERY];
-            query_raw.assign(s + y.off, y.len);
             if (parse_query) {
                 parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
                     std::string key = et.first;
                     std::string val = et.second;
-                    // 一律小写 KEY
-                    php::lowercase_inplace(key.data(), key.size());
                     val.resize(php::url_decode_inplace(val.data(), val.size()));
                     query[key] = val;
                 });
@@ -57,7 +54,12 @@ namespace flame {
         if (update) {
             std::ostringstream ss;
             ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path;
-            if (with_query) ss << "?" << query_raw;
+            if (with_query) {
+                ss << "?";
+                for (auto i=query.begin();i!=query.end();++i) {
+                    ss << i->first << "=" << php::url_encode(i->second.c_str(), i->second.size()) << "&";
+                }
+            }
             raw_ = ss.str();
         }
         return raw_;
diff --git a/src/url.h b/src/url.h
index cc08a2f..6b863eb 100644
--- a/src/url.h
+++ b/src/url.h
@@ -10,7 +10,6 @@ namespace flame {
         std::uint16_t port;
         std::string   path;
         std::map<std::string, std::string> query;
-        std::string   query_raw;
         std::string   user;
         std::string   pass;
 

From 9e99fa8909f9e2df50ed9ef961f1f00c7961caba Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 15 Apr 2019 11:09:13 +0800
Subject: [PATCH 103/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E5=85=BC?=
 =?UTF-8?q?=E5=AE=B9=20proxysql=20=EF=BC=8C=E9=98=B2=E6=AD=A2=E7=94=B1?=
 =?UTF-8?q?=E4=BA=8E=E5=AD=97=E7=AC=A6=E9=9B=86=E5=AF=BC=E8=87=B4=E9=A6=96?=
 =?UTF-8?q?=E4=B8=AA=E6=9F=A5=E8=AF=A2=E5=8D=A1=E4=BD=8F=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mysql/_connection_pool.cpp | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index c5d12c0..b251b7d 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -75,7 +75,7 @@ namespace flame::mysql {
     void _connection_pool::init_options(MYSQL* c) {
         // 这里的 CHARSET 设定会被 reset_connection 重置为系统值
         mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
-        // 8.0.15 默认使用 caching_sha2_password 进行认证，底版本不支持
+        // 8.0.15 默认使用 caching_sha2_password 进行认证，低版本不支持
         mysql_options(c, MYSQL_DEFAULT_AUTH, "mysql_native_password");
         // SSL 参数
         unsigned int ssl = SSL_MODE_DISABLED;
@@ -104,20 +104,25 @@ namespace flame::mysql {
             }
             if (flag_ & FLAG_REUSE_BY_RESET) mysql_reset_connection(c);
             else if (flag_ & FLAG_REUSE_BY_CUSER) mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
-            else if (flag_ & FLAG_REUSE_BY_PROXY) ;// PROXY 已处理复用流程，此处无需处理
+            // else if (flag_ & FLAG_REUSE_BY_PROXY) ; // PROXY 已处理复用流程，此处无需处理
             
             if (flag_ & FLAG_CHARSET_DIFFER) mysql_set_character_set(c, url_.query["charset"].c_str());
             std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
             await_.pop_front();
             auto self = this->shared_from_this();
             // 释放回调函数须持有当前对象引用 self (否则连接池可能先于连接归还被销毁)
-            cb(std::shared_ptr<MYSQL>(c, [this, self](MYSQL *c) {
+            cb(std::shared_ptr<MYSQL>(c, [this, self] (MYSQL *c) {
                 boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
             }));
         }
     }
 
     void _connection_pool::query_charset(MYSQL* c) {
+        // 兼容 proxysql 流程：纯粹的字符集设置，首次 SQL 会卡住:
+        // 代理：2019, Can't initialize character set (null) (path: compiled_in)
+        // 后端：不断尝试连接、断开
+        std::string sql = (boost::format("SET NAMES '%s'") % url_.query["charset"]).str();
+        mysql_real_query(c, sql.c_str(), sql.size());
         // 使用 mysql_get_charactor_set_name() 获取到的字符集与下述查询不同
         if (0 == mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59)) {
             std::unique_ptr<MYSQL_RES, void (*)(MYSQL_RES*)> rst(mysql_store_result(c), mysql_free_result);

From 100d67e349dd2a0fe3ad3e5d2f256679ebf3847c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 15 Apr 2019 11:46:05 +0800
Subject: [PATCH 104/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E7=BB=91?=
 =?UTF-8?q?=E5=AE=9A=E5=BC=82=E5=B8=B8=E5=BA=94=E5=A4=84=E7=90=86=E4=B8=BA?=
 =?UTF-8?q?=20PHP=20=E6=B5=81=E7=A8=8B=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/server.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/http/server.cpp b/src/http/server.cpp
index f8ffd38..a71898d 100644
--- a/src/http/server.cpp
+++ b/src/http/server.cpp
@@ -127,7 +127,11 @@ namespace flame::http {
     }
 
     php::value server::run(php::parameters &params) {
-        accp_.bind(addr_);
+        boost::system::error_code err;
+        accp_.bind(addr_, err);
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to bind TCP socket: %s") % err.message()).str()
+            , err.value());
         accp_.listen(boost::asio::socket_base::max_listen_connections);
 
         coroutine_handler ch{coroutine::current};

From 9207e4f85e2faaaaa0e571d0c369b16bc2e3ee68 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 16 Apr 2019 10:57:27 +0800
Subject: [PATCH 105/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E8=8E=B7?=
 =?UTF-8?q?=E5=8F=96=20SOCKET=20=E7=B1=BB=E5=9E=8B/=E5=9C=B0=E5=9D=80?=
 =?UTF-8?q?=E7=BC=BA=E5=B0=91=E5=8F=82=E6=95=B0=E9=BB=98=E8=AE=A4=E5=80=BC?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt           |  1 -
 src/http/client_poll.cpp | 26 ++++++++++++--------------
 2 files changed, 12 insertions(+), 15 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index a930e39..5087c6a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -86,7 +86,6 @@ target_link_libraries(FLAME
     ${VENDOR_BOOST}/lib/libboost_filesystem.a
     ${VENDOR_CURL}/lib/libcurl.a
     ${VENDOR_NGHTTP2}/lib/libnghttp2.a
-#    sasl2
     cares
     ${VENDOR_OPENSSL}/lib/libssl.a
     ${VENDOR_OPENSSL}/lib/libcrypto.a
diff --git a/src/http/client_poll.cpp b/src/http/client_poll.cpp
index 67e3e5e..fa7b65e 100644
--- a/src/http/client_poll.cpp
+++ b/src/http/client_poll.cpp
@@ -5,22 +5,20 @@
 namespace flame::http {
     
     client_poll* client_poll::create_poll(boost::asio::io_context &io, curl_socket_t fd, client_poll::poll_callback_t cb, void* data) {
-        int       type;
-        socklen_t size;
+        int       type, r;
+        socklen_t size = sizeof(type);
         struct sockaddr addr;
-        int x = ::getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &size);
-        assert(x == 0);
-        ::getsockname(fd, &addr, &size);
+        r = ::getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &size);
+        assert(r == 0);
+        size = sizeof(addr);
+        r = ::getsockname(fd, &addr, &size);
+        assert(r == 0);
         client_poll* poll;
-        if (type == SOCK_STREAM) {
-            poll = new client_poll_tcp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::tcp::v6() : boost::asio::ip::tcp::v4(), ::dup(fd));
-        }
-        else if (type == SOCK_DGRAM) {
-            poll = new client_poll_udp(io, addr.sa_family == AF_INET6 ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
-        }
-        else {
-            return nullptr;
-        }
+        if (type == SOCK_STREAM) poll = new client_poll_tcp(io, addr.sa_family == AF_INET6
+            ? boost::asio::ip::tcp::v6() : boost::asio::ip::tcp::v4(), ::dup(fd));
+        else if (type == SOCK_DGRAM) poll = new client_poll_udp(io, addr.sa_family == AF_INET6
+            ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
+        else return nullptr;
         poll->data = data;
         poll->fd_ = fd;
         poll->cb_ = cb;

From 5e173f6ba4d1fb0c204ffc17afae2ed23b014c9e Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 16 Apr 2019 14:35:14 +0800
Subject: [PATCH 106/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E6=94=AF?=
 =?UTF-8?q?=E6=8C=81=20count=20=E6=9F=A5=E8=AF=A2=20skip/limit=20=E5=8F=82?=
 =?UTF-8?q?=E6=95=B0=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp         |  2 +-
 src/mongodb/collection.cpp | 10 +++++++++-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/controller.cpp b/src/controller.cpp
index 6418bf4..7bc3d96 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -33,7 +33,7 @@ namespace flame {
         return this;
     }
     void controller::initialize(const std::string& title, const php::array& options) {
-        // 多进程模式退出超时时间，默认 1s 超时强制退出
+        // 多进程模式退出超时时间，默认 3000ms 超时强制退出
         if (options.exists("timeout"))
             worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
         else worker_quit = 3000;
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index 20ea059..de7e381 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -245,7 +245,15 @@ namespace flame::mongodb {
         php::array cmd(8);
         cmd.set("count", name_);
         cmd.set("query", params[0]);
-
+        if (params.size() > 1) {
+            if (params[1].typeof(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
+            else if (params[1].typeof(php::TYPE::ARRAY)) {
+                php::array limits = params[1];
+                if (limits.size() > 0) cmd.set("skip", limits[0].to_integer());
+                if (limits.size() > 1) cmd.set("limit", limits[1].to_integer());
+            }
+        }
+        if (params.size() > 1 && params[1].typeof(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
         php::array cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);

From c2ce7d6c8d200a6ff007dfd91725036923e4bcc8 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 16 Apr 2019 14:46:51 +0800
Subject: [PATCH 107/146] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20error=5Fexception?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/server.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/http/server.cpp b/src/http/server.cpp
index a71898d..64016c4 100644
--- a/src/http/server.cpp
+++ b/src/http/server.cpp
@@ -129,7 +129,7 @@ namespace flame::http {
     php::value server::run(php::parameters &params) {
         boost::system::error_code err;
         accp_.bind(addr_, err);
-        if (err) throw php::exception(zend_ce_exception
+        if (err) throw php::exception(zend_ce_error_exception
             , (boost::format("Failed to bind TCP socket: %s") % err.message()).str()
             , err.value());
         accp_.listen(boost::asio::socket_base::max_listen_connections);
@@ -139,7 +139,7 @@ namespace flame::http {
             boost::system::error_code err;
             accp_.async_accept(sock_, ch[err]);
             if (err == boost::asio::error::operation_aborted) break; 
-            else if (err) throw php::exception(zend_ce_exception
+            else if (err) throw php::exception(zend_ce_error_exception
                 , (boost::format("Failed to accept connection: %s") % err.message()).str()
                 , err.value());
             else std::make_shared<_handler>(this, std::move(sock_))->start();

From 54d7a75b3197f68f9e4e55db3f5fb89ea6ed061c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 16 Apr 2019 16:27:30 +0800
Subject: [PATCH 108/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E7=BB=A7?=
 =?UTF-8?q?=E6=89=BF=E9=99=84=E8=BF=9B=E7=A8=8B=20-c=20php.ini=20=E8=AE=BE?=
 =?UTF-8?q?=E7=BD=AE=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php              | 8 +++++---
 src/controller_master.cpp | 5 ++++-
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index 6cbc0b3..299ce40 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -18,9 +18,11 @@
  *  * "timeout" - 多进程退出超时, 单位毫秒 ( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
  *  @see flame\log
  *
- * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式；
- * 主进程将自动进行日志文件写入，子进程自动拉起；
- * 向主进程发送 SIGUSR1 信号将进行进程重载；（陆续停止当前进程并启动新进程）
+ * 多进程模式：
+ *  使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式；
+ *  主进程将自动进行日志文件写入，子进程自动拉起；
+ *  子进程继承主进程的环境变量/ -c /path/to/php.ini 配置（通过命令行 -d XXX=XXX 的临时设置无效）；
+ *  向主进程发送 SIGUSR1 信号将进行进程重载；（陆续停止当前进程并启动新进程）
  */
 function init($process_name, $options = []) {}
 /**
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 55a65be..56b968a 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -1,11 +1,14 @@
 #include "controller_master.h"
 #include "controller.h"
 
+PHPAPI extern char *php_ini_opened_path;
+
 namespace flame {
 
     static std::string start_command_line() {
         std::ostringstream ss;
         ss << php::constant("PHP_BINARY");
+        ss << " -c " << php_ini_opened_path;
         php::array argv = php::server("argv");
         for (auto i = argv.begin(); i != argv.end(); ++i) ss << " " << i->second;
         return ss.str();
@@ -14,7 +17,7 @@ namespace flame {
     controller_master_worker::controller_master_worker(controller_master* m, int i)
     : m_(m), i_(i)
     , sout_(gcontroller->context_x), eout_(gcontroller->context_x) {
-        // 准备命令行及环境变量
+        // 准备命令行及环境变量（完全重新启动一个新的 PHP， 通过环境变量标识其为工作进程）
         std::string cmd = start_command_line();
         boost::process::environment env = gcontroller->env;
         env["FLAME_CUR_WORKER"] = std::to_string(i + 1);

From 3b971c9078cffe6599a57ec5e6f970b4c6239f7c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 16 Apr 2019 17:57:14 +0800
Subject: [PATCH 109/146] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E4=BE=9D=E8=B5=96=20?=
 =?UTF-8?q?phpext=20=E6=9B=BF=E6=8D=A2=20typeof=20=3D>=20type=5Fof=20?=
 =?UTF-8?q?=E8=A7=84=E9=81=BF=E5=85=B3=E9=94=AE=E5=AD=97=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                   |  1 +
 CMakeLists.txt               | 17 +++++----
 src/encoding/encoding.cpp    |  2 +-
 src/flame.cpp                |  4 +--
 src/http/_handler.cpp        |  4 +--
 src/http/client_request.cpp  |  6 ++--
 src/http/http.cpp            |  4 +--
 src/http/server_request.cpp  |  2 +-
 src/http/server_response.cpp |  6 ++--
 src/kafka/kafka.cpp          |  2 +-
 src/kafka/producer.cpp       |  4 +--
 src/mongodb/collection.cpp   | 32 ++++++++---------
 src/mongodb/cursor.cpp       |  4 +--
 src/mongodb/mongodb.cpp      |  2 +-
 src/mongodb/object_id.cpp    |  2 +-
 src/mysql/client.cpp         |  2 +-
 src/mysql/mysql.cpp          | 68 ++++++++++++++++++------------------
 src/mysql/result.cpp         |  4 +--
 src/mysql/tx.cpp             |  2 +-
 src/os/os.cpp                |  4 +--
 src/rabbitmq/rabbitmq.cpp    | 12 +++----
 src/redis/client.cpp         |  8 ++---
 src/redis/tx.cpp             |  8 ++---
 src/tcp/socket.cpp           |  4 +--
 src/udp/socket.cpp           |  6 ++--
 25 files changed, 105 insertions(+), 105 deletions(-)

diff --git a/.gitignore b/.gitignore
index 06dbfd8..1a29d44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
 .vscode
 debug
 release
+stage
 compile_commands.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5087c6a..87a8e2e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,6 +5,11 @@ cmake_minimum_required(VERSION 3.13)
 if(NOT CMAKE_BUILD_TYPE)
     set(CMAKE_BUILD_TYPE Debug)
 endif(NOT CMAKE_BUILD_TYPE)
+set(CLANG_GCC_TOOLCHAIN "<default>" CACHE PATH "gcc toolchain install prefix, used with clang --gcc-toolchain option")
+if (NOT (CLANG_GCC_TOOLCHAIN STREQUAL "<default>"))
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --gcc-toolchain=${CLANG_GCC_TOOLCHAIN}")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --gcc-toolchain=${CLANG_GCC_TOOLCHAIN}")
+endif()
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 # -----------------------------------------------------------------------------
 # 项目源文件
@@ -22,7 +27,7 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
     OUTPUT_STRIP_TRAILING_WHITESPACE)
-set(VENDOR_PHPEXT /data/vendor/phpext-1.1.3)
+set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
 set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
@@ -37,18 +42,12 @@ set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1)
 # -----------------------------------------------------------------------------
 # 编译选项
 # -----------------------------------------------------------------------------
-if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND WITH_GCC_TOOLCHAIN)
-    set(CLANG_EXTRA_COMPILE_FLAGS "--gcc-toolchain=${WITH_GCC_TOOLCHAIN}")
-    set(CLANG_EXTRA_LINK_FLAGS "--gcc-toolchain=${WITH_GCC_TOOLCHAIN}")
-endif()
 target_compile_options(FLAME BEFORE
 	PRIVATE "-std=c++17"
-    PRIVATE "-pthread"
-	PRIVATE "${CLANG_EXTRA_COMPILE_FLAGS}")
+    PRIVATE "-pthread")
 target_link_options(FLAME BEFORE
 	PRIVATE "-static-libstdc++"
-    PRIVATE "-pthread"
-	PRIVATE "${CLANG_EXTRA_COMPILE_FLAGS}")
+    PRIVATE "-pthread")
 set_target_properties(FLAME PROPERTIES
     PREFIX ""
     OUTPUT_NAME "flame")
diff --git a/src/encoding/encoding.cpp b/src/encoding/encoding.cpp
index e7b091b..c35b5be 100644
--- a/src/encoding/encoding.cpp
+++ b/src/encoding/encoding.cpp
@@ -3,7 +3,7 @@
 
 namespace flame::encoding {
     static php::value bson_encode(php::parameters &params) {
-        if (params[0].typeof(php::TYPE::ARRAY)) {
+        if (params[0].type_of(php::TYPE::ARRAY)) {
             auto doc = flame::mongodb::array2bson(params[0]);
             return php::string((const char*)bson_get_data(doc.get()), doc->len);
         }
diff --git a/src/flame.cpp b/src/flame.cpp
index c35ada8..fa6d617 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -22,7 +22,7 @@
 namespace flame {
     static php::value init(php::parameters& params) {
         php::array options(0);
-        if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) options = params[1];
+        if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
         gcontroller->initialize(params[0], options);
         return nullptr;
     }
@@ -56,7 +56,7 @@ namespace flame {
 
     static php::value on(php::parameters &params) {
         std::string event = params[0].to_string();
-        if (!params[1].typeof(php::TYPE::CALLABLE)) throw php::exception(zend_ce_type_error
+        if (!params[1].type_of(php::TYPE::CALLABLE)) throw php::exception(zend_ce_type_error
             , "Failed to set callback: callable required"
             , -1);
         gcontroller->cbmap->insert({event, params[1]});
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index 19a795f..c646f5c 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -79,9 +79,9 @@ namespace flame::http {
             try {
                 php::value rv = true;
                 if (ib != svr_ptr->cb_.end()) rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
-                if (rv.typeof(php::TYPE::NO)) goto HANDLE_BREAK;
+                if (rv.type_of(php::TYPE::NO)) goto HANDLE_BREAK;
                 if (ih != svr_ptr->cb_.end()) rv = ih->second.call({req, res});
-                if (rv.typeof(php::TYPE::NO)) goto HANDLE_BREAK;
+                if (rv.type_of(php::TYPE::NO)) goto HANDLE_BREAK;
                 if (ia != svr_ptr->cb_.end()) rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
 HANDLE_BREAK:
                 if (!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/) {
diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 817ceb0..7cb0a13 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -85,11 +85,11 @@ namespace flame::http {
         curl_easy_setopt(c_easy_, CURLOPT_SSLCERTTYPE, "PEM");
         php::string cert = params[0];
         curl_easy_setopt(c_easy_, CURLOPT_SSLCERT, cert.c_str());
-        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
+        if (params.size() > 1 && params[1].type_of(php::TYPE::STRING)) {
             php::string pkey = params[1];
             curl_easy_setopt(c_easy_, CURLOPT_SSLKEY, pkey.c_str());
         }
-        if (params.size() > 2 && params[2].typeof(php::TYPE::STRING)) {
+        if (params.size() > 2 && params[2].type_of(php::TYPE::STRING)) {
             php::string pass = params[2];
             curl_easy_setopt(c_easy_, CURLOPT_KEYPASSWD, pass.c_str());
         }
@@ -119,7 +119,7 @@ namespace flame::http {
         // 目标请求地址
         // ---------------------------------------------------------------------------
         php::string u = get("url");
-        if (!u.typeof(php::TYPE::STRING)) 
+        if (!u.type_of(php::TYPE::STRING)) 
             throw php::exception(zend_ce_type_error
                 , "Failed to build client request: 'url' typeof 'string' required"
                 , -1);
diff --git a/src/http/http.cpp b/src/http/http.cpp
index 6916e55..5658192 100644
--- a/src/http/http.cpp
+++ b/src/http/http.cpp
@@ -73,11 +73,11 @@ namespace flame::http {
         return client_->exec(params);
     }
     php::string ctype_encode(std::string_view ctype, const php::value& v) {
-        if (v.typeof(php::TYPE::STRING)) return v;
+        if (v.type_of(php::TYPE::STRING)) return v;
 
         php::value r = v;
         if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
-            if (r.typeof(php::TYPE::ARRAY)) {
+            if (r.type_of(php::TYPE::ARRAY)) {
                 r = php::callable("http_build_query")({v});
             }
             else {
diff --git a/src/http/server_request.cpp b/src/http/server_request.cpp
index 792892c..0695c1b 100644
--- a/src/http/server_request.cpp
+++ b/src/http/server_request.cpp
@@ -72,7 +72,7 @@ namespace flame::http {
         set("header", header);
 
         php::string body = ctr_.body();
-        if (body.typeof(php::TYPE::STRING)) {
+        if (body.type_of(php::TYPE::STRING)) {
             auto ctype = ctr_.find(boost::beast::http::field::content_type);
             if (ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0)
                 set("raw_body", body); // 不在 multipart 时保留原始数据
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index 6948ab3..564b507 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -97,7 +97,7 @@ namespace flame::http {
         }
 
         php::array cookies = get("cookie");
-        if (cookies.typeof(php::TYPE::NULLABLE)) cookies = php::array(4);
+        if (cookies.type_of(php::TYPE::NULLABLE)) cookies = php::array(4);
         cookies.set(cookies.size(), cookie);
         return nullptr;
     }
@@ -181,7 +181,7 @@ namespace flame::http {
         status_ |= STATUS_BUILT; // 在 chunked_writer 中设置
         ctr_.result( get("status").to_integer() ); // 非法 status_code 会抛出异常
         php::array headers = get("header");
-        if (headers.typeof(php::TYPE::ARRAY)) {
+        if (headers.type_of(php::TYPE::ARRAY)) {
             for (auto i=headers.begin(); i!=headers.end(); ++i) {
                 php::string key { i->first };
                 php::string val { i->second };
@@ -189,7 +189,7 @@ namespace flame::http {
             }
         }
         php::array cookies = get("cookie");
-        if (cookies.typeof(php::TYPE::ARRAY)) {
+        if (cookies.type_of(php::TYPE::ARRAY)) {
             for(auto i=cookies.begin(); i!=cookies.end(); ++i) {
                 php::array  cookie = i->second;
                 std::string buffer;
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index f711647..d570719 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -72,7 +72,7 @@ namespace flame::kafka {
 
     rd_kafka_headers_t* array2hdrs(const php::array& data) {
         rd_kafka_headers_t *hdrs = nullptr;
-        if (!data.empty() && data.typeof(php::TYPE::ARRAY) && data.size() > 0) {
+        if (!data.empty() && data.type_of(php::TYPE::ARRAY) && data.size() > 0) {
             hdrs = rd_kafka_headers_new(data.size());
             for (auto i = data.begin(); i != data.end(); ++i) {
                 php::string key = i->first.to_string(),
diff --git a/src/kafka/producer.cpp b/src/kafka/producer.cpp
index e76c572..3e3235f 100644
--- a/src/kafka/producer.cpp
+++ b/src/kafka/producer.cpp
@@ -38,14 +38,14 @@ namespace flame::kafka {
             key = msg.get("key").to_string();
             payload = msg.get("payload").to_string();
             header = msg.get("header");
-            if (!header.typeof(php::TYPE::ARRAY)) header = php::array(0);
+            if (!header.type_of(php::TYPE::ARRAY)) header = php::array(0);
         }
         else {
             payload = params[1].to_string();
             if (params.size() > 2) key = params[2].to_string();
             else key = php::string(0);
             if (params.size() > 3) {
-                if (params[3].typeof(php::TYPE::ARRAY)) header = params[4];
+                if (params[3].type_of(php::TYPE::ARRAY)) header = params[4];
                 else header = php::array(0);
             }
         }
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index de7e381..632705e 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -113,7 +113,7 @@ namespace flame::mongodb {
         cmd.set("delete", name_);
         php::array deletes(2), deleteo(2);
         deleteo.set("q", params[0]);
-        if (params.size() > 1 && params[1].typeof(php::TYPE::INTEGER))
+        if (params.size() > 1 && params[1].type_of(php::TYPE::INTEGER))
             deleteo.set("limit", params[1].to_integer());
         else deleteo.set("limit", 0);
         
@@ -170,7 +170,7 @@ namespace flame::mongodb {
 
         if (params.size() > 1) {
             php::array project;
-            if (params[1].typeof(php::TYPE::ARRAY)) {
+            if (params[1].type_of(php::TYPE::ARRAY)) {
                 php::array fields = params[1];
                 if (fields.exists(0)) { // 将纯字段列表形式转换为K/V形式
                     project = php::array(8);
@@ -179,7 +179,7 @@ namespace flame::mongodb {
                 }
                 else project = fields;
             }
-            else if (params[1].typeof(php::TYPE::STRING)) { // 单个字段
+            else if (params[1].type_of(php::TYPE::STRING)) { // 单个字段
                 php::array project(2);
                 project.set(php::string(params[1]), 1);
             }
@@ -189,12 +189,12 @@ namespace flame::mongodb {
             cmd.set("projection", project);
         }
     PROJECTION_ALL:
-        if (params.size() > 2 && params[2].typeof(php::TYPE::ARRAY))
+        if (params.size() > 2 && params[2].type_of(php::TYPE::ARRAY))
             cmd.set("sort", params[2]);
         if (params.size() > 3) {
-            if (params[3].typeof(php::TYPE::INTEGER))
+            if (params[3].type_of(php::TYPE::INTEGER))
                 cmd.set("limit", params[3]);
-            else if (params[3].typeof(php::TYPE::ARRAY)) {
+            else if (params[3].type_of(php::TYPE::ARRAY)) {
                 php::array limits = params[3];
                 if (limits.size() > 0) cmd.set("skip", limits[0].to_integer());
                 if (limits.size() > 1) cmd.set("limit", limits[1].to_integer());
@@ -209,7 +209,7 @@ namespace flame::mongodb {
         php::array cmd(8);
         cmd.set("find", name_);
         cmd.set("filter", params[0]);
-        if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY))
+        if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY))
             cmd.set("sort", params[1]);
 
         cmd.set("limit", 1);
@@ -228,7 +228,7 @@ namespace flame::mongodb {
         project.set(field, 1);
         cmd.set("projection", project);
         cmd.set("filter", params[0]);
-        if (params.size() > 2 && params[2].typeof(php::TYPE::ARRAY))
+        if (params.size() > 2 && params[2].type_of(php::TYPE::ARRAY))
             cmd.set("sort", params[2]);
         cmd.set("limit", 1);
 
@@ -246,14 +246,14 @@ namespace flame::mongodb {
         cmd.set("count", name_);
         cmd.set("query", params[0]);
         if (params.size() > 1) {
-            if (params[1].typeof(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
-            else if (params[1].typeof(php::TYPE::ARRAY)) {
+            if (params[1].type_of(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
+            else if (params[1].type_of(php::TYPE::ARRAY)) {
                 php::array limits = params[1];
                 if (limits.size() > 0) cmd.set("skip", limits[0].to_integer());
                 if (limits.size() > 1) cmd.set("limit", limits[1].to_integer());
             }
         }
-        if (params.size() > 1 && params[1].typeof(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
+        if (params.size() > 1 && params[1].type_of(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
         coroutine_handler ch{coroutine::current};
         auto conn_ = cp_->acquire(ch);
         php::array cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
@@ -289,12 +289,12 @@ namespace flame::mongodb {
         cmd.set("remove", true);
         if (params.size() > 1) {
             php::value f = params[1];
-            if (f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("sort", f);
         }
         if (params.size() > 2) cmd.set("upsert", params[2].to_boolean());
         if (params.size() > 3) {
             php::value f = params[3];
-            if (f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("fields", f);
         }
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
@@ -306,16 +306,16 @@ namespace flame::mongodb {
         cmd.set("query", params[0]);
         if (params.size() > 1) {
             php::value f = params[1];
-            if (f.typeof(php::TYPE::ARRAY)) cmd.set("update", f);
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("update", f);
         }
         if (params.size() > 2) {
             php::value f = params[2];
-            if (f.typeof(php::TYPE::ARRAY)) cmd.set("sort", f);
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("sort", f);
         }
         if (params.size() > 3) cmd.set("upsert", params[3].to_boolean());
         if (params.size() > 4) {
             php::value f = params[4];
-            if (f.typeof(php::TYPE::ARRAY)) cmd.set("fields", f);
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("fields", f);
         }
         if (params.size() > 5) cmd.set("new", params[5].to_boolean());
         
diff --git a/src/mongodb/cursor.cpp b/src/mongodb/cursor.cpp
index 4fda35c..554b672 100644
--- a/src/mongodb/cursor.cpp
+++ b/src/mongodb/cursor.cpp
@@ -20,7 +20,7 @@ namespace flame::mongodb {
             coroutine_handler ch{coroutine::current};
             row = cl_->fetch(cs_, ch);
         }
-        if (row.typeof(php::TYPE::NULLABLE)) {
+        if (row.type_of(php::TYPE::NULLABLE)) {
             // 尽早释放连接
             cs_.reset();
             cl_.reset();
@@ -32,7 +32,7 @@ namespace flame::mongodb {
         if (cs_ && cl_) {
             coroutine_handler ch{coroutine::current};
             php::array data(8);
-            for(php::array row = cl_->fetch(cs_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
+            for(php::array row = cl_->fetch(cs_, ch); !row.type_of(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
                 data.set(data.size(), row);
             // 尽早释放连接
             cs_.reset();
diff --git a/src/mongodb/mongodb.cpp b/src/mongodb/mongodb.cpp
index 6ab95c5..7b21475 100644
--- a/src/mongodb/mongodb.cpp
+++ b/src/mongodb/mongodb.cpp
@@ -98,7 +98,7 @@ namespace flame::mongodb {
     }
 
     std::shared_ptr<bson_t> array2bson(const php::array &v) {
-        assert(v.typeof(php::TYPE::ARRAY));
+        assert(v.type_of(php::TYPE::ARRAY));
         bson_t *doc = bson_new();
         for (auto i = v.begin(); i != v.end(); ++i) {
             php::string key = i->first;
diff --git a/src/mongodb/object_id.cpp b/src/mongodb/object_id.cpp
index f48bf86..4a636d0 100644
--- a/src/mongodb/object_id.cpp
+++ b/src/mongodb/object_id.cpp
@@ -52,7 +52,7 @@ namespace flame::mongodb {
     }
 
     php::value object_id::equal(php::parameters &params) {
-        if (params[0].typeof(php::TYPE::STRING)) {
+        if (params[0].type_of(php::TYPE::STRING)) {
             php::string data = params[0];
             bson_oid_t oid;
             bson_oid_init_from_string(&oid, data.c_str());
diff --git a/src/mysql/client.cpp b/src/mysql/client.cpp
index cfa0f61..b8119e1 100644
--- a/src/mysql/client.cpp
+++ b/src/mysql/client.cpp
@@ -74,7 +74,7 @@ namespace flame::mysql {
 
         php::buffer buffer;
         char quote = '\'';
-        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
+        if (params.size() > 1 && params[1].type_of(php::TYPE::STRING)) {
             php::string q = params[1];
             if (q.data()[0] == '`') quote = '`';
         }
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 546382f..3bc87de 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -41,7 +41,7 @@ namespace flame::mysql {
     }
     // 相等
     static void where_eq(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
-        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(" IS NULL", 8);
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(" IS NULL", 8);
         else {
             php::string str = cond;
             str.to_string();
@@ -51,7 +51,7 @@ namespace flame::mysql {
     }
     // 不等 {!=}
     static void where_ne(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
-        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(" IS NOT NULL", 12);
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(" IS NOT NULL", 12);
         else {
             php::string str = cond;
             str.to_string();
@@ -61,7 +61,7 @@ namespace flame::mysql {
     }
     // 大于 {>}
     static void where_gt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
-        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(">0", 2);
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(">0", 2);
         else {
             php::string str = cond;
             str.to_string();
@@ -71,7 +71,7 @@ namespace flame::mysql {
     }
     // 小于 {<}
     static void where_lt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
-        if (cond.typeof(php::TYPE::NULLABLE)) {
+        if (cond.type_of(php::TYPE::NULLABLE)) {
             buf.append("<0", 2);
         } 
         else {
@@ -83,7 +83,7 @@ namespace flame::mysql {
     }
     // 大于等于 {>=}
     static void where_gte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)  {
-        if (cond.typeof(php::TYPE::NULLABLE)) buf.append(">=0", 3);
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(">=0", 3);
         else {
             php::string str = cond;
             str.to_string();
@@ -93,7 +93,7 @@ namespace flame::mysql {
     }
     // 小于等于 {<=}
     static void where_lte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
-        if (cond.typeof(php::TYPE::NULLABLE)) buf.append("<=0", 3);
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append("<=0", 3);
         else {
             php::string str = cond;
             str.to_string();
@@ -103,7 +103,7 @@ namespace flame::mysql {
     }
     // 某个 IN 无对应符号
     static void where_in(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
-        assert(cond.typeof(php::TYPE::ARRAY) && "目标格式错误");
+        assert(cond.type_of(php::TYPE::ARRAY) && "目标格式错误");
 
         buf.append(" IN (", 5);
         for (auto i = cond.begin(); i != cond.end(); ++i) {
@@ -116,7 +116,7 @@ namespace flame::mysql {
     }
     // WITH IN RANGE
     static void where_wir(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
-        assert(cond.typeof(php::TYPE::ARRAY) && "目标格式错误");
+        assert(cond.type_of(php::TYPE::ARRAY) && "目标格式错误");
 
         buf.append(" BETWEEN ", 9);
         std::int64_t min = std::numeric_limits<std::int64_t>::max(), max = std::numeric_limits<std::int64_t>::min();
@@ -154,8 +154,8 @@ namespace flame::mysql {
         for (auto i = cond.begin(); i != cond.end(); ++i) {
             if (++j > 0) buf.append(separator);
 
-            if (i->first.typeof(php::TYPE::INTEGER))  {
-                if (i->second.typeof(php::TYPE::ARRAY)) where_ex(cc, buf, i->second, i->first, " AND ");
+            if (i->first.type_of(php::TYPE::INTEGER))  {
+                if (i->second.type_of(php::TYPE::ARRAY)) where_ex(cc, buf, i->second, i->first, " AND ");
                 else {
                     php::string str = i->second;
                     str.to_string();
@@ -168,19 +168,19 @@ namespace flame::mysql {
                     if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0) 
                         || (key.size() == 3 && strncasecmp(key.c_str(), "{!}", 3) == 0)) {
 
-                        assert(i->second.typeof(php::TYPE::ARRAY));
+                        assert(i->second.type_of(php::TYPE::ARRAY));
                         buf.append(" NOT ", 5);
                         where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
                     }
                     else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0
                         || strncasecmp(key.c_str(), "{||}", 4) == 0)) {
 
-                        assert(i->second.typeof(php::TYPE::ARRAY));
+                        assert(i->second.type_of(php::TYPE::ARRAY));
                         where_ex(cc, buf, i->second, php::string(nullptr), " OR ");
                     }
                     else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0)
                         || (key.size() == 4 && strncasecmp(key.c_str(), "{&&}", 4) == 0)) {
-                        assert(i->second.typeof(php::TYPE::ARRAY));
+                        assert(i->second.type_of(php::TYPE::ARRAY));
                         where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
                     }
                     else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0) {
@@ -221,7 +221,7 @@ namespace flame::mysql {
                     }
                 }
                 else { // php::TYPE::STRING
-                    if (i->second.typeof(php::TYPE::ARRAY)) {
+                    if (i->second.type_of(php::TYPE::ARRAY)) {
                         php::array cond = i->second;
                         if (cond.exists(0)) {
                             _connection_base::escape(cc, buf, key, '`');
@@ -242,29 +242,29 @@ namespace flame::mysql {
 
     void build_where(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
         buf.append(" WHERE ", 7);
-        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER)) {
+        if (data.type_of(php::TYPE::STRING) || data.type_of(php::TYPE::INTEGER)) {
             buf.push_back(' ');
             buf.append(data);
         }
-        else if (data.typeof(php::TYPE::ARRAY)) {
+        else if (data.type_of(php::TYPE::ARRAY)) {
             where_ex(cc, buf, data, php::string(0), " AND ");
         }
         else buf.push_back('1');
     }
 
     void build_order(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
-        if (data.typeof(php::TYPE::STRING)) {
+        if (data.type_of(php::TYPE::STRING)) {
             buf.append(" ORDER BY ", 10);
             buf.append(data);
         }
-        else if (data.typeof(php::TYPE::ARRAY)) {
+        else if (data.type_of(php::TYPE::ARRAY)) {
             buf.append(" ORDER BY", 9);
             php::array order = data;
             int j = -1;
             for (auto i = order.begin(); i != order.end(); ++i) {
                 if (++j > 0) buf.push_back(',');
-                if (i->first.typeof(php::TYPE::INTEGER)) {
-                    if (i->second.typeof(php::TYPE::STRING)) {
+                if (i->first.type_of(php::TYPE::INTEGER)) {
+                    if (i->second.type_of(php::TYPE::STRING)) {
                         buf.push_back(' ');
                         buf.append(i->second);
                     }
@@ -272,12 +272,12 @@ namespace flame::mysql {
                 else {
                     buf.push_back(' ');
                     _connection_base::escape(cc, buf, i->first, '`');
-                    if (i->second.typeof(php::TYPE::STRING)) {
+                    if (i->second.type_of(php::TYPE::STRING)) {
                         buf.push_back(' ');
                         buf.append(i->second);
                     }
-                    else if (i->second.typeof(php::TYPE::YES)
-                        || (i->second.typeof(php::TYPE::INTEGER) && static_cast<int>(i->second) >= 0))
+                    else if (i->second.type_of(php::TYPE::YES)
+                        || (i->second.type_of(php::TYPE::INTEGER) && static_cast<int>(i->second) >= 0))
                         buf.append(" ASC", 4);
                     else buf.append(" DESC", 5);
                 }
@@ -287,11 +287,11 @@ namespace flame::mysql {
     }
 
     void build_limit(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
-        if (data.typeof(php::TYPE::STRING) || data.typeof(php::TYPE::INTEGER)) {
+        if (data.type_of(php::TYPE::STRING) || data.type_of(php::TYPE::INTEGER)) {
             buf.append(" LIMIT ", 7);
             buf.append(data);
         }
-        else if (data.typeof(php::TYPE::ARRAY)) {
+        else if (data.type_of(php::TYPE::ARRAY)) {
             buf.append(" LIMIT", 6);
             php::array limit = data;
             int j = -1;
@@ -330,7 +330,7 @@ namespace flame::mysql {
             row = rows;
             rows = nullptr;
         }
-        assert(row.typeof(php::TYPE::ARRAY) && "行非数组");
+        assert(row.type_of(php::TYPE::ARRAY) && "行非数组");
         int j = -1;
         for (auto i = row.begin(); i != row.end(); ++i) {
             if (++j > 0) buf.append(", ", 2);
@@ -338,7 +338,7 @@ namespace flame::mysql {
         }
         // 数据
         buf.append(") VALUES", 8);
-        if (rows.typeof(php::TYPE::ARRAY)) {
+        if (rows.type_of(php::TYPE::ARRAY)) {
             buf.push_back('(');
             int j = -1;
             for (auto i = rows.begin(); i != rows.end(); ++i) {
@@ -373,11 +373,11 @@ namespace flame::mysql {
         // 数据
         buf.append(" SET", 4);
         php::array data = params[2];
-        if (data.typeof(php::TYPE::STRING)) {
+        if (data.type_of(php::TYPE::STRING)) {
             buf.push_back(' ');
             buf.append(data);
         }
-        else if (data.typeof(php::TYPE::ARRAY)) {
+        else if (data.type_of(php::TYPE::ARRAY)) {
             int j = -1;
             for (auto i = data.begin(); i != data.end(); ++i) {
                 if (++j > 0) buf.push_back(',');
@@ -402,13 +402,13 @@ namespace flame::mysql {
         buf.append("SELECT ", 7);
         // 字段
         php::value fields = params[1];
-        if (fields.typeof(php::TYPE::STRING)) buf.append(fields);
-        else if (fields.typeof(php::TYPE::ARRAY)) {
+        if (fields.type_of(php::TYPE::STRING)) buf.append(fields);
+        else if (fields.type_of(php::TYPE::ARRAY)) {
             php::array a = fields;
             int j = -1;
             for (auto i = a.begin(); i != a.end(); ++i) {
                 if (++j > 0) buf.append(", ", 2);
-                if (i->first.typeof(php::TYPE::INTEGER)) _connection_base::escape(cc, buf, i->second, '`');
+                if (i->first.type_of(php::TYPE::INTEGER)) _connection_base::escape(cc, buf, i->second, '`');
                 else {
                     buf.append(i->first);
                     buf.push_back('(');
@@ -417,7 +417,7 @@ namespace flame::mysql {
                 }
             }
         }
-        else if (fields.typeof(php::TYPE::NULLABLE)) buf.push_back('*');
+        else if (fields.type_of(php::TYPE::NULLABLE)) buf.push_back('*');
         else throw php::exception(zend_ce_type_error
             , "Failed to build 'SELECT' clause: unsupported type specified for `fields`"
             , -1);
@@ -446,7 +446,7 @@ namespace flame::mysql {
     void build_get(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
         buf.append("SELECT ", 7);
         // 字段
-        if (params[1].typeof(php::TYPE::STRING)) _connection_base::escape(cc, buf, params[1], '`');
+        if (params[1].type_of(php::TYPE::STRING)) _connection_base::escape(cc, buf, params[1], '`');
         else throw php::exception(zend_ce_type_error
             , "Failed to build 'SELECT' clause: unsupported type specified for `field`"
             , -1);
diff --git a/src/mysql/result.cpp b/src/mysql/result.cpp
index d7b8b0f..2deb12b 100644
--- a/src/mysql/result.cpp
+++ b/src/mysql/result.cpp
@@ -20,7 +20,7 @@ namespace flame::mysql {
             coroutine_handler ch{coroutine::current};
             row = cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch);
         }
-        if (row.typeof(php::TYPE::NULLABLE)) {
+        if (row.type_of(php::TYPE::NULLABLE)) {
             // 尽早的释放连接
             rs_.reset();
             cl_.reset();
@@ -34,7 +34,7 @@ namespace flame::mysql {
             auto conn = cl_->acquire(ch);
             php::array data {4}, row;
 
-            for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.typeof(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) {
+            for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.type_of(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) {
                 data.set(data.size(), row);
             }
             // 尽早的释放连接
diff --git a/src/mysql/tx.cpp b/src/mysql/tx.cpp
index fda0c10..7f96b65 100644
--- a/src/mysql/tx.cpp
+++ b/src/mysql/tx.cpp
@@ -70,7 +70,7 @@ namespace flame::mysql {
 
         php::buffer buffer;
         char quote = '\'';
-        if (params.size() > 1 && params[1].typeof(php::TYPE::STRING)) {
+        if (params.size() > 1 && params[1].type_of(php::TYPE::STRING)) {
             php::string q = params[1];
             if (q.data()[0] == '`') quote = '`';
         }
diff --git a/src/os/os.cpp b/src/os/os.cpp
index 1efbd55..0205ab2 100644
--- a/src/os/os.cpp
+++ b/src/os/os.cpp
@@ -78,12 +78,12 @@ namespace flame::os {
         if (params.size() > 2) {
             php::array opts = params[2];
             php::array envs = opts.get("env");
-            if (envs.typeof(php::TYPE::ARRAY)) {
+            if (envs.type_of(php::TYPE::ARRAY)) {
                 for (auto i = envs.begin(); i != envs.end(); ++i)
                     env[i->first.to_string()] = i->second.to_string();
             }
             php::string cwds = opts.get("cwd");
-            if (cwds.typeof(php::TYPE::STRING)) cwdv = cwds.to_string();
+            if (cwds.type_of(php::TYPE::STRING)) cwdv = cwds.to_string();
         }
 
         boost::process::child c(exec, boost::process::args = argv, env, gcontroller->context_x,
diff --git a/src/rabbitmq/rabbitmq.cpp b/src/rabbitmq/rabbitmq.cpp
index 609fa85..64693dc 100644
--- a/src/rabbitmq/rabbitmq.cpp
+++ b/src/rabbitmq/rabbitmq.cpp
@@ -69,14 +69,14 @@ namespace flame::rabbitmq {
     // 仅支持一维数组
     AMQP::Table array2table(const php::array& table) {
         AMQP::Table data;
-        if (!table.typeof(php::TYPE::ARRAY) || table.empty()) return std::move(data);
+        if (!table.type_of(php::TYPE::ARRAY) || table.empty()) return std::move(data);
         for (auto i = table.begin(); i != table.end(); ++i) {
-            if (i->second.typeof(php::TYPE::BOOLEAN)) data.set(i->first.to_string(), i->second.to_boolean());
-            else if (i->second.typeof(php::TYPE::INTEGER)) data.set(i->first.to_string(), i->second.to_integer());
-            else if (i->second.typeof(php::TYPE::STRING)) data.set(i->first.to_string(), i->second.to_string());
-            else if (i->second.typeof(php::TYPE::FLOAT)) data.set(i->first.to_string(), AMQP::Double{i->second.to_float()});
+            if (i->second.type_of(php::TYPE::BOOLEAN)) data.set(i->first.to_string(), i->second.to_boolean());
+            else if (i->second.type_of(php::TYPE::INTEGER)) data.set(i->first.to_string(), i->second.to_integer());
+            else if (i->second.type_of(php::TYPE::STRING)) data.set(i->first.to_string(), i->second.to_string());
+            else if (i->second.type_of(php::TYPE::FLOAT)) data.set(i->first.to_string(), AMQP::Double{i->second.to_float()});
             else throw php::exception(zend_ce_type_error
-                , (boost::format("Failed to convert array: unsupported type '%s'") % i->second.typeof().name()).str()
+                , (boost::format("Failed to convert array: unsupported type '%s'") % i->second.type_of().name()).str()
                 , -1);
         }
         return std::move(data);
diff --git a/src/redis/client.cpp b/src/redis/client.cpp
index bd86fb6..7560f23 100644
--- a/src/redis/client.cpp
+++ b/src/redis/client.cpp
@@ -119,7 +119,7 @@ namespace flame::redis {
         auto rc = cp_->acquire(ch);
         php::string name("ZRANGE", 6);
         php::string last = params[params.size()-1];
-        if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+        if (last.type_of(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
             return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
         else
             return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
@@ -130,7 +130,7 @@ namespace flame::redis {
         auto rc = cp_->acquire(ch);
         php::string name("ZREVRANGE", 9);
         php::string last = params[params.size() - 1];
-        if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+        if (last.type_of(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
             return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
         else
             return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
@@ -142,7 +142,7 @@ namespace flame::redis {
         php::string name("ZRANGEBYSCORE", 13);
 
         for(int i=3; i<params.size(); ++i) {
-            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
                 php::string arg = params[i];
                 if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
                     return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
@@ -157,7 +157,7 @@ namespace flame::redis {
         php::string name("ZREVRANGEBYSCORE", 16);
 
         for(int i=3; i<params.size(); ++i) {
-            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
                 php::string arg = params[i];
                 if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
                     return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
diff --git a/src/redis/tx.cpp b/src/redis/tx.cpp
index f6c5b9f..6b331b1 100644
--- a/src/redis/tx.cpp
+++ b/src/redis/tx.cpp
@@ -108,7 +108,7 @@ namespace flame::redis {
     php::value tx::zrange(php::parameters& params) {
         php::string name("ZRANGE", 6);
         php::string last = params[params.size()-1];
-        if (last.typeof(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+        if (last.type_of(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
             cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
         else
             cl_->push(name, params, reply_type::SIMPLE);
@@ -118,7 +118,7 @@ namespace flame::redis {
     php::value tx::zrevrange(php::parameters& params) {
         php::string name("ZREVRANGE", 9);
         php::string last = params[params.size() - 1];
-        if (last.typeof(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+        if (last.type_of(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
             cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
         else
             cl_->push(name, params, reply_type::SIMPLE);
@@ -129,7 +129,7 @@ namespace flame::redis {
         php::string name("ZRANGEBYSCORE", 13);
 
         for(int i=3; i<params.size(); ++i) {
-            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
                 php::string arg = params[i];
                 if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
                     cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
@@ -145,7 +145,7 @@ namespace flame::redis {
         php::string name("ZREVRANGEBYSCORE", 16);
 
         for(int i=3; i<params.size(); ++i) {
-            if (params[i].typeof(php::TYPE::STRING) && params[i].size() == 10) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
                 php::string arg = params[i];
                 if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
                     cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
diff --git a/src/tcp/socket.cpp b/src/tcp/socket.cpp
index 90ca85d..9ce3c8a 100644
--- a/src/tcp/socket.cpp
+++ b/src/tcp/socket.cpp
@@ -35,11 +35,11 @@ namespace flame::tcp {
             len = socket_.async_read_some(boost::asio::buffer(buffer_.prepare(8192), 8192), ch[err]);
             buffer_.commit(len);
         }
-        else if (params[0].typeof(php::TYPE::STRING)) { // 2. 读取到指定的结束符
+        else if (params[0].type_of(php::TYPE::STRING)) { // 2. 读取到指定的结束符
             std::string delim = params[0];
             len = boost::asio::async_read_until(socket_, buffer_, delim, ch[err]);
         }
-        else if (params[0].typeof(php::TYPE::INTEGER)) { // 3. 读取指定长度
+        else if (params[0].type_of(php::TYPE::INTEGER)) { // 3. 读取指定长度
             std::size_t want = params[0].to_integer();
             if (buffer_.size() >= want) {
                 len = want;
diff --git a/src/udp/socket.cpp b/src/udp/socket.cpp
index 9212232..bf353fc 100644
--- a/src/udp/socket.cpp
+++ b/src/udp/socket.cpp
@@ -38,7 +38,7 @@ namespace flame::udp {
     php::value socket::__construct(php::parameters& params) {
         boost::system::error_code err;
         php::array option;
-        if (params.size() > 0 && params[0].typeof(php::TYPE::STRING)) {
+        if (params.size() > 0 && params[0].type_of(php::TYPE::STRING)) {
             auto pair = addr2pair(params[0]);
             boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
             if (err) throw php::exception(zend_ce_exception
@@ -55,7 +55,7 @@ namespace flame::udp {
             if (err) throw php::exception(zend_ce_exception
                 , (boost::format("failed to bind: %s") % err.message()).str()
                 , err.value());
-            if (params.size() > 1 && params[1].typeof(php::TYPE::ARRAY)) {
+            if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) {
                 option = params[0];
                 goto PARSE_OPTION;
             }
@@ -66,7 +66,7 @@ namespace flame::udp {
             if (err) throw php::exception(zend_ce_exception
                 , (boost::format("Failed to bind: %s") % err.message()).str()
                 , err.value());
-            if (params.size() > 0 && params[0].typeof(php::TYPE::ARRAY)) {
+            if (params.size() > 0 && params[0].type_of(php::TYPE::ARRAY)) {
                 option = params[0];
                 goto PARSE_OPTION;
             }

From a0c2db09f3860bb9fcf710937efa1c21ba41a09c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 16 Apr 2019 18:16:16 +0800
Subject: [PATCH 110/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=86=8D?=
 =?UTF-8?q?=E6=A0=87=E8=AE=B0=20proxy=20=E6=97=B6=E8=BF=9B=E8=A1=8C=20set?=
 =?UTF-8?q?=20names=20=E8=AE=BE=E5=AE=9A=EF=BC=8C=E5=B9=B6=E4=B8=8D?=
 =?UTF-8?q?=E5=9C=A8=E8=BF=9B=E8=A1=8C=E5=A4=8D=E7=94=A8=E8=BF=9E=E6=8E=A5?=
 =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=93=8D=E4=BD=9C=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mysql/_connection_pool.cpp | 17 ++++++++++++-----
 src/mysql/_connection_pool.h   |  1 +
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index b251b7d..ee3de70 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -48,6 +48,10 @@ namespace flame::mysql {
             init_options(c);
 
             if (mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
+                if(url_.query.count("proxy") > 0) {
+                    set_names(c); // 进行此操作以兼容 proxysql 代理
+                    flag_ =  FLAG_CHARSET_EQUAL | FLAG_REUSE_BY_PROXY;
+                }
                 ++size_; // 当前还存在的连接数量
                 release(c);
             }
@@ -91,6 +95,14 @@ namespace flame::mysql {
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
     }
 
+    void _connection_pool::set_names(MYSQL* c) {
+        // 兼容 proxysql 流程：纯粹的字符集设置，首次 SQL 会卡住:
+        // 代理：2019, Can't initialize character set (null) (path: compiled_in)
+        // 后端：不断尝试连接、断开
+        std::string sql = (boost::format("SET NAMES '%s'") % url_.query["charset"]).str();
+        mysql_real_query(c, sql.c_str(), sql.size());
+    }
+
     void _connection_pool::release(MYSQL *c) {
         // 无等待分配的请求, 保存连接(待后续分配)
         if (await_.empty()) conn_.push_back({c, std::chrono::steady_clock::now()});
@@ -118,11 +130,6 @@ namespace flame::mysql {
     }
 
     void _connection_pool::query_charset(MYSQL* c) {
-        // 兼容 proxysql 流程：纯粹的字符集设置，首次 SQL 会卡住:
-        // 代理：2019, Can't initialize character set (null) (path: compiled_in)
-        // 后端：不断尝试连接、断开
-        std::string sql = (boost::format("SET NAMES '%s'") % url_.query["charset"]).str();
-        mysql_real_query(c, sql.c_str(), sql.size());
         // 使用 mysql_get_charactor_set_name() 获取到的字符集与下述查询不同
         if (0 == mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59)) {
             std::unique_ptr<MYSQL_RES, void (*)(MYSQL_RES*)> rst(mysql_store_result(c), mysql_free_result);
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index 6adb47e..87007e4 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -39,6 +39,7 @@ namespace flame::mysql {
 
         void init_options(MYSQL *c);
         void release(MYSQL *c);
+        void set_names(MYSQL* c);
         void query_charset(MYSQL* c);
         void query_version(MYSQL* c);
     };

From ace288dfb5853f382e2b3a0af1ffdd8d0ecc30b9 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 22 Apr 2019 15:04:58 +0800
Subject: [PATCH 111/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9Aset=5Fcookie?=
 =?UTF-8?q?=20=E6=9C=AA=E8=83=BD=E5=BC=95=E7=94=A8=E5=8E=9F=20cookie=20?=
 =?UTF-8?q?=E5=AE=B9=E5=99=A8=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/server_response.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index 564b507..ffa5051 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -4,7 +4,7 @@
 #include "server_response.h"
 
 namespace flame::http {
-    
+
     void server_response::declare(php::extension_entry& ext) {
         php::class_entry<server_response> class_server_response("flame\\http\\server_response");
         class_server_response
@@ -96,7 +96,7 @@ namespace flame::http {
             cookie.set("http_only", http_only);
         }
 
-        php::array cookies = get("cookie");
+        php::array cookies = get("cookie", true);
         if (cookies.type_of(php::TYPE::NULLABLE)) cookies = php::array(4);
         cookies.set(cookies.size(), cookie);
         return nullptr;
@@ -107,7 +107,7 @@ namespace flame::http {
             throw php::exception(zend_ce_error_exception
                 , "Failed to write header: already sent"
                 , -1);
-        
+
         status_ |= STATUS_HEAD_SENT;
         if (params.size() > 0) set("status", params[0].to_integer());
         coroutine_handler ch{coroutine::current};
@@ -136,7 +136,7 @@ namespace flame::http {
             throw php::exception(zend_ce_error_exception
                 , "Failed to write body: response already done"
                 , -1);
-        
+
         coroutine_handler ch{coroutine::current};
         if (!(status_ & STATUS_HEAD_SENT)) {
             status_ |= STATUS_HEAD_SENT;
@@ -174,7 +174,7 @@ namespace flame::http {
         handler_->write_file(this, (root / file.lexically_normal()).string(), ch);
         return nullptr;
     }
-    
+
     // 支持 content-length 形式的用法
     void server_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
         if (status_ & STATUS_BUILT) return; // 重复使用

From 5f3d84db95d86b5e9d7548a7731541923de7b360 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 22 Apr 2019 15:05:56 +0800
Subject: [PATCH 112/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E7=94=B1?=
 =?UTF-8?q?=E4=BA=8E=E4=BD=BF=E7=94=A8=E9=9A=90=E5=BC=8F=20session=20?=
 =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=20Cursor=20SessionId=20=E4=B8=8D?=
 =?UTF-8?q?=E5=8C=B9=E9=85=8D=E9=97=AE=E9=A2=98=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                        |  4 ++--
 src/mongodb/_connection_base.cpp | 21 +++++++++++++++++----
 src/mongodb/_connection_base.h   |  6 ++++--
 src/mongodb/cursor.cpp           |  6 ++++--
 src/mongodb/cursor.h             |  1 +
 5 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index 5b21291..f0ffd70 100644
--- a/README.md
+++ b/README.md
@@ -146,7 +146,7 @@ make && make install
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake3 -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
 make && make install
 # rm /data/vendor/mongoc-1.14.0/lib/*.so*
 ```
@@ -205,4 +205,4 @@ make && make install
 注意：需要下载内含 boost 头文件的版本（与框架依赖版本不同）；
 
 </p>
-</details>
\ No newline at end of file
+</details>
diff --git a/src/mongodb/_connection_base.cpp b/src/mongodb/_connection_base.cpp
index d592468..a4c47d3 100644
--- a/src/mongodb/_connection_base.cpp
+++ b/src/mongodb/_connection_base.cpp
@@ -7,8 +7,11 @@
 namespace flame::mongodb {
 
     void _connection_base::fake_deleter(bson_t *doc) {}
-    
-    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn, php::array& pcmd, int type, coroutine_handler &ch) {
+
+    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn
+        , php::array& pcmd, int type, coroutine_handler &ch
+        , std::shared_ptr<mongoc_client_session_t> session) {
+
         std::shared_ptr<bson_t> cmd = array2bson(pcmd);
         // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏
         //（实际对 reply 的使用流程会重新初始化 reply 导致其值被至于 stack 上，导致此处 heap 内存泄漏）
@@ -18,7 +21,13 @@ namespace flame::mongodb {
         std::uint32_t server_id;
         int  rok = 0;
 
-        boost::asio::post(gcontroller->context_y, [conn, type, &cmd, &rep, &opt, &err, &rok, &ch] () {
+        boost::asio::post(gcontroller->context_y, [&session, conn, type, &cmd, &rep, &opt, &err, &rok, &ch] () {
+            if (!session) {
+                session.reset(mongoc_client_start_session(conn.get(), nullptr, nullptr)
+                    , mongoc_client_session_destroy);
+                mongoc_client_session_append(session.get(), opt.get(), nullptr);
+            }
+
             mongoc_server_description_t* server;
             switch(type) {
             case COMMAND_READ:
@@ -54,6 +63,10 @@ namespace flame::mongodb {
             ch.resume();
         });
         ch.suspend();
+        // size_t size;
+        // char * json = bson_as_relaxed_extended_json(rep.get(), &size);
+        // std::cout << "REPLY: (" << size << ") " << json << "\n";
+        // bson_free(json);
         if (!rok) throw php::exception(zend_ce_exception
             , (boost::format("Failed to execute MongoDB command: %s") % err->message).str()
             , err->code);
@@ -61,7 +74,7 @@ namespace flame::mongodb {
             php::object obj{php::class_entry<cursor>::entry()};
             auto ptr = static_cast<cursor *>(php::native(obj));
             ptr->cl_.reset(new _connection_lock(conn));
-
+            ptr->ss_ = session;
             ptr->cs_.reset(
                 mongoc_cursor_new_from_command_reply_with_opts(conn.get(), rep.get(), opt.get()),
                 mongoc_cursor_destroy);
diff --git a/src/mongodb/_connection_base.h b/src/mongodb/_connection_base.h
index c652c54..eeefed1 100644
--- a/src/mongodb/_connection_base.h
+++ b/src/mongodb/_connection_base.h
@@ -3,7 +3,7 @@
 #include "../coroutine.h"
 
 namespace flame::mongodb {
-    
+
     class _connection_base {
     public:
         enum command_type_t {
@@ -13,7 +13,9 @@ namespace flame::mongodb {
             COMMAND_READ_WRITE,
         };
         virtual std::shared_ptr<mongoc_client_t> acquire(coroutine_handler& ch) = 0;
-        php::value exec(std::shared_ptr<mongoc_client_t> conn, php::array& cmd, int type, coroutine_handler& ch);
+        php::value exec(std::shared_ptr<mongoc_client_t> conn
+            , php::array& cmd, int type, coroutine_handler& ch
+            , std::shared_ptr<mongoc_client_session_t> session = nullptr);
 
         static void fake_deleter(bson_t *doc);
     };
diff --git a/src/mongodb/cursor.cpp b/src/mongodb/cursor.cpp
index 554b672..b57de08 100644
--- a/src/mongodb/cursor.cpp
+++ b/src/mongodb/cursor.cpp
@@ -21,8 +21,9 @@ namespace flame::mongodb {
             row = cl_->fetch(cs_, ch);
         }
         if (row.type_of(php::TYPE::NULLABLE)) {
-            // 尽早释放连接
+            // 尽早释放连接 (注意顺序)
             cs_.reset();
+            ss_.reset();
             cl_.reset();
         }
         return row;
@@ -34,8 +35,9 @@ namespace flame::mongodb {
             php::array data(8);
             for(php::array row = cl_->fetch(cs_, ch); !row.type_of(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
                 data.set(data.size(), row);
-            // 尽早释放连接
+            // 尽早释放连接 (注意顺序)
             cs_.reset();
+            ss_.reset();
             cl_.reset();
             return data;
         }
diff --git a/src/mongodb/cursor.h b/src/mongodb/cursor.h
index 55e2ce8..2538636 100644
--- a/src/mongodb/cursor.h
+++ b/src/mongodb/cursor.h
@@ -17,6 +17,7 @@ namespace flame::mongodb {
     private:
         // 须先销毁 指针 后归还 连接
         std::shared_ptr<_connection_lock> cl_;
+        std::shared_ptr<mongoc_client_session_t> ss_;
         std::shared_ptr<mongoc_cursor_t>  cs_;
         friend class _connection_base;
     };

From d29cee09a08030a9b97adfeaec02d46c1f0da191 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 24 Apr 2019 19:46:20 +0800
Subject: [PATCH 113/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9AKafka=20?=
 =?UTF-8?q?=E7=94=9F=E4=BA=A7=20poll=20=E6=96=B9=E5=BC=8F=EF=BC=9B?=
 =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9AKafka=20=E9=94=99=E8=AF=AF=E4=BF=A1?=
 =?UTF-8?q?=E6=81=AF=E8=AE=B0=E5=BD=95=E6=96=B9=E5=BC=8F=EF=BC=88=E9=81=B5?=
 =?UTF-8?q?=E5=BE=AA=20log=20=E8=AE=B0=E5=BD=95=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/kafka/_consumer.cpp | 32 ++++++++++++-------
 src/kafka/_consumer.h   |  6 ++--
 src/kafka/_producer.cpp | 71 ++++++++++++++++++++++++++++++++++-------
 src/kafka/_producer.h   | 12 +++++--
 src/kafka/consumer.h    |  3 +-
 src/kafka/kafka.cpp     |  7 ++--
 src/kafka/producer.cpp  |  4 +--
 src/log/log.cpp         | 15 ++-------
 src/log/log.h           |  9 ++++++
 src/vendor.h            |  2 +-
 10 files changed, 114 insertions(+), 47 deletions(-)

diff --git a/src/kafka/_consumer.cpp b/src/kafka/_consumer.cpp
index 75505f7..dda6cd9 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/kafka/_consumer.cpp
@@ -1,8 +1,10 @@
 #include "../controller.h"
+#include "../coroutine_queue.h"
+#include "../time/time.h"
+#include "../log/log.h"
 #include "_consumer.h"
 #include "kafka.h"
 #include "message.h"
-#include "../coroutine_queue.h"
 
 namespace flame::kafka {
     static rd_kafka_topic_partition_list_t *array2topics(const php::array &topics) {
@@ -19,12 +21,12 @@ namespace flame::kafka {
             throw php::exception(zend_ce_type_error
                 , "Failed to create Kafka consumer: 'bootstrap.servers' required"
                 , -1);
-        
+
         if (!config.exists("group.id"))
             throw php::exception(zend_ce_type_error
                 , "Failed to create Kafka consumer: 'group.id' required"
                 , -1);
-        
+
         if (topics.size() == 0)
             throw php::exception(zend_ce_type_error
                 , "Failed to create Kafka consumer: target topics missing"
@@ -32,18 +34,21 @@ namespace flame::kafka {
 
         rd_kafka_conf_t *conf = array2conf(config);
         char err[256];
+        rd_kafka_conf_set_opaque(conf, this);
+        rd_kafka_conf_set_error_cb(conf, on_error);
 
         conn_ = rd_kafka_new(RD_KAFKA_CONSUMER, conf, err, sizeof(err));
         if (!conn_)
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to create Kafka consumer: %s") % err).str()/*, 0*/);
-        
+
         auto r = rd_kafka_poll_set_consumer(conn_);
         if (r != RD_KAFKA_RESP_ERR_NO_ERROR)
             throw php::exception(zend_ce_exception
                 , (boost::format("Failed to create Kafka Consumer: %s") % rd_kafka_err2str(r)).str()
                 , r);
-        
+
+
         tops_ = array2topics(topics);
     }
 
@@ -55,6 +60,12 @@ namespace flame::kafka {
         }
     }
 
+    void _consumer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
+        _consumer* self = reinterpret_cast<_consumer*>(data);
+        if (log::level <= log::LEVEL_WARNING)
+            std::cerr << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+    }
+
     void _consumer::subscribe(coroutine_handler &ch) {
         rd_kafka_resp_err_t err;
         boost::asio::post(gcontroller->context_y, [this, &err, &ch] ()  {
@@ -62,20 +73,19 @@ namespace flame::kafka {
             ch.resume();
         });
         ch.suspend();
-        if (err != RD_KAFKA_RESP_ERR_NO_ERROR) 
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
             throw php::exception(zend_ce_exception
                 , (boost::format("failed to subcribe Kafka topics: %s") % rd_kafka_err2str(err)).str()
                 , err);
     }
 
     void _consumer::consume(coroutine_queue<php::object>& q, coroutine_handler& ch) {
-        rd_kafka_resp_err_t err;
+        rd_kafka_resp_err_t err = RD_KAFKA_RESP_ERR_NO_ERROR;
         rd_kafka_message_t* msg = nullptr;
 POLL_MESSAGE:
-        // 由于 poll 动作本身阻塞, 为防止所有工作线被占用, 这里绑定到 strd_ 执行
-        // (理论上仅占用一个协程)
+        // 采用内部队列，理论上仅占用一个工作线程（应保证占用时间不要过长）
         boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]() {
-            msg = rd_kafka_consumer_poll(conn_, 500);
+            msg = rd_kafka_consumer_poll(conn_, 40);
             if (!msg) err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
             else if (msg->err) {
                 err = msg->err;
@@ -131,7 +141,7 @@ namespace flame::kafka {
             throw php::exception(zend_ce_exception
                 , (boost::format("failed to close Kafka consumer: %s") % rd_kafka_err2str(err)).str()
                 , err);
-        
+
         close_ = true;
     }
 
diff --git a/src/kafka/_consumer.h b/src/kafka/_consumer.h
index 7329cfe..d04f7b9 100644
--- a/src/kafka/_consumer.h
+++ b/src/kafka/_consumer.h
@@ -8,8 +8,9 @@ namespace flame {
 }
 
 namespace flame::kafka {
-
-    class _consumer {
+    // 消费者
+    // 目前的实现方式无需 shared_from_this 管理
+    class _consumer/*: public std::enable_shared_from_this<_consumer> */{
     public:
         _consumer(php::array& config, php::array& topics);
         ~_consumer();
@@ -21,5 +22,6 @@ namespace flame::kafka {
         rd_kafka_t* conn_;
         rd_kafka_topic_partition_list_t* tops_;
         bool       close_;
+        static void on_error(rd_kafka_t* conn, int error, const char* reason, void* data);
     };
 } // namespace flame::kafka
diff --git a/src/kafka/_producer.cpp b/src/kafka/_producer.cpp
index c369a86..bd448ff 100644
--- a/src/kafka/_producer.cpp
+++ b/src/kafka/_producer.cpp
@@ -1,10 +1,14 @@
 #include "../controller.h"
+#include "../time/time.h"
+#include "../log/log.h"
 #include "_producer.h"
 #include "kafka.h"
 
 namespace flame::kafka {
-    
-    _producer::_producer(php::array& config, php::array& topics) {
+
+    _producer::_producer(php::array& config, php::array& topics)
+    : poll_(gcontroller->context_y)
+    , close_(false) {
         if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
             throw php::exception(zend_ce_type_error
                 , "Failed to create Kafka producer: 'bootstrap.servers' required"
@@ -15,13 +19,16 @@ namespace flame::kafka {
                 , -1);
 
         rd_kafka_conf_t *conf = array2conf(config);
+        rd_kafka_conf_set_opaque(conf, this);
+        rd_kafka_conf_set_error_cb(conf, on_error);
+
         char err[256];
         conn_ = rd_kafka_new(RD_KAFKA_PRODUCER, conf, err, sizeof(err));
         if (!conn_)
             throw php::exception(zend_ce_type_error
                 , (boost::format("Failed to create Kafka Consumer: %s") % err).str()
                 , -1);
-        
+
         for (auto i = topics.begin(); i != topics.end(); ++i) {
             php::string topic = i->second.to_string();
             tops_[topic] = rd_kafka_topic_new(conn_, topic.c_str(), nullptr);
@@ -29,17 +36,55 @@ namespace flame::kafka {
     }
 
     _producer::~_producer() {
-        if (conn_) {
-            for(auto i=tops_.begin(); i!=tops_.end(); ++i) {
-                rd_kafka_topic_destroy(i->second);
-            }
-            rd_kafka_destroy(conn_);
+        if (!conn_) return;
+        for(auto i=tops_.begin(); i!=tops_.end(); ++i) {
+            rd_kafka_topic_destroy(i->second);
         }
+        rd_kafka_destroy(conn_);
+    }
+
+    void _producer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
+        _producer* self = reinterpret_cast<_producer*>(data);
+        if (log::level <= log::LEVEL_WARNING)
+            std::cerr << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+    }
+
+    void _producer::start() {
+        poll();
+    }
+
+    void _producer::poll(int expire) {
+        poll_.expires_after(std::chrono::milliseconds(expire));
+        poll_.async_wait([this, self = shared_from_this()] (const boost::system::error_code& error) {
+            if (error || close_) return;
+            auto begin = std::chrono::steady_clock::now();
+            int r = rd_kafka_poll(conn_, 40); // 防止对工作线程占用时间过长
+            if (close_) return;
+            auto end = std::chrono::steady_clock::now();
+            // 计算实际 poll 占用时间，稳定间隔
+            int e = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
+            // 无消息时放慢 poll 以减少对工作线程的占用
+            if (r > 0) poll(std::max(40 - e, 1));
+            else poll(std::max(1600 - e, 1));
+        });
+    }
+
+    void _producer::close(coroutine_handler& ch) {
+        if (!conn_) return;
+        close_ = true;
+        poll_.cancel();
+        boost::asio::post(gcontroller->context_y, [this, &ch] {
+            rd_kafka_yield(conn_);
+            rd_kafka_flush(conn_, 10000);
+            ch.resume();
+        });
+        ch.suspend();
     }
 
     void _producer::publish(const php::string &topic, const php::string &key
         , const php::string &payload, const php::array &headers, coroutine_handler &ch) {
-        rd_kafka_resp_err_t err;
+
+        rd_kafka_resp_err_t err = RD_KAFKA_RESP_ERR_NO_ERROR;
         rd_kafka_headers_t* hdrs = array2hdrs(headers);
         boost::asio::post(gcontroller->context_y, [this, &err, &topic, &key, &payload, &hdrs, &ch]() {
             if (key.size() > 0) {
@@ -61,8 +106,6 @@ namespace flame::kafka {
                     RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
                     RD_KAFKA_V_END);
             }
-             // TODO 优化: 是否可以考虑按周期间隔进行调用?
-            if (err == RD_KAFKA_RESP_ERR_NO_ERROR) rd_kafka_poll(conn_, 5);
             ch.resume();
         });
         ch.suspend();
@@ -75,6 +118,10 @@ namespace flame::kafka {
     }
 
     void _producer::flush(coroutine_handler& ch) {
-        if (conn_) rd_kafka_flush(conn_, 10000);
+        if (!conn_) return;
+        poll_.cancel();
+        rd_kafka_yield(conn_); // 停止当前的 poll
+        rd_kafka_flush(conn_, 10000);
+        poll(); // 重新启动 poll 流程
     }
 } // namespace flame::kafka
diff --git a/src/kafka/_producer.h b/src/kafka/_producer.h
index a6c6d13..ed331db 100644
--- a/src/kafka/_producer.h
+++ b/src/kafka/_producer.h
@@ -3,15 +3,23 @@
 #include "../coroutine.h"
 
 namespace flame::kafka {
-    
-    class _producer {
+    // 生产者
+    // 目前的实现方式 poll 定时调用机制需要 shared_from_this 保证生命周期
+    class _producer: public std::enable_shared_from_this<_producer> {
     public:
         _producer(php::array& config, php::array& topics);
         ~_producer();
         void publish(const php::string& topic, const php::string& key, const php::string& payload, const php::array& headers, coroutine_handler& ch);
         void flush(coroutine_handler& ch);
+        void close(coroutine_handler& ch);
+        void start();
     private:
         rd_kafka_t* conn_;
         std::map<php::string, rd_kafka_topic_t*> tops_;
+        boost::asio::steady_timer poll_;
+        bool close_;
+        void poll(int expire = 970);
+        static void on_error(rd_kafka_t* conn, int error, const char* reason, void* data);
+        friend class producer;
     };
 } // namespace flame::kafka
diff --git a/src/kafka/consumer.h b/src/kafka/consumer.h
index db39685..7665830 100644
--- a/src/kafka/consumer.h
+++ b/src/kafka/consumer.h
@@ -3,7 +3,7 @@
 #include "../coroutine.h"
 
 namespace flame::kafka {
-    
+
     class _consumer;
     class consumer : public php::class_base {
     public:
@@ -17,7 +17,6 @@ namespace flame::kafka {
         std::shared_ptr<_consumer> cs_;
         int                        cc_ = 8;
         php::callable              cb_;
-        zend_execute_data*         ex_;
 
         friend php::value consume(php::parameters &params);
     };
diff --git a/src/kafka/kafka.cpp b/src/kafka/kafka.cpp
index d570719..24d8454 100644
--- a/src/kafka/kafka.cpp
+++ b/src/kafka/kafka.cpp
@@ -20,7 +20,7 @@ namespace flame::kafka {
         consumer::declare(ext);
         producer::declare(ext);
     }
-    
+
     php::value consume(php::parameters& params) {
         php::array config = params[0];
         php::array topics = params[1];
@@ -33,7 +33,7 @@ namespace flame::kafka {
         }
         if (!config.exists("log.connection.close")) config.set("log.connection.close", "false");
 
-        ptr->cs_.reset(new _consumer(config, topics));
+        ptr->cs_ = std::make_shared<_consumer>(config, topics);
         coroutine_handler ch {coroutine::current};
         // 订阅
         ptr->cs_->subscribe(ch);
@@ -49,7 +49,8 @@ namespace flame::kafka {
 
         if (!config.exists("log.connection.close")) config.set("log.connection.close", "false");
 
-        ptr->pd_.reset(new _producer(config, topics));
+        ptr->pd_ = std::make_shared<_producer>(config, topics);
+        ptr->pd_->start(); // 不能在构造中启动 poll 流程
         // TODO 优化: 确认首次连接已建立
         return std::move(obj);
     }
diff --git a/src/kafka/producer.cpp b/src/kafka/producer.cpp
index 3e3235f..b141d25 100644
--- a/src/kafka/producer.cpp
+++ b/src/kafka/producer.cpp
@@ -25,7 +25,7 @@ namespace flame::kafka {
     php::value producer::__destruct(php::parameters &params) {
         if (pd_) {
             coroutine_handler ch {coroutine::current};
-            pd_->flush(ch);
+            pd_->close(ch); // 内部存在 flush 机制
         }
         return nullptr;
     }
@@ -54,7 +54,7 @@ namespace flame::kafka {
         pd_->publish(topic, key, payload, header, ch);
         return nullptr;
     }
-    
+
     php::value producer::flush(php::parameters &params) {
         coroutine_handler ch {coroutine::current};
         pd_->flush(ch);
diff --git a/src/log/log.cpp b/src/log/log.cpp
index 0e4d531..1ed8ded 100644
--- a/src/log/log.cpp
+++ b/src/log/log.cpp
@@ -15,17 +15,8 @@ namespace flame::log {
 
     static std::map<std::string, int> LEVEL_I {};
 
-    enum {
-        LEVEL_TRACE,
-        LEVEL_DEBUG,
-        LEVEL_INFO,
-        LEVEL_WARNING,
-        LEVEL_ERROR,
-        LEVEL_FATAL,
-    };
+    int level = 0;
 
-    static int level = 0;
-    
     static void write_ex(int lv, php::parameters& params) {
         if (lv < level) return;
         std::ostream& os = lv > LEVEL_WARNING ? std::cout : std::cerr;
@@ -34,7 +25,7 @@ namespace flame::log {
         os << ")";
         for (int i = 0; i < params.size(); ++i)
             os << ' ' << params[i].ptr();
-        
+
         os << std::endl;
     }
 
@@ -83,4 +74,4 @@ namespace flame::log {
             .function<fatal>("flame\\log\\fail")
             .function<fatal>("flame\\log\\fatal");
     }
-}
\ No newline at end of file
+}
diff --git a/src/log/log.h b/src/log/log.h
index 86974a8..2e6c587 100644
--- a/src/log/log.h
+++ b/src/log/log.h
@@ -2,5 +2,14 @@
 #include "../vendor.h"
 
 namespace flame::log {
+    enum {
+        LEVEL_TRACE,
+        LEVEL_DEBUG,
+        LEVEL_INFO,
+        LEVEL_WARNING,
+        LEVEL_ERROR,
+        LEVEL_FATAL,
+    };
+    extern int level;
     void declare(php::extension_entry &ext);
 } // namespace flame::log
diff --git a/src/vendor.h b/src/vendor.h
index e93682e..e3518d3 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.13.0"
+#define EXTENSION_VERSION "0.13.1"
 
 #include <sys/types.h>
 #include <sys/socket.h>

From 48ce7858b48eca6dfc4b773396479fea93925770 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 26 Apr 2019 16:27:46 +0800
Subject: [PATCH 114/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E4=BD=BF?=
 =?UTF-8?q?=E7=94=A8=20maria=20=E6=9B=BF=E6=8D=A2=20mysql=20=E9=A9=B1?=
 =?UTF-8?q?=E5=8A=A8=EF=BC=8C=E5=B9=B6=E4=BC=98=E5=8C=96=E5=A4=8D=E7=94=A8?=
 =?UTF-8?q?=E6=B5=81=E7=A8=8B=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                 |  9 ++++---
 README.md                      | 11 ++++++++
 doc/mongodb.php                |  3 ++-
 doc/mysql.php                  |  6 ++---
 src/flame.cpp                  |  3 ++-
 src/mysql/_connection_base.cpp | 40 +++++++++++++++++----------
 src/mysql/_connection_pool.cpp | 49 ++++++++++++----------------------
 src/mysql/_connection_pool.h   |  2 ++
 src/mysql/mysql.cpp            |  7 ++---
 src/vendor.h                   |  2 +-
 test/http_2.php                |  3 ++-
 test/tcp_2.php                 |  2 +-
 12 files changed, 77 insertions(+), 60 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 87a8e2e..823ae97 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -30,7 +30,8 @@ execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
 set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_BOOST /data/vendor/boost-1.69.0)
-set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
+# set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
+set(VENDOR_MARIA /data/vendor/mariac-3.0.9)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
@@ -57,7 +58,8 @@ target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_BOOST}/include
     ${VENDOR_PHPEXT}/include
     ${VENDOR_PARSER}/include
-    ${VENDOR_MYSQL}/include
+#    ${VENDOR_MYSQL}/include
+    ${VENDOR_MARIA}/include
     ${VENDOR_HIREDIS}/include
     ${VENDOR_MONGODB}/include/libmongoc-1.0
     ${VENDOR_MONGODB}/include/libbson-1.0
@@ -72,7 +74,8 @@ target_include_directories(FLAME SYSTEM PRIVATE
 target_link_libraries(FLAME
     ${VENDOR_HTTPPARSER}/lib/libhttp_parser.o
     ${VENDOR_PHPEXT}/lib/libphpext.a
-    ${VENDOR_MYSQL}/lib/libmysqlclient.a
+#    ${VENDOR_MYSQL}/lib/libmysqlclient.a
+    ${VENDOR_MARIA}/lib/mariadb/libmariadbclient.a
     ${VENDOR_HIREDIS}/lib/libhiredis.a
     ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
     ${VENDOR_MONGODB}/lib/libbson-static-1.0.a
diff --git a/README.md b/README.md
index f0ffd70..053e039 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,7 @@ make install
 ``` Bash
 make install
 ```
+
 <!--
 #### sasl2
 ``` Bash
@@ -143,6 +144,7 @@ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-
 make && make install
 ```
 -->
+
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
@@ -195,6 +197,7 @@ CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --prefix=/data/vendor/cur
 make && make install
 ```
 
+<!--
 #### mysql-connector-c
 ``` Bash
 mkdir stage && cd stage
@@ -203,6 +206,14 @@ make && make install
 # rm /data/vendor/mysqlc-8.0.15/lib/*.so*
 ```
 注意：需要下载内含 boost 头文件的版本（与框架依赖版本不同）；
+-->
+
+#### maria-connector-c
+```
+mkdir stage && cd stage
+CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig:/data/vendor/curl-7.64.1/lib/pkgconfig cmake3 -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC  -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.0.9 ../
+# rm /data/vendor/mariac-3.0.9/lib/mariadb/*.so*
+```
 
 </p>
 </details>
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 0b2bc3f..4e40234 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -147,8 +147,9 @@ function get(array $query, string $field, array $sort = null) {
     }
     /**
      * 查询并返回匹配文档数量
+     * @param mixed $limit integer/array 请参见 find 函数对应 $limit 说明
      */
-    function count(array $query): int {
+    function count(array $query, mixed $limit): int {
         return 0;
     }
     /**
diff --git a/doc/mysql.php b/doc/mysql.php
index 01da9cf..579db6c 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -6,7 +6,6 @@
  * 2. MySQL 读取数据时数值型字段读取映射为 PHP 对应数值类型；`DATETIME` 映射为 PHP 内置类型 `DateTime`;
  * 3. 客户端提供的简化方法如 `insert()` `update()` 也同时支持与上述反向的映射;
  * 4. 单客户端内连接池上限大小为 6（单进程）；
- * 5. 对 MySQL < 5.7 版本特定用法可能发生连接复用前后干扰的情况；
  */
 namespace flame\mysql;
 /**
@@ -15,9 +14,10 @@
  *  mysql://{user}:{pass}@{host}:{port}/{database}?opt1=val1
  *  目前可用的选项如下:
  *      * "charset" => 字符集
- *      * "ssl" => SSL 模式，可用值 "disabled" (默认) / "preferred" / "verify_ca" / "verify_identity"
+ *      * "auth" => 认证方式，默认 "mysql_native_password" 可用 "caching_sha2_password"（MySQL 8.0 服务器默认） / "sha256_parssword"
  *      * "proxy" => 当前地址是否是代理服务（已提供连接复用机制） "1" - 是，已提供连接复用机制 (禁用框架提供的复用重置机制)， "0" - 否，未提供（默认）
- * 注意：若服务端配置默认字符集与上述字符集不同，每次连接使用（复用）均会进行字符集设置，这里可能会产生部分额外消耗；
+ * 注意：若服务端配置默认字符集与上述字符集不同，且未指定 proxy=1 参数，那么：
+ *      每次连接使用（复用）均会进行字符集设置，这里可能会产生部分额外消耗；
  * @return client 客户端对象
  */
 function connect($url): client {
diff --git a/src/flame.cpp b/src/flame.cpp
index fa6d617..4d9937e 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -113,7 +113,8 @@ extern "C" {
             .desc({"vendor/boost", BOOST_LIB_VERSION})
             .desc({"vendor/phpext", PHPEXT_LIB_VERSION})
             .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
-            .desc({"vendor/mysqlc", mysql_get_client_info()})
+            // .desc({"vendor/mysqlc", mysql_get_client_info()})
+            .desc({"vendor/mariac", MARIADB_PACKAGE_VERSION})
             .desc({"vendor/amqpcpp", "4.1.4"})
             .desc({"vendor/rdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S})
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index 30fa651..c313f1c 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -4,6 +4,7 @@
 #include "result.h"
 
 namespace flame::mysql {
+    
     void _connection_base::escape(std::shared_ptr<MYSQL> conn, php::buffer &b, const php::value &v, char quote) {
         switch (Z_TYPE_P(static_cast<zval *>(v))) {
         case IS_NULL:
@@ -23,25 +24,37 @@ namespace flame::mysql {
         //  break;
         // }
         case IS_STRING: {
-            php::string str = v;
             // 支持字段名 aaa.bbb 进行 ESCAPE 变为 `aaa`.`bbb`
             if (quote == '`') {
-                const char *s = str.c_str(), *c, *e = str.c_str() + str.size();
+                std::string str = v; // 这里进行拷贝（原字符串可能发生更改）
+                char *s = str.data(), *c, *e = str.data() + str.size();
+                char *t = b.prepare(str.size() * 2 + 2);
+                std::size_t n = 0;
+ESCAPE_REMAINING:
                 for (c = s; c < e; ++c) {
+                    if (*c == '`') *c = '\\';
                     if (*c == '.') {
-                        escape(conn, b, php::string(s, c - s), quote);
-                        b.push_back('.');
-                        escape(conn, b, php::string(c + 1, e - c - 1), quote);
-                        goto ESCAPE_FINISHED;
+                        t[n++] = quote;
+                        n += mysql_real_escape_string(conn.get(), t + n, s, c - s);
+                        t[n++] = quote;
+                        t[n++] = '.';
+                        s = c + 1;
+                        goto ESCAPE_REMAINING;
                     }
                 }
+                t[n++] = quote;
+                n += mysql_real_escape_string(conn.get(), t + n, s, c - s);
+                t[n++] = quote;
+                b.commit(n);
+            } else {
+                php::string str = v;
+                char *t = b.prepare(str.size() * 2 + 2);
+                std::size_t n = 0;
+                t[n++] = quote;
+                n += mysql_real_escape_string(conn.get(), t + n, str.c_str(), str.size());
+                t[n++] = quote;
+                b.commit(n);
             }
-            char *to = b.prepare(str.size() * 2 + 2);
-            std::size_t n = 0;
-            to[n++] = quote;
-            n += mysql_real_escape_string_quote(conn.get(), to + n, str.c_str(), str.size(), quote);
-            to[n++] = quote;
-            b.commit(n);
             break;
         }
         case IS_ARRAY: {
@@ -70,7 +83,6 @@ namespace flame::mysql {
             escape(conn, b, str, quote);
         }
         }
-ESCAPE_FINISHED:;
     }
 
     php::object _connection_base::query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch) {
@@ -131,7 +143,7 @@ ESCAPE_FINISHED:;
         ch.suspend();
         if (!row) {
             int err = mysql_errno(conn.get());
-            if (err != 0) 
+            if (err != 0)
                 throw php::exception(zend_ce_error
                     , (boost::format("Failed to fetch MySQL row: %s") % mysql_error(conn.get())).str()
                     , err);
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index ee3de70..c1d12ba 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -5,7 +5,8 @@ namespace flame::mysql {
     _connection_pool::_connection_pool(url u)
     : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
     , flag_(FLAG_UNKNOWN) {
-
+        if(url_.query.count("proxy") > 0 && std::stoi(url_.query["proxy"]) != 0)
+            flag_ =  FLAG_CHARSET_EQUAL | FLAG_REUSE_BY_PROXY;
     }
 
     _connection_pool::~_connection_pool() {
@@ -31,7 +32,7 @@ namespace flame::mysql {
             while (!conn_.empty()) {
                 if (now - conn_.front().ttl < std::chrono::seconds(15)
                     || mysql_ping(conn_.front().conn) == 0) {  // 可用连接
-                    
+
                     MYSQL* c = conn_.front().conn;
                     conn_.pop_front();
                     release(c);
@@ -46,18 +47,15 @@ namespace flame::mysql {
             if (size_ >= max_) return; // 已建立了足够多的连接, 需要等待已分配连接释放
             MYSQL* c = mysql_init(nullptr);
             init_options(c);
-
             if (mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
-                if(url_.query.count("proxy") > 0) {
-                    set_names(c); // 进行此操作以兼容 proxysql 代理
-                    flag_ =  FLAG_CHARSET_EQUAL | FLAG_REUSE_BY_PROXY;
-                }
+                if (flag_ & FLAG_REUSE_BY_PROXY) set_names(c);
                 ++size_; // 当前还存在的连接数量
                 release(c);
             }
             else {
                 errnum = mysql_errno(c);
                 errmsg = mysql_error(c);
+                std::cout << "connect failed:" << errnum << "/" << errmsg << "\n";
                 mysql_close(c);
                 await_.pop_back();
                 ch.resume();
@@ -79,18 +77,8 @@ namespace flame::mysql {
     void _connection_pool::init_options(MYSQL* c) {
         // 这里的 CHARSET 设定会被 reset_connection 重置为系统值
         mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
-        // 8.0.15 默认使用 caching_sha2_password 进行认证，低版本不支持
-        mysql_options(c, MYSQL_DEFAULT_AUTH, "mysql_native_password");
-        // SSL 参数
-        unsigned int ssl = SSL_MODE_DISABLED;
-        if (url_.query.count("ssl") > 0) {
-            std::string sstr = url_.query["ssl"];
-            if (strncasecmp(sstr.c_str(), "preferred", 9) == 0) ssl = SSL_MODE_PREFERRED;
-            else if (strncasecmp(sstr.c_str(), "required", 8) == 0) ssl = SSL_MODE_REQUIRED;
-            else if (strncasecmp(sstr.c_str(), "verify_ca", 9) == 0) ssl = SSL_MODE_VERIFY_CA;
-            else if (strncasecmp(sstr.c_str(), "verify_identity", 15) == 0) ssl = SSL_MODE_VERIFY_IDENTITY;
-        }
-        mysql_options(c, MYSQL_OPT_SSL_MODE, &ssl);
+        // 版本 8.0.3 后默认使用 caching_sha2_password 进行认证，低版本不支持
+        mysql_options(c, MYSQL_DEFAULT_AUTH, url_.query["auth"].c_str());
         unsigned int timeout = 5; // 连接超时
         mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
     }
@@ -110,15 +98,15 @@ namespace flame::mysql {
             // 每次连接复用前，需要清理状态;
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
-            if (flag_ == FLAG_UNKNOWN) {
-                query_version(c);
-                query_charset(c); // 由于上述 reset 动作，会导致字符集被重置为服务端字符集，确认字符集是否匹配
-            }
+            if (!(flag_ & FLAG_REUSE_MASK)) query_version(c);
+            // 由于 reset 动作会导致字符集被重置为服务端字符集，确认字符集是否匹配
+            if (!(flag_ & FLAG_CHARSET_MASK)) query_charset(c);
+
             if (flag_ & FLAG_REUSE_BY_RESET) mysql_reset_connection(c);
             else if (flag_ & FLAG_REUSE_BY_CUSER) mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
             // else if (flag_ & FLAG_REUSE_BY_PROXY) ; // PROXY 已处理复用流程，此处无需处理
-            
-            if (flag_ & FLAG_CHARSET_DIFFER) mysql_set_character_set(c, url_.query["charset"].c_str());
+            if (flag_ & FLAG_CHARSET_DIFFER)
+                mysql_set_character_set(c, url_.query["charset"].c_str());
             std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
             await_.pop_front();
             auto self = this->shared_from_this();
@@ -144,13 +132,10 @@ namespace flame::mysql {
     }
 
     void _connection_pool::query_version(MYSQL* c) {
-        if (url_.query.count("proxy") > 0) {
-            if (std::stoi(url_.query["proxy"]) > 0) {
-                flag_ |= FLAG_REUSE_BY_PROXY;
-                return;
-            }
-        }
-        flag_ |= mysql_get_server_version(c) >= 50700 ? FLAG_REUSE_BY_RESET : FLAG_REUSE_BY_CUSER;
+        // PROXY 存在时不会进行下述动作
+        flag_ |= mysql_get_server_version(c) >= 50700
+            ? FLAG_REUSE_BY_RESET
+            : FLAG_REUSE_BY_CUSER;
     }
 
     void _connection_pool::sweep() {
diff --git a/src/mysql/_connection_pool.h b/src/mysql/_connection_pool.h
index 87007e4..8b4be16 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/mysql/_connection_pool.h
@@ -30,9 +30,11 @@ namespace flame::mysql {
         int flag_;
         enum {
             FLAG_UNKNOWN        = 0x00,
+            FLAG_REUSE_MASK     = 0x0f,
             FLAG_REUSE_BY_RESET = 0x01,
             FLAG_REUSE_BY_CUSER = 0x02,
             FLAG_REUSE_BY_PROXY = 0x04,
+            FLAG_CHARSET_MASK   = 0xf0,
             FLAG_CHARSET_EQUAL  = 0x10,
             FLAG_CHARSET_DIFFER = 0x20,
         };
diff --git a/src/mysql/mysql.cpp b/src/mysql/mysql.cpp
index 3bc87de..cc25e2e 100644
--- a/src/mysql/mysql.cpp
+++ b/src/mysql/mysql.cpp
@@ -30,7 +30,8 @@ namespace flame::mysql {
         url u(params[0]);
         if (u.port < 10) u.port = 3306;
         if (!u.query.count("charset")) u.query["charset"] = "utf8mb4";
-        if (!u.query.count("ssl")) u.query["ssl"] = "disabled";
+        // 版本 8.0.3 后默认使用 caching_sha2_password 进行认证，低版本不支持
+        if (!u.query.count("auth")) u.query["auth"] = "mysql_native_password";
 
         php::object obj(php::class_entry<client>::entry());
         client *ptr = static_cast<client *>(php::native(obj));
@@ -73,7 +74,7 @@ namespace flame::mysql {
     static void where_lt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
         if (cond.type_of(php::TYPE::NULLABLE)) {
             buf.append("<0", 2);
-        } 
+        }
         else {
             php::string str = cond;
             str.to_string();
@@ -165,7 +166,7 @@ namespace flame::mysql {
             else {
                 php::string key = i->first;
                 if (key.c_str()[0] == '{') { // OPERATOR (php::TYPE::STRING)
-                    if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0) 
+                    if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0)
                         || (key.size() == 3 && strncasecmp(key.c_str(), "{!}", 3) == 0)) {
 
                         assert(i->second.type_of(php::TYPE::ARRAY));
diff --git a/src/vendor.h b/src/vendor.h
index e3518d3..76a6721 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -47,7 +47,7 @@ namespace ssl = boost::asio::ssl;
 #include <boost/crc.hpp>
 #include <amqpcpp.h>
 #include <amqpcpp/libboostasio.h>
-#include <mysql.h>
+#include <mariadb/mysql.h>
 // #include <my_sys.h>
 #include <mongoc.h>
 #include <librdkafka/rdkafka.h>
diff --git a/test/http_2.php b/test/http_2.php
index 0c9b14c..6dfb6d2 100644
--- a/test/http_2.php
+++ b/test/http_2.php
@@ -23,6 +23,7 @@ function jsonSerialize()
             return false;
         }
     })->get("/", function($req, $res) {
+        $res->set_cookie("a","b", 3600);
         $res->body = json_encode($req);
     })->post("/", function($req, $res) {
         $res->status = 200;
@@ -77,4 +78,4 @@ function jsonSerialize()
 //     $server->close();
 // });
 
-flame\run();
\ No newline at end of file
+flame\run();
diff --git a/test/tcp_2.php b/test/tcp_2.php
index facb0bc..03fdff2 100644
--- a/test/tcp_2.php
+++ b/test/tcp_2.php
@@ -14,7 +14,7 @@
                     $body = $socket->read($length);
                     echo $body, "\n";
                 }
-                flame\time\sleep(4000);
+                flame\time\sleep(200);
                 $socket->write("HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nok");
             }
         }

From 87c1b8235a22cc7183fbbe32bb414c126146ac33 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 26 Apr 2019 16:36:45 +0800
Subject: [PATCH 115/146] =?UTF-8?q?=E5=88=86=E7=A6=BB=E6=B5=8B=E8=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 test/kafka_1.php | 22 ++--------------------
 test/kafka_2.php | 23 +++++++++++++++++++++++
 2 files changed, 25 insertions(+), 20 deletions(-)
 create mode 100644 test/kafka_2.php

diff --git a/test/kafka_1.php b/test/kafka_1.php
index c5772fa..d199ab6 100644
--- a/test/kafka_1.php
+++ b/test/kafka_1.php
@@ -1,30 +1,12 @@
 <?php
 flame\init("kafka_1");
 
-flame\go(function() {
-    $consumer = flame\kafka\consume([
-        "bootstrap.servers" => "host1:port1,host2:port2",
-        "group.id" => "flame-test-consumer",
-        "auto.offset.reset" => "smallest",
-    ], ["test"]);
-    flame\go(function() use($consumer) {
-        // 60 秒后关闭消费者
-        flame\time\sleep(60000);
-        $consumer->close();
-    });
-    $consumer->run(function($msg) {
-        var_dump($msg);
-        flame\time\sleep(30);
-    });
-});
-
-
 flame\go(function() {
     $producer = flame\kafka\produce([
         "bootstrap.servers" => "host1:port1,host2:port2",
     ], ["test"]);
-    
-    for($i=0;$i<100;++$i) {
+
+    for($i=0;$i<10000;++$i) {
         echo $i, "\n";
         $message = new flame\kafka\message("this is the payload", "this is the key");
         $message->header["key"] = "value";
diff --git a/test/kafka_2.php b/test/kafka_2.php
new file mode 100644
index 0000000..e21164b
--- /dev/null
+++ b/test/kafka_2.php
@@ -0,0 +1,23 @@
+<?php
+flame\init("kafka_1");
+
+flame\go(function() {
+    $consumer = flame\kafka\consume([
+        "bootstrap.servers" => "host1:port1,host2:port2",
+        "group.id" => "flame-test-consumer",
+        "auto.offset.reset" => "smallest",
+    ], ["test"]);
+    flame\go(function() use($consumer) {
+        // 60 秒后关闭消费者
+        flame\time\sleep(60000);
+        $consumer->close();
+    });
+    $consumer->run(function($msg) {
+        var_dump($msg);
+        flame\time\sleep(50);
+    });
+    echo "done.\n";
+    unset($consumer);
+});
+
+flame\run();

From e7ad4b42b58ab5b7ff7031ca33ec717fb934278d Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 30 Apr 2019 15:51:39 +0800
Subject: [PATCH 116/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E4=BD=9C?=
 =?UTF-8?q?=E4=B8=BA=E6=9F=A5=E8=AF=A2=E6=9D=A1=E4=BB=B6=E6=97=B6=EF=BC=8C?=
 =?UTF-8?q?=E7=A9=BA=E6=95=B0=E7=BB=84=E6=8C=89=20OBJECT=20=E8=BD=AC?=
 =?UTF-8?q?=E6=8D=A2=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/mongodb.php            |  2 ++
 src/mongodb/collection.cpp | 26 ++++++++++++++++----------
 src/mongodb/object_id.cpp  |  2 +-
 3 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/doc/mongodb.php b/doc/mongodb.php
index 4e40234..92a2734 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -6,6 +6,8 @@
  * 2. 对应 MongoDB ObjectID 映射类型 class flame\mongodb\object_id;
  * 3. 对应 MongoDB DateTime 映射类型 class flame\mongodb\date_time;
  * 4. 单客户端内连接池上限大小为 6（单进程）；
+ * 5. 由于 PHP 数组混合了关联数组机制，空查询条件时可考虑使用 new StdClass() 空对象代替空数组，以明确区分 JSON Object/Array 两种形式；
+ *    针对 find 相关操作框架内部**对查询条件**已进行了上述**适配**；其他情况的空数组将按照 BSON_ARRAY 转换；
  */
 namespace flame\mongodb;
 
diff --git a/src/mongodb/collection.cpp b/src/mongodb/collection.cpp
index 632705e..4d75a92 100644
--- a/src/mongodb/collection.cpp
+++ b/src/mongodb/collection.cpp
@@ -52,7 +52,7 @@ namespace flame::mongodb {
                 {"sort", php::TYPE::ARRAY, false, true},
                 {"limit", php::TYPE::UNDEFINED, false, true},
             })
-            .method<&collection::find_one>("one", { 
+            .method<&collection::find_one>("one", {
                 {"filter", php::TYPE::ARRAY},
                 {"sort", php::TYPE::ARRAY, false, true},
             })
@@ -165,8 +165,8 @@ namespace flame::mongodb {
     php::value collection::find(php::parameters &params) {
         php::array cmd(8);
         cmd.set("find", name_);
-        if (params.size() > 0)
-            cmd.set("filter", params[0]);
+        if(params[0].empty()) cmd.set("filter", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("filter", params[0]);
 
         if (params.size() > 1) {
             php::array project;
@@ -208,7 +208,9 @@ namespace flame::mongodb {
     php::value collection::find_one(php::parameters &params) {
         php::array cmd(8);
         cmd.set("find", name_);
-        cmd.set("filter", params[0]);
+        if(params[0].empty()) cmd.set("filter", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("filter", params[0]);
+
         if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY))
             cmd.set("sort", params[1]);
 
@@ -219,7 +221,7 @@ namespace flame::mongodb {
         assert(cs.instanceof(php::class_entry<cursor>::entry()));
         return cs.call("fetch_row");
     }
-    
+
     php::value collection::get(php::parameters &params) {
         php::array cmd(8);
         cmd.set("find", name_);
@@ -227,7 +229,8 @@ namespace flame::mongodb {
         php::string field = params[1];
         project.set(field, 1);
         cmd.set("projection", project);
-        cmd.set("filter", params[0]);
+        if (params[0].empty()) cmd.set("filter", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("filter", params[0]);
         if (params.size() > 2 && params[2].type_of(php::TYPE::ARRAY))
             cmd.set("sort", params[2]);
         cmd.set("limit", 1);
@@ -244,6 +247,7 @@ namespace flame::mongodb {
     php::value collection::count(php::parameters &params) {
         php::array cmd(8);
         cmd.set("count", name_);
+        if (params[0].empty()) cmd.set("query", php::object(php::CLASS(zend_standard_class_def)));
         cmd.set("query", params[0]);
         if (params.size() > 1) {
             if (params[1].type_of(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
@@ -277,7 +281,7 @@ namespace flame::mongodb {
         int execute_type = _connection_base::COMMAND_READ;
         if (params.size() > 2)
             execute_type = params[2].to_integer();
-        
+
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, execute_type, ch);
@@ -285,7 +289,8 @@ namespace flame::mongodb {
 
     php::value collection::find_and_delete(php::parameters& params) {
         php::array cmd(8);
-        cmd.set("query", params[0]);
+        if (params[0].empty()) cmd.set("query", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("query", params[0]);
         cmd.set("remove", true);
         if (params.size() > 1) {
             php::value f = params[1];
@@ -303,7 +308,8 @@ namespace flame::mongodb {
 
     php::value collection::find_and_update(php::parameters& params) {
         php::array cmd(8);
-        cmd.set("query", params[0]);
+        if (params[0].empty()) cmd.set("query", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("query", params[0]);
         if (params.size() > 1) {
             php::value f = params[1];
             if (f.type_of(php::TYPE::ARRAY)) cmd.set("update", f);
@@ -318,7 +324,7 @@ namespace flame::mongodb {
             if (f.type_of(php::TYPE::ARRAY)) cmd.set("fields", f);
         }
         if (params.size() > 5) cmd.set("new", params[5].to_boolean());
-        
+
         coroutine_handler ch {coroutine::current};
         auto conn_ = cp_->acquire(ch);
         return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ_WRITE, ch);
diff --git a/src/mongodb/object_id.cpp b/src/mongodb/object_id.cpp
index 4a636d0..2c8478b 100644
--- a/src/mongodb/object_id.cpp
+++ b/src/mongodb/object_id.cpp
@@ -20,7 +20,7 @@ namespace flame::mongodb {
     }
 
     php::value object_id::__construct(php::parameters &params) {
-        if (params.size() > 0) {
+        if (params.size() > 0 && params[0].type_of(php::TYPE::STRING)) {
             php::string oid = params[0].to_string();
             bson_oid_init_from_string(&oid_, oid.c_str());
         }

From e34d9b07082258c4691625817aa59343d5164f81 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 9 May 2019 16:28:27 +0800
Subject: [PATCH 117/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=85=81?=
 =?UTF-8?q?=E8=AE=B8=E9=9D=9E=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=95=B0=E6=8D=AE?=
 =?UTF-8?q?=E4=BD=9C=E4=B8=BA=20HEADER=20=E5=80=BC=EF=BC=9B=E4=BF=AE?=
 =?UTF-8?q?=E6=AD=A3=EF=BC=9A=E9=9D=9E=E5=AD=97=E7=AC=A6=E4=B8=B2=20HEADER?=
 =?UTF-8?q?=20=E5=80=BC=E5=AF=BC=E8=87=B4=E7=9A=84=20TypeError=20=E9=94=99?=
 =?UTF-8?q?=E8=AF=AF=E9=97=AE=E9=A2=98=EF=BC=9B=E6=84=9F=E8=B0=A2=20@cotat?=
 =?UTF-8?q?i?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/client_request.cpp  | 21 ++++++++++-----------
 src/http/server_response.cpp |  6 +++---
 test/http_1.php              |  7 +++++--
 test/http_2.php              |  2 ++
 4 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/src/http/client_request.cpp b/src/http/client_request.cpp
index 7cb0a13..1eda358 100644
--- a/src/http/client_request.cpp
+++ b/src/http/client_request.cpp
@@ -76,7 +76,7 @@ namespace flame::http {
             throw php::exception(zend_ce_error_exception
                 , "Failed to set HTTP version: value out of range"
                 , -1);
-        
+
         curl_easy_setopt(c_easy_, CURLOPT_HTTP_VERSION, v);
         return nullptr;
     }
@@ -98,7 +98,7 @@ namespace flame::http {
 
     php::value client_request::ssl_verify(php::parameters& params) {
         long v = static_cast<int>(params[0]), YES = 1, NO = 0;
-        if (v | CURLOPT_SSL_VERIFYPEER) 
+        if (v | CURLOPT_SSL_VERIFYPEER)
             curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, YES);
         else
             curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, NO);
@@ -106,9 +106,9 @@ namespace flame::http {
             curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, YES);
         else
             curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, NO);
-        if (v | CURLOPT_SSL_VERIFYSTATUS) 
+        if (v | CURLOPT_SSL_VERIFYSTATUS)
             curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, YES);
-        else 
+        else
             curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, NO);
         return nullptr;
     }
@@ -119,11 +119,11 @@ namespace flame::http {
         // 目标请求地址
         // ---------------------------------------------------------------------------
         php::string u = get("url");
-        if (!u.type_of(php::TYPE::STRING)) 
+        if (!u.type_of(php::TYPE::STRING))
             throw php::exception(zend_ce_type_error
                 , "Failed to build client request: 'url' typeof 'string' required"
                 , -1);
-        
+
         curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
         php::string m = get("method");
         curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
@@ -134,9 +134,9 @@ namespace flame::http {
         long keepalive = 1;
         php::array header = get("header");
         for (auto i=header.begin(); i!=header.end(); ++i) {
-            std::string key = i->first;
-            std::string val = i->second;
-            if (strncasecmp(key.c_str(), "content-type", 12) == 0) ctype = val;
+            php::string key = i->first.to_string();
+            php::string val = i->second.to_string();
+            if (strncasecmp(key.c_str(), "content-type", 12) == 0) ctype.assign(val.data(), val.size());
             else if (strncasecmp(key.c_str(), "connection", 10) == 0
                 && strncasecmp(val.c_str(), "close", 5) == 0) keepalive = 0;
             c_head_ = curl_slist_append(c_head_, (boost::format("%s: %s") % key % val).str().c_str());
@@ -149,7 +149,7 @@ namespace flame::http {
         php::array cookie = get("cookie");
         php::buffer cookies;
         for (auto i=cookie.begin(); i!=cookie.end(); ++i) {
-            php::string key = i->first;
+            php::string key = i->first.to_string();
             php::string val = i->second.to_string();
             val = php::url_encode(val.c_str(), val.size());
             cookies.append(key);
@@ -180,4 +180,3 @@ namespace flame::http {
         }
     }
 }
-
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index ffa5051..ea72d35 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -183,9 +183,9 @@ namespace flame::http {
         php::array headers = get("header");
         if (headers.type_of(php::TYPE::ARRAY)) {
             for (auto i=headers.begin(); i!=headers.end(); ++i) {
-                php::string key { i->first };
-                php::string val { i->second };
-                ctr_.set(std::string_view(key.c_str(), key.size()), val);
+                php::string key = i->first.to_string();
+                php::string val = i->second.to_string();
+                ctr_.set(std::string_view(key.c_str(), key.size()), static_cast<std::string>(val));
             }
         }
         php::array cookies = get("cookie");
diff --git a/test/http_1.php b/test/http_1.php
index ad713a0..00644d2 100644
--- a/test/http_1.php
+++ b/test/http_1.php
@@ -8,9 +8,12 @@
         flame\http\get("https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/static/protocol/https/home/img/qrcode/zbios_efde696.png", 3000);
         var_dump( flame\http\put("http://127.0.0.1:8687/", "this is the data for put request", 800000) );
         var_dump( flame\http\get("http://127.0.0.1:8687/") );
-        $req = new flame\http\client_request('https://www.baidu.com/');
+        // $req = new flame\http\client_request('https://www.baidu.com/');
+        $req = new flame\http\client_request('http://127.0.0.1:8687/');
         $req->method = "POST";
-        $req->http_version(flame\http\client_request::HTTP_VERSION_2_PRI);
+        $req->header["test1"] = "string";
+        $req->header["test2"] = 123456;
+        // $req->http_version(flame\http\client_request::HTTP_VERSION_2_PRI);
         var_dump(flame\http\exec($req));
         echo "done.\n";
     });
diff --git a/test/http_2.php b/test/http_2.php
index 6dfb6d2..3f6131a 100644
--- a/test/http_2.php
+++ b/test/http_2.php
@@ -23,6 +23,8 @@ function jsonSerialize()
             return false;
         }
     })->get("/", function($req, $res) {
+        $req->header["test1"] = "string";
+        $req->header["test2"] = 123456;
         $res->set_cookie("a","b", 3600);
         $res->body = json_encode($req);
     })->post("/", function($req, $res) {

From 3d3fca85fd6abd332e96e163391770052ec61e3c Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Sun, 12 May 2019 19:21:31 +0800
Subject: [PATCH 118/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/http.php                   | 10 ++---
 doc/kafka.php                  |  2 +-
 doc/log.php                    | 16 ++++----
 doc/mongodb.php                |  8 ++--
 doc/mysql.php                  | 20 +++++-----
 doc/rabbitmq.php               | 10 ++---
 doc/redis.php                  | 68 ++++++++++++++++++++++++----------
 doc/tcp.php                    |  2 +-
 doc/time.php                   |  2 +-
 doc/udp.php                    |  9 ++---
 src/controller_master.cpp      |  1 -
 src/mysql/_connection_base.cpp |  1 +
 src/mysql/_connection_pool.cpp |  2 +-
 13 files changed, 89 insertions(+), 62 deletions(-)

diff --git a/doc/http.php b/doc/http.php
index f00ea52..84ff188 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -21,13 +21,13 @@ function get(string $url, int $timeout = 3000): client_response {
 /**
  * 使用默认客户端, 构建并执行一个 POST 请求
  */
-function post(string $url, mixed $data, int $timeout = 3000): client_response {
+function post(string $url, $data, int $timeout = 3000): client_response {
     return new client_response();
 }
 /**
  * 使用默认客户端, 构建并执行一个 PUT 请求
  */
-function put(string $url, mixed $data, int $timeout = 3000): client_response {
+function put(string $url, $data, int $timeout = 3000): client_response {
     return new client_response();
 }
 /**
@@ -62,13 +62,13 @@ function get(string $url, int $timeout = 3000): client_response {
     /**
      * 构建并执行一个 POST 请求
      */
-    function post(string $url, mixed $data, int $timeout = 3000): client_response {
+    function post(string $url, $data, int $timeout = 3000): client_response {
         return new client_response();
     }
     /**
      * 构建并执行一个 PUT 请求
      */
-    function put(string $url, mixed $data, int $timeout = 3000): client_response {
+    function put(string $url, $data, int $timeout = 3000): client_response {
         return new client_response();
     }
     /**
@@ -152,7 +152,7 @@ class client_request {
     /**
      * 构建请求, 可选的指定请求体 `$body` (将自动设置为 POST 请求方法)及超时时间 `$timeout`
      */
-    function __construct(string $url, mixed $body = null, int $timeout = 3000) {}
+    function __construct(string $url, $body = null, int $timeout = 3000) {}
     /**
      * @param string $cert_file 证书文件路径
      * @param string $pkey_file 密钥文件路径
diff --git a/doc/kafka.php b/doc/kafka.php
index c2c764e..3e3efee 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -66,7 +66,7 @@ class producer {
      * @param string $key
      * @param array $header
      */
-    function publish(string $topic, mixed $message, string $key = null, array $header = []) {}
+    function publish(string $topic, $message, string $key = null, array $header = []) {}
     /**
      * 清空"生产队列", 若当前队列中还有消息等待其传输完毕;
      */
diff --git a/doc/log.php b/doc/log.php
index b2b2dbb..9ce84a4 100644
--- a/doc/log.php
+++ b/doc/log.php
@@ -8,33 +8,33 @@
 /**
  * 记录 TRACE 级别日志, 自动进行 JSON 形式的序列化;
  */
-function trace($x, $y, $z /* ... */) {}
+function trace($x/*, $y, $z  ... */) {}
 /**
  * 记录 DEBUG 级别日志, 自动进行 JSON 形式的序列化;
  */
-function debug($x, $y, $z /* ... */) {}
+function debug($x/*, $y, $z  ... */) {}
 /**
  * 记录 INFO 级别日志, 自动进行 JSON 形式的序列化;
  */
-function info($x, $y, $z /* ... */) {}
+function info($x/*, $y, $z  ... */) {}
 /**
  * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
  */
-function warn($x, $y, $z /* ... */) {}
+function warn($x/*, $y, $z  ... */) {}
 /**
  * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
  */
-function warning($x, $y, $z /* ... */) {}
+function warning($x/*, $y, $z  ... */) {}
 /**
  * 记录 ERROR 级别日志, 自动进行 JSON 形式的序列化;
  */
-function error($x, $y, $z /* ... */) {}
+function error($x/*, $y, $z  ... */) {}
 /**
  * 同 fatal()
  * @see fatal()
  */
-function fail($x, $y, $z /* ... */) {}
+function fail($x/*, $y, $z  ... */) {}
 /**
  * 记录 FATAL 级别日志, 自动进行 JSON 形式的序列化;
  */
-function fatal($x, $y, $z /* ... */) {}
\ No newline at end of file
+function fatal($x/*, $y, $z  ... */) {}
\ No newline at end of file
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 92a2734..63e6a90 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -120,10 +120,10 @@ function update_one(array $query, array $update, $upsert = false):array {
      *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
      * @param mixed $limit 可以为 int 设置 `limit` 值，或 array 设置 `[$skip, $limit]`
      */
-    function find(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {
+    function find(array $query, array $projection = null, array $sort = null, $limit = null):cursor {
         return new cursor();
     }
-    function find_many(array $query, array $projection = null, array $sort = null, mixed $limit = null):cursor {
+    function find_many(array $query, array $projection = null, array $sort = null, $limit = null):cursor {
         return new cursor();
     }
 
@@ -151,7 +151,7 @@ function get(array $query, string $field, array $sort = null) {
      * 查询并返回匹配文档数量
      * @param mixed $limit integer/array 请参见 find 函数对应 $limit 说明
      */
-    function count(array $query, mixed $limit): int {
+    function count(array $query, $limit): int {
         return 0;
     }
     /**
@@ -164,7 +164,7 @@ function aggregate(array $pipeline, array $options = []): cursor {
     /**
      * 查找满足条件的第一个文档，删除并将其返回
      */
-    function find_and_delete(array $query, array $sort = null, $upsert = false, $fields = null): array {
+    function find_and_delete(array $query, array $sort = null, boolean $upsert = false, $fields = null): array {
         return [];
     }
     /**
diff --git a/doc/mysql.php b/doc/mysql.php
index 579db6c..deffb20 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -131,7 +131,7 @@ function insert(string $table, array $data): result {
      * @param mixed $limit LIMIT 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
      * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
-    function delete(string $table, mixed $where, mixed $order, mixed $limit): result {
+    function delete(string $table, $where, $order = null, $limit = null): result {
         return new result();
     }
     /**
@@ -143,7 +143,7 @@ function delete(string $table, mixed $where, mixed $order, mixed $limit): result
      * @param mixed $limit LIMIT 子句, 请参考上文;
      * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
      */
-    function update(string $table, mixed $where, mixed $modify, mixed $order, mixed $limit): result {
+    function update(string $table, $where, $modify, $order = null, $limit = null): result {
         return new result();
     }
     /**
@@ -158,20 +158,20 @@ function update(string $table, mixed $where, mixed $modify, mixed $order, mixed
      * @param mixed $order ORDER 子句, 请参考上文;
      * @param mixed $limit LIMIT 子句, 请参考上文;
      */
-    function select(string $table, mixed $fields, array $where, mixed $order, mixed $limit): result {
+    function select(string $table, $fields, $where, $order = null, $limit = null): result {
         return new result();
     }
     /**
      * 从指定表筛选获取一条数据, 并立即返回该行数据
      */
-    function one(string $table, mixed $where, mixed $order): array {
+    function one(string $table, $where, $order = null): array {
         return [];
     }
     /**
      * 从指定表获取一行数据的指定字段值
      * @return mixed 字段类型映射请参见顶部说明
      */
-    function get(string $table, string $field, array $where, mixed $order) {
+    function get(string $table, string $field, array $where, $order) {
         return null;
     }
     /**
@@ -208,32 +208,32 @@ function insert(string $table, array $data): array {
     /**
      * @see client::delete()
      */
-    function delete(string $table, mixed $where, mixed $order, mixed $limit): array {
+    function delete(string $table, $where, $order, $limit): array {
         return [];
     }
     /**
      * @see client::update()
      */
-    function update(string $table, array $where, mixed $modify, mixed $order = null, mixed $limit = null): array {
+    function update(string $table, $where, $modify, $order = null, $limit = null): array {
         return [];
     }
     /**
      * @see client::select()
      */
-    function select(string $table, mixed $fields, array $where, mixed $order = null, mixed $limit = null): result {
+    function select(string $table, $fields, $where, $order = null, $limit = null): result {
         return new result();
     }
     /**
      * @see client::one()
      */
-    function one(string $table, array $where, mixed $order = null): array {
+    function one(string $table, $where, $order = null): array {
         return [];
     }
     /**
      * @see client::get()
      * @return mixed 字段类型映射参见顶部说明；
      */
-    function get(string $table, string $field, rray $where, mixed $order = null) {
+    function get(string $table, string $field, $where, $order = null) {
         return null;
     }
 };
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
index eca0387..3a8b5cf 100644
--- a/doc/rabbitmq.php
+++ b/doc/rabbitmq.php
@@ -56,18 +56,18 @@ class producer {
      * @param mixed $message 为 string 时表示生产的消息的包体; 也可以为 message 类型的对象;
      * @param string $routing_key 路由KEY, 可以覆盖 $message 对象内部的 routing_key 属性
      */
-    function publish(string $exchange, mixed $message, string $routing_key = null) {}
+    function publish(string $exchange, $message, string $routing_key = null) {}
 }
 /**
  * 消息对象
  */
 class message implements JsonSerializable {
     /**
-     * 路由键值
+     * @var string 路由键值
      */
     public $routing_key;
     /**
-     * 消息体
+     * @var string 消息体
      */
     public $body;
     public $expiration;
@@ -86,14 +86,14 @@ class message implements JsonSerializable {
     public $user_id;
     public $type_name;
     /**
-     * 消息时间戳，一般为秒级（由于可用户自定指定，含义可能发生变异）
+     * @var int 消息时间戳，一般为秒级（由于可用户自定指定，含义可能发生变异）
      */
     public $timestamp;
     public $message_id;
     /**
      * 构建一条新的 message 消息
      */
-    function __construct($body = "", $routing_key = "") {}
+    function __construct(string $body = "", string $routing_key = "") {}
     /**
      * 返回消息体
      */
diff --git a/doc/redis.php b/doc/redis.php
index 314edc5..cc00cac 100644
--- a/doc/redis.php
+++ b/doc/redis.php
@@ -12,6 +12,51 @@ function connect(string $url):client {
 }
 /**
  * Redis 客户端
+ * 支持 Redis 大部分指令:
+ *
+ * @method get(string $key)
+ * @method int del(string $key)
+ * @method int expire(string $key, int $seconds)
+ * @method int ttl(string $key)
+ * @method string type(string $key)
+ * @method string set(string $key, string $val)
+ * @method string mset(string $key, string $val)
+ * @method array mget(string $key)
+ * @method array scan(int $cursor)
+ * @method int incr(string $key)
+ * @method int incrby(string $key, int $by)
+ * @method int sadd(string $key, string $member)
+ * @method int scard(string $key)
+ * @method string spop(string $key)
+ * @method int srem(string $key, string $member)
+ * @method string lpop(string $key)
+ * @method string rpop(string $key)
+ * @method int llen(string $key)
+ * @method int lpush(string $key)
+ * @method int rpush(string $key)
+ * @method array blpop(string $key)
+ * @method array lrpop(string $key)
+ * @method int hdel(string $key)
+ * @method hget(string $key, string $field)
+ * @method int hset(string $key, string $field, string $val)
+ * @method array hmget(string $key, string $field1)
+ * @method string hmset(string $key, string $field, string $val)
+ * @method array hgetall(string $key)
+ * @method int hincrby(string $key, string $field, int $by)
+ * @method int hlen(string $key)
+ * @method int zadd(string $key, $score, $member)
+ * @method int zincrby(string $key, int $inc, string $member)
+ * @method int zcount(string $key, int $min, int $max)
+ * @method int zcard(string $key)
+ * @method int zrank(string $key, string $member)
+ * @method int zrem(string $key, string $member)
+ * @method string zscore(string $key, string $member)
+ * @method array zrange(string $key, int $start, int $stop)
+ * @method array zrevrange(string $key, int $start, int $stop)
+ * @method array zrangebyscore(string $key, int $min, int $max)
+ * @method array zrevrangebyscore(string $key, int $min, int $max)
+ * ...
+ * 这里仅列出了常用的部分函数及其对应的必要参数
  */
 class client {
     /**
@@ -25,25 +70,6 @@ function __call($name, $argv) {}
     function __isset($name) {
         return true;
     }
-    /**
-     * 支持 Redis 大部分指令:
-     *
-     * @method get()
-     * @method set()
-     * @method incr()
-     * @method zincr()
-     * @method mset()
-     * @method mget()
-     * @method hmset()
-     * @method hmget()
-     * @method hgetall()
-     * @method zrange()
-     * @method zrevrange()
-     * @method zrangebyscore()
-     * @method zrevrangebyscore()
-     * ...
-     * 这里不再一一列举
-     */
     /**
      * 批量执行
      */
@@ -87,3 +113,7 @@ function exec():array {
         return [];
     }
 }
+
+
+
+$r = new client();
diff --git a/doc/tcp.php b/doc/tcp.php
index ef53c5b..1220db6 100644
--- a/doc/tcp.php
+++ b/doc/tcp.php
@@ -30,7 +30,7 @@ class socket {
      *  * 参数为 int 时, 表示读取指定量的数据, 例如 $completion = 1024 表示读取 1024 字节;
      *  * 参数为 string 时, 表示读取到指定的结束符, 例如 $completion = "\r\n" 读取到 "abc\r\n" 字符串;
      */
-    function read(mixed $completion = null) {}
+    function read($completion = null) {}
     /**
      * 发送数据
      * @param string $data
diff --git a/doc/time.php b/doc/time.php
index f6c110c..aae7f27 100644
--- a/doc/time.php
+++ b/doc/time.php
@@ -13,7 +13,7 @@ function sleep(int $ms) {}
  * (此时间戳采用框架缓存机制，较默认获取系统时间函数消耗较小)
  */
 function now():int {
-    return intval(microtime(true) * 1000);
+    return 1557659996427;
 }
 /**
  * 获取标准时间 "YYYY-mm-dd HH:ii:ss"
diff --git a/doc/udp.php b/doc/udp.php
index 49a8538..e4972c8 100644
--- a/doc/udp.php
+++ b/doc/udp.php
@@ -24,15 +24,13 @@ class socket {
      * @param array $option 可用选项如下:
      * * `max` integer - 接收缓冲区上限(默认为 64k 可减小已提升效率)
      */
-    function __construct(mixed $opt_or_address = null, array $option = []) {}
+    function __construct($opt_or_address = null, array $option = []) {}
     /**
-     * 本地地址（含端口）
-     * @var string
+     * @var string 本地地址（含端口
      */
     public $local_address;
     /**
-     * 远端地址（含端口）
-     * @var string
+     * @var string 远端地址（含端口）
      */
     public $remote_address;
     /**
@@ -66,7 +64,6 @@ class server {
     /**
      * 创建一个服务器, 绑定在指定的地址上, 准备服务
      * @param string $address 绑定地址, 例如: "0.0.0.0:8888" 或 ":::8888"
-     * @param string
      */
     function __construct(string $address, array $options = []) {}
     /**
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 56b968a..446dc9c 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -103,7 +103,6 @@ namespace flame {
     }
 
     void controller_master::close_worker(int i) {
-        std::cout << "close_worker: " << i << "\n";
         if (worker_close_[i] == nullptr) return;
         if ((close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_TERMINATE) worker_close_[i]->close_now();
         else worker_close_[i]->close();
diff --git a/src/mysql/_connection_base.cpp b/src/mysql/_connection_base.cpp
index c313f1c..5cfd4b3 100644
--- a/src/mysql/_connection_base.cpp
+++ b/src/mysql/_connection_base.cpp
@@ -101,6 +101,7 @@ namespace flame::mysql {
             ch.resume();
         });
         ch.suspend();
+        // std::cout << "last_query: " << sql << ": " << err << "\n";
         last_query_ = sql;
         if (err != 0) {
             int err = mysql_errno(conn.get());
diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index c1d12ba..27e3816 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -55,7 +55,7 @@ namespace flame::mysql {
             else {
                 errnum = mysql_errno(c);
                 errmsg = mysql_error(c);
-                std::cout << "connect failed:" << errnum << "/" << errmsg << "\n";
+                // std::cout << "errnum: " << errnum << " errmsg: " << errmsg << "\n"; 
                 mysql_close(c);
                 await_.pop_back();
                 ch.resume();

From bf2730ba1b4cea97e1f1a75175cb5f83828065b7 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 13 May 2019 15:54:40 +0800
Subject: [PATCH 119/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E7=A6=81?=
 =?UTF-8?q?=E6=AD=A2=E5=9C=A8=E9=9D=9E=E5=8D=8F=E7=A8=8B=E4=B8=8A=E4=B8=8B?=
 =?UTF-8?q?=E6=96=87=E8=BF=90=E8=A1=8C=E5=8D=8F=E7=A8=8B=E5=87=BD=E6=95=B0?=
 =?UTF-8?q?=EF=BC=88=E9=98=B2=E6=AD=A2=E5=87=BA=E7=8E=B0=E6=AE=B5=E9=94=99?=
 =?UTF-8?q?=E8=AF=AF=E7=AD=89=EF=BC=89,=20=E6=84=9F=E8=B0=A2=20@qingdian-y?=
 =?UTF-8?q?anghao?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/coroutine.cpp | 2 ++
 src/flame.cpp     | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/coroutine.cpp b/src/coroutine.cpp
index b524950..d8af867 100644
--- a/src/coroutine.cpp
+++ b/src/coroutine.cpp
@@ -173,11 +173,13 @@ namespace flame {
     }
 
     void coroutine_handler::resume() {
+        if (!co_) throw php::exception(zend_ce_parse_error, "Failed to resume coroutine, missing 'flame\\go()' ?", 0);
         if (--*stat_ == 0) boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
     }
 
     void coroutine_handler::suspend() {
         assert(std::this_thread::get_id() == gcontroller->mthread_id);
+        if (!co_) throw php::exception(zend_ce_parse_error, "Failed to suspend coroutine, missing 'flame\\go()' ?", 0);
         if (++*stat_ == 1) co_->suspend();
     }
     
diff --git a/src/flame.cpp b/src/flame.cpp
index 4d9937e..7a9bc94 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -68,7 +68,7 @@ namespace flame {
             gcontroller->default_execute_data = EG(current_execute_data);
             gcontroller->run();
         }
-        else throw php::exception(zend_ce_error_exception
+        else throw php::exception(zend_ce_parse_error
             , "Failed to run flame: exception or missing 'flame\\init()' ?"
             , -1);
         return nullptr;

From 546e45179d0f00fc6cfe60eef50a32e9fa8ada60 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 16 May 2019 11:40:06 +0800
Subject: [PATCH 120/146] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=EF=BC=88?=
 =?UTF-8?q?=E5=9B=A0=E5=BC=82=E5=B8=B8=EF=BC=89=E9=87=8D=E5=90=AF=E5=AD=90?=
 =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E7=AD=89=E5=BE=85=E6=9C=9F=E9=97=B4=E5=85=B3?=
 =?UTF-8?q?=E9=97=AD=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=90=AF=E5=8A=A8=E5=AD=90?=
 =?UTF-8?q?=E8=BF=9B=E7=A8=8B=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_master.cpp | 33 ++++++++++++++++++++++++---------
 src/controller_master.h   | 12 ++++++------
 2 files changed, 30 insertions(+), 15 deletions(-)

diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 446dc9c..9c143c0 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -22,7 +22,7 @@ namespace flame {
         boost::process::environment env = gcontroller->env;
         env["FLAME_CUR_WORKER"] = std::to_string(i + 1);
         // 构造进程
-        proc_ = boost::process::child(gcontroller->context_x, cmd, env, 
+        proc_ = boost::process::child(gcontroller->context_x, cmd, env,
             boost::process::std_out > sout_, boost::process::std_err > eout_,
             // 结束回调
             boost::process::on_exit = [this, i](int exit_code, const std::error_code &error) {
@@ -84,7 +84,7 @@ namespace flame {
         if (i >= gcontroller->worker_size) return;
         if (i == 0) worker_spawn_count_ = 0;
         spawn_worker(i);
-        
+
         // 防止启动过快，按间隔进行启动
         tm_inter_.cancel();
         tm_inter_.expires_after(std::chrono::milliseconds(125));
@@ -156,15 +156,16 @@ namespace flame {
 
     void controller_master::notify_exit(int i, controller_master_worker* w, int exit_code) {
         // 异常退出
-        if (exit_code != 0) { 
+        if (exit_code != 0 && (close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_MASTER) {
             int sec = std::rand() % 3 + 3;
-            std::cerr << "(FATAL) worker unexpected exit: (" << exit_code << "), restart in " << sec << " ...\n";
+            std::cerr << "[" << system_time() << "] (FATAL) worker unexpected exit: (" << exit_code << "), restart in " << sec << " ...\n";
             // 少许延迟进行重启
             auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
             tm->async_wait([this, tm, i](const boost::system::error_code &error) {
-                if (error) std::cerr << "(FATAL) failed to restart worker: ("
+                if (error) std::cerr << "[" << system_time()
+                    << "] (FATAL) failed to restart worker: ("
                     << error.value() << ") " << error.message() << std::endl;
-                else spawn_worker(i); // 重启进程
+                else if((close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_MASTER) spawn_worker(i); // 重启进程
             });
         }
         // 下面容器理论上仅会命中一个
@@ -173,9 +174,9 @@ namespace flame {
         delete w;
         --worker_count_;
         // 正常退出
-        if (exit_code == 0) {
+        if (exit_code == 0 || (close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) {
             // 所有进程全部关闭, 同时计划关闭主进程
-            if (worker_count_ == 0 && (close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) close(); 
+            if (worker_count_ == 0 && (close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) close();
             // 重启模式, 关闭了一个进程
             // if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_RESTART
             // && (close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_SIGNALING) {
@@ -195,7 +196,7 @@ namespace flame {
 
     void controller_master::close() {
         // 强制模式，子进程已经全部被结束了 -> 主进退出
-        gcontroller->context_x.stop(); 
+        gcontroller->context_x.stop();
     }
 
     void controller_master::await_signal() {
@@ -256,5 +257,19 @@ namespace flame {
         }
         offile_.reset(&std::clog, boost::null_deleter());
     }
+    // 住进成没有进行 time 明明空间相关的初始化，系统时间需要额外手段获取
+    const char* controller_master::system_time() {
+        static char buffer[24] = {0};
+        std::time_t t = std::time(nullptr);
+        struct tm *m = std::localtime(&t);
+        sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d",
+                1900 + m->tm_year,
+                1 + m->tm_mon,
+                m->tm_mday,
+                m->tm_hour,
+                m->tm_min,
+                m->tm_sec);
+        return buffer;
+    }
 
 }
diff --git a/src/controller_master.h b/src/controller_master.h
index 7cf8e94..31648c8 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -42,10 +42,10 @@ namespace flame {
         void spawn_worker_next(int i = 0);
         void notify_exit(int i, controller_master_worker* w, int exit_code);
         enum {
-            CLOSE_NOTHING, 
+            CLOSE_NOTHING,
             CLOSE_WORKER_SIGNALING = 0x0001, // 信号通知 + 超时
             CLOSE_WORKER_TERMINATE = 0x0002, // 立即停止
-            CLOSE_WORKER_MASK      = 0x000f, 
+            CLOSE_WORKER_MASK      = 0x000f,
             CLOSE_FLAG_RESTART     = 0x0010, // 重启
             CLOSE_FLAG_MASTER      = 0x0020, // 停止主进程
             CLOSE_FLAG_MASK        = 0x00f0,
@@ -53,14 +53,14 @@ namespace flame {
         int close_ = CLOSE_NOTHING;
         void close_worker(int i);
         void close_worker_next(int i = 0);
-
         void close();
 
         void reload_worker();
-        
-
         void reload_output();
+
         void await_signal();
+
+        const char* system_time();
         friend class controller_master_worker;
     };
-}
\ No newline at end of file
+}

From 6ed3d7fea50c72e99e74aa58b5fcb0a84cbb7f9e Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 17 May 2019 11:12:10 +0800
Subject: [PATCH 121/146] =?UTF-8?q?=E8=B0=83=E6=95=B4=EF=BC=9A=E5=BF=BD?=
 =?UTF-8?q?=E7=95=A5=20UDP=20=E8=BF=9E=E6=8E=A5=E5=9E=8B=E7=94=A8=E6=B3=95?=
 =?UTF-8?q?=E7=9A=84=E7=89=B9=E5=AE=9A=E9=94=99=E8=AF=AF=20=EF=BC=9F?=
 =?UTF-8?q?=EF=BC=9F=EF=BC=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller_master.cpp | 2 +-
 src/udp/socket.cpp        | 7 ++++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 9c143c0..73b4430 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -157,7 +157,7 @@ namespace flame {
     void controller_master::notify_exit(int i, controller_master_worker* w, int exit_code) {
         // 异常退出
         if (exit_code != 0 && (close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_MASTER) {
-            int sec = std::rand() % 3 + 3;
+            int sec = std::rand() % 5 + 1;
             std::cerr << "[" << system_time() << "] (FATAL) worker unexpected exit: (" << exit_code << "), restart in " << sec << " ...\n";
             // 少许延迟进行重启
             auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
diff --git a/src/udp/socket.cpp b/src/udp/socket.cpp
index bf353fc..c7f554c 100644
--- a/src/udp/socket.cpp
+++ b/src/udp/socket.cpp
@@ -110,7 +110,7 @@ namespace flame::udp {
         // 数据返回
         if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
         else if (err) throw php::exception(zend_ce_exception
-            , (boost::format("failed to read TCP socket: %s") % err.message()).str()
+            , (boost::format("failed to read UDP socket: %s") % err.message()).str()
             , err.value());
         if (params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
         return php::string(std::move(buffer_));
@@ -126,9 +126,10 @@ namespace flame::udp {
         std::string data = params[0];
         socket_.async_send(boost::asio::buffer(data), ch[err]);
 
-        if (!err || err == boost::asio::error::operation_aborted) return nullptr;
+        if (!err || err == boost::asio::error::operation_aborted
+            || err == boost::asio::error::connection_refused) return nullptr;
         else throw php::exception(zend_ce_exception
-            , (boost::format("Failed to write TCP socket: %s") % err.message()).str()
+            , (boost::format("Failed to write UDP socket: %s") % err.message()).str()
             , err.value());
         return nullptr;
     }

From 9400596865a144a27ca1ed6ae8dab096103daf34 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 23 May 2019 18:20:12 +0800
Subject: [PATCH 122/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=20=E7=94=B1?=
 =?UTF-8?q?=E4=BA=8E=E6=9F=A5=E8=AF=A2=E9=A1=BA=E5=BA=8F=E6=B5=81=E7=A8=8B?=
 =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=AD=97=E7=AC=A6=E9=9B=86=E8=AE=BE=E7=BD=AE?=
 =?UTF-8?q?=E6=97=A0=E6=95=88=E9=97=AE=E9=A2=98;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mysql/_connection_pool.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/mysql/_connection_pool.cpp b/src/mysql/_connection_pool.cpp
index 27e3816..842f40a 100644
--- a/src/mysql/_connection_pool.cpp
+++ b/src/mysql/_connection_pool.cpp
@@ -99,11 +99,10 @@ namespace flame::mysql {
             // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
             // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
             if (!(flag_ & FLAG_REUSE_MASK)) query_version(c);
-            // 由于 reset 动作会导致字符集被重置为服务端字符集，确认字符集是否匹配
-            if (!(flag_ & FLAG_CHARSET_MASK)) query_charset(c);
-
             if (flag_ & FLAG_REUSE_BY_RESET) mysql_reset_connection(c);
             else if (flag_ & FLAG_REUSE_BY_CUSER) mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+            // 由于 reset 动作会导致字符集被重置为服务端字符集，确认字符集是否匹配
+            if (!(flag_ & FLAG_CHARSET_MASK)) query_charset(c);
             // else if (flag_ & FLAG_REUSE_BY_PROXY) ; // PROXY 已处理复用流程，此处无需处理
             if (flag_ & FLAG_CHARSET_DIFFER)
                 mysql_set_character_set(c, url_.query["charset"].c_str());

From 9d5d5d7d970cef0f9ea63c16da1e82fcae9295ab Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 23 May 2019 21:26:02 +0800
Subject: [PATCH 123/146] =?UTF-8?q?=E6=B8=85=E7=90=86=E4=BE=9D=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt | 11 +++-------
 src/url.cpp    | 56 ++++++++++++++------------------------------------
 src/vendor.h   |  1 -
 3 files changed, 18 insertions(+), 50 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 823ae97..e29f266 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,7 +36,6 @@ set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.0)
-set(VENDOR_HTTPPARSER /data/vendor/http-parser-2.9.0)
 set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.37.0)
 set(VENDOR_CURL /data/vendor/curl-7.64.1)
 set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1)
@@ -58,23 +57,19 @@ target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_BOOST}/include
     ${VENDOR_PHPEXT}/include
     ${VENDOR_PARSER}/include
-#    ${VENDOR_MYSQL}/include
     ${VENDOR_MARIA}/include
     ${VENDOR_HIREDIS}/include
     ${VENDOR_MONGODB}/include/libmongoc-1.0
     ${VENDOR_MONGODB}/include/libbson-1.0
     ${VENDOR_AMQP}/include
     ${VENDOR_RDKAFKA}/include
-    ${VENDOR_HTTPPARSER}/include
     ${VENDOR_NGHTTP2}/include
     ${VENDOR_CURL}/include
     ${VENDOR_OPENSSL}/include
 )
 # 链接库
 target_link_libraries(FLAME
-    ${VENDOR_HTTPPARSER}/lib/libhttp_parser.o
     ${VENDOR_PHPEXT}/lib/libphpext.a
-#    ${VENDOR_MYSQL}/lib/libmysqlclient.a
     ${VENDOR_MARIA}/lib/mariadb/libmariadbclient.a
     ${VENDOR_HIREDIS}/lib/libhiredis.a
     ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
@@ -91,7 +86,7 @@ target_link_libraries(FLAME
     cares
     ${VENDOR_OPENSSL}/lib/libssl.a
     ${VENDOR_OPENSSL}/lib/libcrypto.a
-    z
-    dl
-    rt
+#    z
+#    dl
+#    rt
 )
diff --git a/src/url.cpp b/src/url.cpp
index 1dbbf87..8a0d419 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -6,47 +6,21 @@ namespace flame {
 
     url::url(const php::string &str, bool parse_query)
     : raw_(str) {
-        const char* s = str.c_str();
-
-        struct http_parser_url x;
-        http_parser_parse_url(s, str.size(), 0, &x);
-        if (x.field_set & (1 << UF_SCHEMA)) {
-            auto y = x.field_data[UF_SCHEMA];
-            schema.assign(s + y.off, y.len);
-        }
-        if (x.field_set & (1 << UF_USERINFO)) {
-            auto y = x.field_data[UF_USERINFO];
-            int i = 0;
-            for(;i<y.len;++i) {
-                if (s[y.off + i] == ':') break;
-                else user.push_back(s[y.off + i]);
-            }
-            ++i;
-            for (;i<y.len;++i) {
-                pass.push_back(s[y.off + i]);
-            }
-        }
-        if (x.field_set & (1 << UF_HOST)) {
-            auto y = x.field_data[UF_HOST];
-            host.assign(s + y.off, y.len);
-        }
-        port = x.port;
-        if (x.field_set & (1 << UF_PATH)) {
-            auto y = x.field_data[UF_PATH];
-            path.assign(s + y.off, y.len);
-        }
-        if (x.field_set & (1 << UF_QUERY)) {
-            auto y = x.field_data[UF_QUERY];
-            if (parse_query) {
-                parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
-                    std::string key = et.first;
-                    std::string val = et.second;
-                    val.resize(php::url_decode_inplace(val.data(), val.size()));
-                    query[key] = val;
-                });
-                p.parse(s + y.off, y.len);
-                p.end();
-            }
+        auto u = php::parse_url(str);
+        schema.assign(u->scheme);
+        if(u->user) user.assign(u->user);
+        if(u->pass) pass.assign(u->pass);
+        if(u->host) host.assign(u->host);
+        port = u->port;
+        if(u->path) path.assign(u->path);
+        if(u->query && parse_query) {
+            parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
+                php::url_decode_inplace(et.second.data(), et.second.size());
+                query[et.first] = et.second;
+                std::cout << et.first << ": " << et.second << "\n";
+            });
+            p.parse(u->query, strlen(u->query));
+            p.end();
         }
     }
 
diff --git a/src/vendor.h b/src/vendor.h
index 76a6721..cdfeba8 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -19,7 +19,6 @@
 #include <optional>
 #include <unordered_set>
 #include <phpext/phpext.h>
-#include <http_parser.h>
 #include <hiredis/hiredis.h>
 #include <parser/separator_parser.hpp>
 #include <parser/multipart_parser.hpp>

From 44cfa58abde0f205b59e3985b8e736d1d2ae9d43 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 27 May 2019 16:09:23 +0800
Subject: [PATCH 124/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9ARedis=20?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=97=B6=E7=A9=BA=E5=93=8D=E5=BA=94=E5=AF=BC?=
 =?UTF-8?q?=E8=87=B4=E7=9A=84=E5=B4=A9=E6=BA=83=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/redis/_connection_pool.cpp | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/src/redis/_connection_pool.cpp b/src/redis/_connection_pool.cpp
index c5de726..3f051d6 100644
--- a/src/redis/_connection_pool.cpp
+++ b/src/redis/_connection_pool.cpp
@@ -93,39 +93,44 @@ namespace flame::redis {
 
     redisContext *_connection_pool::create(std::string& err) {
         struct timeval tv {5, 0};
-        redisContext* c = redisConnectWithTimeout(url_.host.c_str(), url_.port, tv);
+        std::unique_ptr<redisContext, void(*)(redisContext*)> c {
+            redisConnectWithTimeout(url_.host.c_str(), url_.port, tv), redisFree };
         if (!c) {
             err.assign("connection failed");
             return nullptr;
         }
         else if (c->err) {
             err.assign(c->errstr);
-            redisFree(c);
             return nullptr;
         }
         if (!url_.pass.empty()) { // 认证
-            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply*)redisCommand(c, "AUTH %s"
-                , url_.pass.c_str())
-                , (void (*)(redisReply*))freeReplyObject);
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp {
+                (redisReply*) redisCommand(c.get(), "AUTH %s", url_.pass.c_str()),
+                (void (*)(redisReply*)) freeReplyObject };
 
-            if (!rp || rp->type == REDIS_REPLY_ERROR) {
+            if (!rp) {
+                err.assign(c->errstr);
+                return nullptr;
+            }
+            if (rp->type == REDIS_REPLY_ERROR) {
                 err.assign(rp->str, rp->len);
-                redisFree(c);
                 return nullptr;
             }
         }
         if (url_.path.length() > 1) { // 指定数据库
-            std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "SELECT %d"
-                , std::atoi(url_.path.c_str() + 1))
-                , (void (*)(redisReply*))freeReplyObject);
-
-            if (!rp || rp->type == REDIS_REPLY_ERROR) {
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp {
+                (redisReply *) redisCommand(c.get(), "SELECT %d", std::atoi(url_.path.c_str() + 1)),
+                (void (*)(redisReply*)) freeReplyObject };
+            if (!rp) {
+                err.assign(c->errstr);
+                return nullptr;
+            }
+            if (rp->type == REDIS_REPLY_ERROR) {
                 err.assign(rp->str, rp->len);
-                redisFree(c);
                 return nullptr;
             }
         }
-        return c;
+        return c.release();
     }
 
     void _connection_pool::release(redisContext *c) {

From df9f8d216dd17ab5238a27d0801680a26a37ea7a Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 28 May 2019 13:33:55 +0800
Subject: [PATCH 125/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9Aclient=5Fpol?=
 =?UTF-8?q?l=20=E5=AF=B9=E8=B1=A1=E6=9C=AA=E8=83=BD=E6=AD=A3=E7=A1=AE?=
 =?UTF-8?q?=E9=87=8A=E6=94=BE=EF=BC=88=E6=94=B9=E7=94=A8=20ref=20=E5=BC=95?=
 =?UTF-8?q?=E7=94=A8=E8=AE=A1=E6=95=B0=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/http/client.cpp      | 20 +----------------
 src/http/client.h        |  3 ---
 src/http/client_poll.cpp | 47 ++++++++++++++++++++++++++++++----------
 src/http/client_poll.h   | 18 +++++++--------
 4 files changed, 46 insertions(+), 42 deletions(-)

diff --git a/src/http/client.cpp b/src/http/client.cpp
index c5b40f4..f9ce5fd 100644
--- a/src/http/client.cpp
+++ b/src/http/client.cpp
@@ -72,10 +72,7 @@ namespace flame::http {
             poll = client_poll::create_poll(gcontroller->context_x, fd, c_socket_ready_cb, self);
             curl_multi_assign(self->c_multi_, fd, poll);
         }
-        else {
-            // 
-        }
-        poll->async_wait(action);
+        poll->async_wait(action); // 自行 delete
         return 0;
     }
 
@@ -130,24 +127,9 @@ namespace flame::http {
         return nullptr;
     }
 
-    // curl_socket_t client::c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* addr) {
-    //     client* self = reinterpret_cast<client*>(data);
-    //     curl_socket_t fd = ::socket(addr->family, addr->socktype, addr->protocol);
-    //     return fd;
-    // }
-
-    // int client::c_socket_close_cb(void* data, curl_socket_t fd) {
-    //     client* self = reinterpret_cast<client*>(data);
-    //     return ::close(fd);
-    // }
-
     php::value client::exec_ex(const php::object& req) {
         auto req_ = static_cast<client_request*>(php::native(req));
         if (req_->c_easy_ == nullptr) req_->c_easy_ = curl_easy_init();
-        // curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETFUNCTION, c_socket_open_cb);
-        // curl_easy_setopt(req_->c_easy_, CURLOPT_OPENSOCKETDATA, this);
-        // curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETFUNCTION, c_socket_close_cb);
-        // curl_easy_setopt(req_->c_easy_, CURLOPT_CLOSESOCKETDATA, this);
         curl_easy_setopt(req_->c_easy_, CURLOPT_PIPEWAIT, 1);
         req_->build_ex();
 
diff --git a/src/http/client.h b/src/http/client.h
index 418360b..a1055b9 100644
--- a/src/http/client.h
+++ b/src/http/client.h
@@ -15,9 +15,6 @@ namespace flame::http {
         php::value post(php::parameters& params);
         php::value put(php::parameters& params);
         php::value delete_(php::parameters& params);
-
-        std::map<curl_socket_t, boost::asio::ip::tcp::socket*> c_socks_;
-
     private:
         CURLM *c_multi_ = nullptr;
         int    c_still_ = 0;
diff --git a/src/http/client_poll.cpp b/src/http/client_poll.cpp
index fa7b65e..4e879d2 100644
--- a/src/http/client_poll.cpp
+++ b/src/http/client_poll.cpp
@@ -3,7 +3,9 @@
 #include "client.h"
 
 namespace flame::http {
-    
+
+    std::map<curl_socket_t, client_poll*> client_poll::pool_;
+
     client_poll* client_poll::create_poll(boost::asio::io_context &io, curl_socket_t fd, client_poll::poll_callback_t cb, void* data) {
         int       type, r;
         socklen_t size = sizeof(type);
@@ -19,24 +21,33 @@ namespace flame::http {
         else if (type == SOCK_DGRAM) poll = new client_poll_udp(io, addr.sa_family == AF_INET6
             ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
         else return nullptr;
+
         poll->data = data;
-        poll->fd_ = fd;
-        poll->cb_ = cb;
+        poll->fd_  = fd;
+        poll->cb_  = cb;
         return poll;
     }
 
     client_poll::client_poll()
-    : action_(CURL_POLL_NONE) {
+    : action_(CURL_POLL_NONE)
+    , ref_(0) {
+        
     }
 
     client_poll::~client_poll() {
+        
     }
 
     void client_poll::on_ready(const boost::system::error_code& error, int action) {
+        --ref_;
+        if(ref_ == 0 && action_ == CURL_POLL_REMOVE) {
+            delete this;
+            return;   
+        }
         if (error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
+        
         cb_(error, this, fd_, action);
-        if (action_ == CURL_POLL_REMOVE) delete this;
-        else if (action_ == action) async_wait();
+        if (!error && action_ == action) async_wait();
     }
 
     void client_poll::async_wait(int action) {
@@ -48,35 +59,49 @@ namespace flame::http {
 
     client_poll_tcp::client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd)
     : sock_(io, proto, fd) {
+        
     }
 
     client_poll_tcp::~client_poll_tcp() {
-        // sock_.release();
+
     }
 
     void client_poll_tcp::async_wait() {
         sock_.cancel();
         // 重新监听
-        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) {
+            ++ref_;
             sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
-        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+        }
+        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) {
+            ++ref_;
             sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
+        }
+        if (action_ == CURL_POLL_REMOVE && ref_ == 0) {
+            delete this;
+        }
     }
     
     client_poll_udp::client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd)
     : client_poll(), sock_(io, proto, fd) {
+
     }
 
     client_poll_udp::~client_poll_udp() {
+        
     }
 
     void client_poll_udp::async_wait() {
         sock_.cancel();
         // 重新监听
-        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) 
+        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) {
+            ++ref_;
             sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
-        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) 
+        }
+        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) {
+            ++ref_;
             sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
+        }
     }
 
 }
\ No newline at end of file
diff --git a/src/http/client_poll.h b/src/http/client_poll.h
index 39b81e0..7223cc9 100644
--- a/src/http/client_poll.h
+++ b/src/http/client_poll.h
@@ -9,30 +9,30 @@ namespace flame::http {
         typedef void (*poll_callback_t)(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action);
         virtual ~client_poll();
         static client_poll* create_poll(boost::asio::io_context& io, curl_socket_t fd, poll_callback_t cb, void* data = nullptr);
+        static void destory_poll(client_poll* poll);
         virtual void async_wait() = 0;
         void async_wait(int action);
         void* data;
     protected:
-        curl_socket_t   fd_;
-        int         action_;
-        poll_callback_t cb_;
+        static std::map<curl_socket_t, client_poll*> pool_;
+
+        curl_socket_t    fd_;
+        int          action_;
+        poll_callback_t  cb_;
+        int             ref_;
         void on_ready(const boost::system::error_code& error, int action);
     };
 
-    class client_poll_tcp:public client_poll {
+    class client_poll_tcp: public client_poll {
     public:
-        // client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
         client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
-        // client_poll_tcp(curl_socket_t fd);
         ~client_poll_tcp();
         void async_wait() override;
     private:
         boost::asio::ip::tcp::socket sock_;
-        // boost::asio::ip::tcp::socket& sock_;
-        // boost::asio::posix::stream_descriptor sock_;
     };
 
-    class client_poll_udp:public client_poll {
+    class client_poll_udp: public client_poll {
     public:
         client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd);
         ~client_poll_udp();

From 9358ac81edd89e8f1ff1b015026e652275356621 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 10 Jun 2019 13:10:19 +0800
Subject: [PATCH 126/146] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20SIGUSR1=20?=
 =?UTF-8?q?=E6=A0=87=E8=AE=B0=20HTTP=20=E6=9C=8D=E5=8A=A1=E7=9F=AD?=
 =?UTF-8?q?=E8=BF=9E=20Connection:=20close=20=E5=A4=B4=E4=BF=A1=E6=81=AF;?=
 =?UTF-8?q?=20SIGRTMIN=20+=201=20=E8=BF=9B=E8=A1=8C=E8=BF=9B=E7=A8=8B?=
 =?UTF-8?q?=E9=87=8D=E8=BD=BD;=20=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt            |  23 ++++-----
 LICENCE                   |  22 ++++++++
 README.md                 | 105 ++++++++++++++++++--------------------
 src/controller.h          |   1 +
 src/controller_master.cpp |  23 +++++++--
 src/controller_master.h   |   2 +-
 src/controller_worker.cpp |   8 ++-
 src/http/_handler.cpp     |   4 +-
 src/vendor.h              |   2 +-
 9 files changed, 113 insertions(+), 77 deletions(-)
 create mode 100644 LICENCE

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e29f266..bfbf20c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,23 +22,22 @@ add_library(FLAME SHARED ${SOURCES})
 # -----------------------------------------------------------------------------
 # 依赖项目录
 # -----------------------------------------------------------------------------
-set(VENDOR_PHP /data/vendor/php-7.2.17)
+set(VENDOR_PARSER /data/vendor/parser-1.0.0)
+set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
+set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
+set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1c)
+set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
+set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.1)
+set(VENDOR_BOOST /data/vendor/boost-1.70.0)
+set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.0)
+set(VENDOR_CURL /data/vendor/curl-7.65.1)
+set(VENDOR_MARIA /data/vendor/mariac-3.0.10)
+set(VENDOR_PHP /data/vendor/php-7.2.19)
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
     OUTPUT_STRIP_TRAILING_WHITESPACE)
 set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
-set(VENDOR_PARSER /data/vendor/parser-1.0.0)
-set(VENDOR_BOOST /data/vendor/boost-1.69.0)
-# set(VENDOR_MYSQL /data/vendor/mysqlc-8.0.15)
-set(VENDOR_MARIA /data/vendor/mariac-3.0.9)
-set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
-set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
-set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
-set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.0)
-set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.37.0)
-set(VENDOR_CURL /data/vendor/curl-7.64.1)
-set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1)
 # -----------------------------------------------------------------------------
 # 编译选项
 # -----------------------------------------------------------------------------
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..714672f
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2017 TerryWu
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/README.md b/README.md
index 053e039..eafc9d7 100644
--- a/README.md
+++ b/README.md
@@ -109,38 +109,35 @@ flame\run();
 <details><summary>依赖项编译安装，仅供参考</summary>
 <p>
 
-#### openssl
-``` Bash
-CC=gcc CXX=g++ ./Configure no-shared --prefix=/data/vendor/openssl-1.1.1 linux-x86_64
-make && make install
-```
-
-#### boost
+#### cpp-parser
 ``` Bash
-./bootstrap.sh --prefix=/data/vendor/boost-1.70.0
-./b2 --prefix=/data/vendor/boost-1.70.0 cxxflags="-fPIC" variant=release link=static threading=multi install
+make install
 ```
 
-#### PHP
+#### hiredis
 ``` Bash
-CC=gcc CXX=g++ ./configure --prefix=/data/vendor/php-7.2.17 --with-config-file-path=/data/vendor/php-7.2.17/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --build=x86_64-linux-gnu
+CC=gcc make
+PREFIX=/data/vendor/hiredis-0.14.0 make install
+# rm /data/vendor/hiredis-0.14.0/lib/*.so*
 ```
 
-#### libphpext
+#### AMQP-CPP
 ``` Bash
-CXXFLAGS="-O2" make
-make install
+mkdir stage && cd stage
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.4 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
+make && make install
 ```
 
-#### cpp-parser
+#### openssl
 ``` Bash
-make install
+CC=gcc CXX=g++ ./Configure no-shared --prefix=/data/vendor/openssl-1.1.1c linux-x86_64
+make && make install
 ```
 
 <!--
 #### sasl2
 ``` Bash
-PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/sasl2 --with-openssl=/data/vendor/openssl-1.1.1 --without-ldap --enable-shared=no
+PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/sasl2 --with-openssl=/data/vendor/openssl-1.1.1c --without-ldap --enable-shared=no
 make && make install
 ```
 -->
@@ -148,72 +145,70 @@ make && make install
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
 make && make install
 # rm /data/vendor/mongoc-1.14.0/lib/*.so*
 ```
 
-#### AMQP-CPP
+#### rdkafka
 ``` Bash
-mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.4 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
+CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.0.1 --disable-sasl
 make && make install
+# rm /data/vendor/rdkafka-1.0.1/lib/*.so*
+cp src/snappy.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
+cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
+cp src/xxhash.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
 ```
 
-#### rdkafka
+#### boost
 ``` Bash
-CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.0.0 --disable-sasl
-# rm /data/vendor/rdkafka-1.0.0/lib/*.so*
-cp src/snappy.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
-cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
-cp src/xxhash.h /data/vendor/rdkafka-1.0.0/include/librdkafka/
+./bootstrap.sh --prefix=/data/vendor/boost-1.70.0
+./b2 --prefix=/data/vendor/boost-1.70.0 cxxflags="-fPIC" variant=release link=static threading=multi install
 ```
 
-#### http-parser
+#### nghttp2
 ``` Bash
-mkdir -p /data/vendor/http-parser-2.9.0/lib
-mkdir -p /data/vendor/http-parser-2.9.0/include
-CC=gcc CFLAGS=-fPIC make libhttp_parser.o
-cp libhttp_parser.o /data/vendor/http-parser-2.9.0/lib
-cp http_parser.h /data/vendor/http-parser-2.9.0/include
+mkdir stage && cd stage
+CC=gcc CXX=g++ CFLAGS="-fPIC" CXXFLAGS="-fPIC" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/nghttp2-1.39.0 -DENABLE_LIB_ONLY=ON -DENABLE_SHARED_LIB=OFF -DENABLE_STATIC_LIB=ON -DCMAKE_INSTALL_LIBDIR:PATH=lib ../
+make && make install
 ```
 
-#### hiredis
+#### curl
 ``` Bash
-CC=gcc make
-PREFIX=/data/vendor/hiredis-0.14.0 make install
-# rm /data/vendor/hiredis-0.14.0/lib/*.so*
+CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --prefix=/data/vendor/curl-7.65.1 --with-nghttp2=/data/vendor/nghttp2-1.39.0 --with-ssl=/data/vendor/openssl-1.1.1c --with-pic=pic --enable-ipv6 --enable-shared=no --without-brotli --without-libidn2 --disable-ldap --without-libpsl --without-lber --enable-ares
+make && make install
 ```
 
-#### nghttp2
-``` Bash
-PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/nghttp2-1.37.0 --enable-shared=no --enable-lib-only --with-boost=/data/vendor/boost-1.70.0 --enable-asio-lib
+#### maria-connector-c
+```
+mkdir stage && cd stage
+CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig:/data/vendor/curl-7.65.1/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.0.10 ../
 make && make install
+# rm /data/vendor/mariac-3.0.10/lib/mariadb/*.so*
 ```
 
-#### curl
+#### PHP
 ``` Bash
-CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --prefix=/data/vendor/curl-7.64.1 --with-nghttp2=/data/vendor/nghttp2-1.37.0 --with-ssl=/data/vendor/openssl-1.1.1 --with-pic=pic --enable-ipv6 --enable-shared=no --without-libidn2 --disable-ldap --without-libpsl --without-lber --enable-ares
+CC=gcc CXX=g++ ./configure --prefix=/data/vendor/php-7.2.19 --with-config-file-path=/data/vendor/php-7.2.19/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --build=x86_64-linux-gnu
 make && make install
 ```
 
 <!--
-#### mysql-connector-c
-``` Bash
-mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/mysqlc-8.0.15 -DWITHOUT_SERVER=ON -DWITH_BOOST=../boost/boost_1_68_0 -DWITH_SSL=/data/vendor/openssl-1.1.1 -DOPENSSL_EXECUTABLE=/data/vendor/openssl-1.1.1/bin/openssl -DWITH_CURL=/data/vendor/curl-7.64.1 -DWITH_SASL=OFF -DSASL_SYSTEM_LIBRARY=OFF -DCMAKE_C_FLAGS=-pthread -DCMAKE_CXX_FLAGS=-pthread ../
-make && make install
-# rm /data/vendor/mysqlc-8.0.15/lib/*.so*
-```
-注意：需要下载内含 boost 头文件的版本（与框架依赖版本不同）；
+# external openssl extension for php
+CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" ./configure --with-php-config=/data/vendor/php-7.2.19/bin/php-config --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
+# external curl extension for php
+# modify config.m4 and remove some checkings
+CC=gcc CXX=g++ LDFLAGS="-L/data/vendor/nghttp2-1.37.0/lib -L/data/vendor/openssl-1.1.1c/lib" ./configure --with-php-config=/data/vendor/php-7.2.19/bin/php-config --with-curl=/data/vendor/curl-7.65.1 --build=x86_64-linux-gnu
 -->
 
-#### maria-connector-c
-```
-mkdir stage && cd stage
-CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1/lib/pkgconfig:/data/vendor/curl-7.64.1/lib/pkgconfig cmake3 -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC  -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.0.9 ../
-# rm /data/vendor/mariac-3.0.9/lib/mariadb/*.so*
+#### libphpext
+``` Bash
+CXXFLAGS="-O2" make
+make install
 ```
 
 </p>
 </details>
+
+### 版权声明
+本软件使用 MIT 许可协议；
diff --git a/src/controller.h b/src/controller.h
index ceef3f8..c54ae4a 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -21,6 +21,7 @@ namespace flame {
             STATUS_SHUTDOWN    = 0x02,
             STATUS_EXCEPTION   = 0x04,
             STATUS_RUN         = 0x08,
+            STATUS_CLOSECONN   = 0x10,
         };
         int status;
         std::size_t    worker_size;
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
index 73b4430..658685d 100644
--- a/src/controller_master.cpp
+++ b/src/controller_master.cpp
@@ -1,5 +1,6 @@
 #include "controller_master.h"
 #include "controller.h"
+#include "time/time.h"
 
 PHPAPI extern char *php_ini_opened_path;
 
@@ -50,8 +51,8 @@ namespace flame {
         });
     }
 
-    void controller_master_worker::close() {
-        ::kill(proc_.id(), SIGTERM);
+    void controller_master_worker::close(int sig) {
+        ::kill(proc_.id(), sig);
     }
 
     void controller_master_worker::close_now() {
@@ -205,13 +206,24 @@ namespace flame {
              // 主进程与子进程 signal 处理之间有干扰
             await_signal();
             boost::asio::post(gcontroller->context_x, [this, sig]() {
-                if (sig == SIGUSR1) {
+                if (sig == SIGRTMIN+1) { // 进程重载(主进程保持,逐步重启工作进程)
                     if (close_ != CLOSE_NOTHING) return;
                     close_ = CLOSE_FLAG_RESTART | CLOSE_WORKER_SIGNALING;
                     worker_close_.swap(worker_spawn_); // 准备关闭的进程
                     close_worker_next(); // 关闭现有进程
                 }
-                else if (sig == SIGUSR2) reload_output();
+                else if (sig == SIGUSR2) reload_output(); // 日志重载
+                else if (sig == SIGUSR1) { // 长短连切换
+                    if(gcontroller->status & controller::controller_status::STATUS_CLOSECONN) {
+                        gcontroller->status &= ~controller::controller_status::STATUS_CLOSECONN;
+                        std::cout << "[" << time::iso() << "] [INFO] remove 'Connection: close' header." << std::endl;
+                    }
+                    else {
+                        gcontroller->status |= controller::controller_status::STATUS_CLOSECONN;
+                        std::cout << "[" << time::iso() << "] [INFO] append 'Connection: close' header." << std::endl;
+                    }
+                    for(auto i=worker_spawn_.begin();i!=worker_spawn_.end(); ++i) (*i)->close(SIGUSR1);
+                }
                 else {
                     if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) {
                         close_ = CLOSE_FLAG_MASTER | CLOSE_WORKER_TERMINATE;
@@ -234,8 +246,9 @@ namespace flame {
         for(int i=0;i<gcontroller->worker_size;++i) spawn_worker(i);
         // 2. 监听信号进行日志重载或停止
         signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
-        signal_->add(SIGUSR1); // 进程重载
+        signal_->add(SIGUSR1); // HTTP / 长短连接切换
         signal_->add(SIGUSR2); // 日志重载
+        signal_->add(SIGRTMIN+1); // 进程重载
         await_signal();
         // 3. 启动运行
         thread_ = std::thread([this] {
diff --git a/src/controller_master.h b/src/controller_master.h
index 31648c8..51ef134 100644
--- a/src/controller_master.h
+++ b/src/controller_master.h
@@ -7,7 +7,7 @@ namespace flame {
     class controller_master_worker {
     public:
         controller_master_worker(controller_master* m, int i);
-        void close();
+        void close(int sig = SIGTERM);
         void close_now();
     private:
         controller_master*            m_;
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
index 067e646..7f06207 100644
--- a/src/controller_worker.cpp
+++ b/src/controller_worker.cpp
@@ -19,13 +19,19 @@ namespace flame {
             if (gcontroller->worker_size == 0) signal_.reset(); // 单进程模式
             else await_signal(); // 多进程模式的工作进程需要持续监听
             
-            if(sig == SIGTERM) { // 工作进程仅处理退出信号
+            if(sig == SIGTERM || sig == SIGINT) { // 工作进程仅处理退出信号
                 boost::asio::post(gcontroller->context_x, [this] () {
                     gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
                     auto ft = gcontroller->cbmap->equal_range("quit"); // 需要在协程调用退出回调
                     for (auto i = ft.first; i != ft.second; ++i) coroutine::start(i->second);
                 });
             }
+            else if(sig == SIGUSR1) {
+                if(gcontroller->status & controller::controller_status::STATUS_CLOSECONN) 
+                    gcontroller->status &= ~controller::controller_status::STATUS_CLOSECONN;
+                else
+                    gcontroller->status |= controller::controller_status::STATUS_CLOSECONN;
+            }
         });
     }
 
diff --git a/src/http/_handler.cpp b/src/http/_handler.cpp
index c646f5c..ddae9cf 100644
--- a/src/http/_handler.cpp
+++ b/src/http/_handler.cpp
@@ -90,7 +90,7 @@ namespace flame::http {
                     //     res_ptr->set("body", nullptr);
                     // }
                     // 服务及将停止或服务器以进行关闭后，禁止连接复用
-                    if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_) {
+                    if (gcontroller->status & controller::controller_status::STATUS_CLOSECONN || svr_ptr->closed_) {
                         res_->keep_alive(false);
                         res_->set(boost::beast::http::field::connection, "close");
                     }
@@ -136,7 +136,7 @@ namespace flame::http {
         res_->chunked(true);
         res_->set(boost::beast::http::field::transfer_encoding, "chunked");
         // 服务及将停止或服务器以进行关闭后，禁止连接复用
-        if (gcontroller->status & controller::controller_status::STATUS_SHUTDOWN || svr_ptr->closed_) {
+        if (gcontroller->status & controller::controller_status::STATUS_CLOSECONN || svr_ptr->closed_) {
             res_->keep_alive(false);
             res_->set(boost::beast::http::field::connection, "close");
         }
diff --git a/src/vendor.h b/src/vendor.h
index cdfeba8..d37d830 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.13.1"
+#define EXTENSION_VERSION "0.13.2"
 
 #include <sys/types.h>
 #include <sys/socket.h>

From f8670e3f81f784d467e8887c43558f10598dbf4d Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Mon, 10 Jun 2019 15:39:43 +0800
Subject: [PATCH 127/146] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20curl=5Furl=20?=
 =?UTF-8?q?=E4=BB=A3=E6=9B=BF=20php::parse=5Furl=20=E4=BB=A5=E6=9C=9F?=
 =?UTF-8?q?=E5=87=8F=E5=B0=91=20PHP=20=E9=80=82=E9=85=8D=E5=B7=A5=E4=BD=9C?=
 =?UTF-8?q?=E9=87=8F=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/url.cpp | 39 ++++++++++++++++++++++++++++++---------
 1 file changed, 30 insertions(+), 9 deletions(-)

diff --git a/src/url.cpp b/src/url.cpp
index 8a0d419..ba08d60 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -6,21 +6,42 @@ namespace flame {
 
     url::url(const php::string &str, bool parse_query)
     : raw_(str) {
-        auto u = php::parse_url(str);
-        schema.assign(u->scheme);
-        if(u->user) user.assign(u->user);
-        if(u->pass) pass.assign(u->pass);
-        if(u->host) host.assign(u->host);
-        port = u->port;
-        if(u->path) path.assign(u->path);
-        if(u->query && parse_query) {
+        auto u = curl_url();
+        curl_url_set(u, CURLUPART_URL, str.c_str(), CURLU_NON_SUPPORT_SCHEME);
+        char * tmp;
+        if(curl_url_get(u, CURLUPART_SCHEME, &tmp, 0) == CURLUE_OK) { // SCHEME
+            schema.assign(tmp);
+            curl_free(tmp);
+        }
+        if(curl_url_get(u, CURLUPART_USER, &tmp, 0) == CURLUE_OK) { // USER
+            user.assign(tmp);
+            curl_free(tmp);
+        }
+        if(curl_url_get(u, CURLUPART_PASSWORD, &tmp, 0) == CURLUE_OK) { // PASS
+            pass.assign(tmp);
+            curl_free(tmp);
+        }
+        if(curl_url_get(u, CURLUPART_HOST, &tmp, 0) == CURLUE_OK) { // USER
+            host.assign(tmp);
+            curl_free(tmp);
+        }
+        if(curl_url_get(u, CURLUPART_PORT, &tmp, 0) == CURLUE_OK) { // PORT
+            port = std::atoi(tmp);
+            curl_free(tmp);
+        }
+        if(curl_url_get(u, CURLUPART_PATH, &tmp, 0) == CURLUE_OK) { // PATH
+            path.assign(tmp);
+            curl_free(tmp);
+        }
+        if(parse_query && curl_url_get(u, CURLUPART_QUERY, &tmp, 0) == CURLUE_OK) { // QUERY
             parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
                 php::url_decode_inplace(et.second.data(), et.second.size());
                 query[et.first] = et.second;
                 std::cout << et.first << ": " << et.second << "\n";
             });
-            p.parse(u->query, strlen(u->query));
+            p.parse(tmp, strlen(tmp));
             p.end();
+            curl_free(tmp);
         }
     }
 

From 816b43027d88d76aa1ec7d330f544174d6e7ff63 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 11 Jun 2019 12:19:35 +0800
Subject: [PATCH 128/146] =?UTF-8?q?=E9=9D=99=E6=80=81=E9=93=BE=E6=8E=A5=20?=
 =?UTF-8?q?c-ares=20=E5=BA=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt  | 29 +++++++++++++-------------
 README.md       | 55 +++++++++++++++++++++++++++----------------------
 test/http_1.php | 10 ++++-----
 test/udp_1.php  |  8 +++++++
 4 files changed, 58 insertions(+), 44 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index bfbf20c..1db776b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,22 +22,25 @@ add_library(FLAME SHARED ${SOURCES})
 # -----------------------------------------------------------------------------
 # 依赖项目录
 # -----------------------------------------------------------------------------
+set(VENDOR_BOOST /data/vendor/boost-1.70.0)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
 set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1c)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.1)
-set(VENDOR_BOOST /data/vendor/boost-1.70.0)
+set(VENDOR_PHP /data/vendor/php-7.2.19)
+set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
+set(VENDOR_CARES /data/vendor/cares-1.15.0)
 set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.0)
 set(VENDOR_CURL /data/vendor/curl-7.65.1)
 set(VENDOR_MARIA /data/vendor/mariac-3.0.10)
-set(VENDOR_PHP /data/vendor/php-7.2.19)
+
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
     OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
     OUTPUT_STRIP_TRAILING_WHITESPACE)
-set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
+
 # -----------------------------------------------------------------------------
 # 编译选项
 # -----------------------------------------------------------------------------
@@ -52,25 +55,25 @@ set_target_properties(FLAME PROPERTIES
     OUTPUT_NAME "flame")
 # 包含路径
 target_include_directories(FLAME SYSTEM PRIVATE
-    ${VENDOR_PHP_INCLUDES}
-    ${VENDOR_BOOST}/include
-    ${VENDOR_PHPEXT}/include
     ${VENDOR_PARSER}/include
-    ${VENDOR_MARIA}/include
     ${VENDOR_HIREDIS}/include
-    ${VENDOR_MONGODB}/include/libmongoc-1.0
-    ${VENDOR_MONGODB}/include/libbson-1.0
     ${VENDOR_AMQP}/include
     ${VENDOR_RDKAFKA}/include
+    ${VENDOR_MONGODB}/include/libmongoc-1.0
+    ${VENDOR_MONGODB}/include/libbson-1.0
+    ${VENDOR_MARIA}/include 
     ${VENDOR_NGHTTP2}/include
     ${VENDOR_CURL}/include
+    ${VENDOR_PHPEXT}/include
+    ${VENDOR_PHP_INCLUDES}
     ${VENDOR_OPENSSL}/include
+    ${VENDOR_BOOST}/include
 )
 # 链接库
 target_link_libraries(FLAME
+    ${VENDOR_HIREDIS}/lib/libhiredis.a
     ${VENDOR_PHPEXT}/lib/libphpext.a
     ${VENDOR_MARIA}/lib/mariadb/libmariadbclient.a
-    ${VENDOR_HIREDIS}/lib/libhiredis.a
     ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
     ${VENDOR_MONGODB}/lib/libbson-static-1.0.a
     ${VENDOR_AMQP}/lib/libamqpcpp.a
@@ -82,10 +85,8 @@ target_link_libraries(FLAME
     ${VENDOR_BOOST}/lib/libboost_filesystem.a
     ${VENDOR_CURL}/lib/libcurl.a
     ${VENDOR_NGHTTP2}/lib/libnghttp2.a
-    cares
+    ${VENDOR_CARES}/lib/libcares.a
     ${VENDOR_OPENSSL}/lib/libssl.a
     ${VENDOR_OPENSSL}/lib/libcrypto.a
-#    z
-#    dl
-#    rt
+    z
 )
diff --git a/README.md b/README.md
index eafc9d7..dec1f21 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,12 @@ flame\run();
 <details><summary>依赖项编译安装，仅供参考</summary>
 <p>
 
+#### boost
+``` Bash
+./bootstrap.sh --prefix=/data/vendor/boost-1.70.0
+./b2 --prefix=/data/vendor/boost-1.70.0 cxxflags="-fPIC" variant=release link=static threading=multi install
+```
+
 #### cpp-parser
 ``` Bash
 make install
@@ -160,22 +166,41 @@ cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
 cp src/xxhash.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
 ```
 
-#### boost
+#### PHP
 ``` Bash
-./bootstrap.sh --prefix=/data/vendor/boost-1.70.0
-./b2 --prefix=/data/vendor/boost-1.70.0 cxxflags="-fPIC" variant=release link=static threading=multi install
+CC=gcc CXX=g++ CFLAGS="-pthread" ./configure --prefix=/data/vendor/php-7.2.19 --with-config-file-path=/data/vendor/php-7.2.19/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
+make && make install
+```
+
+<!--
+# external openssl extension for php
+CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" ./configure --with-php-config=/data/vendor/php-7.2.19/bin/php-config --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
+-->
+
+#### libphpext
+``` Bash
+CXXFLAGS="-O2" make
+make install
+```
+
+#### c-ares
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ cmake -DCARES_SHARED=OFF -DCARES_STATIC=ON -DCARES_STATIC_PIC=ON -DCMAKE_INSTALL_PREFIX=/data/vendor/cares-1.15.0 -DCMAKE_BUILD_TYPE=Release ../
+make && make install
 ```
 
 #### nghttp2
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ CFLAGS="-fPIC" CXXFLAGS="-fPIC" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/nghttp2-1.39.0 -DENABLE_LIB_ONLY=ON -DENABLE_SHARED_LIB=OFF -DENABLE_STATIC_LIB=ON -DCMAKE_INSTALL_LIBDIR:PATH=lib ../
+CC=gcc CXX=g++ CFLAGS="-fPIC" CXXFLAGS="-fPIC" PKG_CONFIG_PATH="/data/vendor/cares-1.15.0/lib/pkgconfig:/data/vendor/openssl-1.1.1c/lib/pkgconfig" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/nghttp2-1.39.0 -DENABLE_LIB_ONLY=ON -DENABLE_SHARED_LIB=OFF -DENABLE_STATIC_LIB=ON -DCMAKE_INSTALL_LIBDIR:PATH=lib ../
 make && make install
 ```
 
 #### curl
 ``` Bash
-CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --prefix=/data/vendor/curl-7.65.1 --with-nghttp2=/data/vendor/nghttp2-1.39.0 --with-ssl=/data/vendor/openssl-1.1.1c --with-pic=pic --enable-ipv6 --enable-shared=no --without-brotli --without-libidn2 --disable-ldap --without-libpsl --without-lber --enable-ares
+mkdir stage && cd stage
+CC=gcc CXX=g++ CFLAGS="-pthread -fPIC" CXXFLAGS="-pthread -fPIC" PKG_CONFIG_PATH="/data/vendor/cares-1.15.0/lib/pkgconfig:/data/vendor/nghttp2-1.39.0:/data/vendor/openssl-1.1.1c/lib/pkgconfig" cmake -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/curl-7.65.1 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_USE_LIBSSH2=OFF -DCURL_DISABLE_IMAP=ON -DCURL_DISABLE_GOPHER=ON -DCURL_DISABLE_POP3=ON -DCURL_DISABLE_RTSP=ON -DCURL_DISABLE_SMTP=ON -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_LDAPS=ON -DENABLE_ARES=ON -DCARES_LIBRARY=/data/vendor/cares-1.15.0/lib/libcares.a -DUSE_NGHTTP2=ON -DNGHTTP2_LIBRARY=/data/vendor/nghttp2-1.39.0/lib/libnghttp2.a -DNGHTTP2_INCLUDE_DIR=/data/vendor/nghttp2-1.39.0/include ../
 make && make install
 ```
 
@@ -187,26 +212,6 @@ make && make install
 # rm /data/vendor/mariac-3.0.10/lib/mariadb/*.so*
 ```
 
-#### PHP
-``` Bash
-CC=gcc CXX=g++ ./configure --prefix=/data/vendor/php-7.2.19 --with-config-file-path=/data/vendor/php-7.2.19/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --build=x86_64-linux-gnu
-make && make install
-```
-
-<!--
-# external openssl extension for php
-CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" ./configure --with-php-config=/data/vendor/php-7.2.19/bin/php-config --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
-# external curl extension for php
-# modify config.m4 and remove some checkings
-CC=gcc CXX=g++ LDFLAGS="-L/data/vendor/nghttp2-1.37.0/lib -L/data/vendor/openssl-1.1.1c/lib" ./configure --with-php-config=/data/vendor/php-7.2.19/bin/php-config --with-curl=/data/vendor/curl-7.65.1 --build=x86_64-linux-gnu
--->
-
-#### libphpext
-``` Bash
-CXXFLAGS="-O2" make
-make install
-```
-
 </p>
 </details>
 
diff --git a/test/http_1.php b/test/http_1.php
index 00644d2..443bba3 100644
--- a/test/http_1.php
+++ b/test/http_1.php
@@ -4,12 +4,12 @@
 for($i=0;$i<10;++$i) {
     flame\go(function() {
         flame\http\get("http://www.baidu.com/", 8000);
-        flame\http\get("http://www.baidu.com/", 8000);
+        flame\http\get("https://www.baidu.com/", 8000);
         flame\http\get("https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/static/protocol/https/home/img/qrcode/zbios_efde696.png", 3000);
-        var_dump( flame\http\put("http://127.0.0.1:8687/", "this is the data for put request", 800000) );
-        var_dump( flame\http\get("http://127.0.0.1:8687/") );
-        // $req = new flame\http\client_request('https://www.baidu.com/');
-        $req = new flame\http\client_request('http://127.0.0.1:8687/');
+        // var_dump( flame\http\put("http://127.0.0.1:8687/", "this is the data for put request", 800000) );
+        // var_dump( flame\http\get("http://127.0.0.1:8687/") );
+        $req = new flame\http\client_request('https://www.baidu.com/');
+        // $req = new flame\http\client_request('http://127.0.0.1:8687/');
         $req->method = "POST";
         $req->header["test1"] = "string";
         $req->header["test2"] = 123456;
diff --git a/test/udp_1.php b/test/udp_1.php
index 9ba02b5..e32a0db 100644
--- a/test/udp_1.php
+++ b/test/udp_1.php
@@ -23,4 +23,12 @@
     });
 });
 
+flame\go(function() {
+    $socket = flame\udp\connect("127.0.0.1:6666");
+    for($i=0;$i<10;++$i) {
+        $socket->send("hello");
+        flame\time\sleep(100);
+    }
+});
+
 flame\run();

From 735eeb228fef47ee36f33b1254bc480fe2ba3c78 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 11 Jun 2019 15:39:53 +0800
Subject: [PATCH 129/146] =?UTF-8?q?=E9=99=8D=E7=BA=A7=20curl=207.65.0=20?=
 =?UTF-8?q?=E6=94=BE=E7=BD=AE=20nghttp2/cares=20=E5=86=85=E5=87=BA?=
 =?UTF-8?q?=E7=8E=B0=E6=AE=B5=E9=94=99=E8=AF=AF;=20(=E7=AD=89=E5=BE=85=20h?=
 =?UTF-8?q?ttps://github.com/curl/curl/pull/3997=20=E5=90=88=E5=B9=B6?=
 =?UTF-8?q?=E5=8F=91=E5=B8=83=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt | 7 ++++++-
 README.md      | 8 +++++---
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1db776b..55030cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,7 +33,7 @@ set(VENDOR_PHP /data/vendor/php-7.2.19)
 set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
 set(VENDOR_CARES /data/vendor/cares-1.15.0)
 set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.0)
-set(VENDOR_CURL /data/vendor/curl-7.65.1)
+set(VENDOR_CURL /data/vendor/curl-7.65.0)
 set(VENDOR_MARIA /data/vendor/mariac-3.0.10)
 
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
@@ -88,5 +88,10 @@ target_link_libraries(FLAME
     ${VENDOR_CARES}/lib/libcares.a
     ${VENDOR_OPENSSL}/lib/libssl.a
     ${VENDOR_OPENSSL}/lib/libcrypto.a
+    resolv
     z
+    dl
+    m
+    nsl
+    rt
 )
diff --git a/README.md b/README.md
index dec1f21..9144eb4 100644
--- a/README.md
+++ b/README.md
@@ -200,10 +200,12 @@ make && make install
 #### curl
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ CFLAGS="-pthread -fPIC" CXXFLAGS="-pthread -fPIC" PKG_CONFIG_PATH="/data/vendor/cares-1.15.0/lib/pkgconfig:/data/vendor/nghttp2-1.39.0:/data/vendor/openssl-1.1.1c/lib/pkgconfig" cmake -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/curl-7.65.1 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_USE_LIBSSH2=OFF -DCURL_DISABLE_IMAP=ON -DCURL_DISABLE_GOPHER=ON -DCURL_DISABLE_POP3=ON -DCURL_DISABLE_RTSP=ON -DCURL_DISABLE_SMTP=ON -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_LDAPS=ON -DENABLE_ARES=ON -DCARES_LIBRARY=/data/vendor/cares-1.15.0/lib/libcares.a -DUSE_NGHTTP2=ON -DNGHTTP2_LIBRARY=/data/vendor/nghttp2-1.39.0/lib/libnghttp2.a -DNGHTTP2_INCLUDE_DIR=/data/vendor/nghttp2-1.39.0/include ../
-make && make install
+# quote: cmake for curl is poorly maintained
+# segfault @7.65.1 
+CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --with-ssl=/data/vendor/openssl-1.1.1c --enable-ares=/data/vendor/cares-1.15.0 --with-nghttp2=/data/vendor/nghttp2-1.39.0 --enable-shared=no --enable-static --enable-ipv6 --without-brotli --without-libidn2 --without-libidn --without-librtmp --disable-unix-sockets --disable-ftp --disable-ldap --disable-ldaps --disable-rtsp --disable-dict --disable-file --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --without-libpsl --prefix=/data/vendor/curl-7.65.0
+make && make
 ```
-
+make i
 #### maria-connector-c
 ```
 mkdir stage && cd stage

From 724bcb73fe4be3c5a61f9b0a604f8cd9ea6f9598 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 12 Jun 2019 11:07:48 +0800
Subject: [PATCH 130/146] nghttp2 -> v1.39.1

---
 CMakeLists.txt |  2 +-
 README.md      | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 55030cf..0f310ea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,7 +32,7 @@ set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.1)
 set(VENDOR_PHP /data/vendor/php-7.2.19)
 set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
 set(VENDOR_CARES /data/vendor/cares-1.15.0)
-set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.0)
+set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.1)
 set(VENDOR_CURL /data/vendor/curl-7.65.0)
 set(VENDOR_MARIA /data/vendor/mariac-3.0.10)
 
diff --git a/README.md b/README.md
index 9144eb4..a42586e 100644
--- a/README.md
+++ b/README.md
@@ -193,7 +193,7 @@ make && make install
 #### nghttp2
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ CFLAGS="-fPIC" CXXFLAGS="-fPIC" PKG_CONFIG_PATH="/data/vendor/cares-1.15.0/lib/pkgconfig:/data/vendor/openssl-1.1.1c/lib/pkgconfig" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/nghttp2-1.39.0 -DENABLE_LIB_ONLY=ON -DENABLE_SHARED_LIB=OFF -DENABLE_STATIC_LIB=ON -DCMAKE_INSTALL_LIBDIR:PATH=lib ../
+CC=gcc CXX=g++ CFLAGS="-fPIC" CXXFLAGS="-fPIC" PKG_CONFIG_PATH="/data/vendor/cares-1.15.0/lib/pkgconfig:/data/vendor/openssl-1.1.1c/lib/pkgconfig" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/nghttp2-1.39.1 -DENABLE_LIB_ONLY=ON -DENABLE_SHARED_LIB=OFF -DENABLE_STATIC_LIB=ON -DCMAKE_INSTALL_LIBDIR:PATH=lib ../
 make && make install
 ```
 
@@ -202,14 +202,14 @@ make && make install
 mkdir stage && cd stage
 # quote: cmake for curl is poorly maintained
 # segfault @7.65.1 
-CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --with-ssl=/data/vendor/openssl-1.1.1c --enable-ares=/data/vendor/cares-1.15.0 --with-nghttp2=/data/vendor/nghttp2-1.39.0 --enable-shared=no --enable-static --enable-ipv6 --without-brotli --without-libidn2 --without-libidn --without-librtmp --disable-unix-sockets --disable-ftp --disable-ldap --disable-ldaps --disable-rtsp --disable-dict --disable-file --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --without-libpsl --prefix=/data/vendor/curl-7.65.0
-make && make
+CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --with-ssl=/data/vendor/openssl-1.1.1c --enable-ares=/data/vendor/cares-1.15.0 --with-nghttp2=/data/vendor/nghttp2-1.39.1 --enable-shared=no --enable-static --enable-ipv6 --without-brotli --without-libidn2 --without-libidn --without-librtmp --disable-unix-sockets --disable-ftp --disable-ldap --disable-ldaps --disable-rtsp --disable-dict --disable-file --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --without-libpsl --prefix=/data/vendor/curl-7.65.0
+make && make install
 ```
-make i
+
 #### maria-connector-c
 ```
 mkdir stage && cd stage
-CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig:/data/vendor/curl-7.65.1/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.0.10 ../
+CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig:/data/vendor/curl-7.65.0/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.0.10 ../
 make && make install
 # rm /data/vendor/mariac-3.0.10/lib/mariadb/*.so*
 ```

From cfd8a16c6eb83a4fbfd82f2fcefe7c90fd2b92df Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 12 Jun 2019 12:24:08 +0800
Subject: [PATCH 131/146] =?UTF-8?q?UDP=20Poll=20=E6=9C=AA=E9=87=8A?=
 =?UTF-8?q?=E6=94=BE=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                | 2 +-
 src/http/client_poll.cpp | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index a42586e..ad9b308 100644
--- a/README.md
+++ b/README.md
@@ -207,7 +207,7 @@ make && make install
 ```
 
 #### maria-connector-c
-```
+``` Bash
 mkdir stage && cd stage
 CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig:/data/vendor/curl-7.65.0/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.0.10 ../
 make && make install
diff --git a/src/http/client_poll.cpp b/src/http/client_poll.cpp
index 4e879d2..4a43ed2 100644
--- a/src/http/client_poll.cpp
+++ b/src/http/client_poll.cpp
@@ -77,9 +77,7 @@ namespace flame::http {
             ++ref_;
             sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
         }
-        if (action_ == CURL_POLL_REMOVE && ref_ == 0) {
-            delete this;
-        }
+        if (action_ == CURL_POLL_REMOVE && ref_ == 0) delete this;
     }
     
     client_poll_udp::client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd)
@@ -102,6 +100,7 @@ namespace flame::http {
             ++ref_;
             sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
         }
+        if (action_ == CURL_POLL_REMOVE && ref_ == 0) delete this;
     }
 
 }
\ No newline at end of file

From b90a4cc68cd2b226018bb11ff1527a76cea5ab55 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 13 Jun 2019 15:48:21 +0800
Subject: [PATCH 132/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php     | 32 ++++++++++++++++++++------------
 doc/http.php     | 23 ++++++++++++++---------
 doc/kafka.php    |  6 +++---
 doc/log.php      |  1 +
 doc/mongodb.php  |  2 +-
 doc/mysql.php    |  5 +++--
 doc/rabbitmq.php |  5 +++--
 doc/redis.php    |  2 +-
 doc/tcp.php      |  1 +
 doc/time.php     |  2 +-
 doc/udp.php      |  6 ++++++
 11 files changed, 54 insertions(+), 31 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index 299ce40..49995ef 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -1,15 +1,30 @@
 <?php
 /**
  * 基本框架流程函数, 用于框架初始化, 启动协程等;
+ * 框架可在两种模式下工作：
+ * 
+ * 1. 单进程模式：
+ *  * 一次信号 SIGINT / SIGTERM 将 “通知” 进程退出（等待超时），第二次或超时后立即停止；
+ *  * 信号 SIGUSR1 将切换 HTTP 服务器长短连状态 `Connection: close`；
+ *  * 单进程模式**不支持**日志文件输出，固定输出到 STDERR / STDOUT (取决于错误等级）；
+ * 
+ * 2. 多进程模式：
+ *  * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式；
+ *  * 主进程将自动进行日志文件写入，子进程自动拉起；
+ *  * 端口监听使用 SO_REUSEPORT 在多个工作进程间共享，但不排除外部监听的可能；
+ *  * 向主进程发送 SIGINT / SIGTERM 一次将 ”通知“ 子进程退出（等待超时），第二次或超时后将立即停止；
+ *  * 向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
+ *  * 子进程继承主进程的 环境变量 / -c /path/to/php.ini 配置（通过命令行 -d XXX=XXX 的临时设置无效）；
+ *  * 向主进程发送 SIGUSR1 信号将切换 HTTP 服务器长短连状态 `Connection: close`；
+ *  * 向主进程发送 SIGRTMIN+1 将进行进程重载（陆续停止当前进程并启动新进程）
  */
 namespace flame;
 /**
  * 初始化框架, 设置进程名称及相关配置;
  * @param string $process_name 进程名称
  * @param array  $options 选项配置, 目前可用如下:
- *  * "logger" - 日志输出重定向目标文件 (完整路径, 若不提供使用标准输出);
- *      向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
- *  * "level" - 日志输出级别, 设置该级别下的日志将不被记录; 可用级别如下
+ *  * "logger" - 日志输出重定向目标文件 (完整路径, 若不提供使用标准输出，单进程模式无效);
+ *  * "level" - 日志输出级别, 设置该级别下的日志将不被输出记录; 可用级别如下
  *      "debug"
  *      "info"
  *      "warning"
@@ -17,12 +32,6 @@
  *      "fatal"
  *  * "timeout" - 多进程退出超时, 单位毫秒 ( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
  *  @see flame\log
- *
- * 多进程模式：
- *  使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式；
- *  主进程将自动进行日志文件写入，子进程自动拉起；
- *  子进程继承主进程的环境变量/ -c /path/to/php.ini 配置（通过命令行 -d XXX=XXX 的临时设置无效）；
- *  向主进程发送 SIGUSR1 信号将进行进程重载；（陆续停止当前进程并启动新进程）
  */
 function init($process_name, $options = []) {}
 /**
@@ -57,10 +66,9 @@ function select(queue $q1, $q2/*, ...*/):queue {
 /**
  * 监听框架的通知
  * @param string $event 目前消息存在以下两种:
- *  * "exception" - 当协程发生未捕获异常, 执行对应的回调，并记录错误信息（随后进程会退出）;
- *  * "quit" - 退出消息, 一般用于平滑停止各种服务;
+ *  * "exception" - 当协程发生未捕获异常, 执行对应的回调，并记录错误信息（随后进程会退出），用户可在此回调进行错误报告或报警;
+ *  * "quit" - 退出消息, 用户可在此回调停止各种服务，如停止 HTTP 服务器 / 关闭 MySQL 连接等;
  * @param callable 回调函数
- * 一般在 exception 中进行错误报告或报警，在 quit 中进行退出清理工作；
  */
 function on(string $event, callable $cb) {}
 /**
diff --git a/doc/http.php b/doc/http.php
index 84ff188..ba470bf 100644
--- a/doc/http.php
+++ b/doc/http.php
@@ -2,7 +2,8 @@
 /**
  * 提供基本的 HTTP(S) 客户端、服务端封装；服务端仅支持 HTTP/1.1 协议; 客户端支持 (TLS) HTTP/1.x HTTP/2 协议;
  * 注意：
- * * 默认状态客户端对请求连接进行 KeepAlive 复用；若需要使用短连接，请自行制定 Connection: close 头；
+ * * 默认状态，服务器对客户端请求连接进行 KeepAlive 复用（长连状态）；若需要使用短连接，请自行制定 Connection: close 头（短连状态）；
+ * * 当进程接收到 SIGUSR1 信号时，切换连接复用状态（短连状态附加 Connection: close 头信息）；
  */
 namespace flame\http;
 
@@ -44,7 +45,7 @@ class client {
     /**
      * 构建新的客户端对象
      * @param array $options 选项, 目前支持的选项如下:
-     *  * "connection_per_host" - 单个 HOST 目标连接限制, 默认 16;
+     *  * "connection_per_host" - 单个 HOST 目标连接限制, 默认 16，超出限制时须等待;
      */
     function __construct(array $options = []) {}
     /**
@@ -296,12 +297,12 @@ function close() {}
  */
 class server_request {
     /**
-     * 请求方法
+     * 请求方法（大写）
      * @var string
      */
     public $method = "GET";
     /**
-     * 请求路径
+     * 请求路径（不包含查询参数）
      * @var string
      */
     public $path = "/";
@@ -311,9 +312,10 @@ class server_request {
      */
     public $query;
     /**
-     * 请求头; 所有头信息字段名被转换为**小写**形式;
-     * 注意：暂不支持多项同名 Header 字段;
+     * 请求头;
      * @var array
+     * 注意：所有头信息字段名被转换为**小写**形式;
+     * 注意：由于目前使用数组形式，暂不支持同名字段；
      */
     public $header;
     /**
@@ -355,7 +357,7 @@ class server_request {
     public $file;
     /**
      * 用户可设置的请求关联数据;
-     * 例如: 在 before 回调中从 Cookie 中提取用户信息放在 $req->data["user"] 中, 后续处理器即可访问 $req->data["user"] 获取户信息;
+     * @example 在 before 回调中从 Cookie 中提取用户信息放在 $req->data["user"] 中, 后续处理器即可访问 $req->data["user"] 获取户信息;
      * @var array
      */
     public $data;
@@ -363,6 +365,8 @@ class server_request {
 
 /**
  * 服务端响应对象（由 server 对应处理回调获得）提供接口向客户端返回数据
+ * 默认状态下 HTTP/1.1 使用 长连接（保持连接）；可使用 Connection: close 头禁止；
+ * 向进程发送 SIGUSR1 信号时将会在 长短连 状态切换（短连接状态自动附加 Connection: close 头信息）；
  */
 class server_response {
     /**
@@ -373,6 +377,7 @@ class server_response {
     /**
      * 响应头信息, Key/Val 形式
      * @var array
+     * 注意：一般情况下 Key/Val 均为字符串，其他类型会被转换为字符串输出；
      */
     public $header;
     // public $cookie;
@@ -381,7 +386,7 @@ class server_response {
      * @var mixed 任意数据, 实际响应会序列化此数据; 以下几种 Content-Type 存在内置的序列化:
      *  * "application/json" - 使用 json_encode 进行序列化;
      *  * "application/x-www-form-urlencoded" - 使用 http_build_query 进行序列化;
-     * 其他类型强制转换为文本类型后响应;
+     * 其他类型强制转换为文本类型后返回响应;
      */
     public $body;
     /**
@@ -408,7 +413,7 @@ function end(string $chunk = null) {}
     /**
      * 响应一个文件的内容; 一般需要自行设置对应文件的 Content-Type 以保证浏览器能正常渲染;
      * 下述参数中的 $path 将会被正常化处理, 以保证其不超出 $root 指定的根目录范围;
-     * 注意: 此功能一般仅用于调试或少量文件访问使用, 大量生产环境请考虑使用 nginx 代为处理静态文件;
+     * 注意: 此功能一般仅用于调试或少量文件访问使用, 大量生产环境请考虑使用 nginx 等代为处理静态文件;
      */
     function file(string $root, string $path) {}
 }
diff --git a/doc/kafka.php b/doc/kafka.php
index 3e3efee..6c8fb3d 100644
--- a/doc/kafka.php
+++ b/doc/kafka.php
@@ -5,10 +5,10 @@
 namespace flame\kafka;
 /**
  * @param array $config 基本 Kafka 配置, 以下两个选项必要:
- *  * "bootstrap.servers" - string 服务器地址(9092);
+ *  * "bootstrap.servers" - string 服务器地址(一般为多个端口为 9092 的服务器地址，逗号分隔，例如："127.0.0.1:9092, 127.0.0.2:9092");
  *  * "group.id" - string 消费组名称(同一消费组共享消费进度);
  * 可以使用一个自定义的的参数:
- *  * "concurrent" - 控制并行消费协程数量, 默认为 8 (数值类型)，上限为 256;
+ *  * "concurrent" - 控制并行消费协程数量（即一个消费者消费到数据作为生产过程交由协程消费）, 默认为 8 (数值类型)，上限为 256;
  * @see 其他可配置选项均为字符串类型(非字符串会被强制转换), 请参考:
  *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
  * @param array $topics 待消费的数据源 TOPIC 名称列表
@@ -19,7 +19,7 @@ function consume(array $config, array $topics):consumer {
 
 /**
  * @param array $config 基本 Kafka 配置, 一下两个选项必要:
- *  * "bootstrap.servers" - 服务器地址(9092);
+ *  * "bootstrap.servers" - 服务器地址(一般为多个端口为 9092 的服务器地址，逗号分隔，例如："127.0.0.1:9092, 127.0.0.2:9092");
  *  * "group.id" - 消费组名称(同一消费组共享消费进度);
  * @see 其他可配置选项请参考:
  *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
diff --git a/doc/log.php b/doc/log.php
index 9ce84a4..6a8e41b 100644
--- a/doc/log.php
+++ b/doc/log.php
@@ -3,6 +3,7 @@
 /**
  * 日志模块, 可通过框架配置设置日志等级以过滤日志输出;
  * @see flame\init()
+ * 注意：单进程模式不支持设置输出目标文件，固定输出到 STDOUT / STDERR (由错误等级决定)
  */
 namespace flame\log;
 /**
diff --git a/doc/mongodb.php b/doc/mongodb.php
index 63e6a90..801694f 100644
--- a/doc/mongodb.php
+++ b/doc/mongodb.php
@@ -5,7 +5,7 @@
  * 1. 同一协程中连续进行 MongoDB 查询操作, 且游标不读取数据不销毁 (或 不读取完不销毁) 可能导致进程死锁; (请将游标读取完毕 或 主动 unset 释放游标对象)
  * 2. 对应 MongoDB ObjectID 映射类型 class flame\mongodb\object_id;
  * 3. 对应 MongoDB DateTime 映射类型 class flame\mongodb\date_time;
- * 4. 单客户端内连接池上限大小为 6（单进程）；
+ * 4. 单客户端内连接池上限大小为 6（单进程），即并行访问超过 6 时须排队等待；
  * 5. 由于 PHP 数组混合了关联数组机制，空查询条件时可考虑使用 new StdClass() 空对象代替空数组，以明确区分 JSON Object/Array 两种形式；
  *    针对 find 相关操作框架内部**对查询条件**已进行了上述**适配**；其他情况的空数组将按照 BSON_ARRAY 转换；
  */
diff --git a/doc/mysql.php b/doc/mysql.php
index deffb20..024f61b 100644
--- a/doc/mysql.php
+++ b/doc/mysql.php
@@ -5,7 +5,7 @@
  * 1. 同一协程中连续进行 MySQL 查询操作, 且结果集数据不读取且不销毁(或不读取完且不销毁)可能导致进程死锁; (请将结果集读取完 或 主动 unset 释放结果集对象)
  * 2. MySQL 读取数据时数值型字段读取映射为 PHP 对应数值类型；`DATETIME` 映射为 PHP 内置类型 `DateTime`;
  * 3. 客户端提供的简化方法如 `insert()` `update()` 也同时支持与上述反向的映射;
- * 4. 单客户端内连接池上限大小为 6（单进程）；
+ * 4. 单客户端内连接池上限大小为 6（单进程），即在并行访问超过 6 时须排队等待；
  */
 namespace flame\mysql;
 /**
@@ -17,7 +17,8 @@
  *      * "auth" => 认证方式，默认 "mysql_native_password" 可用 "caching_sha2_password"（MySQL 8.0 服务器默认） / "sha256_parssword"
  *      * "proxy" => 当前地址是否是代理服务（已提供连接复用机制） "1" - 是，已提供连接复用机制 (禁用框架提供的复用重置机制)， "0" - 否，未提供（默认）
  * 注意：若服务端配置默认字符集与上述字符集不同，且未指定 proxy=1 参数，那么：
- *      每次连接使用（复用）均会进行字符集设置，这里可能会产生部分额外消耗；
+ *      每次连接使用（复用）连接重置，字符集被恢复为服务器默认，框架将再次进行字符集设置（这里可能会产生少量额外消耗）；
+ * 注意：若指定了 proxy 参数框架不再进行任何”连接复用“的清理工作（CONNECTION_RESET / CHANGE_USER / CHARSET_RESET)；
  * @return client 客户端对象
  */
 function connect($url): client {
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
index 3a8b5cf..3b8bc2e 100644
--- a/doc/rabbitmq.php
+++ b/doc/rabbitmq.php
@@ -9,7 +9,8 @@
  * @param string $url 连接地址, 形如:
  *  amqp://{username}:{password}@{host}:{port}/{vhost}?key1=val1
  * 可使用参数:
- *  * `prefetch` - int - 用于指定未 ack 时最大读取数量
+ *  * `prefetch` - int - 用于指定未 ack 时最大读取数量，将启动半数的消息处理协程；
+ * 注意：消费者在消费过程中发生网络错误时，会中断消费过程 `run()` 并抛出异常；
  */
 function consume(string $url, string $queue): consumer {
     return new consumer();
@@ -29,7 +30,7 @@ class consumer {
      * 开始消费, 框架将按照一定数量启动并行消费协程, 并使用消息对象调用 $cb 回调函数;
      * @param callable $cb 消费回调, 形如:
      *  function callback($message) {}
-     * 当消费过程失败，将抛出异常（结束消费）
+     * 当消费过程失败（网络连接异常、断开等），将抛出异常（结束消费）
      */
     function run(callable $cb) {}
     /**
diff --git a/doc/redis.php b/doc/redis.php
index cc00cac..28a17dc 100644
--- a/doc/redis.php
+++ b/doc/redis.php
@@ -3,7 +3,7 @@
  * 提供 Redis 基本客户端封装，内部使用连接池：
  * 注意：
  * 1. 暂未实现 SUBSCRIBE 相关命令；
- * 2. 单客户端内连接池大小上限为 6 （单进程）；
+ * 2. 单客户端内连接池大小上限为 6 （单进程），即访问并行很大时若 6 个连接都在使用中，须排队等待；
  */
 namespace flame\redis;
 
diff --git a/doc/tcp.php b/doc/tcp.php
index 1220db6..d8502d0 100644
--- a/doc/tcp.php
+++ b/doc/tcp.php
@@ -41,6 +41,7 @@ function write(string $data) {}
      */
     function close() {}
 }
+
 class server {
     /**
      * 创建一个服务器, 绑定在指定的地址上, 准备服务
diff --git a/doc/time.php b/doc/time.php
index aae7f27..770697c 100644
--- a/doc/time.php
+++ b/doc/time.php
@@ -5,7 +5,7 @@
 namespace flame\time;
 
 /**
- * 当前协程休眠 $ms 毫秒
+ * 当前协程休眠 $ms 毫秒（调度式休眠，实际 CPU 可调度执行其他协程）
  */
 function sleep(int $ms) {}
 /**
diff --git a/doc/udp.php b/doc/udp.php
index e4972c8..9837b52 100644
--- a/doc/udp.php
+++ b/doc/udp.php
@@ -8,11 +8,17 @@
  * 连接指定地址, 并返回 socket 对象
  * @param string $address 连接地址, 例如: "127.0.0.1:8687"
  * 注意: UDP "链接" 能在一定程度上提高效率;
+ * 注意：非连接型可直接使用构造函数创建；
  */
 function connect(string $address):socket {
     return new socket();
 }
 
+/**
+ * 非连接型 socket 可直接调用构造创建：
+ * @example
+ *    $socket = new flame\udp\socket();
+ */
 class socket {
     /**
      * 构建 socket 可选的进行地址绑定

From aa9089a31e00093117889b5b8ea1cadbb566b6d2 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 19 Jun 2019 11:17:14 +0800
Subject: [PATCH 133/146] =?UTF-8?q?=E4=BF=AE=E6=AD=A3:=E7=94=B1=E4=BA=8E?=
 =?UTF-8?q?=E6=9C=AA=E6=8C=87=E5=AE=9A=E5=88=9D=E5=80=BC=E5=A4=A7=E6=A6=82?=
 =?UTF-8?q?=E7=8E=87=E5=AF=BC=E8=87=B4=E7=AB=AF=E5=8F=A3=E9=BB=98=E8=AE=A4?=
 =?UTF-8?q?=E6=9C=BA=E5=88=B6=E5=A4=B1=E6=95=88;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/url.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/url.cpp b/src/url.cpp
index ba08d60..6482f92 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -5,7 +5,8 @@ namespace flame {
     typedef ::parser::separator_parser<std::string, std::string> parser_t;
 
     url::url(const php::string &str, bool parse_query)
-    : raw_(str) {
+    : raw_(str)
+    , port(0) { // 原始类型 port 初始值可能随机
         auto u = curl_url();
         curl_url_set(u, CURLUPART_URL, str.c_str(), CURLU_NON_SUPPORT_SCHEME);
         char * tmp;

From c9866ac4b8cf1a63dc994e1764ff10f942dd3432 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 21 Jun 2019 12:17:37 +0800
Subject: [PATCH 134/146] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E4=BE=9D=E8=B5=96=20?=
 =?UTF-8?q?AMQP-CPP=20->=204.1.5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt | 2 +-
 README.md      | 2 +-
 src/flame.cpp  | 2 +-
 src/vendor.h   | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0f310ea..ef15fd4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,7 +25,7 @@ add_library(FLAME SHARED ${SOURCES})
 set(VENDOR_BOOST /data/vendor/boost-1.70.0)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
-set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.4)
+set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.5)
 set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1c)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.1)
diff --git a/README.md b/README.md
index ad9b308..a5177ab 100644
--- a/README.md
+++ b/README.md
@@ -130,7 +130,7 @@ PREFIX=/data/vendor/hiredis-0.14.0 make install
 #### AMQP-CPP
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.4 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
+CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.5 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
 make && make install
 ```
 
diff --git a/src/flame.cpp b/src/flame.cpp
index 7a9bc94..fc03aff 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -115,7 +115,7 @@ extern "C" {
             .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
             // .desc({"vendor/mysqlc", mysql_get_client_info()})
             .desc({"vendor/mariac", MARIADB_PACKAGE_VERSION})
-            .desc({"vendor/amqpcpp", "4.1.4"})
+            .desc({"vendor/amqpcpp", "4.1.5"})
             .desc({"vendor/rdkafka", rd_kafka_version_str()})
             .desc({"vendor/mongoc", MONGOC_VERSION_S})
             .desc({"vendor/nghttp2", NGHTTP2_VERSION})
diff --git a/src/vendor.h b/src/vendor.h
index d37d830..b72a925 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.13.2"
+#define EXTENSION_VERSION "0.13.3"
 
 #include <sys/types.h>
 #include <sys/socket.h>

From 19fc80d3642a141fa236e514de9909a1057ddddf Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 21 Jun 2019 16:31:02 +0800
Subject: [PATCH 135/146] =?UTF-8?q?=E5=88=86=E6=95=A3=E4=BE=9D=E8=B5=96?=
 =?UTF-8?q?=E5=8A=A0=E8=BD=BD=EF=BC=8C=E4=BB=A5=E6=9C=9F=E6=8F=90=E9=AB=98?=
 =?UTF-8?q?=E7=BC=96=E8=AF=91=E9=80=9F=E5=BA=A6=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/compress/compress.cpp      |  3 +++
 src/flame.cpp                  |  4 ++++
 src/hash/hash.cpp              |  6 ++++-
 src/http/_handler.h            |  1 +
 src/http/client.h              |  1 +
 src/http/client_body.h         |  1 +
 src/http/client_poll.h         |  1 +
 src/http/client_request.h      |  1 +
 src/http/client_response.h     |  1 +
 src/http/http.h                |  5 ++++
 src/http/server.h              |  1 +
 src/http/server_request.h      |  1 +
 src/http/server_response.cpp   |  2 +-
 src/http/server_response.h     |  1 +
 src/http/value_body.h          |  1 +
 src/kafka/_consumer.h          |  1 +
 src/kafka/_producer.h          |  1 +
 src/kafka/consumer.h           |  1 +
 src/kafka/kafka.h              |  1 +
 src/kafka/message.h            |  1 +
 src/kafka/producer.h           |  1 +
 src/mongodb/_connection_base.h |  1 +
 src/mongodb/_connection_lock.h |  1 +
 src/mongodb/_connection_pool.h |  1 +
 src/mongodb/client.h           |  1 +
 src/mongodb/collection.h       |  1 +
 src/mongodb/cursor.h           |  1 +
 src/mongodb/date_time.h        |  1 +
 src/mongodb/mongodb.h          |  1 +
 src/mongodb/object_id.h        |  1 +
 src/mysql/_connection_base.h   |  1 +
 src/mysql/_connection_lock.h   |  1 +
 src/mysql/client.h             |  1 +
 src/mysql/mysql.h              |  1 +
 src/mysql/result.h             |  1 +
 src/mysql/tx.h                 |  1 +
 src/os/os.cpp                  |  7 ++++++
 src/rabbitmq/_client.h         |  1 +
 src/rabbitmq/consumer.h        |  1 +
 src/rabbitmq/message.h         |  1 +
 src/rabbitmq/producer.h        |  1 +
 src/rabbitmq/rabbitmq.h        |  2 ++
 src/redis/_connection_base.h   |  1 +
 src/redis/_connection_lock.h   |  1 +
 src/redis/_connection_pool.h   |  1 +
 src/redis/client.h             |  1 +
 src/redis/redis.h              |  1 +
 src/redis/tx.h                 |  1 +
 src/url.cpp                    |  1 +
 src/vendor.h                   | 42 +++++++---------------------------
 50 files changed, 77 insertions(+), 36 deletions(-)

diff --git a/src/compress/compress.cpp b/src/compress/compress.cpp
index 66a895d..5407e38 100644
--- a/src/compress/compress.cpp
+++ b/src/compress/compress.cpp
@@ -1,4 +1,7 @@
 #include "compress.h"
+extern "C" {
+#include <librdkafka/snappy.h>
+}
 
 namespace flame::compress {
     
diff --git a/src/flame.cpp b/src/flame.cpp
index fc03aff..14c4cf6 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -18,6 +18,9 @@
 #include "hash/hash.h"
 #include "encoding/encoding.h"
 #include "compress/compress.h"
+#include "toml/toml.h"
+#include <boost/version.hpp>
+#include <nghttp2/nghttp2ver.h>
 
 namespace flame {
     static php::value init(php::parameters& params) {
@@ -154,6 +157,7 @@ extern "C" {
             flame::hash::declare(ext);
             flame::encoding::declare(ext);
             flame::compress::declare(ext);
+            flame::toml::declare(ext);
         }
         else {
             ext
diff --git a/src/hash/hash.cpp b/src/hash/hash.cpp
index 55ba653..b90ca2f 100644
--- a/src/hash/hash.cpp
+++ b/src/hash/hash.cpp
@@ -1,5 +1,9 @@
 #include "hash.h"
-
+#include <boost/crc.hpp>
+extern "C" {
+    #include <librdkafka/rdmurmur2.h>
+    #include <librdkafka/xxhash.h>
+}
 
 namespace flame::hash {
     
diff --git a/src/http/_handler.h b/src/http/_handler.h
index 6011ea5..5ca184e 100644
--- a/src/http/_handler.h
+++ b/src/http/_handler.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "http.h"
 
 namespace flame::http {
     class server_response;
diff --git a/src/http/client.h b/src/http/client.h
index a1055b9..8cf0638 100644
--- a/src/http/client.h
+++ b/src/http/client.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "http.h"
 
 namespace flame::http {
     
diff --git a/src/http/client_body.h b/src/http/client_body.h
index efab79c..04f8dfd 100644
--- a/src/http/client_body.h
+++ b/src/http/client_body.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "http.h"
 
 namespace flame::http {
     
diff --git a/src/http/client_poll.h b/src/http/client_poll.h
index 7223cc9..d8e285d 100644
--- a/src/http/client_poll.h
+++ b/src/http/client_poll.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "http.h"
 
 namespace flame::http {
     
diff --git a/src/http/client_request.h b/src/http/client_request.h
index da314af..3314779 100644
--- a/src/http/client_request.h
+++ b/src/http/client_request.h
@@ -2,6 +2,7 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 #include "../url.h"
+#include "http.h"
 
 namespace flame::http {
     
diff --git a/src/http/client_response.h b/src/http/client_response.h
index 6e962b6..bc9ffed 100644
--- a/src/http/client_response.h
+++ b/src/http/client_response.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "http.h"
 
 namespace flame::http {
     
diff --git a/src/http/http.h b/src/http/http.h
index eaaf58b..5b52c82 100644
--- a/src/http/http.h
+++ b/src/http/http.h
@@ -1,5 +1,10 @@
 #pragma once
 #include "../vendor.h"
+#define BOOST_BEAST_USE_STD_STRING_VIEW
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <curl/curl.h>
 
 namespace flame::http {
     
diff --git a/src/http/server.h b/src/http/server.h
index 14a7dce..1b0f4eb 100644
--- a/src/http/server.h
+++ b/src/http/server.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "http.h"
 
 namespace flame::http {
     class acceptor;
diff --git a/src/http/server_request.h b/src/http/server_request.h
index 454ca22..7b0d241 100644
--- a/src/http/server_request.h
+++ b/src/http/server_request.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "http.h"
 
 namespace flame::http {
     
diff --git a/src/http/server_response.cpp b/src/http/server_response.cpp
index ea72d35..1ab1e0f 100644
--- a/src/http/server_response.cpp
+++ b/src/http/server_response.cpp
@@ -167,7 +167,7 @@ namespace flame::http {
         }
         status_ |= STATUS_BODY_END | STATUS_BODY_SENT;
 
-        boost::filesystem::path root, file;
+        std::filesystem::path root, file;
         root += params[0].to_string();
         file += params[1].to_string();
 
diff --git a/src/http/server_response.h b/src/http/server_response.h
index 9c7c069..7d11754 100644
--- a/src/http/server_response.h
+++ b/src/http/server_response.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "http.h"
 #include "value_body.h"
 
 namespace flame::http {
diff --git a/src/http/value_body.h b/src/http/value_body.h
index 9f193b7..0f76ac1 100644
--- a/src/http/value_body.h
+++ b/src/http/value_body.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "http.h"
 
 namespace flame::http {
 
diff --git a/src/kafka/_consumer.h b/src/kafka/_consumer.h
index d04f7b9..10120b2 100644
--- a/src/kafka/_consumer.h
+++ b/src/kafka/_consumer.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "kafka.h"
 
 namespace flame {
     template <class T>
diff --git a/src/kafka/_producer.h b/src/kafka/_producer.h
index ed331db..ff37842 100644
--- a/src/kafka/_producer.h
+++ b/src/kafka/_producer.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "kafka.h"
 
 namespace flame::kafka {
     // 生产者
diff --git a/src/kafka/consumer.h b/src/kafka/consumer.h
index 7665830..f9abf06 100644
--- a/src/kafka/consumer.h
+++ b/src/kafka/consumer.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "kafka.h"
 
 namespace flame::kafka {
 
diff --git a/src/kafka/kafka.h b/src/kafka/kafka.h
index f6ab8b2..2142766 100644
--- a/src/kafka/kafka.h
+++ b/src/kafka/kafka.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include <librdkafka/rdkafka.h>
 
 namespace flame::kafka {
     void declare(php::extension_entry &ext);
diff --git a/src/kafka/message.h b/src/kafka/message.h
index 5f82506..33055c3 100644
--- a/src/kafka/message.h
+++ b/src/kafka/message.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "kafka.h"
 
 namespace flame::kafka {
     
diff --git a/src/kafka/producer.h b/src/kafka/producer.h
index 90a21c6..c0d5d49 100644
--- a/src/kafka/producer.h
+++ b/src/kafka/producer.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "kafka.h"
 
 namespace flame::kafka {
     
diff --git a/src/mongodb/_connection_base.h b/src/mongodb/_connection_base.h
index eeefed1..8f3eb75 100644
--- a/src/mongodb/_connection_base.h
+++ b/src/mongodb/_connection_base.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "mongodb.h"
 
 namespace flame::mongodb {
 
diff --git a/src/mongodb/_connection_lock.h b/src/mongodb/_connection_lock.h
index bc48651..b50bb42 100644
--- a/src/mongodb/_connection_lock.h
+++ b/src/mongodb/_connection_lock.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mongodb.h"
 #include "_connection_base.h"
 
 namespace flame::mongodb {
diff --git a/src/mongodb/_connection_pool.h b/src/mongodb/_connection_pool.h
index c33ba9f..3a6521b 100644
--- a/src/mongodb/_connection_pool.h
+++ b/src/mongodb/_connection_pool.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mongodb.h"
 #include "_connection_base.h"
 
 namespace flame::mongodb {
diff --git a/src/mongodb/client.h b/src/mongodb/client.h
index 1be09ca..eebfe89 100644
--- a/src/mongodb/client.h
+++ b/src/mongodb/client.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mongodb.h"
 
 namespace flame::mongodb {
 
diff --git a/src/mongodb/collection.h b/src/mongodb/collection.h
index a5dd2a4..154990c 100644
--- a/src/mongodb/collection.h
+++ b/src/mongodb/collection.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mongodb.h"
 
 namespace flame::mongodb {
 
diff --git a/src/mongodb/cursor.h b/src/mongodb/cursor.h
index 2538636..b54706e 100644
--- a/src/mongodb/cursor.h
+++ b/src/mongodb/cursor.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mongodb.h"
 
 namespace flame::mongodb {
     class _connection_lock;
diff --git a/src/mongodb/date_time.h b/src/mongodb/date_time.h
index fa0f0f9..ac3e74b 100644
--- a/src/mongodb/date_time.h
+++ b/src/mongodb/date_time.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mongodb.h"
 
 namespace flame::mongodb {
     
diff --git a/src/mongodb/mongodb.h b/src/mongodb/mongodb.h
index 8cd2bac..d286c64 100644
--- a/src/mongodb/mongodb.h
+++ b/src/mongodb/mongodb.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include <mongoc.h>
 
 namespace flame::mongodb {
     void declare(php::extension_entry &ext);
diff --git a/src/mongodb/object_id.h b/src/mongodb/object_id.h
index dddf6fb..2adf0ae 100644
--- a/src/mongodb/object_id.h
+++ b/src/mongodb/object_id.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mongodb.h"
 
 namespace flame::mongodb {
     
diff --git a/src/mysql/_connection_base.h b/src/mysql/_connection_base.h
index b608287..3a8362f 100644
--- a/src/mysql/_connection_base.h
+++ b/src/mysql/_connection_base.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "mysql.h"
 
 namespace flame::mysql {
 
diff --git a/src/mysql/_connection_lock.h b/src/mysql/_connection_lock.h
index 6af7ac6..7c3d6b9 100644
--- a/src/mysql/_connection_lock.h
+++ b/src/mysql/_connection_lock.h
@@ -2,6 +2,7 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 #include "_connection_base.h"
+#include "mysql.h"
 
 namespace flame::mysql {
     class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> {
diff --git a/src/mysql/client.h b/src/mysql/client.h
index ad379bc..744d8de 100644
--- a/src/mysql/client.h
+++ b/src/mysql/client.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mysql.h"
 
 namespace flame::mysql {
 
diff --git a/src/mysql/mysql.h b/src/mysql/mysql.h
index 8e65b48..0be7b4a 100644
--- a/src/mysql/mysql.h
+++ b/src/mysql/mysql.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include <mariadb/mysql.h>
 
 namespace flame::mysql {
 
diff --git a/src/mysql/result.h b/src/mysql/result.h
index 9197656..99cf070 100644
--- a/src/mysql/result.h
+++ b/src/mysql/result.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mysql.h"
 
 namespace flame::mysql {
 
diff --git a/src/mysql/tx.h b/src/mysql/tx.h
index c90572f..217b483 100644
--- a/src/mysql/tx.h
+++ b/src/mysql/tx.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "mysql.h"
 
 namespace flame::mysql {
 
diff --git a/src/os/os.cpp b/src/os/os.cpp
index 0205ab2..08efc5c 100644
--- a/src/os/os.cpp
+++ b/src/os/os.cpp
@@ -2,6 +2,13 @@
 #include "../controller.h"
 #include "os.h"
 #include "process.h"
+#include <filesystem>
+// 获取本地网卡地址
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+
 
 namespace flame::os {
     void declare(php::extension_entry &ext) {
diff --git a/src/rabbitmq/_client.h b/src/rabbitmq/_client.h
index 6cc1edf..2a823ea 100644
--- a/src/rabbitmq/_client.h
+++ b/src/rabbitmq/_client.h
@@ -3,6 +3,7 @@
 #include "../coroutine.h"
 #include "../url.h"
 #include "../coroutine_queue.h"
+#include "rabbitmq.h"
 
 namespace flame::rabbitmq {
 
diff --git a/src/rabbitmq/consumer.h b/src/rabbitmq/consumer.h
index 5d4338e..2ceceda 100644
--- a/src/rabbitmq/consumer.h
+++ b/src/rabbitmq/consumer.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "rabbitmq.h"
 
 namespace flame::rabbitmq {
 
diff --git a/src/rabbitmq/message.h b/src/rabbitmq/message.h
index 13ea716..369c57d 100644
--- a/src/rabbitmq/message.h
+++ b/src/rabbitmq/message.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "rabbitmq.h"
 
 namespace flame::rabbitmq {
 
diff --git a/src/rabbitmq/producer.h b/src/rabbitmq/producer.h
index a66817d..8097218 100644
--- a/src/rabbitmq/producer.h
+++ b/src/rabbitmq/producer.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "rabbitmq.h"
 
 namespace flame::rabbitmq {
     class _client;
diff --git a/src/rabbitmq/rabbitmq.h b/src/rabbitmq/rabbitmq.h
index 6d008fe..1968aa2 100644
--- a/src/rabbitmq/rabbitmq.h
+++ b/src/rabbitmq/rabbitmq.h
@@ -1,5 +1,7 @@
 #pragma once
 #include "../vendor.h"
+#include <amqpcpp.h>
+#include <amqpcpp/libboostasio.h>
 
 namespace flame::rabbitmq {
 
diff --git a/src/redis/_connection_base.h b/src/redis/_connection_base.h
index 92e61e7..1d97f3d 100644
--- a/src/redis/_connection_base.h
+++ b/src/redis/_connection_base.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "redis.h"
 
 namespace flame::redis {
 
diff --git a/src/redis/_connection_lock.h b/src/redis/_connection_lock.h
index aff88da..6147bc9 100644
--- a/src/redis/_connection_lock.h
+++ b/src/redis/_connection_lock.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "../vendor.h"
 #include "../coroutine.h"
+#include "redis.h"
 #include "_connection_base.h"
 
 namespace flame::redis {
diff --git a/src/redis/_connection_pool.h b/src/redis/_connection_pool.h
index 65ab021..ba9bdc3 100644
--- a/src/redis/_connection_pool.h
+++ b/src/redis/_connection_pool.h
@@ -2,6 +2,7 @@
 #include "../vendor.h"
 #include "../coroutine.h"
 #include "../url.h"
+#include "redis.h"
 #include "_connection_base.h"
 
 
diff --git a/src/redis/client.h b/src/redis/client.h
index 37d6717..3d91845 100644
--- a/src/redis/client.h
+++ b/src/redis/client.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "redis.h"
 
 namespace flame::redis {
     class _connection_pool;
diff --git a/src/redis/redis.h b/src/redis/redis.h
index cd4d68f..c18ef96 100644
--- a/src/redis/redis.h
+++ b/src/redis/redis.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include <hiredis/hiredis.h>
 
 namespace flame::redis {
     void declare(php::extension_entry &ext);
diff --git a/src/redis/tx.h b/src/redis/tx.h
index 38037de..919fdca 100644
--- a/src/redis/tx.h
+++ b/src/redis/tx.h
@@ -1,5 +1,6 @@
 #pragma once
 #include "../vendor.h"
+#include "redis.h"
 
 namespace flame::redis {
 
diff --git a/src/url.cpp b/src/url.cpp
index 6482f92..0e3221d 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -1,5 +1,6 @@
 #include "vendor.h"
 #include "url.h"
+#include <curl/curl.h>
 
 namespace flame {
     typedef ::parser::separator_parser<std::string, std::string> parser_t;
diff --git a/src/vendor.h b/src/vendor.h
index b72a925..2115921 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,59 +1,33 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.13.3"
+#define EXTENSION_VERSION "0.13.99"
+
 
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <ifaddrs.h>
-#include <netdb.h>
-#include <cstdio>
-#include <limits>
 #include <algorithm>
-#include <functional> // for std::functional
 #include <cctype>
-#include <utility>
+#include <cstdio>
+#include <filesystem>
+#include <functional>
+#include <limits>
 #include <memory>
 #include <stack>
 #include <string_view>
 #include <optional>
 #include <unordered_set>
+#include <utility>
+
 #include <phpext/phpext.h>
-#include <hiredis/hiredis.h>
 #include <parser/separator_parser.hpp>
 #include <parser/multipart_parser.hpp>
-#include <boost/version.hpp>
 #include <boost/core/null_deleter.hpp>
 #include <boost/context/fiber.hpp>
 #include <boost/context/segmented_stack.hpp>
-#include <boost/filesystem.hpp>
 #include <boost/format.hpp>
-#include <boost/program_options.hpp>
 #include <boost/process.hpp>
 #include <boost/process/async.hpp>
 #include <boost/logic/tribool.hpp>
 #include <boost/logic/tribool_io.hpp>
 #include <boost/asio.hpp>
-#include <boost/asio/ssl.hpp>
-#include <boost/asio/coroutine.hpp>
 using boost::asio::ip::tcp;
 using boost::asio::ip::udp;
-namespace ssl = boost::asio::ssl;
-#define BOOST_BEAST_USE_STD_STRING_VIEW
-#include <boost/beast/core.hpp>
-#include <boost/beast/http.hpp>
-#include <boost/beast/websocket.hpp>
-#include <boost/crc.hpp>
-#include <amqpcpp.h>
-#include <amqpcpp/libboostasio.h>
-#include <mariadb/mysql.h>
-// #include <my_sys.h>
-#include <mongoc.h>
-#include <librdkafka/rdkafka.h>
-extern "C" {
-    #include <librdkafka/snappy.h>
-    #include <librdkafka/rdmurmur2.h>
-    #include <librdkafka/xxhash.h>
-}
-#include <nghttp2/nghttp2ver.h>
-#include <curl/curl.h>

From 482bf8e0602e3a3ff7dd44522dd019824105d100 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 25 Jun 2019 22:18:17 +0800
Subject: [PATCH 136/146] =?UTF-8?q?TOML=E8=A7=A3=E6=9E=90=20=E5=8F=8A=20?=
 =?UTF-8?q?=E5=B1=82=E7=BA=A7=E9=94=AE=E8=AE=BE=E7=BD=AE=E8=AF=BB=E5=8F=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt         |  11 ++--
 README.md              |  40 ++++++++------
 doc/core.php           |  20 +++++++
 doc/toml.php           |  23 ++++++++
 src/flame.cpp          |  25 ++++++++-
 src/toml/_decode.cpp   |   7 +++
 src/toml/_decode.h     |   6 +++
 src/toml/_executor.cpp |  70 ++++++++++++++++++++++++
 src/toml/_executor.h   |  22 ++++++++
 src/toml/toml.cpp      | 119 +++++++++++++++++++++++++++++++++++++++++
 src/toml/toml.h        |  10 ++++
 src/vendor.h           |   1 +
 test/toml_1.php        |  22 ++++++++
 test/toml_1.txt        |  36 +++++++++++++
 14 files changed, 391 insertions(+), 21 deletions(-)
 create mode 100755 doc/toml.php
 create mode 100755 src/toml/_decode.cpp
 create mode 100755 src/toml/_decode.h
 create mode 100755 src/toml/_executor.cpp
 create mode 100755 src/toml/_executor.h
 create mode 100755 src/toml/toml.cpp
 create mode 100755 src/toml/toml.h
 create mode 100755 test/toml_1.php
 create mode 100644 test/toml_1.txt

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ef15fd4..f3c84b2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,17 +24,18 @@ add_library(FLAME SHARED ${SOURCES})
 # -----------------------------------------------------------------------------
 set(VENDOR_BOOST /data/vendor/boost-1.70.0)
 set(VENDOR_PARSER /data/vendor/parser-1.0.0)
+set(VENDOR_LLTOML /data/vendor/lltoml-1.0.0)
 set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
 set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.5)
 set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1c)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
-set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.0.1)
-set(VENDOR_PHP /data/vendor/php-7.2.19)
-set(VENDOR_PHPEXT /data/vendor/phpext-2.0.0)
+set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.1.0)
+set(VENDOR_PHP /data/server/php-7.2.19)
+set(VENDOR_PHPEXT /data/vendor/phpext-2.1.0)
 set(VENDOR_CARES /data/vendor/cares-1.15.0)
 set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.1)
 set(VENDOR_CURL /data/vendor/curl-7.65.0)
-set(VENDOR_MARIA /data/vendor/mariac-3.0.10)
+set(VENDOR_MARIA /data/vendor/mariac-3.1.2)
 
 execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
     COMMAND sed "s/ *-I/;/g"
@@ -56,6 +57,7 @@ set_target_properties(FLAME PROPERTIES
 # 包含路径
 target_include_directories(FLAME SYSTEM PRIVATE
     ${VENDOR_PARSER}/include
+    ${VENDOR_LLTOML}/include
     ${VENDOR_HIREDIS}/include
     ${VENDOR_AMQP}/include
     ${VENDOR_RDKAFKA}/include
@@ -72,6 +74,7 @@ target_include_directories(FLAME SYSTEM PRIVATE
 # 链接库
 target_link_libraries(FLAME
     ${VENDOR_HIREDIS}/lib/libhiredis.a
+    ${VENDOR_LLTOML}/lib/liblltoml.a
     ${VENDOR_PHPEXT}/lib/libphpext.a
     ${VENDOR_MARIA}/lib/mariadb/libmariadbclient.a
     ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
diff --git a/README.md b/README.md
index a5177ab..2246ffc 100644
--- a/README.md
+++ b/README.md
@@ -120,23 +120,30 @@ flame\run();
 make install
 ```
 
+#### lltoml
+``` Bash
+CFLAGS="-O2 -DNDEBUG" CXXFLAGS="-O2 -DNDEBUG" make
+make install
+```
+
 #### hiredis
 ``` Bash
 CC=gcc make
 PREFIX=/data/vendor/hiredis-0.14.0 make install
+# 未提供禁用动态库选项
 # rm /data/vendor/hiredis-0.14.0/lib/*.so*
 ```
 
-#### AMQP-CPP
+#### openssl
 ``` Bash
-mkdir stage && cd stage
-CC=gcc CXX=g++ cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.5 -DCMAKE_CXX_FLAGS=-fPIC -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
+CC=gcc CXX=g++ ./Configure no-shared --prefix=/data/vendor/openssl-1.1.1c linux-x86_64
 make && make install
 ```
 
-#### openssl
+#### AMQP-CPP
 ``` Bash
-CC=gcc CXX=g++ ./Configure no-shared --prefix=/data/vendor/openssl-1.1.1c linux-x86_64
+mkdir stage && cd stage
+CC=gcc CXX=g++ CXXFLAGS="-fPIC -I/data/vendor/openssl-1.1.1c/include" LDFLAGS="-L/data/vendor/openssl-1.1.c/lib" cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.5 -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
 make && make install
 ```
 
@@ -151,35 +158,36 @@ make && make install
 #### mongoc-driver
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+CC=gcc CXX=g++ CFLAGS="-fPIC" LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
 make && make install
+# 未提供 ENABLE_SHARED=OFF 或类似选项
 # rm /data/vendor/mongoc-1.14.0/lib/*.so*
 ```
 
 #### rdkafka
 ``` Bash
-CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.0.1 --disable-sasl
+CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.1.0 --disable-sasl
 make && make install
-# rm /data/vendor/rdkafka-1.0.1/lib/*.so*
-cp src/snappy.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
-cp src/rdmurmur2.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
-cp src/xxhash.h /data/vendor/rdkafka-1.0.1/include/librdkafka/
+# rm /data/vendor/rdkafka-1.1.0/lib/*.so*
+cp src/snappy.h /data/vendor/rdkafka-1.1.0/include/librdkafka/
+cp src/rdmurmur2.h /data/vendor/rdkafka-1.1.0/include/librdkafka/
+cp src/xxhash.h /data/vendor/rdkafka-1.1.0/include/librdkafka/
 ```
 
 #### PHP
 ``` Bash
-CC=gcc CXX=g++ CFLAGS="-pthread" ./configure --prefix=/data/vendor/php-7.2.19 --with-config-file-path=/data/vendor/php-7.2.19/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
+CC=gcc CXX=g++ CFLAGS="-pthread" ./configure --prefix=/data/server/php-7.2.19 --with-config-file-path=/data/server/php-7.2.19/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
 make && make install
 ```
 
 <!--
 # external openssl extension for php
-CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" ./configure --with-php-config=/data/vendor/php-7.2.19/bin/php-config --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
+CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" ./configure --with-php-config=/data/server/php-7.2.19/bin/php-config --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
 -->
 
 #### libphpext
 ``` Bash
-CXXFLAGS="-O2" make
+CXXFLAGS="-O2 -DNDEBUG" make
 make install
 ```
 
@@ -209,9 +217,9 @@ make && make install
 #### maria-connector-c
 ``` Bash
 mkdir stage && cd stage
-CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig:/data/vendor/curl-7.65.0/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.0.10 ../
+CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig:/data/vendor/curl-7.65.0/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.1.2 ../
 make && make install
-# rm /data/vendor/mariac-3.0.10/lib/mariadb/*.so*
+# rm /data/vendor/mariac-3.1.2/lib/mariadb/*.so*
 ```
 
 </p>
diff --git a/doc/core.php b/doc/core.php
index 49995ef..8d8046a 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -39,6 +39,26 @@ function init($process_name, $options = []) {}
  * 注意：框架使用 固定栈大小 方式进行协程调度（相对效率较高）；但为满足大部分需求，栈空间占用相对（初始值）较高；不应启动过多的协程；
  */
 function go(callable $cb) {}
+/**
+ * 读取数组的指定层级键（常用于读取配置数据）
+ * @example
+ *  $a = ["a" => ["b" => 123]];
+ *  var_dump(flame\get($a, "a.b")); // 123
+ * @return mixed
+ * 注意：
+ *  1. 数字下标将被当作文本处理；
+ */
+function get($array, $keys) {}
+/**
+ * 设置数组的层级键值
+ * @example
+ *  $a = [];
+ *  flame\set($a, "a.b", 123);
+ *  var_dump($a); // ["a" => ["b" => 123]]
+ * 注意：
+ *   1. 数字下标将被当作文本处理；
+ */
+function set(&$array, $keys, $val) {}
 /**
  * 获取一个当前协程 ID 标识
  */
diff --git a/doc/toml.php b/doc/toml.php
new file mode 100755
index 0000000..4209645
--- /dev/null
+++ b/doc/toml.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * 提供对 TOML 格式文本、文件的解析；
+ * TOML 格式定义请参考：https://github.com/toml-lang/toml
+ */
+namespace flame\toml;
+
+/**
+ * 解析 TOML 格式的字符串并返回关联数组
+ * @param string $toml 文本 TOML 格式
+ * 注意：若文本不完整或不正确会抛出异常；
+ */
+function parse_string(string $toml): array {
+    return [];
+}
+
+/**
+ * 解析指定 TOML 格式的数据文件，并返回关联数组
+ * @param string $file 待解析文件路径
+ */
+function parse_file($file): array {
+    return [];
+}
diff --git a/src/flame.cpp b/src/flame.cpp
index 14c4cf6..0a4205f 100644
--- a/src/flame.cpp
+++ b/src/flame.cpp
@@ -82,6 +82,20 @@ namespace flame {
         gcontroller->context_y.stop();
         return nullptr;
     }
+
+    static php::value get(php::parameters& params) {
+        php::array  target = params[0];
+        php::string fields = params[1];
+        return flame::toml::get(target, { fields.data(), fields.size() }, 0);
+    }
+
+    static php::value set(php::parameters& params) {
+        php::array  target = params.get(0, true);
+        php::string fields = params[1];
+        php::value  values = params[2];
+        flame::toml::set(target, {fields.data(), fields.size()}, 0, values);
+        return nullptr;
+    }
 }
 
 static std::string openssl_version_str() {
@@ -140,7 +154,16 @@ extern "C" {
                 {"event", php::TYPE::STRING},
                 {"callback", php::TYPE::CALLABLE},
             })
-            .function<flame::quit>("flame\\quit");
+            .function<flame::quit>("flame\\quit")
+            .function<flame::set>("flame\\set", {
+                {"target", php::TYPE::ARRAY, true},
+                {"fields", php::TYPE::STRING},
+                {"values", php::TYPE::UNDEFINED},
+            })
+            .function<flame::get>("flame\\get", {
+                {"target", php::TYPE::ARRAY},
+                {"fields", php::TYPE::STRING},
+            });
 
             flame::core::declare(ext);
             flame::log::declare(ext);
diff --git a/src/toml/_decode.cpp b/src/toml/_decode.cpp
new file mode 100755
index 0000000..744999f
--- /dev/null
+++ b/src/toml/_decode.cpp
@@ -0,0 +1,7 @@
+#include "_decode.h"
+
+namespace flame::toml {
+    void decode_inplace(php::string& str) {
+        php_stripcslashes(str);
+    }
+}
\ No newline at end of file
diff --git a/src/toml/_decode.h b/src/toml/_decode.h
new file mode 100755
index 0000000..013903f
--- /dev/null
+++ b/src/toml/_decode.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame::toml {
+    void decode_inplace(php::string& str);
+}
\ No newline at end of file
diff --git a/src/toml/_executor.cpp b/src/toml/_executor.cpp
new file mode 100755
index 0000000..a01dc4c
--- /dev/null
+++ b/src/toml/_executor.cpp
@@ -0,0 +1,70 @@
+#include "_executor.h"
+#include "_decode.h"
+
+namespace flame::toml {
+
+void _executor::operator ()(const toml_parser::parser& p, std::string_view chunk) {
+    buffer_.append(chunk.data(), chunk.size());
+}
+
+void _executor::operator ()(const toml_parser::parser& p) {
+    php::string raw = std::move(buffer_);
+    std::cout << "(" << (int)p.value_type() << ") " << p.field() << " => [" << raw << "]\n";
+    php::value  val = _executor::restore(raw, p.value_type());
+    
+    // switch(p.container_type()) {
+    // case toml_parser::CONTAINER_TYPE_ARRAY:
+    // break;
+    // case toml_parser::CONTAINER_TYPE_ARRAY_TABLE:
+    // break;
+    // case toml_parser::CONTAINER_TYPE_TABLE:
+    // default:
+    //     ;
+    // }
+    set(root_, p.field(), p.value_array_index(), val);
+}
+
+php::value _executor::restore(php::string& raw, std::uint8_t value_type) {
+    php::value val;
+    // 根据类型还原数据
+    switch(value_type) {
+    case toml_parser::VALUE_TYPE_BOOLEAN:
+        val = raw.to_boolean();
+        break;
+    case toml_parser::VALUE_TYPE_INTEGER:
+        val = raw.to_integer();
+        break;
+    case toml_parser::VALUE_TYPE_HEX_INTEGER:
+        val = std::strtol(raw.data(), nullptr, 16);
+        break;
+    case toml_parser::VALUE_TYPE_OCT_INTEGER:
+        val = std::strtol(raw.data(), nullptr, 8);
+        break;
+    case toml_parser::VALUE_TYPE_BIN_INTEGER:
+        val = std::strtol(raw.data(), nullptr, 2);
+        break;
+    case toml_parser::VALUE_TYPE_INFINITY:
+        val = raw.data()[0] == '-' ? - std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
+        break;
+    case toml_parser::VALUE_TYPE_NAN:
+        val = NAN;
+        break;
+    case toml_parser::VALUE_TYPE_FLOAT:
+        val = raw.to_float();
+        break;
+    case toml_parser::VALUE_TYPE_DATE:
+        val = php::datetime(raw.c_str());
+        break;
+    case toml_parser::VALUE_TYPE_BASIC_STRING:
+        decode_inplace(raw);
+        // fallthrough
+    case toml_parser::VALUE_TYPE_UNKNOWN: // 未知类型的数据按原始字符串处理
+    case toml_parser::VALUE_TYPE_RAW_STRING:
+    default:
+        val = raw;
+    }
+
+    return val;
+}
+
+}
diff --git a/src/toml/_executor.h b/src/toml/_executor.h
new file mode 100755
index 0000000..79bff03
--- /dev/null
+++ b/src/toml/_executor.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "../vendor.h"
+#include "toml.h"
+
+namespace flame::toml {
+
+class _executor {
+public:
+    _executor(php::array& root) noexcept
+    : root_(root) {}
+    void operator ()(const toml_parser::parser& p, std::string_view chunk);
+    void operator ()(const toml_parser::parser& p);
+
+private:
+    php::array&   root_;
+    php::buffer buffer_;
+
+    static php::value restore(php::string& raw, std::uint8_t value_type);
+};
+
+
+}
\ No newline at end of file
diff --git a/src/toml/toml.cpp b/src/toml/toml.cpp
new file mode 100755
index 0000000..21ab472
--- /dev/null
+++ b/src/toml/toml.cpp
@@ -0,0 +1,119 @@
+#include "toml.h"
+#include "_executor.h"
+
+namespace flame::toml {
+
+    void set(php::array& root, std::string_view prefix, std::size_t index, const php::value& v) {
+        std::size_t pe = -1, ps = -1;
+        zval x;
+        ZVAL_ARR(&x, static_cast<zend_array*>(root));
+        php::array ctr (&x, true); // 纯引用：容器数组
+        std::string_view field = prefix.substr( prefix.find_last_of('.') + 1 );
+        prefix = prefix.substr(0, prefix.size() - field.size() - 1);
+        do {
+            ps = pe = pe + 1;
+            pe = prefix.find_first_of('.', ps); // prefix 存在，但不含有 . 也需要进行下面动作
+            auto sv = prefix.substr(ps, pe - ps);
+            if (sv.size() == 0 || sv == "$") {
+                // root 元素不做处理
+            }
+            else if (sv == "#") {
+                php::value y = ctr.get(index);
+                if(y.type_of(php::TYPE::UNDEFINED)) {
+                    php::value z = php::array(4);
+                    ctr.set(index, z, /* seperate = */false); // 更新原数组
+                    ZVAL_ARR(&x, static_cast<zend_array*>(z));
+                }
+                else if (y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to set: container (index) is not an array", -1);
+            }
+            else {
+                php::string key {sv.data(), sv.size()};
+                php::value y = ctr.get(key);
+                if(y.type_of(php::TYPE::UNDEFINED)) {
+                    php::value z = php::array(4);
+                    ctr.set(key, z, /* seperate = */false); // 更新原数组
+                    ZVAL_ARR(&x, static_cast<zend_array*>(z));
+                }
+                else if(y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to set: container (field) is not an array", -1);
+            }
+        } while(pe != prefix.npos);
+
+        if(field.empty()) ctr.set(ctr.size(), v);
+        else ctr.set({field.data(), field.size()}, v);
+    }
+
+    php::value get(php::array root, std::string_view prefix, std::size_t index) {
+        std::size_t pe = -1, ps = -1;
+        zval x;
+        ZVAL_ARR(&x, static_cast<zend_array*>(root));
+        php::array ctr (&x, true); // 指向容器数组
+        std::string_view field = prefix.substr( prefix.find_last_of('.') + 1 );
+        prefix = prefix.substr(0, prefix.size() - field.size() - 1);
+        do {
+            ps = pe = pe + 1;
+            pe = prefix.find_first_of('.', ps); // prefix 存在，但不含有 . 也需要进行下面动作
+            auto sv = prefix.substr(ps, pe - ps);
+            if (sv.size() == 0 || sv == "$") {
+                // root 元素不做处理
+                // 无 prefix 不做处理
+            }
+            else if (sv == "#") {
+                php::value y = ctr.get(index);
+                if (y.type_of(php::TYPE::UNDEFINED)) return php::value();
+                else if (y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to get: container (index) is not an array", -1);
+            }
+            else {
+                php::value y = ctr.get({sv.data(), sv.size()});
+                if (y.type_of(php::TYPE::UNDEFINED)) return php::value();
+                else if (y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to get: container (field) is not an array", -1);
+            }
+        } while(pe != prefix.npos);
+
+        if(field.empty()) return ctr;
+        else return ctr.get({field.data(), field.size()});
+    }
+
+    static php::value parse_string(php::parameters& params) {
+        php::string s = params[0];
+        php::array r(8);
+        _executor  e(r);
+        llparse::toml::parser p({std::ref(e), std::ref(e)});
+        p.parse({s.data(), s.size()});
+        return r;
+    }
+
+    static php::value parse_file(php::parameters& params) {
+        std::string   f = params[0];
+        std::ifstream s {f.c_str()};
+        if (!s.is_open()) 
+            throw php::exception(zend_ce_error_exception, (boost::format("Failed to parse toml: unable to open file '%s'") % f).str(), -1);
+        
+        php::array r(8);
+        _executor  e(r);
+        llparse::toml::parser p({std::ref(e), std::ref(e)});
+        char        buffer[2048];
+        std::size_t length = 0;
+        while(!s.eof()) {
+            s.read(buffer, sizeof(buffer));
+            length = s.gcount();
+            if (length > 0) p.parse({buffer, length});
+        }
+        return r;
+    }
+
+    
+
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<parse_string>("flame\\toml\\parse_string", {
+                {"toml", php::TYPE::STRING},
+            })
+            .function<parse_file>("flame\\toml\\parse_file", {
+                {"file", php::TYPE::STRING},
+            });
+    }
+}
\ No newline at end of file
diff --git a/src/toml/toml.h b/src/toml/toml.h
new file mode 100755
index 0000000..9f5e192
--- /dev/null
+++ b/src/toml/toml.h
@@ -0,0 +1,10 @@
+#include "../vendor.h"
+#include <toml/toml.hpp>
+namespace toml_parser = llparse::toml;
+
+namespace flame::toml {
+    void declare(php::extension_entry &ext);
+
+          void set(php::array& r, std::string_view prefix, std::size_t index, const php::value& v);
+    php::value get(php::array  r, std::string_view prefix, std::size_t index);
+}
diff --git a/src/vendor.h b/src/vendor.h
index 2115921..938bf08 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -11,6 +11,7 @@
 #include <functional>
 #include <limits>
 #include <memory>
+#include <set>
 #include <stack>
 #include <string_view>
 #include <optional>
diff --git a/test/toml_1.php b/test/toml_1.php
new file mode 100755
index 0000000..c36eb37
--- /dev/null
+++ b/test/toml_1.php
@@ -0,0 +1,22 @@
+<?php
+
+
+$x = [
+    "a" => [
+        "b" => "111111",
+    ]
+];
+$y = flame\get($x, "a.b");
+var_dump($y);
+flame\set($x, "a.c", "222222");
+flame\set($x, "a.x.d", "333333");
+flame\set($x, "e.f", "444444");
+flame\set($x, "g.h.i", "555555");
+var_dump($x);
+
+
+$r = flame\toml\parse_file(__DIR__."/toml_1.txt");
+var_dump($r);
+$data = file_get_contents(__DIR__."/toml_1.txt");
+$r = flame\toml\parse_string($data);
+var_dump($r);
diff --git a/test/toml_1.txt b/test/toml_1.txt
new file mode 100644
index 0000000..9bff83a
--- /dev/null
+++ b/test/toml_1.txt
@@ -0,0 +1,36 @@
+
+s2 = "234"
+s3 = '345'
+s4 = """4 \
+5 \
+6""" # commment 1
+s5 = '''5
+6
+7'''
+
+f1 = 1.2 #comment 2
+f2 = 1e+10
+f3 = 2.22e-5
+
+#comment 3
+
+[a.b]
+c = [ x, {y = 1, z = 2} ]
+# comment 4
+d = 3
+e = {f = 4, g = 5}
+
+[[h.i]]
+#comment 5
+x = 1
+y = 2019-01-01 01:01:01
+z = 01:01:01
+
+[[h.i]]
+x = 2
+y = 2019-02-22 02:02:02
+z = 02:02:02
+
+[c] #comment 6
+a = true
+b.c = false
\ No newline at end of file

From 331e75f238d510428c04887b73e32e08579530eb Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 26 Jun 2019 19:21:36 +0800
Subject: [PATCH 137/146] v0.14.0

---
 src/vendor.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/vendor.h b/src/vendor.h
index 938bf08..a83c07f 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.13.99"
+#define EXTENSION_VERSION "0.14.0"
 
 
 #include <algorithm>

From 6647a8231f22f1cc76c9a8a171f741e82cadc473 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 26 Jun 2019 22:01:22 +0800
Subject: [PATCH 138/146] =?UTF-8?q?=E8=A1=A5=E5=85=85=20IDE=20=E8=87=AA?=
 =?UTF-8?q?=E5=8A=A8=E5=AE=8C=E6=88=90=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 2246ffc..2a2855e 100644
--- a/README.md
+++ b/README.md
@@ -97,13 +97,16 @@ flame\run();
 * **encoding** - 提供了若干编码、序列化函数；
 * **compress** - 提供了若干压缩算法；
 
-
-
-### 文档
+### 说明
 * [API 文档](https://github.com/terrywh/php-flame/tree/master/doc)
 * [常见问题](https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
-
-> 将 `/doc` 挂接在 IDE 内可提供 **自动完成** 等功能
+* 自动完成（IDE提示）- 目录 `/doc` 下使用 PHP Doc 语法定义和说明 API 接口，可挂接 IDE 自动完成功能；
+    * VSCode/ (php-intelephense)
+    > ``` Bash
+    > ln -s /path/to/php-flame/doc /path/to/extensions/bmewburn.vscode-intelephense-client-x.x.xx/node_modules/intelephense/lib/stub/flame
+    > ```
+    * PhpStorm
+    > [Configuring Include Paths](https://www.jetbrains.com/help/phpstorm/configuring-include-paths.html#Configuring_Include_Paths.xml)
 
 ### 其他
 <details><summary>依赖项编译安装，仅供参考</summary>

From a7b167949cc560c8497f4a9662b694544b5c48fb Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 27 Jun 2019 18:52:28 +0800
Subject: [PATCH 139/146] Copyright YEAR NAME

---
 LICENCE | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/LICENCE b/LICENCE
index 714672f..d72693d 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2017 TerryWu
+Copyright (c) 2017-2019 terrywh
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

From 69e22296ed39a6d98532ceb3684d0ed8347b1592 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 2 Jul 2019 13:08:36 +0800
Subject: [PATCH 140/146] =?UTF-8?q?=E5=8D=8F=E7=A8=8B=E4=BB=A3=E7=A0=81?=
 =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9B=E4=BD=BF=E7=94=A8=20C++=20=E5=8D=8F?=
 =?UTF-8?q?=E7=A8=8B=E9=87=8D=E6=9E=84=E8=BF=9B=E7=A8=8B=E7=AE=A1=E7=90=86?=
 =?UTF-8?q?=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/controller.cpp                           |  72 -----
 src/controller.h                             |  51 ----
 src/controller_master.cpp                    | 288 -------------------
 src/controller_master.h                      |  66 -----
 src/controller_worker.cpp                    |  65 -----
 src/controller_worker.h                      |  16 --
 src/core.cpp                                 |  52 ----
 src/core.h                                   |   7 -
 src/coroutine.cpp                            | 189 ------------
 src/coroutine.h                              | 177 +++++++-----
 src/coroutine_mutex.h                        |  80 +++---
 src/coroutine_queue.h                        | 126 ++++----
 src/flame.cpp                                | 192 -------------
 src/{ => flame}/compress/compress.cpp        |   0
 src/{ => flame}/compress/compress.h          |   2 +-
 src/flame/controller.cpp                     |  55 ++++
 src/flame/controller.h                       |  46 +++
 src/flame/coroutine.cpp                      | 109 +++++++
 src/flame/coroutine.h                        | 107 +++++++
 src/{ => flame}/encoding/encoding.cpp        |   0
 src/{ => flame}/encoding/encoding.h          |   2 +-
 src/flame/flame.cpp                          |  29 ++
 src/{ => flame}/hash/hash.cpp                |   0
 src/{ => flame}/hash/hash.h                  |   2 +-
 src/{ => flame}/http/_handler.cpp            |  12 +-
 src/{ => flame}/http/_handler.h              |   2 +-
 src/{ => flame}/http/client.cpp              |   1 -
 src/{ => flame}/http/client.h                |   2 +-
 src/{ => flame}/http/client_body.cpp         |   0
 src/{ => flame}/http/client_body.h           |   2 +-
 src/{ => flame}/http/client_poll.cpp         |   0
 src/{ => flame}/http/client_poll.h           |   2 +-
 src/{ => flame}/http/client_request.cpp      |   0
 src/{ => flame}/http/client_request.h        |   4 +-
 src/{ => flame}/http/client_response.cpp     |   0
 src/{ => flame}/http/client_response.h       |   2 +-
 src/{ => flame}/http/http.cpp                |   1 -
 src/{ => flame}/http/http.h                  |   2 +-
 src/{ => flame}/http/server.cpp              |   1 -
 src/{ => flame}/http/server.h                |   2 +-
 src/{ => flame}/http/server_request.cpp      |   0
 src/{ => flame}/http/server_request.h        |   2 +-
 src/{ => flame}/http/server_response.cpp     |   0
 src/{ => flame}/http/server_response.h       |   2 +-
 src/{ => flame}/http/value_body.cpp          |   0
 src/{ => flame}/http/value_body.h            |   2 +-
 src/{ => flame}/http/value_body.ipp          |   0
 src/{ => flame}/kafka/_consumer.cpp          |   2 +-
 src/{ => flame}/kafka/_consumer.h            |   9 +-
 src/{ => flame}/kafka/_producer.cpp          |   0
 src/{ => flame}/kafka/_producer.h            |   2 +-
 src/{ => flame}/kafka/consumer.cpp           |   9 +-
 src/{ => flame}/kafka/consumer.h             |   2 +-
 src/{ => flame}/kafka/kafka.cpp              |   0
 src/{ => flame}/kafka/kafka.h                |   2 +-
 src/{ => flame}/kafka/message.cpp            |   0
 src/{ => flame}/kafka/message.h              |   2 +-
 src/{ => flame}/kafka/producer.cpp           |   0
 src/{ => flame}/kafka/producer.h             |   2 +-
 src/{ => flame}/log/log.cpp                  |   0
 src/{ => flame}/log/log.h                    |   2 +-
 src/flame/master.cpp                         | 106 +++++++
 src/flame/master.h                           |  29 ++
 src/{ => flame}/mongodb/_connection_base.cpp |   0
 src/{ => flame}/mongodb/_connection_base.h   |   2 +-
 src/{ => flame}/mongodb/_connection_lock.cpp |   0
 src/{ => flame}/mongodb/_connection_lock.h   |   2 +-
 src/{ => flame}/mongodb/_connection_pool.cpp |   0
 src/{ => flame}/mongodb/_connection_pool.h   |   2 +-
 src/{ => flame}/mongodb/client.cpp           |   0
 src/{ => flame}/mongodb/client.h             |   2 +-
 src/{ => flame}/mongodb/collection.cpp       |   0
 src/{ => flame}/mongodb/collection.h         |   2 +-
 src/{ => flame}/mongodb/cursor.cpp           |   0
 src/{ => flame}/mongodb/cursor.h             |   2 +-
 src/{ => flame}/mongodb/date_time.cpp        |   0
 src/{ => flame}/mongodb/date_time.h          |   2 +-
 src/{ => flame}/mongodb/mongodb.cpp          |   1 -
 src/{ => flame}/mongodb/mongodb.h            |   2 +-
 src/{ => flame}/mongodb/object_id.cpp        |   0
 src/{ => flame}/mongodb/object_id.h          |   2 +-
 src/{ => flame}/mutex.cpp                    |   2 +-
 src/{ => flame}/mutex.h                      |   4 +-
 src/{ => flame}/mysql/_connection_base.cpp   |   0
 src/{ => flame}/mysql/_connection_base.h     |   2 +-
 src/{ => flame}/mysql/_connection_lock.cpp   |   1 -
 src/{ => flame}/mysql/_connection_lock.h     |   2 +-
 src/{ => flame}/mysql/_connection_pool.cpp   |   0
 src/{ => flame}/mysql/_connection_pool.h     |   5 +-
 src/{ => flame}/mysql/client.cpp             |   0
 src/{ => flame}/mysql/client.h               |   2 +-
 src/{ => flame}/mysql/mysql.cpp              |   1 -
 src/{ => flame}/mysql/mysql.h                |   2 +-
 src/{ => flame}/mysql/result.cpp             |   0
 src/{ => flame}/mysql/result.h               |   2 +-
 src/{ => flame}/mysql/tx.cpp                 |   0
 src/{ => flame}/mysql/tx.h                   |   2 +-
 src/{ => flame}/os/os.cpp                    |   3 +-
 src/{ => flame}/os/os.h                      |   2 +-
 src/{ => flame}/os/process.cpp               |   0
 src/{ => flame}/os/process.h                 |   4 +-
 src/{ => flame}/queue.cpp                    |  20 +-
 src/{ => flame}/queue.h                      |  11 +-
 src/{ => flame}/rabbitmq/_client.cpp         |   1 -
 src/{ => flame}/rabbitmq/_client.h           |   6 +-
 src/{ => flame}/rabbitmq/consumer.cpp        |   7 +-
 src/{ => flame}/rabbitmq/consumer.h          |   2 +-
 src/{ => flame}/rabbitmq/message.cpp         |   0
 src/{ => flame}/rabbitmq/message.h           |   2 +-
 src/{ => flame}/rabbitmq/producer.cpp        |   0
 src/{ => flame}/rabbitmq/producer.h          |   2 +-
 src/{ => flame}/rabbitmq/rabbitmq.cpp        |   3 +-
 src/{ => flame}/rabbitmq/rabbitmq.h          |   2 +-
 src/{ => flame}/redis/_connection_base.cpp   |   0
 src/{ => flame}/redis/_connection_base.h     |   2 +-
 src/{ => flame}/redis/_connection_lock.cpp   |   1 -
 src/{ => flame}/redis/_connection_lock.h     |   2 +-
 src/{ => flame}/redis/_connection_pool.cpp   |   0
 src/{ => flame}/redis/_connection_pool.h     |   4 +-
 src/{ => flame}/redis/client.cpp             |   0
 src/{ => flame}/redis/client.h               |   2 +-
 src/{ => flame}/redis/redis.cpp              |   0
 src/{ => flame}/redis/redis.h                |   2 +-
 src/{ => flame}/redis/tx.cpp                 |   0
 src/{ => flame}/redis/tx.h                   |   2 +-
 src/{ => flame}/tcp/server.cpp               |   7 +-
 src/{ => flame}/tcp/server.h                 |   2 +-
 src/{ => flame}/tcp/socket.cpp               |   1 -
 src/{ => flame}/tcp/socket.h                 |   4 +-
 src/{ => flame}/tcp/tcp.cpp                  |   1 -
 src/{ => flame}/tcp/tcp.h                    |   2 +-
 src/{ => flame}/time/time.cpp                |   1 -
 src/{ => flame}/time/time.h                  |   2 +-
 src/{ => flame}/toml/_decode.cpp             |   0
 src/{ => flame}/toml/_decode.h               |   2 +-
 src/{ => flame}/toml/_executor.cpp           |   0
 src/flame/toml/_executor.h                   |  21 ++
 src/{ => flame}/toml/toml.cpp                |   1 +
 src/{ => flame}/toml/toml.h                  |   2 +-
 src/{ => flame}/udp/server.cpp               |   9 +-
 src/{ => flame}/udp/server.h                 |   4 +-
 src/{ => flame}/udp/socket.cpp               |   1 -
 src/{ => flame}/udp/socket.h                 |   4 +-
 src/{ => flame}/udp/udp.cpp                  |   1 -
 src/{ => flame}/udp/udp.h                    |   2 +-
 src/flame/version.cpp                        |  43 +++
 src/flame/version.h                          |   9 +
 src/flame/worker.cpp                         | 204 +++++++++++++
 src/flame/worker.h                           |  38 +++
 src/ipc.cpp                                  |  15 +
 src/ipc.h                                    |   9 +
 src/logger.h                                 |  47 +++
 src/logger_master.cpp                        |  24 ++
 src/logger_master.h                          |  27 ++
 src/logger_worker.cpp                        |  11 +
 src/logger_worker.h                          |  13 +
 src/process_child.cpp                        |  65 +++++
 src/process_child.h                          |  25 ++
 src/process_manager.cpp                      |  73 +++++
 src/process_manager.h                        |  31 ++
 src/signal_watcher.cpp                       |  14 +
 src/signal_watcher.h                         |  18 ++
 src/toml/_executor.h                         |  22 --
 src/url.cpp                                  | 107 ++++---
 src/url.h                                    |  30 +-
 src/util.cpp                                 |  23 ++
 src/util.h                                   |   9 +
 src/vendor.h                                 |   6 +-
 test/coroutine_1.php                         |  29 +-
 test/coroutine_2.php                         |  23 ++
 170 files changed, 1635 insertions(+), 1410 deletions(-)
 delete mode 100644 src/controller.cpp
 delete mode 100644 src/controller.h
 delete mode 100644 src/controller_master.cpp
 delete mode 100644 src/controller_master.h
 delete mode 100644 src/controller_worker.cpp
 delete mode 100644 src/controller_worker.h
 delete mode 100644 src/core.cpp
 delete mode 100644 src/core.h
 delete mode 100644 src/coroutine.cpp
 delete mode 100644 src/flame.cpp
 rename src/{ => flame}/compress/compress.cpp (100%)
 rename src/{ => flame}/compress/compress.h (81%)
 create mode 100644 src/flame/controller.cpp
 create mode 100644 src/flame/controller.h
 create mode 100644 src/flame/coroutine.cpp
 create mode 100644 src/flame/coroutine.h
 rename src/{ => flame}/encoding/encoding.cpp (100%)
 rename src/{ => flame}/encoding/encoding.h (81%)
 create mode 100644 src/flame/flame.cpp
 rename src/{ => flame}/hash/hash.cpp (100%)
 rename src/{ => flame}/hash/hash.h (80%)
 rename src/{ => flame}/http/_handler.cpp (96%)
 rename src/{ => flame}/http/_handler.h (98%)
 rename src/{ => flame}/http/client.cpp (99%)
 rename src/{ => flame}/http/client.h (97%)
 rename src/{ => flame}/http/client_body.cpp (100%)
 rename src/{ => flame}/http/client_body.h (87%)
 rename src/{ => flame}/http/client_poll.cpp (100%)
 rename src/{ => flame}/http/client_poll.h (98%)
 rename src/{ => flame}/http/client_request.cpp (100%)
 rename src/{ => flame}/http/client_request.h (90%)
 rename src/{ => flame}/http/client_response.cpp (100%)
 rename src/{ => flame}/http/client_response.h (94%)
 rename src/{ => flame}/http/http.cpp (97%)
 rename src/{ => flame}/http/http.h (93%)
 rename src/{ => flame}/http/server.cpp (99%)
 rename src/{ => flame}/http/server.h (97%)
 rename src/{ => flame}/http/server_request.cpp (100%)
 rename src/{ => flame}/http/server_request.h (90%)
 rename src/{ => flame}/http/server_response.cpp (100%)
 rename src/{ => flame}/http/server_response.h (94%)
 rename src/{ => flame}/http/value_body.cpp (100%)
 rename src/{ => flame}/http/value_body.h (95%)
 rename src/{ => flame}/http/value_body.ipp (100%)
 rename src/{ => flame}/kafka/_consumer.cpp (96%)
 rename src/{ => flame}/kafka/_consumer.h (86%)
 rename src/{ => flame}/kafka/_producer.cpp (100%)
 rename src/{ => flame}/kafka/_producer.h (94%)
 rename src/{ => flame}/kafka/consumer.cpp (89%)
 rename src/{ => flame}/kafka/consumer.h (93%)
 rename src/{ => flame}/kafka/kafka.cpp (100%)
 rename src/{ => flame}/kafka/kafka.h (91%)
 rename src/{ => flame}/kafka/message.cpp (100%)
 rename src/{ => flame}/kafka/message.h (92%)
 rename src/{ => flame}/kafka/producer.cpp (100%)
 rename src/{ => flame}/kafka/producer.h (92%)
 rename src/{ => flame}/log/log.cpp (100%)
 rename src/{ => flame}/log/log.h (87%)
 create mode 100644 src/flame/master.cpp
 create mode 100644 src/flame/master.h
 rename src/{ => flame}/mongodb/_connection_base.cpp (100%)
 rename src/{ => flame}/mongodb/_connection_base.h (92%)
 rename src/{ => flame}/mongodb/_connection_lock.cpp (100%)
 rename src/{ => flame}/mongodb/_connection_lock.h (93%)
 rename src/{ => flame}/mongodb/_connection_pool.cpp (100%)
 rename src/{ => flame}/mongodb/_connection_pool.h (93%)
 rename src/{ => flame}/mongodb/client.cpp (100%)
 rename src/{ => flame}/mongodb/client.h (93%)
 rename src/{ => flame}/mongodb/collection.cpp (100%)
 rename src/{ => flame}/mongodb/collection.h (95%)
 rename src/{ => flame}/mongodb/cursor.cpp (100%)
 rename src/{ => flame}/mongodb/cursor.h (93%)
 rename src/{ => flame}/mongodb/date_time.cpp (100%)
 rename src/{ => flame}/mongodb/date_time.h (93%)
 rename src/{ => flame}/mongodb/mongodb.cpp (96%)
 rename src/{ => flame}/mongodb/mongodb.h (90%)
 rename src/{ => flame}/mongodb/object_id.cpp (100%)
 rename src/{ => flame}/mongodb/object_id.h (93%)
 rename src/{ => flame}/mutex.cpp (94%)
 rename src/{ => flame}/mutex.h (90%)
 rename src/{ => flame}/mysql/_connection_base.cpp (100%)
 rename src/{ => flame}/mysql/_connection_base.h (94%)
 rename src/{ => flame}/mysql/_connection_lock.cpp (95%)
 rename src/{ => flame}/mysql/_connection_lock.h (93%)
 rename src/{ => flame}/mysql/_connection_pool.cpp (100%)
 rename src/{ => flame}/mysql/_connection_pool.h (93%)
 rename src/{ => flame}/mysql/client.cpp (100%)
 rename src/{ => flame}/mysql/client.h (95%)
 rename src/{ => flame}/mysql/mysql.cpp (97%)
 rename src/{ => flame}/mysql/mysql.h (95%)
 rename src/{ => flame}/mysql/result.cpp (100%)
 rename src/{ => flame}/mysql/result.h (92%)
 rename src/{ => flame}/mysql/tx.cpp (100%)
 rename src/{ => flame}/mysql/tx.h (94%)
 rename src/{ => flame}/os/os.cpp (96%)
 rename src/{ => flame}/os/os.h (87%)
 rename src/{ => flame}/os/process.cpp (100%)
 rename src/{ => flame}/os/process.h (88%)
 rename src/{ => flame}/queue.cpp (59%)
 rename src/{ => flame}/queue.h (68%)
 rename src/{ => flame}/rabbitmq/_client.cpp (96%)
 rename src/{ => flame}/rabbitmq/_client.h (91%)
 rename src/{ => flame}/rabbitmq/consumer.cpp (92%)
 rename src/{ => flame}/rabbitmq/consumer.h (94%)
 rename src/{ => flame}/rabbitmq/message.cpp (100%)
 rename src/{ => flame}/rabbitmq/message.h (92%)
 rename src/{ => flame}/rabbitmq/producer.cpp (100%)
 rename src/{ => flame}/rabbitmq/producer.h (92%)
 rename src/{ => flame}/rabbitmq/rabbitmq.cpp (96%)
 rename src/{ => flame}/rabbitmq/rabbitmq.h (90%)
 rename src/{ => flame}/redis/_connection_base.cpp (100%)
 rename src/{ => flame}/redis/_connection_base.h (94%)
 rename src/{ => flame}/redis/_connection_lock.cpp (96%)
 rename src/{ => flame}/redis/_connection_lock.h (94%)
 rename src/{ => flame}/redis/_connection_pool.cpp (100%)
 rename src/{ => flame}/redis/_connection_pool.h (94%)
 rename src/{ => flame}/redis/client.cpp (100%)
 rename src/{ => flame}/redis/client.h (95%)
 rename src/{ => flame}/redis/redis.cpp (100%)
 rename src/{ => flame}/redis/redis.h (84%)
 rename src/{ => flame}/redis/tx.cpp (100%)
 rename src/{ => flame}/redis/tx.h (95%)
 rename src/{ => flame}/tcp/server.cpp (92%)
 rename src/{ => flame}/tcp/server.h (93%)
 rename src/{ => flame}/tcp/socket.cpp (96%)
 rename src/{ => flame}/tcp/socket.h (88%)
 rename src/{ => flame}/tcp/tcp.cpp (96%)
 rename src/{ => flame}/tcp/tcp.h (82%)
 rename src/{ => flame}/time/time.cpp (95%)
 rename src/{ => flame}/time/time.h (87%)
 rename src/{ => flame}/toml/_decode.cpp (100%)
 rename src/{ => flame}/toml/_decode.h (75%)
 rename src/{ => flame}/toml/_executor.cpp (100%)
 create mode 100755 src/flame/toml/_executor.h
 rename src/{ => flame}/toml/toml.cpp (99%)
 rename src/{ => flame}/toml/toml.h (92%)
 rename src/{ => flame}/udp/server.cpp (96%)
 rename src/{ => flame}/udp/server.h (90%)
 rename src/{ => flame}/udp/socket.cpp (97%)
 rename src/{ => flame}/udp/socket.h (90%)
 rename src/{ => flame}/udp/udp.cpp (96%)
 rename src/{ => flame}/udp/udp.h (90%)
 create mode 100644 src/flame/version.cpp
 create mode 100644 src/flame/version.h
 create mode 100644 src/flame/worker.cpp
 create mode 100644 src/flame/worker.h
 create mode 100644 src/ipc.cpp
 create mode 100644 src/ipc.h
 create mode 100644 src/logger.h
 create mode 100644 src/logger_master.cpp
 create mode 100644 src/logger_master.h
 create mode 100644 src/logger_worker.cpp
 create mode 100644 src/logger_worker.h
 create mode 100755 src/process_child.cpp
 create mode 100755 src/process_child.h
 create mode 100644 src/process_manager.cpp
 create mode 100644 src/process_manager.h
 create mode 100644 src/signal_watcher.cpp
 create mode 100644 src/signal_watcher.h
 delete mode 100755 src/toml/_executor.h
 create mode 100644 src/util.cpp
 create mode 100644 src/util.h
 create mode 100644 test/coroutine_2.php

diff --git a/src/controller.cpp b/src/controller.cpp
deleted file mode 100644
index 7bc3d96..0000000
--- a/src/controller.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-#include "controller.h"
-#include "coroutine.h"
-#include "controller_master.h"
-#include "controller_worker.h"
-
-namespace flame {
-    // 全局控制器
-    std::unique_ptr<controller> gcontroller;
-
-    controller::controller()
-        : type(process_type::UNKNOWN)
-        , env(boost::this_process::environment())
-        , status(STATUS_UNKNOWN)
-        , cbmap(new std::multimap<std::string, php::callable>()) {
-
-        worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
-        worker_size = std::min(std::max((int)worker_size, 1), 512);
-        // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
-        if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
-        else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
-        else {
-            worker_size = 0;
-            type = process_type::WORKER;
-        }
-        mthread_id = std::this_thread::get_id();
-    }
-    controller *controller::on_init(std::function<void (const php::array &options)> fn) {
-        init_cb.push_back(fn);
-        return this;
-    }
-    controller* controller::on_stop(std::function<void ()> fn) {
-        stop_cb.push_back(fn);
-        return this;
-    }
-    void controller::initialize(const std::string& title, const php::array& options) {
-        // 多进程模式退出超时时间，默认 3000ms 超时强制退出
-        if (options.exists("timeout"))
-            worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
-        else worker_quit = 3000;
-        status |= STATUS_INITIALIZED;
-        for (auto fn : init_cb) fn(options);
-        if (type == process_type::WORKER) {
-            if (worker_size > 0) {
-                std::string index = env["FLAME_CUR_WORKER"].to_string();
-                php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
-            }
-            else php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
-            worker_.reset(new controller_worker());
-            worker_->initialize(options);
-        }
-        else/* if (type == process_type::MASTER)*/ {
-            php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
-            master_.reset(new controller_master());
-            master_->initialize(options);
-        }
-    }
-
-    void controller::run() {
-        status &= controller_status::STATUS_RUN;
-        if (type == process_type::WORKER) worker_->run();
-        else /* if (type == process_type::MASTER)*/ master_->run();
-        delete cbmap;
-        // 运行完毕
-        if (status & controller_status::STATUS_EXCEPTION) _exit(-1);
-        else _exit(0);
-        // 由于 PHP 自行回收可能导致 C++ 空间中的 PHP 对象被进行二次释放导致异常
-    }
-
-    void controller::stop() {
-        for (auto fn : stop_cb) fn();
-    }
-}
diff --git a/src/controller.h b/src/controller.h
deleted file mode 100644
index c54ae4a..0000000
--- a/src/controller.h
+++ /dev/null
@@ -1,51 +0,0 @@
-#pragma once
-#include "vendor.h"
-
-namespace flame {
-    class controller_master;
-    class controller_worker;
-    class controller {
-    public:
-        boost::asio::io_context context_x;
-        boost::asio::io_context context_y;
-        zend_execute_data* default_execute_data;
-        enum class process_type {
-            UNKNOWN = 0,
-            MASTER = 1,
-            WORKER = 2,
-        } type;
-        boost::process::environment env;
-        enum controller_status {
-            STATUS_UNKNOWN     = 0x00,
-            STATUS_INITIALIZED = 0x01,
-            STATUS_SHUTDOWN    = 0x02,
-            STATUS_EXCEPTION   = 0x04,
-            STATUS_RUN         = 0x08,
-            STATUS_CLOSECONN   = 0x10,
-        };
-        int status;
-        std::size_t    worker_size;
-        std::size_t    worker_quit; // 多进程退出超时时间
-        std::thread::id mthread_id;
-        // 防止 PHP 提前回收, 使用堆容器
-        std::multimap<std::string, php::callable>* cbmap;
-    private:
-        // 核心进程对象
-        std::unique_ptr<controller_master> master_;
-        // 工作进程对象
-        std::unique_ptr<controller_worker> worker_;
-        // 公共
-        std::list<std::function<void (const php::array& options)>>  init_cb;
-        std::list<std::function<void ()>>  stop_cb;
-    public:
-        controller();
-        controller(const controller& c) = delete;
-        void initialize(const std::string& title, const php::array &options);
-        void run();
-        controller *on_init(std::function<void(const php::array &options)> fn);
-        controller* on_stop(std::function<void ()> fn);
-        void stop();
-    };
-
-    extern std::unique_ptr<controller> gcontroller;
-}
diff --git a/src/controller_master.cpp b/src/controller_master.cpp
deleted file mode 100644
index 658685d..0000000
--- a/src/controller_master.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-#include "controller_master.h"
-#include "controller.h"
-#include "time/time.h"
-
-PHPAPI extern char *php_ini_opened_path;
-
-namespace flame {
-
-    static std::string start_command_line() {
-        std::ostringstream ss;
-        ss << php::constant("PHP_BINARY");
-        ss << " -c " << php_ini_opened_path;
-        php::array argv = php::server("argv");
-        for (auto i = argv.begin(); i != argv.end(); ++i) ss << " " << i->second;
-        return ss.str();
-    }
-
-    controller_master_worker::controller_master_worker(controller_master* m, int i)
-    : m_(m), i_(i)
-    , sout_(gcontroller->context_x), eout_(gcontroller->context_x) {
-        // 准备命令行及环境变量（完全重新启动一个新的 PHP， 通过环境变量标识其为工作进程）
-        std::string cmd = start_command_line();
-        boost::process::environment env = gcontroller->env;
-        env["FLAME_CUR_WORKER"] = std::to_string(i + 1);
-        // 构造进程
-        proc_ = boost::process::child(gcontroller->context_x, cmd, env,
-            boost::process::std_out > sout_, boost::process::std_err > eout_,
-            // 结束回调
-            boost::process::on_exit = [this, i](int exit_code, const std::error_code &error) {
-                if (error.value() == static_cast<int>(std::errc::no_child_process)) return;
-                m_->notify_exit(i, this, exit_code);
-            });
-        redirect_output(sout_, sbuf_);
-        redirect_output(eout_, ebuf_);
-    }
-
-    void controller_master_worker::redirect_output(boost::process::async_pipe& pipe, boost::asio::streambuf& buffer) {
-        boost::asio::async_read_until(pipe, buffer, '\n', [this, &pipe, &buffer] (const boost::system::error_code &error, std::size_t nread) {
-            if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
-                ; // std::cerr << "(INFO) IGNORE sout\n";
-            else if (error)
-                std::cerr << "(ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
-            else {
-                auto y = buffer.data();
-                *(m_->offile_) << std::string(boost::asio::buffers_begin(y), boost::asio::buffers_begin(y) + nread);
-                buffer.consume(nread);
-                m_->offile_->flush();
-
-                redirect_output(pipe, buffer);
-            }
-        });
-    }
-
-    void controller_master_worker::close(int sig) {
-        ::kill(proc_.id(), sig);
-    }
-
-    void controller_master_worker::close_now() {
-        proc_.terminate();
-        sout_.cancel();
-        eout_.cancel();
-        // 注意: 上面强制进程终止过程 proc_.terminate() 流程不会触发 on_exit 回调
-        // 注意：不能在当前流程直接调用 notify_exit 否则会导致错误的状态使用: RESTART + SINGLE FORCEFUL CLOSE
-        // m_->notify_exit(i_, this, 0);
-        boost::asio::post(std::bind(&controller_master::notify_exit, m_, i_, this, 0));
-    }
-
-    controller_master::controller_master()
-    : worker_spawn_(gcontroller->worker_size, nullptr)
-    , worker_close_(gcontroller->worker_size, nullptr)
-    , worker_spawn_count_(0)
-    , worker_count_(0)
-    , tm_inter_(gcontroller->context_x)
-    , tm_force_(gcontroller->context_x) {
-
-    }
-
-    void controller_master::initialize(const php::array& options) {
-        if (options.exists("logger")) ofpath_ = options.get("logger").to_string();
-        else ofpath_ = "stdout";
-        reload_output();
-    }
-
-    void controller_master::spawn_worker_next(int i) {
-        if (i >= gcontroller->worker_size) return;
-        if (i == 0) worker_spawn_count_ = 0;
-        spawn_worker(i);
-
-        // 防止启动过快，按间隔进行启动
-        tm_inter_.cancel();
-        tm_inter_.expires_after(std::chrono::milliseconds(125));
-        tm_inter_.async_wait([this, i] (const boost::system::error_code& error) {
-            if (error) return;
-            spawn_worker_next(i + 1);
-        });
-    }
-
-    void controller_master::spawn_worker(int i) {
-        worker_spawn_[i] = new controller_master_worker(this, i);
-        ++worker_count_;
-        if(++worker_spawn_count_ == gcontroller->worker_size) { // 所有进程进行了启动或重启
-            if((close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_MASTER) close_ = CLOSE_NOTHING; // 重启流程完毕, 重置状态
-        }
-    }
-
-    void controller_master::close_worker(int i) {
-        if (worker_close_[i] == nullptr) return;
-        if ((close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_TERMINATE) worker_close_[i]->close_now();
-        else worker_close_[i]->close();
-    }
-
-    void controller_master::close_worker_next(int i) {
-        if (i == 0) worker_spawn_count_ = 0;
-        if (i >= gcontroller->worker_size) {
-            // 非重启模式, 所有进程都进行了关闭动作, 统一进行停止超时
-            if((close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_RESTART
-                && (close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_SIGNALING) {
-
-                tm_force_.cancel();
-                tm_force_.expires_after(std::chrono::milliseconds(gcontroller->worker_quit));
-                tm_force_.async_wait([this] (const boost::system::error_code& error) {
-                    if(error) return;
-                    // 切换关闭模式
-                    close_ =  (close_ & CLOSE_FLAG_MASK) | CLOSE_WORKER_TERMINATE;
-                    close_worker_next();
-                });
-            }
-            return;
-        }
-
-        close_worker(i);
-        // 重启模式, 下一个进程的关闭将在本进程结束后开始
-        // 重启模式, 按单个进程设置超时
-        // if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_RESTART
-            // && (close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_SIGNALING) {
-        if (close_ == (CLOSE_FLAG_RESTART | CLOSE_WORKER_SIGNALING)) {
-            tm_force_.cancel();
-            tm_force_.expires_after(std::chrono::milliseconds(gcontroller->worker_quit));
-            tm_force_.async_wait([this, i] (const boost::system::error_code& error) {
-                if(error) return;
-                // 切换关闭模式, 立即结束进程
-                close_ = (close_ & CLOSE_FLAG_MASK) | CLOSE_WORKER_TERMINATE;
-                close_worker(i);
-                // 恢复关闭模式, 本进程结束后继续下一个进程关闭
-                close_ = (close_ & CLOSE_FLAG_MASK) | CLOSE_WORKER_SIGNALING;
-            });
-        }
-        else { // 非重启模式, 继续下一个进程
-            tm_inter_.cancel();
-            tm_inter_.expires_after(std::chrono::milliseconds(125));
-            tm_inter_.async_wait([this, i] (const boost::system::error_code& error) {
-                if (error) return;
-                close_worker_next(i + 1);
-            });
-        }
-    }
-
-    void controller_master::notify_exit(int i, controller_master_worker* w, int exit_code) {
-        // 异常退出
-        if (exit_code != 0 && (close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_MASTER) {
-            int sec = std::rand() % 5 + 1;
-            std::cerr << "[" << system_time() << "] (FATAL) worker unexpected exit: (" << exit_code << "), restart in " << sec << " ...\n";
-            // 少许延迟进行重启
-            auto tm = std::make_shared<boost::asio::steady_timer>(gcontroller->context_x, std::chrono::seconds(sec));
-            tm->async_wait([this, tm, i](const boost::system::error_code &error) {
-                if (error) std::cerr << "[" << system_time()
-                    << "] (FATAL) failed to restart worker: ("
-                    << error.value() << ") " << error.message() << std::endl;
-                else if((close_ & CLOSE_FLAG_MASK) != CLOSE_FLAG_MASTER) spawn_worker(i); // 重启进程
-            });
-        }
-        // 下面容器理论上仅会命中一个
-        if (worker_spawn_[i] == w) worker_spawn_[i] = nullptr;
-        if (worker_close_[i] == w) worker_close_[i] = nullptr;
-        delete w;
-        --worker_count_;
-        // 正常退出
-        if (exit_code == 0 || (close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) {
-            // 所有进程全部关闭, 同时计划关闭主进程
-            if (worker_count_ == 0 && (close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) close();
-            // 重启模式, 关闭了一个进程
-            // if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_RESTART
-            // && (close_ & CLOSE_WORKER_MASK) == CLOSE_WORKER_SIGNALING) {
-            if (close_ == (CLOSE_FLAG_RESTART | CLOSE_WORKER_SIGNALING)) {
-                tm_force_.cancel(); // 取消超时强制
-                spawn_worker(i); // 重启子进程
-                // 继续关闭下一个进程
-                tm_inter_.cancel();
-                tm_inter_.expires_after(std::chrono::milliseconds(125));
-                tm_inter_.async_wait([this, i] (const boost::system::error_code& error) {
-                    if (error) return;
-                    close_worker_next(i + 1);
-                });
-            }
-        }
-    }
-
-    void controller_master::close() {
-        // 强制模式，子进程已经全部被结束了 -> 主进退出
-        gcontroller->context_x.stop();
-    }
-
-    void controller_master::await_signal() {
-        signal_->async_wait([this](const boost::system::error_code &error, int sig) {
-            if (error) return;
-             // 主进程与子进程 signal 处理之间有干扰
-            await_signal();
-            boost::asio::post(gcontroller->context_x, [this, sig]() {
-                if (sig == SIGRTMIN+1) { // 进程重载(主进程保持,逐步重启工作进程)
-                    if (close_ != CLOSE_NOTHING) return;
-                    close_ = CLOSE_FLAG_RESTART | CLOSE_WORKER_SIGNALING;
-                    worker_close_.swap(worker_spawn_); // 准备关闭的进程
-                    close_worker_next(); // 关闭现有进程
-                }
-                else if (sig == SIGUSR2) reload_output(); // 日志重载
-                else if (sig == SIGUSR1) { // 长短连切换
-                    if(gcontroller->status & controller::controller_status::STATUS_CLOSECONN) {
-                        gcontroller->status &= ~controller::controller_status::STATUS_CLOSECONN;
-                        std::cout << "[" << time::iso() << "] [INFO] remove 'Connection: close' header." << std::endl;
-                    }
-                    else {
-                        gcontroller->status |= controller::controller_status::STATUS_CLOSECONN;
-                        std::cout << "[" << time::iso() << "] [INFO] append 'Connection: close' header." << std::endl;
-                    }
-                    for(auto i=worker_spawn_.begin();i!=worker_spawn_.end(); ++i) (*i)->close(SIGUSR1);
-                }
-                else {
-                    if ((close_ & CLOSE_FLAG_MASK) == CLOSE_FLAG_MASTER) {
-                        close_ = CLOSE_FLAG_MASTER | CLOSE_WORKER_TERMINATE;
-                        close_worker_next();
-                    }
-                    else if (close_ == CLOSE_NOTHING) {
-                        close_ = CLOSE_FLAG_MASTER | CLOSE_WORKER_SIGNALING;
-                        worker_close_.swap(worker_spawn_); // 准备关闭的进程
-                        close_worker_next();
-                    }
-                }
-                // signal_.reset();
-            });
-        });
-    }
-    // !!! run 实际上是在 initialize 中执行 (防止实际启用了功能)
-    void controller_master::run() {
-        // 主进程的启动过程:
-        // 1. 新增环境变量及命令行参数, 启动子进程;
-        for(int i=0;i<gcontroller->worker_size;++i) spawn_worker(i);
-        // 2. 监听信号进行日志重载或停止
-        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
-        signal_->add(SIGUSR1); // HTTP / 长短连接切换
-        signal_->add(SIGUSR2); // 日志重载
-        signal_->add(SIGRTMIN+1); // 进程重载
-        await_signal();
-        // 3. 启动运行
-        thread_ = std::thread([this] {
-            gcontroller->context_y.run();
-        });
-        gcontroller->context_x.run();
-        // 4. 等待工作线程结束
-        if (signal_) signal_.reset();
-        thread_.join();
-    }
-
-    void controller_master::reload_output() {
-        if (ofpath_[0] == '/') {
-            offile_.reset(new std::ofstream(ofpath_, std::ios_base::out | std::ios_base::app));
-            // 文件打开失败时不会抛出异常，需要额外的状态检查
-            if (!(*offile_))
-                std::cerr << "(ERROR) failed to create/open logger target file: fallback to standard output\n";
-            else return;
-        }
-        offile_.reset(&std::clog, boost::null_deleter());
-    }
-    // 住进成没有进行 time 明明空间相关的初始化，系统时间需要额外手段获取
-    const char* controller_master::system_time() {
-        static char buffer[24] = {0};
-        std::time_t t = std::time(nullptr);
-        struct tm *m = std::localtime(&t);
-        sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d",
-                1900 + m->tm_year,
-                1 + m->tm_mon,
-                m->tm_mday,
-                m->tm_hour,
-                m->tm_min,
-                m->tm_sec);
-        return buffer;
-    }
-
-}
diff --git a/src/controller_master.h b/src/controller_master.h
deleted file mode 100644
index 51ef134..0000000
--- a/src/controller_master.h
+++ /dev/null
@@ -1,66 +0,0 @@
-#pragma once
-#include "vendor.h"
-
-namespace flame {
-
-    class controller_master;
-    class controller_master_worker {
-    public:
-        controller_master_worker(controller_master* m, int i);
-        void close(int sig = SIGTERM);
-        void close_now();
-    private:
-        controller_master*            m_;
-        int                           i_;
-        boost::process::child      proc_;
-        boost::process::async_pipe sout_;
-        boost::process::async_pipe eout_;
-        boost::asio::streambuf     sbuf_;
-        boost::asio::streambuf     ebuf_;
-        void redirect_output(boost::process::async_pipe& pipe, boost::asio::streambuf& buffer);
-    };
-
-    class controller_master {
-    public:
-        controller_master();
-        void initialize(const php::array& options);
-        void run();
-    private:
-        std::vector<controller_master_worker*>   worker_spawn_;
-        std::vector<controller_master_worker*>   worker_close_;
-        int                                      worker_spawn_count_;
-        int                                      worker_count_;
-        std::unique_ptr<boost::asio::signal_set> signal_;
-        boost::asio::steady_timer tm_inter_;
-        boost::asio::steady_timer tm_force_;
-        std::thread                        thread_;
-
-        std::string                        ofpath_;
-        std::shared_ptr<std::ostream>      offile_;
-
-        void spawn_worker(int i);
-        void spawn_worker_next(int i = 0);
-        void notify_exit(int i, controller_master_worker* w, int exit_code);
-        enum {
-            CLOSE_NOTHING,
-            CLOSE_WORKER_SIGNALING = 0x0001, // 信号通知 + 超时
-            CLOSE_WORKER_TERMINATE = 0x0002, // 立即停止
-            CLOSE_WORKER_MASK      = 0x000f,
-            CLOSE_FLAG_RESTART     = 0x0010, // 重启
-            CLOSE_FLAG_MASTER      = 0x0020, // 停止主进程
-            CLOSE_FLAG_MASK        = 0x00f0,
-        };
-        int close_ = CLOSE_NOTHING;
-        void close_worker(int i);
-        void close_worker_next(int i = 0);
-        void close();
-
-        void reload_worker();
-        void reload_output();
-
-        void await_signal();
-
-        const char* system_time();
-        friend class controller_master_worker;
-    };
-}
diff --git a/src/controller_worker.cpp b/src/controller_worker.cpp
deleted file mode 100644
index 7f06207..0000000
--- a/src/controller_worker.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-#include "coroutine.h"
-#include "controller_worker.h"
-#include "controller.h"
-
-namespace flame {
-    
-    controller_worker::controller_worker() {
-
-    }
-
-    void controller_worker::initialize(const php::array &options) {
-
-    }
-
-    void controller_worker::await_signal() {
-        signal_->async_wait([this] (const boost::system::error_code &error, int sig) {
-            // 主进程与子进程 signal 处理之间有干扰
-            if (error) return; 
-            if (gcontroller->worker_size == 0) signal_.reset(); // 单进程模式
-            else await_signal(); // 多进程模式的工作进程需要持续监听
-            
-            if(sig == SIGTERM || sig == SIGINT) { // 工作进程仅处理退出信号
-                boost::asio::post(gcontroller->context_x, [this] () {
-                    gcontroller->status |= controller::controller_status::STATUS_SHUTDOWN;
-                    auto ft = gcontroller->cbmap->equal_range("quit"); // 需要在协程调用退出回调
-                    for (auto i = ft.first; i != ft.second; ++i) coroutine::start(i->second);
-                });
-            }
-            else if(sig == SIGUSR1) {
-                if(gcontroller->status & controller::controller_status::STATUS_CLOSECONN) 
-                    gcontroller->status &= ~controller::controller_status::STATUS_CLOSECONN;
-                else
-                    gcontroller->status |= controller::controller_status::STATUS_CLOSECONN;
-            }
-        });
-    }
-
-    void controller_worker::run() {
-        // 工作线程的使用是随机的, 需要保持其一直存在
-        auto work = boost::asio::make_work_guard(gcontroller->context_y);
-        // 1. 停止信号调用退出通知
-        signal_.reset(new boost::asio::signal_set(gcontroller->context_y, SIGINT, SIGTERM));
-        signal_->add(SIGUSR1);
-        signal_->add(SIGUSR2);
-        await_signal();
-
-        // 子进程的启动过程:
-        // 2. 启动线程池, 并使用线程池运行 context_y
-        thread_.resize(4);
-        for (int i = 0; i < thread_.size(); ++i) {
-            thread_[i] = new std::thread([this] {
-                gcontroller->context_y.run();
-            });
-        }
-        // 3. 启动 context_x 运行
-        gcontroller->context_x.run();
-        // 4. 确认工作线程停止
-        if (signal_) signal_.reset();
-        work.reset();
-        for (int i = 0; i < thread_.size(); ++i) {
-            thread_[i]->join();
-            delete thread_[i];
-        }
-    }
-}
diff --git a/src/controller_worker.h b/src/controller_worker.h
deleted file mode 100644
index e41f918..0000000
--- a/src/controller_worker.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-#include "vendor.h"
-
-namespace flame {
-    class controller_worker {
-    public:
-        controller_worker();
-        void initialize(const php::array& options);
-        void run();
-    private:
-        std::vector<std::thread*> thread_;
-        std::unique_ptr<boost::asio::signal_set> signal_;
-
-        void await_signal();
-    };
-}
\ No newline at end of file
diff --git a/src/core.cpp b/src/core.cpp
deleted file mode 100644
index 622a096..0000000
--- a/src/core.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#include "controller.h"
-#include "coroutine.h"
-#include "core.h"
-#include "coroutine_queue.h"
-#include "queue.h"
-#include "mutex.h"
-
-namespace flame::core {
-    php::value select(php::parameters& params) {
-        std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
-        std::map< std::shared_ptr<coroutine_queue<php::value>>, php::object > mm;
-        for (auto i = 0; i < params.size(); ++i) {
-            if (!params[i].instanceof(php::class_entry<queue>::entry())) throw php::exception(zend_ce_type_error
-                , "Failed to select: instanceof flame\\queue required"
-                , -1);
-            php::object obj = params[i];
-            queue* ptr = static_cast<queue*>(php::native(obj));
-            qs.push_back( ptr->q_ );
-            mm.insert({ptr->q_, obj});
-        }
-        coroutine_handler ch{coroutine::current};
-        std::shared_ptr<coroutine_queue<php::value>> q = select_queue(qs, ch);
-        return mm[q];
-    }
-
-    php::value co_id(php::parameters& params) {
-        return reinterpret_cast<uintptr_t>(coroutine::current.get());
-    }
-
-    php::value co_count(php::parameters& params) {
-        return coroutine::count;
-    }
-
-    void declare(php::extension_entry &ext) {
-        ext
-            .on_request_shutdown([] (php::extension_entry& ext) -> bool {
-                if (coroutine::count > 0 && (gcontroller->status & controller::controller_status::STATUS_RUN) == 0) {
-                    if (!php::error::exists()) std::cerr << "[FATAL] process exited prematurely: exception or missing 'flame\\run();' ?\n";
-                    _exit(-1);
-                }
-                return true;
-            })
-            .function<select>("flame\\select")
-            .function<co_id>("flame\\co_id")
-            .function<co_count>("flame\\co_count");
-
-        queue::declare(ext);
-        mutex::declare(ext);
-        guard::declare(ext);
-    }
-
-}
diff --git a/src/core.h b/src/core.h
deleted file mode 100644
index deb06bc..0000000
--- a/src/core.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-#include "vendor.h"
-
-namespace flame::core {
-    void declare(php::extension_entry& ext);
-    php::value select(php::parameters &params);
-}
\ No newline at end of file
diff --git a/src/coroutine.cpp b/src/coroutine.cpp
deleted file mode 100644
index d8af867..0000000
--- a/src/coroutine.cpp
+++ /dev/null
@@ -1,189 +0,0 @@
-#include "controller.h"
-#include "coroutine.h"
-
-namespace flame {
-
-    boost::context::fixedsize_stack coroutine::stack_allocator(64 * 1024);
-
-    std::size_t coroutine::count = 0;
-
-    coroutine::php_context_t coroutine::global_context;
-    // 当前协程
-    std::shared_ptr<coroutine> coroutine::current(nullptr);
-
-    void coroutine::save_context(php_context_t &ctx) {
-        ctx.vm_stack = EG(vm_stack);
-        ctx.vm_stack_top = EG(vm_stack_top);
-        ctx.vm_stack_end = EG(vm_stack_end);
-        //   ctx.scope = EG(fake_scope);
-        ctx.current_execute_data = EG(current_execute_data);
-        ctx.exception = EG(exception);
-        ctx.exception_class = EG(exception_class);
-        ctx.error_handling = EG(error_handling);
-    }
-
-    void coroutine::restore_context(php_context_t &ctx) {
-        EG(vm_stack) = ctx.vm_stack;
-        EG(vm_stack_top) = ctx.vm_stack_top;
-        EG(vm_stack_end) = ctx.vm_stack_end;
-        // EG(fake_scope) = ctx.scope;
-        EG(current_execute_data) = ctx.current_execute_data;
-        EG(exception) = ctx.exception;
-        EG(exception_class) = ctx.exception_class;
-        EG(error_handling) = ctx.error_handling;
-    }
-    // 参考 zend_execute.c 
-    static zend_vm_stack zend_vm_stack_new_page(size_t size, zend_vm_stack prev) {
-        zend_vm_stack page = (zend_vm_stack)emalloc(size);
-
-        page->top = ZEND_VM_STACK_ELEMENTS(page);
-        page->end = (zval *)((char *)page + size);
-        page->prev = prev;
-        return page;
-    }
-    // 参考 zend_execute.c
-    static void zend_vm_stack_init(void) {
-        EG(vm_stack) = zend_vm_stack_new_page(4 * sizeof(zval) * 1024, NULL);
-        EG(vm_stack)->top++;
-        EG(vm_stack_top) = EG(vm_stack)->top;
-        EG(vm_stack_end) = EG(vm_stack)->end;
-    }
-
-    std::shared_ptr<coroutine> coroutine::start(php::callable fn) {
-        ++coroutine::count;
-        auto co_ = std::make_shared<coroutine>(fn);
-        // 事实上的协程启动会稍后
-        boost::asio::post(gcontroller->context_x, [co_ /*, ag = std::move(ag)*/] {
-            co_->c1_ = boost::context::fiber(
-                std::allocator_arg, coroutine::stack_allocator,
-                [co_ /*, ag = std::move(ag)*/](boost::context::fiber &&cc) {
-
-                co_->c2_ = std::move(cc);
-                auto work = boost::asio::make_work_guard(gcontroller->context_x);
-
-                save_context(coroutine::global_context);
-                // 启动进入协程
-                coroutine::current = co_;
-
-                EG(current_execute_data) = nullptr;
-                EG(error_handling) = EH_NORMAL;
-                EG(exception_class) = nullptr;
-                EG(exception) = nullptr;
-                zend_vm_stack_init();
-                // 协程运行;
-                co_->fn_.call(/*ag*/);
-                // 实际协程销毁可能会较晚, 保证在 Zend 引擎前释放
-                co_->fn_ = nullptr;
-                // 协程运行完毕
-                zend_vm_stack_destroy();
-                coroutine::current = nullptr;
-                
-                restore_context(coroutine::global_context);
-                boost::asio::post(gcontroller->context_x, [co_]() {
-                    co_->c1_ = boost::context::fiber();
-                    if (--coroutine::count == 0) gcontroller->stop(); // 所有协程结束后退出
-                });
-                return std::move(co_->c2_);
-            });
-
-            co_->c1_ = std::move(co_->c1_).resume();
-        });
-        return co_;
-    }
-
-    coroutine::coroutine(php::callable fn)
-    : fn_(std::move(fn)), c1_(), c2_() {
-        // std::cout << "coroutine\n";
-    }
-
-    coroutine::~coroutine() {
-        // std::cout << "~coroutine\n";
-    }
-
-    void coroutine::suspend() {
-        // 保存 PHP 堆栈
-        coroutine::save_context(php_);
-        coroutine::restore_context(coroutine::global_context);
-        // 离开协程
-        coroutine::current = nullptr;
-        c2_ = std::move(c2_).resume();
-    }
-
-    void coroutine::resume() {
-        // 恢复 PHP 堆栈
-        coroutine::save_context(coroutine::global_context);
-        coroutine::restore_context(php_);
-        // 恢复进入协程
-        coroutine::current = shared_from_this();
-        c1_ = std::move(c1_).resume();
-    }
-    coroutine_handler::coroutine_handler()
-    : co_(nullptr)
-    , err_(nullptr)
-    , stat_(new std::atomic<int>(0)) {
-    }
-
-    coroutine_handler::coroutine_handler(std::shared_ptr<coroutine> co)
-    : co_(co)
-    , err_(nullptr)
-    , stat_(new std::atomic<int>(0)) {
-    }
-
-    // coroutine_handler::coroutine_handler(coroutine_handler&& ch)
-    // : co_(std::move(ch.co_))
-    // , err_(ch.err_)
-    // , stat_(std::move(ch.stat_)) {
-    //     ch.err_ = nullptr;
-    // }
-
-    coroutine_handler::~coroutine_handler() {
-        // std::cout << "~coroutine_handler\n";
-    }
-
-    void coroutine_handler::reset() {
-        co_.reset();
-        *stat_ = 0;
-    }
-
-    void coroutine_handler::reset(std::shared_ptr<coroutine> co) {
-        assert(co_ == nullptr);
-        co_   = co;
-        *stat_ = 0;
-    }
-
-    coroutine_handler::operator bool() const {
-        return co_ != nullptr;
-    }
-
-    void coroutine_handler::operator() (const boost::system::error_code &e, std::size_t n) {
-        if (err_) *err_ = e;
-        co_->len_ = n;
-        resume();
-    }
-
-    void coroutine_handler::operator() (const boost::system::error_code &e) {
-        if (err_) *err_ = e;
-        // co_->len_ = 0;
-        resume();
-    }
-
-    coroutine_handler& coroutine_handler::operator [](boost::system::error_code& e) {
-        err_ = &e;
-        return *this;
-    }
-
-    void coroutine_handler::resume() {
-        if (!co_) throw php::exception(zend_ce_parse_error, "Failed to resume coroutine, missing 'flame\\go()' ?", 0);
-        if (--*stat_ == 0) boost::asio::post(gcontroller->context_x, std::bind(&coroutine::resume, co_));
-    }
-
-    void coroutine_handler::suspend() {
-        assert(std::this_thread::get_id() == gcontroller->mthread_id);
-        if (!co_) throw php::exception(zend_ce_parse_error, "Failed to suspend coroutine, missing 'flame\\go()' ?", 0);
-        if (++*stat_ == 1) co_->suspend();
-    }
-    
-    bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2) {
-        return ch1.co_ < ch2.co_;
-    }
-}
diff --git a/src/coroutine.h b/src/coroutine.h
index 7ca57fb..d6bc770 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -1,100 +1,137 @@
 #pragma once
 #include "vendor.h"
 
-namespace flame {
+class coroutine: public std::enable_shared_from_this<coroutine> {
+public:
+    coroutine(boost::asio::executor ex)
+    : ex_(ex) {
+        
+    }
+    virtual ~coroutine() {
 
-    class coroutine : public std::enable_shared_from_this<coroutine> {
-    public:
-        struct php_context_t {
-            zend_vm_stack vm_stack;
-            zval *vm_stack_top;
-            zval *vm_stack_end;
-            zend_class_entry *scope;
-            zend_execute_data *current_execute_data;
+    }
+    // static std::shared_ptr<coroutine> current;
+    template <class Handler>
+    static void start(boost::asio::executor ex, Handler&& fn) {
+        auto co = std::make_shared<coroutine>(ex);
+        
+        boost::asio::post(co->ex_, [co, fn] () mutable {
+            co->c1_ = boost::context::fiber([co, gd = boost::asio::make_work_guard(co->ex_), fn] (boost::context::fiber&& c2) mutable {
+                co->c2_ = std::move(c2);
+                fn({co});
+                return std::move(co->c2_);
+            });
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+    virtual void suspend() {
+        c2_ = std::move(c2_).resume();
+    }
+    virtual void resume() {
+        boost::asio::post(ex_, [co = shared_from_this()] () {
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+protected:
+    boost::context::fiber c1_;
+    boost::context::fiber c2_;
+    boost::asio::executor ex_;
 
-            zend_object *         exception;
-            zend_error_handling_t error_handling;
-            zend_class_entry *    exception_class;
-        };
-        //
-        static boost::context::fixedsize_stack stack_allocator;
-        static std::size_t count;
-        // 当前协程
-        static std::shared_ptr<coroutine> current;
-        static void save_context(php_context_t &ctx);
-        static void restore_context(php_context_t& ctx);
-        static php_context_t global_context;
-        static std::shared_ptr<coroutine> start(php::callable fn);
-        coroutine(php::callable fn);
-        ~coroutine();
+    friend class coroutine_handler;
+};
 
-        void suspend();
-        void resume();
-        php::callable fn_;
-        // boost::context::continuation c1_;
-        // boost::context::continuation c2_;
-        boost::context::fiber c1_;
-        boost::context::fiber c2_;
-        php_context_t php_;
-        // 用于部分异步请求返回长度信息
-        std::size_t len_;
-        
-    };
+class coroutine_handler {
+public:
+    coroutine_handler() = default;
+    coroutine_handler(std::shared_ptr<coroutine> co)
+    : co_(co) { }
+    coroutine_handler(const coroutine_handler& ch) = default;
+    virtual ~coroutine_handler() {
 
-    struct coroutine_handler {
-    public:
-        coroutine_handler();
-        coroutine_handler(std::shared_ptr<coroutine> co);
-        ~coroutine_handler();
-        // !!! 慎用: 目前仅用于空构造后指定协程
-        void reset(std::shared_ptr<coroutine> co);
-        void reset();
-        void resume();
-        void suspend();
+    }
+    coroutine_handler& operator[](boost::system::error_code& error) {
+        error_ = &error;
+        return *this;
+    }
+    coroutine_handler& operator[](std::size_t& size) {
+        size_ = &size;
+        return *this;
+    }
+    
+    inline void operator()(const boost::system::error_code& error, std::size_t size = 0) {
+        if(error_) *error_ = error;
+        if(size_) *size_ = size;
+        co_->resume();
+    }
 
-        operator bool() const;
-        void operator()(const boost::system::error_code& e, std::size_t n);
-        void operator()(const boost::system::error_code& e);
-        coroutine_handler& operator [](boost::system::error_code& e);
-        
-        boost::system::error_code* err_;
-        std::shared_ptr<coroutine> co_;
-        std::shared_ptr<std::atomic<int>> stat_;
-        friend bool operator<(const coroutine_handler &ch1, const coroutine_handler &ch2);
-      private:
-        
-    };
-} // namespace flame
+    inline bool operator !() {
+        return co_ == nullptr;
+    }
+    
+    inline operator bool() {
+        return co_ != nullptr;
+    }
+
+    void reset() {
+        co_.reset();
+        size_ = nullptr;
+        error_= nullptr;
+    }
+    void reset(std::shared_ptr<coroutine> co) {
+        co_ = co;
+        size_ = nullptr;
+        error_= nullptr;
+    }
+    void reset(coroutine_handler& ch) {
+        co_ = ch.co_;
+        size_ = nullptr;
+        error_= nullptr;
+    }
+
+    inline void suspend() {
+        co_->suspend();
+    }
+    inline void resume() {
+        co_->resume();
+    }
+public:
+    std::size_t*                size_ = nullptr;
+    boost::system::error_code* error_ = nullptr;
+    std::shared_ptr<coroutine>    co_;
+
+private:
+
+};
 
 namespace boost::asio {
     template <>
-    class async_result<::flame::coroutine_handler, void (boost::system::error_code error, std::size_t size)> {
+    class async_result<::coroutine_handler, void (boost::system::error_code error, std::size_t size)> {
     public:
-        explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
+        explicit async_result(::coroutine_handler& ch) : ch_(ch), size_(0) {
+            ch_.size_ = &size_;
         }
-        using completion_handler_type = ::flame::coroutine_handler;
+        using completion_handler_type = ::coroutine_handler;
         using return_type = std::size_t;
         return_type get() {
             ch_.suspend();
-            return ch_.co_->len_;
+            return size_;
         }
     private:
-        ::flame::coroutine_handler &ch_;
+        ::coroutine_handler &ch_;
+        std::size_t size_;
     };
 
     template <>
-    class async_result<::flame::coroutine_handler, void (boost::system::error_code error)> {
+    class async_result<::coroutine_handler, void (boost::system::error_code error)> {
     public:
-        explicit async_result(::flame::coroutine_handler& ch) : ch_(ch) {
+        explicit async_result(::coroutine_handler& ch) : ch_(ch) {
         }
-        using completion_handler_type = ::flame::coroutine_handler;
+        using completion_handler_type = ::coroutine_handler;
         using return_type = void;
         void get() {
             ch_.suspend();
         }
     private:
-        ::flame::coroutine_handler &ch_;
+        ::coroutine_handler &ch_;
     };
-
-    
 } // namespace boost::asio
diff --git a/src/coroutine_mutex.h b/src/coroutine_mutex.h
index bce30ee..7948f0c 100644
--- a/src/coroutine_mutex.h
+++ b/src/coroutine_mutex.h
@@ -1,51 +1,49 @@
 #pragma once
 #include "coroutine.h"
 
-namespace flame {
-    class coroutine_mutex {
-    public:
-        coroutine_mutex()
-        : mt_(false) {
+class coroutine_mutex {
+public:
+    coroutine_mutex()
+    : mt_(false) {
 
-        }
+    }
 
-        void lock(coroutine_handler& ch) {  
-            while(mt_) {
-                cm_.insert(&ch);
-                ch.suspend();
-            }
-            mt_ = true;
+    void lock(coroutine_handler& ch) {  
+        while(mt_) {
+            cm_.insert(&ch);
+            ch.suspend();
         }
+        mt_ = true;
+    }
 
-        bool try_lock() {
-            if (!mt_) {
-                mt_ = true;
-                return true;
-            }
-            return false;
+    bool try_lock() {
+        if (!mt_) {
+            mt_ = true;
+            return true;
         }
+        return false;
+    }
 
-        void unlock() {
-            while(!cm_.empty()) {
-                auto ch = cm_.extract(cm_.begin()).value();
-                ch->resume();
-            }
-            mt_ = false;
-        }
-    private:
-        std::set<coroutine_handler*> cm_;
-        bool mt_;
-    };
-    class coroutine_guard {
-    public:
-        coroutine_guard(coroutine_mutex& cm, coroutine_handler& ch)
-        : cm_(cm) {
-            cm_.lock(ch);
-        }
-        ~coroutine_guard() {
-            cm_.unlock();
+    void unlock() {
+        while(!cm_.empty()) {
+            auto ch = cm_.extract(cm_.begin()).value();
+            ch->resume();
         }
-    private:
-        coroutine_mutex   cm_;
-    };
-}
\ No newline at end of file
+        mt_ = false;
+    }
+private:
+    std::set<coroutine_handler*> cm_;
+    bool mt_;
+};
+class coroutine_guard {
+public:
+    coroutine_guard(coroutine_mutex& cm, coroutine_handler& ch)
+    : cm_(cm) {
+        cm_.lock(ch);
+    }
+    ~coroutine_guard() {
+        cm_.unlock();
+    }
+private:
+    coroutine_mutex   cm_;
+};
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
index e82ac70..b6deb0c 100644
--- a/src/coroutine_queue.h
+++ b/src/coroutine_queue.h
@@ -1,81 +1,79 @@
 #pragma once
 #include "coroutine.h"
 
-namespace flame {
-    template <class T>
-    class coroutine_queue: private boost::noncopyable {
-    public:
-        coroutine_queue(std::size_t n = 1)
-        : n_(n)
-        , closed_(false) {
+template <class T>
+class coroutine_queue: private boost::noncopyable {
+public:
+    coroutine_queue(std::size_t n = 1)
+    : n_(n)
+    , closed_(false) {
+    }
+
+    // !!!! 生产者进行关闭 !!!!
+    void close() {
+        closed_ = true;
+        while (!c_.empty()) {
+            auto ch = c_.extract(c_.begin()).value();
+            ch->resume();
         }
+    }
+    bool is_closed() {
+        return q_.empty() && closed_;
+    }
 
-        // !!!! 生产者进行关闭 !!!!
-        void close() {
-            closed_ = true;
-            while (!c_.empty()) {
-                auto ch = c_.extract(c_.begin()).value();
-                ch->resume();
-            }
+    void push(const T& t, coroutine_handler& ch) {
+        if (closed_) throw php::exception(zend_ce_error_exception
+            , "Failed to push queue: already closed"
+            , -1);
+        if (q_.size() >= n_) {
+            p_.insert(&ch);
+            ch.suspend();
         }
-        bool is_closed() {
-            return q_.empty() && closed_;
+        q_.push_back(t);
+        while(!q_.empty() && !c_.empty()) {
+            auto ch = c_.extract(c_.begin()).value();
+            ch->resume();
         }
-
-        void push(const T& t, coroutine_handler& ch) {
-            if (closed_) throw php::exception(zend_ce_error_exception
-                , "Failed to push queue: already closed"
-                , -1);
-            if (q_.size() >= n_) {
-                p_.insert(&ch);
+    }
+    std::optional<T> pop(coroutine_handler& ch) {
+        for(;;) {
+            if (!q_.empty()) break; // 有数据消费
+            else if (closed_) return std::optional<T>();  // 无数据关闭
+            else { // 无数据等待
+                c_.insert(&ch);
                 ch.suspend();
             }
-            q_.push_back(t);
-            while(!q_.empty() && !c_.empty()) {
-                auto ch = c_.extract(c_.begin()).value();
-                ch->resume();
-            }
         }
-        std::optional<T> pop(coroutine_handler& ch) {
-            for(;;) {
-                if (!q_.empty()) break; // 有数据消费
-                else if (closed_) return std::optional<T>();  // 无数据关闭
-                else { // 无数据等待
-                    c_.insert(&ch);
-                    ch.suspend();
-                }
-            }
-            T t = q_.front();
-            q_.pop_front();
+        T t = q_.front();
+        q_.pop_front();
 
-            while (!p_.empty()) {
-                auto ch = p_.extract(p_.begin()).value();
-                ch->resume();
-            }
-            return std::optional<T>(t);
+        while (!p_.empty()) {
+            auto ch = p_.extract(p_.begin()).value();
+            ch->resume();
         }
+        return std::optional<T>(t);
+    }
 
-    /* private: */
-        std::size_t                 n_;
-        std::list<T>                q_;
-        std::set<coroutine_handler*> c_; // 消费者
-        std::set<coroutine_handler*> p_; // 生产者
-        bool                   closed_;
-    };
+/* private: */
+    std::size_t                 n_;
+    std::list<T>                q_;
+    std::set<coroutine_handler*> c_; // 消费者
+    std::set<coroutine_handler*> p_; // 生产者
+    bool                   closed_;
+};
 
-    template <class T>
-    std::shared_ptr < coroutine_queue<T> > select_queue(std::vector < std::shared_ptr<coroutine_queue<T>> > queues, coroutine_handler &ch) {
+template <class T>
+std::shared_ptr < coroutine_queue<T> > select_queue(std::vector < std::shared_ptr<coroutine_queue<T>> > queues, coroutine_handler &ch) {
 TRY_ALL:
-        bool all_closed = true;
-        for(auto i=queues.begin();i!=queues.end();++i) {
-            if (!(*i)->q_.empty()) return *i;
-            else if (!(*i)->closed_) all_closed = false;
-        }
-        if (all_closed) return std::shared_ptr< coroutine_queue<T> >(nullptr);
-        for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(&ch);
-        ch.suspend();
-        for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(&ch);
-        goto TRY_ALL;
+    bool all_closed = true;
+    for(auto i=queues.begin();i!=queues.end();++i) {
+        if (!(*i)->q_.empty()) return *i;
+        else if (!(*i)->closed_) all_closed = false;
     }
-
+    if (all_closed) return std::shared_ptr< coroutine_queue<T> >(nullptr);
+    for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(&ch);
+    ch.suspend();
+    for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(&ch);
+    goto TRY_ALL;
 }
+
diff --git a/src/flame.cpp b/src/flame.cpp
deleted file mode 100644
index 0a4205f..0000000
--- a/src/flame.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-#include "vendor.h"
-#include "controller.h"
-#include "controller_master.h"
-#include "controller_worker.h"
-#include "coroutine.h"
-#include "core.h"
-#include "log/log.h"
-#include "os/os.h"
-#include "time/time.h"
-#include "mysql/mysql.h"
-#include "redis/redis.h"
-#include "mongodb/mongodb.h"
-#include "kafka/kafka.h"
-#include "rabbitmq/rabbitmq.h"
-#include "tcp/tcp.h"
-#include "udp/udp.h"
-#include "http/http.h"
-#include "hash/hash.h"
-#include "encoding/encoding.h"
-#include "compress/compress.h"
-#include "toml/toml.h"
-#include <boost/version.hpp>
-#include <nghttp2/nghttp2ver.h>
-
-namespace flame {
-    static php::value init(php::parameters& params) {
-        php::array options(0);
-        if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
-        gcontroller->initialize(params[0], options);
-        return nullptr;
-    }
-
-    static php::value go(php::parameters& params) {
-        php::callable fn = params[0];
-        coroutine::start(php::callable([fn] (php::parameters& params) -> php::value {
-            php::value rv;
-            try {
-                rv = fn.call();
-            } catch (const php::exception &ex) {
-                auto ft = gcontroller->cbmap->equal_range("exception");
-                for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
-                php::object obj = ex;
-                std::cerr << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
-
-                boost::asio::post(gcontroller->context_x, [] () {
-                    gcontroller->status |= controller::controller_status::STATUS_EXCEPTION;
-                    gcontroller->context_x.stop();
-                    gcontroller->context_y.stop();
-                });
-            }
-            return rv;
-        }));
-        return nullptr;
-    }
-
-    static php::value fake_fn(php::parameters& params) {
-        return nullptr;
-    }
-
-    static php::value on(php::parameters &params) {
-        std::string event = params[0].to_string();
-        if (!params[1].type_of(php::TYPE::CALLABLE)) throw php::exception(zend_ce_type_error
-            , "Failed to set callback: callable required"
-            , -1);
-        gcontroller->cbmap->insert({event, params[1]});
-        return nullptr;
-    }
-
-    static php::value run(php::parameters& params) {
-        if (gcontroller->status & controller::STATUS_INITIALIZED) {
-            gcontroller->default_execute_data = EG(current_execute_data);
-            gcontroller->run();
-        }
-        else throw php::exception(zend_ce_parse_error
-            , "Failed to run flame: exception or missing 'flame\\init()' ?"
-            , -1);
-        return nullptr;
-    }
-
-    static php::value quit(php::parameters& params) {
-        gcontroller->context_x.stop();
-        gcontroller->context_y.stop();
-        return nullptr;
-    }
-
-    static php::value get(php::parameters& params) {
-        php::array  target = params[0];
-        php::string fields = params[1];
-        return flame::toml::get(target, { fields.data(), fields.size() }, 0);
-    }
-
-    static php::value set(php::parameters& params) {
-        php::array  target = params.get(0, true);
-        php::string fields = params[1];
-        php::value  values = params[2];
-        flame::toml::set(target, {fields.data(), fields.size()}, 0, values);
-        return nullptr;
-    }
-}
-
-static std::string openssl_version_str() {
-    std::string version = (boost::format("%d.%d.%d")
-        % ((OPENSSL_VERSION_NUMBER & 0xff0000000L) >> 28)
-        % ((OPENSSL_VERSION_NUMBER & 0x00ff00000L) >> 20)
-        % ((OPENSSL_VERSION_NUMBER & 0x0000ff000L) >> 12) ).str();
-    char status = (OPENSSL_VERSION_NUMBER & 0x000000ff0L) >>  4;
-    if (status > 0) {
-        version.push_back('a' + status - 1);
-    }
-    return version;
-}
-#define VERSION_MACRO(major, minor, patch) VERSION_JOIN(major, minor, patch)
-#define VERSION_JOIN(major, minor, patch) #major"."#minor"."#patch
-
-extern "C" {
-    ZEND_DLEXPORT zend_module_entry *get_module()  {
-        static php::extension_entry ext(EXTENSION_NAME, EXTENSION_VERSION);
-        std::string sapi = php::constant("PHP_SAPI");
-        if (sapi != "cli") {
-            std::cerr << "[WARNING] FLAME disabled: SAPI='cli' mode only\n";
-            return ext;
-        }
-
-        php::class_entry<php::closure> class_closure("flame\\closure");
-        class_closure.method<&php::closure::__invoke>("__invoke");
-        ext.add(std::move(class_closure));
-
-        ext
-            .desc({"vendor/openssl", openssl_version_str()})
-            .desc({"vendor/boost", BOOST_LIB_VERSION})
-            .desc({"vendor/phpext", PHPEXT_LIB_VERSION})
-            .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
-            // .desc({"vendor/mysqlc", mysql_get_client_info()})
-            .desc({"vendor/mariac", MARIADB_PACKAGE_VERSION})
-            .desc({"vendor/amqpcpp", "4.1.5"})
-            .desc({"vendor/rdkafka", rd_kafka_version_str()})
-            .desc({"vendor/mongoc", MONGOC_VERSION_S})
-            .desc({"vendor/nghttp2", NGHTTP2_VERSION})
-            .desc({"vendor/curl", LIBCURL_VERSION});
-
-        flame::gcontroller.reset(new flame::controller());
-        ext
-            .function<flame::init>("flame\\init", {
-                {"process_name", php::TYPE::STRING},
-                {"options", php::TYPE::ARRAY, false, true},
-            })
-            .function<flame::run>("flame\\run");
-        if (flame::gcontroller->type == flame::controller::process_type::WORKER) {
-            ext
-            .function<flame::go>("flame\\go", {
-                {"coroutine", php::TYPE::CALLABLE},
-            })
-            .function<flame::on>("flame\\on", {
-                {"event", php::TYPE::STRING},
-                {"callback", php::TYPE::CALLABLE},
-            })
-            .function<flame::quit>("flame\\quit")
-            .function<flame::set>("flame\\set", {
-                {"target", php::TYPE::ARRAY, true},
-                {"fields", php::TYPE::STRING},
-                {"values", php::TYPE::UNDEFINED},
-            })
-            .function<flame::get>("flame\\get", {
-                {"target", php::TYPE::ARRAY},
-                {"fields", php::TYPE::STRING},
-            });
-
-            flame::core::declare(ext);
-            flame::log::declare(ext);
-            flame::os::declare(ext);
-            flame::time::declare(ext);
-            flame::mysql::declare(ext);
-            flame::redis::declare(ext);
-            flame::mongodb::declare(ext);
-            flame::kafka::declare(ext);
-            flame::rabbitmq::declare(ext);
-            flame::tcp::declare(ext);
-            flame::udp::declare(ext);
-            flame::http::declare(ext);
-            flame::hash::declare(ext);
-            flame::encoding::declare(ext);
-            flame::compress::declare(ext);
-            flame::toml::declare(ext);
-        }
-        else {
-            ext
-                .function<flame::fake_fn>("flame\\go")
-                .function<flame::fake_fn>("flame\\on");
-        }
-        return ext;
-    }
-};
diff --git a/src/compress/compress.cpp b/src/flame/compress/compress.cpp
similarity index 100%
rename from src/compress/compress.cpp
rename to src/flame/compress/compress.cpp
diff --git a/src/compress/compress.h b/src/flame/compress/compress.h
similarity index 81%
rename from src/compress/compress.h
rename to src/flame/compress/compress.h
index f10fe17..480e4cb 100644
--- a/src/compress/compress.h
+++ b/src/flame/compress/compress.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::compress {
     void declare(php::extension_entry &ext);
diff --git a/src/flame/controller.cpp b/src/flame/controller.cpp
new file mode 100644
index 0000000..562711a
--- /dev/null
+++ b/src/flame/controller.cpp
@@ -0,0 +1,55 @@
+#include "controller.h"
+
+// 全局控制器
+std::unique_ptr<controller> gcontroller;
+
+controller::controller()
+: type(process_type::UNKNOWN)
+, env(boost::this_process::environment())
+, status(STATUS_UNKNOWN)
+, user_cb(new std::multimap<std::string, php::callable>()) {
+
+    worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
+    worker_size = std::min(std::max((int)worker_size, 1), 256);
+    // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
+    if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
+    else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
+    else { // 单进程模式
+        worker_size = 0;
+        type = process_type::WORKER;
+    }
+    mthread_id = std::this_thread::get_id();
+}
+
+controller *controller::on_init(std::function<void (const php::array &options)> fn) {
+    init_cb.push_back(fn);
+    return this;
+}
+
+controller* controller::on_stop(std::function<void ()> fn) {
+    stop_cb.push_back(fn);
+    return this;
+}
+
+controller* controller::on_user(const std::string& event, php::callable cb) {
+    user_cb->insert({event, cb});
+    return this;
+}
+
+void controller::init(php::array options) {
+    for (auto fn : init_cb) fn(options);
+}
+void controller::stop() {
+    for (auto fn : stop_cb) fn();
+    delete user_cb;
+}
+
+void controller::call_user_cb(const std::string& event, std::vector<php::value> params) {
+    auto ft = user_cb->equal_range(event);
+    for(auto i=ft.first; i!=ft.second; ++i) i->second.call(params);
+}
+
+void controller::call_user_cb(const std::string& event) {
+    auto ft = user_cb->equal_range(event);
+    for(auto i=ft.first; i!=ft.second; ++i) i->second.call();
+}
diff --git a/src/flame/controller.h b/src/flame/controller.h
new file mode 100644
index 0000000..5cb0301
--- /dev/null
+++ b/src/flame/controller.h
@@ -0,0 +1,46 @@
+#pragma once
+#include "../vendor.h"
+#include "../logger.h"
+
+class controller {
+public:
+    boost::asio::io_context context_x;
+    boost::asio::io_context context_y;
+    enum class process_type {
+        UNKNOWN = 0,
+        MASTER = 1,
+        WORKER = 2,
+    } type;
+    boost::process::environment env;
+    enum status_t {
+        STATUS_UNKNOWN     = 0x00,
+        STATUS_INITIALIZED = 0x01,
+        STATUS_SHUTDOWN    = 0x02,
+        STATUS_EXCEPTION   = 0x04,
+        STATUS_RUN         = 0x08,
+        STATUS_CLOSECONN   = 0x10,
+    };
+    int status;
+    std::size_t    worker_size;
+    std::size_t    worker_quit; // 多进程退出超时时间
+    std::thread::id mthread_id;
+    // 日志管理器
+    logger_manager*         lm;
+private:
+    std::list<std::function<void (const php::array& options)>>  init_cb;
+    std::list<std::function<void ()>>                           stop_cb;
+    std::multimap<std::string, php::callable>*                  user_cb; // 防止 PHP 提前回收, 使用堆容器
+public:
+    controller();
+    controller(const controller& c) = delete;
+    void init(php::array options);
+    void stop();
+    controller *on_init(std::function<void(const php::array &options)> fn);
+    controller* on_stop(std::function<void ()> fn);
+    controller* on_user(const std::string& event, php::callable cb);
+    void call_user_cb(const std::string& event, std::vector<php::value> params);
+    void call_user_cb(const std::string& event);
+    // TODO write_to_master | write_worker
+};
+
+extern std::unique_ptr<controller> gcontroller;
diff --git a/src/flame/coroutine.cpp b/src/flame/coroutine.cpp
new file mode 100644
index 0000000..9f5bf0d
--- /dev/null
+++ b/src/flame/coroutine.cpp
@@ -0,0 +1,109 @@
+#include "controller.h"
+#include "coroutine.h"
+#include "../util.h"
+
+static void coroutine_php_save_context(flame::coroutine::php_context_t &ctx) {
+    ctx.vm_stack = EG(vm_stack);
+    ctx.vm_stack_top = EG(vm_stack_top);
+    ctx.vm_stack_end = EG(vm_stack_end);
+    //   ctx.scope = EG(fake_scope);
+    ctx.current_execute_data = EG(current_execute_data);
+    ctx.exception = EG(exception);
+    ctx.exception_class = EG(exception_class);
+    ctx.error_handling = EG(error_handling);
+}
+
+static void coroutine_php_restore_context(flame::coroutine::php_context_t &ctx) {
+    EG(vm_stack) = ctx.vm_stack;
+    EG(vm_stack_top) = ctx.vm_stack_top;
+    EG(vm_stack_end) = ctx.vm_stack_end;
+    // EG(fake_scope) = ctx.scope;
+    EG(current_execute_data) = ctx.current_execute_data;
+    EG(exception) = ctx.exception;
+    EG(exception_class) = ctx.exception_class;
+    EG(error_handling) = ctx.error_handling;
+}
+// 参考 zend_execute.c 
+static zend_vm_stack coroutine_php_vm_stack_new_page(size_t size, zend_vm_stack prev) {
+    zend_vm_stack page = (zend_vm_stack)emalloc(size);
+
+    page->top = ZEND_VM_STACK_ELEMENTS(page);
+    page->end = (zval *)((char *)page + size);
+    page->prev = prev;
+    return page;
+}
+
+// 参考 zend_execute.c
+static void coroutine_php_vm_stack_init(void) {
+    EG(current_execute_data) = nullptr;
+    EG(error_handling) = EH_NORMAL;
+    EG(exception_class) = nullptr;
+    EG(exception) = nullptr;
+
+    EG(vm_stack) = coroutine_php_vm_stack_new_page(4 * sizeof(zval) * 1024, NULL);
+    EG(vm_stack)->top++;
+    EG(vm_stack_top) = EG(vm_stack)->top;
+    EG(vm_stack_end) = EG(vm_stack)->end;
+}
+
+namespace flame {
+
+    unsigned int               coroutine::count = 0;
+    std::shared_ptr<coroutine> coroutine::current;
+    coroutine::php_context_t   coroutine::gctx_;
+
+    void coroutine::start(php::callable fn) {
+        ++coroutine::count;
+        auto co = std::make_shared<flame::coroutine>();
+        
+        boost::asio::post(co->ex_, [co, fn] () mutable {
+            co->c1_ = boost::context::fiber([co, gd = boost::asio::make_work_guard(co->ex_), fn] (boost::context::fiber&& c2) mutable {
+                co->c2_ = std::move(c2);
+                coroutine_php_save_context(coroutine::gctx_);
+                coroutine_php_vm_stack_init();
+                coroutine::current = co;
+
+                try {
+                    fn();
+                } catch (const php::exception &ex) {
+                    gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
+                    php::object obj = ex; // 记录错误信息
+                    std::cerr << "[" << util::system_time() << "] (FATAL) " << obj.call("__toString") << "\n";
+
+                    boost::asio::post(co->ex_, [] () {
+                        gcontroller->status |= controller::STATUS_EXCEPTION;
+                        gcontroller->context_x.stop();
+                        gcontroller->context_y.stop();
+                    });
+                }
+                fn = nullptr;
+
+                coroutine::current.reset();
+                zend_vm_stack_destroy();
+                coroutine_php_restore_context(coroutine::gctx_);
+                --coroutine::count;
+                
+                return std::move(co->c2_);
+            });
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+
+    void coroutine::suspend() {
+        coroutine_php_save_context(cctx_);
+        coroutine_php_restore_context(coroutine::gctx_);
+        // coroutine::current.reset();
+        c2_ = std::move(c2_).resume();
+    }
+
+    void coroutine::resume() {
+        auto co = std::static_pointer_cast<coroutine>(shared_from_this());
+        boost::asio::post(ex_, [co] () { 
+            coroutine_php_save_context(coroutine::gctx_);
+            coroutine_php_restore_context(co->cctx_);
+            coroutine::current = co;
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+
+}
diff --git a/src/flame/coroutine.h b/src/flame/coroutine.h
new file mode 100644
index 0000000..b7ac574
--- /dev/null
+++ b/src/flame/coroutine.h
@@ -0,0 +1,107 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "controller.h"
+
+namespace flame {
+
+    class coroutine : public ::coroutine {
+    public:
+        struct php_context_t {
+            zend_vm_stack vm_stack;
+            zval *vm_stack_top;
+            zval *vm_stack_end;
+            zend_class_entry *scope;
+            zend_execute_data *current_execute_data;
+
+            zend_object *         exception;
+            zend_error_handling_t error_handling;
+            zend_class_entry *    exception_class;
+        };
+        static void start(php::callable fn);
+        static unsigned int count;
+        static std::shared_ptr<flame::coroutine> current;
+
+        coroutine()
+        : ::coroutine(gcontroller->context_x.get_executor()) {
+
+        }
+        void suspend();
+        void resume();
+    protected:
+        static php_context_t gctx_;
+               php_context_t cctx_;
+
+        friend class coroutine_handler;
+    };
+
+    class coroutine_handler: public ::coroutine_handler {
+    public:
+        coroutine_handler()
+        : ::coroutine_handler() {
+
+        }
+        coroutine_handler(std::shared_ptr<coroutine> co)
+        : ::coroutine_handler(co) {
+            
+        }
+        ~coroutine_handler() {
+
+        }
+
+        void reset() {
+            co_.reset();
+        }
+        void reset(std::shared_ptr<coroutine> co) {
+            co_ = co;
+        }
+        void reset(const coroutine_handler& ch) {
+            co_ = ch.co_;
+        }
+
+        coroutine_handler& operator[](boost::system::error_code& error) {
+            error_ = &error;
+            return *this;
+        }
+        coroutine_handler& operator[](std::size_t& size) {
+            size_ = &size;
+            return *this;
+        }
+    private:
+        coroutine::php_context_t  ctx_;
+    };
+
+}
+
+namespace boost::asio {
+    template <>
+    class async_result<flame::coroutine_handler, void (boost::system::error_code error, std::size_t size)> {
+    public:
+        explicit async_result(flame::coroutine_handler& ch) : ch_(ch), size_(0) {
+            ch_.operator[](size_);
+        }
+        using completion_handler_type = flame::coroutine_handler;
+        using return_type = std::size_t;
+        return_type get() {
+            ch_.suspend();
+            return size_;
+        }
+    private:
+        flame::coroutine_handler &ch_;
+        std::size_t size_;
+    };
+
+    template <>
+    class async_result<flame::coroutine_handler, void (boost::system::error_code error)> {
+    public:
+        explicit async_result(flame::coroutine_handler& ch) : ch_(ch) {
+        }
+        using completion_handler_type = flame::coroutine_handler;
+        using return_type = void;
+        void get() {
+            ch_.suspend();
+        }
+    private:
+        flame::coroutine_handler &ch_;
+    };
+} // namespace boost::asio
diff --git a/src/encoding/encoding.cpp b/src/flame/encoding/encoding.cpp
similarity index 100%
rename from src/encoding/encoding.cpp
rename to src/flame/encoding/encoding.cpp
diff --git a/src/encoding/encoding.h b/src/flame/encoding/encoding.h
similarity index 81%
rename from src/encoding/encoding.h
rename to src/flame/encoding/encoding.h
index 9ab48f9..dd66fc6 100644
--- a/src/encoding/encoding.h
+++ b/src/flame/encoding/encoding.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::encoding {
     void declare(php::extension_entry &ext);
diff --git a/src/flame/flame.cpp b/src/flame/flame.cpp
new file mode 100644
index 0000000..3a0e96d
--- /dev/null
+++ b/src/flame/flame.cpp
@@ -0,0 +1,29 @@
+#include "../vendor.h"
+#include "controller.h"
+#include "version.h"
+#include "worker.h"
+#include "master.h"
+
+extern "C" {
+    ZEND_DLEXPORT zend_module_entry *get_module()  {
+        static php::extension_entry ext(EXTENSION_NAME, EXTENSION_VERSION);
+        std::string sapi = php::constant("PHP_SAPI");
+        if (sapi != "cli") {
+            std::cerr << "[WARNING] FLAME disabled: SAPI='cli' mode only\n";
+            return ext;
+        }
+        // 内置一个 C++ 函数包裹类
+        php::class_entry<php::closure> class_closure("flame\\closure");
+        class_closure.method<&php::closure::__invoke>("__invoke");
+        ext.add(std::move(class_closure));
+        // 全局控制器
+        gcontroller.reset(new controller());
+        // 扩展版本
+        flame::version::declare(ext);
+        // 主进程与工作进程注册不同的函数实现
+        if (gcontroller->type == ::controller::process_type::WORKER) flame::worker::declare(ext);
+        else flame::master::declare(ext);
+
+        return ext;
+    }
+};
diff --git a/src/hash/hash.cpp b/src/flame/hash/hash.cpp
similarity index 100%
rename from src/hash/hash.cpp
rename to src/flame/hash/hash.cpp
diff --git a/src/hash/hash.h b/src/flame/hash/hash.h
similarity index 80%
rename from src/hash/hash.h
rename to src/flame/hash/hash.h
index 8a2580a..31fac80 100644
--- a/src/hash/hash.h
+++ b/src/flame/hash/hash.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::hash {
     void declare(php::extension_entry &ext);
diff --git a/src/http/_handler.cpp b/src/flame/http/_handler.cpp
similarity index 96%
rename from src/http/_handler.cpp
rename to src/flame/http/_handler.cpp
index ddae9cf..4b05dd2 100644
--- a/src/http/_handler.cpp
+++ b/src/flame/http/_handler.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "../time/time.h"
 #include "_handler.h"
@@ -90,7 +89,7 @@ namespace flame::http {
                     //     res_ptr->set("body", nullptr);
                     // }
                     // 服务及将停止或服务器以进行关闭后，禁止连接复用
-                    if (gcontroller->status & controller::controller_status::STATUS_CLOSECONN || svr_ptr->closed_) {
+                    if (gcontroller->status & controller::STATUS_CLOSECONN || svr_ptr->closed_) {
                         res_->keep_alive(false);
                         res_->set(boost::beast::http::field::connection, "close");
                     }
@@ -119,10 +118,9 @@ namespace flame::http {
             } catch(const php::exception& ex) {
                 res_.reset();
                 req_.reset();
-                
-                auto ft = gcontroller->cbmap->equal_range("exception");
-                for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
-
+                // 调用用户异常回调
+                gcontroller->call_user_cb("exception", {ex});
+                // 记录错误信息
                 php::object obj = ex;
                 std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
             }
@@ -136,7 +134,7 @@ namespace flame::http {
         res_->chunked(true);
         res_->set(boost::beast::http::field::transfer_encoding, "chunked");
         // 服务及将停止或服务器以进行关闭后，禁止连接复用
-        if (gcontroller->status & controller::controller_status::STATUS_CLOSECONN || svr_ptr->closed_) {
+        if (gcontroller->status & controller::STATUS_CLOSECONN || svr_ptr->closed_) {
             res_->keep_alive(false);
             res_->set(boost::beast::http::field::connection, "close");
         }
diff --git a/src/http/_handler.h b/src/flame/http/_handler.h
similarity index 98%
rename from src/http/_handler.h
rename to src/flame/http/_handler.h
index 5ca184e..a1ab5f3 100644
--- a/src/http/_handler.h
+++ b/src/flame/http/_handler.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "http.h"
 
diff --git a/src/http/client.cpp b/src/flame/http/client.cpp
similarity index 99%
rename from src/http/client.cpp
rename to src/flame/http/client.cpp
index f9ce5fd..d79567e 100644
--- a/src/http/client.cpp
+++ b/src/flame/http/client.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "client.h"
 #include "client_poll.h"
diff --git a/src/http/client.h b/src/flame/http/client.h
similarity index 97%
rename from src/http/client.h
rename to src/flame/http/client.h
index 8cf0638..00df8ca 100644
--- a/src/http/client.h
+++ b/src/flame/http/client.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "http.h"
 
 namespace flame::http {
diff --git a/src/http/client_body.cpp b/src/flame/http/client_body.cpp
similarity index 100%
rename from src/http/client_body.cpp
rename to src/flame/http/client_body.cpp
diff --git a/src/http/client_body.h b/src/flame/http/client_body.h
similarity index 87%
rename from src/http/client_body.h
rename to src/flame/http/client_body.h
index 04f8dfd..2cb3187 100644
--- a/src/http/client_body.h
+++ b/src/flame/http/client_body.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "http.h"
 
 namespace flame::http {
diff --git a/src/http/client_poll.cpp b/src/flame/http/client_poll.cpp
similarity index 100%
rename from src/http/client_poll.cpp
rename to src/flame/http/client_poll.cpp
diff --git a/src/http/client_poll.h b/src/flame/http/client_poll.h
similarity index 98%
rename from src/http/client_poll.h
rename to src/flame/http/client_poll.h
index d8e285d..0564383 100644
--- a/src/http/client_poll.h
+++ b/src/flame/http/client_poll.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "http.h"
 
 namespace flame::http {
diff --git a/src/http/client_request.cpp b/src/flame/http/client_request.cpp
similarity index 100%
rename from src/http/client_request.cpp
rename to src/flame/http/client_request.cpp
diff --git a/src/http/client_request.h b/src/flame/http/client_request.h
similarity index 90%
rename from src/http/client_request.h
rename to src/flame/http/client_request.h
index 3314779..fd6f6dd 100644
--- a/src/http/client_request.h
+++ b/src/flame/http/client_request.h
@@ -1,7 +1,7 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
+#include "../../url.h"
 #include "../coroutine.h"
-#include "../url.h"
 #include "http.h"
 
 namespace flame::http {
diff --git a/src/http/client_response.cpp b/src/flame/http/client_response.cpp
similarity index 100%
rename from src/http/client_response.cpp
rename to src/flame/http/client_response.cpp
diff --git a/src/http/client_response.h b/src/flame/http/client_response.h
similarity index 94%
rename from src/http/client_response.h
rename to src/flame/http/client_response.h
index bc9ffed..0757d66 100644
--- a/src/http/client_response.h
+++ b/src/flame/http/client_response.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "http.h"
 
diff --git a/src/http/http.cpp b/src/flame/http/http.cpp
similarity index 97%
rename from src/http/http.cpp
rename to src/flame/http/http.cpp
index 5658192..3630f10 100644
--- a/src/http/http.cpp
+++ b/src/flame/http/http.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "http.h"
 #include "client.h"
diff --git a/src/http/http.h b/src/flame/http/http.h
similarity index 93%
rename from src/http/http.h
rename to src/flame/http/http.h
index 5b52c82..0ff3a8e 100644
--- a/src/http/http.h
+++ b/src/flame/http/http.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #define BOOST_BEAST_USE_STD_STRING_VIEW
 #include <boost/beast/core.hpp>
 #include <boost/beast/http.hpp>
diff --git a/src/http/server.cpp b/src/flame/http/server.cpp
similarity index 99%
rename from src/http/server.cpp
rename to src/flame/http/server.cpp
index 64016c4..5477000 100644
--- a/src/http/server.cpp
+++ b/src/flame/http/server.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "../udp/udp.h"
 #include "server.h"
diff --git a/src/http/server.h b/src/flame/http/server.h
similarity index 97%
rename from src/http/server.h
rename to src/flame/http/server.h
index 1b0f4eb..e0b10c4 100644
--- a/src/http/server.h
+++ b/src/flame/http/server.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "http.h"
 
 namespace flame::http {
diff --git a/src/http/server_request.cpp b/src/flame/http/server_request.cpp
similarity index 100%
rename from src/http/server_request.cpp
rename to src/flame/http/server_request.cpp
diff --git a/src/http/server_request.h b/src/flame/http/server_request.h
similarity index 90%
rename from src/http/server_request.h
rename to src/flame/http/server_request.h
index 7b0d241..b2f4d68 100644
--- a/src/http/server_request.h
+++ b/src/flame/http/server_request.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "http.h"
 
 namespace flame::http {
diff --git a/src/http/server_response.cpp b/src/flame/http/server_response.cpp
similarity index 100%
rename from src/http/server_response.cpp
rename to src/flame/http/server_response.cpp
diff --git a/src/http/server_response.h b/src/flame/http/server_response.h
similarity index 94%
rename from src/http/server_response.h
rename to src/flame/http/server_response.h
index 7d11754..b9ec11f 100644
--- a/src/http/server_response.h
+++ b/src/flame/http/server_response.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "http.h"
 #include "value_body.h"
 
diff --git a/src/http/value_body.cpp b/src/flame/http/value_body.cpp
similarity index 100%
rename from src/http/value_body.cpp
rename to src/flame/http/value_body.cpp
diff --git a/src/http/value_body.h b/src/flame/http/value_body.h
similarity index 95%
rename from src/http/value_body.h
rename to src/flame/http/value_body.h
index 0f76ac1..01afcac 100644
--- a/src/http/value_body.h
+++ b/src/flame/http/value_body.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "http.h"
 
 namespace flame::http {
diff --git a/src/http/value_body.ipp b/src/flame/http/value_body.ipp
similarity index 100%
rename from src/http/value_body.ipp
rename to src/flame/http/value_body.ipp
diff --git a/src/kafka/_consumer.cpp b/src/flame/kafka/_consumer.cpp
similarity index 96%
rename from src/kafka/_consumer.cpp
rename to src/flame/kafka/_consumer.cpp
index dda6cd9..308ff51 100644
--- a/src/kafka/_consumer.cpp
+++ b/src/flame/kafka/_consumer.cpp
@@ -1,8 +1,8 @@
 #include "../controller.h"
-#include "../coroutine_queue.h"
 #include "../time/time.h"
 #include "../log/log.h"
 #include "_consumer.h"
+#include "../../coroutine_queue.h"
 #include "kafka.h"
 #include "message.h"
 
diff --git a/src/kafka/_consumer.h b/src/flame/kafka/_consumer.h
similarity index 86%
rename from src/kafka/_consumer.h
rename to src/flame/kafka/_consumer.h
index 10120b2..bbf7ec7 100644
--- a/src/kafka/_consumer.h
+++ b/src/flame/kafka/_consumer.h
@@ -1,12 +1,11 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "kafka.h"
 
-namespace flame {
-    template <class T>
-    class coroutine_queue;
-}
+
+template <class T>
+class coroutine_queue;
 
 namespace flame::kafka {
     // 消费者
diff --git a/src/kafka/_producer.cpp b/src/flame/kafka/_producer.cpp
similarity index 100%
rename from src/kafka/_producer.cpp
rename to src/flame/kafka/_producer.cpp
diff --git a/src/kafka/_producer.h b/src/flame/kafka/_producer.h
similarity index 94%
rename from src/kafka/_producer.h
rename to src/flame/kafka/_producer.h
index ff37842..8269959 100644
--- a/src/kafka/_producer.h
+++ b/src/flame/kafka/_producer.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "kafka.h"
 
diff --git a/src/kafka/consumer.cpp b/src/flame/kafka/consumer.cpp
similarity index 89%
rename from src/kafka/consumer.cpp
rename to src/flame/kafka/consumer.cpp
index d4f95f0..e631b54 100644
--- a/src/kafka/consumer.cpp
+++ b/src/flame/kafka/consumer.cpp
@@ -1,11 +1,10 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "../time/time.h"
 #include "consumer.h"
 #include "_consumer.h"
 #include "kafka.h"
 #include "message.h"
-#include "../coroutine_queue.h"
+#include "../../coroutine_queue.h"
 
 namespace flame::kafka {
 
@@ -44,9 +43,9 @@ namespace flame::kafka {
                         if (m) cb_.call({m.value()});
                         else break;
                     } catch(const php::exception& ex) {
-                        auto ft = gcontroller->cbmap->equal_range("exception");
-                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
-
+                        // 调用用户异常回调
+                        gcontroller->call_user_cb("exception", {ex});
+                        // 记录错误信息
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
                     }
diff --git a/src/kafka/consumer.h b/src/flame/kafka/consumer.h
similarity index 93%
rename from src/kafka/consumer.h
rename to src/flame/kafka/consumer.h
index f9abf06..255ad80 100644
--- a/src/kafka/consumer.h
+++ b/src/flame/kafka/consumer.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "kafka.h"
 
diff --git a/src/kafka/kafka.cpp b/src/flame/kafka/kafka.cpp
similarity index 100%
rename from src/kafka/kafka.cpp
rename to src/flame/kafka/kafka.cpp
diff --git a/src/kafka/kafka.h b/src/flame/kafka/kafka.h
similarity index 91%
rename from src/kafka/kafka.h
rename to src/flame/kafka/kafka.h
index 2142766..a9ad7a0 100644
--- a/src/kafka/kafka.h
+++ b/src/flame/kafka/kafka.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include <librdkafka/rdkafka.h>
 
 namespace flame::kafka {
diff --git a/src/kafka/message.cpp b/src/flame/kafka/message.cpp
similarity index 100%
rename from src/kafka/message.cpp
rename to src/flame/kafka/message.cpp
diff --git a/src/kafka/message.h b/src/flame/kafka/message.h
similarity index 92%
rename from src/kafka/message.h
rename to src/flame/kafka/message.h
index 33055c3..f2ba398 100644
--- a/src/kafka/message.h
+++ b/src/flame/kafka/message.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "kafka.h"
 
 namespace flame::kafka {
diff --git a/src/kafka/producer.cpp b/src/flame/kafka/producer.cpp
similarity index 100%
rename from src/kafka/producer.cpp
rename to src/flame/kafka/producer.cpp
diff --git a/src/kafka/producer.h b/src/flame/kafka/producer.h
similarity index 92%
rename from src/kafka/producer.h
rename to src/flame/kafka/producer.h
index c0d5d49..3bdc269 100644
--- a/src/kafka/producer.h
+++ b/src/flame/kafka/producer.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "kafka.h"
 
 namespace flame::kafka {
diff --git a/src/log/log.cpp b/src/flame/log/log.cpp
similarity index 100%
rename from src/log/log.cpp
rename to src/flame/log/log.cpp
diff --git a/src/log/log.h b/src/flame/log/log.h
similarity index 87%
rename from src/log/log.h
rename to src/flame/log/log.h
index 2e6c587..734da61 100644
--- a/src/log/log.h
+++ b/src/flame/log/log.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::log {
     enum {
diff --git a/src/flame/master.cpp b/src/flame/master.cpp
new file mode 100644
index 0000000..8ce5412
--- /dev/null
+++ b/src/flame/master.cpp
@@ -0,0 +1,106 @@
+#include "master.h"
+#include "controller.h"
+#include "../util.h"
+
+namespace flame {
+
+    master* master::mm_;
+
+    void master::declare(php::extension_entry& ext) {
+        ext
+        .function<master::init>("flame\\init")
+        .function<master::dummy>("flame\\go")
+        .function<master::dummy>("flame\\on")
+        .function<master::run>("flame\\run");
+    }
+
+    php::value master::init(php::parameters& params) {
+        php::array options = php::array(0);
+        if(params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
+        if (options.exists("timeout"))
+            gcontroller->worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
+        else
+            gcontroller->worker_quit = 3000;
+        
+        gcontroller->status |= controller::STATUS_INITIALIZED;
+        // 设置进程标题
+        std::string title = params[0];
+        php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
+        // 初始化启动
+        gcontroller->init(options);
+        // 主进程控制对象
+        master::mm_ = new master();
+
+        if(options.exists("logger")) master::mm_->lm_.connect(options.get("logger"));
+        else master::mm_->lm_.connect("stdout");
+        return nullptr;
+    }
+
+    php::value master::run(php::parameters& params) {
+         if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+            
+        gcontroller->status |= controller::STATUS_RUN;
+        // 启动工作进程
+        master::get()->pm_.start();
+        std::thread ts {[] () {
+            gcontroller->context_y.run();
+        }};
+        gcontroller->context_x.run();
+
+        if (gcontroller->status & controller::STATUS_EXCEPTION) _exit(-1);
+        else gcontroller->stop();
+
+        ts.join();
+        return nullptr;
+    }
+
+    php::value master::dummy(php::parameters& params) {
+        return nullptr;
+    }
+
+    master::master()
+    : lm_()
+    , pm_(gcontroller->context_x, gcontroller->worker_size)
+    , sw_(gcontroller->context_y) {
+        
+        gcontroller->lm = &lm_;
+        sw_.lg_ = pm_.lg_ = lm_.index(0);
+        sw_.start(std::bind(&master::on_signal, this, std::placeholders::_1, std::placeholders::_2));
+    }
+
+    void master::on_signal(const boost::system::error_code& error, int sig) {
+        if (error) return;
+        switch(sig) {
+        case SIGINT: coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) {
+            pm_.restart(ch);
+        });
+        break;
+        case SIGUSR2: 
+            lm_.reload(); // 日志重载
+        break;
+        case SIGUSR1:
+            if(++stats_ % 2 == 1) 
+                lm_.index(0)->stream() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl;
+            else
+                lm_.index(0)->stream() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl;
+            pm_.signal(SIGUSR1); // 长短连切换
+        break;
+        case SIGTERM:
+            if(++close_ > 1) {
+                sw_.close();
+                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) {
+                    pm_.close(true, ch);
+                });
+                return;
+            }else{
+                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) {
+                    pm_.close(false, ch);
+                });
+            }
+            break;
+        }
+        // 除强制停止信号外，需要持续监听信号
+        sw_.start(std::bind(&master::on_signal, this, std::placeholders::_1, std::placeholders::_2));
+    }
+}
diff --git a/src/flame/master.h b/src/flame/master.h
new file mode 100644
index 0000000..da8ad41
--- /dev/null
+++ b/src/flame/master.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "../vendor.h"
+#include "../process_manager.h"
+#include "../logger_master.h"
+#include "../signal_watcher.h"
+
+namespace flame {
+    class master {
+    public:
+        static inline master* get() {
+            return mm_;
+        }
+        static void declare(php::extension_entry& ext);
+        static php::value init(php::parameters& params);
+        static php::value run(php::parameters& params);
+        static php::value dummy(php::parameters& params);
+
+        master();
+        void on_signal(const boost::system::error_code& error, int sig);
+    private:
+        static master*  mm_;
+
+        process_manager       pm_;
+        logger_manager_master lm_;
+        signal_watcher        sw_;
+        int close_ = 0;
+        int stats_ = 0;
+    };
+}
\ No newline at end of file
diff --git a/src/mongodb/_connection_base.cpp b/src/flame/mongodb/_connection_base.cpp
similarity index 100%
rename from src/mongodb/_connection_base.cpp
rename to src/flame/mongodb/_connection_base.cpp
diff --git a/src/mongodb/_connection_base.h b/src/flame/mongodb/_connection_base.h
similarity index 92%
rename from src/mongodb/_connection_base.h
rename to src/flame/mongodb/_connection_base.h
index 8f3eb75..28488da 100644
--- a/src/mongodb/_connection_base.h
+++ b/src/flame/mongodb/_connection_base.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "mongodb.h"
 
diff --git a/src/mongodb/_connection_lock.cpp b/src/flame/mongodb/_connection_lock.cpp
similarity index 100%
rename from src/mongodb/_connection_lock.cpp
rename to src/flame/mongodb/_connection_lock.cpp
diff --git a/src/mongodb/_connection_lock.h b/src/flame/mongodb/_connection_lock.h
similarity index 93%
rename from src/mongodb/_connection_lock.h
rename to src/flame/mongodb/_connection_lock.h
index b50bb42..a10db40 100644
--- a/src/mongodb/_connection_lock.h
+++ b/src/flame/mongodb/_connection_lock.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mongodb.h"
 #include "_connection_base.h"
 
diff --git a/src/mongodb/_connection_pool.cpp b/src/flame/mongodb/_connection_pool.cpp
similarity index 100%
rename from src/mongodb/_connection_pool.cpp
rename to src/flame/mongodb/_connection_pool.cpp
diff --git a/src/mongodb/_connection_pool.h b/src/flame/mongodb/_connection_pool.h
similarity index 93%
rename from src/mongodb/_connection_pool.h
rename to src/flame/mongodb/_connection_pool.h
index 3a6521b..89c1978 100644
--- a/src/mongodb/_connection_pool.h
+++ b/src/flame/mongodb/_connection_pool.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mongodb.h"
 #include "_connection_base.h"
 
diff --git a/src/mongodb/client.cpp b/src/flame/mongodb/client.cpp
similarity index 100%
rename from src/mongodb/client.cpp
rename to src/flame/mongodb/client.cpp
diff --git a/src/mongodb/client.h b/src/flame/mongodb/client.h
similarity index 93%
rename from src/mongodb/client.h
rename to src/flame/mongodb/client.h
index eebfe89..4dd9693 100644
--- a/src/mongodb/client.h
+++ b/src/flame/mongodb/client.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mongodb.h"
 
 namespace flame::mongodb {
diff --git a/src/mongodb/collection.cpp b/src/flame/mongodb/collection.cpp
similarity index 100%
rename from src/mongodb/collection.cpp
rename to src/flame/mongodb/collection.cpp
diff --git a/src/mongodb/collection.h b/src/flame/mongodb/collection.h
similarity index 95%
rename from src/mongodb/collection.h
rename to src/flame/mongodb/collection.h
index 154990c..916c291 100644
--- a/src/mongodb/collection.h
+++ b/src/flame/mongodb/collection.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mongodb.h"
 
 namespace flame::mongodb {
diff --git a/src/mongodb/cursor.cpp b/src/flame/mongodb/cursor.cpp
similarity index 100%
rename from src/mongodb/cursor.cpp
rename to src/flame/mongodb/cursor.cpp
diff --git a/src/mongodb/cursor.h b/src/flame/mongodb/cursor.h
similarity index 93%
rename from src/mongodb/cursor.h
rename to src/flame/mongodb/cursor.h
index b54706e..d7a995c 100644
--- a/src/mongodb/cursor.h
+++ b/src/flame/mongodb/cursor.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mongodb.h"
 
 namespace flame::mongodb {
diff --git a/src/mongodb/date_time.cpp b/src/flame/mongodb/date_time.cpp
similarity index 100%
rename from src/mongodb/date_time.cpp
rename to src/flame/mongodb/date_time.cpp
diff --git a/src/mongodb/date_time.h b/src/flame/mongodb/date_time.h
similarity index 93%
rename from src/mongodb/date_time.h
rename to src/flame/mongodb/date_time.h
index ac3e74b..5b75005 100644
--- a/src/mongodb/date_time.h
+++ b/src/flame/mongodb/date_time.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mongodb.h"
 
 namespace flame::mongodb {
diff --git a/src/mongodb/mongodb.cpp b/src/flame/mongodb/mongodb.cpp
similarity index 96%
rename from src/mongodb/mongodb.cpp
rename to src/flame/mongodb/mongodb.cpp
index 7b21475..462679c 100644
--- a/src/mongodb/mongodb.cpp
+++ b/src/flame/mongodb/mongodb.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "mongodb.h"
 #include "_connection_pool.h"
diff --git a/src/mongodb/mongodb.h b/src/flame/mongodb/mongodb.h
similarity index 90%
rename from src/mongodb/mongodb.h
rename to src/flame/mongodb/mongodb.h
index d286c64..018631c 100644
--- a/src/mongodb/mongodb.h
+++ b/src/flame/mongodb/mongodb.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include <mongoc.h>
 
 namespace flame::mongodb {
diff --git a/src/mongodb/object_id.cpp b/src/flame/mongodb/object_id.cpp
similarity index 100%
rename from src/mongodb/object_id.cpp
rename to src/flame/mongodb/object_id.cpp
diff --git a/src/mongodb/object_id.h b/src/flame/mongodb/object_id.h
similarity index 93%
rename from src/mongodb/object_id.h
rename to src/flame/mongodb/object_id.h
index 2adf0ae..3347d39 100644
--- a/src/mongodb/object_id.h
+++ b/src/flame/mongodb/object_id.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mongodb.h"
 
 namespace flame::mongodb {
diff --git a/src/mutex.cpp b/src/flame/mutex.cpp
similarity index 94%
rename from src/mutex.cpp
rename to src/flame/mutex.cpp
index 518c8e1..646e044 100644
--- a/src/mutex.cpp
+++ b/src/flame/mutex.cpp
@@ -1,6 +1,6 @@
 #include "coroutine.h"
-#include "coroutine_mutex.h"
 #include "mutex.h"
+#include "../coroutine_mutex.h"
 
 namespace flame {
     void mutex::declare(php::extension_entry &ext) {
diff --git a/src/mutex.h b/src/flame/mutex.h
similarity index 90%
rename from src/mutex.h
rename to src/flame/mutex.h
index dc3c04e..2330b47 100644
--- a/src/mutex.h
+++ b/src/flame/mutex.h
@@ -1,9 +1,9 @@
 #pragma once
-#include "vendor.h"
+#include "../vendor.h"
 
+class coroutine_mutex;
 namespace flame {
 
-    class coroutine_mutex;
     class mutex: public php::class_base {
     public:
         static void declare(php::extension_entry& ext);
diff --git a/src/mysql/_connection_base.cpp b/src/flame/mysql/_connection_base.cpp
similarity index 100%
rename from src/mysql/_connection_base.cpp
rename to src/flame/mysql/_connection_base.cpp
diff --git a/src/mysql/_connection_base.h b/src/flame/mysql/_connection_base.h
similarity index 94%
rename from src/mysql/_connection_base.h
rename to src/flame/mysql/_connection_base.h
index 3a8362f..b8a50ff 100644
--- a/src/mysql/_connection_base.h
+++ b/src/flame/mysql/_connection_base.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "mysql.h"
 
diff --git a/src/mysql/_connection_lock.cpp b/src/flame/mysql/_connection_lock.cpp
similarity index 95%
rename from src/mysql/_connection_lock.cpp
rename to src/flame/mysql/_connection_lock.cpp
index ffedba8..8453607 100644
--- a/src/mysql/_connection_lock.cpp
+++ b/src/flame/mysql/_connection_lock.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "_connection_base.h"
 #include "_connection_lock.h"
diff --git a/src/mysql/_connection_lock.h b/src/flame/mysql/_connection_lock.h
similarity index 93%
rename from src/mysql/_connection_lock.h
rename to src/flame/mysql/_connection_lock.h
index 7c3d6b9..cec27a7 100644
--- a/src/mysql/_connection_lock.h
+++ b/src/flame/mysql/_connection_lock.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "_connection_base.h"
 #include "mysql.h"
diff --git a/src/mysql/_connection_pool.cpp b/src/flame/mysql/_connection_pool.cpp
similarity index 100%
rename from src/mysql/_connection_pool.cpp
rename to src/flame/mysql/_connection_pool.cpp
diff --git a/src/mysql/_connection_pool.h b/src/flame/mysql/_connection_pool.h
similarity index 93%
rename from src/mysql/_connection_pool.h
rename to src/flame/mysql/_connection_pool.h
index 8b4be16..0f4826a 100644
--- a/src/mysql/_connection_pool.h
+++ b/src/flame/mysql/_connection_pool.h
@@ -1,7 +1,8 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
+#include "../../url.h"
 #include "../coroutine.h"
-#include "../url.h"
+
 #include "_connection_base.h"
 
 namespace flame::mysql {
diff --git a/src/mysql/client.cpp b/src/flame/mysql/client.cpp
similarity index 100%
rename from src/mysql/client.cpp
rename to src/flame/mysql/client.cpp
diff --git a/src/mysql/client.h b/src/flame/mysql/client.h
similarity index 95%
rename from src/mysql/client.h
rename to src/flame/mysql/client.h
index 744d8de..ccd6164 100644
--- a/src/mysql/client.h
+++ b/src/flame/mysql/client.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mysql.h"
 
 namespace flame::mysql {
diff --git a/src/mysql/mysql.cpp b/src/flame/mysql/mysql.cpp
similarity index 97%
rename from src/mysql/mysql.cpp
rename to src/flame/mysql/mysql.cpp
index cc25e2e..4a9c0d3 100644
--- a/src/mysql/mysql.cpp
+++ b/src/flame/mysql/mysql.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "mysql.h"
 #include "_connection_base.h"
diff --git a/src/mysql/mysql.h b/src/flame/mysql/mysql.h
similarity index 95%
rename from src/mysql/mysql.h
rename to src/flame/mysql/mysql.h
index 0be7b4a..781a718 100644
--- a/src/mysql/mysql.h
+++ b/src/flame/mysql/mysql.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include <mariadb/mysql.h>
 
 namespace flame::mysql {
diff --git a/src/mysql/result.cpp b/src/flame/mysql/result.cpp
similarity index 100%
rename from src/mysql/result.cpp
rename to src/flame/mysql/result.cpp
diff --git a/src/mysql/result.h b/src/flame/mysql/result.h
similarity index 92%
rename from src/mysql/result.h
rename to src/flame/mysql/result.h
index 99cf070..ae18a0b 100644
--- a/src/mysql/result.h
+++ b/src/flame/mysql/result.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mysql.h"
 
 namespace flame::mysql {
diff --git a/src/mysql/tx.cpp b/src/flame/mysql/tx.cpp
similarity index 100%
rename from src/mysql/tx.cpp
rename to src/flame/mysql/tx.cpp
diff --git a/src/mysql/tx.h b/src/flame/mysql/tx.h
similarity index 94%
rename from src/mysql/tx.h
rename to src/flame/mysql/tx.h
index 217b483..bbaec9a 100644
--- a/src/mysql/tx.h
+++ b/src/flame/mysql/tx.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "mysql.h"
 
 namespace flame::mysql {
diff --git a/src/os/os.cpp b/src/flame/os/os.cpp
similarity index 96%
rename from src/os/os.cpp
rename to src/flame/os/os.cpp
index 08efc5c..18fb1f1 100644
--- a/src/os/os.cpp
+++ b/src/flame/os/os.cpp
@@ -8,7 +8,8 @@
 #include <sys/socket.h>
 #include <ifaddrs.h>
 #include <netdb.h>
-
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
 
 namespace flame::os {
     void declare(php::extension_entry &ext) {
diff --git a/src/os/os.h b/src/flame/os/os.h
similarity index 87%
rename from src/os/os.h
rename to src/flame/os/os.h
index 41f7ca6..f75b35c 100644
--- a/src/os/os.h
+++ b/src/flame/os/os.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::os {
     
diff --git a/src/os/process.cpp b/src/flame/os/process.cpp
similarity index 100%
rename from src/os/process.cpp
rename to src/flame/os/process.cpp
diff --git a/src/os/process.h b/src/flame/os/process.h
similarity index 88%
rename from src/os/process.h
rename to src/flame/os/process.h
index 03abff3..e58e32e 100644
--- a/src/os/process.h
+++ b/src/flame/os/process.h
@@ -1,6 +1,8 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
 
 namespace flame::os {
     class process: public php::class_base {
diff --git a/src/queue.cpp b/src/flame/queue.cpp
similarity index 59%
rename from src/queue.cpp
rename to src/flame/queue.cpp
index 0535b62..38c95c8 100644
--- a/src/queue.cpp
+++ b/src/flame/queue.cpp
@@ -14,7 +14,9 @@ namespace flame {
             .method<&queue::pop>("pop")
             .method<&queue::close>("close")
             .method<&queue::is_closed>("is_closed");
-        ext.add(std::move(class_queue));
+        ext
+            .add(std::move(class_queue))
+            .function<queue::select>("flame\\select");
     }
 
     php::value queue::__construct(php::parameters& params) {
@@ -46,4 +48,20 @@ namespace flame {
     php::value queue::is_closed(php::parameters& params) {
         return q_->is_closed();
     }
+    
+    php::value queue::select(php::parameters& params) {
+        std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
+        std::map< std::shared_ptr<coroutine_queue<php::value>>, php::object > mm;
+        for (auto i = 0; i < params.size(); ++i) {
+            if (!params[i].instanceof(php::class_entry<queue>::entry())) 
+                throw php::exception(zend_ce_type_error, "Failed to select: instanceof flame\\queue required", -1);
+            php::object obj = params[i];
+            queue* ptr = static_cast<queue*>(php::native(obj));
+            qs.push_back( ptr->q_ );
+            mm.insert({ptr->q_, obj});
+        }
+        coroutine_handler ch{coroutine::current};
+        std::shared_ptr<coroutine_queue<php::value>> q = select_queue(qs, ch);
+        return mm[q];
+    }    
 }
diff --git a/src/queue.h b/src/flame/queue.h
similarity index 68%
rename from src/queue.h
rename to src/flame/queue.h
index 8beae75..90c6201 100644
--- a/src/queue.h
+++ b/src/flame/queue.h
@@ -1,16 +1,13 @@
 #pragma once
-#include "vendor.h"
-#include "coroutine_queue.h"
+#include "../vendor.h"
+#include "../coroutine_queue.h"
 
 namespace flame {
 
-    namespace core {
-        php::value select(php::parameters &params);
-    }
-
     class queue: public php::class_base {
     public:
         static void declare(php::extension_entry &ext);
+        static php::value select(php::parameters& params);
         php::value __construct(php::parameters& params);
         php::value push(php::parameters &params);
         php::value pop(php::parameters &params);
@@ -18,7 +15,5 @@ namespace flame {
         php::value is_closed(php::parameters &params);
     private:
         std::shared_ptr<coroutine_queue<php::value>> q_;
-
-        friend php::value core::select(php::parameters &params);
     };
 }
diff --git a/src/rabbitmq/_client.cpp b/src/flame/rabbitmq/_client.cpp
similarity index 96%
rename from src/rabbitmq/_client.cpp
rename to src/flame/rabbitmq/_client.cpp
index 38adb46..de11754 100644
--- a/src/rabbitmq/_client.cpp
+++ b/src/flame/rabbitmq/_client.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "_client.h"
 #include "message.h"
diff --git a/src/rabbitmq/_client.h b/src/flame/rabbitmq/_client.h
similarity index 91%
rename from src/rabbitmq/_client.h
rename to src/flame/rabbitmq/_client.h
index 2a823ea..4843d00 100644
--- a/src/rabbitmq/_client.h
+++ b/src/flame/rabbitmq/_client.h
@@ -1,8 +1,8 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
+#include "../../url.h"
+#include "../../coroutine_queue.h"
 #include "../coroutine.h"
-#include "../url.h"
-#include "../coroutine_queue.h"
 #include "rabbitmq.h"
 
 namespace flame::rabbitmq {
diff --git a/src/rabbitmq/consumer.cpp b/src/flame/rabbitmq/consumer.cpp
similarity index 92%
rename from src/rabbitmq/consumer.cpp
rename to src/flame/rabbitmq/consumer.cpp
index 8d9933e..338c496 100644
--- a/src/rabbitmq/consumer.cpp
+++ b/src/flame/rabbitmq/consumer.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "consumer.h"
 #include "_client.h"
@@ -50,9 +49,9 @@ namespace flame::rabbitmq  {
                     try {
                         cb_.call( {x.value()} );
                     } catch(const php::exception& ex) {
-                        auto ft = gcontroller->cbmap->equal_range("exception");
-                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
-
+                        // 调用用户异常回调
+                        gcontroller->call_user_cb("exception", {ex});
+                        // 记录错误信息
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
                     }
diff --git a/src/rabbitmq/consumer.h b/src/flame/rabbitmq/consumer.h
similarity index 94%
rename from src/rabbitmq/consumer.h
rename to src/flame/rabbitmq/consumer.h
index 2ceceda..6e0ff24 100644
--- a/src/rabbitmq/consumer.h
+++ b/src/flame/rabbitmq/consumer.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "rabbitmq.h"
 
 namespace flame::rabbitmq {
diff --git a/src/rabbitmq/message.cpp b/src/flame/rabbitmq/message.cpp
similarity index 100%
rename from src/rabbitmq/message.cpp
rename to src/flame/rabbitmq/message.cpp
diff --git a/src/rabbitmq/message.h b/src/flame/rabbitmq/message.h
similarity index 92%
rename from src/rabbitmq/message.h
rename to src/flame/rabbitmq/message.h
index 369c57d..9bc9c1e 100644
--- a/src/rabbitmq/message.h
+++ b/src/flame/rabbitmq/message.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "rabbitmq.h"
 
 namespace flame::rabbitmq {
diff --git a/src/rabbitmq/producer.cpp b/src/flame/rabbitmq/producer.cpp
similarity index 100%
rename from src/rabbitmq/producer.cpp
rename to src/flame/rabbitmq/producer.cpp
diff --git a/src/rabbitmq/producer.h b/src/flame/rabbitmq/producer.h
similarity index 92%
rename from src/rabbitmq/producer.h
rename to src/flame/rabbitmq/producer.h
index 8097218..53fee1f 100644
--- a/src/rabbitmq/producer.h
+++ b/src/flame/rabbitmq/producer.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "rabbitmq.h"
 
 namespace flame::rabbitmq {
diff --git a/src/rabbitmq/rabbitmq.cpp b/src/flame/rabbitmq/rabbitmq.cpp
similarity index 96%
rename from src/rabbitmq/rabbitmq.cpp
rename to src/flame/rabbitmq/rabbitmq.cpp
index 64693dc..5d240f0 100644
--- a/src/rabbitmq/rabbitmq.cpp
+++ b/src/flame/rabbitmq/rabbitmq.cpp
@@ -1,6 +1,5 @@
-#include "../controller.h"
+#include "../../url.h"
 #include "../coroutine.h"
-#include "../url.h"
 #include "_client.h"
 #include "rabbitmq.h"
 #include "consumer.h"
diff --git a/src/rabbitmq/rabbitmq.h b/src/flame/rabbitmq/rabbitmq.h
similarity index 90%
rename from src/rabbitmq/rabbitmq.h
rename to src/flame/rabbitmq/rabbitmq.h
index 1968aa2..09bb7dc 100644
--- a/src/rabbitmq/rabbitmq.h
+++ b/src/flame/rabbitmq/rabbitmq.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include <amqpcpp.h>
 #include <amqpcpp/libboostasio.h>
 
diff --git a/src/redis/_connection_base.cpp b/src/flame/redis/_connection_base.cpp
similarity index 100%
rename from src/redis/_connection_base.cpp
rename to src/flame/redis/_connection_base.cpp
diff --git a/src/redis/_connection_base.h b/src/flame/redis/_connection_base.h
similarity index 94%
rename from src/redis/_connection_base.h
rename to src/flame/redis/_connection_base.h
index 1d97f3d..fe2f4eb 100644
--- a/src/redis/_connection_base.h
+++ b/src/flame/redis/_connection_base.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "redis.h"
 
diff --git a/src/redis/_connection_lock.cpp b/src/flame/redis/_connection_lock.cpp
similarity index 96%
rename from src/redis/_connection_lock.cpp
rename to src/flame/redis/_connection_lock.cpp
index 5caf500..6fd257c 100644
--- a/src/redis/_connection_lock.cpp
+++ b/src/flame/redis/_connection_lock.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "_connection_base.h"
 #include "_connection_lock.h"
diff --git a/src/redis/_connection_lock.h b/src/flame/redis/_connection_lock.h
similarity index 94%
rename from src/redis/_connection_lock.h
rename to src/flame/redis/_connection_lock.h
index 6147bc9..d4cb379 100644
--- a/src/redis/_connection_lock.h
+++ b/src/flame/redis/_connection_lock.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "../coroutine.h"
 #include "redis.h"
 #include "_connection_base.h"
diff --git a/src/redis/_connection_pool.cpp b/src/flame/redis/_connection_pool.cpp
similarity index 100%
rename from src/redis/_connection_pool.cpp
rename to src/flame/redis/_connection_pool.cpp
diff --git a/src/redis/_connection_pool.h b/src/flame/redis/_connection_pool.h
similarity index 94%
rename from src/redis/_connection_pool.h
rename to src/flame/redis/_connection_pool.h
index ba9bdc3..70e906c 100644
--- a/src/redis/_connection_pool.h
+++ b/src/flame/redis/_connection_pool.h
@@ -1,7 +1,7 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
+#include "../../url.h"
 #include "../coroutine.h"
-#include "../url.h"
 #include "redis.h"
 #include "_connection_base.h"
 
diff --git a/src/redis/client.cpp b/src/flame/redis/client.cpp
similarity index 100%
rename from src/redis/client.cpp
rename to src/flame/redis/client.cpp
diff --git a/src/redis/client.h b/src/flame/redis/client.h
similarity index 95%
rename from src/redis/client.h
rename to src/flame/redis/client.h
index 3d91845..dbbdcec 100644
--- a/src/redis/client.h
+++ b/src/flame/redis/client.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "redis.h"
 
 namespace flame::redis {
diff --git a/src/redis/redis.cpp b/src/flame/redis/redis.cpp
similarity index 100%
rename from src/redis/redis.cpp
rename to src/flame/redis/redis.cpp
diff --git a/src/redis/redis.h b/src/flame/redis/redis.h
similarity index 84%
rename from src/redis/redis.h
rename to src/flame/redis/redis.h
index c18ef96..988604d 100644
--- a/src/redis/redis.h
+++ b/src/flame/redis/redis.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include <hiredis/hiredis.h>
 
 namespace flame::redis {
diff --git a/src/redis/tx.cpp b/src/flame/redis/tx.cpp
similarity index 100%
rename from src/redis/tx.cpp
rename to src/flame/redis/tx.cpp
diff --git a/src/redis/tx.h b/src/flame/redis/tx.h
similarity index 95%
rename from src/redis/tx.h
rename to src/flame/redis/tx.h
index 919fdca..475aa69 100644
--- a/src/redis/tx.h
+++ b/src/flame/redis/tx.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 #include "redis.h"
 
 namespace flame::redis {
diff --git a/src/tcp/server.cpp b/src/flame/tcp/server.cpp
similarity index 92%
rename from src/tcp/server.cpp
rename to src/flame/tcp/server.cpp
index 463984d..f34061e 100644
--- a/src/tcp/server.cpp
+++ b/src/flame/tcp/server.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "../udp/udp.h"
 #include "../time/time.h"
@@ -84,9 +83,9 @@ namespace flame::tcp {
                     try {
                         cb.call({obj});
                     } catch(const php::exception& ex) {
-                        auto ft = gcontroller->cbmap->equal_range("exception");
-                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
-
+                        // 调用用户异常回调
+                        gcontroller->call_user_cb("exception", {ex});
+                        // 记录错误信息
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
                     }
diff --git a/src/tcp/server.h b/src/flame/tcp/server.h
similarity index 93%
rename from src/tcp/server.h
rename to src/flame/tcp/server.h
index 4b463a8..940a81a 100644
--- a/src/tcp/server.h
+++ b/src/flame/tcp/server.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::tcp {
     class server : public php::class_base {
diff --git a/src/tcp/socket.cpp b/src/flame/tcp/socket.cpp
similarity index 96%
rename from src/tcp/socket.cpp
rename to src/flame/tcp/socket.cpp
index 9ce3c8a..0db883d 100644
--- a/src/tcp/socket.cpp
+++ b/src/flame/tcp/socket.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "socket.h"
 
diff --git a/src/tcp/socket.h b/src/flame/tcp/socket.h
similarity index 88%
rename from src/tcp/socket.h
rename to src/flame/tcp/socket.h
index 8463bd9..4ecd4f3 100644
--- a/src/tcp/socket.h
+++ b/src/flame/tcp/socket.h
@@ -1,7 +1,7 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
+#include "../../coroutine_mutex.h"
 #include "../coroutine.h"
-#include "../coroutine_mutex.h"
 
 namespace flame::tcp {
 
diff --git a/src/tcp/tcp.cpp b/src/flame/tcp/tcp.cpp
similarity index 96%
rename from src/tcp/tcp.cpp
rename to src/flame/tcp/tcp.cpp
index 7a4946f..ed297c2 100644
--- a/src/tcp/tcp.cpp
+++ b/src/flame/tcp/tcp.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "../udp/udp.h"
 #include "tcp.h"
diff --git a/src/tcp/tcp.h b/src/flame/tcp/tcp.h
similarity index 82%
rename from src/tcp/tcp.h
rename to src/flame/tcp/tcp.h
index b2d61e2..9b21a4b 100644
--- a/src/tcp/tcp.h
+++ b/src/flame/tcp/tcp.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::tcp {
     void declare(php::extension_entry &ext);
diff --git a/src/time/time.cpp b/src/flame/time/time.cpp
similarity index 95%
rename from src/time/time.cpp
rename to src/flame/time/time.cpp
index bee3ccd..7913e94 100644
--- a/src/time/time.cpp
+++ b/src/flame/time/time.cpp
@@ -1,5 +1,4 @@
 #include "time.h"
-#include "../controller.h"
 #include "../coroutine.h"
 
 namespace flame::time {
diff --git a/src/time/time.h b/src/flame/time/time.h
similarity index 87%
rename from src/time/time.h
rename to src/flame/time/time.h
index 12d3408..f3e666f 100644
--- a/src/time/time.h
+++ b/src/flame/time/time.h
@@ -1,4 +1,4 @@
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::time {
     void declare(php::extension_entry &ext);
diff --git a/src/toml/_decode.cpp b/src/flame/toml/_decode.cpp
similarity index 100%
rename from src/toml/_decode.cpp
rename to src/flame/toml/_decode.cpp
diff --git a/src/toml/_decode.h b/src/flame/toml/_decode.h
similarity index 75%
rename from src/toml/_decode.h
rename to src/flame/toml/_decode.h
index 013903f..7af516c 100755
--- a/src/toml/_decode.h
+++ b/src/flame/toml/_decode.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::toml {
     void decode_inplace(php::string& str);
diff --git a/src/toml/_executor.cpp b/src/flame/toml/_executor.cpp
similarity index 100%
rename from src/toml/_executor.cpp
rename to src/flame/toml/_executor.cpp
diff --git a/src/flame/toml/_executor.h b/src/flame/toml/_executor.h
new file mode 100755
index 0000000..ce8a2cb
--- /dev/null
+++ b/src/flame/toml/_executor.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../../vendor.h"
+#include "toml.h"
+
+namespace flame::toml {
+
+    class _executor {
+    public:
+        _executor(php::array& root) noexcept
+        : root_(root) {}
+        void operator ()(const toml_parser::parser& p, std::string_view chunk);
+        void operator ()(const toml_parser::parser& p);
+
+    private:
+        php::array&   root_;
+        php::buffer buffer_;
+
+        static php::value restore(php::string& raw, std::uint8_t value_type);
+    };
+}
diff --git a/src/toml/toml.cpp b/src/flame/toml/toml.cpp
similarity index 99%
rename from src/toml/toml.cpp
rename to src/flame/toml/toml.cpp
index 21ab472..5b27819 100755
--- a/src/toml/toml.cpp
+++ b/src/flame/toml/toml.cpp
@@ -1,5 +1,6 @@
 #include "toml.h"
 #include "_executor.h"
+#include <fstream>
 
 namespace flame::toml {
 
diff --git a/src/toml/toml.h b/src/flame/toml/toml.h
similarity index 92%
rename from src/toml/toml.h
rename to src/flame/toml/toml.h
index 9f5e192..c0dab8b 100755
--- a/src/toml/toml.h
+++ b/src/flame/toml/toml.h
@@ -1,4 +1,4 @@
-#include "../vendor.h"
+#include "../../vendor.h"
 #include <toml/toml.hpp>
 namespace toml_parser = llparse::toml;
 
diff --git a/src/udp/server.cpp b/src/flame/udp/server.cpp
similarity index 96%
rename from src/udp/server.cpp
rename to src/flame/udp/server.cpp
index 40d8117..9fb5864 100644
--- a/src/udp/server.cpp
+++ b/src/flame/udp/server.cpp
@@ -1,6 +1,5 @@
-#include "../controller.h"
+#include "../../coroutine_queue.h"
 #include "../coroutine.h"
-#include "../coroutine_queue.h"
 #include "../time/time.h"
 #include "udp.h"
 #include "server.h"
@@ -80,9 +79,9 @@ namespace flame::udp {
                     try {
                         cb_.call({x->first, x->second});
                     } catch(const php::exception& ex) {
-                        auto ft = gcontroller->cbmap->equal_range("exception");
-                        for(auto i=ft.first; i!=ft.second; ++i) i->second.call({ex});
-
+                        // 调用用户异常回调
+                        gcontroller->call_user_cb("exception", {ex});
+                        // 记录错误信息
                         php::object obj = ex;
                         std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
                     }
diff --git a/src/udp/server.h b/src/flame/udp/server.h
similarity index 90%
rename from src/udp/server.h
rename to src/flame/udp/server.h
index 11e5fbb..d3b0d4a 100644
--- a/src/udp/server.h
+++ b/src/flame/udp/server.h
@@ -1,7 +1,7 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
+#include "../../coroutine_mutex.h"
 #include "../coroutine.h"
-#include "../coroutine_mutex.h"
 
 namespace flame::udp {
     class server: public php::class_base {
diff --git a/src/udp/socket.cpp b/src/flame/udp/socket.cpp
similarity index 97%
rename from src/udp/socket.cpp
rename to src/flame/udp/socket.cpp
index c7f554c..25351a1 100644
--- a/src/udp/socket.cpp
+++ b/src/flame/udp/socket.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "udp.h"
 #include "socket.h"
diff --git a/src/udp/socket.h b/src/flame/udp/socket.h
similarity index 90%
rename from src/udp/socket.h
rename to src/flame/udp/socket.h
index 294a455..8e95ae5 100644
--- a/src/udp/socket.h
+++ b/src/flame/udp/socket.h
@@ -1,7 +1,7 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
+#include "../../coroutine_mutex.h"
 #include "../coroutine.h"
-#include "../coroutine_mutex.h"
 
 namespace flame::udp {
     
diff --git a/src/udp/udp.cpp b/src/flame/udp/udp.cpp
similarity index 96%
rename from src/udp/udp.cpp
rename to src/flame/udp/udp.cpp
index 565143b..63f7e62 100644
--- a/src/udp/udp.cpp
+++ b/src/flame/udp/udp.cpp
@@ -1,4 +1,3 @@
-#include "../controller.h"
 #include "../coroutine.h"
 #include "udp.h"
 #include "socket.h"
diff --git a/src/udp/udp.h b/src/flame/udp/udp.h
similarity index 90%
rename from src/udp/udp.h
rename to src/flame/udp/udp.h
index bce6130..b61a209 100644
--- a/src/udp/udp.h
+++ b/src/flame/udp/udp.h
@@ -1,5 +1,5 @@
 #pragma once
-#include "../vendor.h"
+#include "../../vendor.h"
 
 namespace flame::udp {
     extern std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
diff --git a/src/flame/version.cpp b/src/flame/version.cpp
new file mode 100644
index 0000000..c1af73a
--- /dev/null
+++ b/src/flame/version.cpp
@@ -0,0 +1,43 @@
+#include "version.h"
+#include <boost/version.hpp>
+#include <hiredis/hiredis.h>
+#include <nghttp2/nghttp2ver.h>
+#include <openssl/opensslv.h>
+#include <mariadb/mariadb_version.h>
+#include <librdkafka/rdkafka.h>
+// #include <mongoc/mongoc-version.h>
+#include <mongoc/mongoc.h>
+#include <curl/curlver.h>
+
+namespace flame {
+
+#define VERSION_MACRO(major, minor, patch) VERSION_JOIN(major, minor, patch)
+#define VERSION_JOIN(major, minor, patch) #major"."#minor"."#patch
+
+static std::string openssl_version_str() {
+    std::string version = (boost::format("%d.%d.%d")
+        % ((OPENSSL_VERSION_NUMBER & 0xff0000000L) >> 28)
+        % ((OPENSSL_VERSION_NUMBER & 0x00ff00000L) >> 20)
+        % ((OPENSSL_VERSION_NUMBER & 0x0000ff000L) >> 12) ).str();
+    char status = (OPENSSL_VERSION_NUMBER & 0x000000ff0L) >>  4;
+    if (status > 0) {
+        version.push_back('a' + status - 1);
+    }
+    return version;
+}
+
+    void version::declare(php::extension_entry& ext) {
+        ext
+            .desc({"vendor/openssl", openssl_version_str()})
+            .desc({"vendor/boost", BOOST_LIB_VERSION})
+            .desc({"vendor/phpext", PHPEXT_LIB_VERSION})
+            .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
+            // .desc({"vendor/mysqlc", mysql_get_client_info()})
+            .desc({"vendor/mariac", MARIADB_PACKAGE_VERSION})
+            .desc({"vendor/amqpcpp", "4.1.5"})
+            .desc({"vendor/rdkafka", rd_kafka_version_str()})
+            .desc({"vendor/mongoc", MONGOC_VERSION_S})
+            .desc({"vendor/nghttp2", NGHTTP2_VERSION})
+            .desc({"vendor/curl", LIBCURL_VERSION});
+    }
+}
\ No newline at end of file
diff --git a/src/flame/version.h b/src/flame/version.h
new file mode 100644
index 0000000..471fd7f
--- /dev/null
+++ b/src/flame/version.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame {
+    class version {
+    public:
+        static void declare(php::extension_entry& ext);
+    };   
+}
\ No newline at end of file
diff --git a/src/flame/worker.cpp b/src/flame/worker.cpp
new file mode 100644
index 0000000..4526335
--- /dev/null
+++ b/src/flame/worker.cpp
@@ -0,0 +1,204 @@
+#include "controller.h"
+#include "worker.h"
+#include "coroutine.h"
+
+#include "log/log.h"
+#include "os/os.h"
+#include "time/time.h"
+#include "mysql/mysql.h"
+#include "redis/redis.h"
+#include "mongodb/mongodb.h"
+#include "kafka/kafka.h"
+#include "rabbitmq/rabbitmq.h"
+#include "tcp/tcp.h"
+#include "udp/udp.h"
+#include "http/http.h"
+#include "hash/hash.h"
+#include "encoding/encoding.h"
+#include "compress/compress.h"
+#include "toml/toml.h"
+
+namespace flame {
+    worker* worker::ww_;
+
+    void worker::declare(php::extension_entry& ext) {
+        ext
+        .on_request_shutdown([] (php::extension_entry& ext) -> bool {
+            if (flame::coroutine::count > 0 && (gcontroller->status & controller::STATUS_RUN) == 0) {
+                if (!php::error::exists()) std::cerr << "[FATAL] process exited prematurely: exception or missing 'flame\\run();' ?\n";
+                _exit(-1);
+            }
+            return true;
+        })
+        .function<worker::init>("flame\\init", {
+            {"process_name", php::TYPE::STRING},
+            {"options", php::TYPE::ARRAY, false, true},
+        })
+        
+        .function<worker::go>("flame\\go", {
+            {"coroutine", php::TYPE::CALLABLE},
+        })
+        .function<worker::on>("flame\\on", {
+            {"event", php::TYPE::STRING},
+            {"callback", php::TYPE::CALLABLE},
+        })
+        .function<worker::run>("flame\\run")
+        .function<worker::quit>("flame\\quit")
+        .function<worker::co_id>("flame\\co_id")
+        .function<worker::co_ct>("flame\\co_ct")
+        .function<worker::co_ct>("flame\\co_count")
+        .function<worker::set>("flame\\set", {
+            {"target", php::TYPE::ARRAY, true},
+            {"fields", php::TYPE::STRING},
+            {"values", php::TYPE::UNDEFINED},
+        })
+        .function<worker::get>("flame\\get", {
+            {"target", php::TYPE::ARRAY},
+            {"fields", php::TYPE::STRING},
+        });
+        // 子命名空间模块注册
+        flame::log::declare(ext);
+        flame::os::declare(ext);
+        flame::time::declare(ext);
+        flame::mysql::declare(ext);
+        flame::redis::declare(ext);
+        flame::mongodb::declare(ext);
+        flame::kafka::declare(ext);
+        flame::rabbitmq::declare(ext);
+        flame::tcp::declare(ext);
+        flame::udp::declare(ext);
+        flame::http::declare(ext);
+        flame::hash::declare(ext);
+        flame::encoding::declare(ext);
+        flame::compress::declare(ext);
+        flame::toml::declare(ext);
+    }
+
+    php::value worker::init(php::parameters& params) {
+        php::array options = php::array(0);
+        if(params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
+        if (options.exists("timeout"))
+            gcontroller->worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
+        else
+            gcontroller->worker_quit = 3000;
+        
+        gcontroller->status |= controller::STATUS_INITIALIZED;
+        // 设置进程标题
+        std::string title = params[0];
+        if (gcontroller->worker_size > 0) {
+            std::string index = gcontroller->env["FLAME_CUR_WORKER"].to_string();
+            php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
+        }
+        else
+            php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
+        
+        gcontroller->init(options);
+        worker::ww_ = new worker();
+        return nullptr;
+    }
+
+    php::value worker::go(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        php::callable fn = params[0];
+        flame::coroutine::start(fn);
+        return nullptr;
+    }
+
+    php::value worker::on(php::parameters &params) {
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        std::string event = params[0].to_string();
+        if (!params[1].type_of(php::TYPE::CALLABLE)) throw php::exception(zend_ce_type_error, "Failed to set callback: callable required", -1);
+        gcontroller->on_user(event, params[1]);
+        return nullptr;
+    }
+
+    php::value worker::run(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        gcontroller->status |= controller::STATUS_RUN;
+
+        auto tswork = boost::asio::make_work_guard(gcontroller->context_y);
+        std::thread ts[4];
+        for (int i=0; i<4; ++i) {
+            ts[i] = std::thread([] {
+                gcontroller->context_y.run();
+            });
+        }
+        gcontroller->context_x.run();
+        tswork.reset();
+        worker::get()->sw_.close();
+        for (int i=0; i<4; ++i) ts[i].join();
+        gcontroller->stop();
+        return nullptr;
+    }
+
+    php::value worker::quit(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_RUN) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+        
+        gcontroller->context_x.stop();
+        gcontroller->context_y.stop();
+        return nullptr;
+    }
+
+    php::value worker::co_id(php::parameters& params) {
+        return reinterpret_cast<uintptr_t>(coroutine::current.get());
+    }
+
+    php::value worker::co_ct(php::parameters& params) {
+        return coroutine::count;
+    }
+
+    php::value worker::get(php::parameters& params) {
+        php::array  target = params[0];
+        php::string fields = params[1];
+        return flame::toml::get(target, { fields.data(), fields.size() }, 0);
+    }
+
+    php::value worker::set(php::parameters& params) {
+        php::array  target = params.get(0, true);
+        php::string fields = params[1];
+        php::value  values = params[2];
+        flame::toml::set(target, {fields.data(), fields.size()}, 0, values);
+        return nullptr;
+    }
+
+    worker::worker() 
+    : lm_()
+    , sw_(gcontroller->context_y) {
+
+        gcontroller->lm = &lm_;
+        sw_.lg_ = lm_.index(0);
+        sw_.start(std::bind(&worker::on_signal, this, std::placeholders::_1, std::placeholders::_2));
+    }
+
+    void worker::on_signal(const boost::system::error_code error, int sig) {
+        if(error) return;
+        switch(sig) {
+        case SIGINT:
+        case SIGTERM:
+            if(++close_ > 1) {
+                gcontroller->context_x.stop();
+                gcontroller->context_y.stop();
+                return;
+            }
+            else coroutine::start(php::callable([] (php::parameters& params) -> php::value { // 通知用户退出(超时后主进程将杀死子进程)
+                gcontroller->call_user_cb("quit");
+                return nullptr;
+            }));
+            break;
+        case SIGUSR1:
+            gcontroller->status ^= controller::STATUS_CLOSECONN;
+            break;
+        case SIGUSR2: // 日志重载, 与子进程无关
+            break;
+        }
+        // 强制结束时不再重复监听
+        sw_.start(std::bind(&worker::on_signal, this, std::placeholders::_1, std::placeholders::_2));
+    }
+}
\ No newline at end of file
diff --git a/src/flame/worker.h b/src/flame/worker.h
new file mode 100644
index 0000000..ff8f30e
--- /dev/null
+++ b/src/flame/worker.h
@@ -0,0 +1,38 @@
+#pragma once
+#include "../vendor.h"
+#include "../logger_worker.h"
+#include "../signal_watcher.h"
+
+namespace flame {
+    class worker {
+    public:
+        static inline worker* get() {
+            return ww_;
+        }
+        static void declare(php::extension_entry& ext);
+        
+        // 主流程相关
+        static php::value init(php::parameters& params);
+        static php::value go(php::parameters& params);
+        static php::value on(php::parameters &params);
+        static php::value run(php::parameters& params);
+        static php::value quit(php::parameters& params);
+        // 协程相关
+        static php::value co_id(php::parameters& params);
+        static php::value co_ct(php::parameters& params);
+        // 级联数组设置、读取
+        static php::value get(php::parameters& params);
+        static php::value set(php::parameters& params);
+
+        worker();
+        void on_signal(const boost::system::error_code error, int sig);
+    private:
+        static worker* ww_;
+
+        logger_manager_worker lm_;
+        signal_watcher sw_;
+
+        int close_ = 0;
+        int stats_ = 0;
+    };
+}
\ No newline at end of file
diff --git a/src/ipc.cpp b/src/ipc.cpp
new file mode 100644
index 0000000..ee85c7d
--- /dev/null
+++ b/src/ipc.cpp
@@ -0,0 +1,15 @@
+#include "ipc.h"
+
+
+// coroutine::start(php::callable([this] (php::parameters& params) -> php::value {
+//         coroutine_handler ch { coroutine::current };
+//         boost::system::error_code ec;
+// RECONNECT:
+//         socket_.async_connect(address_, ch[ec]);
+//         if(ec) {
+//             co_sleep(std::chrono::milliseconds(std::rand() % 500 + 500), ch);
+//             goto RECONNECT;
+//         }
+        
+//         return nullptr;
+//     }));
\ No newline at end of file
diff --git a/src/ipc.h b/src/ipc.h
new file mode 100644
index 0000000..5c2002d
--- /dev/null
+++ b/src/ipc.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "vendor.h"
+
+class ipc_abstract {
+public:
+    
+    boost::asio::local::stream_protocol::socket    socket_;
+    boost::asio::local::stream_protocol::endpoint address_;
+};
diff --git a/src/logger.h b/src/logger.h
new file mode 100644
index 0000000..e448748
--- /dev/null
+++ b/src/logger.h
@@ -0,0 +1,47 @@
+#pragma once
+
+class logger {
+public:
+    logger(unsigned int index)
+    : ref_(1)
+    , idx_(index) { }
+
+    virtual ~logger() = default;
+
+    unsigned int addref() { return ++ref_; }
+    unsigned int delref() { return --ref_; }
+
+    unsigned int index() {
+        return idx_;
+    }
+    virtual void reload() {}
+    virtual std::ostream& stream() = 0;
+    void write(std::string_view data, bool flush = true) {
+        std::ostream& os = stream();
+        os << data;
+        if(flush) os.flush();
+    }
+private:
+    unsigned int ref_;
+    unsigned int idx_;
+};
+
+class logger_manager {
+public:
+    virtual unsigned int connect(const std::string& filepath) = 0;
+    void reload() {
+        for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload();
+    }
+    // 删除对应的 logger 引用
+    void destroy(unsigned int w) {
+        if(logger_[w]->delref() == 0) {
+            logger_.erase(w);
+        }
+    }
+    logger* index(unsigned int w) {
+        return logger_[w].get();
+    }
+protected:
+    std::map<unsigned int, std::unique_ptr<logger>> logger_;
+};
+
diff --git a/src/logger_master.cpp b/src/logger_master.cpp
new file mode 100644
index 0000000..2f9b562
--- /dev/null
+++ b/src/logger_master.cpp
@@ -0,0 +1,24 @@
+#include "logger_master.h"
+
+void logger_master::reload() {
+    file_.reset(new std::ofstream(path_, std::ios_base::out | std::ios_base::app));
+    if (file_->fail()) // 文件打开失败时不会抛出异常，需要额外的状态检查
+        std::cerr << "(ERROR) failed to create/open logger: file cannot be created or accessed\n";
+    else return;
+    file_.reset(&std::clog, boost::null_deleter());
+}
+
+unsigned int logger_manager_master::connect(const std::string& filepath) {
+    std::filesystem::path path = filepath;
+    
+    for(auto i=logger_.begin();i!=logger_.end();++i) {
+        if(static_cast<logger_master*>(i->second.get())->path_ == path) {
+            i->second->addref();
+            return i->second->index();
+        }
+    }
+    auto p = logger_.insert({index_, std::make_unique<logger_master>(path, index_)});
+    ++index_;
+    p.first->second->reload();
+    return p.first->second->index();
+}
diff --git a/src/logger_master.h b/src/logger_master.h
new file mode 100644
index 0000000..cd83678
--- /dev/null
+++ b/src/logger_master.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "vendor.h"
+#include <fstream>
+#include "logger.h"
+
+class logger_master: public logger {
+private:
+    std::shared_ptr<std::ostream> file_;
+    std::filesystem::path         path_;
+public:
+    logger_master(std::filesystem::path path, int index)
+    : logger(index)
+    , path_(path) {}
+
+    void reload() override;
+    std::ostream& stream() override {
+        return *file_;
+    }
+    friend class logger_manager_master;
+};
+
+class logger_manager_master: public logger_manager {
+public:
+    unsigned int connect(const std::string& filepath) override;
+private:
+    unsigned int index_ = 0;
+};
\ No newline at end of file
diff --git a/src/logger_worker.cpp b/src/logger_worker.cpp
new file mode 100644
index 0000000..de34984
--- /dev/null
+++ b/src/logger_worker.cpp
@@ -0,0 +1,11 @@
+#include "logger_worker.h"
+
+std::ostream& logger_worker::stream() {
+    // TODO 需要 IPC 支持
+    return std::cerr;
+}
+
+unsigned int logger_manager_worker::connect(const std::string& filepath) {
+    // TODO 需要 IPC 支持
+    return 0;
+}
\ No newline at end of file
diff --git a/src/logger_worker.h b/src/logger_worker.h
new file mode 100644
index 0000000..d937b50
--- /dev/null
+++ b/src/logger_worker.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "vendor.h"
+#include "logger.h"
+
+class logger_worker: public logger {
+public:
+    std::ostream& stream() override;
+};
+
+class logger_manager_worker: public logger_manager {
+public:
+    unsigned int connect(const std::string& filepath) override;
+};
diff --git a/src/process_child.cpp b/src/process_child.cpp
new file mode 100755
index 0000000..bdc6e2d
--- /dev/null
+++ b/src/process_child.cpp
@@ -0,0 +1,65 @@
+#include "process_child.h"
+#include "process_manager.h"
+#include "logger.h"
+#include "util.h"
+
+extern "C" {
+PHPAPI extern char *php_ini_opened_path;
+}
+
+static std::string php_cmd() {
+    std::ostringstream ss;
+    ss << php::constant("PHP_BINARY");
+    ss << " -c " << php_ini_opened_path;
+    php::array argv = php::server("argv");
+    for (auto i = argv.begin(); i != argv.end(); ++i) ss << " " << i->second;
+    return ss.str();
+}
+
+process_child::process_child(boost::asio::io_context& io, process_manager* m, int i)
+: manager_(m), index_(i)
+, sout_(io), eout_(io) {
+    // 准备命令行及环境变量（完全重新启动一个新的 PHP， 通过环境变量标识其为工作进程）
+    boost::process::environment env = boost::this_process::environment();
+    env["FLAME_CUR_WORKER"] = std::to_string(i + 1);
+    // 构造进程
+    proc_ = boost::process::child(io, php_cmd(), env,
+        boost::process::std_out > sout_, boost::process::std_err > eout_,
+        // 结束回调
+        boost::process::on_exit = [this, &io] (int exit_code, const std::error_code &error) {
+            if (error.value() == static_cast<int>(std::errc::no_child_process)) return;
+            boost::asio::post(io, [exit_code, this] () {
+                manager_->on_child_close(this, exit_code == 0);
+            });
+        });
+    redirect_output(sout_, sbuf_);
+    redirect_output(eout_, ebuf_);
+
+    boost::asio::post(io, [this] () {
+        manager_->on_child_start(this);
+    });
+}
+
+void process_child::redirect_output(boost::process::async_pipe& pipe, std::string& data) {
+    boost::asio::async_read_until(pipe, boost::asio::dynamic_buffer(data), '\n', [this, &pipe, &data] (const boost::system::error_code &error, std::size_t nread) {
+        if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
+            ; // std::cerr << "(INFO) IGNORE sout\n";
+        else if (error) {
+            manager_->lg_->stream() << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
+        }
+        else {
+            manager_->lg_->write({data.c_str(), nread}, true);
+            data.erase(0, nread);
+            redirect_output(pipe, data);
+        }
+    });
+}
+
+void process_child::close(bool force) {
+    if(force) proc_.terminate();
+    else ::kill(proc_.id(), SIGTERM);
+}
+
+void process_child::signal(int sig) {
+    ::kill(proc_.id(), sig);
+}
\ No newline at end of file
diff --git a/src/process_child.h b/src/process_child.h
new file mode 100755
index 0000000..66f449a
--- /dev/null
+++ b/src/process_child.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "vendor.h"
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
+
+class logger;
+class process_manager;
+class process_child {
+public:
+    process_child(boost::asio::io_context& io, process_manager* m, int i);
+    void close(bool force = false);
+    void signal(int sig);
+private:
+    process_manager*        manager_;
+    int                       index_;
+
+    boost::process::child      proc_;
+    boost::process::async_pipe sout_;
+    boost::process::async_pipe eout_;
+    std::string                sbuf_;
+    std::string                ebuf_;
+    void redirect_output(boost::process::async_pipe& pipe, std::string& buffer);
+
+    friend class process_manager;
+};
\ No newline at end of file
diff --git a/src/process_manager.cpp b/src/process_manager.cpp
new file mode 100644
index 0000000..411cf95
--- /dev/null
+++ b/src/process_manager.cpp
@@ -0,0 +1,73 @@
+#include "process_manager.h"
+#include "coroutine.h"
+#include "util.h"
+
+process_manager::process_manager(boost::asio::io_context& io, unsigned int count)
+: io_(io)
+, count_(count) {
+
+}
+
+void process_manager::on_child_start(process_child* w) {
+
+}
+
+void process_manager::on_child_close(process_child* w, bool normal) {
+
+}
+
+void process_manager::start() {
+    for(int i=0;i<count_;++i) {
+        child_[i].reset(new process_child(io_, this, i));
+    }
+}
+
+void process_manager::restart(coroutine_handler& ch) {
+    std::vector<std::unique_ptr<process_child>> start;
+    for(int i=0;i<count_;++i) {
+        start[i].reset(new process_child(io_, this, i));
+        ch_start_.reset(ch);
+        ch_start_.suspend(); // 等待进程启动回调 on_child_event
+        ch_start_.reset();
+        // 启动间隔
+        util::co_sleep(io_, std::chrono::milliseconds(140), ch);
+        // 新进程已经启动 1/4 后，开始停止原进程（一次性动作）
+        if(i == count_ / 4) coroutine::start(io_.get_executor(), [this] (coroutine_handler ch) {
+            close(false, ch);
+        });
+    }
+    child_ = std::move(start);
+}
+
+void process_manager::close(unsigned int timeout_ms, coroutine_handler& ch) {
+    std::vector<std::unique_ptr<process_child>> closing = std::move(child_);
+    for(int i=0;i<count_;++i) closing[i]->close(timeout_ms == 0);
+    if(timeout_ms == 0) return;
+
+    int  stopped = 0;
+    bool timeout = false;
+    boost::asio::steady_timer tm(io_);
+    tm.expires_after(std::chrono::milliseconds(timeout_ms));
+    tm.async_wait([&ch, &timeout, this] (const boost::system::error_code& error) mutable {
+        if(error) return;
+        timeout = true;
+        ch.resume();
+    });
+WAIT_FOR_CLOSE:
+    ch_close_.reset(ch);
+    ch_close_.suspend(); // 等待回调 on_child_event
+    ch_close_.reset();
+
+    if(timeout) {
+        child_ = std::move(closing);
+        close(0, ch);
+        return;
+    }
+    if(++stopped < count_) goto WAIT_FOR_CLOSE;
+    else tm.cancel();
+}
+
+
+void process_manager::signal(int sig) {
+    for(int i=0;i<count_;++i) child_[i]->signal(sig);
+}
diff --git a/src/process_manager.h b/src/process_manager.h
new file mode 100644
index 0000000..87a9427
--- /dev/null
+++ b/src/process_manager.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "vendor.h"
+#include "coroutine.h"
+#include "process_child.h"
+
+class logger;
+class process_manager {
+public:
+    process_manager(boost::asio::io_context& io, unsigned int count);
+    void start();
+    void restart(coroutine_handler& ch);
+    void close(unsigned int timeout_ms, coroutine_handler& ch);
+    void signal(int signal);
+    unsigned int count() {
+        return count_;
+    }
+    logger*                  lg_; // master -> set
+private:
+    boost::asio::io_context& io_;
+    unsigned int          count_;
+
+    std::vector<std::unique_ptr<process_child>> child_;
+
+    void on_child_start(process_child* w);
+    void on_child_close(process_child* w, bool normal);
+
+    coroutine_handler ch_close_;
+    coroutine_handler ch_start_;
+    
+    friend class process_child;
+};
diff --git a/src/signal_watcher.cpp b/src/signal_watcher.cpp
new file mode 100644
index 0000000..ebbce1b
--- /dev/null
+++ b/src/signal_watcher.cpp
@@ -0,0 +1,14 @@
+#include "signal_watcher.h"
+#include "logger.h"
+
+signal_watcher::signal_watcher(boost::asio::io_context& io)
+: ss_(new boost::asio::signal_set(io)) {
+    ss_->add(SIGINT);
+    ss_->add(SIGTERM);
+    ss_->add(SIGUSR1);
+    ss_->add(SIGUSR2);
+}
+
+void signal_watcher::close() {
+    ss_.reset();
+}
\ No newline at end of file
diff --git a/src/signal_watcher.h b/src/signal_watcher.h
new file mode 100644
index 0000000..41561c1
--- /dev/null
+++ b/src/signal_watcher.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "vendor.h"
+
+class logger;
+class signal_watcher {
+public:
+    explicit signal_watcher(boost::asio::io_context& io);
+    template <class Handler>
+    void start(Handler&& cb) {
+        ss_->async_wait(cb);
+    }
+    void close();
+    logger*  lg_;
+private:
+    std::unique_ptr<boost::asio::signal_set> ss_;
+    int close_ = 0;
+    int stats_ = 0;
+};
\ No newline at end of file
diff --git a/src/toml/_executor.h b/src/toml/_executor.h
deleted file mode 100755
index 79bff03..0000000
--- a/src/toml/_executor.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-#include "../vendor.h"
-#include "toml.h"
-
-namespace flame::toml {
-
-class _executor {
-public:
-    _executor(php::array& root) noexcept
-    : root_(root) {}
-    void operator ()(const toml_parser::parser& p, std::string_view chunk);
-    void operator ()(const toml_parser::parser& p);
-
-private:
-    php::array&   root_;
-    php::buffer buffer_;
-
-    static php::value restore(php::string& raw, std::uint8_t value_type);
-};
-
-
-}
\ No newline at end of file
diff --git a/src/url.cpp b/src/url.cpp
index 0e3221d..cdccffb 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -2,64 +2,61 @@
 #include "url.h"
 #include <curl/curl.h>
 
-namespace flame {
-    typedef ::parser::separator_parser<std::string, std::string> parser_t;
+typedef parser::separator_parser<std::string, std::string> parser_t;
 
-    url::url(const php::string &str, bool parse_query)
-    : raw_(str)
-    , port(0) { // 原始类型 port 初始值可能随机
-        auto u = curl_url();
-        curl_url_set(u, CURLUPART_URL, str.c_str(), CURLU_NON_SUPPORT_SCHEME);
-        char * tmp;
-        if(curl_url_get(u, CURLUPART_SCHEME, &tmp, 0) == CURLUE_OK) { // SCHEME
-            schema.assign(tmp);
-            curl_free(tmp);
-        }
-        if(curl_url_get(u, CURLUPART_USER, &tmp, 0) == CURLUE_OK) { // USER
-            user.assign(tmp);
-            curl_free(tmp);
-        }
-        if(curl_url_get(u, CURLUPART_PASSWORD, &tmp, 0) == CURLUE_OK) { // PASS
-            pass.assign(tmp);
-            curl_free(tmp);
-        }
-        if(curl_url_get(u, CURLUPART_HOST, &tmp, 0) == CURLUE_OK) { // USER
-            host.assign(tmp);
-            curl_free(tmp);
-        }
-        if(curl_url_get(u, CURLUPART_PORT, &tmp, 0) == CURLUE_OK) { // PORT
-            port = std::atoi(tmp);
-            curl_free(tmp);
-        }
-        if(curl_url_get(u, CURLUPART_PATH, &tmp, 0) == CURLUE_OK) { // PATH
-            path.assign(tmp);
-            curl_free(tmp);
-        }
-        if(parse_query && curl_url_get(u, CURLUPART_QUERY, &tmp, 0) == CURLUE_OK) { // QUERY
-            parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
-                php::url_decode_inplace(et.second.data(), et.second.size());
-                query[et.first] = et.second;
-                std::cout << et.first << ": " << et.second << "\n";
-            });
-            p.parse(tmp, strlen(tmp));
-            p.end();
-            curl_free(tmp);
-        }
+url::url(const php::string &str, bool parse_query)
+: raw_(str)
+, port(0) { // 原始类型 port 初始值可能随机
+    auto u = curl_url();
+    curl_url_set(u, CURLUPART_URL, str.c_str(), CURLU_NON_SUPPORT_SCHEME);
+    char * tmp;
+    if(curl_url_get(u, CURLUPART_SCHEME, &tmp, 0) == CURLUE_OK) { // SCHEME
+        schema.assign(tmp);
+        curl_free(tmp);
     }
+    if(curl_url_get(u, CURLUPART_USER, &tmp, 0) == CURLUE_OK) { // USER
+        user.assign(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_PASSWORD, &tmp, 0) == CURLUE_OK) { // PASS
+        pass.assign(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_HOST, &tmp, 0) == CURLUE_OK) { // USER
+        host.assign(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_PORT, &tmp, 0) == CURLUE_OK) { // PORT
+        port = std::atoi(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_PATH, &tmp, 0) == CURLUE_OK) { // PATH
+        path.assign(tmp);
+        curl_free(tmp);
+    }
+    if(parse_query && curl_url_get(u, CURLUPART_QUERY, &tmp, 0) == CURLUE_OK) { // QUERY
+        parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
+            php::url_decode_inplace(et.second.data(), et.second.size());
+            query[et.first] = et.second;
+            std::cout << et.first << ": " << et.second << "\n";
+        });
+        p.parse(tmp, strlen(tmp));
+        p.end();
+        curl_free(tmp);
+    }
+}
 
-    std::string url::str(bool with_query, bool update) {
-        if (update) {
-            std::ostringstream ss;
-            ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path;
-            if (with_query) {
-                ss << "?";
-                for (auto i=query.begin();i!=query.end();++i) {
-                    ss << i->first << "=" << php::url_encode(i->second.c_str(), i->second.size()) << "&";
-                }
+std::string url::str(bool with_query, bool update) {
+    if (update) {
+        std::ostringstream ss;
+        ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path;
+        if (with_query) {
+            ss << "?";
+            for (auto i=query.begin();i!=query.end();++i) {
+                ss << i->first << "=" << php::url_encode(i->second.c_str(), i->second.size()) << "&";
             }
-            raw_ = ss.str();
         }
-        return raw_;
+        raw_ = ss.str();
     }
-
-} // namespace flame
\ No newline at end of file
+    return raw_;
+}
diff --git a/src/url.h b/src/url.h
index 6b863eb..cc030e9 100644
--- a/src/url.h
+++ b/src/url.h
@@ -1,20 +1,18 @@
 #pragma once
 #include "vendor.h"
 
-namespace flame {
-    class url {
-    public:
-        url(const php::string& str, bool parse_query=true);
-        std::string schema;
-        std::string   host;
-        std::uint16_t port;
-        std::string   path;
-        std::map<std::string, std::string> query;
-        std::string   user;
-        std::string   pass;
+class url {
+public:
+    url(const php::string& str, bool parse_query=true);
+    std::string schema;
+    std::string   host;
+    std::uint16_t port;
+    std::string   path;
+    std::map<std::string, std::string> query;
+    std::string   user;
+    std::string   pass;
 
-        std::string   str(bool update = false, bool with_query = true);
-    private:
-        std::string raw_;
-    };
-}
\ No newline at end of file
+    std::string   str(bool update = false, bool with_query = true);
+private:
+    std::string raw_;
+};
\ No newline at end of file
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..36c9358
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,23 @@
+#include "util.h"
+#include "coroutine.h"
+
+const char* util::system_time() {
+    static char buffer[24] = {0};
+    std::time_t t = std::time(nullptr);
+    struct tm *m = std::localtime(&t);
+    sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d",
+            1900 + m->tm_year,
+            1 + m->tm_mon,
+            m->tm_mday,
+            m->tm_hour,
+            m->tm_min,
+            m->tm_sec);
+    return buffer;
+}
+
+
+void util::co_sleep(boost::asio::io_context& io, std::chrono::milliseconds ms, coroutine_handler& ch) {
+    boost::asio::steady_timer tm(io);
+    tm.expires_after(ms);
+    tm.async_wait(ch);
+}
\ No newline at end of file
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..3b775a4
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "vendor.h"
+
+class coroutine_handler;
+class util {
+public:
+    static const char* system_time();
+    static void co_sleep(boost::asio::io_context& io, std::chrono::milliseconds ms, coroutine_handler& ch);
+};
diff --git a/src/vendor.h b/src/vendor.h
index a83c07f..d2b314a 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #define EXTENSION_NAME "flame"
-#define EXTENSION_VERSION "0.14.0"
+#define EXTENSION_VERSION "0.15.0"
 
 
 #include <algorithm>
@@ -25,10 +25,10 @@
 #include <boost/context/fiber.hpp>
 #include <boost/context/segmented_stack.hpp>
 #include <boost/format.hpp>
-#include <boost/process.hpp>
-#include <boost/process/async.hpp>
 #include <boost/logic/tribool.hpp>
 #include <boost/logic/tribool_io.hpp>
 #include <boost/asio.hpp>
 using boost::asio::ip::tcp;
 using boost::asio::ip::udp;
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
diff --git a/test/coroutine_1.php b/test/coroutine_1.php
index 2c8d4e2..19ad46b 100644
--- a/test/coroutine_1.php
+++ b/test/coroutine_1.php
@@ -2,22 +2,19 @@
 flame\init("coroutine_1");
 
 flame\go(function() {
-    flame\go(function() {
-        for($i=0;$i<1000;++$i) {
-            flame\time\sleep(1);
-            flame\go(function() use($i) {
-                echo "x: ".flame\co_id()."\n";
-                flame\time\sleep(mt_rand(10, 50));
-                echo "a: $i\n";
-                flame\time\sleep(mt_rand(10, 50));
-                echo "b: $i\n";
-                flame\time\sleep(mt_rand(10, 50));
-                echo "c: $i\n";
-            });
-        }
-        echo "done2\n";
-    });
-    echo "done1\n";
+    flame\time\sleep(100);
+    echo "c1:", flame\co_id(), "\n";
+    flame\time\sleep(200);
+    echo "c1:", flame\co_id(), "\n";
 });
 
+
+flame\go(function() {
+    flame\time\sleep(200);
+    echo "c2:", flame\co_id(), "\n";
+    flame\time\sleep(100);
+    echo "c2:", flame\co_id(), "\n";
+});
+
+
 flame\run();
\ No newline at end of file
diff --git a/test/coroutine_2.php b/test/coroutine_2.php
new file mode 100644
index 0000000..0d26680
--- /dev/null
+++ b/test/coroutine_2.php
@@ -0,0 +1,23 @@
+<?php
+flame\init("coroutine_1");
+
+flame\go(function() {
+    flame\go(function() {
+        for($i=0;$i<1000;++$i) {
+            flame\time\sleep(1);
+            flame\go(function() use($i) {
+                echo "x: ".flame\co_id()."\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "a: $i\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "b: $i\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "c: $i\n";
+            });
+        }
+        echo "done2\n";
+    });
+    echo "done1\n";
+});
+
+flame\run();
\ No newline at end of file

From fe3a330df4e62681372eda5d08e48ad48efe9347 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 2 Jul 2019 19:19:32 +0800
Subject: [PATCH 141/146] =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=B5=81=E7=A8=8B?=
 =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=8C=E7=BB=9F=E4=B8=80=E5=AE=8C=E5=96=84?=
 =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=AF=B9=E8=B1=A1=E5=88=9B=E5=BB=BA=E3=80=81?=
 =?UTF-8?q?=E8=8E=B7=E5=8F=96=E3=80=81=E8=BE=93=E5=87=BA=E8=BF=87=E7=A8=8B?=
 =?UTF-8?q?;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/flame/controller.cpp        | 106 ++++++++++++++++++--------------
 src/flame/controller.h          |  84 +++++++++++++------------
 src/flame/coroutine.cpp         |  19 ++----
 src/flame/flame.cpp             |   4 +-
 src/flame/http/_handler.cpp     |   2 +-
 src/flame/kafka/_consumer.cpp   |   6 +-
 src/flame/kafka/_producer.cpp   |   6 +-
 src/flame/kafka/consumer.cpp    |   2 +-
 src/flame/log/log.cpp           |  64 +++++++++----------
 src/flame/log/log.h             |  13 ++--
 src/flame/log/logger.cpp        |  64 +++++++++++++++++++
 src/flame/log/logger.h          |  37 +++++++++++
 src/flame/master.cpp            |  28 ++++-----
 src/flame/master.h              |   4 +-
 src/flame/rabbitmq/consumer.cpp |   2 +-
 src/flame/tcp/server.cpp        |   2 +-
 src/flame/toml/_executor.cpp    |   2 +-
 src/flame/udp/server.cpp        |   2 +-
 src/flame/worker.cpp            |  43 ++++++++++---
 src/flame/worker.h              |   4 +-
 src/logger.h                    |  25 ++++----
 src/logger_master.cpp           |   7 ++-
 src/logger_master.h             |   6 +-
 src/logger_worker.cpp           |  23 +++++--
 src/logger_worker.h             |  11 +++-
 src/process_child.cpp           |   6 +-
 src/process_manager.h           |   2 +-
 src/signal_watcher.h            |   2 +-
 src/url.cpp                     |   2 +-
 src/vendor.h                    |   2 +
 test/logger_1.php               |   8 +++
 31 files changed, 373 insertions(+), 215 deletions(-)
 create mode 100644 src/flame/log/logger.cpp
 create mode 100644 src/flame/log/logger.h
 create mode 100644 test/logger_1.php

diff --git a/src/flame/controller.cpp b/src/flame/controller.cpp
index 562711a..c8224e0 100644
--- a/src/flame/controller.cpp
+++ b/src/flame/controller.cpp
@@ -1,55 +1,69 @@
 #include "controller.h"
+#include "master.h"
+#include "worker.h"
+// #include "../logger_master.h"
+// #include "../logger_worker.h"
 
-// 全局控制器
-std::unique_ptr<controller> gcontroller;
-
-controller::controller()
-: type(process_type::UNKNOWN)
-, env(boost::this_process::environment())
-, status(STATUS_UNKNOWN)
-, user_cb(new std::multimap<std::string, php::callable>()) {
-
-    worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
-    worker_size = std::min(std::max((int)worker_size, 1), 256);
-    // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
-    if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
-    else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
-    else { // 单进程模式
-        worker_size = 0;
-        type = process_type::WORKER;
-    }
-    mthread_id = std::this_thread::get_id();
-}
+namespace flame {
+    // 全局控制器
+    std::unique_ptr<controller> gcontroller;
 
-controller *controller::on_init(std::function<void (const php::array &options)> fn) {
-    init_cb.push_back(fn);
-    return this;
-}
+    controller::controller()
+    : type(process_type::UNKNOWN)
+    , env(boost::this_process::environment())
+    , status(STATUS_UNKNOWN)
+    , user_cb(new std::multimap<std::string, php::callable>()) {
 
-controller* controller::on_stop(std::function<void ()> fn) {
-    stop_cb.push_back(fn);
-    return this;
-}
+        worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
+        worker_size = std::min(std::max((int)worker_size, 1), 256);
+        // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
+        if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
+        else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
+        else { // 单进程模式
+            worker_size = 0;
+            type = process_type::WORKER;
+        }
+        mthread_id = std::this_thread::get_id();
+    }
 
-controller* controller::on_user(const std::string& event, php::callable cb) {
-    user_cb->insert({event, cb});
-    return this;
-}
+    controller *controller::on_init(std::function<void (const php::array &options)> fn) {
+        init_cb.push_back(fn);
+        return this;
+    }
 
-void controller::init(php::array options) {
-    for (auto fn : init_cb) fn(options);
-}
-void controller::stop() {
-    for (auto fn : stop_cb) fn();
-    delete user_cb;
-}
+    controller* controller::on_stop(std::function<void ()> fn) {
+        stop_cb.push_back(fn);
+        return this;
+    }
 
-void controller::call_user_cb(const std::string& event, std::vector<php::value> params) {
-    auto ft = user_cb->equal_range(event);
-    for(auto i=ft.first; i!=ft.second; ++i) i->second.call(params);
-}
+    controller* controller::on_user(const std::string& event, php::callable cb) {
+        user_cb->insert({event, cb});
+        return this;
+    }
 
-void controller::call_user_cb(const std::string& event) {
-    auto ft = user_cb->equal_range(event);
-    for(auto i=ft.first; i!=ft.second; ++i) i->second.call();
+    void controller::init(php::array options) {
+        for (auto fn : init_cb) fn(options);
+    }
+    void controller::stop() {
+        for (auto fn : stop_cb) fn();
+        delete user_cb;
+    }
+
+    void controller::call_user_cb(const std::string& event, std::vector<php::value> params) {
+        auto ft = user_cb->equal_range(event);
+        for(auto i=ft.first; i!=ft.second; ++i) i->second.call(params);
+    }
+
+    void controller::call_user_cb(const std::string& event) {
+        auto ft = user_cb->equal_range(event);
+        for(auto i=ft.first; i!=ft.second; ++i) i->second.call();
+    }
+
+    std::ostream& controller::output(int w) {
+        return type == process_type::MASTER ? master::get()->lm_->index(w)->stream() : worker::get()->lm_->index(w)->stream();
+    }
+
+    logger_manager* controller::logger_manager() {
+        return type == process_type::MASTER ? master::get()->lm_.get() : worker::get()->lm_.get();
+    }
 }
diff --git a/src/flame/controller.h b/src/flame/controller.h
index 5cb0301..8ee88e3 100644
--- a/src/flame/controller.h
+++ b/src/flame/controller.h
@@ -2,45 +2,49 @@
 #include "../vendor.h"
 #include "../logger.h"
 
-class controller {
-public:
-    boost::asio::io_context context_x;
-    boost::asio::io_context context_y;
-    enum class process_type {
-        UNKNOWN = 0,
-        MASTER = 1,
-        WORKER = 2,
-    } type;
-    boost::process::environment env;
-    enum status_t {
-        STATUS_UNKNOWN     = 0x00,
-        STATUS_INITIALIZED = 0x01,
-        STATUS_SHUTDOWN    = 0x02,
-        STATUS_EXCEPTION   = 0x04,
-        STATUS_RUN         = 0x08,
-        STATUS_CLOSECONN   = 0x10,
+namespace flame {
+
+    class controller {
+    public:
+        boost::asio::io_context context_x;
+        boost::asio::io_context context_y;
+        enum class process_type {
+            UNKNOWN = 0,
+            MASTER = 1,
+            WORKER = 2,
+        } type;
+        boost::process::environment env;
+        enum status_t {
+            STATUS_UNKNOWN     = 0x00,
+            STATUS_INITIALIZED = 0x01,
+            STATUS_SHUTDOWN    = 0x02,
+            STATUS_EXCEPTION   = 0x04,
+            STATUS_RUN         = 0x08,
+            STATUS_CLOSECONN   = 0x10,
+        };
+        int status;
+        std::size_t    worker_size;
+        std::size_t    worker_quit; // 多进程退出超时时间
+        std::thread::id mthread_id;
+    private:
+        std::list<std::function<void (const php::array& options)>>  init_cb;
+        std::list<std::function<void ()>>                           stop_cb;
+        std::multimap<std::string, php::callable>*                  user_cb; // 防止 PHP 提前回收, 使用堆容器
+    public:
+        controller();
+        controller(const controller& c) = delete;
+        void init(php::array options);
+        void stop();
+        controller *on_init(std::function<void(const php::array &options)> fn);
+        controller* on_stop(std::function<void ()> fn);
+        controller* on_user(const std::string& event, php::callable cb);
+        void call_user_cb(const std::string& event, std::vector<php::value> params);
+        void call_user_cb(const std::string& event);
+        std::ostream& output(int w);
+        logger_manager* logger_manager();
+        // TODO write_to_master | write_worker
     };
-    int status;
-    std::size_t    worker_size;
-    std::size_t    worker_quit; // 多进程退出超时时间
-    std::thread::id mthread_id;
-    // 日志管理器
-    logger_manager*         lm;
-private:
-    std::list<std::function<void (const php::array& options)>>  init_cb;
-    std::list<std::function<void ()>>                           stop_cb;
-    std::multimap<std::string, php::callable>*                  user_cb; // 防止 PHP 提前回收, 使用堆容器
-public:
-    controller();
-    controller(const controller& c) = delete;
-    void init(php::array options);
-    void stop();
-    controller *on_init(std::function<void(const php::array &options)> fn);
-    controller* on_stop(std::function<void ()> fn);
-    controller* on_user(const std::string& event, php::callable cb);
-    void call_user_cb(const std::string& event, std::vector<php::value> params);
-    void call_user_cb(const std::string& event);
-    // TODO write_to_master | write_worker
-};
 
-extern std::unique_ptr<controller> gcontroller;
+    extern std::unique_ptr<controller> gcontroller;
+
+}
\ No newline at end of file
diff --git a/src/flame/coroutine.cpp b/src/flame/coroutine.cpp
index 9f5bf0d..a582b87 100644
--- a/src/flame/coroutine.cpp
+++ b/src/flame/coroutine.cpp
@@ -54,28 +54,17 @@ namespace flame {
 
     void coroutine::start(php::callable fn) {
         ++coroutine::count;
+        coroutine_php_save_context(coroutine::gctx_);
         auto co = std::make_shared<flame::coroutine>();
         
         boost::asio::post(co->ex_, [co, fn] () mutable {
             co->c1_ = boost::context::fiber([co, gd = boost::asio::make_work_guard(co->ex_), fn] (boost::context::fiber&& c2) mutable {
                 co->c2_ = std::move(c2);
-                coroutine_php_save_context(coroutine::gctx_);
+                
                 coroutine_php_vm_stack_init();
                 coroutine::current = co;
-
-                try {
-                    fn();
-                } catch (const php::exception &ex) {
-                    gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
-                    php::object obj = ex; // 记录错误信息
-                    std::cerr << "[" << util::system_time() << "] (FATAL) " << obj.call("__toString") << "\n";
-
-                    boost::asio::post(co->ex_, [] () {
-                        gcontroller->status |= controller::STATUS_EXCEPTION;
-                        gcontroller->context_x.stop();
-                        gcontroller->context_y.stop();
-                    });
-                }
+                
+                fn.call(); // 产生 PHP 进栈 操作，才能进行捕获异常
                 fn = nullptr;
 
                 coroutine::current.reset();
diff --git a/src/flame/flame.cpp b/src/flame/flame.cpp
index 3a0e96d..80ac301 100644
--- a/src/flame/flame.cpp
+++ b/src/flame/flame.cpp
@@ -17,11 +17,11 @@ extern "C" {
         class_closure.method<&php::closure::__invoke>("__invoke");
         ext.add(std::move(class_closure));
         // 全局控制器
-        gcontroller.reset(new controller());
+        flame::gcontroller.reset(new flame::controller());
         // 扩展版本
         flame::version::declare(ext);
         // 主进程与工作进程注册不同的函数实现
-        if (gcontroller->type == ::controller::process_type::WORKER) flame::worker::declare(ext);
+        if (flame::gcontroller->type == flame::controller::process_type::WORKER) flame::worker::declare(ext);
         else flame::master::declare(ext);
 
         return ext;
diff --git a/src/flame/http/_handler.cpp b/src/flame/http/_handler.cpp
index 4b05dd2..7b46198 100644
--- a/src/flame/http/_handler.cpp
+++ b/src/flame/http/_handler.cpp
@@ -122,7 +122,7 @@ namespace flame::http {
                 gcontroller->call_user_cb("exception", {ex});
                 // 记录错误信息
                 php::object obj = ex;
-                std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
+                gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
             }
             return nullptr;
         }));
diff --git a/src/flame/kafka/_consumer.cpp b/src/flame/kafka/_consumer.cpp
index 308ff51..06e78fc 100644
--- a/src/flame/kafka/_consumer.cpp
+++ b/src/flame/kafka/_consumer.cpp
@@ -1,6 +1,6 @@
 #include "../controller.h"
 #include "../time/time.h"
-#include "../log/log.h"
+#include "../log/logger.h"
 #include "_consumer.h"
 #include "../../coroutine_queue.h"
 #include "kafka.h"
@@ -62,8 +62,8 @@ namespace flame::kafka {
 
     void _consumer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
         _consumer* self = reinterpret_cast<_consumer*>(data);
-        if (log::level <= log::LEVEL_WARNING)
-            std::cerr << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+        if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
+            gcontroller->output(0) << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
     }
 
     void _consumer::subscribe(coroutine_handler &ch) {
diff --git a/src/flame/kafka/_producer.cpp b/src/flame/kafka/_producer.cpp
index bd448ff..4dff745 100644
--- a/src/flame/kafka/_producer.cpp
+++ b/src/flame/kafka/_producer.cpp
@@ -1,6 +1,6 @@
 #include "../controller.h"
 #include "../time/time.h"
-#include "../log/log.h"
+#include "../log/logger.h"
 #include "_producer.h"
 #include "kafka.h"
 
@@ -45,8 +45,8 @@ namespace flame::kafka {
 
     void _producer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
         _producer* self = reinterpret_cast<_producer*>(data);
-        if (log::level <= log::LEVEL_WARNING)
-            std::cerr << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+        if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
+            gcontroller->output(0) << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
     }
 
     void _producer::start() {
diff --git a/src/flame/kafka/consumer.cpp b/src/flame/kafka/consumer.cpp
index e631b54..0ba1a64 100644
--- a/src/flame/kafka/consumer.cpp
+++ b/src/flame/kafka/consumer.cpp
@@ -47,7 +47,7 @@ namespace flame::kafka {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
+                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
                     }
                 }
                 if (--count == 0) ch_run.resume();
diff --git a/src/flame/log/log.cpp b/src/flame/log/log.cpp
index 1ed8ded..f0ce8bc 100644
--- a/src/flame/log/log.cpp
+++ b/src/flame/log/log.cpp
@@ -1,69 +1,65 @@
 #include "../controller.h"
 #include "log.h"
-#include "../time/time.h"
+#include "logger.h"
+#include "../coroutine.h"
 
 namespace flame::log {
-
-    static std::string LEVEL_S[] = {
-        "TRACE",
-        "DEBUG",
-        "INFO",
-        "WARNING",
-        "ERROR",
-        "FATAL",
-    };
-
-    static std::map<std::string, int> LEVEL_I {};
-
-    int level = 0;
-
-    static void write_ex(int lv, php::parameters& params) {
-        if (lv < level) return;
-        std::ostream& os = lv > LEVEL_WARNING ? std::cout : std::cerr;
-        os << '[' << time::iso() << "] (";
-        os << LEVEL_S[lv];
-        os << ")";
-        for (int i = 0; i < params.size(); ++i)
-            os << ' ' << params[i].ptr();
-
-        os << std::endl;
-    }
+    
+    logger* logger_ = nullptr;
 
     static php::value trace(php::parameters &params) {
-        write_ex(LEVEL_TRACE, params);
+        logger_->write_ex(logger::LEVEL_TRACE, params);
         return nullptr;
     }
 
     static php::value debug(php::parameters &params) {
-        write_ex(LEVEL_DEBUG, params);
+        logger_->write_ex(logger::LEVEL_DEBUG, params);
         return nullptr;
     }
 
     static php::value info(php::parameters &params) {
-        write_ex(LEVEL_INFO, params);
+        logger_->write_ex(logger::LEVEL_INFO, params);
         return nullptr;
     }
 
     static php::value warning(php::parameters &params) {
-        write_ex(LEVEL_WARNING, params);
+        logger_->write_ex(logger::LEVEL_WARNING, params);
         return nullptr;
     }
 
     static php::value error(php::parameters &params) {
-        write_ex(LEVEL_ERROR, params);
+        logger_->write_ex(logger::LEVEL_ERROR, params);
         return nullptr;
     }
 
     static php::value fatal(php::parameters &params) {
-        write_ex(LEVEL_FATAL, params);
+        logger_->write_ex(logger::LEVEL_FATAL, params);
         return nullptr;
     }
 
+    php::value connect(php::parameters& params) {
+        php::object obj {php::class_entry<logger>::entry()};
+        logger* ptr = static_cast<logger*>(php::native(obj));
+        coroutine_handler ch { coroutine::current };
+        ptr->lg_ = gcontroller->logger_manager()->connect(params[0], ch);
+        return obj;
+    }
+
     void declare(php::extension_entry &ext) {
         gcontroller->on_init([] (const php::array& options) {
-            if (options.exists("level")) level = options.get("level").to_integer();
-            else level = 0;
+            if (options.exists("level")) logger::LEVEL_OPT = options.get("level").to_integer();
+            else logger::LEVEL_OPT = logger::LEVEL_TRACE;
+            std::string output = "stdout";
+            if (options.exists("logger")) output = options.get("logger").to_string();
+            logger_ = new logger();
+            // 启动的一个更轻量的 C++ 内部协程
+            ::coroutine::start(gcontroller->context_x.get_executor(), [output] (::coroutine_handler ch) {
+                logger_->set_logger( gcontroller->logger_manager()->connect(output, ch) );
+            });
         });
+        for(int i=0;i<logger::LEVEL_STR.size();++i) {
+            ext.constant({"flame\\log\\" + logger::LEVEL_STR[i], i}); // 日志等级常量
+        }
         ext
             .function<trace>("flame\\log\\trace")
             .function<debug>("flame\\log\\debug")
diff --git a/src/flame/log/log.h b/src/flame/log/log.h
index 734da61..378be4d 100644
--- a/src/flame/log/log.h
+++ b/src/flame/log/log.h
@@ -2,14 +2,9 @@
 #include "../../vendor.h"
 
 namespace flame::log {
-    enum {
-        LEVEL_TRACE,
-        LEVEL_DEBUG,
-        LEVEL_INFO,
-        LEVEL_WARNING,
-        LEVEL_ERROR,
-        LEVEL_FATAL,
-    };
-    extern int level;
+    class logger;
+    // 默认日志记录器
+    extern logger* logger_;
     void declare(php::extension_entry &ext);
+    php::value connect(php::parameters& params);
 } // namespace flame::log
diff --git a/src/flame/log/logger.cpp b/src/flame/log/logger.cpp
new file mode 100644
index 0000000..492dd36
--- /dev/null
+++ b/src/flame/log/logger.cpp
@@ -0,0 +1,64 @@
+#include "log.h"
+#include "logger.h"
+#include "../time/time.h"
+#include "../../logger.h"
+#include "../controller.h"
+
+namespace flame::log {
+    
+    std::array<std::string, 6> logger::LEVEL_STR = {
+        "TRACE",
+        "DEBUG",
+        "INFO",
+        "WARNING",
+        "ERROR",
+        "FATAL",
+    };
+    int logger::LEVEL_OPT = logger::LEVEL_TRACE;
+
+    logger::~logger() {
+        gcontroller->logger_manager()->destroy(lg_);
+    }
+
+    void logger::write_ex(int lv, php::parameters& params) {
+        if (lv < LEVEL_OPT) return;
+        std::ostream& os = lg_ ? lg_->stream() : std::cout;
+        os << '[' << time::iso() << "] (";
+        os << LEVEL_STR[lv];
+        os << ")";
+        for (int i = 0; i < params.size(); ++i)
+            os << ' ' << params[i].ptr();
+
+        os << std::endl;
+    }
+
+    php::value logger::trace(php::parameters &params) {
+        write_ex(LEVEL_TRACE, params);
+        return nullptr;
+    }
+
+    php::value logger::debug(php::parameters &params) {
+        write_ex(LEVEL_DEBUG, params);
+        return nullptr;
+    }
+
+    php::value logger::info(php::parameters &params) {
+        write_ex(LEVEL_INFO, params);
+        return nullptr;
+    }
+
+    php::value logger::warning(php::parameters &params) {
+        write_ex(LEVEL_WARNING, params);
+        return nullptr;
+    }
+
+    php::value logger::error(php::parameters &params) {
+        write_ex(LEVEL_ERROR, params);
+        return nullptr;
+    }
+
+    php::value logger::fatal(php::parameters &params) {
+        write_ex(LEVEL_FATAL, params);
+        return nullptr;
+    }
+}
diff --git a/src/flame/log/logger.h b/src/flame/log/logger.h
new file mode 100644
index 0000000..17ae309
--- /dev/null
+++ b/src/flame/log/logger.h
@@ -0,0 +1,37 @@
+#pragma once
+#include "../../vendor.h"
+
+class logger;
+namespace flame::log {
+    
+
+    class logger: public php::class_base {
+    public:
+        enum {
+            LEVEL_TRACE,
+            LEVEL_DEBUG,
+            LEVEL_INFO,
+            LEVEL_WARNING,
+            LEVEL_ERROR,
+            LEVEL_FATAL,
+        };
+        static std::array<std::string, 6> LEVEL_STR;
+        static int LEVEL_OPT;
+        ~logger();
+        void write_ex(int lv, php::parameters& params);
+        php::value trace(php::parameters &params);
+        php::value debug(php::parameters &params);
+        php::value info(php::parameters &params);
+        php::value warning(php::parameters &params);
+        php::value error(php::parameters &params);
+        php::value fatal(php::parameters &params);
+
+        void set_logger(::logger* lg) {
+            lg_ = lg;
+        }
+    private:
+        ::logger* lg_ = nullptr;
+
+        friend php::value connect(php::parameters& params);
+    };
+}
\ No newline at end of file
diff --git a/src/flame/master.cpp b/src/flame/master.cpp
index 8ce5412..7fd84ad 100644
--- a/src/flame/master.cpp
+++ b/src/flame/master.cpp
@@ -26,13 +26,10 @@ namespace flame {
         // 设置进程标题
         std::string title = params[0];
         php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
-        // 初始化启动
-        gcontroller->init(options);
         // 主进程控制对象
         master::mm_ = new master();
-
-        if(options.exists("logger")) master::mm_->lm_.connect(options.get("logger"));
-        else master::mm_->lm_.connect("stdout");
+        // 初始化启动
+        gcontroller->init(options);
         return nullptr;
     }
 
@@ -60,41 +57,40 @@ namespace flame {
     }
 
     master::master()
-    : lm_()
+    : lm_(new logger_manager_master)
     , pm_(gcontroller->context_x, gcontroller->worker_size)
     , sw_(gcontroller->context_y) {
         
-        gcontroller->lm = &lm_;
-        sw_.lg_ = pm_.lg_ = lm_.index(0);
         sw_.start(std::bind(&master::on_signal, this, std::placeholders::_1, std::placeholders::_2));
     }
 
     void master::on_signal(const boost::system::error_code& error, int sig) {
         if (error) return;
         switch(sig) {
-        case SIGINT: coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) {
-            pm_.restart(ch);
-        });
+        case SIGINT: 
+            coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                pm_.restart(ch);
+            });
         break;
         case SIGUSR2: 
-            lm_.reload(); // 日志重载
+            lm_->reload(); // 日志重载
         break;
         case SIGUSR1:
             if(++stats_ % 2 == 1) 
-                lm_.index(0)->stream() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl;
+                lm_->index(0)->stream() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl;
             else
-                lm_.index(0)->stream() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl;
+                lm_->index(0)->stream() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl;
             pm_.signal(SIGUSR1); // 长短连切换
         break;
         case SIGTERM:
             if(++close_ > 1) {
                 sw_.close();
-                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) {
+                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
                     pm_.close(true, ch);
                 });
                 return;
             }else{
-                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) {
+                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
                     pm_.close(false, ch);
                 });
             }
diff --git a/src/flame/master.h b/src/flame/master.h
index da8ad41..6e55bf1 100644
--- a/src/flame/master.h
+++ b/src/flame/master.h
@@ -21,9 +21,11 @@ namespace flame {
         static master*  mm_;
 
         process_manager       pm_;
-        logger_manager_master lm_;
+        std::unique_ptr<logger_manager> lm_;
         signal_watcher        sw_;
         int close_ = 0;
         int stats_ = 0;
+
+        friend class controller;
     };
 }
\ No newline at end of file
diff --git a/src/flame/rabbitmq/consumer.cpp b/src/flame/rabbitmq/consumer.cpp
index 338c496..0fb1469 100644
--- a/src/flame/rabbitmq/consumer.cpp
+++ b/src/flame/rabbitmq/consumer.cpp
@@ -53,7 +53,7 @@ namespace flame::rabbitmq  {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
+                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
                     }
                 }
                 if (--count == 0)  ch.resume(); // ----> 1
diff --git a/src/flame/tcp/server.cpp b/src/flame/tcp/server.cpp
index f34061e..87bebee 100644
--- a/src/flame/tcp/server.cpp
+++ b/src/flame/tcp/server.cpp
@@ -87,7 +87,7 @@ namespace flame::tcp {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
+                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
                     }
                     return nullptr;
                 }));
diff --git a/src/flame/toml/_executor.cpp b/src/flame/toml/_executor.cpp
index a01dc4c..4ef63e0 100755
--- a/src/flame/toml/_executor.cpp
+++ b/src/flame/toml/_executor.cpp
@@ -9,7 +9,7 @@ void _executor::operator ()(const toml_parser::parser& p, std::string_view chunk
 
 void _executor::operator ()(const toml_parser::parser& p) {
     php::string raw = std::move(buffer_);
-    std::cout << "(" << (int)p.value_type() << ") " << p.field() << " => [" << raw << "]\n";
+    // std::cout << "(" << (int)p.value_type() << ") " << p.field() << " => [" << raw << "]\n";
     php::value  val = _executor::restore(raw, p.value_type());
     
     // switch(p.container_type()) {
diff --git a/src/flame/udp/server.cpp b/src/flame/udp/server.cpp
index 9fb5864..f0cb335 100644
--- a/src/flame/udp/server.cpp
+++ b/src/flame/udp/server.cpp
@@ -83,7 +83,7 @@ namespace flame::udp {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        std::cerr << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
+                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
                     }
                 }
                 if (--count == 0) ch.resume();
diff --git a/src/flame/worker.cpp b/src/flame/worker.cpp
index 4526335..8d2efff 100644
--- a/src/flame/worker.cpp
+++ b/src/flame/worker.cpp
@@ -2,6 +2,8 @@
 #include "worker.h"
 #include "coroutine.h"
 
+#include "queue.h"
+#include "mutex.h"
 #include "log/log.h"
 #include "os/os.h"
 #include "time/time.h"
@@ -24,9 +26,13 @@ namespace flame {
     void worker::declare(php::extension_entry& ext) {
         ext
         .on_request_shutdown([] (php::extension_entry& ext) -> bool {
-            if (flame::coroutine::count > 0 && (gcontroller->status & controller::STATUS_RUN) == 0) {
-                if (!php::error::exists()) std::cerr << "[FATAL] process exited prematurely: exception or missing 'flame\\run();' ?\n";
-                _exit(-1);
+            if (gcontroller->status & controller::STATUS_INITIALIZED) {
+                if (php::error::exists()) 
+                    std::cerr << "[WARNING] process exited prematurely: uncaught exception / error\n";
+                if (!(gcontroller->status & controller::STATUS_RUN)) 
+                    std::cerr << "[WARNING] process exited prematurely: missing 'flame\\run();'\n";
+                
+                gcontroller->status & controller::STATUS_EXCEPTION ? _exit(-1) : _exit(0);
             }
             return true;
         })
@@ -56,6 +62,9 @@ namespace flame {
             {"target", php::TYPE::ARRAY},
             {"fields", php::TYPE::STRING},
         });
+        // 顶级命名空间
+        flame::mutex::declare(ext);
+        flame::queue::declare(ext);
         // 子命名空间模块注册
         flame::log::declare(ext);
         flame::os::declare(ext);
@@ -92,8 +101,8 @@ namespace flame {
         else
             php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
         
-        gcontroller->init(options);
         worker::ww_ = new worker();
+        gcontroller->init(options);        
         return nullptr;
     }
 
@@ -101,8 +110,24 @@ namespace flame {
         if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
             throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
 
-        php::callable fn = params[0];
-        flame::coroutine::start(fn);
+        coroutine::start(php::callable([fn = php::callable(params[0])] (php::parameters& params) -> php::value {
+            try { // 函数调用产生了堆栈过程，可以捕获异常
+                fn.call(); 
+            } catch (const php::exception &ex) {
+                gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
+                php::object obj = ex; // 记录错误信息
+                gcontroller->output(0) << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
+
+                boost::asio::post(gcontroller->context_x, [] () {
+                    gcontroller->status |= controller::STATUS_EXCEPTION;
+                    gcontroller->context_x.stop();
+                    gcontroller->context_y.stop();
+                });
+            }
+            return nullptr;
+        }));
+        // 如下形式无法在内部无法捕获到 PHP 错误（顶层栈）
+        // flame::coroutine::start(params[0]);
         return nullptr;
     }
 
@@ -141,6 +166,8 @@ namespace flame {
         if ((gcontroller->status & controller::STATUS_RUN) == 0)
             throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
         
+        gcontroller->call_user_cb("quit");
+
         gcontroller->context_x.stop();
         gcontroller->context_y.stop();
         return nullptr;
@@ -169,11 +196,9 @@ namespace flame {
     }
 
     worker::worker() 
-    : lm_()
+    : lm_(new logger_manager_worker)
     , sw_(gcontroller->context_y) {
 
-        gcontroller->lm = &lm_;
-        sw_.lg_ = lm_.index(0);
         sw_.start(std::bind(&worker::on_signal, this, std::placeholders::_1, std::placeholders::_2));
     }
 
diff --git a/src/flame/worker.h b/src/flame/worker.h
index ff8f30e..c8869d3 100644
--- a/src/flame/worker.h
+++ b/src/flame/worker.h
@@ -29,10 +29,12 @@ namespace flame {
     private:
         static worker* ww_;
 
-        logger_manager_worker lm_;
+        std::unique_ptr<logger_manager> lm_;
         signal_watcher sw_;
 
         int close_ = 0;
         int stats_ = 0;
+
+        friend class controller;
     };
 }
\ No newline at end of file
diff --git a/src/logger.h b/src/logger.h
index e448748..76efac6 100644
--- a/src/logger.h
+++ b/src/logger.h
@@ -1,10 +1,14 @@
 #pragma once
+#include "vendor.h"
 
 class logger {
 public:
     logger(unsigned int index)
     : ref_(1)
-    , idx_(index) { }
+    , idx_(index) {
+        
+
+    }
 
     virtual ~logger() = default;
 
@@ -16,30 +20,27 @@ class logger {
     }
     virtual void reload() {}
     virtual std::ostream& stream() = 0;
-    void write(std::string_view data, bool flush = true) {
-        std::ostream& os = stream();
-        os << data;
-        if(flush) os.flush();
-    }
+    virtual void write(std::string_view data, bool flush = true) = 0;
 private:
     unsigned int ref_;
     unsigned int idx_;
+
 };
 
+class coroutine_handler;
 class logger_manager {
 public:
-    virtual unsigned int connect(const std::string& filepath) = 0;
+    virtual ~logger_manager() = default;
+    virtual logger* connect(const std::string& filepath, coroutine_handler& ch) = 0;
     void reload() {
         for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload();
     }
     // 删除对应的 logger 引用
-    void destroy(unsigned int w) {
-        if(logger_[w]->delref() == 0) {
-            logger_.erase(w);
-        }
+    void destroy(logger* l) {
+        if(l->delref() == 0) logger_.erase(l->index());
     }
     logger* index(unsigned int w) {
-        return logger_[w].get();
+        return logger_.empty() ? nullptr : logger_[w].get();
     }
 protected:
     std::map<unsigned int, std::unique_ptr<logger>> logger_;
diff --git a/src/logger_master.cpp b/src/logger_master.cpp
index 2f9b562..62ffea5 100644
--- a/src/logger_master.cpp
+++ b/src/logger_master.cpp
@@ -1,4 +1,5 @@
 #include "logger_master.h"
+#include "coroutine.h"
 
 void logger_master::reload() {
     file_.reset(new std::ofstream(path_, std::ios_base::out | std::ios_base::app));
@@ -8,17 +9,17 @@ void logger_master::reload() {
     file_.reset(&std::clog, boost::null_deleter());
 }
 
-unsigned int logger_manager_master::connect(const std::string& filepath) {
+logger* logger_manager_master::connect(const std::string& filepath, coroutine_handler& ch) {
     std::filesystem::path path = filepath;
     
     for(auto i=logger_.begin();i!=logger_.end();++i) {
         if(static_cast<logger_master*>(i->second.get())->path_ == path) {
             i->second->addref();
-            return i->second->index();
+            return i->second.get();
         }
     }
     auto p = logger_.insert({index_, std::make_unique<logger_master>(path, index_)});
     ++index_;
     p.first->second->reload();
-    return p.first->second->index();
+    return p.first->second.get();
 }
diff --git a/src/logger_master.h b/src/logger_master.h
index cd83678..4cd1bf4 100644
--- a/src/logger_master.h
+++ b/src/logger_master.h
@@ -16,12 +16,16 @@ class logger_master: public logger {
     std::ostream& stream() override {
         return *file_;
     }
+    void write(std::string_view data, bool flush = true) override {
+        *file_ << data;
+        if(flush) file_->flush();
+    }
     friend class logger_manager_master;
 };
 
 class logger_manager_master: public logger_manager {
 public:
-    unsigned int connect(const std::string& filepath) override;
+    logger* connect(const std::string& filepath, coroutine_handler& ch) override;
 private:
     unsigned int index_ = 0;
 };
\ No newline at end of file
diff --git a/src/logger_worker.cpp b/src/logger_worker.cpp
index de34984..bd8334c 100644
--- a/src/logger_worker.cpp
+++ b/src/logger_worker.cpp
@@ -1,11 +1,22 @@
 #include "logger_worker.h"
+#include "coroutine.h"
 
-std::ostream& logger_worker::stream() {
-    // TODO 需要 IPC 支持
-    return std::cerr;
+std::ostream& logger_worker_out::stream() {
+    return std::clog;
 }
 
-unsigned int logger_manager_worker::connect(const std::string& filepath) {
-    // TODO 需要 IPC 支持
-    return 0;
+void logger_worker_out::write(std::string_view data, bool flush) {
+    std::clog << data;
+    if(flush) std::clog.flush();
+}
+
+logger* logger_manager_worker::connect(const std::string& filepath, coroutine_handler& ch) {
+    // TODO 实现基于 IPC 的通道支持
+
+    auto i = logger_.find(0);
+    if(i == logger_.end()) {
+        std::cout << "logger_manager_worker::connect\n";
+        logger_[0].reset(new logger_worker_out(0));
+    }
+    return logger_[0].get();
 }
\ No newline at end of file
diff --git a/src/logger_worker.h b/src/logger_worker.h
index d937b50..685b31f 100644
--- a/src/logger_worker.h
+++ b/src/logger_worker.h
@@ -2,12 +2,19 @@
 #include "vendor.h"
 #include "logger.h"
 
-class logger_worker: public logger {
+class logger_worker_out: public logger {
 public:
+    using logger::logger;
     std::ostream& stream() override;
+    void write(std::string_view data, bool flush = true) override;
+};
+
+class logger_worker_ipc: public logger {
+    std::ostream& stream() override;
+    void write(std::string_view data, bool flush = true) override;
 };
 
 class logger_manager_worker: public logger_manager {
 public:
-    unsigned int connect(const std::string& filepath) override;
+    logger* connect(const std::string& filepath, coroutine_handler& ch) override;
 };
diff --git a/src/process_child.cpp b/src/process_child.cpp
index bdc6e2d..3a0d90b 100755
--- a/src/process_child.cpp
+++ b/src/process_child.cpp
@@ -43,12 +43,12 @@ process_child::process_child(boost::asio::io_context& io, process_manager* m, in
 void process_child::redirect_output(boost::process::async_pipe& pipe, std::string& data) {
     boost::asio::async_read_until(pipe, boost::asio::dynamic_buffer(data), '\n', [this, &pipe, &data] (const boost::system::error_code &error, std::size_t nread) {
         if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
-            ; // std::cerr << "(INFO) IGNORE sout\n";
+            ; // 忽略
         else if (error) {
-            manager_->lg_->stream() << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
+            (manager_->lg_ ? manager_->lg_->stream() : std::cerr) << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
         }
         else {
-            manager_->lg_->write({data.c_str(), nread}, true);
+            (manager_->lg_ ? manager_->lg_->stream() : std::cerr) << std::string_view(data.c_str(), nread);
             data.erase(0, nread);
             redirect_output(pipe, data);
         }
diff --git a/src/process_manager.h b/src/process_manager.h
index 87a9427..61f6d58 100644
--- a/src/process_manager.h
+++ b/src/process_manager.h
@@ -14,7 +14,7 @@ class process_manager {
     unsigned int count() {
         return count_;
     }
-    logger*                  lg_; // master -> set
+    logger*        lg_ = nullptr; // 等待填充
 private:
     boost::asio::io_context& io_;
     unsigned int          count_;
diff --git a/src/signal_watcher.h b/src/signal_watcher.h
index 41561c1..d736120 100644
--- a/src/signal_watcher.h
+++ b/src/signal_watcher.h
@@ -10,7 +10,7 @@ class signal_watcher {
         ss_->async_wait(cb);
     }
     void close();
-    logger*  lg_;
+    logger*  lg_ = nullptr; // 等待填充
 private:
     std::unique_ptr<boost::asio::signal_set> ss_;
     int close_ = 0;
diff --git a/src/url.cpp b/src/url.cpp
index cdccffb..f5d60a4 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -38,7 +38,7 @@ url::url(const php::string &str, bool parse_query)
         parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
             php::url_decode_inplace(et.second.data(), et.second.size());
             query[et.first] = et.second;
-            std::cout << et.first << ": " << et.second << "\n";
+            // std::cout << et.first << ": " << et.second << "\n";
         });
         p.parse(tmp, strlen(tmp));
         p.end();
diff --git a/src/vendor.h b/src/vendor.h
index d2b314a..afbc297 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -9,6 +9,8 @@
 #include <cstdio>
 #include <filesystem>
 #include <functional>
+#include <iostream>
+#include <ostream>
 #include <limits>
 #include <memory>
 #include <set>
diff --git a/test/logger_1.php b/test/logger_1.php
new file mode 100644
index 0000000..d91fdeb
--- /dev/null
+++ b/test/logger_1.php
@@ -0,0 +1,8 @@
+<?php
+flame\init("logger_1");
+
+flame\go(function() {
+    flame\log\warning("This is a warning message");
+});
+
+flame\run();
\ No newline at end of file

From dcd0596d30a512b714ab7a647aba5fe5715cea36 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Tue, 2 Jul 2019 21:13:49 +0800
Subject: [PATCH 142/146] =?UTF-8?q?=E9=87=8D=E6=9E=84=20HTTP=20=E6=A8=A1?=
 =?UTF-8?q?=E5=9D=97=E5=8D=8F=E7=A8=8B=E6=9C=BA=E5=88=B6=EF=BC=8C=E6=8C=89?=
 =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=88=9B=E5=BB=BA=E4=BB=A5=E5=87=8F=E5=B0=91?=
 =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=B6=88=E8=80=97=EF=BC=8C=E7=AE=80=E5=8C=96?=
 =?UTF-8?q?=E8=AF=B7=E6=B1=82=E7=BB=93=E6=9D=9F=E5=A4=84=E7=90=86=E8=BF=87?=
 =?UTF-8?q?=E7=A8=8B=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/flame/http/_handler.cpp        | 200 +++++++++++++----------------
 src/flame/http/_handler.h          |  11 +-
 src/flame/http/server.cpp          |   8 +-
 src/flame/http/server_response.cpp |  16 +--
 src/flame/http/server_response.h   |   1 -
 test/http_2.php                    |  38 ++----
 6 files changed, 113 insertions(+), 161 deletions(-)

diff --git a/src/flame/http/_handler.cpp b/src/flame/http/_handler.cpp
index 7b46198..1a73bba 100644
--- a/src/flame/http/_handler.cpp
+++ b/src/flame/http/_handler.cpp
@@ -17,46 +17,37 @@ namespace flame::http {
     _handler::~_handler() {
     }
 
-    void _handler::start() {
-        req_.reset();
-        res_.reset();
-        if ((req_ && !req_->keep_alive()) || (res_ && !res_->keep_alive()))  return;
-        // 第一次请求或允许复用
-        read_request();
+    php::value _handler::run(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+
+        while(true) {
+            if (!prepare(ch)) return nullptr;
+            if (!execute(ch)) return nullptr;
+
+            // 由于错误清理了对象 或 不允许连接复用
+            if (!res_ || !res_->keep_alive()) return nullptr;
+        }
     }
 
-    void _handler::read_request() {
-        // 读取请求
+    bool _handler::prepare(coroutine_handler& ch) {
+        boost::system::error_code error;
         req_.reset(new boost::beast::http::request_parser<value_body<true>>());
         req_->header_limit(16 * 1024);
         req_->body_limit(body_max_size);
-        boost::beast::http::async_read(socket_, buffer_, *req_
-            , [self = shared_from_this(), this] (const boost::system::error_code &error, std::size_t n) {
-
-            if (error == boost::beast::http::error::end_of_stream
-                || error == boost::asio::error::operation_aborted
-                || error == boost::asio::error::connection_reset) {
-
-                res_.reset();
-                req_.reset();
-                return;
-            }
-            else if (error) {
-                res_.reset();
-                req_.reset();
-                // std::cerr << "[" << time::iso() << "] (WARNING) Failed to read http request: (" << error.value() << ") " << error.message() << std::endl;
-                return;
-            }
-            res_.reset(new boost::beast::http::message<false, value_body<false>>());
-            // 默认为请求 KeepAlive 配置
-            res_->keep_alive(req_->get().keep_alive());
-            // 默认应为非 chunked 模式
-            assert(!res_->chunked());
-            call_handler();
-        });
+        boost::beast::http::async_read(socket_, buffer_, *req_, ch[error]);
+        if(error) return false;
+        res_.reset(new boost::beast::http::message<false, value_body<false>>());
+        // 服务及将停止或服务器以进行关闭后，禁止连接复用
+        if (gcontroller->status & controller::STATUS_CLOSECONN || svr_ptr->closed_) {
+            res_->keep_alive(false);
+            res_->set(boost::beast::http::field::connection, "close");
+        }else{
+            res_->keep_alive(req_->get().keep_alive()); // 默认为请求 KeepAlive 配置
+        }
+        return true;
     }
 
-    void _handler::call_handler() {
+    bool _handler::execute(coroutine_handler& ch) {
         php::object req = php::object(php::class_entry<server_request>::entry());
         php::object res = php::object(php::class_entry<server_response>::entry());
         server_request *req_ptr = static_cast<server_request *>(php::native(req));
@@ -65,67 +56,62 @@ namespace flame::http {
         req_ptr->build_ex(req_->get()); // 将 C++ 请求对象展开到 PHP 中
         res_ptr->handler_ = shared_from_this();
 
-        coroutine::start(php::callable([self = shared_from_this(), this, req, req_ptr, res, res_ptr] (php::parameters& params) -> php::value {
-            coroutine_handler ch{coroutine::current};
-            std::string route = req.get("method");
-            route.push_back(':');
-            route.append(req.get("path"));
-
-            auto ib = svr_ptr->cb_.find("before"),
-                ih = svr_ptr->cb_.find(route),
-                ia = svr_ptr->cb_.find("after");
-
-            try {
-                php::value rv = true;
-                if (ib != svr_ptr->cb_.end()) rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
-                if (rv.type_of(php::TYPE::NO)) goto HANDLE_BREAK;
-                if (ih != svr_ptr->cb_.end()) rv = ih->second.call({req, res});
-                if (rv.type_of(php::TYPE::NO)) goto HANDLE_BREAK;
-                if (ia != svr_ptr->cb_.end()) rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
-HANDLE_BREAK:
-                if (!res_->chunked()/* && !(res_ptr->status_ & server_response::STATUS_BODY_SENT)*/) {
-                    // if (!(res_ptr->status_ & server_response::STATUS_HEAD_SENT)) {
-                    //     res_ptr->set("status", 404);
-                    //     res_ptr->set("body", nullptr);
-                    // }
-                    // 服务及将停止或服务器以进行关闭后，禁止连接复用
-                    if (gcontroller->status & controller::STATUS_CLOSECONN || svr_ptr->closed_) {
-                        res_->keep_alive(false);
-                        res_->set(boost::beast::http::field::connection, "close");
-                    }
-                    res_ptr->build_ex(*res_);
-                    // Content 方式, 待结束
-                    php::string body = res.get("body");
-                    if (!body.empty())
-                        body = ctype_encode(res_->find(boost::beast::http::field::content_type)->value(), body);
-                    res_->body() = body;
-                    res_->prepare_payload();
-
-                    boost::system::error_code error;
-                    boost::beast::http::async_write(socket_, *res_, ch[error]);
-                    if (error) {
-                        res_.reset();
-                        req_.reset();
-                        throw php::exception(zend_ce_exception
-                            , (boost::format("Failed to write response: %s") % error.message()).str()
-                            , error.value());
-                    }
-                    else start();
+        std::string route = req.get("method");
+        route.push_back(':');
+        route.append(req.get("path"));
+
+        auto ib = svr_ptr->cb_.find("before"),
+            ih = svr_ptr->cb_.find(route),
+            ia = svr_ptr->cb_.find("after");
+
+        try {
+            php::value rv = true;
+            if (ib != svr_ptr->cb_.end()) rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
+            if (!res_) return false; // 处理过程网络异常，下同
+            if (rv.type_of(php::TYPE::NO)) goto HANDLE_SKIPPED;
+            if (ih != svr_ptr->cb_.end()) rv = ih->second.call({req, res});
+            if (!res_) return false;
+            if (rv.type_of(php::TYPE::NO)) goto HANDLE_SKIPPED;
+            if (ia != svr_ptr->cb_.end()) rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
+            if (!res_) return false;
+HANDLE_SKIPPED:
+            if (res_->chunked()) {
+                // Chunked 方式响应，但未结束：
+                if(!(res_ptr->status_ & res_ptr->STATUS_BODY_END)) {
+                    write_end(res_ptr, ch); // 结束
                 }
-                /*else {
-                    // Chunked 未结束 -> server_response::__destruct() -> finish()
-                }*/
-            } catch(const php::exception& ex) {
-                res_.reset();
-                req_.reset();
-                // 调用用户异常回调
-                gcontroller->call_user_cb("exception", {ex});
-                // 记录错误信息
-                php::object obj = ex;
-                gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
             }
-            return nullptr;
-        }));
+            else { // 使用 body 属性进行响应
+                write_body(res_ptr, ch);
+            }
+            return true;
+            /*else {
+                // Chunked 未结束 -> server_response::__destruct() -> finish()
+            }*/
+        } catch(const php::exception& ex) {
+            gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
+            // 记录错误信息
+            php::object obj = ex;
+            gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
+            return false;
+        }
+    }
+
+    void _handler::write_body(server_response* res_ptr, coroutine_handler& ch) {
+        res_ptr->build_ex(*res_);
+        // Content 方式, 待结束
+        php::string body = res_ptr->get("body");
+        if (!body.empty())
+            body = ctype_encode(res_->find(boost::beast::http::field::content_type)->value(), body);
+        res_->body() = body;
+        res_->prepare_payload();
+
+        boost::system::error_code error;
+        boost::beast::http::async_write(socket_, *res_, ch[error]);
+        if(error) {
+            req_.reset();
+            res_.reset();
+        }
     }
 
     void _handler::write_head(server_response *res_ptr, coroutine_handler &ch) {
@@ -145,17 +131,14 @@ namespace flame::http {
         if (error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception
-                , (boost::format("Failed to write header: %s") % error.message()).str()
-                , error.value());
         }
     }
 
-    void _handler::write_body(server_response *res_ptr, php::string data, coroutine_handler &ch) {
+    void _handler::write_chunk(server_response *res_ptr, php::string data, coroutine_handler &ch) {
         if (!res_) return;
         if (!res_->chunked())
             throw php::exception(zend_ce_error_exception
-                , "Failed to write_body: 'chunked' encoding required"
+                , "Failed to write_chunk: 'chunked' encoding required"
                 , -1);
 
         boost::system::error_code error;
@@ -164,9 +147,6 @@ namespace flame::http {
         if (error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception
-                , (boost::format("Failed to write body: %s") % error.message()).str()
-                , error.value());
         }
     }
 
@@ -180,9 +160,6 @@ namespace flame::http {
         if (error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception
-                , (boost::format("Failed to write body: %s") % error.message()).str()
-                , error.value());
         }
     }
 
@@ -190,7 +167,7 @@ namespace flame::http {
         if (!res_) return;
         if (!res_->chunked())
             throw php::exception(zend_ce_error_exception
-                , "Failed to write_body: 'chunked' encoding required"
+                , "Failed to write_file: 'chunked' encoding required"
                 , -1);
         std::ifstream file(path);
         char buffer[2048];
@@ -212,7 +189,7 @@ namespace flame::http {
                 res_.reset();
                 req_.reset();
                 throw php::exception(zend_ce_exception
-                    , (boost::format("Failed to write file: %s") % error.message()).str()
+                    , (boost::format("Failed to write_file: %s") % error.message()).str()
                     , error.value());
             }
             boost::asio::async_write(socket_, boost::beast::http::make_chunk( boost::asio::buffer(buffer, size) ), ch[error]);
@@ -223,9 +200,6 @@ namespace flame::http {
         if (error) {
             res_.reset();
             req_.reset();
-            throw php::exception(zend_ce_exception
-                , (boost::format("Failed to write file: %s") % error.message()).str()
-                , error.value());
         }
         // file.close();
     }
@@ -235,11 +209,11 @@ namespace flame::http {
             if (res_->chunked() && !(res_ptr->status_ & server_response::STATUS_BODY_END)) {
                 boost::system::error_code error;
                 boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
-                res_.reset();
-                req_.reset();
-                if (error) std::cerr << "[" << time::iso() << "] (ERROR) Failed to write body: (" 
-                    << error.value() << ") " << error.message() << "\n";
-                else start();
+                
+                if (error) {
+                    res_.reset();
+                    req_.reset();
+                }
             }
         }
     }
diff --git a/src/flame/http/_handler.h b/src/flame/http/_handler.h
index a1ab5f3..c14275d 100644
--- a/src/flame/http/_handler.h
+++ b/src/flame/http/_handler.h
@@ -14,20 +14,21 @@ namespace flame::http {
     public:
         _handler(server *svr, boost::asio::ip::tcp::socket &&sock);
         ~_handler();
-        void start();
+        php::value run(php::parameters& params);
         
-        void read_request();
-        void call_handler();
+        bool prepare(coroutine_handler& ch);
+        bool execute(coroutine_handler& ch);
 
+        void write_body(server_response* res_ptr, coroutine_handler& ch);
         void write_head(server_response* res_ptr, coroutine_handler& ch);
-        void write_body(server_response* res_ptr, php::string data, coroutine_handler& ch);
+        void write_chunk(server_response* res_ptr, php::string data, coroutine_handler& ch);
         void write_end(server_response* res_ptr, coroutine_handler& ch);
         void write_file(server_response* res_ptr, std::string file, coroutine_handler& ch);
 
         // 由 server_response 销毁时调用
         void finish(server_response *res, coroutine_handler &ch);
 
-      private:
+    private:
         server *svr_ptr;
         php::object svr_obj;
         boost::asio::ip::tcp::socket socket_;
diff --git a/src/flame/http/server.cpp b/src/flame/http/server.cpp
index 5477000..ec2be5d 100644
--- a/src/flame/http/server.cpp
+++ b/src/flame/http/server.cpp
@@ -138,10 +138,10 @@ namespace flame::http {
             boost::system::error_code err;
             accp_.async_accept(sock_, ch[err]);
             if (err == boost::asio::error::operation_aborted) break; 
-            else if (err) throw php::exception(zend_ce_error_exception
-                , (boost::format("Failed to accept connection: %s") % err.message()).str()
-                , err.value());
-            else std::make_shared<_handler>(this, std::move(sock_))->start();
+            else if (err) throw php::exception(zend_ce_error_exception, (boost::format("Failed to accept connection: %s") % err.message()).str(), err.value());
+            else coroutine::start(php::callable( // 按连接启动处理协程（目前仅支持 HTTP/1.1 按 HTTP/2 机制须按照 STREAM 启动协程）
+                std::bind(&_handler::run, std::make_shared<_handler>(this, std::move(sock_)), std::placeholders::_1)
+            ));
         }
         return nullptr;
     }
diff --git a/src/flame/http/server_response.cpp b/src/flame/http/server_response.cpp
index 1ab1e0f..8f43910 100644
--- a/src/flame/http/server_response.cpp
+++ b/src/flame/http/server_response.cpp
@@ -13,7 +13,6 @@ namespace flame::http {
             .property({"cookie", nullptr})
             .property({"body",   nullptr})
             .method<&server_response::__construct>("__construct", {}, php::PRIVATE)
-            .method<&server_response::__destruct>("__destruct")
             .method<&server_response::set_cookie>("set_cookie", {
                 {"name", php::TYPE::STRING},
                 {"value", php::TYPE::STRING, false, true},
@@ -53,15 +52,6 @@ namespace flame::http {
         return nullptr;
     }
 
-    php::value server_response::__destruct(php::parameters& params) {
-        // 响应还未完全结束, 由 handler 代理 last_chunk 响应
-        if (handler_)  {
-            coroutine_handler ch{coroutine::current};
-            handler_->finish(this, ch);
-        }
-        return nullptr;
-    }
-
     php::value server_response::set_cookie(php::parameters& params) {
         php::array cookie(4);
         php::string name = params[0], val;
@@ -128,9 +118,10 @@ namespace flame::http {
         }
         status_ |= STATUS_BODY_SENT;
         php::string chunk = params[0].to_string();
-        handler_->write_body(this, chunk, ch);
+        handler_->write_chunk(this, chunk, ch);
         return nullptr;
     }
+    
     php::value server_response::end(php::parameters& params) {
         if (status_ & STATUS_BODY_END)
             throw php::exception(zend_ce_error_exception
@@ -145,7 +136,7 @@ namespace flame::http {
         if (params.size() > 0) {
             status_ |= STATUS_BODY_SENT;
             php::string chunk = params[0].to_string();
-            handler_->write_body(this, chunk, ch);
+            handler_->write_chunk(this, chunk, ch);
         }
         else {
             status_ |= STATUS_BODY_END;
@@ -154,6 +145,7 @@ namespace flame::http {
         }
         return nullptr;
     }
+
     php::value server_response::file(php::parameters& params) {
         if ((status_ & STATUS_BODY_END) || (status_ & STATUS_BODY_SENT))
             throw php::exception(zend_ce_error_exception
diff --git a/src/flame/http/server_response.h b/src/flame/http/server_response.h
index b9ec11f..958c00c 100644
--- a/src/flame/http/server_response.h
+++ b/src/flame/http/server_response.h
@@ -11,7 +11,6 @@ namespace flame::http {
         static void declare(php::extension_entry& ext);
         // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
         php::value __construct(php::parameters& params);
-        php::value __destruct(php::parameters& params);
         php::value set_cookie(php::parameters& params);
         php::value write_header(php::parameters& params);
         php::value write(php::parameters& params);
diff --git a/test/http_2.php b/test/http_2.php
index 3f6131a..9d3ca42 100644
--- a/test/http_2.php
+++ b/test/http_2.php
@@ -11,7 +11,7 @@ function jsonSerialize()
 
 flame\go(function() {
     $server = new flame\http\server(":::56101");
-    $poll;
+    $q = new flame\queue();
 
     $server->before(function($req, $res, $m) {
         $req->data["start"] = flame\time\now();
@@ -34,42 +34,28 @@ function jsonSerialize()
         // 文件上传
         var_dump($req->file);
         // return $res->body = json_encode($req);
-    })->get("/poll", function($req, $res) use(&$poll) {
+    })->get("/poll", function($req, $res) use($q) {
         $res->header["Content-Type"] = "text/event-stream";
         $res->header["Cache-Control"] = "no-cache";
         $res->write_header(200);
-        $poll = $res;
-        // flame\go(function() use($res) {
-        //     var_dump($res);
-        //     for($i=0;$i<50;++$i) {
-        //         flame\time\sleep(1000);
-        //         echo "write\n";
-        //         $res->write("event: time\n");
-        //         $res->write("data: ".flame\time\now()."\n\n");
-        //     }
-        //     echo "done1\n";
-        // });
-    })->get("/push", function($req, $res) use(&$poll) {
-        if($poll) {
-            $poll->write("event: time\n");
-            $poll->write("data: ".flame\time\now()."\n\n");
-            $res->body = true;
-        }else{
-            $res->body = false;
+        while($data = $q->pop()) {
+            $res->write($data);
         }
-    })->get("/abc", function($req, $res) {
+    })->get("/push", function($req, $res) use($q) {
+        $q->push("event: time\ndata: ".flame\time\now()."\n\n");
+        $res->body = "done";
+    })->get("/file", function($req, $res) {
         $res->header["Content-Type"] = "text/html";
         $res->file(__DIR__, "/coroutine_1.php");
-    })->get("/from", function($req, $res) {
-        $r = flame\http\get("http://127.0.0.1:56120/");
-        flame\time\sleep(10000);
-        $res->body = $r->body;
+    })->get("/exception", function($req, $res) {
+        throw new exception("Some Exception");
+        $res->body = "done";
     })->get("/json", function($req, $res) {
         $res->header["content-type"] = "application/json";
         $res->body = ["data" => new JsonObject()];
     })->after(function($req, $res, $m) {
         $end = flame\time\now();
-        // echo "elapsed: ", ($end - $req->data["start"]), "ms\n";
+        // echo "elapsed: ", ($end - $req->data["start"]), "ms\n";5
     });
     $server->run();
     echo "done2\n";

From 93e8868f5af365ffcb3717878f182c75f7995164 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Wed, 3 Jul 2019 22:22:46 +0800
Subject: [PATCH 143/146] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=97=A5=E5=BF=97?=
 =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E6=9D=A5=E6=BA=90=EF=BC=8C=E5=BB=BA=E7=AB=8B?=
 =?UTF-8?q?=E6=97=A5=E5=BF=97=20IPC=20=E6=9C=BA=E5=88=B6=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                                |   2 +-
 src/coroutine.h                               |   7 ++
 src/flame/controller.cpp                      |   9 --
 src/flame/controller.h                        |   4 -
 src/flame/http/_handler.cpp                   |   3 +-
 src/flame/kafka/_consumer.cpp                 |   2 +-
 src/flame/kafka/_producer.cpp                 |   2 +-
 src/flame/kafka/consumer.cpp                  |   3 +-
 src/flame/log/log.cpp                         |  26 +---
 src/flame/log/log.h                           |   3 -
 src/flame/log/logger.cpp                      |  46 ++++++-
 src/flame/log/logger.h                        |  20 +--
 src/flame/master.cpp                          |  69 ++++++----
 src/flame/master.h                            |  33 +++--
 src/flame/rabbitmq/consumer.cpp               |   3 +-
 src/flame/tcp/server.cpp                      |   3 +-
 src/flame/udp/server.cpp                      |   3 +-
 src/flame/worker.cpp                          |  48 ++++---
 src/flame/worker.h                            |  33 +++--
 src/ipc.cpp                                   |  48 +++++--
 src/ipc.h                                     |  44 ++++++-
 src/logger.h                                  |  48 -------
 src/logger_master.cpp                         |  25 ----
 src/logger_master.h                           |  31 -----
 src/logger_worker.cpp                         |  22 ----
 src/logger_worker.h                           |  20 ---
 src/master_ipc.cpp                            | 119 ++++++++++++++++++
 src/master_ipc.h                              |  36 ++++++
 src/master_logger.cpp                         |  14 +++
 src/master_logger.h                           |  20 +++
 src/master_logger_manager.cpp                 |  42 +++++++
 src/master_logger_manager.h                   |  26 ++++
 src/{process_child.cpp => master_process.cpp} |  28 ++---
 src/{process_child.h => master_process.h}     |  13 +-
 ...manager.cpp => master_process_manager.cpp} |  42 ++++---
 src/master_process_manager.h                  |  39 ++++++
 src/process_manager.h                         |  31 -----
 src/signal_watcher.cpp                        |  14 ---
 src/signal_watcher.h                          |  31 +++--
 src/vendor.h                                  |   1 +
 src/worker_ipc.cpp                            | 105 ++++++++++++++++
 src/worker_ipc.h                              |  33 +++++
 src/worker_logger.cpp                         |  10 ++
 src/worker_logger.h                           |  19 +++
 src/worker_logger_buffer.cpp                  |  39 ++++++
 src/worker_logger_buffer.h                    |  19 +++
 src/worker_logger_manager.cpp                 |  47 +++++++
 src/worker_logger_manager.h                   |  21 ++++
 test/logger_1.php                             |   2 +
 49 files changed, 930 insertions(+), 378 deletions(-)
 delete mode 100644 src/logger.h
 delete mode 100644 src/logger_master.cpp
 delete mode 100644 src/logger_master.h
 delete mode 100644 src/logger_worker.cpp
 delete mode 100644 src/logger_worker.h
 create mode 100644 src/master_ipc.cpp
 create mode 100644 src/master_ipc.h
 create mode 100644 src/master_logger.cpp
 create mode 100644 src/master_logger.h
 create mode 100644 src/master_logger_manager.cpp
 create mode 100644 src/master_logger_manager.h
 rename src/{process_child.cpp => master_process.cpp} (65%)
 rename src/{process_child.h => master_process.h} (63%)
 rename src/{process_manager.cpp => master_process_manager.cpp} (56%)
 create mode 100644 src/master_process_manager.h
 delete mode 100644 src/process_manager.h
 delete mode 100644 src/signal_watcher.cpp
 create mode 100644 src/worker_ipc.cpp
 create mode 100644 src/worker_ipc.h
 create mode 100644 src/worker_logger.cpp
 create mode 100644 src/worker_logger.h
 create mode 100644 src/worker_logger_buffer.cpp
 create mode 100644 src/worker_logger_buffer.h
 create mode 100644 src/worker_logger_manager.cpp
 create mode 100644 src/worker_logger_manager.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f3c84b2..6baa161 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,7 +31,7 @@ set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1c)
 set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
 set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.1.0)
 set(VENDOR_PHP /data/server/php-7.2.19)
-set(VENDOR_PHPEXT /data/vendor/phpext-2.1.0)
+set(VENDOR_PHPEXT /data/vendor/phpext-2.2.0)
 set(VENDOR_CARES /data/vendor/cares-1.15.0)
 set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.1)
 set(VENDOR_CURL /data/vendor/curl-7.65.0)
diff --git a/src/coroutine.h b/src/coroutine.h
index d6bc770..eae7b9c 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -94,6 +94,13 @@ class coroutine_handler {
     inline void resume() {
         co_->resume();
     }
+
+    void error(const boost::system::error_code& error) {
+        if(error_) *error_ = error;
+    }
+    void size(std::size_t sz) {
+        if(size_) *size_ = sz;
+    }
 public:
     std::size_t*                size_ = nullptr;
     boost::system::error_code* error_ = nullptr;
diff --git a/src/flame/controller.cpp b/src/flame/controller.cpp
index c8224e0..3476793 100644
--- a/src/flame/controller.cpp
+++ b/src/flame/controller.cpp
@@ -1,5 +1,4 @@
 #include "controller.h"
-#include "master.h"
 #include "worker.h"
 // #include "../logger_master.h"
 // #include "../logger_worker.h"
@@ -58,12 +57,4 @@ namespace flame {
         auto ft = user_cb->equal_range(event);
         for(auto i=ft.first; i!=ft.second; ++i) i->second.call();
     }
-
-    std::ostream& controller::output(int w) {
-        return type == process_type::MASTER ? master::get()->lm_->index(w)->stream() : worker::get()->lm_->index(w)->stream();
-    }
-
-    logger_manager* controller::logger_manager() {
-        return type == process_type::MASTER ? master::get()->lm_.get() : worker::get()->lm_.get();
-    }
 }
diff --git a/src/flame/controller.h b/src/flame/controller.h
index 8ee88e3..a3b289a 100644
--- a/src/flame/controller.h
+++ b/src/flame/controller.h
@@ -1,6 +1,5 @@
 #pragma once
 #include "../vendor.h"
-#include "../logger.h"
 
 namespace flame {
 
@@ -40,9 +39,6 @@ namespace flame {
         controller* on_user(const std::string& event, php::callable cb);
         void call_user_cb(const std::string& event, std::vector<php::value> params);
         void call_user_cb(const std::string& event);
-        std::ostream& output(int w);
-        logger_manager* logger_manager();
-        // TODO write_to_master | write_worker
     };
 
     extern std::unique_ptr<controller> gcontroller;
diff --git a/src/flame/http/_handler.cpp b/src/flame/http/_handler.cpp
index 1a73bba..db00910 100644
--- a/src/flame/http/_handler.cpp
+++ b/src/flame/http/_handler.cpp
@@ -1,5 +1,6 @@
 #include "../coroutine.h"
 #include "../time/time.h"
+#include "../log/logger.h"
 #include "_handler.h"
 #include "http.h"
 #include "server.h"
@@ -92,7 +93,7 @@ namespace flame::http {
             gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
             // 记录错误信息
             php::object obj = ex;
-            gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
+            log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
             return false;
         }
     }
diff --git a/src/flame/kafka/_consumer.cpp b/src/flame/kafka/_consumer.cpp
index 06e78fc..472db21 100644
--- a/src/flame/kafka/_consumer.cpp
+++ b/src/flame/kafka/_consumer.cpp
@@ -63,7 +63,7 @@ namespace flame::kafka {
     void _consumer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
         _consumer* self = reinterpret_cast<_consumer*>(data);
         if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
-            gcontroller->output(0) << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
     }
 
     void _consumer::subscribe(coroutine_handler &ch) {
diff --git a/src/flame/kafka/_producer.cpp b/src/flame/kafka/_producer.cpp
index 4dff745..24bd757 100644
--- a/src/flame/kafka/_producer.cpp
+++ b/src/flame/kafka/_producer.cpp
@@ -46,7 +46,7 @@ namespace flame::kafka {
     void _producer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
         _producer* self = reinterpret_cast<_producer*>(data);
         if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
-            gcontroller->output(0) << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
     }
 
     void _producer::start() {
diff --git a/src/flame/kafka/consumer.cpp b/src/flame/kafka/consumer.cpp
index 0ba1a64..5223943 100644
--- a/src/flame/kafka/consumer.cpp
+++ b/src/flame/kafka/consumer.cpp
@@ -5,6 +5,7 @@
 #include "kafka.h"
 #include "message.h"
 #include "../../coroutine_queue.h"
+#include "../log/logger.h"
 
 namespace flame::kafka {
 
@@ -47,7 +48,7 @@ namespace flame::kafka {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
                     }
                 }
                 if (--count == 0) ch_run.resume();
diff --git a/src/flame/log/log.cpp b/src/flame/log/log.cpp
index f0ce8bc..3cf3e25 100644
--- a/src/flame/log/log.cpp
+++ b/src/flame/log/log.cpp
@@ -1,12 +1,10 @@
-#include "../controller.h"
+#include "../coroutine.h"
+#include "../worker.h"
 #include "log.h"
 #include "logger.h"
-#include "../coroutine.h"
 
 namespace flame::log {
     
-    logger* logger_ = nullptr;
-
     static php::value trace(php::parameters &params) {
         logger_->write_ex(logger::LEVEL_TRACE, params);
         return nullptr;
@@ -38,28 +36,14 @@ namespace flame::log {
     }
 
     php::value connect(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
         php::object obj {php::class_entry<logger>::entry()};
         logger* ptr = static_cast<logger*>(php::native(obj));
-        coroutine_handler ch { coroutine::current };
-        ptr->lg_ = gcontroller->logger_manager()->connect(params[0], ch);
+        ptr->connect(params[0], ch);
         return obj;
     }
 
     void declare(php::extension_entry &ext) {
-        gcontroller->on_init([] (const php::array& options) {
-            if (options.exists("level")) logger::LEVEL_OPT = options.get("level").to_integer();
-            else logger::LEVEL_OPT = logger::LEVEL_TRACE;
-            std::string output = "stdout";
-            if (options.exists("logger")) output = options.get("logger").to_string();
-            logger_ = new logger();
-            // 启动的一个更轻量的 C++ 内部协程
-            ::coroutine::start(gcontroller->context_x.get_executor(), [output] (::coroutine_handler ch) {
-                logger_->set_logger( gcontroller->logger_manager()->connect(output, ch) );
-            });
-        });
-        for(int i=0;i<logger::LEVEL_STR.size();++i) {
-            ext.constant({"flame\\log\\" + logger::LEVEL_STR[i], i}); // 日志等级常量
-        }
         ext
             .function<trace>("flame\\log\\trace")
             .function<debug>("flame\\log\\debug")
@@ -69,5 +53,7 @@ namespace flame::log {
             .function<error>("flame\\log\\error")
             .function<fatal>("flame\\log\\fail")
             .function<fatal>("flame\\log\\fatal");
+        
+        logger::declare(ext);
     }
 }
diff --git a/src/flame/log/log.h b/src/flame/log/log.h
index 378be4d..0b69f90 100644
--- a/src/flame/log/log.h
+++ b/src/flame/log/log.h
@@ -2,9 +2,6 @@
 #include "../../vendor.h"
 
 namespace flame::log {
-    class logger;
-    // 默认日志记录器
-    extern logger* logger_;
     void declare(php::extension_entry &ext);
     php::value connect(php::parameters& params);
 } // namespace flame::log
diff --git a/src/flame/log/logger.cpp b/src/flame/log/logger.cpp
index 492dd36..5046f7d 100644
--- a/src/flame/log/logger.cpp
+++ b/src/flame/log/logger.cpp
@@ -1,8 +1,9 @@
+#include "../../worker_logger.h"
 #include "log.h"
 #include "logger.h"
+#include "../worker.h"
 #include "../time/time.h"
-#include "../../logger.h"
-#include "../controller.h"
+#include "../coroutine.h"
 
 namespace flame::log {
     
@@ -16,13 +17,48 @@ namespace flame::log {
     };
     int logger::LEVEL_OPT = logger::LEVEL_TRACE;
 
-    logger::~logger() {
-        gcontroller->logger_manager()->destroy(lg_);
+    logger* logger_ = nullptr;
+
+    void logger::declare(php::extension_entry& ext) {
+        gcontroller->on_init([] (const php::array& options) {
+            if (options.exists("level")) logger::LEVEL_OPT = options.get("level").to_integer();
+            else logger::LEVEL_OPT = logger::LEVEL_TRACE;
+            std::string output = "stdout";
+            if (options.exists("logger")) output = options.get("logger").to_string();
+            logger_ = new logger();
+            // 启动的一个更轻量的 C++ 内部协程
+            ::coroutine::start(gcontroller->context_x.get_executor(), [output] (::coroutine_handler ch) {
+                logger_->connect(output, ch);
+            });
+        });
+        for(int i=0;i<logger::LEVEL_STR.size();++i) {
+            ext.constant({"flame\\log\\" + logger::LEVEL_STR[i], i}); // 日志等级常量
+        }
+
+        php::class_entry<logger> class_logger("flame\\log\\logger");
+        class_logger
+            .method<&logger::trace>("trace")
+            .method<&logger::debug>("debug")
+            .method<&logger::info>("info")
+            .method<&logger::warning>("warn")
+            .method<&logger::warning>("warning")
+            .method<&logger::error>("error")
+            .method<&logger::fatal>("fail")
+            .method<&logger::fatal>("fatal");
+        ext.add(std::move(class_logger));
+    }
+    
+    void logger::connect(const std::string& path, ::coroutine_handler& ch) {
+        lg_ = worker::get()->lm_connect(path, ch);
+    }
+    
+    std::ostream& logger::stream() {
+        return lg_ ? lg_->stream() : std::clog; // 由于 lg_ 的填充是异步的，须防止其还未初始化就被其他模块访问
     }
 
     void logger::write_ex(int lv, php::parameters& params) {
         if (lv < LEVEL_OPT) return;
-        std::ostream& os = lg_ ? lg_->stream() : std::cout;
+        std::ostream& os = logger::stream();
         os << '[' << time::iso() << "] (";
         os << LEVEL_STR[lv];
         os << ")";
diff --git a/src/flame/log/logger.h b/src/flame/log/logger.h
index 17ae309..2a7518c 100644
--- a/src/flame/log/logger.h
+++ b/src/flame/log/logger.h
@@ -1,9 +1,9 @@
 #pragma once
 #include "../../vendor.h"
 
-class logger;
+class coroutine_handler;
+class worker_logger;
 namespace flame::log {
-    
 
     class logger: public php::class_base {
     public:
@@ -15,23 +15,23 @@ namespace flame::log {
             LEVEL_ERROR,
             LEVEL_FATAL,
         };
+        static void declare(php::extension_entry& ext);
         static std::array<std::string, 6> LEVEL_STR;
         static int LEVEL_OPT;
-        ~logger();
+
+        // 使用父类型 coroutine_handler 引用能够兼容 C++ / PHP 协程
+        void connect(const std::string& path, ::coroutine_handler& ch);
         void write_ex(int lv, php::parameters& params);
+        std::ostream& stream();
         php::value trace(php::parameters &params);
         php::value debug(php::parameters &params);
         php::value info(php::parameters &params);
         php::value warning(php::parameters &params);
         php::value error(php::parameters &params);
         php::value fatal(php::parameters &params);
-
-        void set_logger(::logger* lg) {
-            lg_ = lg;
-        }
     private:
-        ::logger* lg_ = nullptr;
-
-        friend php::value connect(php::parameters& params);
+        std::shared_ptr<worker_logger> lg_;
     };
+    // 默认日志记录器
+    extern logger* logger_;
 }
\ No newline at end of file
diff --git a/src/flame/master.cpp b/src/flame/master.cpp
index 7fd84ad..56fe2ee 100644
--- a/src/flame/master.cpp
+++ b/src/flame/master.cpp
@@ -1,10 +1,12 @@
 #include "master.h"
 #include "controller.h"
 #include "../util.h"
+#include "../master_logger.h"
+#include "../master_process.h"
 
 namespace flame {
 
-    master* master::mm_;
+    std::shared_ptr<master> master::mm_;
 
     void master::declare(php::extension_entry& ext) {
         ext
@@ -16,7 +18,7 @@ namespace flame {
 
     php::value master::init(php::parameters& params) {
         php::array options = php::array(0);
-        if(params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
+        if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
         if (options.exists("timeout"))
             gcontroller->worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
         else
@@ -26,10 +28,17 @@ namespace flame {
         // 设置进程标题
         std::string title = params[0];
         php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
+
         // 主进程控制对象
-        master::mm_ = new master();
+        master::mm_.reset(new master());
+        if (options.exists("logger")) 
+            master::mm_->lg_ = master::mm_->lm_connect(options.get("logger"));
+        else
+            master::mm_->lg_ = master::mm_->lm_connect("stdout");
         // 初始化启动
         gcontroller->init(options);
+        // 信号监听启动
+        master::mm_->sw_watch();
         return nullptr;
     }
 
@@ -38,12 +47,16 @@ namespace flame {
             throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
             
         gcontroller->status |= controller::STATUS_RUN;
-        // 启动工作进程
-        master::get()->pm_.start();
+
+        master::get()->pm_start(); // 启动工作进程
         std::thread ts {[] () {
             gcontroller->context_y.run();
         }};
         gcontroller->context_x.run();
+        
+        master::mm_->sw_close();
+        master::mm_->ipc_close();
+        master::mm_->lm_close();
 
         if (gcontroller->status & controller::STATUS_EXCEPTION) _exit(-1);
         else gcontroller->stop();
@@ -57,46 +70,52 @@ namespace flame {
     }
 
     master::master()
-    : lm_(new logger_manager_master)
-    , pm_(gcontroller->context_x, gcontroller->worker_size)
-    , sw_(gcontroller->context_y) {
+    : master_process_manager(gcontroller->context_x, gcontroller->worker_size)
+    , signal_watcher(gcontroller->context_y)
+    , master_logger_manager()
+    , master_ipc(gcontroller->context_y) {
         
-        sw_.start(std::bind(&master::on_signal, this, std::placeholders::_1, std::placeholders::_2));
     }
 
-    void master::on_signal(const boost::system::error_code& error, int sig) {
-        if (error) return;
+    std::ostream& master::output() {
+        return lg_->stream();
+    }
+    // !!! 此函数在工作线程中工作
+    bool master::on_signal(int sig) {
         switch(sig) {
         case SIGINT: 
-            coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
-                pm_.restart(ch);
+            coroutine::start(gcontroller->context_x.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                pm_reset(ch);
             });
         break;
         case SIGUSR2: 
-            lm_->reload(); // 日志重载
+            lm_reload(); // 日志重载
         break;
         case SIGUSR1:
             if(++stats_ % 2 == 1) 
-                lm_->index(0)->stream() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl;
+                output() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl;
             else
-                lm_->index(0)->stream() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl;
-            pm_.signal(SIGUSR1); // 长短连切换
+                output() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl;
+            pm_kills(SIGUSR1); // 长短连切换
         break;
         case SIGTERM:
             if(++close_ > 1) {
-                sw_.close();
-                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
-                    pm_.close(true, ch);
+                sw_close();
+                coroutine::start(gcontroller->context_x.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                    pm_close(0, ch); // 立即关闭
                 });
-                return;
+                return false; // 除强制停止信号外，需要持续监听信号
             }else{
-                coroutine::start(gcontroller->context_x.get_executor(), [this] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
-                    pm_.close(false, ch);
+                coroutine::start(gcontroller->context_x.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                    pm_close(gcontroller->worker_quit, ch); // 超时关闭
                 });
             }
             break;
         }
-        // 除强制停止信号外，需要持续监听信号
-        sw_.start(std::bind(&master::on_signal, this, std::placeholders::_1, std::placeholders::_2));
+        return true;
+    }
+    // !!! 此函数在工作线程中工作
+    bool master::on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) {
+        return master_ipc::on_message(msg, sock);
     }
 }
diff --git a/src/flame/master.h b/src/flame/master.h
index 6e55bf1..1baf914 100644
--- a/src/flame/master.h
+++ b/src/flame/master.h
@@ -1,13 +1,14 @@
 #pragma once
 #include "../vendor.h"
-#include "../process_manager.h"
-#include "../logger_master.h"
+#include "../master_process_manager.h"
+#include "../master_logger_manager.h"
 #include "../signal_watcher.h"
+#include "../master_ipc.h"
 
 namespace flame {
-    class master {
+    class master: public std::enable_shared_from_this<master>, public master_process_manager, public signal_watcher, public master_logger_manager, public master_ipc {
     public:
-        static inline master* get() {
+        static inline std::shared_ptr<master> get() {
             return mm_;
         }
         static void declare(php::extension_entry& ext);
@@ -16,16 +17,28 @@ namespace flame {
         static php::value dummy(php::parameters& params);
 
         master();
-        void on_signal(const boost::system::error_code& error, int sig);
+        std::ostream& output() override;
+    protected:
+        std::shared_ptr<master_process_manager> pm_self() override {
+            return std::static_pointer_cast<master_process_manager>(shared_from_this());
+        }
+        virtual std::shared_ptr<signal_watcher> sw_self() override {
+            return std::static_pointer_cast<signal_watcher>(shared_from_this());
+        }
+        virtual std::shared_ptr<master_logger_manager> lm_self() override {
+            return std::static_pointer_cast<master_logger_manager>(shared_from_this());
+        }
+        virtual std::shared_ptr<master_ipc> ipc_self() override {
+            return std::static_pointer_cast<master_ipc>(shared_from_this());
+        }
+        bool on_signal(int sig) override;
+        bool on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) override;
     private:
-        static master*  mm_;
+        static std::shared_ptr<master> mm_;
+        master_logger* lg_;
 
-        process_manager       pm_;
-        std::unique_ptr<logger_manager> lm_;
-        signal_watcher        sw_;
         int close_ = 0;
         int stats_ = 0;
-
         friend class controller;
     };
 }
\ No newline at end of file
diff --git a/src/flame/rabbitmq/consumer.cpp b/src/flame/rabbitmq/consumer.cpp
index 0fb1469..95d196b 100644
--- a/src/flame/rabbitmq/consumer.cpp
+++ b/src/flame/rabbitmq/consumer.cpp
@@ -3,6 +3,7 @@
 #include "_client.h"
 #include "message.h"
 #include "../time/time.h"
+#include "../log/logger.h"
 
 namespace flame::rabbitmq  {
     
@@ -53,7 +54,7 @@ namespace flame::rabbitmq  {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
                     }
                 }
                 if (--count == 0)  ch.resume(); // ----> 1
diff --git a/src/flame/tcp/server.cpp b/src/flame/tcp/server.cpp
index 87bebee..44fa431 100644
--- a/src/flame/tcp/server.cpp
+++ b/src/flame/tcp/server.cpp
@@ -4,6 +4,7 @@
 #include "server.h"
 #include "tcp.h"
 #include "socket.h"
+#include "../log/logger.h"
 
 namespace flame::tcp {
 
@@ -87,7 +88,7 @@ namespace flame::tcp {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
                     }
                     return nullptr;
                 }));
diff --git a/src/flame/udp/server.cpp b/src/flame/udp/server.cpp
index f0cb335..aca1199 100644
--- a/src/flame/udp/server.cpp
+++ b/src/flame/udp/server.cpp
@@ -3,6 +3,7 @@
 #include "../time/time.h"
 #include "udp.h"
 #include "server.h"
+#include "../log/logger.h"
 
 namespace flame::udp {
     
@@ -83,7 +84,7 @@ namespace flame::udp {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        gcontroller->output(0) << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
                     }
                 }
                 if (--count == 0) ch.resume();
diff --git a/src/flame/worker.cpp b/src/flame/worker.cpp
index 8d2efff..0fb9465 100644
--- a/src/flame/worker.cpp
+++ b/src/flame/worker.cpp
@@ -1,3 +1,4 @@
+#include "../worker_logger.h"
 #include "controller.h"
 #include "worker.h"
 #include "coroutine.h"
@@ -5,6 +6,7 @@
 #include "queue.h"
 #include "mutex.h"
 #include "log/log.h"
+#include "log/logger.h"
 #include "os/os.h"
 #include "time/time.h"
 #include "mysql/mysql.h"
@@ -21,7 +23,7 @@
 #include "toml/toml.h"
 
 namespace flame {
-    worker* worker::ww_;
+    std::shared_ptr<worker> worker::ww_;
 
     void worker::declare(php::extension_entry& ext) {
         ext
@@ -100,9 +102,12 @@ namespace flame {
         }
         else
             php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
-        
-        worker::ww_ = new worker();
-        gcontroller->init(options);        
+
+        worker::ww_.reset(new worker());
+
+        gcontroller->init(options); // 首个 logger 的初始化过程在 logger 注册 on_init 回调中进行（异步的）
+        // 信号监听启动
+        worker::ww_->sw_watch();
         return nullptr;
     }
 
@@ -116,7 +121,7 @@ namespace flame {
             } catch (const php::exception &ex) {
                 gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
                 php::object obj = ex; // 记录错误信息
-                gcontroller->output(0) << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
+                log::logger_->stream() << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
 
                 boost::asio::post(gcontroller->context_x, [] () {
                     gcontroller->status |= controller::STATUS_EXCEPTION;
@@ -156,7 +161,9 @@ namespace flame {
         }
         gcontroller->context_x.run();
         tswork.reset();
-        worker::get()->sw_.close();
+        worker::ww_->sw_close();
+        worker::ww_->ipc_close();
+        worker::ww_->lm_close();
         for (int i=0; i<4; ++i) ts[i].join();
         gcontroller->stop();
         return nullptr;
@@ -195,22 +202,26 @@ namespace flame {
         return nullptr;
     }
 
-    worker::worker() 
-    : lm_(new logger_manager_worker)
-    , sw_(gcontroller->context_y) {
+    worker::worker()
+    : signal_watcher(gcontroller->context_y)
+    , worker_ipc(gcontroller->context_y)
+    , worker_logger_manager(this) {
+
+    }
 
-        sw_.start(std::bind(&worker::on_signal, this, std::placeholders::_1, std::placeholders::_2));
+    std::ostream& worker::output() {
+        return log::logger_ ? log::logger_->stream() : std::clog;
     }
 
-    void worker::on_signal(const boost::system::error_code error, int sig) {
-        if(error) return;
+    // !!! 此函数在工作线程中运行
+    bool worker::on_signal(int sig) {
         switch(sig) {
         case SIGINT:
         case SIGTERM:
             if(++close_ > 1) {
                 gcontroller->context_x.stop();
                 gcontroller->context_y.stop();
-                return;
+                return false; // 多次停止信号立即结束
             }
             else coroutine::start(php::callable([] (php::parameters& params) -> php::value { // 通知用户退出(超时后主进程将杀死子进程)
                 gcontroller->call_user_cb("quit");
@@ -218,12 +229,17 @@ namespace flame {
             }));
             break;
         case SIGUSR1:
-            gcontroller->status ^= controller::STATUS_CLOSECONN;
+            boost::asio::post(gcontroller->context_x, [] () {
+                gcontroller->status ^= controller::STATUS_CLOSECONN;
+            });
             break;
         case SIGUSR2: // 日志重载, 与子进程无关
             break;
         }
-        // 强制结束时不再重复监听
-        sw_.start(std::bind(&worker::on_signal, this, std::placeholders::_1, std::placeholders::_2));
+        return true;
+    }
+
+    void worker::on_notify(std::shared_ptr<ipc::message_t> msg) {
+        gcontroller->call_user_cb("notify", {php::json_decode(&msg->payload[0], msg->length)});
     }
 }
\ No newline at end of file
diff --git a/src/flame/worker.h b/src/flame/worker.h
index c8869d3..75ada33 100644
--- a/src/flame/worker.h
+++ b/src/flame/worker.h
@@ -1,16 +1,16 @@
 #pragma once
 #include "../vendor.h"
-#include "../logger_worker.h"
+#include "../worker_logger_manager.h"
 #include "../signal_watcher.h"
+#include "../worker_ipc.h"
 
 namespace flame {
-    class worker {
+    class worker: public std::enable_shared_from_this<worker>, public signal_watcher, public worker_ipc, public worker_logger_manager {
     public:
-        static inline worker* get() {
-            return ww_;
+        static inline std::shared_ptr<worker> get() {
+            return worker::ww_;
         }
         static void declare(php::extension_entry& ext);
-        
         // 主流程相关
         static php::value init(php::parameters& params);
         static php::value go(php::parameters& params);
@@ -23,18 +23,25 @@ namespace flame {
         // 级联数组设置、读取
         static php::value get(php::parameters& params);
         static php::value set(php::parameters& params);
-
+        
         worker();
-        void on_signal(const boost::system::error_code error, int sig);
+        std::ostream& output() override;
+    protected:
+        virtual std::shared_ptr<signal_watcher> sw_self() override {
+            return std::static_pointer_cast<signal_watcher>(shared_from_this());
+        }
+        virtual std::shared_ptr<worker_ipc> ipc_self() override {
+            return std::static_pointer_cast<worker_ipc>(shared_from_this());
+        }
+        virtual std::shared_ptr<worker_logger_manager> lm_self() override {
+            return std::static_pointer_cast<worker_logger_manager>(shared_from_this());
+        }
+        bool on_signal(int sig) override;
+        void on_notify(std::shared_ptr<ipc::message_t> msg) override;
     private:
-        static worker* ww_;
-
-        std::unique_ptr<logger_manager> lm_;
-        signal_watcher sw_;
+        static std::shared_ptr<worker> ww_;
 
         int close_ = 0;
         int stats_ = 0;
-
-        friend class controller;
     };
 }
\ No newline at end of file
diff --git a/src/ipc.cpp b/src/ipc.cpp
index ee85c7d..43715df 100644
--- a/src/ipc.cpp
+++ b/src/ipc.cpp
@@ -1,15 +1,39 @@
 #include "ipc.h"
 
+static boost::pool<> pool_(ipc::MESSAGE_INIT_CAPACITY);
 
-// coroutine::start(php::callable([this] (php::parameters& params) -> php::value {
-//         coroutine_handler ch { coroutine::current };
-//         boost::system::error_code ec;
-// RECONNECT:
-//         socket_.async_connect(address_, ch[ec]);
-//         if(ec) {
-//             co_sleep(std::chrono::milliseconds(std::rand() % 500 + 500), ch);
-//             goto RECONNECT;
-//         }
-        
-//         return nullptr;
-//     }));
\ No newline at end of file
+ipc::message_t* ipc::malloc_message(std::uint16_t length) {
+    ipc::message_t* msg;
+    if(length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
+        length = (length + sizeof(ipc::message_t) + 4096 - 1) & ~(4096-1);
+        msg = (ipc::message_t*)malloc(length);
+        msg->length = length - sizeof(ipc::message_t);
+    }
+    else {
+        msg = (ipc::message_t*)malloc(ipc::MESSAGE_INIT_CAPACITY);
+        msg->length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t);
+    }
+    return msg;
+}
+
+void ipc::free_message(ipc::message_t* msg) {
+    if(msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) 
+        free(msg);
+    else
+        pool_.free(msg);
+}
+
+std::shared_ptr<ipc::message_t> ipc::create_message() {
+    return std::shared_ptr<ipc::message_t>(ipc::malloc_message(), ipc::free_message);
+}
+
+void ipc::relloc_message(std::shared_ptr<ipc::message_t>& msg, std::uint16_t copy, std::uint16_t length) {
+    ipc::message_t* m = malloc_message(length);
+    std::memcpy(m, msg.get(), sizeof(ipc::message_t) + copy);
+    msg.reset(m, free_message);
+}
+
+std::uint32_t ipc::create_uid() {
+    static std::uint32_t start = 0;
+    return ++start;
+}
diff --git a/src/ipc.h b/src/ipc.h
index 5c2002d..cd28e5f 100644
--- a/src/ipc.h
+++ b/src/ipc.h
@@ -1,9 +1,45 @@
 #pragma once
 #include "vendor.h"
+#include "coroutine.h"
 
-class ipc_abstract {
+class ipc {
 public:
-    
-    boost::asio::local::stream_protocol::socket    socket_;
-    boost::asio::local::stream_protocol::endpoint address_;
+    // 传输协议：消息格式
+    struct message_t {
+        std::uint8_t  command;
+        std::uint8_t  target;
+        std::uint16_t length;
+        std::uint32_t unique_id;
+        char payload[0];
+    };
+    // 传输协议：命令
+    enum command_t {
+        MESSAGE_INIT_CAPACITY = 2048,
+
+        COMMAND_REGISTER       = 0x01,
+        COMMAND_LOGGER_CONNECT = 0x02,
+        COMMAND_LOGGER_DESTROY = 0x03,
+        COMMAND_LOGGER_DATA    = 0x04,
+
+        COMMAND_TRANSFER_TO_CHILD = 0x10,
+        COMMAND_NOTIFY_DATA    = 0x10,
+    };
+    struct callback_t {
+        coroutine_handler&               cch;
+        std::shared_ptr<ipc::message_t>& res;
+    };
+    // 消息容器构建
+    static message_t* malloc_message(std::uint16_t length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t));
+    // 消息容器释放
+    static void free_message(message_t* msg);
+    // 消息容器构建
+    static std::shared_ptr<message_t> create_message();
+    // 消息容器长度调整
+    static void relloc_message(std::shared_ptr<ipc::message_t>& msg, std::uint16_t copy, std::uint16_t length);
+    // 生成消息ID
+    static std::uint32_t create_uid();
+    // 请求并等待响应   
+    virtual std::shared_ptr<ipc::message_t> ipc_request(std::shared_ptr<ipc::message_t> data, coroutine_handler& ch) = 0;
+    // 请求（无响应）
+    virtual void ipc_request(std::shared_ptr<ipc::message_t> data) = 0;
 };
diff --git a/src/logger.h b/src/logger.h
deleted file mode 100644
index 76efac6..0000000
--- a/src/logger.h
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma once
-#include "vendor.h"
-
-class logger {
-public:
-    logger(unsigned int index)
-    : ref_(1)
-    , idx_(index) {
-        
-
-    }
-
-    virtual ~logger() = default;
-
-    unsigned int addref() { return ++ref_; }
-    unsigned int delref() { return --ref_; }
-
-    unsigned int index() {
-        return idx_;
-    }
-    virtual void reload() {}
-    virtual std::ostream& stream() = 0;
-    virtual void write(std::string_view data, bool flush = true) = 0;
-private:
-    unsigned int ref_;
-    unsigned int idx_;
-
-};
-
-class coroutine_handler;
-class logger_manager {
-public:
-    virtual ~logger_manager() = default;
-    virtual logger* connect(const std::string& filepath, coroutine_handler& ch) = 0;
-    void reload() {
-        for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload();
-    }
-    // 删除对应的 logger 引用
-    void destroy(logger* l) {
-        if(l->delref() == 0) logger_.erase(l->index());
-    }
-    logger* index(unsigned int w) {
-        return logger_.empty() ? nullptr : logger_[w].get();
-    }
-protected:
-    std::map<unsigned int, std::unique_ptr<logger>> logger_;
-};
-
diff --git a/src/logger_master.cpp b/src/logger_master.cpp
deleted file mode 100644
index 62ffea5..0000000
--- a/src/logger_master.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include "logger_master.h"
-#include "coroutine.h"
-
-void logger_master::reload() {
-    file_.reset(new std::ofstream(path_, std::ios_base::out | std::ios_base::app));
-    if (file_->fail()) // 文件打开失败时不会抛出异常，需要额外的状态检查
-        std::cerr << "(ERROR) failed to create/open logger: file cannot be created or accessed\n";
-    else return;
-    file_.reset(&std::clog, boost::null_deleter());
-}
-
-logger* logger_manager_master::connect(const std::string& filepath, coroutine_handler& ch) {
-    std::filesystem::path path = filepath;
-    
-    for(auto i=logger_.begin();i!=logger_.end();++i) {
-        if(static_cast<logger_master*>(i->second.get())->path_ == path) {
-            i->second->addref();
-            return i->second.get();
-        }
-    }
-    auto p = logger_.insert({index_, std::make_unique<logger_master>(path, index_)});
-    ++index_;
-    p.first->second->reload();
-    return p.first->second.get();
-}
diff --git a/src/logger_master.h b/src/logger_master.h
deleted file mode 100644
index 4cd1bf4..0000000
--- a/src/logger_master.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-#include "vendor.h"
-#include <fstream>
-#include "logger.h"
-
-class logger_master: public logger {
-private:
-    std::shared_ptr<std::ostream> file_;
-    std::filesystem::path         path_;
-public:
-    logger_master(std::filesystem::path path, int index)
-    : logger(index)
-    , path_(path) {}
-
-    void reload() override;
-    std::ostream& stream() override {
-        return *file_;
-    }
-    void write(std::string_view data, bool flush = true) override {
-        *file_ << data;
-        if(flush) file_->flush();
-    }
-    friend class logger_manager_master;
-};
-
-class logger_manager_master: public logger_manager {
-public:
-    logger* connect(const std::string& filepath, coroutine_handler& ch) override;
-private:
-    unsigned int index_ = 0;
-};
\ No newline at end of file
diff --git a/src/logger_worker.cpp b/src/logger_worker.cpp
deleted file mode 100644
index bd8334c..0000000
--- a/src/logger_worker.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "logger_worker.h"
-#include "coroutine.h"
-
-std::ostream& logger_worker_out::stream() {
-    return std::clog;
-}
-
-void logger_worker_out::write(std::string_view data, bool flush) {
-    std::clog << data;
-    if(flush) std::clog.flush();
-}
-
-logger* logger_manager_worker::connect(const std::string& filepath, coroutine_handler& ch) {
-    // TODO 实现基于 IPC 的通道支持
-
-    auto i = logger_.find(0);
-    if(i == logger_.end()) {
-        std::cout << "logger_manager_worker::connect\n";
-        logger_[0].reset(new logger_worker_out(0));
-    }
-    return logger_[0].get();
-}
\ No newline at end of file
diff --git a/src/logger_worker.h b/src/logger_worker.h
deleted file mode 100644
index 685b31f..0000000
--- a/src/logger_worker.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-#include "vendor.h"
-#include "logger.h"
-
-class logger_worker_out: public logger {
-public:
-    using logger::logger;
-    std::ostream& stream() override;
-    void write(std::string_view data, bool flush = true) override;
-};
-
-class logger_worker_ipc: public logger {
-    std::ostream& stream() override;
-    void write(std::string_view data, bool flush = true) override;
-};
-
-class logger_manager_worker: public logger_manager {
-public:
-    logger* connect(const std::string& filepath, coroutine_handler& ch) override;
-};
diff --git a/src/master_ipc.cpp b/src/master_ipc.cpp
new file mode 100644
index 0000000..44ecc62
--- /dev/null
+++ b/src/master_ipc.cpp
@@ -0,0 +1,119 @@
+#include "master_ipc.h"
+#include "master_logger.h"
+#include "util.h"
+#include "coroutine.h"
+
+master_ipc::master_ipc(boost::asio::io_context& io)
+: io_(io)
+, server_(io) {
+    
+}
+
+master_ipc::~master_ipc() {
+    ipc_close();
+    std::filesystem::remove(svrsck_);
+}
+
+void master_ipc::ipc_run(coroutine_handler ch) {
+    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getpid()).str();
+    std::error_code ec;
+    std::filesystem::remove(svrsck_, ec);
+    boost::asio::local::stream_protocol::endpoint addr(svrsck_);
+    server_.bind(addr);
+    server_.listen();
+
+    boost::system::error_code error;
+    while(true) {
+        auto sock = std::make_shared<boost::asio::local::stream_protocol::socket>(io_);
+        server_.async_accept(*sock, ch[error]);
+        if (error) break;
+        // C++ 协程
+        // coroutine::start(io_.get_executor(), std::bind(&master_ipc::ipc_read, ipc_self(), sock, std::placeholders::_1));
+        coroutine::start(io_.get_executor(), [this, self = ipc_self(), sock = std::move(sock)] (coroutine_handler ch) {
+            ipc_read(sock, ch);
+        });
+    }
+    if (error && error != boost::asio::error::operation_aborted) output() << "[" << util::system_time() << "] (ERROR) Failed to accept ipc connection: (" << error.value() << ") " << error.message() << "\n";
+}
+
+void master_ipc::ipc_close() {
+    boost::system::error_code error;
+    server_.close(error);
+    for(auto i=socket_.begin();i!=socket_.end();++i) {
+        i->second->close(error);
+    }
+}
+
+// void master_ipc::ipc_read(socket_ptr sock, coroutine_handler ch) {
+void master_ipc::ipc_read(socket_ptr sock, coroutine_handler& ch) {
+    boost::system::error_code error;
+    while(true) {
+        std::shared_ptr<ipc::message_t> msg = ipc::create_message();
+        boost::asio::async_read(*sock, boost::asio::buffer(msg.get(), sizeof(ipc::message_t)), ch[error]);
+        if (error) break;
+        if (msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
+            ipc::relloc_message(msg, 0, msg->length);
+        }
+        boost::asio::async_read(*sock, boost::asio::buffer(&msg->payload[0], msg->length), ch[error]);
+        if (error) break;
+        if (!on_message(msg, sock)) break;
+    }
+    
+    if (error && error != boost::asio::error::operation_aborted && error != boost::asio::error::eof) 
+        output() << "[" << util::system_time() << "] (ERROR) Failed to read ipc connection: (" << error.value() << ") " << error.message() << "\n";
+    // 发生次数较少，效率不太重要了
+    for(auto i=socket_.begin();i!=socket_.end();++i) {
+        if(i->second == sock) {
+            socket_.erase(i);
+            break;
+        }
+    }
+    sock->close(error);
+}
+
+bool master_ipc::on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) {
+    if(msg->command >= ipc::COMMAND_TRANSFER_TO_CHILD) send(msg);
+    else if(msg->command == ipc::COMMAND_REGISTER) socket_[msg->target] = sock;
+    return true;
+}
+
+std::shared_ptr<ipc::message_t> master_ipc::ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch) {
+    std::shared_ptr<ipc::message_t> res;
+    callback_.emplace(req->unique_id, ipc::callback_t {ch, res});
+    send(req);
+    ch.suspend();
+    return res;
+}
+
+void master_ipc::ipc_request(std::shared_ptr<ipc::message_t> data) {
+    send(data);
+}
+
+void master_ipc::send(std::shared_ptr<ipc::message_t> msg) {
+    if(sendq_.empty()) {
+        sendq_.push_back(msg);
+        send_next();
+    }else
+        sendq_.push_back(msg);
+}
+
+void master_ipc::send_next() {
+    std::shared_ptr<ipc::message_t> msg = sendq_.front();
+    auto i = socket_.find(msg->target);
+    if(i == socket_.end()) {
+        boost::asio::post(io_, [this] () {
+            sendq_.pop_front();
+            send_next();       
+        });
+    }
+    else {
+        boost::asio::async_write(*i->second, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), [this] (const boost::system::error_code& error, std::size_t size) {
+            if(error) {
+                output() << "[" << util::system_time() << "] [FATAL] Failed to write ipc message: (" << error.value() << ") " << error.message() << "\n";
+                return;
+            }
+            sendq_.pop_front();
+            send_next();
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/master_ipc.h b/src/master_ipc.h
new file mode 100644
index 0000000..235197e
--- /dev/null
+++ b/src/master_ipc.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "vendor.h"
+#include "coroutine.h"
+#include "ipc.h"
+
+class master_ipc {
+public:
+    using socket_ptr = std::shared_ptr<boost::asio::local::stream_protocol::socket>;
+    master_ipc(boost::asio::io_context& io);
+    virtual ~master_ipc();
+    // 输出
+    virtual std::ostream& output() = 0;
+    // 监听连接及连接数据接收
+    void ipc_run(coroutine_handler ch);
+    // void ipc_read(socket_ptr sock, coroutine_handler ch);
+    void ipc_read(socket_ptr sock, coroutine_handler& ch);
+    void ipc_close();
+    // 请求并等待响应
+    std::shared_ptr<ipc::message_t> ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch);
+    // 请求（无响应）
+    void ipc_request(std::shared_ptr<ipc::message_t> data);
+protected:
+    virtual bool on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock);
+    virtual std::shared_ptr<master_ipc> ipc_self() = 0;
+private:
+    boost::asio::io_context&                          io_;
+    std::filesystem::path                         svrsck_;
+    boost::asio::local::stream_protocol::acceptor server_;
+    std::map<std::uint8_t, socket_ptr>            socket_;
+    std::map<std::uint32_t, ipc::callback_t>    callback_;
+    std::list<std::shared_ptr<ipc::message_t>>     sendq_;
+    // 连接数据接收
+    
+    void send(std::shared_ptr<ipc::message_t> msg);
+    void send_next();
+};
diff --git a/src/master_logger.cpp b/src/master_logger.cpp
new file mode 100644
index 0000000..e716c90
--- /dev/null
+++ b/src/master_logger.cpp
@@ -0,0 +1,14 @@
+#include "master_logger.h"
+#include "coroutine.h"
+
+void master_logger::reload() {
+    file_.reset(new std::ofstream(path_, std::ios_base::out | std::ios_base::app));
+    if (file_->fail()) // 文件打开失败时不会抛出异常，需要额外的状态检查
+        std::cerr << "(ERROR) failed to create/open logger: file cannot be created or accessed\n";
+    else return;
+    file_.reset(&std::clog, boost::null_deleter());
+}
+
+void master_logger::close() {
+    file_.reset(&std::clog, boost::null_deleter());
+}
\ No newline at end of file
diff --git a/src/master_logger.h b/src/master_logger.h
new file mode 100644
index 0000000..0af1d37
--- /dev/null
+++ b/src/master_logger.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "vendor.h"
+#include <fstream>
+
+class master_logger {
+private:
+    std::uint8_t                   idx_;
+    unsigned int                   ref_;
+    std::shared_ptr<std::ostream> file_;
+    std::filesystem::path         path_;
+public:
+    master_logger(std::filesystem::path path, int index): idx_(index), ref_(1), path_(path) {}
+
+    void reload();
+    std::ostream& stream() {
+        return *file_;
+    }
+    void close();
+    friend class master_logger_manager;
+};
diff --git a/src/master_logger_manager.cpp b/src/master_logger_manager.cpp
new file mode 100644
index 0000000..d6b7eea
--- /dev/null
+++ b/src/master_logger_manager.cpp
@@ -0,0 +1,42 @@
+#include "master_logger_manager.h"
+#include "master_logger.h"
+
+
+master_logger_manager::master_logger_manager() {}
+master_logger_manager::~master_logger_manager() {}
+
+master_logger* master_logger_manager::lm_connect(const std::string& filepath) {
+    std::filesystem::path path = filepath;
+    
+    for(auto i=logger_.begin();i!=logger_.end();++i) {
+        if(static_cast<master_logger*>(i->second.get())->path_ == path) {
+            ++i->second->ref_;
+            return i->second.get();
+        }
+    }
+    auto p = logger_.insert({index_, std::make_unique<master_logger>(path, index_)});
+    ++index_;
+    p.first->second->reload();
+    return p.first->second.get();
+}
+
+void master_logger_manager::lm_destroy(std::uint8_t idx) {
+    auto i = logger_.find(idx);
+    if(i == logger_.end()) return;
+    assert(i->second->ref_ > 0 && "引用计数异常");
+    if(--i->second->ref_ > 0) return;
+    logger_.erase(i);
+}
+
+master_logger* master_logger_manager::lm_get(std::uint8_t idx) {
+    auto i = logger_.find(idx);
+    return i == logger_.end() ? nullptr : i->second.get();
+}
+
+void master_logger_manager::lm_reload() {
+    for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload();
+}
+
+void master_logger_manager::lm_close() {
+    for(auto i=logger_.begin();i!=logger_.end();++i) i->second->close();
+}
\ No newline at end of file
diff --git a/src/master_logger_manager.h b/src/master_logger_manager.h
new file mode 100644
index 0000000..220d6ba
--- /dev/null
+++ b/src/master_logger_manager.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "vendor.h"
+
+class master_logger;
+class master_logger_manager {
+public:
+    master_logger_manager();
+    virtual ~master_logger_manager();
+    // 日志引用
+    master_logger* lm_connect(const std::string& filepath);
+    // void lm_destroy(master_logger* ml) {
+    //     assert(ml->ref_ > 0 && "引用计数异常");
+    //     if(--ml->ref_ > 0) return;
+    //     logger_.erase(ml->idx_);
+    // }
+    master_logger* lm_get(std::uint8_t idx);
+    // 引用清理
+    void lm_destroy(std::uint8_t idx);
+    // 日志重载
+    void lm_reload();
+    void lm_close();
+    virtual std::shared_ptr<master_logger_manager> lm_self() = 0;
+private:
+    std::uint8_t                                            index_ = 0;
+    std::map<unsigned int, std::unique_ptr<master_logger>> logger_;
+};
\ No newline at end of file
diff --git a/src/process_child.cpp b/src/master_process.cpp
similarity index 65%
rename from src/process_child.cpp
rename to src/master_process.cpp
index 3a0d90b..fd3fc5b 100755
--- a/src/process_child.cpp
+++ b/src/master_process.cpp
@@ -1,10 +1,10 @@
-#include "process_child.h"
-#include "process_manager.h"
-#include "logger.h"
+#include "master_process.h"
+#include "master_process_manager.h"
+#include "master_logger.h"
 #include "util.h"
 
 extern "C" {
-PHPAPI extern char *php_ini_opened_path;
+    PHPAPI extern char *php_ini_opened_path;
 }
 
 static std::string php_cmd() {
@@ -16,12 +16,12 @@ static std::string php_cmd() {
     return ss.str();
 }
 
-process_child::process_child(boost::asio::io_context& io, process_manager* m, int i)
-: manager_(m), index_(i)
+master_process::master_process(boost::asio::io_context& io, master_process_manager* mgr, std::uint8_t idx)
+: mgr_(mgr), idx_(idx)
 , sout_(io), eout_(io) {
     // 准备命令行及环境变量（完全重新启动一个新的 PHP， 通过环境变量标识其为工作进程）
     boost::process::environment env = boost::this_process::environment();
-    env["FLAME_CUR_WORKER"] = std::to_string(i + 1);
+    env["FLAME_CUR_WORKER"] = std::to_string(idx + 1);
     // 构造进程
     proc_ = boost::process::child(io, php_cmd(), env,
         boost::process::std_out > sout_, boost::process::std_err > eout_,
@@ -29,37 +29,37 @@ process_child::process_child(boost::asio::io_context& io, process_manager* m, in
         boost::process::on_exit = [this, &io] (int exit_code, const std::error_code &error) {
             if (error.value() == static_cast<int>(std::errc::no_child_process)) return;
             boost::asio::post(io, [exit_code, this] () {
-                manager_->on_child_close(this, exit_code == 0);
+                mgr_->on_child_close(this, exit_code == 0);
             });
         });
     redirect_output(sout_, sbuf_);
     redirect_output(eout_, ebuf_);
 
     boost::asio::post(io, [this] () {
-        manager_->on_child_start(this);
+        mgr_->on_child_start(this);
     });
 }
 
-void process_child::redirect_output(boost::process::async_pipe& pipe, std::string& data) {
+void master_process::redirect_output(boost::process::async_pipe& pipe, std::string& data) {
     boost::asio::async_read_until(pipe, boost::asio::dynamic_buffer(data), '\n', [this, &pipe, &data] (const boost::system::error_code &error, std::size_t nread) {
         if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
             ; // 忽略
         else if (error) {
-            (manager_->lg_ ? manager_->lg_->stream() : std::cerr) << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
+            mgr_->output() << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
         }
         else {
-            (manager_->lg_ ? manager_->lg_->stream() : std::cerr) << std::string_view(data.c_str(), nread);
+            mgr_->output() << std::string_view(data.c_str(), nread);
             data.erase(0, nread);
             redirect_output(pipe, data);
         }
     });
 }
 
-void process_child::close(bool force) {
+void master_process::close(bool force) {
     if(force) proc_.terminate();
     else ::kill(proc_.id(), SIGTERM);
 }
 
-void process_child::signal(int sig) {
+void master_process::signal(int sig) {
     ::kill(proc_.id(), sig);
 }
\ No newline at end of file
diff --git a/src/process_child.h b/src/master_process.h
similarity index 63%
rename from src/process_child.h
rename to src/master_process.h
index 66f449a..41c5e77 100755
--- a/src/process_child.h
+++ b/src/master_process.h
@@ -3,16 +3,15 @@
 #include <boost/process.hpp>
 #include <boost/process/async.hpp>
 
-class logger;
-class process_manager;
-class process_child {
+class master_process_manager;
+class master_process {
 public:
-    process_child(boost::asio::io_context& io, process_manager* m, int i);
+    master_process(boost::asio::io_context& io, master_process_manager* m, std::uint8_t idx);
     void close(bool force = false);
     void signal(int sig);
 private:
-    process_manager*        manager_;
-    int                       index_;
+    master_process_manager*     mgr_;
+    std::uint8_t                idx_;
 
     boost::process::child      proc_;
     boost::process::async_pipe sout_;
@@ -21,5 +20,5 @@ class process_child {
     std::string                ebuf_;
     void redirect_output(boost::process::async_pipe& pipe, std::string& buffer);
 
-    friend class process_manager;
+    friend class master_process_manager;
 };
\ No newline at end of file
diff --git a/src/process_manager.cpp b/src/master_process_manager.cpp
similarity index 56%
rename from src/process_manager.cpp
rename to src/master_process_manager.cpp
index 411cf95..0e11b8d 100644
--- a/src/process_manager.cpp
+++ b/src/master_process_manager.cpp
@@ -1,31 +1,28 @@
-#include "process_manager.h"
+#include "master_process_manager.h"
+#include "master_process.h"
 #include "coroutine.h"
 #include "util.h"
 
-process_manager::process_manager(boost::asio::io_context& io, unsigned int count)
+master_process_manager::master_process_manager(boost::asio::io_context& io, unsigned int count)
 : io_(io)
 , count_(count) {
 
 }
 
-void process_manager::on_child_start(process_child* w) {
-
-}
-
-void process_manager::on_child_close(process_child* w, bool normal) {
-
+master_process_manager::~master_process_manager() {
+    
 }
 
-void process_manager::start() {
+void master_process_manager::pm_start() {
     for(int i=0;i<count_;++i) {
-        child_[i].reset(new process_child(io_, this, i));
+        child_[i].reset(new master_process(io_, this, i));
     }
 }
 
-void process_manager::restart(coroutine_handler& ch) {
-    std::vector<std::unique_ptr<process_child>> start;
+void master_process_manager::pm_reset(coroutine_handler& ch) {
+    std::vector<std::unique_ptr<master_process>> start;
     for(int i=0;i<count_;++i) {
-        start[i].reset(new process_child(io_, this, i));
+        start[i].reset(new master_process(io_, this, i));
         ch_start_.reset(ch);
         ch_start_.suspend(); // 等待进程启动回调 on_child_event
         ch_start_.reset();
@@ -33,14 +30,14 @@ void process_manager::restart(coroutine_handler& ch) {
         util::co_sleep(io_, std::chrono::milliseconds(140), ch);
         // 新进程已经启动 1/4 后，开始停止原进程（一次性动作）
         if(i == count_ / 4) coroutine::start(io_.get_executor(), [this] (coroutine_handler ch) {
-            close(false, ch);
+            pm_close(false, ch);
         });
     }
     child_ = std::move(start);
 }
 
-void process_manager::close(unsigned int timeout_ms, coroutine_handler& ch) {
-    std::vector<std::unique_ptr<process_child>> closing = std::move(child_);
+void master_process_manager::pm_close(unsigned int timeout_ms, coroutine_handler& ch) {
+    std::vector<std::unique_ptr<master_process>> closing = std::move(child_);
     for(int i=0;i<count_;++i) closing[i]->close(timeout_ms == 0);
     if(timeout_ms == 0) return;
 
@@ -60,14 +57,21 @@ void process_manager::close(unsigned int timeout_ms, coroutine_handler& ch) {
 
     if(timeout) {
         child_ = std::move(closing);
-        close(0, ch);
+        pm_close(0, ch);
         return;
     }
     if(++stopped < count_) goto WAIT_FOR_CLOSE;
     else tm.cancel();
 }
 
-
-void process_manager::signal(int sig) {
+void master_process_manager::pm_kills(int sig) {
     for(int i=0;i<count_;++i) child_[i]->signal(sig);
 }
+
+void master_process_manager::on_child_start(master_process* w) {
+    if (ch_start_) ch_start_.resume(); // 陆续起停流程
+}
+
+void master_process_manager::on_child_close(master_process* w, bool normal) {
+    if (ch_close_) ch_close_.resume(); // 陆续起停流程
+}
\ No newline at end of file
diff --git a/src/master_process_manager.h b/src/master_process_manager.h
new file mode 100644
index 0000000..b9ad058
--- /dev/null
+++ b/src/master_process_manager.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "vendor.h"
+#include "coroutine.h"
+
+class master_process;
+class master_process_manager {
+public:
+    master_process_manager(boost::asio::io_context& io, unsigned int count);
+    virtual ~master_process_manager();
+    // 输出
+    virtual std::ostream& output() = 0;
+protected:
+    virtual std::shared_ptr<master_process_manager> pm_self() = 0;
+    // 启动子进程
+    void pm_start();
+    // 重启子进程（陆续）
+    void pm_reset(coroutine_handler& ch);
+    // 停止子进程
+    void pm_close(unsigned int ms, coroutine_handler& ch);
+    // 发送信号
+    void pm_kills(int sig);
+    // 进程数量
+    std::uint8_t pm_count() {
+        return count_;
+    }
+    // 进程启动回调
+    virtual void on_child_start(master_process* w);
+    // 进程停止回调
+    virtual void on_child_close(master_process* w, bool normal);
+private:
+    boost::asio::io_context&                        io_;
+    unsigned int                                 count_;
+    std::vector<std::unique_ptr<master_process>> child_;
+    
+    coroutine_handler ch_close_;
+    coroutine_handler ch_start_;
+    
+    friend class master_process;
+};
diff --git a/src/process_manager.h b/src/process_manager.h
deleted file mode 100644
index 61f6d58..0000000
--- a/src/process_manager.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-#include "vendor.h"
-#include "coroutine.h"
-#include "process_child.h"
-
-class logger;
-class process_manager {
-public:
-    process_manager(boost::asio::io_context& io, unsigned int count);
-    void start();
-    void restart(coroutine_handler& ch);
-    void close(unsigned int timeout_ms, coroutine_handler& ch);
-    void signal(int signal);
-    unsigned int count() {
-        return count_;
-    }
-    logger*        lg_ = nullptr; // 等待填充
-private:
-    boost::asio::io_context& io_;
-    unsigned int          count_;
-
-    std::vector<std::unique_ptr<process_child>> child_;
-
-    void on_child_start(process_child* w);
-    void on_child_close(process_child* w, bool normal);
-
-    coroutine_handler ch_close_;
-    coroutine_handler ch_start_;
-    
-    friend class process_child;
-};
diff --git a/src/signal_watcher.cpp b/src/signal_watcher.cpp
deleted file mode 100644
index ebbce1b..0000000
--- a/src/signal_watcher.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#include "signal_watcher.h"
-#include "logger.h"
-
-signal_watcher::signal_watcher(boost::asio::io_context& io)
-: ss_(new boost::asio::signal_set(io)) {
-    ss_->add(SIGINT);
-    ss_->add(SIGTERM);
-    ss_->add(SIGUSR1);
-    ss_->add(SIGUSR2);
-}
-
-void signal_watcher::close() {
-    ss_.reset();
-}
\ No newline at end of file
diff --git a/src/signal_watcher.h b/src/signal_watcher.h
index d736120..d730526 100644
--- a/src/signal_watcher.h
+++ b/src/signal_watcher.h
@@ -4,15 +4,30 @@
 class logger;
 class signal_watcher {
 public:
-    explicit signal_watcher(boost::asio::io_context& io);
-    template <class Handler>
-    void start(Handler&& cb) {
-        ss_->async_wait(cb);
+    explicit signal_watcher(boost::asio::io_context& io)
+    : ss_(new boost::asio::signal_set(io)) {
+        ss_->add(SIGINT);
+        ss_->add(SIGTERM);
+        ss_->add(SIGUSR1);
+        ss_->add(SIGUSR2);
     }
-    void close();
-    logger*  lg_ = nullptr; // 等待填充
+    virtual ~signal_watcher() {
+        std::cout << "~signal_watcher\n";
+        ss_.reset();
+    }
+    void sw_watch() {
+        ss_->async_wait([this, self = sw_self()] (const boost::system::error_code& error, int sig) {
+            if (error) return;
+            if (!on_signal(sig)) return;
+            sw_watch();
+        });
+    }
+    void sw_close() {
+        ss_.reset();
+    }
+    virtual std::shared_ptr<signal_watcher> sw_self() = 0;
+protected:
+    virtual bool on_signal(int sig) = 0;
 private:
     std::unique_ptr<boost::asio::signal_set> ss_;
-    int close_ = 0;
-    int stats_ = 0;
 };
\ No newline at end of file
diff --git a/src/vendor.h b/src/vendor.h
index afbc297..c24d050 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -34,3 +34,4 @@ using boost::asio::ip::tcp;
 using boost::asio::ip::udp;
 #include <boost/process.hpp>
 #include <boost/process/async.hpp>
+#include <boost/pool/pool.hpp>
diff --git a/src/worker_ipc.cpp b/src/worker_ipc.cpp
new file mode 100644
index 0000000..72d00b6
--- /dev/null
+++ b/src/worker_ipc.cpp
@@ -0,0 +1,105 @@
+#include "worker_ipc.h"
+#include "util.h"
+
+worker_ipc::worker_ipc(boost::asio::io_context& io)
+: socket_(new boost::asio::local::stream_protocol::socket(io)) {
+
+}
+
+worker_ipc::~worker_ipc() {
+    std::cout << "~worker_ipc\n";
+    ipc_close();
+}
+
+std::shared_ptr<ipc::message_t> worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch) {
+    std::shared_ptr<ipc::message_t> res;
+    callback_.emplace(req->unique_id, ipc::callback_t {ch, res});
+    send(req);
+    std::cout << "before suspend\n";
+    ch.suspend();
+    std::cout << "after suspend\n";
+    return res;
+}
+
+void worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> data) {
+    send(data);
+}
+
+static void json_message_free(ipc::message_t* msg) {
+    zend_string* ptr = reinterpret_cast<zend_string*>((char*)(msg) - offsetof(zend_string, val));
+    smart_str str { ptr, 0 };
+    smart_str_free(&str);
+}
+
+void worker_ipc::ipc_notify(std::uint8_t target, php::value data) {
+    smart_str str; // !!!! 避免 JSON 文本的 COPY 复制
+    smart_str_alloc(&str, 1, false);
+    ipc::message_t* msg = (ipc::message_t*)str.s->val;
+    msg->command = ipc::COMMAND_NOTIFY_DATA;
+    msg->target  = target;
+    str.s->len = sizeof(ipc::message_t);
+    php::json_encode_to(&str, data);
+    msg->length = str.s->len - sizeof(ipc::message_t);
+    send(std::shared_ptr<ipc::message_t>{msg, ipc::free_message});
+}
+
+void worker_ipc::ipc_run(coroutine_handler ch) {
+    // TODO
+    // 连接主进程 IPC 通道
+    // 发送注册消息
+    // 
+    boost::system::error_code error;
+    while(true) {
+        auto msg = ipc::create_message();
+        boost::asio::async_read(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t)), ch[error]);
+        if(error) break;
+        if(msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
+            ipc::relloc_message(msg, 0, msg->length);
+        }
+        boost::asio::async_read(*socket_, boost::asio::buffer(&msg->payload[0], msg->length), ch[error]);
+        if(error) break;
+
+        switch(msg->command) {
+        case ipc::COMMAND_LOGGER_CONNECT: {
+            auto pcb = callback_.extract(msg->unique_id);
+            assert(!pcb.empty() && "未找到必要回调");
+            pcb.mapped().res = msg;
+            pcb.mapped().cch.resume();
+        }
+        break;
+        case ipc::COMMAND_NOTIFY_DATA:
+            on_notify(msg);
+        break;
+        }
+    }
+
+    if (error && error != boost::asio::error::operation_aborted && error != boost::asio::error::eof) 
+        output() << "[" << util::system_time() << "] (ERROR) Failed to read ipc connection: (" << error.value() << ") " << error.message() << "\n";
+    socket_->close(error);
+}
+
+void worker_ipc::ipc_close() {
+    socket_->close();
+}
+
+void worker_ipc::send(std::shared_ptr<ipc::message_t> msg) {
+    
+    if(sendq_.empty()) {
+        sendq_.push_back(msg);
+        send_next();
+    }else
+        sendq_.push_back(msg);
+}
+
+void worker_ipc::send_next() {
+    std::shared_ptr<ipc::message_t> msg = sendq_.front();
+
+    boost::asio::async_write(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), [this] (const boost::system::error_code& error, std::size_t size) {
+        if(error) {
+            output() << "[" << util::system_time() << "] [FATAL] Failed to write ipc message: (" << error.value() << ") " << error.message() << "\n";
+            return;
+        }
+        sendq_.pop_front();
+        send_next();
+    });
+}
diff --git a/src/worker_ipc.h b/src/worker_ipc.h
new file mode 100644
index 0000000..07e0889
--- /dev/null
+++ b/src/worker_ipc.h
@@ -0,0 +1,33 @@
+#pragma once
+#include "vendor.h"
+#include "ipc.h"
+
+class worker_ipc {
+public:
+    worker_ipc(boost::asio::io_context& io);
+    virtual ~worker_ipc();
+    using socket_ptr = std::shared_ptr<boost::asio::local::stream_protocol::socket>;
+    
+    virtual std::ostream& output() = 0;
+    // 请求并等待响应
+    std::shared_ptr<ipc::message_t> ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch);
+    // 请求（无响应）
+    void ipc_request(std::shared_ptr<ipc::message_t> data);
+    // 简化 notify 请求
+    void ipc_notify(std::uint8_t target, php::value data);
+    // 读取及消息监听
+    void ipc_run(coroutine_handler ch);
+    // 关闭通道
+    void ipc_close();
+    // 创建消息唯一ID
+    
+protected:
+    virtual std::shared_ptr<worker_ipc> ipc_self() = 0;
+    virtual void on_notify(std::shared_ptr<ipc::message_t> msg) = 0;
+private:
+    socket_ptr                                 socket_;
+    std::map<std::uint32_t, ipc::callback_t> callback_;
+    std::list<std::shared_ptr<ipc::message_t>>  sendq_;
+    void send(std::shared_ptr<ipc::message_t> msg);
+    void send_next();
+};
diff --git a/src/worker_logger.cpp b/src/worker_logger.cpp
new file mode 100644
index 0000000..3e621c4
--- /dev/null
+++ b/src/worker_logger.cpp
@@ -0,0 +1,10 @@
+#include "worker_logger.h"
+#include "worker_logger_manager.h"
+#include "worker_logger_buffer.h"
+
+worker_logger::worker_logger(worker_logger_manager* mgr, unsigned int idx)
+: idx_(idx)
+, wlb_(new worker_logger_buffer(mgr))
+, oss_(wlb_.get()) {
+    
+}
diff --git a/src/worker_logger.h b/src/worker_logger.h
new file mode 100644
index 0000000..9d5233c
--- /dev/null
+++ b/src/worker_logger.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "vendor.h"
+
+class coroutine_handler;
+class worker_logger_manager;
+class worker_logger_buffer;
+class worker_logger {
+public:
+    worker_logger(worker_logger_manager* mgr, unsigned int index);
+    std::ostream& stream() {
+        return oss_;
+    }
+private:
+    std::uint8_t idx_;
+    std::unique_ptr<worker_logger_buffer> wlb_;
+    std::ostream oss_;
+    friend class worker_logger_manager;
+};
+
diff --git a/src/worker_logger_buffer.cpp b/src/worker_logger_buffer.cpp
new file mode 100644
index 0000000..8ad0a27
--- /dev/null
+++ b/src/worker_logger_buffer.cpp
@@ -0,0 +1,39 @@
+#include "worker_logger_buffer.h"
+#include "worker_logger_manager.h"
+#include "worker_ipc.h"
+
+worker_logger_buffer::worker_logger_buffer(worker_logger_manager* mgr)
+: mgr_(mgr)
+, msg_(ipc::create_message()) {
+    cap_ = msg_->length;
+    msg_->length = 0;
+}
+
+int worker_logger_buffer::overflow(int ch) {
+    char c = ch;
+    msg_->payload[msg_->length] = c;
+    ++msg_->length;
+    if(c == '\n') transfer_msg();
+    return ch;
+}
+
+long worker_logger_buffer::xsputn(const char* s, long c) {
+    if(cap_ - msg_->length < c) {
+        assert(cap_ < 2048 * 1024);
+        ipc::relloc_message(msg_, msg_->length, msg_->length + c);
+    }
+    std::memcpy(msg_->payload + msg_->length, s, c);
+    msg_->length += c;
+
+    if(s[c-1] == '\n') transfer_msg();
+    return c;
+}
+
+void worker_logger_buffer::transfer_msg() {
+    msg_->command = ipc::COMMAND_NOTIFY_DATA;
+    msg_->target  = 0;
+    mgr_->ipc_->ipc_request(msg_);
+    msg_ = ipc::create_message();
+    cap_ = msg_->length;
+    msg_->length  = 0; // 以 length 累计当前需要发送的数据
+}
\ No newline at end of file
diff --git a/src/worker_logger_buffer.h b/src/worker_logger_buffer.h
new file mode 100644
index 0000000..0ab939c
--- /dev/null
+++ b/src/worker_logger_buffer.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <memory>
+#include <streambuf>
+#include "ipc.h"
+
+class worker_logger_manager;
+class worker_logger_buffer: public std::streambuf {
+public:
+    worker_logger_buffer(worker_logger_manager* mgr);
+protected:
+    int overflow(int ch = EOF) override;
+    long xsputn(const char* s, long c) override;
+private:
+    worker_logger_manager*          mgr_;
+    std::shared_ptr<ipc::message_t> msg_;
+    std::uint16_t                   cap_;
+
+    void transfer_msg();
+};
diff --git a/src/worker_logger_manager.cpp b/src/worker_logger_manager.cpp
new file mode 100644
index 0000000..041a109
--- /dev/null
+++ b/src/worker_logger_manager.cpp
@@ -0,0 +1,47 @@
+#include "worker_logger_manager.h"
+#include "worker_logger.h"
+#include "worker_ipc.h"
+#include "worker_logger_buffer.h"
+
+worker_logger_manager::~worker_logger_manager() {
+    std::cout << "~worker_logger_manager\n";
+}
+
+std::shared_ptr<worker_logger> worker_logger_manager::lm_connect(const std::string& filepath, coroutine_handler& ch) {
+    std::shared_ptr<worker_logger> wl;
+    auto i = logger_.find(0);
+    if(i == logger_.end() || i->second.expired()) {
+        // IPC: 在父进程创建或链接到指定的日志文件
+        auto msg = ipc::create_message();
+        msg->command = ipc::COMMAND_LOGGER_CONNECT;
+        msg->unique_id = ipc::create_uid();
+        std::memcpy(msg->payload, filepath.data(), filepath.size());
+        // TODO
+        wl = std::make_shared<worker_logger>(this, 0);
+        /*
+        msg = ipc_->ipc_request(msg, ch);
+        // 使用该日志文件标号创建 LOGGER 对象，以支持实际日志发送
+        wl = std::make_shared<worker_logger>(this, msg->target);
+        */
+        logger_.emplace(0, wl);
+    }
+    else wl = i->second.lock();
+    return wl;
+}
+
+void worker_logger_manager::lm_destroy(std::uint8_t idx) {
+    auto i = logger_.find(idx);
+    if(i == logger_.end()) return;
+    logger_.erase(i);
+
+    // IPC: 通知父进程日志器不再使用
+    auto msg = ipc::create_message();
+    msg->command = ipc::COMMAND_LOGGER_DESTROY;
+    msg->target  = idx;
+    // msg->length  = 0;
+    ipc_->ipc_request(msg);
+}
+
+void worker_logger_manager::lm_close() {
+    
+}
\ No newline at end of file
diff --git a/src/worker_logger_manager.h b/src/worker_logger_manager.h
new file mode 100644
index 0000000..5cb833b
--- /dev/null
+++ b/src/worker_logger_manager.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "ipc.h"
+
+class worker_ipc;
+class worker_logger;
+class worker_logger_manager {
+public:
+    worker_logger_manager(worker_ipc* c): ipc_(c) {}
+    virtual ~worker_logger_manager();
+    // 连接到指定的日志文件
+    std::shared_ptr<worker_logger> lm_connect(const std::string& filepath, coroutine_handler& ch);
+    void                           lm_destroy(std::uint8_t idx);
+    void lm_close();
+protected:
+    virtual std::shared_ptr<worker_logger_manager> lm_self() = 0;
+private:
+    std::map<std::uint8_t, std::weak_ptr<worker_logger>> logger_;
+    worker_ipc* ipc_; // 目前的继承实现方式，此字段与 this 相等
+    friend class worker_logger;
+    friend class worker_logger_buffer;
+};
diff --git a/test/logger_1.php b/test/logger_1.php
index d91fdeb..6a1f2ce 100644
--- a/test/logger_1.php
+++ b/test/logger_1.php
@@ -3,6 +3,8 @@
 
 flame\go(function() {
     flame\log\warning("This is a warning message");
+    flame\time\sleep(1000);
+    flame\log\warn("This is another warning message");
 });
 
 flame\run();
\ No newline at end of file

From 242fc494d5ba1a63387019bb02878babbd322417 Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Thu, 4 Jul 2019 14:14:58 +0800
Subject: [PATCH 144/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AE=A2=E6=88=B7?=
 =?UTF-8?q?=E7=AB=AF=20IPC=20=E8=BF=9E=E6=8E=A5=E8=BF=87=E7=A8=8B;=20?=
 =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=85=81=E8=AE=B8=E5=8D=95=E8=BF=9B=E7=A8=8B?=
 =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=96=87=E4=BB=B6;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/flame/controller.cpp      |  9 +++++---
 src/flame/controller.h        |  1 +
 src/flame/log/logger.cpp      |  2 +-
 src/flame/worker.cpp          |  6 ++---
 src/flame/worker.h            |  2 +-
 src/master_ipc.cpp            |  3 +--
 src/worker_ipc.cpp            | 42 ++++++++++++++++++++++++++---------
 src/worker_ipc.h              | 11 ++++++---
 src/worker_logger.cpp         | 24 ++++++++++++++++----
 src/worker_logger.h           | 15 +++++++------
 src/worker_logger_manager.cpp | 42 ++++++++++++++++++++++-------------
 src/worker_logger_manager.h   |  1 +
 test/logger_1.php             |  4 +++-
 13 files changed, 111 insertions(+), 51 deletions(-)

diff --git a/src/flame/controller.cpp b/src/flame/controller.cpp
index 3476793..c6d9162 100644
--- a/src/flame/controller.cpp
+++ b/src/flame/controller.cpp
@@ -14,10 +14,13 @@ namespace flame {
     , user_cb(new std::multimap<std::string, php::callable>()) {
 
         worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
-        worker_size = std::min(std::max((int)worker_size, 1), 256);
+        worker_size = std::min(std::max((int)worker_size, 0), 256);
         // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
-        if (env.count("FLAME_CUR_WORKER") > 0) type = process_type::WORKER;
-        else if (env.count("FLAME_MAX_WORKERS") > 0) type = process_type::MASTER;
+        if (env.count("FLAME_CUR_WORKER") > 0) {
+            type = process_type::WORKER;
+            worker_idx = std::atoi(env["FLAME_CUR_WORKER"].to_string().c_str());
+        }
+        else if (worker_size > 0) type = process_type::MASTER;
         else { // 单进程模式
             worker_size = 0;
             type = process_type::WORKER;
diff --git a/src/flame/controller.h b/src/flame/controller.h
index a3b289a..c0bcf2a 100644
--- a/src/flame/controller.h
+++ b/src/flame/controller.h
@@ -22,6 +22,7 @@ namespace flame {
             STATUS_CLOSECONN   = 0x10,
         };
         int status;
+        std::uint8_t   worker_idx;
         std::size_t    worker_size;
         std::size_t    worker_quit; // 多进程退出超时时间
         std::thread::id mthread_id;
diff --git a/src/flame/log/logger.cpp b/src/flame/log/logger.cpp
index 5046f7d..89decd8 100644
--- a/src/flame/log/logger.cpp
+++ b/src/flame/log/logger.cpp
@@ -23,7 +23,7 @@ namespace flame::log {
         gcontroller->on_init([] (const php::array& options) {
             if (options.exists("level")) logger::LEVEL_OPT = options.get("level").to_integer();
             else logger::LEVEL_OPT = logger::LEVEL_TRACE;
-            std::string output = "stdout";
+            std::string output = "<clog>";
             if (options.exists("logger")) output = options.get("logger").to_string();
             logger_ = new logger();
             // 启动的一个更轻量的 C++ 内部协程
diff --git a/src/flame/worker.cpp b/src/flame/worker.cpp
index 0fb9465..2e20c7d 100644
--- a/src/flame/worker.cpp
+++ b/src/flame/worker.cpp
@@ -103,7 +103,7 @@ namespace flame {
         else
             php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
 
-        worker::ww_.reset(new worker());
+        worker::ww_.reset(new worker(gcontroller->worker_idx));
 
         gcontroller->init(options); // 首个 logger 的初始化过程在 logger 注册 on_init 回调中进行（异步的）
         // 信号监听启动
@@ -202,9 +202,9 @@ namespace flame {
         return nullptr;
     }
 
-    worker::worker()
+    worker::worker(std::uint8_t idx)
     : signal_watcher(gcontroller->context_y)
-    , worker_ipc(gcontroller->context_y)
+    , worker_ipc(gcontroller->context_y, idx)
     , worker_logger_manager(this) {
 
     }
diff --git a/src/flame/worker.h b/src/flame/worker.h
index 75ada33..36cb988 100644
--- a/src/flame/worker.h
+++ b/src/flame/worker.h
@@ -24,7 +24,7 @@ namespace flame {
         static php::value get(php::parameters& params);
         static php::value set(php::parameters& params);
         
-        worker();
+        worker(std::uint8_t idx);
         std::ostream& output() override;
     protected:
         virtual std::shared_ptr<signal_watcher> sw_self() override {
diff --git a/src/master_ipc.cpp b/src/master_ipc.cpp
index 44ecc62..34969c4 100644
--- a/src/master_ipc.cpp
+++ b/src/master_ipc.cpp
@@ -6,7 +6,7 @@
 master_ipc::master_ipc(boost::asio::io_context& io)
 : io_(io)
 , server_(io) {
-    
+    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getpid()).str();
 }
 
 master_ipc::~master_ipc() {
@@ -15,7 +15,6 @@ master_ipc::~master_ipc() {
 }
 
 void master_ipc::ipc_run(coroutine_handler ch) {
-    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getpid()).str();
     std::error_code ec;
     std::filesystem::remove(svrsck_, ec);
     boost::asio::local::stream_protocol::endpoint addr(svrsck_);
diff --git a/src/worker_ipc.cpp b/src/worker_ipc.cpp
index 72d00b6..d3152ef 100644
--- a/src/worker_ipc.cpp
+++ b/src/worker_ipc.cpp
@@ -1,9 +1,10 @@
 #include "worker_ipc.h"
 #include "util.h"
 
-worker_ipc::worker_ipc(boost::asio::io_context& io)
-: socket_(new boost::asio::local::stream_protocol::socket(io)) {
-
+worker_ipc::worker_ipc(boost::asio::io_context& io, std::uint8_t idx)
+: socket_(new boost::asio::local::stream_protocol::socket(io))
+, idx_(idx) {
+    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getpid()).str();
 }
 
 worker_ipc::~worker_ipc() {
@@ -44,13 +45,21 @@ void worker_ipc::ipc_notify(std::uint8_t target, php::value data) {
 }
 
 void worker_ipc::ipc_run(coroutine_handler ch) {
-    // TODO
+    boost::system::error_code error;
+    std::shared_ptr<ipc::message_t> msg;
+    std::uint64_t tmp;
+
+    ++status_;
     // 连接主进程 IPC 通道
+    boost::asio::local::stream_protocol::endpoint addr(svrsck_);
+    socket_->async_connect(addr, ch[error]);
+    if (error) goto IPC_FAILED;
+    ++status_;
+    send_login();
     // 发送注册消息
-    // 
-    boost::system::error_code error;
+    ++status_;
     while(true) {
-        auto msg = ipc::create_message();
+        msg = ipc::create_message();
         boost::asio::async_read(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t)), ch[error]);
         if(error) break;
         if(msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
@@ -72,7 +81,7 @@ void worker_ipc::ipc_run(coroutine_handler ch) {
         break;
         }
     }
-
+IPC_FAILED:
     if (error && error != boost::asio::error::operation_aborted && error != boost::asio::error::eof) 
         output() << "[" << util::system_time() << "] (ERROR) Failed to read ipc connection: (" << error.value() << ") " << error.message() << "\n";
     socket_->close(error);
@@ -82,11 +91,22 @@ void worker_ipc::ipc_close() {
     socket_->close();
 }
 
+bool worker_ipc::ipc_enabled() {
+    return status_ > -1;
+}
+
+void worker_ipc::send_login() {
+    auto msg = ipc::create_message();
+    msg->command = ipc::COMMAND_REGISTER;
+    msg->target  = idx_;
+    msg->length  = 0;
+    send(msg);
+}
+
 void worker_ipc::send(std::shared_ptr<ipc::message_t> msg) {
-    
-    if(sendq_.empty()) {
+    if (sendq_.empty()) {
         sendq_.push_back(msg);
-        send_next();
+        if (status_ > 0) send_next();
     }else
         sendq_.push_back(msg);
 }
diff --git a/src/worker_ipc.h b/src/worker_ipc.h
index 07e0889..e09a867 100644
--- a/src/worker_ipc.h
+++ b/src/worker_ipc.h
@@ -4,7 +4,7 @@
 
 class worker_ipc {
 public:
-    worker_ipc(boost::asio::io_context& io);
+    worker_ipc(boost::asio::io_context& io, std::uint8_t idx);
     virtual ~worker_ipc();
     using socket_ptr = std::shared_ptr<boost::asio::local::stream_protocol::socket>;
     
@@ -19,15 +19,20 @@ class worker_ipc {
     void ipc_run(coroutine_handler ch);
     // 关闭通道
     void ipc_close();
-    // 创建消息唯一ID
-    
+    // IPC 检测
+    bool ipc_enabled();
 protected:
     virtual std::shared_ptr<worker_ipc> ipc_self() = 0;
     virtual void on_notify(std::shared_ptr<ipc::message_t> msg) = 0;
 private:
+    std::filesystem::path                      svrsck_;
+    std::uint8_t                                  idx_;
     socket_ptr                                 socket_;
     std::map<std::uint32_t, ipc::callback_t> callback_;
     std::list<std::shared_ptr<ipc::message_t>>  sendq_;
+    int                                        status_ = -1;
+
+    void send_login();
     void send(std::shared_ptr<ipc::message_t> msg);
     void send_next();
 };
diff --git a/src/worker_logger.cpp b/src/worker_logger.cpp
index 3e621c4..60e67bc 100644
--- a/src/worker_logger.cpp
+++ b/src/worker_logger.cpp
@@ -2,9 +2,25 @@
 #include "worker_logger_manager.h"
 #include "worker_logger_buffer.h"
 
-worker_logger::worker_logger(worker_logger_manager* mgr, unsigned int idx)
-: idx_(idx)
+worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx) 
+: idx_(0)
+, path_(path)
 , wlb_(new worker_logger_buffer(mgr))
-, oss_(wlb_.get()) {
-    
+, oss_(new std::ostream(wlb_.get())) {
+
+}
+
+worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx, bool local)
+: idx_(0)
+, path_(path) {
+    if(path.string() != "<clog>") {
+        auto fb = new std::filebuf();
+        fb->open(path, std::ios_base::app);
+        oss_.reset(new std::ostream(fb));
+        wlb_.reset(fb);
+    }
+}
+
+std::ostream& worker_logger::stream() {
+    return oss_ ? (*oss_) : std::clog;
 }
diff --git a/src/worker_logger.h b/src/worker_logger.h
index 9d5233c..c9a0811 100644
--- a/src/worker_logger.h
+++ b/src/worker_logger.h
@@ -1,19 +1,20 @@
 #pragma once
 #include "vendor.h"
+#include <filesystem>
 
 class coroutine_handler;
 class worker_logger_manager;
 class worker_logger_buffer;
 class worker_logger {
 public:
-    worker_logger(worker_logger_manager* mgr, unsigned int index);
-    std::ostream& stream() {
-        return oss_;
-    }
+    worker_logger(worker_logger_manager* mgr, const std::filesystem::path& file, std::uint8_t index);
+    worker_logger(worker_logger_manager* mgr, const std::filesystem::path& file, std::uint8_t index, bool local);
+    std::ostream& stream();
 private:
-    std::uint8_t idx_;
-    std::unique_ptr<worker_logger_buffer> wlb_;
-    std::ostream oss_;
+    std::uint8_t           idx_;
+    std::filesystem::path path_;
+    std::unique_ptr<std::streambuf> wlb_;
+    std::unique_ptr<std::ostream>   oss_;
     friend class worker_logger_manager;
 };
 
diff --git a/src/worker_logger_manager.cpp b/src/worker_logger_manager.cpp
index 041a109..d0ebc65 100644
--- a/src/worker_logger_manager.cpp
+++ b/src/worker_logger_manager.cpp
@@ -1,31 +1,43 @@
 #include "worker_logger_manager.h"
 #include "worker_logger.h"
 #include "worker_ipc.h"
-#include "worker_logger_buffer.h"
 
 worker_logger_manager::~worker_logger_manager() {
     std::cout << "~worker_logger_manager\n";
 }
 
-std::shared_ptr<worker_logger> worker_logger_manager::lm_connect(const std::string& filepath, coroutine_handler& ch) {
+std::shared_ptr<worker_logger> worker_logger_manager::lm_connect(const std::string& file, coroutine_handler& ch) {
+    std::filesystem::path path = file;
+    path = path.lexically_normal();
+
+    for (auto i=logger_.begin();i!=logger_.end();) {
+        if(i->second.expired()) i = logger_.erase(i);
+        else {
+            std::shared_ptr<worker_logger> lg = i->second.lock();
+            if(lg->path_ == path) return lg;
+            ++i;
+        }
+    }
+    
     std::shared_ptr<worker_logger> wl;
-    auto i = logger_.find(0);
-    if(i == logger_.end() || i->second.expired()) {
-        // IPC: 在父进程创建或链接到指定的日志文件
+    std::uint8_t index = 0;
+    if (ipc_->ipc_enabled()) {
         auto msg = ipc::create_message();
         msg->command = ipc::COMMAND_LOGGER_CONNECT;
         msg->unique_id = ipc::create_uid();
-        std::memcpy(msg->payload, filepath.data(), filepath.size());
-        // TODO
-        wl = std::make_shared<worker_logger>(this, 0);
-        /*
+        std::memcpy(msg->payload, file.data(), file.size());
         msg = ipc_->ipc_request(msg, ch);
+        index = msg->target;
         // 使用该日志文件标号创建 LOGGER 对象，以支持实际日志发送
-        wl = std::make_shared<worker_logger>(this, msg->target);
-        */
-        logger_.emplace(0, wl);
+        wl = std::make_shared<worker_logger>(this, file, msg->target);
+    }
+    else {
+        index = lindex_;
+        wl = std::make_shared<worker_logger>(this, file, index, true);
+        ++lindex_;
     }
-    else wl = i->second.lock();
+    
+    logger_.emplace(index, wl);
     return wl;
 }
 
@@ -38,10 +50,10 @@ void worker_logger_manager::lm_destroy(std::uint8_t idx) {
     auto msg = ipc::create_message();
     msg->command = ipc::COMMAND_LOGGER_DESTROY;
     msg->target  = idx;
-    // msg->length  = 0;
+    msg->length  = 0;
     ipc_->ipc_request(msg);
 }
 
 void worker_logger_manager::lm_close() {
-    
+
 }
\ No newline at end of file
diff --git a/src/worker_logger_manager.h b/src/worker_logger_manager.h
index 5cb833b..8f1d564 100644
--- a/src/worker_logger_manager.h
+++ b/src/worker_logger_manager.h
@@ -15,6 +15,7 @@ class worker_logger_manager {
     virtual std::shared_ptr<worker_logger_manager> lm_self() = 0;
 private:
     std::map<std::uint8_t, std::weak_ptr<worker_logger>> logger_;
+    std::uint8_t                                         lindex_ = 0;
     worker_ipc* ipc_; // 目前的继承实现方式，此字段与 this 相等
     friend class worker_logger;
     friend class worker_logger_buffer;
diff --git a/test/logger_1.php b/test/logger_1.php
index 6a1f2ce..e888c76 100644
--- a/test/logger_1.php
+++ b/test/logger_1.php
@@ -1,5 +1,7 @@
 <?php
-flame\init("logger_1");
+flame\init("logger_1", [
+    "logger" => __DIR__."/logger_1.log"
+]);
 
 flame\go(function() {
     flame\log\warning("This is a warning message");

From a4d5bd17b34068bb5954d237b9eb05eef0af480b Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 5 Jul 2019 15:14:36 +0800
Subject: [PATCH 145/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=20IPC=20=E6=B5=81?=
 =?UTF-8?q?=E7=A8=8B;=20=E5=AE=8C=E5=96=84=E6=97=A5=E5=BF=97=20IPC=20?=
 =?UTF-8?q?=E6=B5=81=E7=A8=8B;=20=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97?=
 =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8C=E6=AD=A5=E6=9C=BA=E5=88=B6;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php                    |  20 +++---
 doc/log.php                     |  59 +++++++++++++++-
 src/coroutine.h                 | 120 ++++++++++++++++++--------------
 src/flame/controller.h          |  11 +--
 src/flame/coroutine.h           |   3 +-
 src/flame/flame.cpp             |   3 +-
 src/flame/http/_handler.cpp     |   2 +-
 src/flame/kafka/_consumer.cpp   |   2 +-
 src/flame/kafka/_producer.cpp   |   2 +-
 src/flame/kafka/consumer.cpp    |   2 +-
 src/flame/log/log.cpp           |   7 ++
 src/flame/log/logger.cpp        |  25 +++++--
 src/flame/log/logger.h          |   5 +-
 src/flame/master.cpp            | 103 ++++++++++++++++++---------
 src/flame/master.h              |   4 +-
 src/flame/mongodb/client.cpp    |   2 +-
 src/flame/rabbitmq/consumer.cpp |   2 +-
 src/flame/tcp/server.cpp        |   2 +-
 src/flame/udp/server.cpp        |   2 +-
 src/flame/worker.cpp            |  57 ++++++++++-----
 src/flame/worker.h              |   5 +-
 src/ipc.h                       |   2 +
 src/master_ipc.cpp              |  52 +++++++-------
 src/master_ipc.h                |   5 +-
 src/master_logger.cpp           |  26 ++++---
 src/master_logger.h             |  17 +++--
 src/master_logger_buffer.cpp    |  45 ++++++++++++
 src/master_logger_buffer.h      |  20 ++++++
 src/master_logger_manager.cpp   |  21 +++---
 src/master_logger_manager.h     |  25 ++++---
 src/master_process.cpp          |   8 ++-
 src/master_process_manager.cpp  |  88 ++++++++++++++---------
 src/master_process_manager.h    |  18 +++--
 src/signal_watcher.h            |   3 +-
 src/url.cpp                     |   2 +-
 src/worker_ipc.cpp              |  83 +++++++++++-----------
 src/worker_ipc.h                |   5 +-
 src/worker_logger.cpp           |   5 +-
 src/worker_logger_buffer.cpp    |  10 ++-
 src/worker_logger_buffer.h      |   3 +-
 src/worker_logger_manager.cpp   |  12 ++--
 test/logger_1.php               |   2 +-
 test/logger_2.php               |  22 ++++++
 test/logger_3.php               |  14 ++++
 test/worker_1.php               |  15 ----
 45 files changed, 629 insertions(+), 312 deletions(-)
 create mode 100644 src/master_logger_buffer.cpp
 create mode 100644 src/master_logger_buffer.h
 create mode 100644 test/logger_2.php
 create mode 100644 test/logger_3.php
 delete mode 100644 test/worker_1.php

diff --git a/doc/core.php b/doc/core.php
index 8d8046a..387734d 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -4,15 +4,18 @@
  * 框架可在两种模式下工作：
  * 
  * 1. 单进程模式：
- *  * 一次信号 SIGINT / SIGTERM 将 “通知” 进程退出（等待超时），第二次或超时后立即停止；
+ *  * 信号 SIGQUIT 将 “通知” 进程退出 (触发 `quit` 回调)，等待超时后立即终止；
+ *  * 信号 SIGINT / SIGTERM 立即终止；
  *  * 信号 SIGUSR1 将切换 HTTP 服务器长短连状态 `Connection: close`；
  *  * 单进程模式**不支持**日志文件输出，固定输出到 STDERR / STDOUT (取决于错误等级）；
  * 
- * 2. 多进程模式：
- *  * 使用环境变量 FLAME_MAX_WORKERS=X 启动多进程模式；
+ * 2. 父子进程模式：
+ *  * 使用环境变量 FLAME_MAX_WORKERS=X 启动父子进程模式；
  *  * 主进程将自动进行日志文件写入，子进程自动拉起；
  *  * 端口监听使用 SO_REUSEPORT 在多个工作进程间共享，但不排除外部监听的可能；
- *  * 向主进程发送 SIGINT / SIGTERM 一次将 ”通知“ 子进程退出（等待超时），第二次或超时后将立即停止；
+ * 
+ *  * 向主进程发送 SIGQUIT 将 ”通知“ 子进程退出 (触发 `quit` 回调)，并等待超时后立即终止；
+ *  * 项主进程发送 SIGINT / SIGTERM 将立即终止；
  *  * 向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
  *  * 子进程继承主进程的 环境变量 / -c /path/to/php.ini 配置（通过命令行 -d XXX=XXX 的临时设置无效）；
  *  * 向主进程发送 SIGUSR1 信号将切换 HTTP 服务器长短连状态 `Connection: close`；
@@ -30,7 +33,7 @@
  *      "warning"
  *      "error"
  *      "fatal"
- *  * "timeout" - 多进程退出超时, 单位毫秒 ( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
+ *  * "timeout" - 进程退出超时, 单位毫秒 ( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
  *  @see flame\log
  */
 function init($process_name, $options = []) {}
@@ -94,10 +97,11 @@ function on(string $event, callable $cb) {}
 /**
  * 用于在用户处理流程中退出
  * 注意：
- * 使用 PHP 内置 exit() 进行退出可能导致框架提供的清理、退出机制无效；
- * 请示用本函数代替；
+ *  1. 使用 PHP 内置 exit() 进行退出可能导致框架提供的清理、退出机制无效；(请示用本函数代替)
+ *  2. 调用本函数主动退出进程，不会触发上述 `on("quit", $cb)` 注册的回调;
+ *  3. 父子进程模式，非停止流程，当 $exit_code 不为 0 时表示异常退出，父进程会（1s ~ 3s 后）将当前工作进程重启；
  */
-function quit() {}
+function quit(int $exit_code = 0) {}
 /**
  * 协程型队列
  */
diff --git a/doc/log.php b/doc/log.php
index 6a8e41b..92a43c5 100644
--- a/doc/log.php
+++ b/doc/log.php
@@ -3,9 +3,19 @@
 /**
  * 日志模块, 可通过框架配置设置日志等级以过滤日志输出;
  * @see flame\init()
- * 注意：单进程模式不支持设置输出目标文件，固定输出到 STDOUT / STDERR (由错误等级决定)
  */
 namespace flame\log;
+
+/**
+ * 创建（连接到 IPC 通道）一个新的日志记录器（记录到指定文件）
+ */
+function connect(string $filepath): logger {
+    return new logger();
+}
+/**
+ * 记录 无前缀（无时间戳 、无警告级别）的日志数据
+ */
+function write($x/*, $y, $z  ... */) {}
 /**
  * 记录 TRACE 级别日志, 自动进行 JSON 形式的序列化;
  */
@@ -38,4 +48,49 @@ function fail($x/*, $y, $z  ... */) {}
 /**
  * 记录 FATAL 级别日志, 自动进行 JSON 形式的序列化;
  */
-function fatal($x/*, $y, $z  ... */) {}
\ No newline at end of file
+function fatal($x/*, $y, $z  ... */) {}
+
+class logger {
+    /**
+     * 禁止手动创建日志对象，请使用 flame\log\connect() 函数
+     * @see flame\log\connect()
+     */
+    private function __construct() {}
+    /**
+     * 记录 无前缀（无时间戳 、无警告级别）的日志数据
+     */
+    function write($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 TRACE 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function trace($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 DEBUG 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function debug($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 INFO 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function info($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function warn($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function warning($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 ERROR 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function error($x/*, $y, $z  ... */) {}
+    /**
+     * 同 fatal()
+     * @see fatal()
+     */
+    function fail($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 FATAL 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function fatal($x/*, $y, $z  ... */) {}
+};
diff --git a/src/coroutine.h b/src/coroutine.h
index eae7b9c..36f1360 100644
--- a/src/coroutine.h
+++ b/src/coroutine.h
@@ -1,45 +1,7 @@
 #pragma once
 #include "vendor.h"
 
-class coroutine: public std::enable_shared_from_this<coroutine> {
-public:
-    coroutine(boost::asio::executor ex)
-    : ex_(ex) {
-        
-    }
-    virtual ~coroutine() {
-
-    }
-    // static std::shared_ptr<coroutine> current;
-    template <class Handler>
-    static void start(boost::asio::executor ex, Handler&& fn) {
-        auto co = std::make_shared<coroutine>(ex);
-        
-        boost::asio::post(co->ex_, [co, fn] () mutable {
-            co->c1_ = boost::context::fiber([co, gd = boost::asio::make_work_guard(co->ex_), fn] (boost::context::fiber&& c2) mutable {
-                co->c2_ = std::move(c2);
-                fn({co});
-                return std::move(co->c2_);
-            });
-            co->c1_ = std::move(co->c1_).resume();
-        });
-    }
-    virtual void suspend() {
-        c2_ = std::move(c2_).resume();
-    }
-    virtual void resume() {
-        boost::asio::post(ex_, [co = shared_from_this()] () {
-            co->c1_ = std::move(co->c1_).resume();
-        });
-    }
-protected:
-    boost::context::fiber c1_;
-    boost::context::fiber c2_;
-    boost::asio::executor ex_;
-
-    friend class coroutine_handler;
-};
-
+class coroutine;
 class coroutine_handler {
 public:
     coroutine_handler() = default;
@@ -58,18 +20,14 @@ class coroutine_handler {
         return *this;
     }
     
-    inline void operator()(const boost::system::error_code& error, std::size_t size = 0) {
-        if(error_) *error_ = error;
-        if(size_) *size_ = size;
-        co_->resume();
-    }
+    void operator()(const boost::system::error_code& error, std::size_t size = 0);
 
     inline bool operator !() {
-        return co_ == nullptr;
+        return !co_;
     }
     
     inline operator bool() {
-        return co_ != nullptr;
+        return !!co_;
     }
 
     void reset() {
@@ -77,6 +35,7 @@ class coroutine_handler {
         size_ = nullptr;
         error_= nullptr;
     }
+    
     void reset(std::shared_ptr<coroutine> co) {
         co_ = co;
         size_ = nullptr;
@@ -88,17 +47,13 @@ class coroutine_handler {
         error_= nullptr;
     }
 
-    inline void suspend() {
-        co_->suspend();
-    }
-    inline void resume() {
-        co_->resume();
-    }
+    void suspend();
+    void resume();
 
-    void error(const boost::system::error_code& error) {
+    inline void error(const boost::system::error_code& error) {
         if(error_) *error_ = error;
     }
-    void size(std::size_t sz) {
+    inline void size(std::size_t sz) {
         if(size_) *size_ = sz;
     }
 public:
@@ -110,6 +65,63 @@ class coroutine_handler {
 
 };
 
+class coroutine: public std::enable_shared_from_this<coroutine> {
+public:
+    coroutine(boost::asio::executor ex)
+    : ex_(ex) {
+        
+    }
+    virtual ~coroutine() {
+
+    }
+    
+    template <class Handler>
+    static void start(boost::asio::executor ex, Handler&& fn);
+
+    virtual void suspend() {
+        c2_ = std::move(c2_).resume();
+    }
+    virtual void resume() {
+        boost::asio::post(ex_, [co = shared_from_this()] () {
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+protected:
+    boost::context::fiber c1_;
+    boost::context::fiber c2_;
+    boost::asio::executor ex_;
+
+    friend class coroutine_handler;
+};
+
+template <class Handler>
+void coroutine::start(boost::asio::executor ex, Handler&& fn) {
+    auto co = std::make_shared<coroutine>(ex);
+    
+    boost::asio::post(co->ex_, [co, fn] () mutable {
+        co->c1_ = boost::context::fiber([co, gd = boost::asio::make_work_guard(co->ex_), fn] (boost::context::fiber&& c2) mutable {
+            co->c2_ = std::move(c2);
+            fn(coroutine_handler{co});
+            return std::move(co->c2_);
+        });
+        co->c1_ = std::move(co->c1_).resume();
+    });
+}
+
+inline void coroutine_handler::operator()(const boost::system::error_code& error, std::size_t size) {
+    if(error_) *error_ = error;
+    if(size_) *size_ = size;
+    co_->resume();
+}
+
+inline void coroutine_handler::suspend() {
+    co_->suspend();
+}
+
+inline void coroutine_handler::resume() {
+    co_->resume();
+}
+
 namespace boost::asio {
     template <>
     class async_result<::coroutine_handler, void (boost::system::error_code error, std::size_t size)> {
diff --git a/src/flame/controller.h b/src/flame/controller.h
index c0bcf2a..0bdbbb6 100644
--- a/src/flame/controller.h
+++ b/src/flame/controller.h
@@ -7,6 +7,7 @@ namespace flame {
     public:
         boost::asio::io_context context_x;
         boost::asio::io_context context_y;
+        boost::asio::io_context context_z;
         enum class process_type {
             UNKNOWN = 0,
             MASTER = 1,
@@ -16,10 +17,12 @@ namespace flame {
         enum status_t {
             STATUS_UNKNOWN     = 0x00,
             STATUS_INITIALIZED = 0x01,
-            STATUS_SHUTDOWN    = 0x02,
-            STATUS_EXCEPTION   = 0x04,
-            STATUS_RUN         = 0x08,
-            STATUS_CLOSECONN   = 0x10,
+            STATUS_CLOSING     = 0x02,
+            STATUS_QUITING     = 0x04,
+            STATUS_RSETING     = 0x08,
+            STATUS_EXCEPTION   = 0x10,
+            STATUS_RUN         = 0x20,
+            STATUS_CLOSECONN   = 0x40,
         };
         int status;
         std::uint8_t   worker_idx;
diff --git a/src/flame/coroutine.h b/src/flame/coroutine.h
index b7ac574..5066eae 100644
--- a/src/flame/coroutine.h
+++ b/src/flame/coroutine.h
@@ -41,6 +41,7 @@ namespace flame {
         : ::coroutine_handler() {
 
         }
+        coroutine_handler(const coroutine_handler& ch) = default;
         coroutine_handler(std::shared_ptr<coroutine> co)
         : ::coroutine_handler(co) {
             
@@ -67,8 +68,6 @@ namespace flame {
             size_ = &size;
             return *this;
         }
-    private:
-        coroutine::php_context_t  ctx_;
     };
 
 }
diff --git a/src/flame/flame.cpp b/src/flame/flame.cpp
index 80ac301..0ef5ed8 100644
--- a/src/flame/flame.cpp
+++ b/src/flame/flame.cpp
@@ -1,4 +1,5 @@
 #include "../vendor.h"
+#include "../util.h"
 #include "controller.h"
 #include "version.h"
 #include "worker.h"
@@ -9,7 +10,7 @@ extern "C" {
         static php::extension_entry ext(EXTENSION_NAME, EXTENSION_VERSION);
         std::string sapi = php::constant("PHP_SAPI");
         if (sapi != "cli") {
-            std::cerr << "[WARNING] FLAME disabled: SAPI='cli' mode only\n";
+            std::cerr << "[" << util::system_time() << "] (WARNING) FLAME disabled: SAPI='cli' mode only\n";
             return ext;
         }
         // 内置一个 C++ 函数包裹类
diff --git a/src/flame/http/_handler.cpp b/src/flame/http/_handler.cpp
index db00910..b930cf7 100644
--- a/src/flame/http/_handler.cpp
+++ b/src/flame/http/_handler.cpp
@@ -93,7 +93,7 @@ namespace flame::http {
             gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
             // 记录错误信息
             php::object obj = ex;
-            log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << "\n";
+            log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << std::endl;
             return false;
         }
     }
diff --git a/src/flame/kafka/_consumer.cpp b/src/flame/kafka/_consumer.cpp
index 472db21..2fb641d 100644
--- a/src/flame/kafka/_consumer.cpp
+++ b/src/flame/kafka/_consumer.cpp
@@ -63,7 +63,7 @@ namespace flame::kafka {
     void _consumer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
         _consumer* self = reinterpret_cast<_consumer*>(data);
         if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
-            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << std::endl;
     }
 
     void _consumer::subscribe(coroutine_handler &ch) {
diff --git a/src/flame/kafka/_producer.cpp b/src/flame/kafka/_producer.cpp
index 24bd757..ad321dc 100644
--- a/src/flame/kafka/_producer.cpp
+++ b/src/flame/kafka/_producer.cpp
@@ -46,7 +46,7 @@ namespace flame::kafka {
     void _producer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
         _producer* self = reinterpret_cast<_producer*>(data);
         if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
-            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << "\n";
+            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << std::endl;
     }
 
     void _producer::start() {
diff --git a/src/flame/kafka/consumer.cpp b/src/flame/kafka/consumer.cpp
index 5223943..95eeac7 100644
--- a/src/flame/kafka/consumer.cpp
+++ b/src/flame/kafka/consumer.cpp
@@ -48,7 +48,7 @@ namespace flame::kafka {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << std::endl;
                     }
                 }
                 if (--count == 0) ch_run.resume();
diff --git a/src/flame/log/log.cpp b/src/flame/log/log.cpp
index 3cf3e25..badc556 100644
--- a/src/flame/log/log.cpp
+++ b/src/flame/log/log.cpp
@@ -5,6 +5,11 @@
 
 namespace flame::log {
     
+    static php::value write(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_EMPTY, params);
+        return nullptr;
+    }
+
     static php::value trace(php::parameters &params) {
         logger_->write_ex(logger::LEVEL_TRACE, params);
         return nullptr;
@@ -45,6 +50,8 @@ namespace flame::log {
 
     void declare(php::extension_entry &ext) {
         ext
+            .function<connect>("flame\\log\\connect")
+            .function<write>("flame\\log\\write")
             .function<trace>("flame\\log\\trace")
             .function<debug>("flame\\log\\debug")
             .function<info>("flame\\log\\info")
diff --git a/src/flame/log/logger.cpp b/src/flame/log/logger.cpp
index 89decd8..68d45d9 100644
--- a/src/flame/log/logger.cpp
+++ b/src/flame/log/logger.cpp
@@ -37,6 +37,8 @@ namespace flame::log {
 
         php::class_entry<logger> class_logger("flame\\log\\logger");
         class_logger
+            .method<&logger::__construct>("__construct", php::PRIVATE) // 禁止手动创建
+            .method<&logger::write>("write")
             .method<&logger::trace>("trace")
             .method<&logger::debug>("debug")
             .method<&logger::info>("info")
@@ -48,6 +50,10 @@ namespace flame::log {
         ext.add(std::move(class_logger));
     }
     
+    php::value logger::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
     void logger::connect(const std::string& path, ::coroutine_handler& ch) {
         lg_ = worker::get()->lm_connect(path, ch);
     }
@@ -59,13 +65,20 @@ namespace flame::log {
     void logger::write_ex(int lv, php::parameters& params) {
         if (lv < LEVEL_OPT) return;
         std::ostream& os = logger::stream();
-        os << '[' << time::iso() << "] (";
-        os << LEVEL_STR[lv];
-        os << ")";
-        for (int i = 0; i < params.size(); ++i)
-            os << ' ' << params[i].ptr();
+        if (lv != LEVEL_EMPTY) {
+            os << '[' << time::iso() << "] (";
+            os << LEVEL_STR[lv];
+            os << ") ";
+        }
+        int i = 0;
+        for (; i < params.size() - 1; ++i)
+            os << params[i].ptr() << ' ';
+        os << params[i].ptr() << std::endl; // 使用 endl 会进行 flush 使日志快速出现
+    }
 
-        os << std::endl;
+    php::value logger::write(php::parameters& params) {
+        write_ex(LEVEL_EMPTY, params);
+        return nullptr;
     }
 
     php::value logger::trace(php::parameters &params) {
diff --git a/src/flame/log/logger.h b/src/flame/log/logger.h
index 2a7518c..8e55212 100644
--- a/src/flame/log/logger.h
+++ b/src/flame/log/logger.h
@@ -14,15 +14,18 @@ namespace flame::log {
             LEVEL_WARNING,
             LEVEL_ERROR,
             LEVEL_FATAL,
+
+            LEVEL_EMPTY,
         };
         static void declare(php::extension_entry& ext);
         static std::array<std::string, 6> LEVEL_STR;
         static int LEVEL_OPT;
-
+        php::value __construct(php::parameters& params);
         // 使用父类型 coroutine_handler 引用能够兼容 C++ / PHP 协程
         void connect(const std::string& path, ::coroutine_handler& ch);
         void write_ex(int lv, php::parameters& params);
         std::ostream& stream();
+        php::value write(php::parameters &params);
         php::value trace(php::parameters &params);
         php::value debug(php::parameters &params);
         php::value info(php::parameters &params);
diff --git a/src/flame/master.cpp b/src/flame/master.cpp
index 56fe2ee..bf6afc4 100644
--- a/src/flame/master.cpp
+++ b/src/flame/master.cpp
@@ -31,14 +31,16 @@ namespace flame {
 
         // 主进程控制对象
         master::mm_.reset(new master());
-        if (options.exists("logger")) 
-            master::mm_->lg_ = master::mm_->lm_connect(options.get("logger"));
+        if (options.exists("logger")) {
+            php::string logger = options.get("logger");
+            master::mm_->lg_ = master::mm_->lm_connect({logger.data(), logger.size()});
+        }
         else
             master::mm_->lg_ = master::mm_->lm_connect("stdout");
         // 初始化启动
         gcontroller->init(options);
-        // 信号监听启动
-        master::mm_->sw_watch();
+        master::mm_->ipc_start(); // IPC 启动
+        master::mm_->sw_watch(); // 信号监听启动
         return nullptr;
     }
 
@@ -47,21 +49,27 @@ namespace flame {
             throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
             
         gcontroller->status |= controller::STATUS_RUN;
-
-        master::get()->pm_start(); // 启动工作进程
-        std::thread ts {[] () {
+        master::mm_->pm_start(gcontroller->context_x); // 启动工作进程
+        
+        std::thread ts[2];
+        ts[0] = std::thread([] () {
             gcontroller->context_y.run();
-        }};
+        });
+        ts[1] = std::thread([] () {
+            gcontroller->context_z.run();
+        });
         gcontroller->context_x.run();
-        
+
         master::mm_->sw_close();
         master::mm_->ipc_close();
         master::mm_->lm_close();
 
-        if (gcontroller->status & controller::STATUS_EXCEPTION) _exit(-1);
-        else gcontroller->stop();
+        // if (gcontroller->status & controller::STATUS_EXCEPTION) _exit(-1);
+        gcontroller->stop();
+        ts[0].join();
+        ts[1].join();
 
-        ts.join();
+        master::mm_.reset();
         return nullptr;
     }
 
@@ -70,9 +78,9 @@ namespace flame {
     }
 
     master::master()
-    : master_process_manager(gcontroller->context_x, gcontroller->worker_size)
+    : master_process_manager(gcontroller->context_y, gcontroller->worker_size, gcontroller->worker_quit)
     , signal_watcher(gcontroller->context_y)
-    , master_logger_manager()
+    , master_logger_manager(gcontroller->context_y)
     , master_ipc(gcontroller->context_y) {
         
     }
@@ -83,39 +91,66 @@ namespace flame {
     // !!! 此函数在工作线程中工作
     bool master::on_signal(int sig) {
         switch(sig) {
-        case SIGINT: 
-            coroutine::start(gcontroller->context_x.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
-                pm_reset(ch);
-            });
         break;
         case SIGUSR2: 
             lm_reload(); // 日志重载
         break;
         case SIGUSR1:
-            if(++stats_ % 2 == 1) 
-                output() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl;
-            else
-                output() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl;
-            pm_kills(SIGUSR1); // 长短连切换
+            boost::asio::post(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] () {
+                gcontroller->status ^= controller::STATUS_CLOSECONN;
+                gcontroller->status & controller::STATUS_CLOSECONN ? 
+                    (output() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl) :
+                    (output() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl) ;
+
+                pm_kills(SIGUSR1); // 长短连切换
+            });
+        break;
+        case SIGINT:
+        case SIGQUIT:
+            coroutine::start(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                if(gcontroller->status & (controller::STATUS_CLOSING | controller::STATUS_QUITING | controller::STATUS_RSETING)) return; // 关闭进行中
+                gcontroller->status |= controller::STATUS_QUITING | controller::STATUS_CLOSECONN;
+                pm_close(ch, true); // 立即关闭
+            });
+            return false; // 除强制停止信号外，需要持续监听信号  
         break;
         case SIGTERM:
-            if(++close_ > 1) {
-                sw_close();
-                coroutine::start(gcontroller->context_x.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
-                    pm_close(0, ch); // 立即关闭
-                });
-                return false; // 除强制停止信号外，需要持续监听信号
-            }else{
-                coroutine::start(gcontroller->context_x.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
-                    pm_close(gcontroller->worker_quit, ch); // 超时关闭
+            coroutine::start(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                if(gcontroller->status & (controller::STATUS_CLOSING | controller::STATUS_QUITING | controller::STATUS_RSETING)) return; // 关闭进行中
+                gcontroller->status |= controller::STATUS_CLOSING | controller::STATUS_CLOSECONN;
+                pm_close(ch); // 超时关闭
+            });
+        break;
+        default:
+            if(sig == SIGRTMIN + 1)
+                coroutine::start(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                    if(gcontroller->status & (controller::STATUS_CLOSING | controller::STATUS_QUITING | controller::STATUS_RSETING)) return; // 关闭进行中
+                    gcontroller->status |=   controller::STATUS_RSETING | controller::STATUS_CLOSECONN;
+                    pm_reset(ch);
+                    gcontroller->status &= ~(controller::STATUS_RSETING | controller::STATUS_CLOSECONN);
                 });
-            }
-            break;
         }
         return true;
     }
     // !!! 此函数在工作线程中工作
     bool master::on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) {
+        switch(msg->command) {
+        // 工作进程创建了新的日志对象，连接到指定的文件输出
+        case ipc::COMMAND_LOGGER_CONNECT:
+            msg->xdata[0]  = lm_connect({&msg->payload[0], msg->length})->index();
+            msg->target = msg->source; // 响应给来源的工作进程
+            msg->length = 0;
+            ipc_request(msg);
+            return true;
+        // 工作进程写入日志数据
+        case ipc::COMMAND_LOGGER_DATA:
+            // 使用 xdata[0] 标识实际写入的目标日志
+            lm_get(msg->xdata[0])->stream() << "~~~~~" << std::string_view {&msg->payload[0], msg->length};
+            return true;
+        case ipc::COMMAND_LOGGER_DESTROY:
+            lm_destroy(msg->target);
+            return true;
+        }
         return master_ipc::on_message(msg, sock);
     }
 }
diff --git a/src/flame/master.h b/src/flame/master.h
index 1baf914..33afa68 100644
--- a/src/flame/master.h
+++ b/src/flame/master.h
@@ -31,14 +31,14 @@ namespace flame {
         virtual std::shared_ptr<master_ipc> ipc_self() override {
             return std::static_pointer_cast<master_ipc>(shared_from_this());
         }
+        // void on_child_close(master_process* w, bool normal) override;
         bool on_signal(int sig) override;
         bool on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) override;
+        
     private:
         static std::shared_ptr<master> mm_;
         master_logger* lg_;
 
-        int close_ = 0;
-        int stats_ = 0;
         friend class controller;
     };
 }
\ No newline at end of file
diff --git a/src/flame/mongodb/client.cpp b/src/flame/mongodb/client.cpp
index 34c4905..63532ae 100644
--- a/src/flame/mongodb/client.cpp
+++ b/src/flame/mongodb/client.cpp
@@ -38,7 +38,7 @@ namespace flame::mongodb {
     }
 
     php::value client::dump(php::parameters& params) {
-        std::cout << bson_as_relaxed_extended_json(array2bson(params[0]).get(), nullptr) << std::endl;
+        std::clog << bson_as_relaxed_extended_json(array2bson(params[0]).get(), nullptr) << std::endl;
         return nullptr;
     }
 
diff --git a/src/flame/rabbitmq/consumer.cpp b/src/flame/rabbitmq/consumer.cpp
index 95d196b..082de06 100644
--- a/src/flame/rabbitmq/consumer.cpp
+++ b/src/flame/rabbitmq/consumer.cpp
@@ -54,7 +54,7 @@ namespace flame::rabbitmq  {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << std::endl;
                     }
                 }
                 if (--count == 0)  ch.resume(); // ----> 1
diff --git a/src/flame/tcp/server.cpp b/src/flame/tcp/server.cpp
index 44fa431..52b1df6 100644
--- a/src/flame/tcp/server.cpp
+++ b/src/flame/tcp/server.cpp
@@ -88,7 +88,7 @@ namespace flame::tcp {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << std::endl;
                     }
                     return nullptr;
                 }));
diff --git a/src/flame/udp/server.cpp b/src/flame/udp/server.cpp
index aca1199..7661ad1 100644
--- a/src/flame/udp/server.cpp
+++ b/src/flame/udp/server.cpp
@@ -84,7 +84,7 @@ namespace flame::udp {
                         gcontroller->call_user_cb("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
-                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << "\n";
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << std::endl;
                     }
                 }
                 if (--count == 0) ch.resume();
diff --git a/src/flame/worker.cpp b/src/flame/worker.cpp
index 2e20c7d..345bc80 100644
--- a/src/flame/worker.cpp
+++ b/src/flame/worker.cpp
@@ -1,3 +1,4 @@
+#include "../util.h"
 #include "../worker_logger.h"
 #include "controller.h"
 #include "worker.h"
@@ -30,9 +31,9 @@ namespace flame {
         .on_request_shutdown([] (php::extension_entry& ext) -> bool {
             if (gcontroller->status & controller::STATUS_INITIALIZED) {
                 if (php::error::exists()) 
-                    std::cerr << "[WARNING] process exited prematurely: uncaught exception / error\n";
+                    log::logger_->stream() << "[" << util::system_time() << "] (WARNING) process exited prematurely: uncaught exception / error\n" << std::endl;
                 if (!(gcontroller->status & controller::STATUS_RUN)) 
-                    std::cerr << "[WARNING] process exited prematurely: missing 'flame\\run();'\n";
+                    log::logger_->stream() << "[" << util::system_time() << "] (WARNING) process exited prematurely: missing 'flame\\run();'" << std::endl;
                 
                 gcontroller->status & controller::STATUS_EXCEPTION ? _exit(-1) : _exit(0);
             }
@@ -105,9 +106,11 @@ namespace flame {
 
         worker::ww_.reset(new worker(gcontroller->worker_idx));
 
-        gcontroller->init(options); // 首个 logger 的初始化过程在 logger 注册 on_init 回调中进行（异步的）
         // 信号监听启动
         worker::ww_->sw_watch();
+        if(gcontroller->worker_size > 0) worker::ww_->ipc_start();
+        
+        gcontroller->init(options); // 首个 logger 的初始化过程在 logger 注册 on_init 回调中进行（异步的）
         return nullptr;
     }
 
@@ -121,12 +124,13 @@ namespace flame {
             } catch (const php::exception &ex) {
                 gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
                 php::object obj = ex; // 记录错误信息
-                log::logger_->stream() << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << "\n";
+                log::logger_->stream() << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << std::endl;
 
                 boost::asio::post(gcontroller->context_x, [] () {
                     gcontroller->status |= controller::STATUS_EXCEPTION;
                     gcontroller->context_x.stop();
                     gcontroller->context_y.stop();
+                    gcontroller->context_z.stop();
                 });
             }
             return nullptr;
@@ -154,11 +158,14 @@ namespace flame {
 
         auto tswork = boost::asio::make_work_guard(gcontroller->context_y);
         std::thread ts[4];
-        for (int i=0; i<4; ++i) {
+        for (int i=0; i<3; ++i) {
             ts[i] = std::thread([] {
                 gcontroller->context_y.run();
             });
         }
+        ts[3] = std::thread([] {
+            gcontroller->context_z.run();
+        });
         gcontroller->context_x.run();
         tswork.reset();
         worker::ww_->sw_close();
@@ -166,17 +173,17 @@ namespace flame {
         worker::ww_->lm_close();
         for (int i=0; i<4; ++i) ts[i].join();
         gcontroller->stop();
+        worker::ww_.reset();
         return nullptr;
     }
 
     php::value worker::quit(php::parameters& params) {
         if ((gcontroller->status & controller::STATUS_RUN) == 0)
             throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
-        
-        gcontroller->call_user_cb("quit");
 
         gcontroller->context_x.stop();
         gcontroller->context_y.stop();
+        gcontroller->context_z.stop();
         return nullptr;
     }
 
@@ -204,7 +211,7 @@ namespace flame {
 
     worker::worker(std::uint8_t idx)
     : signal_watcher(gcontroller->context_y)
-    , worker_ipc(gcontroller->context_y, idx)
+    , worker_ipc(gcontroller->context_z, idx)
     , worker_logger_manager(this) {
 
     }
@@ -217,17 +224,24 @@ namespace flame {
     bool worker::on_signal(int sig) {
         switch(sig) {
         case SIGINT:
-        case SIGTERM:
-            if(++close_ > 1) {
+            if(gcontroller->worker_size > 0) break; // 多进程模式忽略
+            [[fallthrough]]; // 单进程模式通 SIGQUIT 处理
+        case SIGQUIT:
+            boost::asio::post(gcontroller->context_x, [] () {
+                if(gcontroller->worker_size > 0) {
+                    gcontroller->status |= controller::STATUS_EXCEPTION;
+                }
                 gcontroller->context_x.stop();
                 gcontroller->context_y.stop();
-                return false; // 多次停止信号立即结束
-            }
-            else coroutine::start(php::callable([] (php::parameters& params) -> php::value { // 通知用户退出(超时后主进程将杀死子进程)
+                gcontroller->context_z.stop();
+            });
+            return false; // 多次停止信号立即结束
+        break;
+        case SIGTERM:
+            coroutine::start(php::callable([] (php::parameters& params) -> php::value { // 通知用户退出(超时后主进程将杀死子进程)
                 gcontroller->call_user_cb("quit");
                 return nullptr;
             }));
-            break;
         case SIGUSR1:
             boost::asio::post(gcontroller->context_x, [] () {
                 gcontroller->status ^= controller::STATUS_CLOSECONN;
@@ -238,8 +252,17 @@ namespace flame {
         }
         return true;
     }
-
-    void worker::on_notify(std::shared_ptr<ipc::message_t> msg) {
-        gcontroller->call_user_cb("notify", {php::json_decode(&msg->payload[0], msg->length)});
+    // !!! 此函数在工作线程中工作
+    bool worker::on_message(std::shared_ptr<ipc::message_t> msg) {
+        switch(msg->command) {
+        case ipc::COMMAND_NOTIFY_DATA:
+            boost::asio::post(gcontroller->context_x, [msg] () {
+                std::cout << "notfiy data: " << std::string_view {&msg->payload[0], msg->length} << "\n";
+            // TODO 调度到主线程，通过协程队列传递
+            // gcontroller->call_user_cb("notify", {php::json_decode(&msg->payload[0], msg->length)});
+            });
+        break;
+        }
+        return true;
     }
 }
\ No newline at end of file
diff --git a/src/flame/worker.h b/src/flame/worker.h
index 36cb988..dcabfa1 100644
--- a/src/flame/worker.h
+++ b/src/flame/worker.h
@@ -37,11 +37,8 @@ namespace flame {
             return std::static_pointer_cast<worker_logger_manager>(shared_from_this());
         }
         bool on_signal(int sig) override;
-        void on_notify(std::shared_ptr<ipc::message_t> msg) override;
+        bool on_message(std::shared_ptr<ipc::message_t> msg) override;
     private:
         static std::shared_ptr<worker> ww_;
-
-        int close_ = 0;
-        int stats_ = 0;
     };
 }
\ No newline at end of file
diff --git a/src/ipc.h b/src/ipc.h
index cd28e5f..cf4227a 100644
--- a/src/ipc.h
+++ b/src/ipc.h
@@ -7,7 +7,9 @@ class ipc {
     // 传输协议：消息格式
     struct message_t {
         std::uint8_t  command;
+        std::uint8_t  source;
         std::uint8_t  target;
+        std::uint8_t  xdata[3];
         std::uint16_t length;
         std::uint32_t unique_id;
         char payload[0];
diff --git a/src/master_ipc.cpp b/src/master_ipc.cpp
index 34969c4..54cb38e 100644
--- a/src/master_ipc.cpp
+++ b/src/master_ipc.cpp
@@ -5,7 +5,7 @@
 
 master_ipc::master_ipc(boost::asio::io_context& io)
 : io_(io)
-, server_(io) {
+, server_(io, boost::asio::local::stream_protocol()) {
     svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getpid()).str();
 }
 
@@ -14,25 +14,27 @@ master_ipc::~master_ipc() {
     std::filesystem::remove(svrsck_);
 }
 
+void master_ipc::ipc_start() {
+    ::coroutine::start(io_.get_executor(), std::bind(&master_ipc::ipc_run, ipc_self(), std::placeholders::_1));
+}
+// !!! 工作线程中的协程
 void master_ipc::ipc_run(coroutine_handler ch) {
     std::error_code ec;
     std::filesystem::remove(svrsck_, ec);
     boost::asio::local::stream_protocol::endpoint addr(svrsck_);
     server_.bind(addr);
     server_.listen();
-
     boost::system::error_code error;
     while(true) {
         auto sock = std::make_shared<boost::asio::local::stream_protocol::socket>(io_);
         server_.async_accept(*sock, ch[error]);
         if (error) break;
-        // C++ 协程
-        // coroutine::start(io_.get_executor(), std::bind(&master_ipc::ipc_read, ipc_self(), sock, std::placeholders::_1));
-        coroutine::start(io_.get_executor(), [this, self = ipc_self(), sock = std::move(sock)] (coroutine_handler ch) {
-            ipc_read(sock, ch);
-        });
+        coroutine::start(io_.get_executor(), std::bind(&master_ipc::ipc_read, ipc_self(), sock, std::placeholders::_1));
     }
-    if (error && error != boost::asio::error::operation_aborted) output() << "[" << util::system_time() << "] (ERROR) Failed to accept ipc connection: (" << error.value() << ") " << error.message() << "\n";
+    if (error && error != boost::asio::error::operation_aborted)
+        output() << "[" << util::system_time() << "] (ERROR) Failed to accept ipc connection: (" << error.value() << ") " << error.message() << "\n";
+    
+    server_.close(error);
 }
 
 void master_ipc::ipc_close() {
@@ -43,11 +45,10 @@ void master_ipc::ipc_close() {
     }
 }
 
-// void master_ipc::ipc_read(socket_ptr sock, coroutine_handler ch) {
-void master_ipc::ipc_read(socket_ptr sock, coroutine_handler& ch) {
+void master_ipc::ipc_read(socket_ptr sock, coroutine_handler ch) {
     boost::system::error_code error;
     while(true) {
-        std::shared_ptr<ipc::message_t> msg = ipc::create_message();
+        auto msg = ipc::create_message();
         boost::asio::async_read(*sock, boost::asio::buffer(msg.get(), sizeof(ipc::message_t)), ch[error]);
         if (error) break;
         if (msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
@@ -57,7 +58,6 @@ void master_ipc::ipc_read(socket_ptr sock, coroutine_handler& ch) {
         if (error) break;
         if (!on_message(msg, sock)) break;
     }
-    
     if (error && error != boost::asio::error::operation_aborted && error != boost::asio::error::eof) 
         output() << "[" << util::system_time() << "] (ERROR) Failed to read ipc connection: (" << error.value() << ") " << error.message() << "\n";
     // 发生次数较少，效率不太重要了
@@ -72,38 +72,42 @@ void master_ipc::ipc_read(socket_ptr sock, coroutine_handler& ch) {
 
 bool master_ipc::on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) {
     if(msg->command >= ipc::COMMAND_TRANSFER_TO_CHILD) send(msg);
-    else if(msg->command == ipc::COMMAND_REGISTER) socket_[msg->target] = sock;
+    else if(msg->command == ipc::COMMAND_REGISTER) socket_[msg->source] = sock;
     return true;
 }
 
 std::shared_ptr<ipc::message_t> master_ipc::ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch) {
     std::shared_ptr<ipc::message_t> res;
     callback_.emplace(req->unique_id, ipc::callback_t {ch, res});
+    req->source = 0;
     send(req);
     ch.suspend();
     return res;
 }
 
-void master_ipc::ipc_request(std::shared_ptr<ipc::message_t> data) {
-    send(data);
+void master_ipc::ipc_request(std::shared_ptr<ipc::message_t> req) {
+    req->source = 0;
+    send(req);
 }
 
 void master_ipc::send(std::shared_ptr<ipc::message_t> msg) {
-    if(sendq_.empty()) {
-        sendq_.push_back(msg);
-        send_next();
-    }else
-        sendq_.push_back(msg);
+    boost::asio::post(io_, [this, self = ipc_self(), msg] () {
+        if(sendq_.empty()) {
+            sendq_.push_back(msg);
+            send_next();
+        }else
+            sendq_.push_back(msg);
+    });
 }
 
 void master_ipc::send_next() {
+    if (sendq_.empty()) return;
     std::shared_ptr<ipc::message_t> msg = sendq_.front();
     auto i = socket_.find(msg->target);
     if(i == socket_.end()) {
-        boost::asio::post(io_, [this] () {
-            sendq_.pop_front();
-            send_next();       
-        });
+        // std::cout << "message_dropped: " << (int) msg->command << " => " << (int) msg->target << "\n";
+        sendq_.pop_front();
+        boost::asio::post(io_, std::bind(&master_ipc::send_next, ipc_self()));
     }
     else {
         boost::asio::async_write(*i->second, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), [this] (const boost::system::error_code& error, std::size_t size) {
diff --git a/src/master_ipc.h b/src/master_ipc.h
index 235197e..2194185 100644
--- a/src/master_ipc.h
+++ b/src/master_ipc.h
@@ -10,10 +10,11 @@ class master_ipc {
     virtual ~master_ipc();
     // 输出
     virtual std::ostream& output() = 0;
+    void ipc_start();
     // 监听连接及连接数据接收
     void ipc_run(coroutine_handler ch);
-    // void ipc_read(socket_ptr sock, coroutine_handler ch);
-    void ipc_read(socket_ptr sock, coroutine_handler& ch);
+    void ipc_read(socket_ptr sock, coroutine_handler ch);
+    // void ipc_read(socket_ptr sock, coroutine_handler& ch);
     void ipc_close();
     // 请求并等待响应
     std::shared_ptr<ipc::message_t> ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch);
diff --git a/src/master_logger.cpp b/src/master_logger.cpp
index e716c90..356ae5b 100644
--- a/src/master_logger.cpp
+++ b/src/master_logger.cpp
@@ -1,14 +1,24 @@
 #include "master_logger.h"
-#include "coroutine.h"
+#include "master_logger_buffer.h"
+#include "util.h"
 
-void master_logger::reload() {
-    file_.reset(new std::ofstream(path_, std::ios_base::out | std::ios_base::app));
-    if (file_->fail()) // 文件打开失败时不会抛出异常，需要额外的状态检查
-        std::cerr << "(ERROR) failed to create/open logger: file cannot be created or accessed\n";
-    else return;
-    file_.reset(&std::clog, boost::null_deleter());
+void master_logger::reload(boost::asio::io_context& io) {
+    if(path_.string() != "<clog>") {
+        auto fb = new master_logger_buffer(io, path_);
+        // fb->open(path_, std::ios_base::app);
+        ssb_.reset(fb);
+        oss_.reset(new std::ostream(fb));
+
+        fb->persync(); // 启动周期性文件缓冲刷新服务
+    }
+    if (oss_->fail()) { // 文件打开失败时不会抛出异常，需要额外的状态检查
+        std::cerr << "[" << util::system_time() << "] (WARNING) Failed to access logger file, fallback to 'clog'\n";
+        oss_.reset(&std::clog, boost::null_deleter());
+    }
 }
 
 void master_logger::close() {
-    file_.reset(&std::clog, boost::null_deleter());
+    oss_.reset(&std::clog, boost::null_deleter());
+    // ssb_->close();
+    ssb_.reset();
 }
\ No newline at end of file
diff --git a/src/master_logger.h b/src/master_logger.h
index 0af1d37..382ec36 100644
--- a/src/master_logger.h
+++ b/src/master_logger.h
@@ -4,16 +4,19 @@
 
 class master_logger {
 private:
-    std::uint8_t                   idx_;
-    unsigned int                   ref_;
-    std::shared_ptr<std::ostream> file_;
-    std::filesystem::path         path_;
+    std::uint8_t                     idx_;
+    unsigned int                     ref_;
+    std::shared_ptr<std::ostream>    oss_;
+    std::unique_ptr<std::streambuf>  ssb_;
+    std::filesystem::path           path_;
 public:
     master_logger(std::filesystem::path path, int index): idx_(index), ref_(1), path_(path) {}
-
-    void reload();
+    std::uint8_t index() {
+        return idx_;
+    }
+    void reload(boost::asio::io_context& io);
     std::ostream& stream() {
-        return *file_;
+        return *oss_;
     }
     void close();
     friend class master_logger_manager;
diff --git a/src/master_logger_buffer.cpp b/src/master_logger_buffer.cpp
new file mode 100644
index 0000000..c570455
--- /dev/null
+++ b/src/master_logger_buffer.cpp
@@ -0,0 +1,45 @@
+#include "master_logger_buffer.h"
+#include "master_logger_manager.h"
+
+master_logger_buffer::master_logger_buffer(boost::asio::io_context& io, std::filesystem::path path)
+: tms_(io) {
+    open(path, std::ios_base::app);
+}
+
+int master_logger_buffer::overflow(int ch) {
+    char c = ch;
+    ch = std::filebuf::overflow(ch);
+    
+    if(c == '\n' && ++lns_ > 64) { // 积攒的行数超过 64 行刷新
+        lns_ = 0;
+        pubsync();
+        persync();
+    }
+    return ch;
+}
+
+long master_logger_buffer::xsputn(const char* s, long c) {
+    c = std::filebuf::xsputn(s, c);
+
+    if(s[c-1] == '\n' && ++lns_ > 64) { // 积攒的行数超过 64 行刷新
+        lns_ = 0;
+        pubsync();
+        persync();
+    }
+    return c;
+}
+
+
+void master_logger_buffer::persync() {
+    tms_.cancel();
+    tms_.expires_after(std::chrono::milliseconds(2400));
+    tms_.async_wait([this] (const boost::system::error_code& error) {
+        if(error) return;
+        pubsync();
+        persync(); // 每 2400 毫秒对文件进行一次刷新
+    });
+}
+
+// void master_logger_buffer::close() {
+//     tms_.cancel();
+// }
\ No newline at end of file
diff --git a/src/master_logger_buffer.h b/src/master_logger_buffer.h
new file mode 100644
index 0000000..a0e93cf
--- /dev/null
+++ b/src/master_logger_buffer.h
@@ -0,0 +1,20 @@
+#pragma once
+#include <memory>
+#include <streambuf>
+#include "ipc.h"
+
+class master_logger_manager;
+// 实现行数或周期为同步、刷写标志的缓冲区
+class master_logger_buffer: public std::filebuf {
+public:
+    master_logger_buffer(boost::asio::io_context& io, std::filesystem::path path);
+    void persync();
+    // void close();
+protected:
+    int  overflow(int ch = EOF) override;
+    long xsputn(const char* s, long c) override;
+private:
+    master_logger_manager*          mgr_;
+    boost::asio::steady_timer       tms_;
+    unsigned int                    lns_ = 0;
+};
diff --git a/src/master_logger_manager.cpp b/src/master_logger_manager.cpp
index d6b7eea..a7df097 100644
--- a/src/master_logger_manager.cpp
+++ b/src/master_logger_manager.cpp
@@ -1,11 +1,15 @@
 #include "master_logger_manager.h"
-#include "master_logger.h"
 
+master_logger_manager::master_logger_manager(boost::asio::io_context& io)
+: io_(io) {
 
-master_logger_manager::master_logger_manager() {}
-master_logger_manager::~master_logger_manager() {}
+}
+
+master_logger_manager::~master_logger_manager() {
+    // std::cout << "~master_logger_manager\n";
+}
 
-master_logger* master_logger_manager::lm_connect(const std::string& filepath) {
+master_logger* master_logger_manager::lm_connect(std::string_view filepath) {
     std::filesystem::path path = filepath;
     
     for(auto i=logger_.begin();i!=logger_.end();++i) {
@@ -16,7 +20,7 @@ master_logger* master_logger_manager::lm_connect(const std::string& filepath) {
     }
     auto p = logger_.insert({index_, std::make_unique<master_logger>(path, index_)});
     ++index_;
-    p.first->second->reload();
+    p.first->second->reload(io_);
     return p.first->second.get();
 }
 
@@ -28,13 +32,8 @@ void master_logger_manager::lm_destroy(std::uint8_t idx) {
     logger_.erase(i);
 }
 
-master_logger* master_logger_manager::lm_get(std::uint8_t idx) {
-    auto i = logger_.find(idx);
-    return i == logger_.end() ? nullptr : i->second.get();
-}
-
 void master_logger_manager::lm_reload() {
-    for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload();
+    for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload(io_);
 }
 
 void master_logger_manager::lm_close() {
diff --git a/src/master_logger_manager.h b/src/master_logger_manager.h
index 220d6ba..d1e26d3 100644
--- a/src/master_logger_manager.h
+++ b/src/master_logger_manager.h
@@ -1,19 +1,15 @@
 #pragma once
 #include "vendor.h"
+#include "master_logger.h"
 
-class master_logger;
 class master_logger_manager {
 public:
-    master_logger_manager();
+    master_logger_manager(boost::asio::io_context& io);
     virtual ~master_logger_manager();
     // 日志引用
-    master_logger* lm_connect(const std::string& filepath);
-    // void lm_destroy(master_logger* ml) {
-    //     assert(ml->ref_ > 0 && "引用计数异常");
-    //     if(--ml->ref_ > 0) return;
-    //     logger_.erase(ml->idx_);
-    // }
+    master_logger* lm_connect(std::string_view filepath);
     master_logger* lm_get(std::uint8_t idx);
+    std::ostream&  lm_get(std::uint8_t idx, bool output);
     // 引用清理
     void lm_destroy(std::uint8_t idx);
     // 日志重载
@@ -21,6 +17,17 @@ class master_logger_manager {
     void lm_close();
     virtual std::shared_ptr<master_logger_manager> lm_self() = 0;
 private:
+    boost::asio::io_context&                                   io_;
     std::uint8_t                                            index_ = 0;
     std::map<unsigned int, std::unique_ptr<master_logger>> logger_;
-};
\ No newline at end of file
+};
+
+inline master_logger* master_logger_manager::lm_get(std::uint8_t idx) {
+    auto i = logger_.find(idx);
+    return i == logger_.end() ? nullptr : i->second.get();
+}
+
+inline std::ostream& master_logger_manager::lm_get(std::uint8_t idx, bool output) {
+    auto i = logger_.find(idx);
+    return i == logger_.end() ? std::clog : i->second.get()->stream();
+}
diff --git a/src/master_process.cpp b/src/master_process.cpp
index fd3fc5b..9f42768 100755
--- a/src/master_process.cpp
+++ b/src/master_process.cpp
@@ -25,6 +25,8 @@ master_process::master_process(boost::asio::io_context& io, master_process_manag
     // 构造进程
     proc_ = boost::process::child(io, php_cmd(), env,
         boost::process::std_out > sout_, boost::process::std_err > eout_,
+        // boost::process::std_out > boost::process::null, boost::process::std_err > boost::process::null,
+        // boost::process::std_out > stdout, boost::process::std_err > stdout,
         // 结束回调
         boost::process::on_exit = [this, &io] (int exit_code, const std::error_code &error) {
             if (error.value() == static_cast<int>(std::errc::no_child_process)) return;
@@ -43,12 +45,12 @@ master_process::master_process(boost::asio::io_context& io, master_process_manag
 void master_process::redirect_output(boost::process::async_pipe& pipe, std::string& data) {
     boost::asio::async_read_until(pipe, boost::asio::dynamic_buffer(data), '\n', [this, &pipe, &data] (const boost::system::error_code &error, std::size_t nread) {
         if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
-            ; // 忽略
+            ; // std::cout << "redirect_output stopped" << std::endl; // 忽略
         else if (error) {
             mgr_->output() << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
         }
         else {
-            mgr_->output() << std::string_view(data.c_str(), nread);
+            mgr_->output() << "-----" << std::string_view(data.data(), nread);
             data.erase(0, nread);
             redirect_output(pipe, data);
         }
@@ -57,7 +59,7 @@ void master_process::redirect_output(boost::process::async_pipe& pipe, std::stri
 
 void master_process::close(bool force) {
     if(force) proc_.terminate();
-    else ::kill(proc_.id(), SIGTERM);
+    else ::kill(proc_.id(), SIGQUIT);
 }
 
 void master_process::signal(int sig) {
diff --git a/src/master_process_manager.cpp b/src/master_process_manager.cpp
index 0e11b8d..b381f68 100644
--- a/src/master_process_manager.cpp
+++ b/src/master_process_manager.cpp
@@ -3,24 +3,33 @@
 #include "coroutine.h"
 #include "util.h"
 
-master_process_manager::master_process_manager(boost::asio::io_context& io, unsigned int count)
+master_process_manager::master_process_manager(boost::asio::io_context& io, unsigned int count, unsigned int close)
 : io_(io)
-, count_(count) {
+, count_(count)
+, close_(close)
+, child_(count)
+, timer_(io)
+, status_(0) {
 
 }
 
 master_process_manager::~master_process_manager() {
-    
+    // std::cout << "~master_process_manager\n";
 }
 
-void master_process_manager::pm_start() {
+void master_process_manager::pm_start(boost::asio::io_context& io) {
+    // 这个 work_guard 为了使得主线程在子进程未全部退出前保持运行
+    // 实际主线程并没有工作量
+    work_.reset(new boost::asio::io_context::work(io));
     for(int i=0;i<count_;++i) {
         child_[i].reset(new master_process(io_, this, i));
     }
 }
 
 void master_process_manager::pm_reset(coroutine_handler& ch) {
-    std::vector<std::unique_ptr<master_process>> start;
+    if(status_ & (STATUS_RSETING | STATUS_ACLOSED)) return;
+
+    std::vector<std::unique_ptr<master_process>> start(count_);
     for(int i=0;i<count_;++i) {
         start[i].reset(new master_process(io_, this, i));
         ch_start_.reset(ch);
@@ -30,38 +39,46 @@ void master_process_manager::pm_reset(coroutine_handler& ch) {
         util::co_sleep(io_, std::chrono::milliseconds(140), ch);
         // 新进程已经启动 1/4 后，开始停止原进程（一次性动作）
         if(i == count_ / 4) coroutine::start(io_.get_executor(), [this] (coroutine_handler ch) {
-            pm_close(false, ch);
+            pm_close(ch);
         });
     }
     child_ = std::move(start);
 }
 
-void master_process_manager::pm_close(unsigned int timeout_ms, coroutine_handler& ch) {
-    std::vector<std::unique_ptr<master_process>> closing = std::move(child_);
-    for(int i=0;i<count_;++i) closing[i]->close(timeout_ms == 0);
-    if(timeout_ms == 0) return;
-
-    int  stopped = 0;
-    bool timeout = false;
-    boost::asio::steady_timer tm(io_);
-    tm.expires_after(std::chrono::milliseconds(timeout_ms));
-    tm.async_wait([&ch, &timeout, this] (const boost::system::error_code& error) mutable {
-        if(error) return;
-        timeout = true;
-        ch.resume();
-    });
-WAIT_FOR_CLOSE:
-    ch_close_.reset(ch);
-    ch_close_.suspend(); // 等待回调 on_child_event
-    ch_close_.reset();
+void master_process_manager::pm_close(coroutine_handler& ch, bool now) {
+    if(status_ & (STATUS_RSETING | STATUS_ACLOSED)) return;
 
-    if(timeout) {
-        child_ = std::move(closing);
-        pm_close(0, ch);
+    if(status_ & STATUS_CLOSING) {
+        timer_.cancel(); // 提前强制关闭 ---> (3)
         return;
     }
-    if(++stopped < count_) goto WAIT_FOR_CLOSE;
-    else tm.cancel();
+    status_ |= STATUS_CLOSING;
+    for(int i=0;i<count_;++i) child_[i]->close(now);
+    if (now) {
+        status_ |= STATUS_ACLOSED;
+        timer_.cancel(); // 可能有异常重启中的进程存在
+        goto SHUTDOWN;
+    }
+    {
+        int  stopped = count_;
+        bool timeout = false;
+        timer_.expires_after(std::chrono::milliseconds(close_));
+        timer_.async_wait([&ch, &timeout, &stopped, this, self = pm_self()] (const boost::system::error_code& error) mutable {
+            if(status_ & STATUS_ACLOSED) return;  // (2) 结束后栈数据丢失（ch/timeout/stopped)
+            timeout = true; // (3) 还未完全关闭时超时或提前强制关闭
+            ch.resume();
+        });
+    WAIT_FOR_CLOSE:
+        ch_close_.reset(ch);
+        ch_close_.suspend(); // 等待回调 on_child_event
+        ch_close_.reset();
+        if(timeout) return pm_close(ch, true);
+        else if(--stopped > 0) goto WAIT_FOR_CLOSE;
+    }
+SHUTDOWN:
+    status_ |= STATUS_ACLOSED;   // -------> (2)
+    timer_.cancel();
+    work_.reset();
 }
 
 void master_process_manager::pm_kills(int sig) {
@@ -73,5 +90,14 @@ void master_process_manager::on_child_start(master_process* w) {
 }
 
 void master_process_manager::on_child_close(master_process* w, bool normal) {
-    if (ch_close_) ch_close_.resume(); // 陆续起停流程
-}
\ No newline at end of file
+    if (!(status_ & STATUS_CLOSING) && !normal) {
+        unsigned int rand = std::rand() % 2000 + 1000;
+        output() << "[" << util::system_time() << "] (WARNING) unexpected worker process stopping, restart in " << int(rand/1000) << "s ...\n";
+        timer_.expires_after(std::chrono::milliseconds(rand));
+        timer_.async_wait([this, self = pm_self(), idx = w->idx_] (const boost::system::error_code& error) {
+            if(error) return;
+            child_[idx].reset(new master_process(io_, this, idx));
+        });
+    }
+    else if (ch_close_) ch_close_.resume(); // 陆续起停流程
+}
diff --git a/src/master_process_manager.h b/src/master_process_manager.h
index b9ad058..8a675dc 100644
--- a/src/master_process_manager.h
+++ b/src/master_process_manager.h
@@ -5,35 +5,45 @@
 class master_process;
 class master_process_manager {
 public:
-    master_process_manager(boost::asio::io_context& io, unsigned int count);
+    master_process_manager(boost::asio::io_context& io, unsigned int count, unsigned int timeout_ms);
     virtual ~master_process_manager();
     // 输出
     virtual std::ostream& output() = 0;
 protected:
     virtual std::shared_ptr<master_process_manager> pm_self() = 0;
     // 启动子进程
-    void pm_start();
+    void pm_start(boost::asio::io_context& io); // 此 io 非彼 io
     // 重启子进程（陆续）
     void pm_reset(coroutine_handler& ch);
     // 停止子进程
-    void pm_close(unsigned int ms, coroutine_handler& ch);
+    void pm_close(coroutine_handler& ch, bool now = false);
     // 发送信号
     void pm_kills(int sig);
     // 进程数量
     std::uint8_t pm_count() {
         return count_;
     }
+protected:
     // 进程启动回调
     virtual void on_child_start(master_process* w);
     // 进程停止回调
     virtual void on_child_close(master_process* w, bool normal);
 private:
+    std::unique_ptr<boost::asio::io_context::work> work_;
     boost::asio::io_context&                        io_;
     unsigned int                                 count_;
+    unsigned int                                 close_;
     std::vector<std::unique_ptr<master_process>> child_;
-    
+    boost::asio::steady_timer                    timer_;
     coroutine_handler ch_close_;
     coroutine_handler ch_start_;
+    int status_;
+    enum {
+        STATUS_CLOSING = 0x01,
+        STATUS_RSETING = 0x02,
+        STATUS_QUITING = 0x04,
+        STATUS_ACLOSED = 0x08,
+    };
     
     friend class master_process;
 };
diff --git a/src/signal_watcher.h b/src/signal_watcher.h
index d730526..93ea894 100644
--- a/src/signal_watcher.h
+++ b/src/signal_watcher.h
@@ -10,10 +10,11 @@ class signal_watcher {
         ss_->add(SIGTERM);
         ss_->add(SIGUSR1);
         ss_->add(SIGUSR2);
+        ss_->add(SIGQUIT);
     }
     virtual ~signal_watcher() {
-        std::cout << "~signal_watcher\n";
         ss_.reset();
+        // std::cout << "~signal_watcher\n";
     }
     void sw_watch() {
         ss_->async_wait([this, self = sw_self()] (const boost::system::error_code& error, int sig) {
diff --git a/src/url.cpp b/src/url.cpp
index f5d60a4..82dd653 100644
--- a/src/url.cpp
+++ b/src/url.cpp
@@ -38,12 +38,12 @@ url::url(const php::string &str, bool parse_query)
         parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
             php::url_decode_inplace(et.second.data(), et.second.size());
             query[et.first] = et.second;
-            // std::cout << et.first << ": " << et.second << "\n";
         });
         p.parse(tmp, strlen(tmp));
         p.end();
         curl_free(tmp);
     }
+    curl_url_cleanup(u);
 }
 
 std::string url::str(bool with_query, bool update) {
diff --git a/src/worker_ipc.cpp b/src/worker_ipc.cpp
index d3152ef..ca198b8 100644
--- a/src/worker_ipc.cpp
+++ b/src/worker_ipc.cpp
@@ -2,28 +2,29 @@
 #include "util.h"
 
 worker_ipc::worker_ipc(boost::asio::io_context& io, std::uint8_t idx)
-: socket_(new boost::asio::local::stream_protocol::socket(io))
+: io_(io)
+, socket_(new boost::asio::local::stream_protocol::socket(io))
 , idx_(idx) {
-    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getpid()).str();
+    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getppid()).str();
 }
 
 worker_ipc::~worker_ipc() {
-    std::cout << "~worker_ipc\n";
+    // std::cout << "~worker_ipc\n";
     ipc_close();
 }
 
 std::shared_ptr<ipc::message_t> worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch) {
     std::shared_ptr<ipc::message_t> res;
     callback_.emplace(req->unique_id, ipc::callback_t {ch, res});
+    req->source = idx_;
     send(req);
-    std::cout << "before suspend\n";
     ch.suspend();
-    std::cout << "after suspend\n";
     return res;
 }
 
-void worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> data) {
-    send(data);
+void worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> req) {
+    req->source = idx_;
+    send(req);
 }
 
 static void json_message_free(ipc::message_t* msg) {
@@ -37,6 +38,7 @@ void worker_ipc::ipc_notify(std::uint8_t target, php::value data) {
     smart_str_alloc(&str, 1, false);
     ipc::message_t* msg = (ipc::message_t*)str.s->val;
     msg->command = ipc::COMMAND_NOTIFY_DATA;
+    msg->source  = idx_;
     msg->target  = target;
     str.s->len = sizeof(ipc::message_t);
     php::json_encode_to(&str, data);
@@ -44,20 +46,34 @@ void worker_ipc::ipc_notify(std::uint8_t target, php::value data) {
     send(std::shared_ptr<ipc::message_t>{msg, ipc::free_message});
 }
 
+void worker_ipc::ipc_start() {
+    // !!!! 注意本地日志的判定机制与时序、时间有关
+    ++status_; // 0
+    coroutine::start(io_.get_executor(), 
+            std::bind(&worker_ipc::ipc_run, ipc_self(), std::placeholders::_1)); // 由于线程池影响，特殊的启动方式
+}
+// !!!! 工作线程
 void worker_ipc::ipc_run(coroutine_handler ch) {
     boost::system::error_code error;
     std::shared_ptr<ipc::message_t> msg;
     std::uint64_t tmp;
-
-    ++status_;
+    
     // 连接主进程 IPC 通道
     boost::asio::local::stream_protocol::endpoint addr(svrsck_);
     socket_->async_connect(addr, ch[error]);
     if (error) goto IPC_FAILED;
-    ++status_;
-    send_login();
+    ++status_; // 1
     // 发送注册消息
-    ++status_;
+    msg = ipc::create_message();
+    msg->command = ipc::COMMAND_REGISTER;
+    msg->source  = idx_;
+    msg->target  = 0;
+    msg->length  = 0;
+    boost::asio::async_write(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), ch[error]);
+    if (error) goto IPC_FAILED;
+    ++status_; // 2
+    // 已经缓冲在队列中的数据开始发送
+    send_next();
     while(true) {
         msg = ipc::create_message();
         boost::asio::async_read(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t)), ch[error]);
@@ -65,25 +81,20 @@ void worker_ipc::ipc_run(coroutine_handler ch) {
         if(msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
             ipc::relloc_message(msg, 0, msg->length);
         }
-        boost::asio::async_read(*socket_, boost::asio::buffer(&msg->payload[0], msg->length), ch[error]);
-        if(error) break;
-
-        switch(msg->command) {
-        case ipc::COMMAND_LOGGER_CONNECT: {
-            auto pcb = callback_.extract(msg->unique_id);
-            assert(!pcb.empty() && "未找到必要回调");
+        if(msg->length > 0) {
+            boost::asio::async_read(*socket_, boost::asio::buffer(&msg->payload[0], msg->length), ch[error]);
+            if(error) break;
+        }
+        if(!on_message(msg)) break;
+        auto pcb = callback_.extract(msg->unique_id);
+        if(!pcb.empty()) {
             pcb.mapped().res = msg;
             pcb.mapped().cch.resume();
         }
-        break;
-        case ipc::COMMAND_NOTIFY_DATA:
-            on_notify(msg);
-        break;
-        }
     }
 IPC_FAILED:
     if (error && error != boost::asio::error::operation_aborted && error != boost::asio::error::eof) 
-        output() << "[" << util::system_time() << "] (ERROR) Failed to read ipc connection: (" << error.value() << ") " << error.message() << "\n";
+        output() << "[" << util::system_time() << "] (ERROR) Failed during IPC with master: (" << error.value() << ") " << error.message() << "\n";
     socket_->close(error);
 }
 
@@ -94,29 +105,23 @@ void worker_ipc::ipc_close() {
 bool worker_ipc::ipc_enabled() {
     return status_ > -1;
 }
-
-void worker_ipc::send_login() {
-    auto msg = ipc::create_message();
-    msg->command = ipc::COMMAND_REGISTER;
-    msg->target  = idx_;
-    msg->length  = 0;
-    send(msg);
-}
-
+// 由于实际调用者来自主线程，需要线性转接到（多个）工作线程中
 void worker_ipc::send(std::shared_ptr<ipc::message_t> msg) {
-    if (sendq_.empty()) {
-        sendq_.push_back(msg);
-        if (status_ > 0) send_next();
-    }else
+    assert(status_ > -1);
+    boost::asio::post(io_, [this, self = ipc_self(), msg] () {
         sendq_.push_back(msg);
+        if(status_ > 1 && sendq_.size() == 1) send_next();
+        // C++11 后， std::list::size() 是常数及时间复杂度
+    });
 }
 
 void worker_ipc::send_next() {
+    if (sendq_.empty()) return;
     std::shared_ptr<ipc::message_t> msg = sendq_.front();
 
     boost::asio::async_write(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), [this] (const boost::system::error_code& error, std::size_t size) {
         if(error) {
-            output() << "[" << util::system_time() << "] [FATAL] Failed to write ipc message: (" << error.value() << ") " << error.message() << "\n";
+            output() << "[" << util::system_time() << "] [FATAL] Failed during IPC with master: (" << error.value() << ") " << error.message() << "\n";
             return;
         }
         sendq_.pop_front();
diff --git a/src/worker_ipc.h b/src/worker_ipc.h
index e09a867..cdc8671 100644
--- a/src/worker_ipc.h
+++ b/src/worker_ipc.h
@@ -15,6 +15,7 @@ class worker_ipc {
     void ipc_request(std::shared_ptr<ipc::message_t> data);
     // 简化 notify 请求
     void ipc_notify(std::uint8_t target, php::value data);
+    void ipc_start();
     // 读取及消息监听
     void ipc_run(coroutine_handler ch);
     // 关闭通道
@@ -23,8 +24,9 @@ class worker_ipc {
     bool ipc_enabled();
 protected:
     virtual std::shared_ptr<worker_ipc> ipc_self() = 0;
-    virtual void on_notify(std::shared_ptr<ipc::message_t> msg) = 0;
+    virtual bool on_message(std::shared_ptr<ipc::message_t> msg) = 0;
 private:
+    boost::asio::io_context&                       io_;
     std::filesystem::path                      svrsck_;
     std::uint8_t                                  idx_;
     socket_ptr                                 socket_;
@@ -32,7 +34,6 @@ class worker_ipc {
     std::list<std::shared_ptr<ipc::message_t>>  sendq_;
     int                                        status_ = -1;
 
-    void send_login();
     void send(std::shared_ptr<ipc::message_t> msg);
     void send_next();
 };
diff --git a/src/worker_logger.cpp b/src/worker_logger.cpp
index 60e67bc..11e4a14 100644
--- a/src/worker_logger.cpp
+++ b/src/worker_logger.cpp
@@ -5,14 +5,15 @@
 worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx) 
 : idx_(0)
 , path_(path)
-, wlb_(new worker_logger_buffer(mgr))
+, wlb_(new worker_logger_buffer(mgr, idx))
 , oss_(new std::ostream(wlb_.get())) {
-
+    // std::cout << "worker_logger:: ipc(" << path << ")" << std::endl;
 }
 
 worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx, bool local)
 : idx_(0)
 , path_(path) {
+    // std::cout << "worker_logger:: local(" << path << ")" << std::endl;
     if(path.string() != "<clog>") {
         auto fb = new std::filebuf();
         fb->open(path, std::ios_base::app);
diff --git a/src/worker_logger_buffer.cpp b/src/worker_logger_buffer.cpp
index 8ad0a27..7961d07 100644
--- a/src/worker_logger_buffer.cpp
+++ b/src/worker_logger_buffer.cpp
@@ -2,10 +2,12 @@
 #include "worker_logger_manager.h"
 #include "worker_ipc.h"
 
-worker_logger_buffer::worker_logger_buffer(worker_logger_manager* mgr)
+worker_logger_buffer::worker_logger_buffer(worker_logger_manager* mgr, std::uint8_t idx)
 : mgr_(mgr)
-, msg_(ipc::create_message()) {
+, msg_(ipc::create_message())
+, idx_(idx) {
     cap_ = msg_->length;
+    msg_->xdata[0]  = idx_;
     msg_->length = 0;
 }
 
@@ -30,10 +32,12 @@ long worker_logger_buffer::xsputn(const char* s, long c) {
 }
 
 void worker_logger_buffer::transfer_msg() {
-    msg_->command = ipc::COMMAND_NOTIFY_DATA;
+    msg_->command = ipc::COMMAND_LOGGER_DATA;
     msg_->target  = 0;
     mgr_->ipc_->ipc_request(msg_);
+
     msg_ = ipc::create_message();
     cap_ = msg_->length;
+    msg_->xdata[0]   = idx_; // 实际写入的日志文件
     msg_->length  = 0; // 以 length 累计当前需要发送的数据
 }
\ No newline at end of file
diff --git a/src/worker_logger_buffer.h b/src/worker_logger_buffer.h
index 0ab939c..362da50 100644
--- a/src/worker_logger_buffer.h
+++ b/src/worker_logger_buffer.h
@@ -6,7 +6,7 @@
 class worker_logger_manager;
 class worker_logger_buffer: public std::streambuf {
 public:
-    worker_logger_buffer(worker_logger_manager* mgr);
+    worker_logger_buffer(worker_logger_manager* mgr, std::uint8_t idx);
 protected:
     int overflow(int ch = EOF) override;
     long xsputn(const char* s, long c) override;
@@ -14,6 +14,7 @@ class worker_logger_buffer: public std::streambuf {
     worker_logger_manager*          mgr_;
     std::shared_ptr<ipc::message_t> msg_;
     std::uint16_t                   cap_;
+    std::uint8_t                    idx_;
 
     void transfer_msg();
 };
diff --git a/src/worker_logger_manager.cpp b/src/worker_logger_manager.cpp
index d0ebc65..0cb3dcf 100644
--- a/src/worker_logger_manager.cpp
+++ b/src/worker_logger_manager.cpp
@@ -3,7 +3,7 @@
 #include "worker_ipc.h"
 
 worker_logger_manager::~worker_logger_manager() {
-    std::cout << "~worker_logger_manager\n";
+    // std::cout << "~worker_logger_manager\n";
 }
 
 std::shared_ptr<worker_logger> worker_logger_manager::lm_connect(const std::string& file, coroutine_handler& ch) {
@@ -23,13 +23,15 @@ std::shared_ptr<worker_logger> worker_logger_manager::lm_connect(const std::stri
     std::uint8_t index = 0;
     if (ipc_->ipc_enabled()) {
         auto msg = ipc::create_message();
-        msg->command = ipc::COMMAND_LOGGER_CONNECT;
+        msg->command   = ipc::COMMAND_LOGGER_CONNECT;
+        msg->target    = 0; // 发送给主进程的消息
         msg->unique_id = ipc::create_uid();
-        std::memcpy(msg->payload, file.data(), file.size());
+        msg->length = file.size();
+        std::memcpy(msg->payload, file.data(), msg->length);
         msg = ipc_->ipc_request(msg, ch);
-        index = msg->target;
+        index = msg->xdata[0];
         // 使用该日志文件标号创建 LOGGER 对象，以支持实际日志发送
-        wl = std::make_shared<worker_logger>(this, file, msg->target);
+        wl = std::make_shared<worker_logger>(this, file, index);
     }
     else {
         index = lindex_;
diff --git a/test/logger_1.php b/test/logger_1.php
index e888c76..5f8013d 100644
--- a/test/logger_1.php
+++ b/test/logger_1.php
@@ -9,4 +9,4 @@
     flame\log\warn("This is another warning message");
 });
 
-flame\run();
\ No newline at end of file
+flame\run();
diff --git a/test/logger_2.php b/test/logger_2.php
new file mode 100644
index 0000000..5dd0716
--- /dev/null
+++ b/test/logger_2.php
@@ -0,0 +1,22 @@
+<?php
+flame\init("logger_2", [
+    "logger" => __DIR__."/logger_2.log", // 目标日志文件
+]);
+
+flame\go(function() {
+    for($i=0;$i<10000;++$i) {
+        flame\time\sleep(rand(4000, 5000));
+        flame\log\trace("WORKING:", intval(getenv("FLAME_CUR_WORKER")));
+        flame\time\sleep(rand(4000, 5000));
+        // 标准、错误输出在多进程模式重定向到默认日志文件
+        echo "[", flame\time\iso(), "] (TRACE) WORKING: ", intval(getenv("FLAME_CUR_WORKER")), "\n";
+    }
+    flame\log\trace("DONE:", intval(getenv("FLAME_CUR_WORKER")));
+});
+
+flame\on("quit", function() {
+    flame\log\trace("QUITING:", intval(getenv("FLAME_CUR_WORKER")));
+    // flame\quit();
+});
+
+flame\run();
diff --git a/test/logger_3.php b/test/logger_3.php
new file mode 100644
index 0000000..b41b4f3
--- /dev/null
+++ b/test/logger_3.php
@@ -0,0 +1,14 @@
+<?php
+flame\init("logger_3", [
+    "logger" => __DIR__."/logger_3.log"
+]);
+
+flame\go(function() {
+    $logger = flame\log\connect(__DIR__."/logger_3.log.extra");
+    for($i=0;$i<10000;++$i) { 
+        $logger->write("无前缀的日志数据：", flame\time\now());
+        flame\time\sleep(rand(4000, 5000));
+    }
+});
+
+flame\run();
diff --git a/test/worker_1.php b/test/worker_1.php
deleted file mode 100644
index bf94519..0000000
--- a/test/worker_1.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-flame\init("worker_1");
-
-flame\go(function() {
-    flame\time\sleep(1000);
-    flame\log\trace("WORKING:", intval(getenv("FLAME_CUR_WORKER")) - 1);
-    flame\time\sleep(60000);
-});
-
-flame\on("quit", function() {
-    flame\log\trace("QUITING:", intval(getenv("FLAME_CUR_WORKER")) - 1);
-    flame\quit();
-});
-
-flame\run();

From c55dbed396d9b991deaf21a193c19917bdd792dd Mon Sep 17 00:00:00 2001
From: terrywh <terry.wuhao@gmail.com>
Date: Fri, 5 Jul 2019 20:11:36 +0800
Subject: [PATCH 146/146] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=88=B6=E5=AD=90?=
 =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E6=A8=A1=E5=BC=8F=E5=8F=8A=E5=8D=95=E8=BF=9B?=
 =?UTF-8?q?=E7=A8=8B=E6=A8=A1=E5=BC=8F=E4=BF=A1=E5=8F=B7=E3=80=81=E8=BF=9B?=
 =?UTF-8?q?=E7=A8=8B=E7=AE=A1=E7=90=86=E3=80=81IPC=20=E8=B5=B7=E5=81=9C?=
 =?UTF-8?q?=E7=AD=89=E7=9B=B8=E5=85=B3=E6=B5=81=E7=A8=8B=EF=BC=9B=E6=96=B0?=
 =?UTF-8?q?=E5=A2=9E=EF=BC=9AIPC=20=E5=AD=90=E8=BF=9B=E7=A8=8B=E9=80=9A?=
 =?UTF-8?q?=E8=AE=AF=E6=9C=BA=E5=88=B6=20flame\send()=20/=20flame\on("mess?=
 =?UTF-8?q?age")=20=E7=AD=89=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/core.php                           | 31 +++++++--
 src/coroutine_queue.h                  |  1 +
 src/flame/controller.cpp               | 34 ++++++----
 src/flame/controller.h                 | 14 ++--
 src/flame/coroutine.cpp                |  1 +
 src/flame/http/_handler.cpp            |  2 +-
 src/flame/kafka/consumer.cpp           |  2 +-
 src/flame/master.cpp                   | 11 ++--
 src/flame/mongodb/_connection_base.cpp |  4 --
 src/flame/mysql/_connection_base.cpp   |  1 -
 src/flame/mysql/_connection_pool.cpp   |  1 -
 src/flame/mysql/client.cpp             |  2 +-
 src/flame/rabbitmq/consumer.cpp        |  2 +-
 src/flame/tcp/server.cpp               |  2 +-
 src/flame/udp/server.cpp               |  2 +-
 src/flame/worker.cpp                   | 91 ++++++++++++++++++++++----
 src/flame/worker.h                     |  9 +++
 src/ipc.cpp                            | 37 +++++------
 src/ipc.h                              |  9 +--
 src/master_logger.cpp                  | 18 ++---
 src/master_process_manager.cpp         | 27 ++++----
 src/master_process_manager.h           |  8 ++-
 src/worker_ipc.cpp                     | 34 ++++++----
 src/worker_logger.cpp                  |  3 +-
 24 files changed, 230 insertions(+), 116 deletions(-)

diff --git a/doc/core.php b/doc/core.php
index 387734d..b6bde23 100644
--- a/doc/core.php
+++ b/doc/core.php
@@ -4,18 +4,19 @@
  * 框架可在两种模式下工作：
  * 
  * 1. 单进程模式：
- *  * 信号 SIGQUIT 将 “通知” 进程退出 (触发 `quit` 回调)，等待超时后立即终止；
- *  * 信号 SIGINT / SIGTERM 立即终止；
+ *  * 信号 SIGQUIT 立即终止进程；
+ *  * 信号 SIGINT / SIGTERM 将 “通知” 进程退出 (触发 `quit` 回调)，等待超时后立即终止；
  *  * 信号 SIGUSR1 将切换 HTTP 服务器长短连状态 `Connection: close`；
  *  * 单进程模式**不支持**日志文件输出，固定输出到 STDERR / STDOUT (取决于错误等级）；
  * 
  * 2. 父子进程模式：
  *  * 使用环境变量 FLAME_MAX_WORKERS=X 启动父子进程模式；
+ *  * 子进程存在环境变量 FLAME_CUR_WORKER=N 标识该进程标号，1 ~ FLAME_MAX_WORKERS
  *  * 主进程将自动进行日志文件写入，子进程自动拉起；
  *  * 端口监听使用 SO_REUSEPORT 在多个工作进程间共享，但不排除外部监听的可能；
  * 
- *  * 向主进程发送 SIGQUIT 将 ”通知“ 子进程退出 (触发 `quit` 回调)，并等待超时后立即终止；
- *  * 项主进程发送 SIGINT / SIGTERM 将立即终止；
+ *  * 向主进程发送 SIGINT / SIGQUIT 将立即终止进程；
+ *  * 项主进程发送 SIGTERM 将 ”通知“ 子进程退出 (触发 `quit` 回调)，并等待超时后立即终止；
  *  * 向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
  *  * 子进程继承主进程的 环境变量 / -c /path/to/php.ini 配置（通过命令行 -d XXX=XXX 的临时设置无效）；
  *  * 向主进程发送 SIGUSR1 信号将切换 HTTP 服务器长短连状态 `Connection: close`；
@@ -87,13 +88,33 @@ function select(queue $q1, $q2/*, ...*/):queue {
     return new queue();
 }
 /**
- * 监听框架的通知
+ * 向指定工作进程发送消息通知
+ * @param int $target_worker 目标工作进程编号，1 ~ FLAME_MAX_WORKERS；当前进程可读取环境变量 FLAME_CUR_WORKER 获取编号；
+ * @param mixed $data 消息数据，自动进行 JSON 序列化传输；
+ * 注意：
+ *   对象类型数据进行 JSON 序列化可能丢失数据细节而无法还原；
+ *   目标进程将会收到 `message`（自动 JSON 反序列化数据）回调;
+ *   @see flame\on("message", $cb);
+ */
+function send(int $target_worker, $data) {
+
+}
+/**
+ * 为指定事件添加处理回调（函数）
  * @param string $event 目前消息存在以下两种:
  *  * "exception" - 当协程发生未捕获异常, 执行对应的回调，并记录错误信息（随后进程会退出），用户可在此回调进行错误报告或报警;
  *  * "quit" - 退出消息, 用户可在此回调停止各种服务，如停止 HTTP 服务器 / 关闭 MySQL 连接等;
+ *  * "message" - 消息通知，（启动内部协程）接收来自各子进程之间的相互通讯数据；回调函数接收一个不限类型参数；
+ * @see flame\send()
  * @param callable 回调函数
  */
 function on(string $event, callable $cb) {}
+/**
+ * 删除指定事件的所有处理回调（函数）
+ * 注意：
+ *  由于 `message` 事件内部启动协程，阻止了程序的停止；须取消对应回调，使该内部协程停止后，进程才可以正常退出；
+ */
+function off(string $event) {}
 /**
  * 用于在用户处理流程中退出
  * 注意：
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
index b6deb0c..6bf6826 100644
--- a/src/coroutine_queue.h
+++ b/src/coroutine_queue.h
@@ -35,6 +35,7 @@ class coroutine_queue: private boost::noncopyable {
             ch->resume();
         }
     }
+
     std::optional<T> pop(coroutine_handler& ch) {
         for(;;) {
             if (!q_.empty()) break; // 有数据消费
diff --git a/src/flame/controller.cpp b/src/flame/controller.cpp
index c6d9162..18b2821 100644
--- a/src/flame/controller.cpp
+++ b/src/flame/controller.cpp
@@ -11,7 +11,7 @@ namespace flame {
     : type(process_type::UNKNOWN)
     , env(boost::this_process::environment())
     , status(STATUS_UNKNOWN)
-    , user_cb(new std::multimap<std::string, php::callable>()) {
+    , evnt_cb(new std::multimap<std::string, php::callable>()) {
 
         worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
         worker_size = std::min(std::max((int)worker_size, 0), 256);
@@ -33,31 +33,41 @@ namespace flame {
         return this;
     }
 
+    void controller::init(php::array options) {
+        for (auto fn : init_cb) fn(options);
+    }
+
     controller* controller::on_stop(std::function<void ()> fn) {
         stop_cb.push_back(fn);
         return this;
     }
 
-    controller* controller::on_user(const std::string& event, php::callable cb) {
-        user_cb->insert({event, cb});
+    void controller::stop() {
+        for (auto fn : stop_cb) fn();
+        delete evnt_cb;
+    }
+
+    controller* controller::add_event(const std::string& event, php::callable cb) {
+        evnt_cb->insert({event, cb});
         return this;
     }
 
-    void controller::init(php::array options) {
-        for (auto fn : init_cb) fn(options);
+    controller* controller::del_event(const std::string& event) {
+        evnt_cb->erase(event);
+        return this;
     }
-    void controller::stop() {
-        for (auto fn : stop_cb) fn();
-        delete user_cb;
+
+    std::size_t controller::cnt_event(const std::string& event) {
+        return evnt_cb->count(event);
     }
 
-    void controller::call_user_cb(const std::string& event, std::vector<php::value> params) {
-        auto ft = user_cb->equal_range(event);
+    void controller::event(const std::string& event, std::vector<php::value> params) {
+        auto ft = evnt_cb->equal_range(event);
         for(auto i=ft.first; i!=ft.second; ++i) i->second.call(params);
     }
 
-    void controller::call_user_cb(const std::string& event) {
-        auto ft = user_cb->equal_range(event);
+    void controller::event(const std::string& event) {
+        auto ft = evnt_cb->equal_range(event);
         for(auto i=ft.first; i!=ft.second; ++i) i->second.call();
     }
 }
diff --git a/src/flame/controller.h b/src/flame/controller.h
index 0bdbbb6..2b9aa8d 100644
--- a/src/flame/controller.h
+++ b/src/flame/controller.h
@@ -32,17 +32,19 @@ namespace flame {
     private:
         std::list<std::function<void (const php::array& options)>>  init_cb;
         std::list<std::function<void ()>>                           stop_cb;
-        std::multimap<std::string, php::callable>*                  user_cb; // 防止 PHP 提前回收, 使用堆容器
+        std::multimap<std::string, php::callable>*                  evnt_cb; // 防止 PHP 提前回收, 使用堆容器
     public:
         controller();
         controller(const controller& c) = delete;
-        void init(php::array options);
-        void stop();
         controller *on_init(std::function<void(const php::array &options)> fn);
+        void init(php::array options);
         controller* on_stop(std::function<void ()> fn);
-        controller* on_user(const std::string& event, php::callable cb);
-        void call_user_cb(const std::string& event, std::vector<php::value> params);
-        void call_user_cb(const std::string& event);
+        void stop();
+        controller* add_event(const std::string& event, php::callable cb);
+        controller* del_event(const std::string& event);
+        std::size_t cnt_event(const std::string& event);
+        void event(const std::string& event, std::vector<php::value> params);
+        void event(const std::string& event);
     };
 
     extern std::unique_ptr<controller> gcontroller;
diff --git a/src/flame/coroutine.cpp b/src/flame/coroutine.cpp
index a582b87..334d012 100644
--- a/src/flame/coroutine.cpp
+++ b/src/flame/coroutine.cpp
@@ -70,6 +70,7 @@ namespace flame {
                 coroutine::current.reset();
                 zend_vm_stack_destroy();
                 coroutine_php_restore_context(coroutine::gctx_);
+                
                 --coroutine::count;
                 
                 return std::move(co->c2_);
diff --git a/src/flame/http/_handler.cpp b/src/flame/http/_handler.cpp
index b930cf7..319842e 100644
--- a/src/flame/http/_handler.cpp
+++ b/src/flame/http/_handler.cpp
@@ -90,7 +90,7 @@ namespace flame::http {
                 // Chunked 未结束 -> server_response::__destruct() -> finish()
             }*/
         } catch(const php::exception& ex) {
-            gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
+            gcontroller->event("exception", {ex}); // 调用用户异常回调
             // 记录错误信息
             php::object obj = ex;
             log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << std::endl;
diff --git a/src/flame/kafka/consumer.cpp b/src/flame/kafka/consumer.cpp
index 95eeac7..026e878 100644
--- a/src/flame/kafka/consumer.cpp
+++ b/src/flame/kafka/consumer.cpp
@@ -45,7 +45,7 @@ namespace flame::kafka {
                         else break;
                     } catch(const php::exception& ex) {
                         // 调用用户异常回调
-                        gcontroller->call_user_cb("exception", {ex});
+                        gcontroller->event("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
                         log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << std::endl;
diff --git a/src/flame/master.cpp b/src/flame/master.cpp
index bf6afc4..dafdd14 100644
--- a/src/flame/master.cpp
+++ b/src/flame/master.cpp
@@ -10,10 +10,10 @@ namespace flame {
 
     void master::declare(php::extension_entry& ext) {
         ext
-        .function<master::init>("flame\\init")
-        .function<master::dummy>("flame\\go")
-        .function<master::dummy>("flame\\on")
-        .function<master::run>("flame\\run");
+            .function<master::init>("flame\\init")
+            .function<master::dummy>("flame\\go")
+            .function<master::dummy>("flame\\on")
+            .function<master::run>("flame\\run");
     }
 
     php::value master::init(php::parameters& params) {
@@ -36,7 +36,7 @@ namespace flame {
             master::mm_->lg_ = master::mm_->lm_connect({logger.data(), logger.size()});
         }
         else
-            master::mm_->lg_ = master::mm_->lm_connect("stdout");
+            master::mm_->lg_ = master::mm_->lm_connect("<clog>");
         // 初始化启动
         gcontroller->init(options);
         master::mm_->ipc_start(); // IPC 启动
@@ -59,7 +59,6 @@ namespace flame {
             gcontroller->context_z.run();
         });
         gcontroller->context_x.run();
-
         master::mm_->sw_close();
         master::mm_->ipc_close();
         master::mm_->lm_close();
diff --git a/src/flame/mongodb/_connection_base.cpp b/src/flame/mongodb/_connection_base.cpp
index a4c47d3..a453af9 100644
--- a/src/flame/mongodb/_connection_base.cpp
+++ b/src/flame/mongodb/_connection_base.cpp
@@ -63,10 +63,6 @@ namespace flame::mongodb {
             ch.resume();
         });
         ch.suspend();
-        // size_t size;
-        // char * json = bson_as_relaxed_extended_json(rep.get(), &size);
-        // std::cout << "REPLY: (" << size << ") " << json << "\n";
-        // bson_free(json);
         if (!rok) throw php::exception(zend_ce_exception
             , (boost::format("Failed to execute MongoDB command: %s") % err->message).str()
             , err->code);
diff --git a/src/flame/mysql/_connection_base.cpp b/src/flame/mysql/_connection_base.cpp
index 5cfd4b3..c313f1c 100644
--- a/src/flame/mysql/_connection_base.cpp
+++ b/src/flame/mysql/_connection_base.cpp
@@ -101,7 +101,6 @@ namespace flame::mysql {
             ch.resume();
         });
         ch.suspend();
-        // std::cout << "last_query: " << sql << ": " << err << "\n";
         last_query_ = sql;
         if (err != 0) {
             int err = mysql_errno(conn.get());
diff --git a/src/flame/mysql/_connection_pool.cpp b/src/flame/mysql/_connection_pool.cpp
index 842f40a..d9d94ae 100644
--- a/src/flame/mysql/_connection_pool.cpp
+++ b/src/flame/mysql/_connection_pool.cpp
@@ -55,7 +55,6 @@ namespace flame::mysql {
             else {
                 errnum = mysql_errno(c);
                 errmsg = mysql_error(c);
-                // std::cout << "errnum: " << errnum << " errmsg: " << errmsg << "\n"; 
                 mysql_close(c);
                 await_.pop_back();
                 ch.resume();
diff --git a/src/flame/mysql/client.cpp b/src/flame/mysql/client.cpp
index b8119e1..f878fbc 100644
--- a/src/flame/mysql/client.cpp
+++ b/src/flame/mysql/client.cpp
@@ -64,7 +64,7 @@ namespace flame::mysql {
     }
 
     php::value client::__destruct(php::parameters &params) {
-        // std::cout << "client destruct\n";
+        // std::cout << "~client" << std::endl;
         return nullptr;
     }
 
diff --git a/src/flame/rabbitmq/consumer.cpp b/src/flame/rabbitmq/consumer.cpp
index 082de06..3564755 100644
--- a/src/flame/rabbitmq/consumer.cpp
+++ b/src/flame/rabbitmq/consumer.cpp
@@ -51,7 +51,7 @@ namespace flame::rabbitmq  {
                         cb_.call( {x.value()} );
                     } catch(const php::exception& ex) {
                         // 调用用户异常回调
-                        gcontroller->call_user_cb("exception", {ex});
+                        gcontroller->event("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
                         log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << std::endl;
diff --git a/src/flame/tcp/server.cpp b/src/flame/tcp/server.cpp
index 52b1df6..7253510 100644
--- a/src/flame/tcp/server.cpp
+++ b/src/flame/tcp/server.cpp
@@ -85,7 +85,7 @@ namespace flame::tcp {
                         cb.call({obj});
                     } catch(const php::exception& ex) {
                         // 调用用户异常回调
-                        gcontroller->call_user_cb("exception", {ex});
+                        gcontroller->event("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
                         log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << std::endl;
diff --git a/src/flame/udp/server.cpp b/src/flame/udp/server.cpp
index 7661ad1..945ff3a 100644
--- a/src/flame/udp/server.cpp
+++ b/src/flame/udp/server.cpp
@@ -81,7 +81,7 @@ namespace flame::udp {
                         cb_.call({x->first, x->second});
                     } catch(const php::exception& ex) {
                         // 调用用户异常回调
-                        gcontroller->call_user_cb("exception", {ex});
+                        gcontroller->event("exception", {ex});
                         // 记录错误信息
                         php::object obj = ex;
                         log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << std::endl;
diff --git a/src/flame/worker.cpp b/src/flame/worker.cpp
index 345bc80..018c1ae 100644
--- a/src/flame/worker.cpp
+++ b/src/flame/worker.cpp
@@ -51,6 +51,9 @@ namespace flame {
             {"event", php::TYPE::STRING},
             {"callback", php::TYPE::CALLABLE},
         })
+        .function<worker::off>("flame\\off", {
+            {"event", php::TYPE::STRING},
+        })
         .function<worker::run>("flame\\run")
         .function<worker::quit>("flame\\quit")
         .function<worker::co_id>("flame\\co_id")
@@ -64,6 +67,10 @@ namespace flame {
         .function<worker::get>("flame\\get", {
             {"target", php::TYPE::ARRAY},
             {"fields", php::TYPE::STRING},
+        })
+        .function<worker::send>("flame\\send", {
+            {"target", php::TYPE::INTEGER},
+            {"data", php::TYPE::UNDEFINED},
         });
         // 顶级命名空间
         flame::mutex::declare(ext);
@@ -109,7 +116,7 @@ namespace flame {
         // 信号监听启动
         worker::ww_->sw_watch();
         if(gcontroller->worker_size > 0) worker::ww_->ipc_start();
-        
+
         gcontroller->init(options); // 首个 logger 的初始化过程在 logger 注册 on_init 回调中进行（异步的）
         return nullptr;
     }
@@ -122,7 +129,7 @@ namespace flame {
             try { // 函数调用产生了堆栈过程，可以捕获异常
                 fn.call(); 
             } catch (const php::exception &ex) {
-                gcontroller->call_user_cb("exception", {ex}); // 调用用户异常回调
+                gcontroller->event("exception", {ex}); // 调用用户异常回调
                 php::object obj = ex; // 记录错误信息
                 log::logger_->stream() << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << std::endl;
 
@@ -141,12 +148,30 @@ namespace flame {
     }
 
     php::value worker::on(php::parameters &params) {
+
         if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
             throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
 
         std::string event = params[0].to_string();
         if (!params[1].type_of(php::TYPE::CALLABLE)) throw php::exception(zend_ce_type_error, "Failed to set callback: callable required", -1);
-        gcontroller->on_user(event, params[1]);
+        // message 事件需要启动协程辅助，故需要对应的停止机制
+        if(event == "message" && gcontroller->cnt_event(event) == 0) {
+            worker::ww_->msg_start();
+        }
+        gcontroller->add_event(event, params[1]);
+        return nullptr;
+    }
+
+    php::value worker::off(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        std::string event = params[0].to_string();
+        gcontroller->del_event(event);
+        // message 事件启动了额外的协程，需要停止
+        if(event == "message"/*&& gcontroller->cnt_event(event) == 0 */) {
+            worker::ww_->msg_close();
+        }
         return nullptr;
     }
 
@@ -156,7 +181,8 @@ namespace flame {
 
         gcontroller->status |= controller::STATUS_RUN;
 
-        auto tswork = boost::asio::make_work_guard(gcontroller->context_y);
+        auto ywork = boost::asio::make_work_guard(gcontroller->context_y);
+        auto zwork = boost::asio::make_work_guard(gcontroller->context_z);
         std::thread ts[4];
         for (int i=0; i<3; ++i) {
             ts[i] = std::thread([] {
@@ -167,11 +193,16 @@ namespace flame {
             gcontroller->context_z.run();
         });
         gcontroller->context_x.run();
-        tswork.reset();
+        ywork.reset();
+        zwork.reset();
+
         worker::ww_->sw_close();
         worker::ww_->ipc_close();
         worker::ww_->lm_close();
-        for (int i=0; i<4; ++i) ts[i].join();
+
+        for (int i=0; i<4; ++i) {
+            ts[i].join();
+        }
         gcontroller->stop();
         worker::ww_.reset();
         return nullptr;
@@ -209,8 +240,13 @@ namespace flame {
         return nullptr;
     }
 
+    php::value worker::send(php::parameters& params) {
+        worker::ww_->ipc_notify(static_cast<int>(params[0]), params[1]);
+        return nullptr;
+    }
+
     worker::worker(std::uint8_t idx)
-    : signal_watcher(gcontroller->context_y)
+    : signal_watcher(gcontroller->context_z)
     , worker_ipc(gcontroller->context_z, idx)
     , worker_logger_manager(this) {
 
@@ -239,7 +275,7 @@ namespace flame {
         break;
         case SIGTERM:
             coroutine::start(php::callable([] (php::parameters& params) -> php::value { // 通知用户退出(超时后主进程将杀死子进程)
-                gcontroller->call_user_cb("quit");
+                gcontroller->event("quit");
                 return nullptr;
             }));
         case SIGUSR1:
@@ -255,14 +291,43 @@ namespace flame {
     // !!! 此函数在工作线程中工作
     bool worker::on_message(std::shared_ptr<ipc::message_t> msg) {
         switch(msg->command) {
-        case ipc::COMMAND_NOTIFY_DATA:
-            boost::asio::post(gcontroller->context_x, [msg] () {
-                std::cout << "notfiy data: " << std::string_view {&msg->payload[0], msg->length} << "\n";
-            // TODO 调度到主线程，通过协程队列传递
-            // gcontroller->call_user_cb("notify", {php::json_decode(&msg->payload[0], msg->length)});
+        case ipc::COMMAND_MESSAGE_STRING:
+            boost::asio::post(gcontroller->context_x, [this, self = worker::ww_, msg] () {
+                msgq_.push_back(php::string(&msg->payload[0], msg->length));
+                if(msgq_.size() == 1 && msgc_) msgc_.resume();
+            });
+        break;
+        case ipc::COMMAND_MESSAGE_JSON: // !!! json_decode('"abc"') === null !!!
+            boost::asio::post(gcontroller->context_x, [this, self = worker::ww_, msg] () {
+                msgq_.push_back(php::json_decode(&msg->payload[0], msg->length));
+                if(msgq_.size() == 1 && msgc_) msgc_.resume();
             });
         break;
         }
         return true;
     }
+
+    void worker::msg_start() {
+        coroutine::start(php::callable([this, self = worker::ww_] (php::parameters& params) -> php::value {
+            coroutine_handler ch {coroutine::current};
+            while(true) {
+                while(msgq_.empty()) {
+                    msgc_.reset(ch);
+                    ch.suspend();
+                    msgc_.reset();
+                }
+                php::value v = msgq_.front();
+                msgq_.pop_front();
+
+                if(v.type_of(php::TYPE::UNDEFINED)) break;
+                gcontroller->event("message", {v});
+            }
+            return nullptr;
+        }));
+    }
+
+    void worker::msg_close() {
+        msgq_.push_back(php::value());
+        if(msgq_.size() == 1 && msgc_) msgc_.resume();
+    }
 }
\ No newline at end of file
diff --git a/src/flame/worker.h b/src/flame/worker.h
index dcabfa1..2ff56f1 100644
--- a/src/flame/worker.h
+++ b/src/flame/worker.h
@@ -3,6 +3,7 @@
 #include "../worker_logger_manager.h"
 #include "../signal_watcher.h"
 #include "../worker_ipc.h"
+#include "../coroutine_queue.h"
 
 namespace flame {
     class worker: public std::enable_shared_from_this<worker>, public signal_watcher, public worker_ipc, public worker_logger_manager {
@@ -15,6 +16,7 @@ namespace flame {
         static php::value init(php::parameters& params);
         static php::value go(php::parameters& params);
         static php::value on(php::parameters &params);
+        static php::value off(php::parameters& params);
         static php::value run(php::parameters& params);
         static php::value quit(php::parameters& params);
         // 协程相关
@@ -23,6 +25,8 @@ namespace flame {
         // 级联数组设置、读取
         static php::value get(php::parameters& params);
         static php::value set(php::parameters& params);
+        // 传递消息到其他工作进程
+        static php::value send(php::parameters& params);
         
         worker(std::uint8_t idx);
         std::ostream& output() override;
@@ -40,5 +44,10 @@ namespace flame {
         bool on_message(std::shared_ptr<ipc::message_t> msg) override;
     private:
         static std::shared_ptr<worker> ww_;
+        std::list<php::value> msgq_; // notify queue
+        coroutine_handler     msgc_; // notify coroutine
+
+        void msg_start();
+        void msg_close();
     };
 }
\ No newline at end of file
diff --git a/src/ipc.cpp b/src/ipc.cpp
index 43715df..da656a5 100644
--- a/src/ipc.cpp
+++ b/src/ipc.cpp
@@ -2,35 +2,30 @@
 
 static boost::pool<> pool_(ipc::MESSAGE_INIT_CAPACITY);
 
-ipc::message_t* ipc::malloc_message(std::uint16_t length) {
-    ipc::message_t* msg;
+static void free_message(ipc::message_t* msg) {
+    pool_.free(msg);
+}
+
+std::shared_ptr<ipc::message_t> ipc::create_message(std::uint16_t length) {
     if(length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
-        length = (length + sizeof(ipc::message_t) + 4096 - 1) & ~(4096-1);
-        msg = (ipc::message_t*)malloc(length);
-        msg->length = length - sizeof(ipc::message_t);
+        ipc::message_t* msg = (ipc::message_t*)pool_.malloc();
+        msg->length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t);
+        return std::shared_ptr<ipc::message_t>(msg, free_message);
     }
     else {
-        msg = (ipc::message_t*)malloc(ipc::MESSAGE_INIT_CAPACITY);
-        msg->length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t);
+        length = (sizeof(ipc::message_t) + length + 4096 - 1) & ~(4096-1);
+        ipc::message_t* msg = (ipc::message_t*)malloc(length);
+        msg->length = length - sizeof(ipc::message_t);
+        return std::shared_ptr<ipc::message_t>(msg, free);
     }
-    return msg;
-}
-
-void ipc::free_message(ipc::message_t* msg) {
-    if(msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) 
-        free(msg);
-    else
-        pool_.free(msg);
-}
-
-std::shared_ptr<ipc::message_t> ipc::create_message() {
-    return std::shared_ptr<ipc::message_t>(ipc::malloc_message(), ipc::free_message);
 }
 
 void ipc::relloc_message(std::shared_ptr<ipc::message_t>& msg, std::uint16_t copy, std::uint16_t length) {
-    ipc::message_t* m = malloc_message(length);
+    length = (sizeof(ipc::message_t) + length + 4096 - 1) & ~(4096-1);
+
+    ipc::message_t* m = (ipc::message_t*)malloc(length);
     std::memcpy(m, msg.get(), sizeof(ipc::message_t) + copy);
-    msg.reset(m, free_message);
+    msg.reset(m, free);
 }
 
 std::uint32_t ipc::create_uid() {
diff --git a/src/ipc.h b/src/ipc.h
index cf4227a..c971ff3 100644
--- a/src/ipc.h
+++ b/src/ipc.h
@@ -24,18 +24,15 @@ class ipc {
         COMMAND_LOGGER_DATA    = 0x04,
 
         COMMAND_TRANSFER_TO_CHILD = 0x10,
-        COMMAND_NOTIFY_DATA    = 0x10,
+        COMMAND_MESSAGE_STRING = 0x10,
+        COMMAND_MESSAGE_JSON   = 0x11,
     };
     struct callback_t {
         coroutine_handler&               cch;
         std::shared_ptr<ipc::message_t>& res;
     };
     // 消息容器构建
-    static message_t* malloc_message(std::uint16_t length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t));
-    // 消息容器释放
-    static void free_message(message_t* msg);
-    // 消息容器构建
-    static std::shared_ptr<message_t> create_message();
+    static std::shared_ptr<message_t> create_message(std::uint16_t length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t));
     // 消息容器长度调整
     static void relloc_message(std::shared_ptr<ipc::message_t>& msg, std::uint16_t copy, std::uint16_t length);
     // 生成消息ID
diff --git a/src/master_logger.cpp b/src/master_logger.cpp
index 356ae5b..528174c 100644
--- a/src/master_logger.cpp
+++ b/src/master_logger.cpp
@@ -3,17 +3,19 @@
 #include "util.h"
 
 void master_logger::reload(boost::asio::io_context& io) {
+    oss_.reset(&std::clog, boost::null_deleter());
+
     if(path_.string() != "<clog>") {
         auto fb = new master_logger_buffer(io, path_);
         // fb->open(path_, std::ios_base::app);
-        ssb_.reset(fb);
-        oss_.reset(new std::ostream(fb));
-
-        fb->persync(); // 启动周期性文件缓冲刷新服务
-    }
-    if (oss_->fail()) { // 文件打开失败时不会抛出异常，需要额外的状态检查
-        std::cerr << "[" << util::system_time() << "] (WARNING) Failed to access logger file, fallback to 'clog'\n";
-        oss_.reset(&std::clog, boost::null_deleter());
+        if(fb->is_open()) {
+            ssb_.reset(fb);
+            oss_.reset(new std::ostream(fb));
+            fb->persync(); // 启动周期性文件缓冲刷新服务
+        }
+        else { // 文件打开失败时不会抛出异常，需要额外的状态检查
+            std::cerr << "[" << util::system_time() << "] (WARNING) Failed to access / create logger file, fallback to 'clog'" << std::endl;
+        }
     }
 }
 
diff --git a/src/master_process_manager.cpp b/src/master_process_manager.cpp
index b381f68..633d31f 100644
--- a/src/master_process_manager.cpp
+++ b/src/master_process_manager.cpp
@@ -5,8 +5,9 @@
 
 master_process_manager::master_process_manager(boost::asio::io_context& io, unsigned int count, unsigned int close)
 : io_(io)
-, count_(count)
-, close_(close)
+, cmax_(count)
+, crun_(0)
+, tquit_(close)
 , child_(count)
 , timer_(io)
 , status_(0) {
@@ -21,7 +22,7 @@ void master_process_manager::pm_start(boost::asio::io_context& io) {
     // 这个 work_guard 为了使得主线程在子进程未全部退出前保持运行
     // 实际主线程并没有工作量
     work_.reset(new boost::asio::io_context::work(io));
-    for(int i=0;i<count_;++i) {
+    for(int i=0;i<cmax_;++i) {
         child_[i].reset(new master_process(io_, this, i));
     }
 }
@@ -29,8 +30,8 @@ void master_process_manager::pm_start(boost::asio::io_context& io) {
 void master_process_manager::pm_reset(coroutine_handler& ch) {
     if(status_ & (STATUS_RSETING | STATUS_ACLOSED)) return;
 
-    std::vector<std::unique_ptr<master_process>> start(count_);
-    for(int i=0;i<count_;++i) {
+    std::vector<std::unique_ptr<master_process>> start(cmax_);
+    for(int i=0;i<cmax_;++i) {
         start[i].reset(new master_process(io_, this, i));
         ch_start_.reset(ch);
         ch_start_.suspend(); // 等待进程启动回调 on_child_event
@@ -38,7 +39,7 @@ void master_process_manager::pm_reset(coroutine_handler& ch) {
         // 启动间隔
         util::co_sleep(io_, std::chrono::milliseconds(140), ch);
         // 新进程已经启动 1/4 后，开始停止原进程（一次性动作）
-        if(i == count_ / 4) coroutine::start(io_.get_executor(), [this] (coroutine_handler ch) {
+        if(i == cmax_ / 4) coroutine::start(io_.get_executor(), [this] (coroutine_handler ch) {
             pm_close(ch);
         });
     }
@@ -53,16 +54,16 @@ void master_process_manager::pm_close(coroutine_handler& ch, bool now) {
         return;
     }
     status_ |= STATUS_CLOSING;
-    for(int i=0;i<count_;++i) child_[i]->close(now);
+    for(int i=0;i<cmax_;++i) child_[i]->close(now);
     if (now) {
         status_ |= STATUS_ACLOSED;
         timer_.cancel(); // 可能有异常重启中的进程存在
         goto SHUTDOWN;
     }
     {
-        int  stopped = count_;
+        int  stopped = cmax_;
         bool timeout = false;
-        timer_.expires_after(std::chrono::milliseconds(close_));
+        timer_.expires_after(std::chrono::milliseconds(tquit_));
         timer_.async_wait([&ch, &timeout, &stopped, this, self = pm_self()] (const boost::system::error_code& error) mutable {
             if(status_ & STATUS_ACLOSED) return;  // (2) 结束后栈数据丢失（ch/timeout/stopped)
             timeout = true; // (3) 还未完全关闭时超时或提前强制关闭
@@ -82,14 +83,16 @@ void master_process_manager::pm_close(coroutine_handler& ch, bool now) {
 }
 
 void master_process_manager::pm_kills(int sig) {
-    for(int i=0;i<count_;++i) child_[i]->signal(sig);
+    for(int i=0;i<cmax_;++i) child_[i]->signal(sig);
 }
 
 void master_process_manager::on_child_start(master_process* w) {
+    ++crun_;
     if (ch_start_) ch_start_.resume(); // 陆续起停流程
 }
 
 void master_process_manager::on_child_close(master_process* w, bool normal) {
+    --crun_;
     if (!(status_ & STATUS_CLOSING) && !normal) {
         unsigned int rand = std::rand() % 2000 + 1000;
         output() << "[" << util::system_time() << "] (WARNING) unexpected worker process stopping, restart in " << int(rand/1000) << "s ...\n";
@@ -99,5 +102,7 @@ void master_process_manager::on_child_close(master_process* w, bool normal) {
             child_[idx].reset(new master_process(io_, this, idx));
         });
     }
-    else if (ch_close_) ch_close_.resume(); // 陆续起停流程
+    if (ch_close_) ch_close_.resume(); // 陆续起停流程
+    if (crun_ == 0 && normal) work_.reset(); // 正常退出了最后一个进程
+    // 这里不太严格：三个进程 1、2 异常退出，3正常退出时也结束了程序
 }
diff --git a/src/master_process_manager.h b/src/master_process_manager.h
index 8a675dc..9bac11a 100644
--- a/src/master_process_manager.h
+++ b/src/master_process_manager.h
@@ -21,7 +21,7 @@ class master_process_manager {
     void pm_kills(int sig);
     // 进程数量
     std::uint8_t pm_count() {
-        return count_;
+        return cmax_;
     }
 protected:
     // 进程启动回调
@@ -31,8 +31,10 @@ class master_process_manager {
 private:
     std::unique_ptr<boost::asio::io_context::work> work_;
     boost::asio::io_context&                        io_;
-    unsigned int                                 count_;
-    unsigned int                                 close_;
+    unsigned int                                 cmax_;
+    unsigned int                                 crun_;
+    unsigned int                                 tquit_;
+
     std::vector<std::unique_ptr<master_process>> child_;
     boost::asio::steady_timer                    timer_;
     coroutine_handler ch_close_;
diff --git a/src/worker_ipc.cpp b/src/worker_ipc.cpp
index ca198b8..28e2cfb 100644
--- a/src/worker_ipc.cpp
+++ b/src/worker_ipc.cpp
@@ -27,23 +27,35 @@ void worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> req) {
     send(req);
 }
 
-static void json_message_free(ipc::message_t* msg) {
+static void free_json_message(ipc::message_t* msg) {
     zend_string* ptr = reinterpret_cast<zend_string*>((char*)(msg) - offsetof(zend_string, val));
     smart_str str { ptr, 0 };
     smart_str_free(&str);
 }
 
 void worker_ipc::ipc_notify(std::uint8_t target, php::value data) {
-    smart_str str; // !!!! 避免 JSON 文本的 COPY 复制
-    smart_str_alloc(&str, 1, false);
-    ipc::message_t* msg = (ipc::message_t*)str.s->val;
-    msg->command = ipc::COMMAND_NOTIFY_DATA;
-    msg->source  = idx_;
-    msg->target  = target;
-    str.s->len = sizeof(ipc::message_t);
-    php::json_encode_to(&str, data);
-    msg->length = str.s->len - sizeof(ipc::message_t);
-    send(std::shared_ptr<ipc::message_t>{msg, ipc::free_message});
+    if(data.type_of(php::TYPE::STRING)) {
+        php::string str = data;
+        auto msg = ipc::create_message(str.size());
+        msg->command = ipc::COMMAND_MESSAGE_STRING;
+        msg->source  = idx_;
+        msg->target  = target;
+        std::memcpy(&msg->payload[0], str.data(), str.size());
+        msg->length = str.size();
+        send(msg);
+    }
+    else {
+        smart_str str {nullptr, 0}; // !!!! 避免 JSON 文本的 COPY 复制
+        smart_str_alloc(&str, 1, false);
+        ipc::message_t* msg = (ipc::message_t*)str.s->val;
+        msg->command = ipc::COMMAND_MESSAGE_JSON;
+        msg->source  = idx_;
+        msg->target  = target;
+        str.s->len = sizeof(ipc::message_t);
+        php::json_encode_to(&str, data);
+        msg->length = str.s->len - sizeof(ipc::message_t);
+        send(std::shared_ptr<ipc::message_t>(msg, free_json_message));
+    }
 }
 
 void worker_ipc::ipc_start() {
diff --git a/src/worker_logger.cpp b/src/worker_logger.cpp
index 11e4a14..9794049 100644
--- a/src/worker_logger.cpp
+++ b/src/worker_logger.cpp
@@ -7,13 +7,12 @@ worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::
 , path_(path)
 , wlb_(new worker_logger_buffer(mgr, idx))
 , oss_(new std::ostream(wlb_.get())) {
-    // std::cout << "worker_logger:: ipc(" << path << ")" << std::endl;
+    
 }
 
 worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx, bool local)
 : idx_(0)
 , path_(path) {
-    // std::cout << "worker_logger:: local(" << path << ")" << std::endl;
     if(path.string() != "<clog>") {
         auto fb = new std::filebuf();
         fb->open(path, std::ios_base::app);
