From d21f67dd2fa4830dc4cc39db7a93430b978e05aa Mon Sep 17 00:00:00 2001 From: terrywh 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(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 gcontroller; +// 当前协程 +// std::shared_ptr 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 fn) { + // before_.push_back(fn); + // return this; + // } + // controller* controller::after(std::function 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(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(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;iwrite(boost::format(" %3% [%1%] (INFO) master receives signal '%2%', exiting ...") % time::datetime() % sig); + // signal_ = sig; + // for(int i=0; i 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 worker_; + boost::process::group group_; + boost::asio::signal_set signal_; + + }; + class controller_worker { + public: + controller_worker(); + void run(); + + private: + std::vector 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 master_; + // 工作进程对象 + std::unique_ptr worker_; + + // 公共 + std::list> init_cb; + std::list> stop_cb; + public: + controller(); + controller(const controller& c) = delete; + void initialize(); + void run(); + controller* on_init(std::function fn); + controller* on_stop(std::function fn); + }; + + extern std::unique_ptr 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("flame\\init", { + {"process_name", php::TYPE::STRING}, + }) + .function("flame\\go", { + {"coroutine", php::TYPE::CALLABLE}, + }) + .function("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(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 { + 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 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 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(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("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 +#include +#include +#include +#include + +#include +#include +#include // for std::functional +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using boost::asio::ip::tcp; +using boost::asio::ip::udp; +namespace ssl = boost::asio::ssl; +#include +#include +#include +#include +#include +#include +#include +#include +#include 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 @@ + 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 @@ +(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("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(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 { + class coroutine : public std::enable_shared_from_this + { 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 conn, php::buffer &b, const php::value &v, char quote) + { + switch (Z_TYPE_P(static_cast(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 c, php::buffer &b, const php::value &v, char quote = '\''); // 方便使用 + virtual std::shared_ptr 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 c) + : conn_(c) + { + + } + _connection_lock::~_connection_lock() + { + } + std::shared_ptr _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 c); + ~_connection_lock(); + std::shared_ptr acquire(coroutine_handler& ch) override; + private: + std::shared_ptr 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 _connection_pool::acquire(coroutine_handler& ch) + { + std::shared_ptr conn; + // 提交异步任务 + boost::asio::post(guard_, [this, &conn, &ch] () { + // 设置对应的回调, 在获取连接后恢复协程 + await_.push_back([&conn, &ch] (std::shared_ptr 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 c)> cb = await_.front(); + await_.pop_front(); + auto ptr = this->shared_from_this(); + cb( + std::shared_ptr(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 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)>> await_; + struct connection_t + { + MYSQL *conn; + std::chrono::time_point ttl; + }; + std::list 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 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 ¶ms) + { + coroutine_handler ch {coroutine::current}; + std::shared_ptr 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 ¶ms) + { + // std::shared_ptr co = coroutine::current; + // c_->exec([](std::shared_ptr 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 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::entry()); + // transaction *tx_ = static_cast(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 ¶ms) + { + // c_->query(coroutine::current, php::object(this), params[0]); + // return coroutine::async(); + return nullptr; + } + php::value client::insert(php::parameters ¶ms) + { + // 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 ¶ms) + { + // 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 ¶ms) + { + // 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 ¶ms) + { + // 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 ¶ms) + { + // 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 ¶ms) + { + // 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 ¶ms); + php::value escape(php::parameters ¶ms); + php::value begin_tx(php::parameters ¶ms); + // php::value where(php::parameters ¶ms); + // php::value order(php::parameters ¶ms); + // php::value limit(php::parameters ¶ms); + php::value query(php::parameters ¶ms); + php::value insert(php::parameters ¶ms); + php::value delete_(php::parameters ¶ms); + php::value update(php::parameters ¶ms); + php::value select(php::parameters ¶ms); + php::value one(php::parameters ¶ms); + php::value get(php::parameters ¶ms); + + protected: + std::shared_ptr<_connection_pool> cp_; + friend php::value connect(php::parameters ¶ms); + }; +} // 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::entry()); + + client *ptr = static_cast(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("flame\\mysql\\connect", { + {"url", php::TYPE::STRING}, + }); + client::declare(ext); + } + + // 相等 + static void where_eq(std::shared_ptr 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 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 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 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 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 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 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(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 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::max(), max = std::numeric_limits::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 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 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 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 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 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 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(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 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 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 cc, php::buffer &buf, php::parameters ¶ms) + { + 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 cc, php::buffer &buf, php::parameters ¶ms) + { + 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 cc, php::buffer &buf, php::parameters ¶ms) + { + 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 cc, php::buffer &buf, php::parameters ¶ms) + { + 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 cc, php::buffer &buf, php::parameters ¶ms) + { + 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 cc, php::buffer &buf, php::parameters ¶ms) + { + 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 cm, php::buffer &buf, const php::value &data); + void build_order(std::shared_ptr cm, php::buffer &buf, const php::value &data); + void build_limit(std::shared_ptr cm, php::buffer &buf, const php::value &data); + void build_insert(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); + void build_delete(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); + void build_update(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); + void build_select(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); + void build_one(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); + void build_get(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); +} \ 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(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("flame\\time\\sleep", { - {"duration", php::TYPE::INTEGER}, - }); -} +namespace flame::time +{ + + static std::chrono::time_point time_system; + static std::chrono::time_point 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(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 now() + { + std::chrono::milliseconds diff = std::chrono::duration_cast( + 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 ¶ms) + { + return std::chrono::duration_cast(now().time_since_epoch()).count(); + } + static php::value iso(php::parameters ¶ms) + { + return iso(); + } + + void declare(php::extension_entry& ext) + { + time_steady = std::chrono::steady_clock::now(); + time_system = std::chrono::system_clock::now(); + + ext + .function("flame\\time\\sleep", + { + {"duration", php::TYPE::INTEGER}, + }) + // 毫秒时间戳 + .function("flame\\time\\now") + // 标准时间 YYYY-mm-dd HH:ii:ss + .function("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 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 parser_t; + url::url(http://github.com/const%20php::string%20&str) { + const char* s = str.c_str(); + + struct http_parser_url x; + http_parser_parse_url(http://github.com/s,%20str.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 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 #include #include +#include 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 @@ +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 @@ + 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 @@ + 字符集 + * + * @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 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(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 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::entry()); + result* ptr = static_cast(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(mysql_affected_rows(conn.get()))); + obj.set("insert_id", static_cast(mysql_insert_id(conn.get()))); + } + return std::move(obj); + } + + php::array _connection_base::fetch(std::shared_ptr conn, std::shared_ptr 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(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 c, php::buffer &b, const php::value &v, char quote = '\''); // 方便使用 virtual std::shared_ptr acquire(coroutine_handler& ch) = 0; + php::object query(std::shared_ptr conn, std::string sql, coroutine_handler& ch); + php::array fetch(std::shared_ptr conn, std::shared_ptr 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 _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 c); ~_connection_lock(); std::shared_ptr 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 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 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 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 ¶ms) { coroutine_handler ch {coroutine::current}; @@ -74,85 +78,75 @@ namespace flame::mysql } php::value client::begin_tx(php::parameters ¶ms) { - // std::shared_ptr co = coroutine::current; - // c_->exec([](std::shared_ptr 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 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::entry()); - // transaction *tx_ = static_cast(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::entry()); + tx *ptr = static_cast(php::native(obj)); + ptr->cl_ = cl; // 继续持有当前连接 + return std::move(obj); } php::value client::query(php::parameters ¶ms) { - // 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 ¶ms) { - // 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 ¶ms) { - // 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 ¶ms) { - // 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 ¶ms) { - // 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 ¶ms) { - // 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 ¶ms) { - // 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(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::entry()); - - client *ptr = static_cast(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("flame\\mysql\\connect", { + .function("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::entry()); + client *ptr = static_cast(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 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 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 co, const php::object &ref, const php::string &field) + // { + // co->stack(php::value([field](php::parameters ¶ms) -> 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 ¶ms) -> php::value { + // php::object rs = params[0]; + // result *rs_ = static_cast(php::native(rs)); + + // assert(rs.instanceof (php::class_entry::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 co, const php::object &ref, bool fetch_all, php::array &data_all) + // { + // c_->exec([this](std::shared_ptr 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(row); }, [this, co, ref, fetch_all, data_all](std::shared_ptr c, MYSQL_RES *r, int error) mutable { + // MYSQL* conn = c.get(); + // fetch_row_t* row = reinterpret_cast(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 ¶ms) + { + coroutine_handler ch{coroutine::current}; + return cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch); + } + php::value result::fetch_all(php::parameters ¶ms) + { + 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 c, int &error) -> MYSQL_RES * { + // mysql_free_result(r); + // return nullptr; }, [cl](std::shared_ptr 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 ¶ms); + php::value fetch_all(php::parameters ¶ms); + + private: + std::shared_ptr<_connection_lock> cl_; + std::shared_ptr 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 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 ¶ms) + { + return nullptr; + } + php::value tx::escape(php::parameters ¶ms) + { + coroutine_handler ch{coroutine::current}; + std::shared_ptr 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 ¶ms) + { + coroutine_handler ch{coroutine::current}; + cl_->commit(ch); + return nullptr; + } + php::value tx::rollback(php::parameters ¶ms) + { + coroutine_handler ch{coroutine::current}; + cl_->rollback(ch); + return nullptr; + } + php::value tx::query(php::parameters ¶ms) + { + coroutine_handler ch{coroutine::current}; + std::shared_ptr conn = cl_->acquire(ch); + return cl_->query(conn, params[0], ch); + } + php::value tx::insert(php::parameters ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { + 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(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 ¶ms); // 私有 + php::value commit(php::parameters ¶ms); + php::value rollback(php::parameters ¶ms); + + php::value escape(php::parameters ¶ms); + php::value query(php::parameters ¶ms); + + php::value insert(php::parameters ¶ms); + php::value delete_(php::parameters ¶ms); + php::value update(php::parameters ¶ms); + php::value select(php::parameters ¶ms); + php::value one(php::parameters ¶ms); + php::value get(php::parameters ¶ms); + + 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 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(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(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(rp->integer); + case REDIS_REPLY_ARRAY: { + php::array data(rp->elements); + for(int i=0;ielements;++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 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 c) + : conn_(c) + { + } + + _connection_lock::~_connection_lock() + { + } + + std::shared_ptr _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 c); + ~_connection_lock(); + std::shared_ptr 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 conn_; + std::list 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 _connection_pool::acquire(coroutine_handler &ch) + { + std::shared_ptr conn; + // 提交异步任务 + boost::asio::post(guard_, [this, &conn, &ch]() { + // 设置对应的回调, 在获取连接后恢复协程 + await_.push_back([&conn, &ch](std::shared_ptr 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 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 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 c)> cb = await_.front(); + await_.pop_front(); + auto ptr = this->shared_from_this(); + cb( + std::shared_ptr(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 acquire(coroutine_handler &ch) override; + php::value exec(std::shared_ptr rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler &ch); + php::value exec(std::shared_ptr 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)>> await_; + struct connection_t + { + redisContext *conn; + std::chrono::time_point ttl; + }; + std::list 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 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; iexec(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; iexec(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::entry()); + // tx* ptr = static_cast(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 ¶ms); // 私有 + php::value __call(php::parameters ¶ms); + // 处理特殊情况的命令 + php::value mget(php::parameters ¶ms); + php::value hmget(php::parameters ¶ms); + php::value hgetall(php::parameters ¶ms); + php::value hscan(php::parameters ¶ms); + php::value sscan(php::parameters ¶ms); + php::value zscan(php::parameters ¶ms); + php::value zrange(php::parameters ¶ms); + php::value zrevrange(php::parameters ¶ms); + php::value zrangebyscore(php::parameters ¶ms); + php::value zrevrangebyscore(php::parameters ¶ms); + php::value unsubscribe(php::parameters ¶ms); + php::value punsubscribe(php::parameters ¶ms); + // 批量 + php::value multi(php::parameters ¶ms); + // 用于标记不实现的功能 + php::value unimplement(php::parameters ¶ms); + private: + std::shared_ptr<_connection_pool> cp_; + friend php::value connect(php::parameters ¶ms); + }; +} // 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("flame\\redis\\connect", + { + {"uri", php::TYPE::STRING} + }); + client::declare(ext); + // tx::declare(ext); + } + + php::value connect(php::parameters ¶ms) + { + url u(params[0]); + php::object obj {php::class_entry::entry()}; + client *ptr = static_cast(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 ¶ms); +} // 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 #include #include +#include +#include #include #include #include @@ -44,4 +46,3 @@ namespace ssl = boost::asio::ssl; #include #include #include -#include 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 @@ +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 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::entry()); - // tx* ptr = static_cast(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::entry()); + tx *ptr = static_cast(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 ¶ms) 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 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; ipush(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; ipush(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 ¶ms); // 私有 + php::value exec(php::parameters& params); + php::value __call(php::parameters ¶ms); + // 处理特殊情况的命令 + php::value mget(php::parameters ¶ms); + php::value hmget(php::parameters ¶ms); + php::value hgetall(php::parameters ¶ms); + php::value hscan(php::parameters ¶ms); + php::value sscan(php::parameters ¶ms); + php::value zscan(php::parameters ¶ms); + php::value zrange(php::parameters ¶ms); + php::value zrevrange(php::parameters ¶ms); + php::value zrangebyscore(php::parameters ¶ms); + php::value zrevrangebyscore(php::parameters ¶ms); + php::value unsubscribe(php::parameters ¶ms); + php::value punsubscribe(php::parameters ¶ms); + // 批量 + php::value multi(php::parameters ¶ms); + // 用于标记不实现的功能 + php::value unimplement(php::parameters ¶ms); + + 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 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 @@ +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;icontext_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 conn, php::array& pcmd, bool write, coroutine_handler &ch) + { + auto cmd = array2bson(pcmd); + std::shared_ptr rep(bson_new(), bson_destroy); + auto err = std::make_shared(); + 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::entry()}; + auto ptr = static_cast(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(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 acquire(coroutine_handler& ch) = 0; + php::value exec(std::shared_ptr 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 c) + : conn_(c) + { + + } + std::shared_ptr _connection_lock::acquire(coroutine_handler &ch) + { + return conn_; + } + php::array _connection_lock::fetch(std::shared_ptr cs, coroutine_handler &ch) + { + bool has = false; + auto err = std::make_shared(); + 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(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 c); + std::shared_ptr acquire(coroutine_handler &ch) override; + php::array fetch(std::shared_ptr cs, coroutine_handler &ch); + private: + std::shared_ptr 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 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 _connection_pool::acquire(coroutine_handler &ch) + { + std::shared_ptr 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 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 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 ¶ms) + { + 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 ¶ms) + { + php::object obj(php::class_entry::entry()); + collection *ptr = static_cast(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 ¶ms) + { + 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 ¶ms); + php::value execute(php::parameters ¶ms); + php::value __get(php::parameters ¶ms); + php::value __isset(php::parameters ¶ms); + private: + std::shared_ptr<_connection_pool> cp_; + friend php::value connect(php::parameters ¶ms); + }; +} // 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 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 ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { + 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::entry())); + return cs.call("fetch_row"); + } + php::value collection::get(php::parameters ¶ms) + { + 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::entry())); + php::array row = cs.call("fetch_row"); + if(row.empty()) { + return nullptr; + }else{ + return row.get(field); + } + } + php::value collection::count(php::parameters ¶ms) + { + 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 ¶ms) + { + 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 ¶ms) + { // 私有 + return nullptr; + } + php::value insert(php::parameters ¶ms); + php::value delete_(php::parameters ¶ms); + php::value update(php::parameters ¶ms); + php::value find(php::parameters ¶ms); + php::value one(php::parameters ¶ms); + php::value get(php::parameters ¶ms); + php::value count(php::parameters ¶ms); + php::value aggregate(php::parameters ¶ms); + + 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 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 ¶ms) + { + coroutine_handler ch{coroutine::current}; + return cl_->fetch(cs_, ch); + } + php::value cursor::fetch_all(php::parameters ¶ms) + { + 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 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 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(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 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("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 ¶ms) + { + php::object obj(php::class_entry::entry()); + client *ptr = static_cast(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::entry()); + object_id *o_ = static_cast(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::entry()); + date_time *o_ = static_cast(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 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 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(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(val)); + break; + case IS_DOUBLE: + bson_append_double(doc, key.c_str(), key.size(), static_cast(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(o.call("getTimestamp")) * 1000); + } + else if (o.instanceof (php::class_entry::entry())) + { + date_time *o_ = static_cast(php::native(o)); + bson_append_date_time(doc, key.c_str(), key.size(), o_->tm_); + } + else if (o.instanceof (php::class_entry::entry())) + { + object_id *o_ = static_cast(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(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 ¶ms); + php::value iter2value(bson_iter_t *i); + php::array bson2array(std::shared_ptr v); + php::array bson2array(bson_t* v); + std::shared_ptr 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 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 ¶ms) + { + 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 ¶ms) + { + php::string str(24); + bson_oid_to_string(&oid_, str.data()); + return std::move(str); + } + php::value object_id::unix(php::parameters ¶ms) + { + return bson_oid_get_time_t(&oid_); + } + php::value object_id::to_datetime(php::parameters ¶ms) + { + return php::datetime(bson_oid_get_time_t(&oid_) * 1000); + } + php::value object_id::to_json(php::parameters ¶ms) + { + // 定制 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 ¶ms) + { + 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::entry())) + { + php::object obj = params[0]; + object_id *ptr = static_cast(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 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::entry()); - result* ptr = static_cast(php::native(obj)); + if(rst) // 存在结果集 { + php::object obj(php::class_entry::entry()); + result* ptr = static_cast(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(mysql_num_rows(rst))); + return std::move(obj); } else // 更新型操作 { - obj.set("affect_rows", static_cast(mysql_affected_rows(conn.get()))); - obj.set("insert_id", static_cast(mysql_insert_id(conn.get()))); + php::array data(2); + data.set(php::string("affected_rows", 11), static_cast(mysql_affected_rows(conn.get()))); + data.set(php::string("insert_id", 9), static_cast(mysql_insert_id(conn.get()))); + return std::move(data); } - return std::move(obj); } php::array _connection_base::fetch(std::shared_ptr conn, std::shared_ptr 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 c)> cb = await_.front(); await_.pop_front(); - auto ptr = this->shared_from_this(); + auto self = this->shared_from_this(); cb( - std::shared_ptr(c, [this, ptr] (MYSQL *c) { - boost::asio::post(guard_, std::bind(&_connection_pool::release, ptr, c)); - }) - ); + // 释放回调函数须持有当前对象引用 self + // (否则连接池可能先于连接归还被销毁) + std::shared_ptr(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 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 co, const php::object &ref, const php::string &field) - // { - // co->stack(php::value([field](php::parameters ¶ms) -> 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 ¶ms) -> php::value { - // php::object rs = params[0]; - // result *rs_ = static_cast(php::native(rs)); - - // assert(rs.instanceof (php::class_entry::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 co, const php::object &ref, bool fetch_all, php::array &data_all) - // { - // c_->exec([this](std::shared_ptr 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(row); }, [this, co, ref, fetch_all, data_all](std::shared_ptr c, MYSQL_RES *r, int error) mutable { - // MYSQL* conn = c.get(); - // fetch_row_t* row = reinterpret_cast(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 ¶ms) { 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 c, int &error) -> MYSQL_RES * { - // mysql_free_result(r); - // return nullptr; }, [cl](std::shared_ptr 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 c)> cb = await_.front(); await_.pop_front(); - auto ptr = this->shared_from_this(); + auto self = this->shared_from_this(); cb( - std::shared_ptr(c, [this, ptr] (redisContext *c) + // 释放回调函数须持有当前对象引用 self + // (否则连接池可能先于连接归还被销毁) + std::shared_ptr(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 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 @@ +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 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;icontext_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 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("flame\\os\\interfaces") + .function("flame\\os\\spawn", + { + {"executable", php::TYPE::STRING}, + {"arguments", php::TYPE::ARRAY, false, true}, + {"options", php::TYPE::ARRAY, false, true}, + }) + .function("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 ¶ms) + { + 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 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 ¶ms) + { + php::object proc(php::class_entry::entry()); + process *proc_ = static_cast(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 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 ¶ms) + { + php::object proc = spawn(params); + process *proc_ = static_cast(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 ¶ms); + php::value spawn(php::parameters ¶ms); + php::value exec(php::parameters ¶ms); + +} // 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 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 ¶ms) + { + if (params.size() > 0) + { + ::kill(get("pid"), params[0].to_integer()); + } + else + { + ::kill(get("pid"), SIGTERM); + } + return nullptr; + } + php::value process::wait(php::parameters ¶ms) + { + if (!exit_) { + ch_.reset(coroutine::current); + ch_.suspend(); + } + return nullptr; + } + php::value process::stdout(php::parameters ¶ms) + { + wait(params); + return out_.get(); + } + php::value process::stderr(php::parameters ¶ms) + { + 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 out_; + std::future 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 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 @@ + + * 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::start(php::callable fn, zend_execute_data* execute_data) { auto co = std::make_shared(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 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::entry()); + message* ptr = static_cast(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(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 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 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> cos(cc_); + int run = 0; + for(int i=0;i 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 ¶ms); // 私有 + php::value run(php::parameters ¶ms); + php::value commit(php::parameters ¶ms); + php::value close(php::parameters ¶ms); + + 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 ¶ms); + }; +} // 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("flame\\kafka\\consume", { + {"options", php::TYPE::ARRAY}, + {"topics", php::TYPE::ARRAY}, + }) + .function("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::entry()); + consumer* ptr = static_cast(php::native(obj)); + ptr->cs_.reset(new _consumer(config, topics)); + if (config.exists("concurrent")) + { + ptr->cc_ = std::min(std::min(std::max(static_cast(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::entry()); + producer* ptr = static_cast(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 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(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 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 ¶ms) + { + return nullptr; + } + php::value producer::__destruct(php::parameters ¶ms) + { + coroutine_handler ch {coroutine::current}; + pd_->flush(ch); + return nullptr; + } + php::value producer::publish(php::parameters ¶ms) + { + php::string topic = params[0].to_string(), key, payload; + php::array header; + if (params[1].instanceof (php::class_entry::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 ¶ms) + { + 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 ¶ms); // 私有 + php::value __destruct(php::parameters ¶ms); + php::value publish(php::parameters ¶ms); + php::value flush(php::parameters ¶ms); + + private: + std::shared_ptr<_producer> pd_; + friend php::value produce(php::parameters ¶ms); + }; +} // 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 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 @@ + "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 @@ + "/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 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 @@ +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 gcontroller; -// 当前协程 -// std::shared_ptr 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 q; + // static php::value produce(php::parameters& params) + // { + // coroutine_handler ch{coroutine::current}; + // q.push(static_cast(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> > qs; + std::map< std::shared_ptr>, php::object > mm; + for (auto i = 0; i < params.size(); ++i) + { + if(!params[i].instanceof(php::class_entry::entry())) + { + throw php::exception(zend_ce_type_error, "only flame\\queue can be selected", -1); + } + php::object obj = params[i]; + queue* ptr = static_cast(php::native(obj)); + qs.push_back( ptr->q_ ); + mm.insert({ptr->q_, obj}); + } + coroutine_handler ch{coroutine::current}; + std::shared_ptr> 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("flame\\go", { {"coroutine", php::TYPE::CALLABLE}, }) - .function("flame\\run"); + .function("flame\\run") + .function("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::start(php::callable fn, zend_execute_data* execute_data) + std::shared_ptr coroutine::start(php::callable fn, /*std::vector ag,*/ zend_execute_data *execute_data) { auto co = std::make_shared(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 current; static void save_context(php_context_t &ctx); static void restore_context(php_context_t& ctx); - static std::shared_ptr start(php::callable fn, zend_execute_data* execute_data = nullptr); + static std::shared_ptr start(php::callable fn, /*std::vector ag = std::vector(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 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 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> cos(cc_); - int run = 0; - for(int i=0;i php::value - { + coroutine::start(php::value([this, &count](php::parameters ¶ms) -> 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("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 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 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(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 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 ¶ms); + private: + std::shared_ptr 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("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 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 reuse_port; + + php::value server::__construct(php::parameters ¶ms) + { + 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 ¶ms) + { + 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::entry()); + socket* ptr = static_cast(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 ¶ms) + { + 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 ¶ms); + php::value run(php::parameters ¶ms); + php::value close(php::parameters ¶ms); + 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 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 ¶m); + php::value write(php::parameters ¶ms); + php::value close(php::parameters ¶ms); + + 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 ¶ms); + }; +} // 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 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("flame\\tcp\\connect", + { + {"address", php::TYPE::STRING}, + }); + socket::declare(ext); + server::declare(ext); + } + + std::pair addr2pair(const std::string &addr) + { + char *s = const_cast(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(addr), ""); + } + else + { + return std::make_pair( + std::string(s, p - s), std::string(p+1, e-p-1)); + } + } + php::value connect(php::parameters& params) { + php::object obj(php::class_entry::entry()); + socket *ptr = static_cast(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 ¶ms); + std::pair 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 #include #include +#include #include #include #include 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 @@ +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 @@ +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 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 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 @@ + gcontroller; + // 全局控制器 + std::unique_ptr 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 fn) -{ - init_cb.push_back(fn); - return this; -} -controller* controller::on_stop(std::function 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 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;icontext_y.run(); - }); + init_cb.push_back(fn); + return this; } - // 3. 启动 context_x 运行 - gcontroller->context_x.run(); - work.reset(); - for (int i=0; i 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 fn) { - // before_.push_back(fn); - // return this; - // } - // controller* controller::after(std::function 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(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(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;iwrite(boost::format(" %3% [%1%] (INFO) master receives signal '%2%', exiting ...") % time::datetime() % sig); - // signal_ = sig; - // for(int i=0; i 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;irun(); + } + 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 worker_; - boost::process::group group_; - boost::asio::signal_set signal_; - - }; - class controller_worker { - public: - controller_worker(); - void run(); - - private: - std::vector 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 master_; // 工作进程对象 std::unique_ptr worker_; - // 公共 - std::list> init_cb; + std::list> init_cb; std::list> 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 fn); + controller *on_init(std::function fn); controller* on_stop(std::function 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(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(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;iworker_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 worker_; + std::vector pipe_; + std::vector buff_; + boost::process::group group_; + boost::asio::signal_set signal_; + std::thread thread_; + std::size_t count_; + + std::string ofpath_; + std::shared_ptr 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 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 q; - // static php::value produce(php::parameters& params) - // { - // coroutine_handler ch{coroutine::current}; - // q.push(static_cast(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> > qs; @@ -61,6 +53,7 @@ namespace flame { ext .function("flame\\init", { {"process_name", php::TYPE::STRING}, + {"options", php::TYPE::ARRAY, false, true}, }) .function("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(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 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 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 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 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 ¶ms) + { + write_ex(LEVEL_TRACE, params); + return nullptr; + } + static php::value debug(php::parameters ¶ms) + { + write_ex(LEVEL_DEBUG, params); + return nullptr; + } + static php::value info(php::parameters ¶ms) + { + write_ex(LEVEL_INFO, params); + return nullptr; + } + static php::value warning(php::parameters ¶ms) + { + write_ex(LEVEL_WARNING, params); + return nullptr; + } + static php::value error(php::parameters ¶ms) + { + write_ex(LEVEL_ERROR, params); + return nullptr; + } + static php::value fatal(php::parameters ¶ms) + { + 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("flame\\log\\trace") + .function("flame\\log\\debug") + .function("flame\\log\\info") + .function("flame\\log\\warn") + .function("flame\\log\\warning") + .function("flame\\log\\error") + .function("flame\\log\\fail") + .function("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 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 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 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(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(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(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;iworker_size;++i) { - pipe_.emplace_back(gcontroller->context_x); + sout_.push_back(std::make_unique(gcontroller->context_x)); + eout_.push_back(std::make_unique(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 worker_; - std::vector pipe_; - std::vector buff_; - boost::process::group group_; - boost::asio::signal_set signal_; + std::vector> worker_; + std::vector> sout_; + std::vector> eout_; + std::vector sbuf_; + std::vector ebuf_; + boost::process::group group_; + std::unique_ptr 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> > qs; @@ -49,16 +26,7 @@ namespace flame { return mm[q]; } void declare(php::extension_entry &ext) { - gcontroller.reset(new controller()); ext - .function("flame\\init", { - {"process_name", php::TYPE::STRING}, - {"options", php::TYPE::ARRAY, false, true}, - }) - .function("flame\\go", { - {"coroutine", php::TYPE::CALLABLE}, - }) - .function("flame\\run") .function("flame\\select"); + .function("flame\\select") .function("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 ¶ms) + 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 ¶ms) + { + 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 ¶ms) { 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 out_; std::future 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 #include #include +#include #include #include #include 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 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 @@ 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 @@ @@ -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 @@ 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(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(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 @@ $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 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 ¶ms) { - 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 ¶ms) From 842f7d2b367957faf66163c4c90c460d3297e4c2 Mon Sep 17 00:00:00 2001 From: terrywh 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(i->second) >= 0)) + else if (i->second.typeof(php::TYPE::YES) || (i->second.typeof(php::TYPE::INTEGER) && static_cast(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 #include From 68b65ebcb9bec05189ea190e2515bebdddf7a186 Mon Sep 17 00:00:00 2001 From: terrywh 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 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(php::native(obj)); if (config.exists("concurrent")) { - ptr->cc_ = std::min(std::min(std::max(static_cast(config.get("concurrent")), 1), 8), 256); + ptr->cc_ = std::min(std::max(static_cast(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 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 conn, php::array& pcmd, bool write, coroutine_handler &ch) { - auto cmd = array2bson(pcmd); - std::shared_ptr rep(bson_new(), bson_destroy); - auto err = std::make_shared(); + std::shared_ptr cmd = array2bson(pcmd); + // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏 + //(实际对 reply 的使用流程会重新初始化 reply 导致其值被至于 stack 上,导致此处 heap 内存泄漏) + bson_t reply; + std::shared_ptr rep(&reply, bson_destroy); + std::shared_ptr err = std::make_shared(); 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 rst(mysql_store_result(c)); + std::unique_ptr 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 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 #include 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 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(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* 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 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 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 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 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 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& 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::entry()); message* ptr = static_cast(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 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& 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 q; // 启动若干协程, 然后进行"并行"消费 int count = cc_; for (int i = 0; i < count; ++i) { // 启动协程开始消费 - coroutine::start(php::value([this, &count](php::parameters ¶ms) -> php::value { + coroutine::start(php::value([this, &ch_run, &q, &count](php::parameters ¶ms) -> 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 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 ¶ms); }; 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 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 ¶ms) { 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 ¶ms); + php::value dump(php::parameters ¶ms); php::value execute(php::parameters ¶ms); php::value __get(php::parameters ¶ms); php::value __isset(php::parameters ¶ms); @@ -16,4 +17,4 @@ namespace flame::mongodb std::shared_ptr<_connection_pool> cp_; friend php::value connect(php::parameters ¶ms); }; -} // 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::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 #include @@ -47,6 +47,6 @@ namespace ssl = boost::asio::ssl; #include #include #include -#include +// #include #include #include From e5677eecba1d56921c753a50c4b22fa879462da2 Mon Sep 17 00:00:00 2001 From: terrywh 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 conn, php::array& pcmd, bool write, coroutine_handler &ch) + php::value _connection_base::exec(std::shared_ptr conn, php::array& pcmd, int type, coroutine_handler &ch) { std::shared_ptr cmd = array2bson(pcmd); // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏 //(实际对 reply 的使用流程会重新初始化 reply 导致其值被至于 stack 上,导致此处 heap 内存泄漏) - bson_t reply; - std::shared_ptr rep(&reply, bson_destroy); + bson_t reply, option = BSON_INITIALIZER; + std::shared_ptr rep(&reply, bson_destroy), opt(&option, bson_destroy); std::shared_ptr err = std::make_shared(); + 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::entry()}; auto ptr = static_cast(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(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 acquire(coroutine_handler& ch) = 0; - php::value exec(std::shared_ptr conn, php::array& cmd, bool write, coroutine_handler& ch); + php::value exec(std::shared_ptr 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 c) - : conn_(c) + : conn_(c) + , guard_(gcontroller->context_y) { + + } + _connection_lock::~_connection_lock() { } std::shared_ptr _connection_lock::acquire(coroutine_handler &ch) @@ -18,21 +22,16 @@ namespace flame::mongodb bool has = false; auto err = std::make_shared(); 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(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 c); + ~_connection_lock(); std::shared_ptr acquire(coroutine_handler &ch) override; php::array fetch(std::shared_ptr cs, coroutine_handler &ch); private: + boost::asio::io_context::strand guard_; // 防止对 cursor 的并行访问 std::shared_ptr 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 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 ¶ms) { - 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 ¶ms) { @@ -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 ¶ms) { @@ -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 ¶ms) { + 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 ¶ms) { @@ -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 ¶ms) + php::value collection::find_one(php::parameters ¶ms) { 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::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::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 ¶ms) @@ -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 ¶ms); php::value delete_(php::parameters ¶ms); + php::value delete_one(php::parameters& params); php::value update(php::parameters ¶ms); + php::value update_one(php::parameters& params); php::value find(php::parameters ¶ms); - php::value one(php::parameters ¶ms); + php::value find_one(php::parameters ¶ms); php::value get(php::parameters ¶ms); php::value count(php::parameters ¶ms); php::value aggregate(php::parameters ¶ms); - + 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 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 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 _connection_pool::acquire(coroutine_handler& ch) { std::shared_ptr 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 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 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 @@ + "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 @@ 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 @@ ("flame\\compress\\snappy_compress", { + {"data", php::TYPE::STRING}, + }) + .function("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(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("flame\\select") - .function("flame\\co_id") - .function("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 ¶ms); -} \ 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::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::start(php::callable fn) { - ++coroutine::count; - auto co_ = std::make_shared(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(0)) { - } - - coroutine_handler::coroutine_handler(std::shared_ptr co) - : co_(co) - , err_(nullptr) - , stat_(new std::atomic(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 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 { +public: + coroutine(boost::asio::executor ex) + : ex_(ex) { + + } + virtual ~coroutine() { - class coroutine : public std::enable_shared_from_this { - 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 current; + template + static void start(boost::asio::executor ex, Handler&& fn) { + auto co = std::make_shared(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 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 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 co) + : co_(co) { } + coroutine_handler(const coroutine_handler& ch) = default; + virtual ~coroutine_handler() { - struct coroutine_handler { - public: - coroutine_handler(); - coroutine_handler(std::shared_ptr co); - ~coroutine_handler(); - // !!! 慎用: 目前仅用于空构造后指定协程 - void reset(std::shared_ptr 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 co_; - std::shared_ptr> 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 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 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 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 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 coroutine_queue: private boost::noncopyable { - public: - coroutine_queue(std::size_t n = 1) - : n_(n) - , closed_(false) { +template +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 pop(coroutine_handler& ch) { + for(;;) { + if (!q_.empty()) break; // 有数据消费 + else if (closed_) return std::optional(); // 无数据关闭 + 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 pop(coroutine_handler& ch) { - for(;;) { - if (!q_.empty()) break; // 有数据消费 - else if (closed_) return std::optional(); // 无数据关闭 - 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); + while (!p_.empty()) { + auto ch = p_.extract(p_.begin()).value(); + ch->resume(); } + return std::optional(t); + } - /* private: */ - std::size_t n_; - std::list q_; - std::set c_; // 消费者 - std::set p_; // 生产者 - bool closed_; - }; +/* private: */ + std::size_t n_; + std::list q_; + std::set c_; // 消费者 + std::set p_; // 生产者 + bool closed_; +}; - template - std::shared_ptr < coroutine_queue > select_queue(std::vector < std::shared_ptr> > queues, coroutine_handler &ch) { +template +std::shared_ptr < coroutine_queue > select_queue(std::vector < std::shared_ptr> > 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 >(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 >(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 -#include - -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 ¶ms) { - 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 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", { - {"process_name", php::TYPE::STRING}, - {"options", php::TYPE::ARRAY, false, true}, - }) - .function("flame\\run"); - if (flame::gcontroller->type == flame::controller::process_type::WORKER) { - ext - .function("flame\\go", { - {"coroutine", php::TYPE::CALLABLE}, - }) - .function("flame\\on", { - {"event", php::TYPE::STRING}, - {"callback", php::TYPE::CALLABLE}, - }) - .function("flame\\quit") - .function("flame\\set", { - {"target", php::TYPE::ARRAY, true}, - {"fields", php::TYPE::STRING}, - {"values", php::TYPE::UNDEFINED}, - }) - .function("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\\go") - .function("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 gcontroller; + +controller::controller() +: type(process_type::UNKNOWN) +, env(boost::this_process::environment()) +, status(STATUS_UNKNOWN) +, user_cb(new std::multimap()) { + + 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 fn) { + init_cb.push_back(fn); + return this; +} + +controller* controller::on_stop(std::function 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 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> init_cb; + std::list> stop_cb; + std::multimap* user_cb; // 防止 PHP 提前回收, 使用堆容器 +public: + controller(); + controller(const controller& c) = delete; + void init(php::array options); + void stop(); + controller *on_init(std::function fn); + controller* on_stop(std::function fn); + controller* on_user(const std::string& event, php::callable cb); + void call_user_cb(const std::string& event, std::vector params); + void call_user_cb(const std::string& event); + // TODO write_to_master | write_worker +}; + +extern std::unique_ptr 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::current; + coroutine::php_context_t coroutine::gctx_; + + void coroutine::start(php::callable fn) { + ++coroutine::count; + auto co = std::make_shared(); + + 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(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 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 co) + : ::coroutine_handler(co) { + + } + ~coroutine_handler() { + + } + + void reset() { + co_.reset(); + } + void reset(std::shared_ptr 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 { + 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 { + 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 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 #include 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 coroutine_queue; -} + +template +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 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("flame\\init") + .function("flame\\go") + .function("flame\\on") + .function("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(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 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 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 #include #include - +#include +#include 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 +#include 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("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> > qs; + std::map< std::shared_ptr>, php::object > mm; + for (auto i = 0; i < params.size(); ++i) { + if (!params[i].instanceof(php::class_entry::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(php::native(obj)); + qs.push_back( ptr->q_ ); + mm.insert({ptr->q_, obj}); + } + coroutine_handler ch{coroutine::current}; + std::shared_ptr> 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 ¶ms); - } - 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 ¶ms); php::value pop(php::parameters ¶ms); @@ -18,7 +15,5 @@ namespace flame { php::value is_closed(php::parameters ¶ms); private: std::shared_ptr> q_; - - friend php::value core::select(php::parameters ¶ms); }; } 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 #include 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 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 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 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 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 +#include +#include +#include +#include +#include +// #include +#include +#include + +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("flame\\init", { + {"process_name", php::TYPE::STRING}, + {"options", php::TYPE::ARRAY, false, true}, + }) + + .function("flame\\go", { + {"coroutine", php::TYPE::CALLABLE}, + }) + .function("flame\\on", { + {"event", php::TYPE::STRING}, + {"callback", php::TYPE::CALLABLE}, + }) + .function("flame\\run") + .function("flame\\quit") + .function("flame\\co_id") + .function("flame\\co_ct") + .function("flame\\co_count") + .function("flame\\set", { + {"target", php::TYPE::ARRAY, true}, + {"fields", php::TYPE::STRING}, + {"values", php::TYPE::UNDEFINED}, + }) + .function("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(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 ¶ms) { + 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(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 ¶ms); + 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> 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(i->second.get())->path_ == path) { + i->second->addref(); + return i->second->index(); + } + } + auto p = logger_.insert({index_, std::make_unique(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 +#include "logger.h" + +class logger_master: public logger { +private: + std::shared_ptr 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(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 +#include + +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> start; + for(int i=0;i> closing = std::move(child_); + for(int i=0;iclose(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;isignal(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> 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 + void start(Handler&& cb) { + ss_->async_wait(cb); + } + void close(); + logger* lg_; +private: + std::unique_ptr 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 -namespace flame { - typedef ::parser::separator_parser parser_t; +typedef parser::separator_parser parser_t; - url::url(http://github.com/const%20php::string%20&str,%20bool%20parse_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(http://github.com/const%20php::string%20&str,%20bool%20parse_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(http://github.com/const%20php::string&%20str,%20bool%20parse_query=true); - std::string schema; - std::string host; - std::uint16_t port; - std::string path; - std::map query; - std::string user; - std::string pass; +class url { +public: + url(http://github.com/const%20php::string&%20str,%20bool%20parse_query=true); + std::string schema; + std::string host; + std::uint16_t port; + std::string path; + std::map 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 @@ -25,10 +25,10 @@ #include #include #include -#include -#include #include #include #include using boost::asio::ip::tcp; using boost::asio::ip::udp; +#include +#include 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 @@ + 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 gcontroller; - -controller::controller() -: type(process_type::UNKNOWN) -, env(boost::this_process::environment()) -, status(STATUS_UNKNOWN) -, user_cb(new std::multimap()) { - - 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 gcontroller; -controller *controller::on_init(std::function 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()) { -controller* controller::on_stop(std::function 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 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 fn) { + stop_cb.push_back(fn); + return this; + } -void controller::call_user_cb(const std::string& event, std::vector 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 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> init_cb; + std::list> stop_cb; + std::multimap* user_cb; // 防止 PHP 提前回收, 使用堆容器 + public: + controller(); + controller(const controller& c) = delete; + void init(php::array options); + void stop(); + controller *on_init(std::function fn); + controller* on_stop(std::function fn); + controller* on_user(const std::string& event, php::callable cb); + void call_user_cb(const std::string& event, std::vector 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> init_cb; - std::list> stop_cb; - std::multimap* user_cb; // 防止 PHP 提前回收, 使用堆容器 -public: - controller(); - controller(const controller& c) = delete; - void init(php::array options); - void stop(); - controller *on_init(std::function fn); - controller* on_stop(std::function fn); - controller* on_user(const std::string& event, php::callable cb); - void call_user_cb(const std::string& event, std::vector params); - void call_user_cb(const std::string& event); - // TODO write_to_master | write_worker -}; -extern std::unique_ptr gcontroller; + extern std::unique_ptr 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(); 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 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 ¶ms) { - write_ex(LEVEL_TRACE, params); + logger_->write_ex(logger::LEVEL_TRACE, params); return nullptr; } static php::value debug(php::parameters ¶ms) { - write_ex(LEVEL_DEBUG, params); + logger_->write_ex(logger::LEVEL_DEBUG, params); return nullptr; } static php::value info(php::parameters ¶ms) { - write_ex(LEVEL_INFO, params); + logger_->write_ex(logger::LEVEL_INFO, params); return nullptr; } static php::value warning(php::parameters ¶ms) { - write_ex(LEVEL_WARNING, params); + logger_->write_ex(logger::LEVEL_WARNING, params); return nullptr; } static php::value error(php::parameters ¶ms) { - write_ex(LEVEL_ERROR, params); + logger_->write_ex(logger::LEVEL_ERROR, params); return nullptr; } static php::value fatal(php::parameters ¶ms) { - 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::entry()}; + logger* ptr = static_cast(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("flame\\log\\trace") .function("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 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 ¶ms) { + write_ex(LEVEL_TRACE, params); + return nullptr; + } + + php::value logger::debug(php::parameters ¶ms) { + write_ex(LEVEL_DEBUG, params); + return nullptr; + } + + php::value logger::info(php::parameters ¶ms) { + write_ex(LEVEL_INFO, params); + return nullptr; + } + + php::value logger::warning(php::parameters ¶ms) { + write_ex(LEVEL_WARNING, params); + return nullptr; + } + + php::value logger::error(php::parameters ¶ms) { + write_ex(LEVEL_ERROR, params); + return nullptr; + } + + php::value logger::fatal(php::parameters ¶ms) { + 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 LEVEL_STR; + static int LEVEL_OPT; + ~logger(); + void write_ex(int lv, php::parameters& params); + php::value trace(php::parameters ¶ms); + php::value debug(php::parameters ¶ms); + php::value info(php::parameters ¶ms); + php::value warning(php::parameters ¶ms); + php::value error(php::parameters ¶ms); + php::value fatal(php::parameters ¶ms); + + 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 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 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> 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(i->second.get())->path_ == path) { i->second->addref(); - return i->second->index(); + return i->second.get(); } } auto p = logger_.insert({index_, std::make_unique(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 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(http://github.com/const%20php::string%20&str,%20bool%20parse_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 #include #include +#include +#include #include #include #include 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 @@ + 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>()); 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>()); - // 默认为请求 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>()); + // 服务及将停止或服务器以进行关闭后,禁止连接复用 + 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::entry()); php::object res = php::object(php::class_entry::entry()); server_request *req_ptr = static_cast(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 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 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 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 ¶ms) { 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::entry()}; logger* ptr = static_cast(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("flame\\log\\trace") .function("flame\\log\\debug") @@ -69,5 +53,7 @@ namespace flame::log { .function("flame\\log\\error") .function("flame\\log\\fail") .function("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 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 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 ¶ms); php::value debug(php::parameters ¶ms); php::value info(php::parameters ¶ms); php::value warning(php::parameters ¶ms); php::value error(php::parameters ¶ms); php::value fatal(php::parameters ¶ms); - - void set_logger(::logger* lg) { - lg_ = lg; - } private: - ::logger* lg_ = nullptr; - - friend php::value connect(php::parameters& params); + std::shared_ptr 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::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(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 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, public master_process_manager, public signal_watcher, public master_logger_manager, public master_ipc { public: - static inline master* get() { + static inline std::shared_ptr 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 pm_self() override { + return std::static_pointer_cast(shared_from_this()); + } + virtual std::shared_ptr sw_self() override { + return std::static_pointer_cast(shared_from_this()); + } + virtual std::shared_ptr lm_self() override { + return std::static_pointer_cast(shared_from_this()); + } + virtual std::shared_ptr ipc_self() override { + return std::static_pointer_cast(shared_from_this()); + } + bool on_signal(int sig) override; + bool on_message(std::shared_ptr msg, socket_ptr sock) override; private: - static master* mm_; + static std::shared_ptr mm_; + master_logger* lg_; - process_manager pm_; - std::unique_ptr 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::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 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, public signal_watcher, public worker_ipc, public worker_logger_manager { public: - static inline worker* get() { - return ww_; + static inline std::shared_ptr 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 sw_self() override { + return std::static_pointer_cast(shared_from_this()); + } + virtual std::shared_ptr ipc_self() override { + return std::static_pointer_cast(shared_from_this()); + } + virtual std::shared_ptr lm_self() override { + return std::static_pointer_cast(shared_from_this()); + } + bool on_signal(int sig) override; + void on_notify(std::shared_ptr msg) override; private: - static worker* ww_; - - std::unique_ptr lm_; - signal_watcher sw_; + static std::shared_ptr 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::create_message() { + return std::shared_ptr(ipc::malloc_message(), ipc::free_message); +} + +void ipc::relloc_message(std::shared_ptr& 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& 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 create_message(); + // 消息容器长度调整 + static void relloc_message(std::shared_ptr& msg, std::uint16_t copy, std::uint16_t length); + // 生成消息ID + static std::uint32_t create_uid(); + // 请求并等待响应 + virtual std::shared_ptr ipc_request(std::shared_ptr data, coroutine_handler& ch) = 0; + // 请求(无响应) + virtual void ipc_request(std::shared_ptr 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> 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(i->second.get())->path_ == path) { - i->second->addref(); - return i->second.get(); - } - } - auto p = logger_.insert({index_, std::make_unique(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 -#include "logger.h" - -class logger_master: public logger { -private: - std::shared_ptr 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(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 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 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 master_ipc::ipc_request(std::shared_ptr req, coroutine_handler& ch) { + std::shared_ptr 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 data) { + send(data); +} + +void master_ipc::send(std::shared_ptr msg) { + if(sendq_.empty()) { + sendq_.push_back(msg); + send_next(); + }else + sendq_.push_back(msg); +} + +void master_ipc::send_next() { + std::shared_ptr 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; + 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_request(std::shared_ptr req, coroutine_handler& ch); + // 请求(无响应) + void ipc_request(std::shared_ptr data); +protected: + virtual bool on_message(std::shared_ptr msg, socket_ptr sock); + virtual std::shared_ptr ipc_self() = 0; +private: + boost::asio::io_context& io_; + std::filesystem::path svrsck_; + boost::asio::local::stream_protocol::acceptor server_; + std::map socket_; + std::map callback_; + std::list> sendq_; + // 连接数据接收 + + void send(std::shared_ptr 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 + +class master_logger { +private: + std::uint8_t idx_; + unsigned int ref_; + std::shared_ptr 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(i->second.get())->path_ == path) { + ++i->second->ref_; + return i->second.get(); + } + } + auto p = logger_.insert({index_, std::make_unique(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 lm_self() = 0; +private: + std::uint8_t index_ = 0; + std::map> 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(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 #include -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> start; +void master_process_manager::pm_reset(coroutine_handler& ch) { + std::vector> start; for(int i=0;i> closing = std::move(child_); +void master_process_manager::pm_close(unsigned int timeout_ms, coroutine_handler& ch) { + std::vector> closing = std::move(child_); for(int i=0;iclose(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;isignal(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 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> 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> 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 - 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 sw_self() = 0; +protected: + virtual bool on_signal(int sig) = 0; private: std::unique_ptr 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 #include +#include 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 worker_ipc::ipc_request(std::shared_ptr req, coroutine_handler& ch) { + std::shared_ptr 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 data) { + send(data); +} + +static void json_message_free(ipc::message_t* msg) { + zend_string* ptr = reinterpret_cast((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{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 msg) { + + if(sendq_.empty()) { + sendq_.push_back(msg); + send_next(); + }else + sendq_.push_back(msg); +} + +void worker_ipc::send_next() { + std::shared_ptr 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; + + virtual std::ostream& output() = 0; + // 请求并等待响应 + std::shared_ptr ipc_request(std::shared_ptr req, coroutine_handler& ch); + // 请求(无响应) + void ipc_request(std::shared_ptr 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 ipc_self() = 0; + virtual void on_notify(std::shared_ptr msg) = 0; +private: + socket_ptr socket_; + std::map callback_; + std::list> sendq_; + void send(std::shared_ptr 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 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 +#include +#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 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_manager::lm_connect(const std::string& filepath, coroutine_handler& ch) { + std::shared_ptr 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(this, 0); + /* + msg = ipc_->ipc_request(msg, ch); + // 使用该日志文件标号创建 LOGGER 对象,以支持实际日志发送 + wl = std::make_shared(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 lm_connect(const std::string& filepath, coroutine_handler& ch); + void lm_destroy(std::uint8_t idx); + void lm_close(); +protected: + virtual std::shared_ptr lm_self() = 0; +private: + std::map> 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 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()) { 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 = ""; 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 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 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 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; @@ -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 ipc_self() = 0; virtual void on_notify(std::shared_ptr msg) = 0; private: + std::filesystem::path svrsck_; + std::uint8_t idx_; socket_ptr socket_; std::map callback_; std::list> sendq_; + int status_ = -1; + + void send_login(); void send(std::shared_ptr 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() != "") { + 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 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 wlb_; - std::ostream oss_; + std::uint8_t idx_; + std::filesystem::path path_; + std::unique_ptr wlb_; + std::unique_ptr 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_manager::lm_connect(const std::string& filepath, coroutine_handler& ch) { +std::shared_ptr 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 lg = i->second.lock(); + if(lg->path_ == path) return lg; + ++i; + } + } + std::shared_ptr 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(this, 0); - /* + std::memcpy(msg->payload, file.data(), file.size()); msg = ipc_->ipc_request(msg, ch); + index = msg->target; // 使用该日志文件标号创建 LOGGER 对象,以支持实际日志发送 - wl = std::make_shared(this, msg->target); - */ - logger_.emplace(0, wl); + wl = std::make_shared(this, file, msg->target); + } + else { + index = lindex_; + wl = std::make_shared(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 lm_self() = 0; private: std::map> 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 @@ __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 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 { -public: - coroutine(boost::asio::executor ex) - : ex_(ex) { - - } - virtual ~coroutine() { - - } - // static std::shared_ptr current; - template - static void start(boost::asio::executor ex, Handler&& fn) { - auto co = std::make_shared(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 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 { +public: + coroutine(boost::asio::executor ex) + : ex_(ex) { + + } + virtual ~coroutine() { + + } + + template + 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 +void coroutine::start(boost::asio::executor ex, Handler&& fn) { + auto co = std::make_shared(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 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 ¶ms) { + logger_->write_ex(logger::LEVEL_EMPTY, params); + return nullptr; + } + static php::value trace(php::parameters ¶ms) { logger_->write_ex(logger::LEVEL_TRACE, params); return nullptr; @@ -45,6 +50,8 @@ namespace flame::log { void declare(php::extension_entry &ext) { ext + .function("flame\\log\\connect") + .function("flame\\log\\write") .function("flame\\log\\trace") .function("flame\\log\\debug") .function("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 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 ¶ms) { 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 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 ¶ms); php::value trace(php::parameters ¶ms); php::value debug(php::parameters ¶ms); php::value info(php::parameters ¶ms); 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 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 ipc_self() override { return std::static_pointer_cast(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 msg, socket_ptr sock) override; + private: static std::shared_ptr 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 msg) { - gcontroller->call_user_cb("notify", {php::json_decode(&msg->payload[0], msg->length)}); + // !!! 此函数在工作线程中工作 + bool worker::on_message(std::shared_ptr 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(shared_from_this()); } bool on_signal(int sig) override; - void on_notify(std::shared_ptr msg) override; + bool on_message(std::shared_ptr msg) override; private: static std::shared_ptr 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(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 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 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 master_ipc::ipc_request(std::shared_ptr req, coroutine_handler& ch) { std::shared_ptr 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 data) { - send(data); +void master_ipc::ipc_request(std::shared_ptr req) { + req->source = 0; + send(req); } void master_ipc::send(std::shared_ptr 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 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_request(std::shared_ptr 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() != "") { + 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 file_; - std::filesystem::path path_; + std::uint8_t idx_; + unsigned int ref_; + std::shared_ptr oss_; + std::unique_ptr 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 +#include +#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(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 lm_self() = 0; private: + boost::asio::io_context& io_; std::uint8_t index_ = 0; std::map> 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(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> start; + if(status_ & (STATUS_RSETING | STATUS_ACLOSED)) return; + + std::vector> start(count_); for(int i=0;i> closing = std::move(child_); - for(int i=0;iclose(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;iclose(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 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 work_; boost::asio::io_context& io_; unsigned int count_; + unsigned int close_; std::vector> 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(http://github.com/const%20php::string%20&str,%20bool%20parse_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 worker_ipc::ipc_request(std::shared_ptr req, coroutine_handler& ch) { std::shared_ptr 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 data) { - send(data); +void worker_ipc::ipc_request(std::shared_ptr 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{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 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 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 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 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 ipc_self() = 0; - virtual void on_notify(std::shared_ptr msg) = 0; + virtual bool on_message(std::shared_ptr 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> sendq_; int status_ = -1; - void send_login(); void send(std::shared_ptr 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() != "") { 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 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_manager::lm_connect(const std::string& file, coroutine_handler& ch) { @@ -23,13 +23,15 @@ std::shared_ptr 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(this, file, msg->target); + wl = std::make_shared(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 @@ + __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 @@ + __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 @@ - 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 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()) { + , evnt_cb(new std::multimap()) { 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 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 params) { - auto ft = user_cb->equal_range(event); + void controller::event(const std::string& event, std::vector 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> init_cb; std::list> stop_cb; - std::multimap* user_cb; // 防止 PHP 提前回收, 使用堆容器 + std::multimap* evnt_cb; // 防止 PHP 提前回收, 使用堆容器 public: controller(); controller(const controller& c) = delete; - void init(php::array options); - void stop(); controller *on_init(std::function fn); + void init(php::array options); controller* on_stop(std::function fn); - controller* on_user(const std::string& event, php::callable cb); - void call_user_cb(const std::string& event, std::vector 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 params); + void event(const std::string& event); }; extern std::unique_ptr 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("flame\\init") - .function("flame\\go") - .function("flame\\on") - .function("flame\\run"); + .function("flame\\init") + .function("flame\\go") + .function("flame\\on") + .function("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(""); // 初始化启动 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 ¶ms) { - // 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("flame\\off", { + {"event", php::TYPE::STRING}, + }) .function("flame\\run") .function("flame\\quit") .function("flame\\co_id") @@ -64,6 +67,10 @@ namespace flame { .function("flame\\get", { {"target", php::TYPE::ARRAY}, {"fields", php::TYPE::STRING}, + }) + .function("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 ¶ms) { + 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(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 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, 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 ¶ms); + 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 msg) override; private: static std::shared_ptr ww_; + std::list 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::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(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(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::create_message() { - return std::shared_ptr(ipc::malloc_message(), ipc::free_message); } void ipc::relloc_message(std::shared_ptr& 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& 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 create_message(); + static std::shared_ptr create_message(std::uint16_t length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)); // 消息容器长度调整 static void relloc_message(std::shared_ptr& 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() != "") { 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> start(count_); - for(int i=0;i> start(cmax_); + for(int i=0;iclose(now); + for(int i=0;iclose(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;isignal(sig); + for(int i=0;isignal(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 work_; boost::asio::io_context& io_; - unsigned int count_; - unsigned int close_; + unsigned int cmax_; + unsigned int crun_; + unsigned int tquit_; + std::vector> 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 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((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{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(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() != "") { auto fb = new std::filebuf(); fb->open(path, std::ios_base::app);