├── .gitignore ├── LICENCE ├── README-structures.png ├── README.md ├── doc ├── compress.php ├── core.php ├── encoding.php ├── flame.php ├── hash.php ├── http.php ├── kafka.php ├── log.php ├── mongodb.php ├── mysql.php ├── os.php ├── rabbitmq.php ├── redis.php ├── smtp.php ├── tcp.php ├── time.php ├── toml.php ├── udp.php └── util.php ├── run-tests.php ├── src ├── core │ ├── basic_snowflake.cpp │ ├── basic_snowflake.h │ ├── clock.cpp │ ├── clock.h │ ├── cluster.cpp │ ├── cluster.h │ ├── context.cpp │ ├── context.h │ ├── coroutine.h │ ├── default_sink.cpp │ ├── default_sink.h │ ├── exception.h │ ├── extension │ │ ├── core.cpp │ │ ├── core.h │ │ ├── coroutine.cpp │ │ ├── coroutine.h │ │ ├── extension.cpp │ │ ├── log.cpp │ │ ├── log.h │ │ ├── time.cpp │ │ ├── time.h │ │ ├── util.cpp │ │ └── util.h │ ├── logger.cpp │ ├── logger.h │ ├── parse_url.cpp │ ├── parse_url.h │ ├── random_string.cpp │ ├── random_string.h │ ├── syslog_sink.cpp │ ├── syslog_sink.h │ └── vendor.h ├── coroutine_mutex.h ├── coroutine_queue.h ├── flame │ ├── compress │ │ ├── compress.cpp │ │ └── compress.h │ ├── controller.cpp │ ├── controller.h │ ├── encoding │ │ ├── encoding.cpp │ │ └── encoding.h │ ├── flame.cpp │ ├── hash │ │ ├── hash.cpp │ │ └── hash.h │ ├── http │ │ ├── _handler.cpp │ │ ├── _handler.h │ │ ├── client.cpp │ │ ├── client.h │ │ ├── client_body.cpp │ │ ├── client_body.h │ │ ├── client_poll.cpp │ │ ├── client_poll.h │ │ ├── client_request.cpp │ │ ├── client_request.h │ │ ├── client_response.cpp │ │ ├── client_response.h │ │ ├── http.cpp │ │ ├── http.h │ │ ├── server.cpp │ │ ├── server.h │ │ ├── server_request.cpp │ │ ├── server_request.h │ │ ├── server_response.cpp │ │ ├── server_response.h │ │ ├── value_body.cpp │ │ ├── value_body.h │ │ └── value_body.ipp │ ├── kafka │ │ ├── _consumer.cpp │ │ ├── _consumer.h │ │ ├── _producer.cpp │ │ ├── _producer.h │ │ ├── consumer.cpp │ │ ├── consumer.h │ │ ├── kafka.cpp │ │ ├── kafka.h │ │ ├── message.cpp │ │ ├── message.h │ │ ├── producer.cpp │ │ └── producer.h │ ├── log │ │ ├── log.cpp │ │ ├── log.h │ │ ├── logger.cpp │ │ └── logger.h │ ├── master.cpp │ ├── master.h │ ├── mongodb │ │ ├── _connection_base.cpp │ │ ├── _connection_base.h │ │ ├── _connection_lock.cpp │ │ ├── _connection_lock.h │ │ ├── _connection_pool.cpp │ │ ├── _connection_pool.h │ │ ├── client.cpp │ │ ├── client.h │ │ ├── collection.cpp │ │ ├── collection.h │ │ ├── cursor.cpp │ │ ├── cursor.h │ │ ├── date_time.cpp │ │ ├── date_time.h │ │ ├── mongodb.cpp │ │ ├── mongodb.h │ │ ├── object_id.cpp │ │ └── object_id.h │ ├── mutex.cpp │ ├── mutex.h │ ├── mysql │ │ ├── _connection_base.cpp │ │ ├── _connection_base.h │ │ ├── _connection_lock.cpp │ │ ├── _connection_lock.h │ │ ├── _connection_pool.cpp │ │ ├── _connection_pool.h │ │ ├── client.cpp │ │ ├── client.h │ │ ├── mysql.cpp │ │ ├── mysql.h │ │ ├── result.cpp │ │ ├── result.h │ │ ├── tx.cpp │ │ └── tx.h │ ├── os │ │ ├── os.cpp │ │ ├── os.h │ │ ├── process.cpp │ │ └── process.h │ ├── queue.cpp │ ├── queue.h │ ├── rabbitmq │ │ ├── _client.cpp │ │ ├── _client.h │ │ ├── consumer.cpp │ │ ├── consumer.h │ │ ├── message.cpp │ │ ├── message.h │ │ ├── producer.cpp │ │ ├── producer.h │ │ ├── rabbitmq.cpp │ │ └── rabbitmq.h │ ├── redis │ │ ├── _connection_base.cpp │ │ ├── _connection_base.h │ │ ├── _connection_lock.cpp │ │ ├── _connection_lock.h │ │ ├── _connection_pool.cpp │ │ ├── _connection_pool.h │ │ ├── client.cpp │ │ ├── client.h │ │ ├── redis.cpp │ │ ├── redis.h │ │ ├── tx.cpp │ │ └── tx.h │ ├── smtp │ │ ├── client.cpp │ │ ├── client.h │ │ ├── message.cpp │ │ ├── message.h │ │ ├── smtp.cpp │ │ └── smtp.h │ ├── tcp │ │ ├── server.cpp │ │ ├── server.h │ │ ├── socket.cpp │ │ ├── socket.h │ │ ├── tcp.cpp │ │ └── tcp.h │ ├── time │ │ ├── scheduler.cpp │ │ ├── scheduler.h │ │ ├── time.cpp │ │ ├── time.h │ │ ├── timer.cpp │ │ └── timer.h │ ├── toml │ │ ├── _decode.cpp │ │ ├── _decode.h │ │ ├── _executor.cpp │ │ ├── _executor.h │ │ ├── toml.cpp │ │ └── toml.h │ ├── udp │ │ ├── server.cpp │ │ ├── server.h │ │ ├── socket.cpp │ │ ├── socket.h │ │ ├── udp.cpp │ │ └── udp.h │ ├── version.cpp │ ├── version.h │ ├── worker.cpp │ └── worker.h ├── http │ └── extension │ │ └── extension.cpp ├── ipc.cpp ├── ipc.h ├── master_ipc.cpp ├── master_ipc.h ├── master_logger.cpp ├── master_logger.h ├── master_logger_buffer.cpp ├── master_logger_buffer.h ├── master_logger_manager.cpp ├── master_logger_manager.h ├── master_process.cpp ├── master_process.h ├── master_process_manager.cpp ├── master_process_manager.h ├── sentry │ ├── config.txt │ └── exception.txt ├── signal_watcher.h ├── worker_ipc.cpp ├── worker_ipc.h ├── worker_logger.cpp ├── worker_logger.h ├── worker_logger_buffer.cpp ├── worker_logger_buffer.h ├── worker_logger_manager.cpp └── worker_logger_manager.h ├── tests └── core │ ├── clock.cpp │ ├── core.cpp │ └── run.phpt └── xmake.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .xmake 2 | .vscode 3 | build 4 | compile_commands.json 5 | tests/*/*.php 6 | tests/*/*.exp 7 | tests/*/*.log 8 | tests/*/*.out 9 | tests/*/*.diff 10 | tests/*/*.sh 11 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2019 terrywh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README-structures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrywh/php-flame/b8d6c82dcac230248f9bdcd8300e5f2de417f21f/README-structures.png -------------------------------------------------------------------------------- /doc/compress.php: -------------------------------------------------------------------------------- 1 | 32 | * array(1) { 33 | * [0]=> 34 | * array(2) { 35 | * ["family"]=> 36 | * string(4) "IPv4" 37 | * ["address"]=> 38 | * string(9) "127.0.0.1" 39 | * } 40 | * } 41 | * ["eth0"]=> 42 | * array(1) { 43 | * [0]=> 44 | * array(2) { 45 | * ["family"]=> 46 | * string(4) "IPv4" 47 | * ["address"]=> 48 | * string(13) "10.110.16.197" 49 | * } 50 | * } 51 | * } 52 | */ 53 | function interfaces():array { 54 | return []; 55 | } 56 | /** 57 | * 异步启动进程 58 | * @param array $options 目前可用的选项如下: 59 | * * "cwd" - string - 工作路径; 60 | * * "env" - array - 环境变量,K/V 结构文本; 61 | * @return 进程对象 62 | */ 63 | function spawn(string $command, array $argv = [], array $options = []):process { 64 | return new process(); 65 | } 66 | /** 67 | * 调用上述 spawn() 异步启动进程, 并等待其结束, 返回进程标准输出 68 | * @param array $options 目前可用的选项如下: 69 | * * "cwd" - string - 工作路径; 70 | * * "env" - array - 环境变量,K/V 结构文本; 71 | * @return string 进程标准输出内容 72 | */ 73 | function exec(string $command, array $argv, array $options = []):string { 74 | return "output of the process"; 75 | } 76 | 77 | /** 78 | * 进程对象 79 | */ 80 | class process { 81 | /** 82 | * 向进程发送指定信号 83 | */ 84 | function kill(int $signal = SIGTERM) {} 85 | /** 86 | * 等待进程结束 87 | */ 88 | function wait() {} 89 | /** 90 | * 获取进程标准输出(若进程还未结束需要等待) 91 | */ 92 | function stdout():string { 93 | return "stdout output"; 94 | } 95 | /** 96 | * 获取进程错误输出(若进程还未结束需要等待) 97 | */ 98 | function stderr():string { 99 | return "stderr output"; 100 | } 101 | /** 102 | * 与底层进程对象脱离(不再绑定生命周期) 103 | */ 104 | function detach() { 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /doc/tcp.php: -------------------------------------------------------------------------------- 1 | ["b" => 123]]; 43 | * var_dump(flame\get($a, "a.b")); // 123 44 | * ``` 45 | * @return mixed 46 | * 47 | * 注意: 48 | * 1. 数字下标将被当作文本处理; 49 | */ 50 | function get($array, $keys) {} 51 | /** 52 | * 设置数组的层级键值 53 | * @example 示例: 54 | * ``` 55 | * $a = []; 56 | * flame\set($a, "a.b", 123); 57 | * var_dump($a); // ["a" => ["b" => 123]] 58 | * ``` 59 | * 60 | * 注意: 61 | * 1. 数字下标将被当作文本处理; 62 | */ 63 | function set(&$array, $keys, $value) {} 64 | 65 | -------------------------------------------------------------------------------- /src/core/basic_snowflake.cpp: -------------------------------------------------------------------------------- 1 | #include "basic_snowflake.h" 2 | #include "context.h" 3 | 4 | namespace core { 5 | // 6 | basic_snowflake::basic_snowflake(int16_t node, int64_t epoch) 7 | : parts_ { 0, node % 1024, 0 } 8 | , epoch_(epoch) { 9 | std::uniform_int_distribution dist(0, 1 << 11); 10 | parts_.seq = dist($context->random) & 0x0fffl; 11 | } 12 | 13 | int64_t basic_snowflake::next_id() { 14 | parts_.time = static_cast(clock::get_const_instance()) - epoch_; 15 | ++parts_.seq; // 非单线程环境可能重复 16 | return flake_; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/core/basic_snowflake.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_SNOWFLAKE_H 2 | #define CORE_SNOWFLAKE_H 3 | 4 | #include "vendor.h" 5 | 6 | namespace core { 7 | 8 | class basic_snowflake { 9 | public: 10 | static const int64_t default_epoch = 1288834974657; 11 | explicit basic_snowflake(int16_t node, int64_t epoch = default_epoch); 12 | std::int64_t next_id(); 13 | 14 | protected: 15 | union { 16 | struct { 17 | std::int64_t time:42; 18 | std::int64_t node:10; 19 | std::int64_t seq:12; 20 | } parts_; 21 | int64_t flake_; 22 | }; 23 | std::int64_t epoch_; 24 | }; 25 | } 26 | #endif // CORE_SNOWFLAKE_H 27 | -------------------------------------------------------------------------------- /src/core/clock.cpp: -------------------------------------------------------------------------------- 1 | #include "clock.h" 2 | 3 | namespace core { 4 | // 初始化时间计算参照 5 | clock::clock() 6 | : sys_(std::chrono::system_clock::now()) 7 | , sty_(std::chrono::steady_clock::now()) {} 8 | // 时间点 9 | clock::operator std::chrono::system_clock::time_point() const { 10 | return (std::chrono::steady_clock::now() - sty_) + sys_; 11 | } 12 | // 时间点 13 | std::chrono::system_clock::time_point clock::time_point() const { 14 | return (std::chrono::steady_clock::now() - sty_) + sys_; 15 | } 16 | // 毫秒(时间戳) 17 | clock::operator std::int64_t() const { 18 | return std::chrono::duration_cast( 19 | static_cast(*this).time_since_epoch() 20 | ).count(); 21 | } 22 | // 格式化时间 23 | std::string clock::format(const char* style) const { 24 | std::string s(19, '\0'); 25 | format(s.data(), style); 26 | return s; 27 | } 28 | // 格式化时间 29 | void clock::format(char* buffer, const char* style) const { 30 | auto tp = static_cast(*this); 31 | auto tt = std::chrono::system_clock::to_time_t(tp); 32 | fmt::format_to(buffer, style, *std::localtime(&tt)); 33 | } 34 | // Linux 时间戳 35 | std::time_t clock::epoch() const { 36 | return std::chrono::system_clock::to_time_t( 37 | static_cast(*this)); 38 | } 39 | 40 | // YYYY-MM-DDTHH:II:SS.SSSZ 41 | std::string clock::utc() const { 42 | auto now = time_point(); 43 | std::time_t tt = std::chrono::system_clock::to_time_t(now); 44 | std::tm* tm = std::gmtime(&tt); 45 | return fmt::format("{:%FT%H:%M}:{:%S}Z", *tm, now.time_since_epoch()); 46 | } 47 | // YYYY-MM-DD HH:II:SS 48 | std::string clock::iso() const { 49 | auto now = time_point(); 50 | std::time_t tt = std::chrono::system_clock::to_time_t(now); 51 | std::tm* tm = std::gmtime(&tt); 52 | return fmt::format("{:%F %T}", *tm); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/core/clock.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_CLOCK_H 2 | #define CORE_CLOCK_H 3 | 4 | #include "vendor.h" 5 | 6 | namespace core { 7 | 8 | // 常用辅助函数:时间 9 | class clock: public boost::serialization::singleton { 10 | public: 11 | // 初始化时间计算参照 12 | clock(); 13 | // 时间点 14 | operator std::chrono::system_clock::time_point() const; 15 | // 时间点 16 | std::chrono::system_clock::time_point time_point() const; 17 | // 毫秒 18 | operator std::int64_t() const; 19 | // 格式化(本地时间) 20 | std::string format(const char* style = "%Y-%m-%d %H:%M:%S") const; 21 | // 格式化(本地时间) 22 | void format(char* buffer, const char* style = "%Y-%m-%d %H:%M:%S") const; 23 | // Linux 时间戳 24 | std::time_t epoch() const; 25 | // 标准时间:YYYY-MM-DDTHH:II:SS.MSZ 26 | std::string utc() const; 27 | // 标准时间:YYYY-MM-DD HH:II:SS 28 | std::string iso() const; 29 | private: 30 | std::chrono::system_clock::time_point sys_; // 系统时间参照 31 | std::chrono::steady_clock::time_point sty_; // 稳定时间参照 32 | }; 33 | } 34 | 35 | #endif // CORE_CLOCK_H 36 | -------------------------------------------------------------------------------- /src/core/cluster.h: -------------------------------------------------------------------------------- 1 | /* PHP 进程管理 */ 2 | #ifndef CORE_CLUSTER_H 3 | #define CORE_CLUSTER_H 4 | 5 | #include "vendor.h" 6 | 7 | namespace core { 8 | // 进程组 9 | class cluster { 10 | public: 11 | // 12 | static unsigned int evaluate_child_process_count(); 13 | class child_process { 14 | // 工作进程命令行 15 | std::string cmd_; 16 | // 子进程 17 | std::vector> child_; 18 | // 启动指定子进程 19 | void on_fork(int index); 20 | // 指定子进程退出 21 | void on_exit(int index, int exit_code); 22 | public: 23 | // 启动 24 | child_process(unsigned int count, const std::string& cmd); 25 | // 停止 26 | ~child_process(); 27 | 28 | // 强制停止(立即终止所有工作,应仅在主进程调用) 29 | void stop(); 30 | }; 31 | // 工作线程(子进程内有效) 32 | class worker_thread { 33 | std::vector worker_; 34 | // 防止工作线程退出 35 | boost::asio::executor_work_guard guard_; 36 | public: 37 | worker_thread(unsigned int count); 38 | 39 | ~worker_thread(); 40 | }; 41 | 42 | class handle_signal { 43 | public: 44 | handle_signal(child_process* cp, worker_thread* wt); 45 | private: 46 | // Zend 引擎内置了信号处理流程,这里不能接管 47 | // boost::asio::signal_set set_; 48 | }; 49 | }; 50 | } 51 | 52 | 53 | #endif // CORE_CLUSTER_H -------------------------------------------------------------------------------- /src/core/context.cpp: -------------------------------------------------------------------------------- 1 | #include "context.h" 2 | 3 | namespace core { 4 | 5 | std::unique_ptr $context { new context() }; 6 | // 7 | context::context() 8 | // TODO 是否获取自定义的 PHP 配置中种子值? 9 | : env(boost::this_process::environment()) 10 | , random(reinterpret_cast(this)) { 11 | 12 | } 13 | // 14 | bool context::in_state(state_t s) { 15 | switch(s) { 16 | default: 17 | return (status.state & static_cast(s)) > 0; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/core/context.h: -------------------------------------------------------------------------------- 1 | #ifndef CPP_CORE_CONTROLLER_H 2 | #define CPP_CORE_CONTROLLER_H 3 | 4 | #include "coroutine.h" 5 | #include "clock.h" 6 | #include "logger.h" 7 | 8 | namespace core { 9 | 10 | class context; 11 | extern std::unique_ptr $context; 12 | // 工作上下文 13 | class context { 14 | public: 15 | enum state_t { 16 | STATE_UNKNOWN = 0, 17 | STATE_INIT = 0x01, 18 | STATE_STOP = 0x02, 19 | STATE_ERROR = 0x04, 20 | }; 21 | // 运行状态 22 | struct runtime_status_t { 23 | pid_t ppid; // 父进程 ID 24 | pid_t pid; // 进程 ID 25 | std::string host; 26 | int state; // 整体状态 27 | } status; 28 | // 环境变量 29 | boost::process::environment env; 30 | // 选项配置 31 | struct option_t { 32 | // 服务配置 33 | struct service_t { 34 | std::string name; // 服务名称 35 | } service; 36 | // 日志输出 37 | struct logger_t { 38 | core::logger::severity_t severity; // beyond which log record will be discarded 39 | std::vector target; 40 | } logger; 41 | } option; 42 | // 随机引擎 43 | std::default_random_engine random; 44 | // 默认上下文(主线程,导出提供其他扩展使用) 45 | boost::asio::io_context io_m; 46 | // 支持上下文(辅线程,导出提供其他扩展使用) 47 | boost::asio::io_context io_w; 48 | // 默认日志 49 | std::unique_ptr logger; 50 | public: 51 | // 52 | context(); 53 | // 状态判定 54 | [[nodiscard]] bool in_state(state_t s); 55 | // 判定是否为主进程 56 | [[nodiscard]] bool is_main() { 57 | return env.count("FLAME_CUR_WORKER") == 0; 58 | } 59 | // 协程辅助 60 | template 61 | void co_sleep(std::chrono::milliseconds ms, CoroutineHandlerT&& ch, boost::asio::io_context& io = $context->io_m) { 62 | #ifndef NDEBUG 63 | assert(io.get_executor() == ch.executor()); 64 | #endif 65 | boost::asio::steady_timer tm { io }; 66 | tm.expires_after(ms); 67 | tm.async_wait(ch); 68 | } 69 | // 事件辅助 70 | template 71 | void ev_after(std::chrono::milliseconds ms, Handler&& cb, boost::asio::io_context& io = $context->io_m) { 72 | auto tm = std::make_unique(io); 73 | tm->expires_after(ms); 74 | tm->async_wait([tm = std::move(tm), cb = std::move(cb)] (const boost::system::error_code& error) { 75 | cb(); 76 | }); 77 | } 78 | }; 79 | 80 | } 81 | 82 | #endif // CPP_CORE_CONTROLLER_H 83 | -------------------------------------------------------------------------------- /src/core/default_sink.cpp: -------------------------------------------------------------------------------- 1 | #include "default_sink.h" 2 | #include "context.h" 3 | #include "clock.h" 4 | 5 | namespace core { 6 | // 7 | default_sink::default_sink(int fd) 8 | : fd_(fd) { 9 | 10 | } 11 | // 12 | void default_sink::prepare(std::ostream& os, logger::severity_t level) const { 13 | auto tp = clock::get_const_instance().time_point(); 14 | std::time_t tt = std::chrono::system_clock::to_time_t(tp); 15 | std::tm* tm = std::gmtime(&tt); 16 | // 尽量与 syslog 的形式保持一致 17 | // priority datetime hostname app pid message_id structured_data message 18 | fmt::print(os, "<{}> {:%FT%H:%M}T{:%S}Z php-flame/{} {} {} ", 19 | logger::severity_str[static_cast(level)], 20 | *tm, tp.time_since_epoch(), 21 | $context->status.host, 22 | $context->option.service.name, $context->status.pid); 23 | } 24 | // 25 | void default_sink::write(const char* data, std::size_t size) const { 26 | ::write(fd_, data, size); 27 | } 28 | // 0764 29 | const int file_sink::file_mode = S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH; 30 | 31 | file_sink::file_sink(std::string_view path) 32 | : default_sink(::open(path.data(), O_APPEND | O_CREAT, S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH)) { 33 | 34 | } 35 | } -------------------------------------------------------------------------------- /src/core/default_sink.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_LOG_DEFAULT_SINK_H 2 | #define CORE_LOG_DEFAULT_SINK_H 3 | 4 | #include "logger.h" 5 | #include "basic_snowflake.h" 6 | 7 | namespace core { 8 | // 默认日志输出目标,系统 ::write 提供文件描述符的原子型写入 9 | class default_sink: public logger::basic_sink { 10 | public: 11 | virtual void prepare(std::ostream& os, logger::severity_t severity) const override; 12 | virtual void write(const char* data, std::size_t size) const override; 13 | protected: 14 | default_sink(int fd); 15 | int fd_; 16 | friend class logger; 17 | }; 18 | // 标准输出 19 | class stdout_sink: public default_sink { 20 | public: 21 | stdout_sink() 22 | : default_sink(1) {} 23 | }; 24 | // 错误输出 25 | class stderr_sink: public default_sink { 26 | public: 27 | stderr_sink() 28 | : default_sink(2) {} 29 | }; 30 | // 提供文件数据写入支持 31 | class file_sink: public default_sink { 32 | public: 33 | file_sink(std::string_view path); 34 | // 文件模式 0764 35 | static const int file_mode; 36 | }; 37 | } 38 | 39 | #endif // CORE_LOG_DEFAULT_SINK_H 40 | -------------------------------------------------------------------------------- /src/core/exception.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_EXCEPTION_H 2 | #define CORE_EXCEPTION_H 3 | 4 | #include "vendor.h" 5 | 6 | namespace core { 7 | 8 | class unknown_error: public std::exception { 9 | public: 10 | unknown_error(const std::string& msg) 11 | : msg_(msg) {} 12 | const char* what() const noexcept { 13 | return msg_.c_str(); 14 | } 15 | private: 16 | std::string msg_; 17 | }; 18 | template 19 | [[noreturn]] void raise(F&& format, Args&&... args) { 20 | throw unknown_error( fmt::format(std::forward(format), std::forward(args)...) ); 21 | } 22 | } 23 | 24 | #endif // CORE_EXCEPTION_H 25 | -------------------------------------------------------------------------------- /src/core/extension/core.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_EXTENSION_CORE_H 2 | #define CORE_EXTENSION_CORE_H 3 | 4 | #include "../vendor.h" 5 | 6 | namespace core { namespace extension { 7 | 8 | class core { 9 | public: 10 | static void declare(php::module_entry& entry); 11 | private: 12 | // 框架入口(启动“主”协程) 13 | static php::value run(php::parameters& params); 14 | // 启动协程 15 | static php::value go(php::parameters& params); 16 | // 框架消息· 17 | static php::value on(php::parameters& params); 18 | }; 19 | 20 | }} 21 | 22 | #endif // CORE_EXTENSION_CORE_H 23 | 24 | -------------------------------------------------------------------------------- /src/core/extension/coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include "coroutine.h" 2 | #include "../context.h" 3 | 4 | namespace core { namespace extension { 5 | // 参考 zend_execute.c 6 | static zend_vm_stack vm_stack_new_page(size_t size, zend_vm_stack prev) { 7 | zend_vm_stack page = (zend_vm_stack)emalloc(size); 8 | 9 | page->top = ZEND_VM_STACK_ELEMENTS(page); 10 | page->end = (zval *)((char *)page + size); 11 | page->prev = prev; 12 | return page; 13 | } 14 | // 参考 zend_execute.c 15 | void coroutine_traits::init_context(coroutine_context &ctx) { 16 | ctx.vm_stack = vm_stack_new_page(COROUTINE_PHP_STACK_SIZE, NULL); 17 | ++ctx.vm_stack->top; 18 | ctx.vm_stack_top = ctx.vm_stack->top; 19 | ctx.vm_stack_end = ctx.vm_stack->end; 20 | 21 | ctx.current_execute_data = nullptr; 22 | ctx.error_handling = EH_NORMAL; 23 | ctx.exception_class = nullptr; 24 | ctx.exception = nullptr; 25 | } 26 | // 将当前上下文保存到参数容器中 27 | void coroutine_traits::save_context(coroutine_context &ctx) { 28 | ctx.vm_stack = EG(vm_stack); 29 | ctx.vm_stack_top = EG(vm_stack_top); 30 | ctx.vm_stack_end = EG(vm_stack_end); 31 | // ctx.scope = EG(fake_scope); 32 | ctx.current_execute_data = EG(current_execute_data); 33 | ctx.exception = EG(exception); 34 | ctx.exception_class = EG(exception_class); 35 | ctx.error_handling = EG(error_handling); 36 | } 37 | // 从参数容器中恢复上下文 38 | void coroutine_traits::restore_context(coroutine_context &ctx) { 39 | EG(vm_stack) = ctx.vm_stack; 40 | EG(vm_stack_top) = ctx.vm_stack_top; 41 | EG(vm_stack_end) = ctx.vm_stack_end; 42 | // EG(fake_scope) = ctx.scope; 43 | EG(current_execute_data) = ctx.current_execute_data; 44 | EG(exception) = ctx.exception; 45 | EG(exception_class) = ctx.exception_class; 46 | EG(error_handling) = ctx.error_handling; 47 | } 48 | 49 | coroutine_context coroutine_traits::gtx_; 50 | unsigned int coroutine_traits::cnt_ = 0u; 51 | 52 | void coroutine_traits::start() { 53 | ++cnt_; 54 | init_context(ctx_); 55 | restore_context(ctx_); 56 | } 57 | 58 | void coroutine_traits::end() { 59 | --cnt_; 60 | zend_vm_stack_destroy(); // 相当于对 ctx_ 操作 61 | restore_context(gtx_); 62 | } 63 | 64 | void coroutine_traits::yield() { 65 | save_context(ctx_); 66 | restore_context(gtx_); 67 | } 68 | 69 | void coroutine_traits::resume() { 70 | save_context(gtx_); 71 | restore_context(ctx_); 72 | } 73 | }} -------------------------------------------------------------------------------- /src/core/extension/coroutine.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_EXTENSION_COROUTINE_H 2 | #define CORE_EXTENSION_COROUTINE_H 3 | 4 | #include "../vendor.h" 5 | #include "../coroutine.h" 6 | 7 | namespace core { namespace extension { 8 | 9 | // 自定义的栈,为控制栈空间大小 php 64k + 32k 10 | #define COROUTINE_PHP_STACK_SIZE 4 * sizeof(zval) * 1024 11 | #define COROUTINE_CPP_STACK_SIZE 32 * 1024 12 | 13 | struct coroutine_context { 14 | zend_vm_stack vm_stack; 15 | zval *vm_stack_top; 16 | zval *vm_stack_end; 17 | zend_class_entry *scope; 18 | zend_execute_data *current_execute_data; 19 | 20 | zend_object * exception; 21 | zend_error_handling_t error_handling; 22 | zend_class_entry * exception_class; 23 | }; 24 | // 25 | class coroutine_traits { 26 | public: 27 | static unsigned int cnt_; // 活跃协程数量 28 | static coroutine_context gtx_; // 全局运行状态,用于进入退出 29 | coroutine_context ctx_; // 当前协程运行状态,用于恢复 30 | 31 | // 初始化上下文 32 | static void init_context(coroutine_context &ctx); 33 | // 将当前上下文保存到参数容器中 34 | static void save_context(coroutine_context &ctx); 35 | // 从参数容器中恢复上下文 36 | static void restore_context(coroutine_context &ctx); 37 | public: 38 | void start(); 39 | void yield(); 40 | void resume(); 41 | void end(); 42 | }; 43 | 44 | using coroutine = basic_coroutine; 45 | using coroutine_handler = basic_coroutine_handler; 46 | }} 47 | 48 | 49 | namespace boost::asio { 50 | template <> 51 | class async_result<::core::extension::coroutine_handler, void (boost::system::error_code error, std::size_t size)> { 52 | public: 53 | explicit async_result(::core::extension::coroutine_handler& ch) : ch_(ch), size_(0) { 54 | ch_.count_ = &size_; 55 | } 56 | using completion_handler_type = ::core::extension::coroutine_handler; 57 | using return_type = std::size_t; 58 | return_type get() { 59 | ch_.yield(); 60 | return size_; 61 | } 62 | private: 63 | ::core::extension::coroutine_handler &ch_; 64 | std::size_t size_; 65 | }; 66 | 67 | template <> 68 | class async_result<::core::extension::coroutine_handler, void (boost::system::error_code error)> { 69 | public: 70 | explicit async_result(::core::extension::coroutine_handler& ch) : ch_(ch) { 71 | } 72 | using completion_handler_type = ::core::extension::coroutine_handler; 73 | using return_type = void; 74 | void get() { 75 | ch_.yield(); 76 | } 77 | private: 78 | ::core::extension::coroutine_handler &ch_; 79 | }; 80 | } // namespace boost::asio 81 | 82 | #endif // CORE_EXTENSION_COROUTINE_H 83 | -------------------------------------------------------------------------------- /src/core/extension/extension.cpp: -------------------------------------------------------------------------------- 1 | #include "../context.h" 2 | #include "core.h" 3 | #include "time.h" 4 | #include "util.h" 5 | #include "log.h" 6 | 7 | extern "C" { 8 | // PHP 扩展模块入口 9 | ZEND_DLEXPORT zend_module_entry* get_module() { 10 | // 构建模块:在离开函数后保留 module 定义 11 | static php::module_entry entry("flame-core", "v0.18.0"); 12 | entry 13 | .declare(); 14 | if(core::$context->is_main()) 15 | return entry; 16 | 17 | // 仅在工作进程生成注册相关函数功能 18 | entry 19 | .declare() 20 | .declare() 21 | .declare(); 22 | return entry; 23 | } 24 | } -------------------------------------------------------------------------------- /src/core/extension/log.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_LOG_H 2 | #define CORE_LOG_H 3 | 4 | #include "../vendor.h" 5 | 6 | namespace core { namespace extension { 7 | 8 | class log { 9 | public: 10 | static void declare(php::module_entry& entry); 11 | }; 12 | 13 | }} 14 | 15 | #endif // CORE_LOG_H 16 | -------------------------------------------------------------------------------- /src/core/extension/time.cpp: -------------------------------------------------------------------------------- 1 | #include "time.h" 2 | #include "coroutine.h" 3 | #include "../clock.h" 4 | #include "../context.h" 5 | 6 | namespace core { namespace extension { 7 | 8 | void time::declare(php::module_entry& entry) { 9 | entry 10 | - php::function("flame\\time\\now", { 11 | { php::TYPE_STRING }, 12 | }) 13 | - php::function("flame\\time\\iso", { 14 | { php::TYPE_STRING }, 15 | }) 16 | - php::function("flame\\time\\utc", { 17 | { php::TYPE_STRING }, 18 | }) 19 | - php::function("flame\\time\\sleep", { 20 | { php::FAKE_VOID }, 21 | { php::TYPE_INTEGER, "ms" }, // milliseconds 22 | }) 23 | ; 24 | } 25 | // 获取当前时间戳(毫秒) 26 | php::value time::now(php::parameters& params) { 27 | return static_cast(clock::get_const_instance()); 28 | } 29 | // 当前时间串 30 | php::value time::iso(php::parameters& params) { 31 | return clock::get_const_instance().iso(); 32 | } 33 | // 当前时间串 34 | php::value time::utc(php::parameters& params) { 35 | return clock::get_const_instance().utc(); 36 | } 37 | // 暂定当前协程,并在若干时间后恢复 38 | php::value time::sleep(php::parameters& params) { 39 | int ms = std::max(static_cast(params[0]), 1); 40 | $context->co_sleep(std::chrono::milliseconds(ms), coroutine_handler {coroutine::current()}); 41 | return nullptr; 42 | } 43 | }} -------------------------------------------------------------------------------- /src/core/extension/time.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_EXTENSION_TIME_H 2 | #define CORE_EXTENSION_TIME_H 3 | 4 | #include "../vendor.h" 5 | 6 | namespace core { namespace extension { 7 | // 8 | class time { 9 | public: 10 | static void declare(php::module_entry& entry); 11 | private: 12 | static php::value now(php::parameters& params); // milliseconds 13 | static php::value iso(php::parameters& params); // yyyy-mm-dd hh:ii:ss 14 | static php::value utc(php::parameters& params); // yyyy-mm-ddThh:ii:ssZ 15 | static php::value sleep(php::parameters& params); 16 | }; 17 | }} 18 | 19 | #endif // CORE_EXTENSION_TIME_H 20 | -------------------------------------------------------------------------------- /src/core/extension/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "../context.h" 3 | namespace core { namespace extension { 4 | 5 | void util::declare(php::module_entry& entry) { 6 | entry 7 | .declare(); 8 | entry 9 | - php::function("flame\\util\\unique_id", { 10 | { php::TYPE_INTEGER }, // 返回: 无 11 | { php::TYPE_INTEGER, "node" }, // 节点 12 | { php::TYPE_INTEGER | php::ALLOW_NULL, "epoch" }, // 可选,时间原点 13 | }) 14 | - php::function("flame\\util\\get", { 15 | { php::FAKE_MIXED }, // 返回值,取决于实际获取到的数据 16 | { php::TYPE_ARRAY, "array"}, // 源数组 17 | { php::TYPE_STRING, "keys" }, // 操作 KEY 18 | }) 19 | - php::function("flame\\util\\set", { 20 | { php::FAKE_MIXED }, // 返回值,取决于实际获取到的数据 21 | { php::TYPE_ARRAY | php::BYREF, "array"}, // 源数组(引用) 22 | { php::TYPE_STRING, "keys"}, // 操作 KEY 23 | { php::FAKE_MIXED, "value"}, // 设置值 24 | }); 25 | } 26 | // 27 | void util::snowflake::declare(php::module_entry& entry) { 28 | entry.declare("flame\\util\\snowflake") 29 | - php::method<&snowflake::__construct>("__construct", { 30 | { php::FAKE_NONE }, // OR TYPE_UNDEFINED 31 | { php::TYPE_INTEGER | php::ALLOW_NULL, "node" }, 32 | { php::TYPE_INTEGER | php::ALLOW_NULL, "epoch", PHP_DEFAULT_VALUE(1288834974657)}, // 注意这里的常量须与 C++ 定义统一 33 | }) 34 | - php::method<&snowflake::next_id>("next_id", { 35 | { php::TYPE_INTEGER }, 36 | }); 37 | } 38 | // 唯一 id 生成 39 | php::value util::unique_id(php::parameters& params) { 40 | // desprecated use flame\snowflake class instead; 41 | basic_snowflake s { static_cast($context->status.pid) }; 42 | return s.next_id(); 43 | } 44 | // 简化多级 KEY 数组读取 45 | php::value util::get(php::parameters& params) { 46 | return php::array::get(params[0], params[1]); 47 | } 48 | // 简化多级 KEY 数组读取 49 | php::value util::set(php::parameters& params) { 50 | php::array::set(params[0], params[1], params[2]); 51 | return nullptr; 52 | } 53 | // 54 | util::snowflake::snowflake() 55 | : basic_snowflake(0) {} 56 | // 对可选参数处理,并填充基类数据 57 | php::value util::snowflake::__construct(php::parameters& params) { 58 | parts_.node = static_cast(params[0]) % 1024; 59 | if(params.size() > 1) 60 | epoch_ = static_cast(params[1]); 61 | return nullptr; 62 | } 63 | // 导出代理基类函数 64 | php::value util::snowflake::next_id(php::parameters& params) { 65 | return basic_snowflake::next_id(); 66 | } 67 | }} -------------------------------------------------------------------------------- /src/core/extension/util.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_EXTENSION_UTIL_H 2 | #define CORE_EXTENSION_UTIL_H 3 | 4 | #include "../vendor.h" 5 | #include "../basic_snowflake.h" 6 | 7 | namespace core { namespace extension { 8 | 9 | class util { 10 | public: 11 | static void declare(php::module_entry& entry); 12 | private: 13 | // 获取一个 snowflake 兼容的唯一标识 14 | static php::value unique_id(php::parameters& params); 15 | // 简化多级 KEY 数组读取 16 | static php::value get(php::parameters& params); 17 | // 简化多级 KEY 数组读取 18 | static php::value set(php::parameters& params); 19 | // 封装 snowflake 兼容的简化版本唯一标识生成算法 20 | class snowflake: public basic_snowflake { 21 | public: 22 | static void declare(php::module_entry& entry); 23 | snowflake(); 24 | // function __construct(int $node = null, int $epoch = null); 25 | php::value __construct(php::parameters& params); 26 | // function next_id() 27 | php::value next_id(php::parameters& params); 28 | }; 29 | }; 30 | 31 | }} 32 | 33 | #endif // CORE_EXTENSION_UTIL_H 34 | -------------------------------------------------------------------------------- /src/core/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "logger.h" 2 | #include "default_sink.h" 3 | #include "syslog_sink.h" 4 | #include "context.h" 5 | #include "exception.h" 6 | 7 | namespace core { 8 | 9 | const std::string logger::severity_str[] = { 10 | "EMERGENCY", 11 | "ALERT", 12 | "CRITICAL", 13 | "ERROR", 14 | "WARNING", 15 | "NOTICE", 16 | "INFO", 17 | "DEBUG", 18 | }; 19 | // 创建默认控制台输出 20 | logger::logger() { 21 | sink_.emplace_back(new default_sink(1)); 22 | } 23 | // 项日志加入一项输出目标 24 | void logger::add_sink(std::unique_ptr sink) { 25 | // 初始状态由框架构建了默认的输出,当用户自行指定时,替换原有输出 26 | if(sink_.size() == 1 && dynamic_cast(sink_.front().get())) { 27 | sink_.clear(); 28 | } 29 | sink_.emplace_back(std::move(sink)); 30 | } 31 | 32 | std::unique_ptr logger::make_sink(const std::string_view& target) { 33 | if(target[0] == '.' || target[0] == '/') // 形如 ./xxxx ../xxxx /xxxx 表示文件路径 34 | return std::unique_ptr(new file_sink(target)); 35 | else if(target[0] == 's') { 36 | if(target == "stdout") 37 | return std::unique_ptr(new stdout_sink()); 38 | else if(target == "stderr") 39 | return std::unique_ptr(new stderr_sink()); 40 | else if(target == "syslog") 41 | return std::unique_ptr(new syslog_sink($context->option.service.name)); 42 | } 43 | else if(target == "console") 44 | return std::unique_ptr(new stdout_sink()); 45 | else if(target == "error") 46 | return std::unique_ptr(new stderr_sink()); 47 | 48 | raise("failed to create log sink target '{}' unknown", target); 49 | } 50 | } -------------------------------------------------------------------------------- /src/core/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_LOGGER_H 2 | #define CORE_LOGGER_H 3 | 4 | #include "vendor.h" 5 | 6 | namespace core { 7 | // 日志 8 | class logger { 9 | public: 10 | // 信息严重程度 (0~7) 11 | enum class severity_t { 12 | EMERGENCY = LOG_EMERG, // 0 13 | ALERT = LOG_ALERT, // 1 14 | CRITICAL = LOG_CRIT, // 2 15 | ERROR = LOG_ERR, // 3 16 | WARNING = LOG_WARNING, // 4 17 | NOTICE = LOG_NOTICE, // 5 18 | INFO = LOG_INFO, // 6 19 | DEBUG = LOG_DEBUG, // 7 20 | }; 21 | class sbuffer: public std::basic_stringbuf { 22 | public: 23 | const char* data() { 24 | return pbase(); 25 | } 26 | std::size_t size() { 27 | // 参考 stringbuf::str() 实现 28 | return pptr() > egptr() 29 | ? pptr() - pbase() 30 | : pptr() - egptr(); 31 | } 32 | }; 33 | // 信息严重程度对应文本 34 | static const std::string severity_str[8]; 35 | // 日志的存储目标 36 | class basic_sink { 37 | public: 38 | virtual void prepare(std::ostream& os, severity_t severity) const = 0; 39 | virtual void write(const char* data, std::size_t size) const = 0; 40 | }; 41 | // 构建默认日志器 42 | logger(); 43 | // 项日志加入一项输出目标 44 | void add_sink(std::unique_ptr sink); 45 | // 46 | void add_sink(const std::string_view& target) { 47 | add_sink(make_sink(target)); 48 | } 49 | // 50 | template 51 | void write(severity_t severity, Callback&& writer) const { 52 | for(auto& sink: sink_) { 53 | sbuffer sb; 54 | std::ostream os{&sb}; 55 | sink->prepare(os, severity); 56 | writer(os); 57 | os.put('\n'); 58 | sink->write(sb.data(), sb.size()); 59 | } 60 | } 61 | // 62 | static std::unique_ptr make_sink(const std::string_view& target); 63 | private: 64 | // 日志的输出目标 65 | std::vector> sink_; 66 | }; 67 | } 68 | 69 | #endif // CORE_LOGGER_H 70 | -------------------------------------------------------------------------------- /src/core/parse_url.cpp: -------------------------------------------------------------------------------- 1 | #include "parse_url.h" 2 | #include 3 | 4 | namespace core { 5 | // 6 | static std::regex r { R"regex(^([^:]+)://(([^:@]+)(:([^@]+))?@)?([^:/]+)(:([^/]+))?(/[^\?]+)?(\?.+)?$)regex" }; 7 | // 用于解析 URL 字符串 8 | parsed_url parse_url(std::string_view url) { // 原始类型 port 初始值可能随机 9 | std::cmatch m; 10 | if(!std::regex_match(url.begin(), url.end(), m, r)) { 11 | throw std::runtime_error("failed to parse url: ill-formed"); 12 | } 13 | return std::move(m); 14 | } 15 | } -------------------------------------------------------------------------------- /src/core/parse_url.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_URL_H 2 | #define CORE_URL_H 3 | 4 | #include "vendor.h" 5 | 6 | namespace core { 7 | 8 | class parsed_url { 9 | public: 10 | parsed_url(std::cmatch&& m) 11 | : m_(std::move(m)) {} 12 | 13 | inline std::string_view scheme() const { 14 | return gm(m_[1]); 15 | } 16 | inline std::string_view user() const { 17 | return gm(m_[3]); 18 | } 19 | inline std::string_view pass() const { 20 | return gm(m_[5]); 21 | } 22 | inline std::string_view host() const { 23 | return gm(m_[6]); 24 | } 25 | inline std::string_view port() const { 26 | return gm(m_[8]); 27 | } 28 | inline std::string_view path() const { 29 | return gm(m_[9]); 30 | } 31 | inline std::string_view query() const { 32 | return gm(m_[10]); 33 | } 34 | private: 35 | std::cmatch m_; 36 | // 读取匹配子组对应字符串视图 37 | std::string_view gm(const std::csub_match& m) const { 38 | return {m.first, static_cast(m.second - m.first)}; 39 | } 40 | }; 41 | 42 | // 解析或生成 URL 字符串 43 | parsed_url parse_url(std::string_view url); 44 | } 45 | #endif // CORE_URL_H 46 | -------------------------------------------------------------------------------- /src/core/random_string.cpp: -------------------------------------------------------------------------------- 1 | #include "random_string.h" 2 | #include "context.h" 3 | 4 | namespace core { 5 | 6 | random_string::random_string(const std::string& table) 7 | : stable_( table ) 8 | , distri_( 0, stable_.size() ) { 9 | 10 | } 11 | // 使用指定码表构建指定长度的随机字符串 12 | std::string random_string::make(std::size_t size) const { 13 | std::string s(size, '\0'); 14 | make(s.data(), size); 15 | return s; 16 | } 17 | // 在指定缓存空间中生成(可临时调整生成长度) 18 | void random_string::make(char* const buffer, std::size_t size) const { 19 | for(int i=0;irandom) % size; 21 | buffer[i] = stable_[r]; 22 | } 23 | buffer[size] = '\0'; 24 | } 25 | // 在公共缓存空间中生成(可临时调整生成长度) 26 | std::string_view random_string::build(std::size_t size) const { 27 | static char buffer[256]; 28 | make(buffer, size); 29 | return std::string_view(buffer, size); 30 | } 31 | } -------------------------------------------------------------------------------- /src/core/random_string.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_RANDOM_H 2 | #define CORE_RANDOM_H 3 | 4 | #include "vendor.h" 5 | 6 | namespace core { 7 | 8 | // 随机字符串 9 | class random_string { 10 | public: 11 | random_string(const std::string& table = 12 | "ABCDEFGHIJKLMKOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); 13 | // 使用指定码表构建指定长度的随机字符串 14 | std::string make(std::size_t size = 16) const; 15 | // 在指定缓存空间中生成(可临时调整生成长度) 16 | void make(char* const buffer, std::size_t size = 16) const; 17 | // 在公共缓存空间中生成(可临时调整生成长度) 18 | std::string_view build(std::size_t size = 16) const; 19 | private: 20 | const std::string& stable_; 21 | mutable std::uniform_int_distribution distri_; 22 | }; 23 | } 24 | 25 | #endif // CORE_RANDOM_H 26 | -------------------------------------------------------------------------------- /src/core/syslog_sink.cpp: -------------------------------------------------------------------------------- 1 | #include "syslog_sink.h" 2 | #include 3 | #include 4 | 5 | namespace core { 6 | 7 | int syslog_sink::option_ = LOG_CONS | LOG_PID; 8 | 9 | syslog_sink::syslog_sink(std::string_view name) { 10 | ::openlog(name.data(), option_, LOG_USER); 11 | } 12 | 13 | syslog_sink::~syslog_sink() { 14 | ::closelog(); 15 | } 16 | 17 | void syslog_sink::prepare(std::ostream& os, logger::severity_t severity) const { 18 | os.put( static_cast(static_cast(severity)) ); 19 | } 20 | void syslog_sink::write(const char* data, std::size_t size) const { 21 | ::syslog(static_cast(data[0]), "%.*s", size-1, data); 22 | } 23 | } -------------------------------------------------------------------------------- /src/core/syslog_sink.h: -------------------------------------------------------------------------------- 1 | #ifndef FLAME_CORE_LOG_SYSLOG_SINK_H 2 | #define FLAME_CORE_LOG_SYSLOG_SINK_H 3 | 4 | #include "logger.h" 5 | 6 | namespace core { 7 | 8 | class syslog_sink: public logger::basic_sink { 9 | public: 10 | syslog_sink(std::string_view name); 11 | ~syslog_sink(); 12 | virtual void prepare(std::ostream& os, logger::severity_t severity) const override; 13 | virtual void write(const char* data, std::size_t size) const override; 14 | private: 15 | static int option_; 16 | }; 17 | } 18 | 19 | #endif // FLAME_CORE_LOG_SYSLOG_SINK_H 20 | -------------------------------------------------------------------------------- /src/core/vendor.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_VENDOR_H 2 | #define CORE_VENDOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include // for constants 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #endif // CORE_VENDOR_H 38 | -------------------------------------------------------------------------------- /src/coroutine_mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coroutine.h" 3 | 4 | class coroutine_mutex { 5 | public: 6 | coroutine_mutex() 7 | : mt_(false) { 8 | 9 | } 10 | 11 | void lock(coroutine_handler& ch) { 12 | while(mt_) { 13 | cm_.insert(&ch); 14 | ch.suspend(); 15 | } 16 | mt_ = true; 17 | } 18 | 19 | bool try_lock() { 20 | if (!mt_) { 21 | mt_ = true; 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | void unlock(bool quiting = false) { 28 | while(!cm_.empty() && !quiting) { 29 | auto ch = cm_.extract(cm_.begin()).value(); 30 | ch->resume(); 31 | } 32 | mt_ = false; 33 | } 34 | private: 35 | std::set cm_; 36 | bool mt_; 37 | }; 38 | class coroutine_guard { 39 | public: 40 | coroutine_guard(coroutine_mutex& cm, coroutine_handler& ch) 41 | : cm_(cm) { 42 | cm_.lock(ch); 43 | } 44 | ~coroutine_guard() { 45 | cm_.unlock(); 46 | } 47 | private: 48 | coroutine_mutex cm_; 49 | }; 50 | -------------------------------------------------------------------------------- /src/coroutine_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "coroutine.h" 3 | 4 | template 5 | class coroutine_queue: private boost::noncopyable { 6 | public: 7 | coroutine_queue(std::size_t n = 1) 8 | : n_(n) 9 | , closed_(false) { 10 | } 11 | 12 | // !!!! 生产者进行关闭 !!!! 13 | void close() { 14 | closed_ = true; 15 | while (!c_.empty()) { 16 | auto ch = c_.extract(c_.begin()).value(); 17 | ch->resume(); 18 | } 19 | } 20 | bool is_closed() { 21 | return q_.empty() && closed_; 22 | } 23 | 24 | void push(const T& t, coroutine_handler& ch) { 25 | if (closed_) throw php::exception(zend_ce_error_exception 26 | , "Failed to push queue: already closed" 27 | , -1); 28 | if (q_.size() >= n_) { 29 | p_.insert(&ch); 30 | ch.suspend(); 31 | } 32 | q_.push_back(t); 33 | while(!q_.empty() && !c_.empty()) { 34 | auto ch = c_.extract(c_.begin()).value(); 35 | ch->resume(); 36 | } 37 | } 38 | 39 | std::optional pop(coroutine_handler& ch) { 40 | for(;;) { 41 | if (!q_.empty()) break; // 有数据消费 42 | else if (closed_) return std::optional(); // 无数据关闭 43 | else { // 无数据等待 44 | c_.insert(&ch); 45 | ch.suspend(); 46 | } 47 | } 48 | T t = q_.front(); 49 | q_.pop_front(); 50 | 51 | while (!p_.empty()) { 52 | auto ch = p_.extract(p_.begin()).value(); 53 | ch->resume(); 54 | } 55 | return std::optional(t); 56 | } 57 | 58 | /* private: */ 59 | std::size_t n_; 60 | std::list q_; 61 | std::set c_; // 消费者 62 | std::set p_; // 生产者 63 | bool closed_; 64 | }; 65 | 66 | template 67 | std::shared_ptr < coroutine_queue > select_queue(std::vector < std::shared_ptr> > queues, coroutine_handler &ch) { 68 | TRY_ALL: 69 | bool all_closed = true; 70 | for(auto i=queues.begin();i!=queues.end();++i) { 71 | if (!(*i)->q_.empty()) return *i; 72 | else if (!(*i)->closed_) all_closed = false; 73 | } 74 | if (all_closed) return std::shared_ptr< coroutine_queue >(nullptr); 75 | for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(&ch); 76 | ch.suspend(); 77 | for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(&ch); 78 | goto TRY_ALL; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/flame/compress/compress.cpp: -------------------------------------------------------------------------------- 1 | #include "compress.h" 2 | extern "C" { 3 | #include 4 | } 5 | 6 | namespace flame::compress { 7 | 8 | static php::value snappy_compress(php::parameters& params) { 9 | php::string data = params[0].to_string(); 10 | struct iovec iov_in = { 11 | .iov_base = data.data(), 12 | .iov_len = data.size(), 13 | }; 14 | std::size_t len = rd_kafka_snappy_max_compressed_length(data.size()); 15 | php::string out(len); 16 | struct iovec iov_out = { 17 | .iov_base = out.data(), 18 | .iov_len = 0xffffffff, 19 | }; 20 | struct snappy_env env; 21 | rd_kafka_snappy_init_env(&env); 22 | rd_kafka_snappy_compress_iov(&env, &iov_in, 1, data.size(), &iov_out); 23 | rd_kafka_snappy_free_env(&env); 24 | out.shrink(iov_out.iov_len); 25 | return std::move(out); 26 | } 27 | 28 | static php::value snappy_uncompress(php::parameters& params) { 29 | php::string data = params[0].to_string(); 30 | std::size_t len; 31 | rd_kafka_snappy_uncompressed_length(data.c_str(), data.size(), &len); 32 | php::string out(len); 33 | rd_kafka_snappy_uncompress(data.c_str(), data.size(), out.data()); 34 | return std::move(out); 35 | } 36 | 37 | void declare(php::extension_entry &ext) { 38 | ext 39 | .function("flame\\compress\\snappy_compress", { 40 | {"data", php::TYPE::STRING}, 41 | }) 42 | .function("flame\\compress\\snappy_uncompress", { 43 | {"data", php::TYPE::STRING}, 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/flame/compress/compress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::compress { 5 | void declare(php::extension_entry &ext); 6 | } // namespace flame::compress 7 | -------------------------------------------------------------------------------- /src/flame/controller.cpp: -------------------------------------------------------------------------------- 1 | #include "controller.h" 2 | #include "worker.h" 3 | // #include "../logger_master.h" 4 | // #include "../logger_worker.h" 5 | 6 | namespace flame { 7 | // 全局控制器 8 | std::unique_ptr gcontroller; 9 | 10 | controller::controller() 11 | : type(process_type::UNKNOWN) 12 | , env(boost::this_process::environment()) 13 | , status(STATUS_UNKNOWN) 14 | , evnt_cb(new std::multimap()) { 15 | 16 | worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str()); 17 | worker_size = std::min(std::max((int)worker_size, 0), 256); 18 | // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程 19 | if (env.count("FLAME_CUR_WORKER") > 0) { 20 | type = process_type::WORKER; 21 | worker_idx = std::atoi(env["FLAME_CUR_WORKER"].to_string().c_str()); 22 | } 23 | else if (worker_size > 0) type = process_type::MASTER; 24 | else { // 单进程模式 25 | worker_size = 0; 26 | type = process_type::WORKER; 27 | } 28 | mthread_id = std::this_thread::get_id(); 29 | } 30 | 31 | controller *controller::on_init(std::function fn) { 32 | init_cb.push_back(fn); 33 | return this; 34 | } 35 | 36 | void controller::init(php::array options) { 37 | for (auto fn : init_cb) fn(options); 38 | } 39 | 40 | controller* controller::on_stop(std::function fn) { 41 | stop_cb.push_back(fn); 42 | return this; 43 | } 44 | 45 | void controller::stop() { 46 | for (auto fn : stop_cb) fn(); 47 | evnt_cb.reset(); 48 | } 49 | 50 | controller* controller::add_event(const std::string& event, php::callable cb) { 51 | evnt_cb->insert({event, cb}); 52 | return this; 53 | } 54 | 55 | controller* controller::del_event(const std::string& event) { 56 | evnt_cb->erase(event); 57 | return this; 58 | } 59 | 60 | std::size_t controller::cnt_event(const std::string& event) { 61 | return evnt_cb->count(event); 62 | } 63 | 64 | void controller::event(const std::string& event, std::vector params) { 65 | auto ft = evnt_cb->equal_range(event); 66 | for(auto i=ft.first; i!=ft.second; ++i) i->second.call(params); 67 | } 68 | 69 | void controller::event(const std::string& event) { 70 | auto ft = evnt_cb->equal_range(event); 71 | for(auto i=ft.first; i!=ft.second; ++i) i->second.call(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/flame/controller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../vendor.h" 3 | 4 | namespace flame { 5 | 6 | class controller { 7 | public: 8 | boost::asio::io_context context_x; 9 | boost::asio::io_context context_y; 10 | boost::asio::io_context context_z; 11 | enum class process_type { 12 | UNKNOWN = 0, 13 | MASTER = 1, 14 | WORKER = 2, 15 | } type; 16 | boost::process::environment env; 17 | enum status_t { 18 | STATUS_UNKNOWN = 0x00, 19 | STATUS_INITIALIZED = 0x01, 20 | // 通知进程退出 21 | STATUS_CLOSING = 0x02, 22 | // 进程正在退出(强制) 23 | STATUS_QUITING = 0x04, 24 | STATUS_RSETING = 0x08, 25 | STATUS_EXCEPTION = 0x10, 26 | STATUS_RUN = 0x20, 27 | STATUS_CLOSECONN = 0x40, 28 | }; 29 | int status; 30 | std::uint8_t worker_idx; 31 | std::size_t worker_size; 32 | std::size_t worker_quit; // 多进程退出超时时间 33 | std::thread::id mthread_id; 34 | private: 35 | std::list> init_cb; 36 | std::list> stop_cb; 37 | std::unique_ptr> evnt_cb; // 防止 PHP 提前回收, 使用堆容器 38 | public: 39 | controller(); 40 | controller(const controller& c) = delete; 41 | controller *on_init(std::function fn); 42 | void init(php::array options); 43 | controller* on_stop(std::function fn); 44 | void stop(); 45 | controller* add_event(const std::string& event, php::callable cb); 46 | controller* del_event(const std::string& event); 47 | std::size_t cnt_event(const std::string& event); 48 | void event(const std::string& event, std::vector params); 49 | void event(const std::string& event); 50 | }; 51 | 52 | extern std::unique_ptr gcontroller; 53 | 54 | } -------------------------------------------------------------------------------- /src/flame/encoding/encoding.cpp: -------------------------------------------------------------------------------- 1 | #include "encoding.h" 2 | #include "../mongodb/mongodb.h" 3 | 4 | namespace flame::encoding { 5 | static php::value bson_encode(php::parameters ¶ms) { 6 | if (params[0].type_of(php::TYPE::ARRAY)) { 7 | auto doc = flame::mongodb::array2bson(params[0]); 8 | return php::string((const char*)bson_get_data(doc.get()), doc->len); 9 | } 10 | else throw php::exception(zend_ce_type_error 11 | , "Failed to encode: typeof 'array' required" 12 | , -1); 13 | } 14 | 15 | static php::value bson_decode(php::parameters ¶ms) { 16 | php::string data = params[0].to_string(); 17 | bson_t* doc = bson_new_from_data((const uint8_t*)data.data(), data.size()); 18 | if (doc == nullptr) return nullptr; 19 | php::array rv = flame::mongodb::bson2array(doc); 20 | bson_destroy(doc); 21 | return std::move(rv); 22 | } 23 | 24 | void declare(php::extension_entry &ext) { 25 | ext 26 | .function("flame\\encoding\\bson_encode", { 27 | {"data", php::TYPE::ARRAY}, 28 | }) 29 | .function("flame\\encoding\\bson_decode", { 30 | {"data", php::TYPE::STRING}, 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/flame/encoding/encoding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::encoding { 5 | void declare(php::extension_entry &ext); 6 | } // namespace flame::encoding 7 | -------------------------------------------------------------------------------- /src/flame/flame.cpp: -------------------------------------------------------------------------------- 1 | #include "../vendor.h" 2 | #include "../util.h" 3 | #include "controller.h" 4 | #include "version.h" 5 | #include "worker.h" 6 | #include "master.h" 7 | 8 | #define EXTENSION_NAME "flame" 9 | #define EXTENSION_VERSION "0.17.4" 10 | 11 | extern "C" { 12 | ZEND_DLEXPORT zend_module_entry *get_module() { 13 | static php::extension_entry ext(EXTENSION_NAME, EXTENSION_VERSION); 14 | std::string sapi = php::constant("PHP_SAPI"); 15 | if (sapi != "cli") { 16 | std::cerr << "[" << util::system_time() << "] (WARNING) FLAME disabled: SAPI='cli' mode only\n"; 17 | return ext; 18 | } 19 | // 内置一个 C++ 函数包裹类 20 | php::class_entry class_closure("flame\\closure"); 21 | class_closure.method<&php::closure::__invoke>("__invoke"); 22 | ext.add(std::move(class_closure)); 23 | // 全局控制器 24 | flame::gcontroller.reset(new flame::controller()); 25 | // 扩展版本 26 | flame::version::declare(ext); 27 | // 主进程与工作进程注册不同的函数实现 28 | if (flame::gcontroller->type == flame::controller::process_type::WORKER) flame::worker::declare(ext); 29 | else flame::master::declare(ext); 30 | 31 | return ext; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/flame/hash/hash.cpp: -------------------------------------------------------------------------------- 1 | #include "hash.h" 2 | #include 3 | extern "C" { 4 | #include 5 | #include 6 | } 7 | 8 | namespace flame::hash { 9 | 10 | template 11 | static php::string dec2hex(INTEGER_TYPE dec) { 12 | php::string hex(sizeof(INTEGER_TYPE) * 2); 13 | sprintf(hex.data(), "%0*lx", sizeof(INTEGER_TYPE) * 2, dec); 14 | return std::move(hex); 15 | } 16 | 17 | static php::value murmur2(php::parameters& params) { 18 | php::string data = params[0].to_string(); 19 | std::uint32_t raw = rd_murmur2(data.c_str(), data.size()); 20 | if (params.size() > 1 && params[1].to_boolean()) return raw; 21 | return dec2hex(raw); 22 | } 23 | 24 | static php::value xxh64(php::parameters& params) { 25 | php::string data = params[0].to_string(); 26 | unsigned long long seed = 0; 27 | if (params.size() > 1) { 28 | seed = params[1].to_integer(); 29 | } 30 | std::uint64_t raw = XXH64(data.c_str(), data.size(), seed); 31 | if (params.size() > 2 && params[2].to_boolean()) return raw; 32 | return dec2hex(raw); 33 | } 34 | 35 | static php::value crc64(php::parameters& params) { 36 | php::string data = params[0].to_string(); 37 | boost::crc_optimal<64, 0x42F0E1EBA9EA3693, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true, true> crc64; 38 | crc64.process_bytes(data.c_str(), data.size()); 39 | std::uint64_t raw = crc64.checksum(); 40 | if (params.size() > 1 && params[1].to_boolean()) return raw; 41 | return dec2hex(raw); 42 | } 43 | 44 | void declare(php::extension_entry &ext) { 45 | ext 46 | .function("flame\\hash\\murmur2", { 47 | {"data", php::TYPE::STRING}, 48 | }) 49 | .function("flame\\hash\\xxh64", { 50 | {"data", php::TYPE::STRING}, 51 | {"seed", php::TYPE::INTEGER, false, true}, 52 | }) 53 | .function("flame\\hash\\crc64", { 54 | {"data", php::TYPE::STRING}, 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/flame/hash/hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::hash { 5 | void declare(php::extension_entry &ext); 6 | } // namespace flame::hash 7 | -------------------------------------------------------------------------------- /src/flame/http/_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "http.h" 5 | 6 | namespace flame::http { 7 | class server_response; 8 | class server_request; 9 | 10 | template 11 | class value_body; 12 | class server; 13 | class _handler : public std::enable_shared_from_this<_handler> { 14 | public: 15 | _handler(server *svr, boost::asio::ip::tcp::socket &&sock); 16 | ~_handler(); 17 | php::value run(php::parameters& params); 18 | 19 | bool prepare(coroutine_handler& ch); 20 | bool execute(coroutine_handler& ch); 21 | 22 | void write_body(server_response* res_ptr, coroutine_handler& ch); 23 | void write_head(server_response* res_ptr, coroutine_handler& ch); 24 | void write_chunk(server_response* res_ptr, php::string data, coroutine_handler& ch); 25 | void write_end(server_response* res_ptr, coroutine_handler& ch); 26 | void write_file(server_response* res_ptr, std::string file, coroutine_handler& ch); 27 | 28 | // 由 server_response 销毁时调用 29 | void finish(server_response *res, coroutine_handler &ch); 30 | 31 | private: 32 | server *svr_ptr; 33 | php::object svr_obj; 34 | boost::asio::ip::tcp::socket socket_; 35 | 36 | 37 | // std::shared_ptr>> req_; 38 | std::shared_ptr>> req_; 39 | std::shared_ptr>> res_; 40 | boost::beast::flat_buffer buffer_; 41 | }; 42 | // enum 43 | // { 44 | // RESPONSE_STATUS_HEADER_BUILT = 0x01, 45 | // RESPONSE_STATUS_HEADER_SENT = 0x02, 46 | // RESPONSE_STATUS_FINISHED = 0x04, 47 | // RESPONSE_STATUS_DETACHED = 0x08, 48 | 49 | // RESPONSE_TARGET_WRITE_HEADER = 0x01, 50 | // RESPONSE_TARGET_WRITE_CHUNK = 0x02, 51 | // RESPONSE_TARGET_WRITE_CHUNK_LAST = 0x03, 52 | // }; 53 | } // namespace flame::http 54 | -------------------------------------------------------------------------------- /src/flame/http/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "http.h" 4 | 5 | namespace flame::http { 6 | 7 | class client_poll; 8 | class client: public php::class_base { 9 | public: 10 | static void declare(php::extension_entry& ext); 11 | client(); 12 | ~client(); 13 | php::value __construct(php::parameters& params); 14 | php::value exec(php::parameters& params); 15 | php::value get(php::parameters& params); 16 | php::value post(php::parameters& params); 17 | php::value put(php::parameters& params); 18 | php::value delete_(php::parameters& params); 19 | private: 20 | CURLM *c_multi_ = nullptr; 21 | int c_still_ = 0; 22 | boost::asio::steady_timer c_timer_; 23 | ; 24 | 25 | php::value exec_ex(const php::object& req); 26 | static int c_socket_cb(CURL* e, curl_socket_t fd, int action, void* cbp, void* data); 27 | static int c_timer_cb(CURLM *m, long timeout_ms, void* data); 28 | static curl_socket_t c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* address); 29 | static int c_socket_close_cb(void* data, curl_socket_t fd); 30 | static void c_socket_ready_cb(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action); 31 | void check_done(); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/flame/http/client_body.cpp: -------------------------------------------------------------------------------- 1 | #include "client_body.h" 2 | 3 | namespace flame::http { 4 | void client_body::declare(php::extension_entry& ext) { 5 | php::class_entry class_client_body("flame\\http\\client_body"); 6 | ext.add(std::move(class_client_body)); 7 | } 8 | } -------------------------------------------------------------------------------- /src/flame/http/client_body.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "http.h" 4 | 5 | namespace flame::http { 6 | 7 | class client_body: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry& ext); 10 | }; 11 | } -------------------------------------------------------------------------------- /src/flame/http/client_poll.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "http.h" 4 | 5 | namespace flame::http { 6 | 7 | class client_poll { 8 | public: 9 | client_poll(); 10 | typedef void (*poll_callback_t)(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action); 11 | virtual ~client_poll(); 12 | static client_poll* create_poll(boost::asio::io_context& io, curl_socket_t fd, poll_callback_t cb, void* data = nullptr); 13 | static void destory_poll(client_poll* poll); 14 | virtual void async_wait() = 0; 15 | void async_wait(int action); 16 | void* data; 17 | protected: 18 | static std::map pool_; 19 | 20 | curl_socket_t fd_; 21 | int action_; 22 | poll_callback_t cb_; 23 | int ref_; 24 | void on_ready(const boost::system::error_code& error, int action); 25 | }; 26 | 27 | class client_poll_tcp: public client_poll { 28 | public: 29 | client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd); 30 | ~client_poll_tcp(); 31 | void async_wait() override; 32 | private: 33 | boost::asio::ip::tcp::socket sock_; 34 | }; 35 | 36 | class client_poll_udp: public client_poll { 37 | public: 38 | client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd); 39 | ~client_poll_udp(); 40 | void async_wait() override; 41 | private: 42 | boost::asio::ip::udp::socket sock_; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/flame/http/client_request.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../../url.h" 4 | #include "../coroutine.h" 5 | #include "http.h" 6 | 7 | namespace flame::http { 8 | 9 | class client_request: public php::class_base { 10 | public: 11 | static void declare(php::extension_entry& ext); 12 | php::value __construct(php::parameters& params); 13 | client_request(); 14 | ~client_request(); 15 | php::value http_version(php::parameters& params); 16 | php::value cert(php::parameters& params); 17 | php::value insecure(php::parameters& params); 18 | php::value option(php::parameters& params); 19 | private: 20 | void build_ex(); 21 | CURL* c_easy_ = nullptr; // cleanup in client 22 | curl_slist* c_head_ = nullptr; // owner 23 | 24 | friend class client; 25 | friend class _connection_pool; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/flame/http/client_response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "http.h" 5 | 6 | namespace flame::http { 7 | 8 | class client_response: public php::class_base { 9 | public: 10 | static void declare(php::extension_entry& ext); 11 | php::value to_string(php::parameters& params); 12 | // 声明为 ZEND_ACC_PRIVATE 禁止创建(不会被调用) 13 | php::value __construct(php::parameters& params); 14 | ~client_response(); 15 | private: 16 | CURL* c_easy_ = nullptr; 17 | php::array c_head_; 18 | php::buffer c_body_; 19 | CURLcode c_final_ = CURLE_OK; 20 | char c_error_[CURL_ERROR_SIZE]; 21 | coroutine_handler c_coro_; 22 | void build_ex(); 23 | // 接收 curl 回调(响应数据) 24 | static size_t c_write_cb(char *ptr, size_t size, size_t nmemb, void *data); 25 | static size_t c_header_cb(char *buffer, size_t size, size_t nitems, void *data); 26 | friend class client; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/flame/http/http.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #define BOOST_BEAST_USE_STD_STRING_VIEW 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace flame::http { 10 | 11 | class _connection_pool; 12 | class client; 13 | extern client* client_; 14 | extern std::int64_t body_max_size; 15 | 16 | void declare(php::extension_entry& ext); 17 | php::value get(php::parameters& params); 18 | php::value post(php::parameters& params); 19 | php::value put(php::parameters& params); 20 | php::value delete_(php::parameters& params); 21 | php::value exec(php::parameters& params); 22 | 23 | php::string ctype_encode(std::string_view ctype, const php::value& v); 24 | php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* file = nullptr); 25 | } 26 | -------------------------------------------------------------------------------- /src/flame/http/server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "http.h" 4 | 5 | namespace flame::http { 6 | class acceptor; 7 | class server : public php::class_base { 8 | public: 9 | static void declare(php::extension_entry &ext); 10 | 11 | server(); 12 | 13 | php::value __construct(php::parameters ¶ms); 14 | php::value before(php::parameters ¶ms); 15 | php::value after(php::parameters ¶ms); 16 | php::value put(php::parameters ¶ms); 17 | php::value delete_(php::parameters ¶ms); 18 | php::value post(php::parameters ¶ms); 19 | php::value patch(php::parameters ¶ms); 20 | php::value get(php::parameters ¶ms); 21 | php::value head(php::parameters ¶ms); 22 | php::value options(php::parameters ¶ms); 23 | php::value run(php::parameters ¶ms); 24 | php::value close(php::parameters ¶ms); 25 | 26 | private: 27 | boost::asio::ip::tcp::endpoint addr_; 28 | boost::asio::ip::tcp::acceptor accp_; 29 | boost::asio::ip::tcp::socket sock_; 30 | std::map cb_; 31 | bool closed_; 32 | 33 | friend class _handler; 34 | }; 35 | } // namespace flame::http 36 | -------------------------------------------------------------------------------- /src/flame/http/server_request.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "http.h" 4 | 5 | namespace flame::http { 6 | 7 | class handler; 8 | class server_request: public php::class_base { 9 | public: 10 | static void declare(php::extension_entry& ext); 11 | server_request(); 12 | ~server_request(); 13 | php::value __construct(php::parameters& params); 14 | private: 15 | void build_ex(const boost::beast::http::message>& ctr); 16 | 17 | friend class _handler; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/flame/http/server_response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "http.h" 4 | #include "value_body.h" 5 | 6 | namespace flame::http { 7 | 8 | class _handler; 9 | class server_response: public php::class_base { 10 | public: 11 | static void declare(php::extension_entry& ext); 12 | // 声明为 ZEND_ACC_PRIVATE 禁止创建(不会被调用) 13 | php::value __construct(php::parameters& params); 14 | php::value set_cookie(php::parameters& params); 15 | php::value write_header(php::parameters& params); 16 | php::value write(php::parameters& params); 17 | php::value end(php::parameters& params); 18 | php::value file(php::parameters& params); 19 | server_response(); 20 | ~server_response(); 21 | private: 22 | enum { 23 | STATUS_BUILT = 0x01, 24 | STATUS_HEAD_SENT = 0x02, 25 | STATUS_BODY_SENT = 0x04, 26 | STATUS_BODY_END = 0x08, 27 | }; 28 | int status_; 29 | std::shared_ptr<_handler> handler_; // 允许通过 $res 对象保留 handler 持续响应 30 | void build_ex(boost::beast::http::message> &ctr); 31 | 32 | friend class _handler; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/flame/http/value_body.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "value_body.h" 3 | 4 | namespace flame::http { 5 | 6 | void value_body_reader::init(boost::optional n, boost::system::error_code& error) { 7 | error.assign(0, error.category()); 8 | } 9 | 10 | void value_body_reader::finish(boost::system::error_code& error) { 11 | v_ = std::move(b_); 12 | error.assign(0, error.category()); 13 | } 14 | 15 | void value_body_writer::init(boost::system::error_code& error) { 16 | error.assign(0, error.category()); 17 | } 18 | 19 | boost::optional> value_body_writer::get(boost::system::error_code& error) { 20 | if (v_.empty()) { 21 | return {{boost::asio::const_buffer(nullptr, 0), false}}; 22 | } 23 | else { 24 | php::string data = v_; 25 | error.assign(0, error.category()); 26 | return {{boost::asio::const_buffer(data.data(), data.size()), false}}; 27 | } 28 | } 29 | } // namespace flame::http 30 | -------------------------------------------------------------------------------- /src/flame/http/value_body.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "http.h" 4 | 5 | namespace flame::http { 6 | 7 | class value_body_reader; 8 | class value_body_writer; 9 | template 10 | class value_body { 11 | public: 12 | // Body 13 | using value_type = php::value; 14 | static std::uint64_t size(const value_type& v); 15 | // BodyReader (Body) 16 | using reader = value_body_reader; 17 | // BodyWriter (Body) 18 | using writer = value_body_writer; 19 | }; 20 | 21 | class value_body_reader { 22 | public: 23 | template 24 | value_body_reader(boost::beast::http::header& h, php::value& v); 25 | void init(boost::optional n, boost::system::error_code& error); 26 | template 27 | std::size_t put(ConstBufferSequence s, boost::system::error_code& error); 28 | void finish(boost::system::error_code& error); 29 | private: 30 | php::value& v_; 31 | php::buffer b_; 32 | }; 33 | 34 | class value_body_writer { 35 | public: 36 | using const_buffers_type = boost::asio::const_buffer; 37 | template 38 | value_body_writer(const boost::beast::http::header& h, const php::value& v); 39 | void init(boost::system::error_code& error); 40 | boost::optional> get(boost::system::error_code& error); 41 | private: 42 | // const boost::beast::http::header& h_; 43 | const php::value& v_; 44 | }; 45 | } // namnespace flame::http 46 | 47 | #include "value_body.ipp" 48 | -------------------------------------------------------------------------------- /src/flame/http/value_body.ipp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace flame::http { 4 | 5 | template 6 | value_body_reader::value_body_reader(boost::beast::http::header& h, php::value& v) 7 | : v_(v) { 8 | 9 | } 10 | 11 | template 12 | std::size_t value_body_reader::put(ConstBufferSequence s, boost::system::error_code& error) { 13 | std::size_t n = 0; 14 | for (auto i=boost::asio::buffer_sequence_begin(s); i!=boost::asio::buffer_sequence_end(s); ++i) { 15 | boost::asio::const_buffer cbuf(*i); 16 | b_.append((const char *)cbuf.data(), cbuf.size()); 17 | n += cbuf.size(); 18 | } 19 | error.assign(0, error.category()); 20 | return n; 21 | } 22 | 23 | template 24 | value_body_writer::value_body_writer(const boost::beast::http::header& h, const php::value& v) 25 | : v_(v) {} 26 | 27 | template 28 | std::uint64_t value_body::size(const value_body::value_type& v) { 29 | return v.size(); 30 | } 31 | 32 | } // namespace flame::http 33 | -------------------------------------------------------------------------------- /src/flame/kafka/_consumer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "kafka.h" 5 | 6 | 7 | template 8 | class coroutine_queue; 9 | 10 | namespace flame::kafka { 11 | // 消费者 12 | // 目前的实现方式无需 shared_from_this 管理 13 | class _consumer: public std::enable_shared_from_this<_consumer> { 14 | public: 15 | _consumer(php::array& config, php::array& topics); 16 | ~_consumer(); 17 | void subscribe(coroutine_handler& ch); 18 | // 启动轻量的 C++ 协程进行消费 19 | rd_kafka_resp_err_t consume(coroutine_queue& q, ::coroutine_handler& ch); 20 | void commit(const php::object& msg, coroutine_handler& ch); 21 | void close(); 22 | private: 23 | rd_kafka_t* conn_; 24 | rd_kafka_topic_partition_list_t* tops_; 25 | bool close_; 26 | static void on_error(rd_kafka_t* conn, int error, const char* reason, void* data); 27 | }; 28 | } // namespace flame::kafka 29 | -------------------------------------------------------------------------------- /src/flame/kafka/_producer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "kafka.h" 5 | 6 | namespace flame::kafka { 7 | // 生产者 8 | // 目前的实现方式 poll 定时调用机制需要 shared_from_this 保证生命周期 9 | class _producer: public std::enable_shared_from_this<_producer> { 10 | public: 11 | _producer(php::array& config, php::array& topics); 12 | ~_producer(); 13 | void publish(const php::string& topic, std::int32_t partition, 14 | const php::string& key, const php::string& payload, 15 | const php::array& headers, coroutine_handler& ch); 16 | void flush(coroutine_handler& ch); 17 | void close(); 18 | void start(); 19 | private: 20 | rd_kafka_t* conn_; 21 | std::map tops_; 22 | boost::asio::steady_timer poll_; 23 | bool close_; 24 | void poll(int expire = 800); 25 | static void on_error(rd_kafka_t* conn, int error, const char* reason, void* data); 26 | friend class producer; 27 | }; 28 | } // namespace flame::kafka 29 | -------------------------------------------------------------------------------- /src/flame/kafka/consumer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "kafka.h" 5 | 6 | namespace flame::kafka { 7 | 8 | class _consumer; 9 | class consumer : public php::class_base { 10 | public: 11 | static void declare(php::extension_entry &ext); 12 | php::value __construct(php::parameters ¶ms); // 私有 13 | php::value run(php::parameters ¶ms); 14 | php::value commit(php::parameters ¶ms); 15 | php::value close(php::parameters ¶ms); 16 | private: 17 | std::shared_ptr<_consumer> cs_; 18 | int cc_ = 8; 19 | php::callable cb_; 20 | bool close_ = false; 21 | 22 | friend php::value consume(php::parameters ¶ms); 23 | }; 24 | } // namespace flame::kafka 25 | -------------------------------------------------------------------------------- /src/flame/kafka/kafka.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include 4 | 5 | namespace flame::kafka { 6 | void declare(php::extension_entry &ext); 7 | php::value consume(php::parameters& params); 8 | php::value produce(php::parameters& params); 9 | 10 | rd_kafka_conf_t* array2conf(const php::array& data); 11 | rd_kafka_headers_t* array2hdrs(const php::array& data); 12 | php::array hdrs2array(rd_kafka_headers_t* hdrs); 13 | } // namespace flame::kafka 14 | -------------------------------------------------------------------------------- /src/flame/kafka/message.cpp: -------------------------------------------------------------------------------- 1 | #include "message.h" 2 | #include "kafka.h" 3 | #include "../time/time.h" 4 | 5 | namespace flame::kafka { 6 | void message::declare(php::extension_entry& ext) { 7 | php::class_entry class_message("flame\\kafka\\message"); 8 | class_message 9 | .implements(&php_json_serializable_ce) 10 | .property({"topic", ""}) 11 | .property({"partition", -1}) 12 | .property({"key", ""}) 13 | .property({"offset", -1}) 14 | .property({"header", nullptr}) 15 | .property({"payload", nullptr}) 16 | .property({"timestamp", 0}) 17 | .method<&message::__construct>("__construct", { 18 | {"payload", php::TYPE::STRING, false, true}, 19 | {"key", php::TYPE::STRING, false, true}, 20 | }) 21 | .method<&message::to_string>("__toString") 22 | .method<&message::to_json>("jsonSerialize"); 23 | ext.add(std::move(class_message)); 24 | } 25 | 26 | message::~message() { 27 | if (msg_) rd_kafka_message_destroy(msg_); 28 | } 29 | 30 | void message::build_ex(rd_kafka_message_t* msg) { 31 | // 用于单挑 message 的提交 32 | msg_ = msg; 33 | // !!! 是否必须复制出 PHP 的内容? 34 | set("topic", php::string(rd_kafka_topic_name(msg->rkt))); 35 | set("partition", msg->partition); 36 | set("key", php::string((const char*)msg->key, msg->key_len)); 37 | set("offset", msg->offset); 38 | rd_kafka_headers_t* hdrs; 39 | if (rd_kafka_message_headers(msg, &hdrs) == RD_KAFKA_RESP_ERR_NO_ERROR) 40 | set("header", hdrs2array(hdrs)); 41 | // header 42 | set("payload", php::string((const char*)msg->payload, msg->len)); 43 | set("timestamp", rd_kafka_message_timestamp(msg, nullptr)); 44 | } 45 | 46 | php::value message::__construct(php::parameters& params) { 47 | if (params.size() > 0) set("payload", params[0].to_string()); 48 | if (params.size() > 1) set("key", params[1].to_string()); 49 | set("header", php::array(4)); 50 | set("timestamp", 51 | std::chrono::duration_cast(flame::time::now().time_since_epoch()).count()); 52 | return nullptr; 53 | } 54 | 55 | php::value message::to_json(php::parameters& params) { 56 | php::array json(4); 57 | json.set("topic", get("topic")); 58 | json.set("key", get("key")); 59 | json.set("payload",get("payload")); 60 | return json; 61 | } 62 | 63 | php::value message::to_string(php::parameters& params) { 64 | return get("payload"); 65 | } 66 | } // namespace flame::kafka 67 | -------------------------------------------------------------------------------- /src/flame/kafka/message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "kafka.h" 4 | 5 | namespace flame::kafka { 6 | 7 | class _consumer; 8 | class message: public php::class_base { 9 | public: 10 | static void declare(php::extension_entry& ext); 11 | ~message(); 12 | void build_ex(rd_kafka_message_t* msg); 13 | php::value __construct(php::parameters& params); // 私有 14 | php::value to_json(php::parameters& params); 15 | php::value to_string(php::parameters& params); 16 | private: 17 | rd_kafka_message_t *msg_ = nullptr; 18 | 19 | friend class _consumer; 20 | // friend class _producer; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/flame/kafka/producer.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "kafka.h" 3 | #include "message.h" 4 | #include "_producer.h" 5 | #include "producer.h" 6 | 7 | namespace flame::kafka { 8 | 9 | void producer::declare(php::extension_entry &ext) { 10 | php::class_entry class_producer("flame\\kafka\\producer"); 11 | class_producer 12 | .method<&producer::__construct>("__construct", {}, php::PRIVATE) 13 | .method<&producer::__destruct>("__destruct") 14 | .method<&producer::publish>("publish", { 15 | {"topic", php::TYPE::STRING}, 16 | }) 17 | .method<&producer::flush>("flush") 18 | .method<&producer::close>("close"); 19 | ext.add(std::move(class_producer)); 20 | } 21 | 22 | php::value producer::__construct(php::parameters ¶ms) { 23 | return nullptr; 24 | } 25 | 26 | php::value producer::__destruct(php::parameters ¶ms) { 27 | if (pd_) pd_->close(); // 内部存在 flush 机制 28 | return nullptr; 29 | } 30 | 31 | php::value producer::publish(php::parameters& params) { 32 | php::string topic = params[0].to_string(), key(0), payload; 33 | php::array header; 34 | std::int32_t p = RD_KAFKA_PARTITION_UA, i = 1; 35 | // 目标分区 36 | if (params[1].type_of(php::TYPE::INTEGER)) { 37 | p = params[1].to_integer(); 38 | ++i; 39 | } 40 | if (params[i].instanceof (php::class_entry::entry())) { 41 | php::object msg = params[i]; 42 | key = msg.get("key").to_string(); 43 | payload = msg.get("payload").to_string(); 44 | header = msg.get("header"); 45 | if (!header.type_of(php::TYPE::ARRAY)) header = php::array(0); 46 | } 47 | else { 48 | payload = params[i].to_string(); 49 | if (params.size() > i+1) key = params[i+1].to_string(); 50 | else key = php::string(0); 51 | if (params.size() > i+2) { 52 | if (params[i+2].type_of(php::TYPE::ARRAY)) header = params[i+2]; 53 | else header = php::array(0); 54 | } 55 | } 56 | 57 | coroutine_handler ch {coroutine::current}; 58 | pd_->publish(topic, p, key, payload, header, ch); 59 | return nullptr; 60 | } 61 | 62 | php::value producer::flush(php::parameters ¶ms) { 63 | coroutine_handler ch {coroutine::current}; 64 | pd_->flush(ch); 65 | return nullptr; 66 | } 67 | 68 | php::value producer::close(php::parameters ¶ms) { 69 | pd_->close(); 70 | pd_.reset(); 71 | return nullptr; 72 | } 73 | } // namespace flame::kafka 74 | -------------------------------------------------------------------------------- /src/flame/kafka/producer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "kafka.h" 4 | 5 | namespace flame::kafka { 6 | 7 | class _producer; 8 | class producer : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value __construct(php::parameters ¶ms); // 私有 12 | php::value __destruct(php::parameters ¶ms); 13 | php::value publish(php::parameters ¶ms); 14 | php::value publish_to(php::parameters& params); 15 | php::value flush(php::parameters ¶ms); 16 | php::value close(php::parameters ¶ms); 17 | private: 18 | std::shared_ptr<_producer> pd_; 19 | friend php::value produce(php::parameters ¶ms); 20 | }; 21 | } // namespace flame::kafka 22 | -------------------------------------------------------------------------------- /src/flame/log/log.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "../worker.h" 3 | #include "log.h" 4 | #include "logger.h" 5 | 6 | namespace flame::log { 7 | 8 | static php::value write(php::parameters ¶ms) { 9 | logger_->write_ex(logger::LEVEL_EMPTY, params); 10 | return nullptr; 11 | } 12 | 13 | static php::value trace(php::parameters ¶ms) { 14 | logger_->write_ex(logger::LEVEL_TRACE, params); 15 | return nullptr; 16 | } 17 | 18 | static php::value debug(php::parameters ¶ms) { 19 | logger_->write_ex(logger::LEVEL_DEBUG, params); 20 | return nullptr; 21 | } 22 | 23 | static php::value info(php::parameters ¶ms) { 24 | logger_->write_ex(logger::LEVEL_INFO, params); 25 | return nullptr; 26 | } 27 | 28 | static php::value warning(php::parameters ¶ms) { 29 | logger_->write_ex(logger::LEVEL_WARNING, params); 30 | return nullptr; 31 | } 32 | 33 | static php::value error(php::parameters ¶ms) { 34 | logger_->write_ex(logger::LEVEL_ERROR, params); 35 | return nullptr; 36 | } 37 | 38 | static php::value fatal(php::parameters ¶ms) { 39 | logger_->write_ex(logger::LEVEL_FATAL, params); 40 | return nullptr; 41 | } 42 | 43 | php::value connect(php::parameters& params) { 44 | coroutine_handler ch {coroutine::current}; 45 | php::object obj {php::class_entry::entry()}; 46 | logger* ptr = static_cast(php::native(obj)); 47 | ptr->connect(params[0], ch); 48 | return obj; 49 | } 50 | 51 | void declare(php::extension_entry &ext) { 52 | ext 53 | .function("flame\\log\\connect") 54 | .function("flame\\log\\write") 55 | .function("flame\\log\\trace") 56 | .function("flame\\log\\debug") 57 | .function("flame\\log\\info") 58 | .function("flame\\log\\warn") 59 | .function("flame\\log\\warning") 60 | .function("flame\\log\\error") 61 | .function("flame\\log\\fail") 62 | .function("flame\\log\\fatal"); 63 | 64 | logger::declare(ext); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/flame/log/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::log { 5 | void declare(php::extension_entry &ext); 6 | php::value connect(php::parameters& params); 7 | } // namespace flame::log 8 | -------------------------------------------------------------------------------- /src/flame/log/logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | class coroutine_handler; 5 | class worker_logger; 6 | namespace flame::log { 7 | 8 | class logger: public php::class_base { 9 | public: 10 | enum { 11 | LEVEL_TRACE, 12 | LEVEL_DEBUG, 13 | LEVEL_INFO, 14 | LEVEL_WARNING, 15 | LEVEL_ERROR, 16 | LEVEL_FATAL, 17 | 18 | LEVEL_EMPTY, 19 | }; 20 | static void declare(php::extension_entry& ext); 21 | static std::array LEVEL_STR; 22 | static int LEVEL_OPT; 23 | php::value __construct(php::parameters& params); 24 | // 使用父类型 coroutine_handler 引用能够兼容 C++ / PHP 协程 25 | void connect(const std::string& path, ::coroutine_handler& ch); 26 | void write_ex(int lv, php::parameters& params); 27 | std::ostream& stream(); 28 | php::value write(php::parameters ¶ms); 29 | php::value trace(php::parameters ¶ms); 30 | php::value debug(php::parameters ¶ms); 31 | php::value info(php::parameters ¶ms); 32 | php::value warning(php::parameters ¶ms); 33 | php::value error(php::parameters ¶ms); 34 | php::value fatal(php::parameters ¶ms); 35 | private: 36 | std::shared_ptr lg_; 37 | }; 38 | // 默认日志记录器 39 | extern logger* logger_; 40 | } -------------------------------------------------------------------------------- /src/flame/master.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../vendor.h" 3 | #include "../master_process_manager.h" 4 | #include "../master_logger_manager.h" 5 | #include "../signal_watcher.h" 6 | #include "../master_ipc.h" 7 | 8 | namespace flame { 9 | class master: public std::enable_shared_from_this, public master_process_manager, public signal_watcher, public master_logger_manager, public master_ipc { 10 | public: 11 | static inline std::shared_ptr get() { 12 | return mm_; 13 | } 14 | static void declare(php::extension_entry& ext); 15 | static php::value init(php::parameters& params); 16 | static php::value run(php::parameters& params); 17 | static php::value dummy(php::parameters& params); 18 | 19 | master(); 20 | std::ostream& output() override; 21 | protected: 22 | std::shared_ptr pm_self() override { 23 | return std::static_pointer_cast(shared_from_this()); 24 | } 25 | virtual std::shared_ptr sw_self() override { 26 | return std::static_pointer_cast(shared_from_this()); 27 | } 28 | virtual std::shared_ptr lm_self() override { 29 | return std::static_pointer_cast(shared_from_this()); 30 | } 31 | virtual std::shared_ptr ipc_self() override { 32 | return std::static_pointer_cast(shared_from_this()); 33 | } 34 | // void on_child_close(master_process* w, bool normal) override; 35 | bool on_signal(int sig) override; 36 | bool on_message(std::shared_ptr msg, socket_ptr sock) override; 37 | 38 | private: 39 | static std::shared_ptr mm_; 40 | master_logger* lg_; 41 | 42 | friend class controller; 43 | }; 44 | } -------------------------------------------------------------------------------- /src/flame/mongodb/_connection_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "mongodb.h" 5 | 6 | namespace flame::mongodb { 7 | 8 | class _connection_base { 9 | public: 10 | enum command_type_t { 11 | COMMAND_RAW, 12 | COMMAND_WRITE, 13 | COMMAND_READ, 14 | COMMAND_READ_WRITE, 15 | }; 16 | virtual std::shared_ptr acquire(coroutine_handler& ch) = 0; 17 | php::value exec(std::shared_ptr conn 18 | , php::array& cmd, int type, coroutine_handler& ch 19 | , std::shared_ptr session = nullptr); 20 | 21 | static void fake_deleter(bson_t *doc); 22 | 23 | private: 24 | bool cp_sess_ = true; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/flame/mongodb/_connection_lock.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller.h" 2 | #include "_connection_lock.h" 3 | #include "mongodb.h" 4 | 5 | namespace flame::mongodb { 6 | _connection_lock::_connection_lock(std::shared_ptr c) 7 | : conn_(c) 8 | , guard_(gcontroller->context_y) { 9 | } 10 | 11 | _connection_lock::~_connection_lock() { 12 | } 13 | 14 | std::shared_ptr _connection_lock::acquire(coroutine_handler &ch) { 15 | return conn_; 16 | } 17 | 18 | php::array _connection_lock::fetch(std::shared_ptr cs, coroutine_handler &ch) { 19 | bool has = false; 20 | auto err = std::make_shared(); 21 | const bson_t *doc; 22 | boost::asio::post(guard_, [this, &cs, &ch, &has, &err, &doc]() { 23 | if (!mongoc_cursor_next(cs.get(), &doc)) has = mongoc_cursor_error(cs.get(), err.get()); 24 | ch.resume(); 25 | }); 26 | ch.suspend(); 27 | // 发生了错误 28 | if (has) throw php::exception(zend_ce_exception 29 | , (boost::format("Failed to fetch document: %s") % err->message).str() 30 | , err->code); 31 | // 文档 doc 仅为 "引用" 流程 32 | return bson2array(const_cast(doc)); 33 | } 34 | } // namespace flame::mongodb 35 | -------------------------------------------------------------------------------- /src/flame/mongodb/_connection_lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mongodb.h" 4 | #include "_connection_base.h" 5 | 6 | namespace flame::mongodb { 7 | 8 | class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> { 9 | public: 10 | _connection_lock(std::shared_ptr c); 11 | ~_connection_lock(); 12 | std::shared_ptr acquire(coroutine_handler &ch) override; 13 | php::array fetch(std::shared_ptr cs, coroutine_handler &ch); 14 | private: 15 | boost::asio::io_context::strand guard_; // 防止对 cursor 的并行访问 16 | std::shared_ptr conn_; 17 | }; 18 | } // namespace flame::mongodb 19 | -------------------------------------------------------------------------------- /src/flame/mongodb/_connection_pool.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller.h" 2 | #include "mongodb.h" 3 | #include "_connection_pool.h" 4 | 5 | namespace flame::mongodb { 6 | 7 | _connection_pool::_connection_pool(const std::string& url) 8 | : guard_(gcontroller->context_y) { 9 | std::unique_ptr uri(mongoc_uri_new(url.c_str()), mongoc_uri_destroy); 10 | const bson_t* options = mongoc_uri_get_options(uri.get()); 11 | if (!bson_has_field(options, MONGOC_URI_READPREFERENCE)) { 12 | mongoc_read_prefs_t* pref = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); // secondaryPreferred 13 | mongoc_uri_set_read_prefs_t(uri.get(), pref); 14 | mongoc_read_prefs_destroy(pref); 15 | } 16 | mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000); 17 | mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 16); 18 | 19 | p_ = mongoc_client_pool_new(uri.get()); 20 | } 21 | 22 | _connection_pool::~_connection_pool() { 23 | mongoc_client_pool_destroy(p_); 24 | } 25 | 26 | std::shared_ptr _connection_pool::acquire(coroutine_handler &ch) { 27 | std::shared_ptr conn; 28 | auto self = shared_from_this(); 29 | boost::asio::post(guard_, [this, self, &conn, &ch] () { 30 | await_.push_back([this, self, &conn, &ch] (mongoc_client_t* c) { 31 | // 对应释放(归还)连接过程, 须持有当前对象的引用 self (否则当前对象可能先于连接释放被销毁) 32 | conn.reset(c, [this, self] (mongoc_client_t* c) { 33 | boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c)); 34 | }); 35 | ch.resume(); 36 | }); 37 | mongoc_client_t* c = mongoc_client_pool_try_pop(p_); 38 | if (c) release(c); 39 | }); 40 | ch.suspend(); 41 | return conn; 42 | } 43 | 44 | void _connection_pool::release(mongoc_client_t* c) { 45 | if (await_.empty()) mongoc_client_pool_push(p_, c); 46 | else { 47 | auto cb = await_.front(); 48 | await_.pop_front(); 49 | cb(c); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/flame/mongodb/_connection_pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mongodb.h" 4 | #include "_connection_base.h" 5 | 6 | namespace flame::mongodb { 7 | 8 | class _connection_pool: public _connection_base, public std::enable_shared_from_this<_connection_pool> { 9 | public: 10 | _connection_pool(const std::string& url); 11 | ~_connection_pool(); 12 | std::shared_ptr acquire(coroutine_handler &ch) override; 13 | void release(mongoc_client_t* c); 14 | private: 15 | mongoc_client_pool_t* p_; 16 | boost::asio::io_context::strand guard_; // 防止下面队列并行访问 17 | std::list> await_; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/flame/mongodb/client.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "mongodb.h" 3 | #include "_connection_pool.h" 4 | #include "client.h" 5 | #include "collection.h" 6 | 7 | namespace flame::mongodb { 8 | 9 | void client::declare(php::extension_entry &ext) { 10 | php::class_entry class_client("flame\\mongodb\\client"); 11 | class_client 12 | .constant({"COMMAND_RAW", _connection_base::COMMAND_RAW}) 13 | .constant({"COMMAND_READ", _connection_base::COMMAND_READ}) 14 | .constant({"COMMAND_READ_WRITE", _connection_base::COMMAND_READ_WRITE}) 15 | .constant({"COMMAND_WRITE", _connection_base::COMMAND_WRITE}) 16 | .method<&client::__construct>("__construct", {}, php::PRIVATE) 17 | .method<&client::dump>("dump", { 18 | {"data", php::TYPE::ARRAY}, 19 | }) 20 | .method<&client::execute>("execute", { 21 | {"command", php::TYPE::ARRAY}, 22 | {"write", php::TYPE::INTEGER, false, true}, 23 | }) 24 | .method<&client::__get>("collection", { 25 | {"name", php::TYPE::STRING} 26 | }) 27 | .method<&client::__get>("__get", { 28 | {"name", php::TYPE::STRING} 29 | }) 30 | .method<&client::__isset>("__isset", { 31 | {"name", php::TYPE::STRING} 32 | }); 33 | ext.add(std::move(class_client)); 34 | } 35 | 36 | php::value client::__construct(php::parameters ¶ms) { 37 | return nullptr; 38 | } 39 | 40 | php::value client::dump(php::parameters& params) { 41 | std::clog << bson_as_relaxed_extended_json(array2bson(params[0]).get(), nullptr) << std::endl; 42 | return nullptr; 43 | } 44 | 45 | php::value client::execute(php::parameters ¶ms) { 46 | int write = _connection_base::COMMAND_RAW; 47 | if (params.size() > 1) write = params[1].to_integer(); 48 | coroutine_handler ch {coroutine::current}; 49 | auto conn_ = cp_->acquire(ch); 50 | 51 | php::array cmd = params[0]; 52 | return cp_->exec(conn_, cmd, write, ch); 53 | } 54 | 55 | php::value client::__get(php::parameters ¶ms) { 56 | php::object obj(php::class_entry::entry()); 57 | collection *ptr = static_cast(php::native(obj)); 58 | ptr->cp_ = cp_; 59 | ptr->name_ = params[0]; 60 | obj.set("name", params[0]); 61 | return std::move(obj); 62 | } 63 | 64 | php::value client::__isset(php::parameters ¶ms) { 65 | return true; 66 | } 67 | } // namespace flame::mongodb 68 | -------------------------------------------------------------------------------- /src/flame/mongodb/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mongodb.h" 4 | 5 | namespace flame::mongodb { 6 | 7 | class _connection_pool; 8 | class client : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value __construct(php::parameters ¶ms); 12 | php::value dump(php::parameters ¶ms); 13 | php::value execute(php::parameters ¶ms); 14 | php::value __get(php::parameters ¶ms); 15 | php::value __isset(php::parameters ¶ms); 16 | private: 17 | std::shared_ptr<_connection_pool> cp_; 18 | friend php::value connect(php::parameters ¶ms); 19 | }; 20 | } // namespace flame::mongodb 21 | -------------------------------------------------------------------------------- /src/flame/mongodb/collection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mongodb.h" 4 | 5 | namespace flame::mongodb { 6 | 7 | class _connection_pool; 8 | class collection : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value __construct(php::parameters ¶ms); 12 | php::value insert(php::parameters ¶ms); 13 | php::value delete_(php::parameters ¶ms); 14 | php::value delete_one(php::parameters& params); 15 | php::value update(php::parameters ¶ms); 16 | php::value update_one(php::parameters& params); 17 | php::value find(php::parameters ¶ms); 18 | php::value find_many(php::parameters ¶ms); 19 | php::value find_one(php::parameters ¶ms); 20 | php::value get(php::parameters ¶ms); 21 | php::value count(php::parameters ¶ms); 22 | php::value aggregate(php::parameters ¶ms); 23 | php::value find_and_update(php::parameters& params); 24 | php::value find_and_delete(php::parameters& params); 25 | private: 26 | std::shared_ptr<_connection_pool> cp_; 27 | php::string name_; 28 | friend class client; 29 | }; 30 | } // namespace flame::mongodb 31 | -------------------------------------------------------------------------------- /src/flame/mongodb/cursor.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "cursor.h" 3 | #include "_connection_lock.h" 4 | 5 | namespace flame::mongodb { 6 | 7 | void cursor::declare(php::extension_entry &ext) { 8 | php::class_entry class_cursor("flame\\mongodb\\cursor"); 9 | class_cursor 10 | .method<&cursor::__construct>("__construct", {}, php::PRIVATE) 11 | .method<&cursor::__destruct>("__destruct") 12 | .method<&cursor::fetch_row>("fetch_row") 13 | .method<&cursor::fetch_all>("fetch_all"); 14 | ext.add(std::move(class_cursor)); 15 | } 16 | 17 | php::value cursor::fetch_row(php::parameters ¶ms) { 18 | php::array row(nullptr); 19 | if (cs_ && cl_) { 20 | coroutine_handler ch{coroutine::current}; 21 | row = cl_->fetch(cs_, ch); 22 | } 23 | if (row.type_of(php::TYPE::NULLABLE)) { 24 | // 尽早释放连接 (注意顺序) 25 | cs_.reset(); 26 | ss_.reset(); 27 | cl_.reset(); 28 | } 29 | return row; 30 | } 31 | 32 | php::value cursor::fetch_all(php::parameters ¶ms) { 33 | if (cs_ && cl_) { 34 | coroutine_handler ch{coroutine::current}; 35 | php::array data(8); 36 | for(php::array row = cl_->fetch(cs_, ch); !row.type_of(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch)) 37 | data.set(data.size(), row); 38 | // 尽早释放连接 (注意顺序) 39 | cs_.reset(); 40 | ss_.reset(); 41 | cl_.reset(); 42 | return data; 43 | } 44 | else return nullptr; 45 | } 46 | } // namespace flame::mongodb 47 | -------------------------------------------------------------------------------- /src/flame/mongodb/cursor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mongodb.h" 4 | 5 | namespace flame::mongodb { 6 | class _connection_lock; 7 | class cursor: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry& ext); 10 | php::value __construct(php::parameters& params) { // 私有 11 | return nullptr; 12 | } 13 | php::value __destruct(php::parameters& params) { 14 | return nullptr; 15 | } 16 | php::value fetch_row(php::parameters& params); 17 | php::value fetch_all(php::parameters& params); 18 | private: 19 | // 须先销毁 指针 后归还 连接 20 | std::shared_ptr<_connection_lock> cl_; 21 | std::shared_ptr ss_; 22 | std::shared_ptr cs_; 23 | friend class _connection_base; 24 | }; 25 | } // namespace flame::mongodb 26 | -------------------------------------------------------------------------------- /src/flame/mongodb/date_time.cpp: -------------------------------------------------------------------------------- 1 | #include "date_time.h" 2 | #include "../time/time.h" 3 | 4 | namespace flame::mongodb { 5 | 6 | void date_time::declare(php::extension_entry& ext) { 7 | php::class_entry class_date_time("flame\\mongodb\\date_time"); 8 | class_date_time 9 | .implements(&php_json_serializable_ce) 10 | .method<&date_time::__construct>("__construct", { 11 | {"milliseconds", php::TYPE::INTEGER, false, true} 12 | }) 13 | .method<&date_time::to_string>("__toString") 14 | .method<&date_time::to_json>("__toJSON") 15 | .method<&date_time::to_datetime>("__toDateTime") 16 | .method<&date_time::unix>("unix") 17 | .method<&date_time::unix_ms>("unix_ms") 18 | .method<&date_time::to_json>("jsonSerialize") 19 | .method<&date_time::to_json>("__debugInfo") 20 | .method<&date_time::iso>("iso"); 21 | ext.add(std::move(class_date_time)); 22 | } 23 | 24 | php::value date_time::__construct(php::parameters& params) { 25 | if (params.size() > 0) tm_ = params[0].to_integer(); 26 | else // 默认以当前时间建立 27 | tm_ = std::chrono::duration_cast(time::now().time_since_epoch()).count(); 28 | return nullptr; 29 | } 30 | 31 | php::value date_time::to_string(php::parameters& params) { 32 | return std::to_string(tm_); 33 | } 34 | 35 | php::value date_time::unix(php::parameters& params) { 36 | return std::int64_t(tm_ / 1000); 37 | } 38 | 39 | php::value date_time::unix_ms(php::parameters& params) { 40 | return tm_; 41 | } 42 | 43 | php::value date_time::to_datetime(php::parameters& params) { 44 | return php::datetime(tm_); 45 | } 46 | 47 | php::value date_time::to_json(php::parameters& params) { 48 | // 自定义 JSON 形式 (保持和官方 MongoDB 驱动形式一致) 49 | php::array ret(1), num(1); 50 | num.set("$numberLong", std::to_string(tm_)); 51 | ret.set("$date", num); 52 | return std::move(ret); 53 | } 54 | 55 | php::value date_time::iso(php::parameters& params) { 56 | return time::iso(std::chrono::system_clock::time_point( std::chrono::milliseconds(tm_) )); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/flame/mongodb/date_time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mongodb.h" 4 | 5 | namespace flame::mongodb { 6 | 7 | class date_time: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry& ext); 10 | php::value __construct(php::parameters& params); 11 | php::value to_string(php::parameters& params); 12 | php::value unix(php::parameters& params); 13 | php::value unix_ms(php::parameters& params); 14 | php::value to_datetime(php::parameters& params); 15 | php::value to_json(php::parameters& params); 16 | php::value iso(php::parameters& params); 17 | private: 18 | std::int64_t tm_; 19 | friend php::value iter2value(bson_iter_t *i); 20 | friend std::shared_ptr array2bson(const php::array &v); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/flame/mongodb/mongodb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include 4 | 5 | namespace flame::mongodb { 6 | void declare(php::extension_entry &ext); 7 | php::value connect(php::parameters ¶ms); 8 | php::value iter2value(bson_iter_t *i); 9 | php::array bson2array(std::shared_ptr v); 10 | php::array bson2array(bson_t* v); 11 | std::shared_ptr array2bson(const php::array &v); 12 | } // namespace flame::mongodb 13 | -------------------------------------------------------------------------------- /src/flame/mongodb/object_id.cpp: -------------------------------------------------------------------------------- 1 | #include "object_id.h" 2 | 3 | namespace flame::mongodb { 4 | 5 | void object_id::declare(php::extension_entry &ext) { 6 | php::class_entry class_object_id("flame\\mongodb\\object_id"); 7 | class_object_id 8 | .implements(&php_json_serializable_ce) 9 | .method<&object_id::__construct>("__construct") 10 | .method<&object_id::to_string>("__toString") 11 | .method<&object_id::to_json>("__toJSON") 12 | .method<&object_id::to_datetime>("__toDateTime") 13 | .method<&object_id::unix>("unix") 14 | .method<&object_id::to_json>("jsonSerialize") 15 | .method<&object_id::to_json>("__debugInfo") 16 | .method<&object_id::equal>("equal",{ 17 | {"object_id", "?flame\\mongodb\\object_id"} 18 | }); 19 | ext.add(std::move(class_object_id)); 20 | } 21 | 22 | php::value object_id::__construct(php::parameters ¶ms) { 23 | if (params.size() > 0 && params[0].type_of(php::TYPE::STRING)) { 24 | php::string oid = params[0].to_string(); 25 | bson_oid_init_from_string(&oid_, oid.c_str()); 26 | } 27 | else bson_oid_init(&oid_, nullptr); 28 | return nullptr; 29 | } 30 | 31 | php::value object_id::to_string(php::parameters ¶ms) { 32 | php::string str(24); 33 | bson_oid_to_string(&oid_, str.data()); 34 | return std::move(str); 35 | } 36 | 37 | php::value object_id::unix(php::parameters ¶ms) { 38 | return bson_oid_get_time_t(&oid_); 39 | } 40 | 41 | php::value object_id::to_datetime(php::parameters ¶ms) { 42 | return php::datetime(bson_oid_get_time_t(&oid_) * 1000); 43 | } 44 | 45 | php::value object_id::to_json(php::parameters ¶ms) { 46 | // 定制 JSON 输出形式 (为何官方 MongoDB 驱动保持一致) 47 | php::array oid(1); 48 | php::string str(24); 49 | bson_oid_to_string(&oid_, str.data()); 50 | oid.set("$oid", str); 51 | return std::move(oid); 52 | } 53 | 54 | php::value object_id::equal(php::parameters ¶ms) { 55 | if (params[0].type_of(php::TYPE::STRING)) { 56 | php::string data = params[0]; 57 | bson_oid_t oid; 58 | bson_oid_init_from_string(&oid, data.c_str()); 59 | return bson_oid_equal(&oid_, &oid); 60 | } 61 | else if (params[0].instanceof (php::class_entry::entry())) { 62 | php::object obj = params[0]; 63 | object_id *ptr = static_cast(php::native(obj)); 64 | return bson_oid_equal(&oid_, &ptr->oid_); 65 | } 66 | else return false; 67 | } 68 | } // namespace flame::mongodb 69 | -------------------------------------------------------------------------------- /src/flame/mongodb/object_id.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mongodb.h" 4 | 5 | namespace flame::mongodb { 6 | 7 | class object_id: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry& ext); 10 | php::value __construct(php::parameters& params); 11 | php::value to_string(php::parameters& params); 12 | php::value unix(php::parameters& params); 13 | php::value to_datetime(php::parameters& params); 14 | php::value to_json(php::parameters& params); 15 | php::value equal(php::parameters& params); 16 | private: 17 | bson_oid_t oid_; 18 | friend php::value iter2value(bson_iter_t *i); 19 | friend std::shared_ptr array2bson(const php::array &v); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/flame/mutex.cpp: -------------------------------------------------------------------------------- 1 | #include "coroutine.h" 2 | #include "mutex.h" 3 | #include "../coroutine_mutex.h" 4 | 5 | namespace flame { 6 | void mutex::declare(php::extension_entry &ext) { 7 | php::class_entry class_mutex("flame\\mutex"); 8 | class_mutex 9 | .method<&mutex::__construct>("__construct", { 10 | {"n", php::TYPE::INTEGER, false, true}, 11 | }) 12 | .method<&mutex::lock>("lock") 13 | .method<&mutex::unlock>("unlock"); 14 | ext.add(std::move(class_mutex)); 15 | } 16 | 17 | php::value mutex::__construct(php::parameters& params) { 18 | mutex_.reset(new coroutine_mutex()); 19 | return nullptr; 20 | } 21 | 22 | php::value mutex::lock(php::parameters& params) { 23 | coroutine_handler ch {coroutine::current}; 24 | mutex_->lock(ch); 25 | return nullptr; 26 | } 27 | 28 | php::value mutex::unlock(php::parameters& params) { 29 | mutex_->unlock(); 30 | return nullptr; 31 | } 32 | 33 | void guard::declare(php::extension_entry &ext) { 34 | php::class_entry class_guard("flame\\guard"); 35 | class_guard 36 | .method<&guard::__construct>("__construct", { 37 | {"mutex", "flame\\mutex"}, 38 | }) 39 | .method<&guard::__destruct>("__destruct"); 40 | ext.add(std::move(class_guard)); 41 | } 42 | 43 | php::value guard::__construct(php::parameters & params) { 44 | php::object obj = params[0]; 45 | mutex_ = static_cast(php::native(obj))->mutex_; 46 | coroutine_handler ch {coroutine::current}; 47 | mutex_->lock(ch); 48 | return nullptr; 49 | } 50 | 51 | php::value guard::__destruct(php::parameters & params) { 52 | // 强制退出协程上下文丢失,无法进行 RESUME 恢复操作 53 | mutex_->unlock( (gcontroller->status & controller::STATUS_QUITING) > 0 ); 54 | return nullptr; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/flame/mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../vendor.h" 3 | 4 | class coroutine_mutex; 5 | namespace flame { 6 | 7 | class mutex: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry& ext); 10 | php::value __construct(php::parameters& params); 11 | php::value lock(php::parameters& params); 12 | php::value unlock(php::parameters& params); 13 | 14 | private: 15 | std::shared_ptr mutex_; 16 | friend class guard; 17 | }; 18 | 19 | class guard: public php::class_base { 20 | public: 21 | static void declare(php::extension_entry& ext); 22 | php::value __construct(php::parameters& params); 23 | php::value __destruct(php::parameters ¶ms); 24 | private: 25 | std::shared_ptr mutex_; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/flame/mysql/_connection_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "mysql.h" 5 | 6 | namespace flame::mysql { 7 | 8 | class _connection_base { 9 | public: 10 | // 此函数仅允许 escape 主线程调用 11 | static void escape(std::shared_ptr c, php::buffer &b, const php::value &v, char quote = '\''); // 方便使用 12 | virtual std::shared_ptr acquire(coroutine_handler& ch) = 0; 13 | php::string format(std::shared_ptr conn, php::parameters& params); 14 | php::object query(std::shared_ptr conn, std::string sql, coroutine_handler& ch); 15 | php::array fetch(std::shared_ptr conn, std::shared_ptr rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch); 16 | const std::string& last_query(); 17 | protected: 18 | private: 19 | std::string last_query_; 20 | }; 21 | } // namespace flame::mysql 22 | -------------------------------------------------------------------------------- /src/flame/mysql/_connection_lock.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "_connection_base.h" 3 | #include "_connection_lock.h" 4 | 5 | namespace flame::mysql { 6 | 7 | _connection_lock::_connection_lock(std::shared_ptr c) 8 | : conn_(c) { 9 | } 10 | 11 | _connection_lock::~_connection_lock() { 12 | } 13 | 14 | std::shared_ptr _connection_lock::acquire(coroutine_handler &ch) { 15 | return conn_; 16 | } 17 | 18 | void _connection_lock::begin_tx(coroutine_handler &ch) { 19 | int err = 0; 20 | boost::asio::post(gcontroller->context_y, [this, &ch, &err]() { 21 | err = mysql_real_query(conn_.get(), "START TRANSACTION", 17); 22 | ch.resume(); 23 | }); 24 | ch.suspend(); 25 | if (err != 0) { 26 | err = mysql_errno(conn_.get()); 27 | throw php::exception(zend_ce_exception 28 | , (boost::format("Failed to start transaction: %s") % mysql_error(conn_.get())).str() 29 | , err); 30 | } 31 | } 32 | 33 | void _connection_lock::commit(coroutine_handler &ch) { 34 | int err = 0; 35 | boost::asio::post(gcontroller->context_y, [this, &ch, &err]() { 36 | err = mysql_real_query(conn_.get(), "COMMIT", 6); 37 | ch.resume(); 38 | }); 39 | ch.suspend(); 40 | if (err != 0) { 41 | err = mysql_errno(conn_.get()); 42 | throw php::exception(zend_ce_exception 43 | , (boost::format("failed to commit transaction: %s") % mysql_error(conn_.get())).str() 44 | , err); 45 | } 46 | } 47 | 48 | void _connection_lock::rollback(coroutine_handler &ch) { 49 | int err = 0; 50 | boost::asio::post(gcontroller->context_y, [this, &ch, &err]() { 51 | err = mysql_real_query(conn_.get(), "ROLLBACK", 8); 52 | ch.resume(); 53 | }); 54 | ch.suspend(); 55 | if (err != 0) { 56 | err = mysql_errno(conn_.get()); 57 | throw php::exception(zend_ce_error 58 | , (boost::format("failed to rollback transaction: %s") % mysql_error(conn_.get())).str() 59 | , err); 60 | } 61 | } 62 | 63 | void _connection_lock::rollback() { 64 | // 由于不存在协程上下文,需要共享延长生存周期 65 | boost::asio::post(gcontroller->context_y, [this, self = shared_from_this()]() { 66 | mysql_real_query(conn_.get(), "ROLLBACK", 8); 67 | }); 68 | } 69 | 70 | } // namespace flame::mysql 71 | -------------------------------------------------------------------------------- /src/flame/mysql/_connection_lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "_connection_base.h" 5 | #include "mysql.h" 6 | 7 | namespace flame::mysql { 8 | class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> { 9 | public: 10 | _connection_lock(std::shared_ptr c); 11 | ~_connection_lock(); 12 | std::shared_ptr acquire(coroutine_handler& ch) override; 13 | void begin_tx(coroutine_handler& ch); 14 | void commit(coroutine_handler& ch); 15 | void rollback(coroutine_handler& ch); 16 | void rollback(); 17 | private: 18 | std::shared_ptr conn_; 19 | }; 20 | } // namespace flame::mysql 21 | -------------------------------------------------------------------------------- /src/flame/mysql/_connection_pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../../url.h" 4 | #include "../coroutine.h" 5 | 6 | #include "_connection_base.h" 7 | 8 | namespace flame::mysql { 9 | 10 | class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool> { 11 | public: 12 | _connection_pool(url u); 13 | ~_connection_pool(); 14 | std::shared_ptr acquire(coroutine_handler &ch) override; 15 | void sweep(); 16 | void close(); 17 | private: 18 | url url_; 19 | const std::uint16_t min_; 20 | const std::uint16_t max_; 21 | std::uint16_t size_; 22 | 23 | boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题; 24 | std::list)>> await_; 25 | struct connection_t { 26 | MYSQL *conn; 27 | std::chrono::time_point ttl; 28 | }; 29 | std::list conn_; 30 | // 扫描闲置连接的定时器 (工作线程) 31 | boost::asio::steady_timer sweep_; 32 | 33 | int flag_; 34 | enum { 35 | FLAG_UNKNOWN = 0x00, 36 | FLAG_REUSE_MASK = 0x0f, 37 | FLAG_REUSE_BY_RESET = 0x01, 38 | FLAG_REUSE_BY_CUSER = 0x02, 39 | FLAG_REUSE_BY_PROXY = 0x04, 40 | FLAG_CHARSET_MASK = 0xf0, 41 | FLAG_CHARSET_EQUAL = 0x10, 42 | FLAG_CHARSET_DIFFER = 0x20, 43 | }; 44 | 45 | void init_options(MYSQL *c); 46 | void release(MYSQL *c); 47 | void set_names(MYSQL* c); 48 | void query_charset(MYSQL* c); 49 | void query_version(MYSQL* c); 50 | }; 51 | } // namespace flame::mysql 52 | -------------------------------------------------------------------------------- /src/flame/mysql/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mysql.h" 4 | 5 | namespace flame::mysql { 6 | 7 | class _connection_pool; 8 | class client : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value __construct(php::parameters ¶ms); 12 | php::value __destruct(php::parameters ¶ms); 13 | php::value escape(php::parameters ¶ms); 14 | php::value begin_tx(php::parameters ¶ms); 15 | // php::value where(php::parameters ¶ms); 16 | // php::value order(php::parameters ¶ms); 17 | // php::value limit(php::parameters ¶ms); 18 | php::value query(php::parameters ¶ms); 19 | php::value insert(php::parameters ¶ms); 20 | php::value delete_(php::parameters ¶ms); 21 | php::value update(php::parameters ¶ms); 22 | php::value select(php::parameters ¶ms); 23 | php::value one(php::parameters ¶ms); 24 | php::value get(php::parameters ¶ms); 25 | php::value server_version(php::parameters& params); 26 | php::value last_query(php::parameters& params); 27 | protected: 28 | std::shared_ptr<_connection_pool> cp_; 29 | friend php::value connect(php::parameters ¶ms); 30 | }; 31 | } // namespace flame::mysql 32 | -------------------------------------------------------------------------------- /src/flame/mysql/mysql.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include 4 | 5 | namespace flame::mysql { 6 | 7 | void declare(php::extension_entry &ext); 8 | php::value connect(php::parameters& params); 9 | 10 | void build_where(std::shared_ptr cm, php::buffer &buf, const php::value &data); 11 | void build_order(std::shared_ptr cm, php::buffer &buf, const php::value &data); 12 | void build_limit(std::shared_ptr cm, php::buffer &buf, const php::value &data); 13 | void build_insert(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); 14 | void build_delete(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); 15 | void build_update(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); 16 | void build_select(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); 17 | void build_one(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); 18 | void build_get(std::shared_ptr cm, php::buffer &buf, php::parameters ¶ms); 19 | } 20 | -------------------------------------------------------------------------------- /src/flame/mysql/result.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "result.h" 3 | #include "_connection_lock.h" 4 | 5 | namespace flame::mysql { 6 | 7 | void result::declare(php::extension_entry &ext) { 8 | php::class_entry class_result("flame\\mysql\\result"); 9 | class_result 10 | .property({"stored_rows", 0}) 11 | .method<&result::fetch_row>("fetch_row") 12 | .method<&result::fetch_all>("fetch_all"); 13 | 14 | ext.add(std::move(class_result)); 15 | } 16 | 17 | php::value result::fetch_row(php::parameters ¶ms) { 18 | php::array row(nullptr); 19 | if (cl_ && rs_) { 20 | coroutine_handler ch{coroutine::current}; 21 | row = cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch); 22 | } 23 | if (row.type_of(php::TYPE::NULLABLE)) { 24 | // 尽早的释放连接 25 | rs_.reset(); 26 | cl_.reset(); 27 | } 28 | return row; 29 | } 30 | 31 | php::value result::fetch_all(php::parameters ¶ms) { 32 | if (cl_ && rs_) { 33 | coroutine_handler ch{coroutine::current}; 34 | auto conn = cl_->acquire(ch); 35 | php::array data {4}, row; 36 | 37 | for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.type_of(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) { 38 | data.set(data.size(), row); 39 | } 40 | // 尽早的释放连接 41 | rs_.reset(); 42 | cl_.reset(); 43 | return data; 44 | } 45 | else { 46 | return nullptr; 47 | } 48 | } 49 | } // namespace flame::mysql 50 | -------------------------------------------------------------------------------- /src/flame/mysql/result.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mysql.h" 4 | 5 | namespace flame::mysql { 6 | 7 | class _connection_lock; 8 | class result: public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value fetch_row(php::parameters ¶ms); 12 | php::value fetch_all(php::parameters ¶ms); 13 | 14 | private: 15 | std::shared_ptr<_connection_lock> cl_; 16 | std::shared_ptr rs_; 17 | MYSQL_FIELD *f_; 18 | unsigned int n_; // Number Of Fields 19 | friend class _connection_base; 20 | }; 21 | } // namespace flame::mysql 22 | -------------------------------------------------------------------------------- /src/flame/mysql/tx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "mysql.h" 4 | 5 | namespace flame::mysql { 6 | 7 | class _connection_lock; 8 | class tx : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value __construct(php::parameters ¶ms); // 私有 12 | php::value __destruct(php::parameters& params); 13 | php::value commit(php::parameters ¶ms); 14 | php::value rollback(php::parameters ¶ms); 15 | 16 | php::value escape(php::parameters ¶ms); 17 | php::value query(php::parameters ¶ms); 18 | 19 | php::value insert(php::parameters ¶ms); 20 | php::value delete_(php::parameters ¶ms); 21 | php::value update(php::parameters ¶ms); 22 | php::value select(php::parameters ¶ms); 23 | php::value one(php::parameters ¶ms); 24 | php::value get(php::parameters ¶ms); 25 | 26 | protected: 27 | std::shared_ptr<_connection_lock> cl_; 28 | bool done_ = false; 29 | friend class client; 30 | }; 31 | } // namespace flame::mysql 32 | -------------------------------------------------------------------------------- /src/flame/os/os.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::os { 5 | 6 | void declare(php::extension_entry &ext); 7 | php::value interfaces(php::parameters ¶ms); 8 | php::value spawn(php::parameters ¶ms); 9 | php::value exec(php::parameters ¶ms); 10 | 11 | } // namespace flame::os 12 | -------------------------------------------------------------------------------- /src/flame/os/process.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "process.h" 3 | 4 | namespace flame::os { 5 | 6 | void process::declare(php::extension_entry &ext) { 7 | ext 8 | .constant({"flame\\os\\SIGTERM", SIGTERM}) 9 | .constant({"flame\\os\\SIGKILL", SIGKILL}) 10 | .constant({"flame\\os\\SIGINT", SIGINT}) 11 | .constant({"flame\\os\\SIGUSR1", SIGUSR1}) 12 | .constant({"flame\\os\\SIGUSR2", SIGUSR2}); 13 | 14 | php::class_entry class_process("flame\\os\\process"); 15 | class_process 16 | .property({"pid", 0}) 17 | .method<&process::__construct>("__construct", {}, php::PRIVATE) 18 | .method<&process::__destruct>("__destruct") 19 | .method<&process::kill>("kill", { 20 | {"signal", php::TYPE::INTEGER, false, true} 21 | }) 22 | .method<&process::detach>("detach") 23 | .method<&process::wait>("wait") 24 | .method<&process::stdout>("stdout") 25 | .method<&process::stderr>("stderr"); 26 | ext.add(std::move(class_process)); 27 | } 28 | 29 | php::value process::__construct(php::parameters& params) { 30 | return nullptr; 31 | } 32 | 33 | php::value process::__destruct(php::parameters& params) { 34 | if (!detach_) { 35 | if (!exit_ && !c_->wait_for(std::chrono::milliseconds(10000))) 36 | c_->terminate(); 37 | if (c_->joinable()) c_->join(); 38 | } 39 | return nullptr; 40 | } 41 | 42 | php::value process::kill(php::parameters ¶ms) { 43 | if (!exit_ && c_->valid()) { 44 | if (params.size() > 0) ::kill(get("pid"), params[0].to_integer()); 45 | else ::kill(get("pid"), SIGTERM); 46 | } 47 | return nullptr; 48 | } 49 | 50 | php::value process::detach(php::parameters& params) { 51 | detach_ = true; 52 | c_->detach(); 53 | return nullptr; 54 | } 55 | 56 | php::value process::wait(php::parameters ¶ms) { 57 | if (!exit_ && c_->valid()) { 58 | ch_.reset(coroutine::current); 59 | ch_.suspend(); 60 | ch_.reset(); 61 | } 62 | if (c_->joinable()) c_->join(); 63 | return nullptr; 64 | } 65 | 66 | php::value process::stdout(php::parameters ¶ms) { 67 | wait(params); 68 | if (c_->valid()) return out_.get(); 69 | return nullptr; 70 | } 71 | 72 | php::value process::stderr(php::parameters ¶ms) { 73 | wait(params); 74 | if (c_->valid()) return err_.get(); 75 | return nullptr; 76 | } 77 | } // namespace flame::os 78 | -------------------------------------------------------------------------------- /src/flame/os/process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include 5 | #include 6 | 7 | namespace flame::os { 8 | class process: public php::class_base { 9 | public: 10 | static void declare(php::extension_entry& ext); 11 | php::value __construct(php::parameters& params); 12 | php::value __destruct(php::parameters& params); 13 | php::value kill(php::parameters& params); 14 | php::value wait(php::parameters& params); 15 | php::value detach(php::parameters& params); 16 | php::value stdout(php::parameters& params); 17 | php::value stderr(php::parameters& params); 18 | private: 19 | std::unique_ptr c_; 20 | coroutine_handler ch_; 21 | std::future out_; 22 | std::future err_; 23 | bool exit_ = false; 24 | bool detach_ = false; 25 | friend class php::value spawn(php::parameters& params); 26 | friend class php::value exec(php::parameters& params); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/flame/queue.cpp: -------------------------------------------------------------------------------- 1 | #include "coroutine.h" 2 | #include "queue.h" 3 | 4 | namespace flame { 5 | void queue::declare(php::extension_entry &ext) { 6 | php::class_entry class_queue("flame\\queue"); 7 | class_queue 8 | .method<&queue::__construct>("__construct", { 9 | {"n", php::TYPE::INTEGER, false, true}, 10 | }) 11 | .method<&queue::push>("push", { 12 | {"value", php::TYPE::UNDEFINED} 13 | }) 14 | .method<&queue::pop>("pop") 15 | .method<&queue::close>("close") 16 | .method<&queue::is_closed>("is_closed"); 17 | ext 18 | .add(std::move(class_queue)) 19 | .function("flame\\select"); 20 | } 21 | 22 | php::value queue::__construct(php::parameters& params) { 23 | if (params.size() > 0) 24 | q_.reset(new coroutine_queue(static_cast(params[0]))); 25 | else 26 | q_.reset(new coroutine_queue(1)); 27 | return nullptr; 28 | } 29 | 30 | php::value queue::push(php::parameters ¶ms) { 31 | coroutine_handler ch{coroutine::current}; 32 | q_->push(params[0], ch); 33 | return nullptr; 34 | } 35 | 36 | php::value queue::pop(php::parameters ¶ms) { 37 | coroutine_handler ch{coroutine::current}; 38 | auto x = q_->pop(ch); 39 | if (x) return x.value(); 40 | else return nullptr; 41 | } 42 | 43 | php::value queue::close(php::parameters ¶ms) { 44 | q_->close(); 45 | return nullptr; 46 | } 47 | 48 | php::value queue::is_closed(php::parameters& params) { 49 | return q_->is_closed(); 50 | } 51 | 52 | php::value queue::select(php::parameters& params) { 53 | std::vector< std::shared_ptr> > qs; 54 | std::map< std::shared_ptr>, php::object > mm; 55 | for (auto i = 0; i < params.size(); ++i) { 56 | if (!params[i].instanceof(php::class_entry::entry())) 57 | throw php::exception(zend_ce_type_error, "Failed to select: instanceof flame\\queue required", -1); 58 | php::object obj = params[i]; 59 | queue* ptr = static_cast(php::native(obj)); 60 | qs.push_back( ptr->q_ ); 61 | mm.insert({ptr->q_, obj}); 62 | } 63 | coroutine_handler ch{coroutine::current}; 64 | std::shared_ptr> q = select_queue(qs, ch); 65 | return mm[q]; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/flame/queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../vendor.h" 3 | #include "../coroutine_queue.h" 4 | 5 | namespace flame { 6 | 7 | class queue: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry &ext); 10 | static php::value select(php::parameters& params); 11 | php::value __construct(php::parameters& params); 12 | php::value push(php::parameters ¶ms); 13 | php::value pop(php::parameters ¶ms); 14 | php::value close(php::parameters ¶ms); 15 | php::value is_closed(php::parameters ¶ms); 16 | private: 17 | std::shared_ptr> q_; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/flame/rabbitmq/_client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../../url.h" 4 | #include "../../coroutine_queue.h" 5 | #include "../coroutine.h" 6 | #include "rabbitmq.h" 7 | 8 | namespace flame::rabbitmq { 9 | 10 | class _client: public std::enable_shared_from_this<_client> { 11 | public: 12 | _client(url u, coroutine_handler& ch); 13 | 14 | void consume(const std::string& queue, coroutine_queue& q, coroutine_handler& ch); 15 | void confirm(std::uint64_t tag, coroutine_handler& ch); 16 | void reject(std::uint64_t tag, int flags, coroutine_handler& ch); 17 | void consumer_close(coroutine_handler& ch); 18 | void consumer_close(); 19 | void publish(const std::string& ex, const std::string& rk, const AMQP::Envelope& env, coroutine_handler& ch); 20 | void publish(const std::string& ex, const std::string& rk, const char* msg, size_t len, coroutine_handler& ch); 21 | // void producer_cb(AMQP::DeferredPublisher& defer, coroutine_handler& ch); 22 | void heartbeat(); 23 | bool has_error(); 24 | const std::string& error(); 25 | private: 26 | AMQP::LibBoostAsioHandler chl_; 27 | AMQP::TcpConnection con_; 28 | AMQP::TcpChannel chn_; 29 | int pf_; 30 | int fl_; 31 | // 心跳定时器(在工作线程创建) 32 | boost::asio::steady_timer heartb_tm_; 33 | std::string consumer_tg_; 34 | coroutine_handler consumer_ch_; 35 | bool producer_cb_; 36 | coroutine_handler producer_ch_; 37 | std::string error_; 38 | 39 | friend class consumer; 40 | friend php::value consume(php::parameters& params); 41 | friend php::value produce(php::parameters& params); 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/flame/rabbitmq/consumer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "rabbitmq.h" 4 | 5 | namespace flame::rabbitmq { 6 | 7 | class _client; 8 | class consumer : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value __construct(php::parameters ¶ms); // 私有 12 | php::value __destruct(php::parameters& params); 13 | php::value run(php::parameters ¶ms); 14 | php::value confirm(php::parameters ¶ms); 15 | php::value reject(php::parameters ¶ms); 16 | php::value close(php::parameters ¶ms); 17 | private: 18 | // 实际的客户端对象可能超过当前对象的生存期 19 | std::shared_ptr<_client> cc_; 20 | php::callable cb_; 21 | std::string qn_; 22 | int concurrent_; 23 | 24 | friend php::value consume(php::parameters ¶ms); 25 | }; 26 | } // namespace flame::rabbitmq 27 | -------------------------------------------------------------------------------- /src/flame/rabbitmq/message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "rabbitmq.h" 4 | 5 | namespace flame::rabbitmq { 6 | 7 | class message : public php::class_base { 8 | public: 9 | static void declare(php::extension_entry &ext); 10 | php::value __construct(php::parameters ¶ms); // 私有 11 | php::value to_json(php::parameters ¶ms); 12 | php::value to_string(php::parameters ¶ms); 13 | 14 | void build_ex(const AMQP::Message &msg, std::uint64_t tag); 15 | void build_ex(AMQP::Envelope &env); 16 | std::uint64_t tag_; 17 | }; 18 | } // namespace flame::rabbitmq 19 | -------------------------------------------------------------------------------- /src/flame/rabbitmq/producer.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "producer.h" 3 | #include "_client.h" 4 | #include "message.h" 5 | 6 | namespace flame::rabbitmq { 7 | 8 | void producer::declare(php::extension_entry &ext) { 9 | php::class_entry class_producer("flame\\rabbitmq\\producer"); 10 | class_producer 11 | .method<&producer::__construct>("__construct", {}, php::PRIVATE) 12 | .method<&producer::publish>("publish", { 13 | {"exchange", php::TYPE::STRING}, 14 | {"message", php::TYPE::UNDEFINED}, 15 | }); 16 | ext.add(std::move(class_producer)); 17 | } 18 | 19 | php::value producer::__construct(php::parameters ¶ms) { 20 | return nullptr; 21 | } 22 | 23 | php::value producer::publish(php::parameters ¶ms) { 24 | std::string exch = params[0]; 25 | std::string routing_key; 26 | if (params.size() > 2) routing_key = params[2].to_string(); 27 | 28 | coroutine_handler ch{coroutine::current}; 29 | if (params[1].instanceof(php::class_entry::entry())) { 30 | php::object msg = params[1]; 31 | message *msg_ = static_cast(php::native(msg)); 32 | php::string body = msg.get("body"); 33 | body.to_string(); 34 | AMQP::Envelope env(body.c_str(), body.size()); 35 | msg_->build_ex(env); 36 | // 未指定时使用 message 默认的 routing_key 37 | if (routing_key.empty()) routing_key = msg.get("routing_key").to_string(); 38 | 39 | coroutine_handler ch {coroutine::current}; 40 | cc_->publish(exch, routing_key, env, ch); 41 | } 42 | else { 43 | php::string msg = params[1]; 44 | msg.to_string(); 45 | cc_->publish(exch, routing_key, msg.c_str(), msg.size(), ch); 46 | } 47 | return nullptr; 48 | } 49 | } // namespace flame::rabbitmq 50 | -------------------------------------------------------------------------------- /src/flame/rabbitmq/producer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "rabbitmq.h" 4 | 5 | namespace flame::rabbitmq { 6 | class _client; 7 | class producer: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry& ext); 10 | php::value __construct(php::parameters& params); // 私有 11 | php::value publish(php::parameters& params); 12 | private: 13 | // 实际的客户端对象可能超过当前对象的生存期 14 | std::shared_ptr<_client> cc_; 15 | 16 | friend php::value produce(php::parameters& params); 17 | friend class _client; 18 | }; 19 | } // namespace flame::rabbitmq 20 | -------------------------------------------------------------------------------- /src/flame/rabbitmq/rabbitmq.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include 4 | #include 5 | 6 | namespace flame::rabbitmq { 7 | 8 | void declare(php::extension_entry &ext); 9 | php::value consume(php::parameters ¶ms); 10 | php::value produce(php::parameters ¶ms); 11 | 12 | php::array table2array(const AMQP::Table &table); 13 | AMQP::Table array2table(const php::array &table); 14 | } // namespace flame::rabbitmq 15 | -------------------------------------------------------------------------------- /src/flame/redis/_connection_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "redis.h" 5 | 6 | namespace flame::redis { 7 | 8 | enum class reply_type { 9 | SIMPLE = 0x0000, // 默认返回处理 10 | ASSOC_ARRAY_1 = 0x0001, // 返回数据多项按 KEY VAL 生成关联数组 11 | ASSOC_ARRAY_2 = 0x0002, // 第一层普通数组, 第二层关联数组 (HSCAN/ZSCAN) 12 | ASSOC_ARRAY_3 = 0x0010, // 返回数据多项按 VAL KEY 生成关联数组 13 | COMBINE_1 = 0x0004, // 与参数结合生成关联数组 14 | COMBINE_2 = 0x0008, // 同上, 但偏移错位 1 个参数 15 | EXEC = 0x0100, // 事务特殊处理方式(需要结合上面几种方式) 16 | PIPE = 0x0200, // 与事务形式相同 17 | }; 18 | 19 | class _connection_base { 20 | public: 21 | virtual std::shared_ptr acquire(coroutine_handler &ch) = 0; 22 | php::value reply2value(redisReply *rp, php::array &argv, reply_type rt); 23 | std::string format(php::string& name, php::array& argv); 24 | }; 25 | } // namespace flame::mysql 26 | -------------------------------------------------------------------------------- /src/flame/redis/_connection_lock.cpp: -------------------------------------------------------------------------------- 1 | #include "../coroutine.h" 2 | #include "_connection_base.h" 3 | #include "_connection_lock.h" 4 | 5 | namespace flame::redis { 6 | 7 | _connection_lock::_connection_lock(std::shared_ptr c) 8 | : conn_(c) { 9 | } 10 | 11 | _connection_lock::~_connection_lock() { 12 | } 13 | 14 | std::shared_ptr _connection_lock::acquire(coroutine_handler &ch) { 15 | return conn_; 16 | } 17 | 18 | void _connection_lock::push(php::string &name, php::array &argv, reply_type type) { 19 | // 暂未提交, 仅记录 20 | // TODO 优化: 是否可以在提交前在进行 format 操作 (减少内存占用的持续时间) ? 21 | cmds_.push_back({name, argv, type, format(name, argv)}); 22 | } 23 | 24 | void _connection_lock::push(php::string &name, php::parameters& params, reply_type type) { 25 | php::array argv {params}; 26 | push(name, argv, type); 27 | } 28 | 29 | php::array _connection_lock::exec(coroutine_handler &ch) { 30 | boost::asio::post(gcontroller->context_x, [this, &ch] () { 31 | // 在工作线程中, 提交所有待执行命令 32 | for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) 33 | redisAppendFormattedCommand(conn_.get(), i->strs.c_str(), i->strs.size()); 34 | // 读取对应的返回值 35 | for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) 36 | redisGetReply(conn_.get(), (void**)&i->reply); 37 | // 回到主线程 38 | ch.resume(); 39 | }); 40 | ch.suspend(); 41 | // TODO 若执行过程当中存在异常,如何上报?(似乎 Redis 原则上不会部分失败) 42 | // 整合个命令返回值 43 | php::array data(cmds_.size()); 44 | for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) { 45 | data.set(data.size(), reply2value(i->reply, i->argv, i->type)); 46 | freeReplyObject(i->reply); 47 | } 48 | // 命令执行完毕 (所有返回值处理完成) 49 | cmds_.clear(); 50 | return std::move(data); 51 | } 52 | } // namespace flame::redis 53 | -------------------------------------------------------------------------------- /src/flame/redis/_connection_lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "redis.h" 5 | #include "_connection_base.h" 6 | 7 | namespace flame::redis { 8 | 9 | class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> { 10 | public: 11 | _connection_lock(std::shared_ptr c); 12 | ~_connection_lock(); 13 | std::shared_ptr acquire(coroutine_handler &ch) override; 14 | void push(php::string &name, php::array &argv, reply_type rt); 15 | void push(php::string &name, php::parameters &argv, reply_type rt); 16 | php::array exec(coroutine_handler& ch); 17 | struct command_t { 18 | php::string name; 19 | php::array argv; 20 | reply_type type; 21 | std::string strs; 22 | redisReply* reply; 23 | }; 24 | private: 25 | std::shared_ptr conn_; 26 | std::list cmds_; 27 | }; 28 | } // namespace flame::redis 29 | -------------------------------------------------------------------------------- /src/flame/redis/_connection_pool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../../url.h" 4 | #include "../coroutine.h" 5 | #include "redis.h" 6 | #include "_connection_base.h" 7 | 8 | 9 | namespace flame::redis { 10 | class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool> { 11 | public: 12 | _connection_pool(url u); 13 | ~_connection_pool(); 14 | std::shared_ptr acquire(coroutine_handler &ch) override; 15 | php::value exec(std::shared_ptr rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler &ch); 16 | php::value exec(std::shared_ptr rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler &ch); 17 | void sweep(); 18 | private: 19 | url url_; 20 | const std::uint16_t min_; 21 | const std::uint16_t max_; 22 | std::uint16_t size_; 23 | 24 | boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题; 25 | std::list)>> await_; 26 | struct connection_t { 27 | redisContext *conn; 28 | std::chrono::time_point ttl; 29 | }; 30 | std::list conn_; 31 | // 连接池闲置扫描(工作线程) 32 | boost::asio::steady_timer sweep_; 33 | 34 | bool ping(redisContext* c); 35 | redisContext *create(std::string& err); 36 | void release(redisContext *c); 37 | 38 | friend php::value connect(php::parameters& params); 39 | }; 40 | } // namespace flame::mysql 41 | -------------------------------------------------------------------------------- /src/flame/redis/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "redis.h" 4 | 5 | namespace flame::redis { 6 | class _connection_pool; 7 | class client : public php::class_base { 8 | public: 9 | static void declare(php::extension_entry &ext); 10 | php::value __construct(php::parameters ¶ms); // 私有 11 | php::value __call(php::parameters ¶ms); 12 | php::value __isset(php::parameters& params); 13 | // 处理特殊情况的命令 14 | php::value mget(php::parameters ¶ms); 15 | php::value hmget(php::parameters ¶ms); 16 | php::value hgetall(php::parameters ¶ms); 17 | php::value hscan(php::parameters ¶ms); 18 | php::value sscan(php::parameters ¶ms); 19 | php::value zscan(php::parameters ¶ms); 20 | php::value zrange(php::parameters ¶ms); 21 | php::value zrevrange(php::parameters ¶ms); 22 | php::value zrangebyscore(php::parameters ¶ms); 23 | php::value zrevrangebyscore(php::parameters ¶ms); 24 | php::value zpopmin(php::parameters ¶ms); 25 | php::value zpopmax(php::parameters ¶ms); 26 | php::value unsubscribe(php::parameters ¶ms); 27 | php::value punsubscribe(php::parameters ¶ms); 28 | // 批量 29 | php::value multi(php::parameters ¶ms); 30 | // 用于标记不实现的功能 31 | php::value unimplement(php::parameters ¶ms); 32 | private: 33 | std::shared_ptr<_connection_pool> cp_; 34 | friend php::value connect(php::parameters ¶ms); 35 | }; 36 | } // namespace flame::redis 37 | -------------------------------------------------------------------------------- /src/flame/redis/redis.cpp: -------------------------------------------------------------------------------- 1 | #include "redis.h" 2 | #include "_connection_pool.h" 3 | #include "client.h" 4 | #include "tx.h" 5 | 6 | namespace flame::redis { 7 | 8 | void declare(php::extension_entry& ext) { 9 | ext.function("flame\\redis\\connect", { 10 | {"uri", php::TYPE::STRING} 11 | }); 12 | client::declare(ext); 13 | tx::declare(ext); 14 | } 15 | 16 | php::value connect(php::parameters ¶ms) { 17 | url u(params[0]); 18 | php::object obj {php::class_entry::entry()}; 19 | client *ptr = static_cast(php::native(obj)); 20 | ptr->cp_.reset(new _connection_pool(u)); 21 | ptr->cp_->sweep(); // 启动自动清理扫描 22 | gcontroller->on_stop([cp = ptr->cp_] () { 23 | cp->sweep_.cancel(); 24 | }); 25 | return std::move(obj); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/flame/redis/redis.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include 4 | 5 | namespace flame::redis { 6 | void declare(php::extension_entry &ext); 7 | php::value connect(php::parameters ¶ms); 8 | } // namespace flame::redis 9 | -------------------------------------------------------------------------------- /src/flame/redis/tx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "redis.h" 4 | 5 | namespace flame::redis { 6 | 7 | class _connection_lock; 8 | class tx : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | php::value __construct(php::parameters ¶ms); // 私有 12 | php::value exec(php::parameters& params); 13 | php::value __call(php::parameters ¶ms); 14 | // 处理特殊情况的命令 15 | php::value mget(php::parameters ¶ms); 16 | php::value hmget(php::parameters ¶ms); 17 | php::value hgetall(php::parameters ¶ms); 18 | php::value hscan(php::parameters ¶ms); 19 | php::value sscan(php::parameters ¶ms); 20 | php::value zscan(php::parameters ¶ms); 21 | php::value zrange(php::parameters ¶ms); 22 | php::value zrevrange(php::parameters ¶ms); 23 | php::value zrangebyscore(php::parameters ¶ms); 24 | php::value zrevrangebyscore(php::parameters ¶ms); 25 | php::value unsubscribe(php::parameters ¶ms); 26 | php::value punsubscribe(php::parameters ¶ms); 27 | // 批量 28 | php::value multi(php::parameters ¶ms); 29 | // 用于标记不实现的功能 30 | php::value unimplement(php::parameters ¶ms); 31 | private: 32 | std::shared_ptr<_connection_lock> cl_; 33 | friend class client; 34 | }; 35 | } // namespace flame::redis 36 | -------------------------------------------------------------------------------- /src/flame/smtp/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "smtp.h" 4 | 5 | namespace flame::http { 6 | class client_poll; 7 | } 8 | namespace flame::smtp { 9 | 10 | class client: public php::class_base { 11 | public: 12 | static void declare(php::extension_entry& ext); 13 | client(); 14 | ~client(); 15 | php::value __construct(php::parameters& params); 16 | php::value post(php::parameters& params); 17 | private: 18 | std::string c_rurl_; // 服务器地址 smtp://user:pass@xxx.xxx.xxx.xxx:port/ 19 | std::string c_from_; // 解析后的 URL 用于设置 FROM 信息 20 | CURLM *c_multi_ = nullptr; 21 | int c_still_ = 0; 22 | boost::asio::steady_timer c_timer_; 23 | 24 | php::value post_ex(const php::object& req); 25 | static int c_socket_cb(CURL* e, curl_socket_t fd, int action, void* cbp, void* data); 26 | static int c_timer_cb(CURLM *m, long timeout_ms, void* data); 27 | static curl_socket_t c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* address); 28 | static int c_socket_close_cb(void* data, curl_socket_t fd); 29 | static void c_socket_ready_cb(const boost::system::error_code& error, http::client_poll* poll, curl_socket_t fd, int action); 30 | void check_done(); 31 | 32 | friend php::value connect(php::parameters& params); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/flame/smtp/message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | #include "smtp.h" 5 | 6 | namespace flame::smtp { 7 | 8 | class message: public php::class_base { 9 | public: 10 | struct part_t { 11 | std::string content_type; 12 | std::string data; 13 | }; 14 | 15 | static void declare(php::extension_entry& ext); 16 | php::value __construct(php::parameters& params); 17 | message(); 18 | ~message(); 19 | php::value from(php::parameters& params); 20 | php::value to(php::parameters& params); 21 | php::value cc(php::parameters& params); 22 | php::value subject(php::parameters& params); 23 | php::value append(php::parameters& params); 24 | php::value to_string(php::parameters& params); 25 | private: 26 | void build_ex(); 27 | CURL* c_easy_; // owner 28 | curl_slist* c_rcpt_; // owner 29 | 30 | int status_; 31 | 32 | std::string boundary_; 33 | 34 | std::size_t c_size_; 35 | php::buffer c_data_; 36 | php::string c_mail_; 37 | static std::size_t read_cb(char* buffer, std::size_t size, std::size_t n, void *data); 38 | 39 | CURLcode c_code_; 40 | coroutine_handler c_coro_; 41 | 42 | static std::string random(int size = 16); 43 | friend class client; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/flame/smtp/smtp.cpp: -------------------------------------------------------------------------------- 1 | #include "smtp.h" 2 | #include "message.h" 3 | #include "client.h" 4 | #include "../../url.h" 5 | #include "../../util.h" 6 | 7 | namespace flame::smtp { 8 | 9 | static php::value create_boundary(php::parameters& params) { 10 | int size = params[0]; 11 | return php::string(util::random_string(size), size); 12 | } 13 | 14 | void declare(php::extension_entry& ext) { 15 | ext 16 | .on_module_startup([] (php::extension_entry& ext) -> bool { 17 | curl_global_init(CURL_GLOBAL_DEFAULT); 18 | return true; 19 | }) 20 | .function("flame\\smtp\\connect", { 21 | {"url", php::TYPE::STRING}, 22 | }) 23 | .function("flame\\smtp\\create_boundary", { 24 | {"size", php::TYPE::INTEGER} 25 | }); 26 | message::declare(ext); 27 | client::declare(ext); 28 | } 29 | 30 | php::value connect(php::parameters& params) { 31 | php::object obj(php::class_entry::entry()); 32 | client* cli = static_cast(php::native(obj)); 33 | 34 | cli->c_rurl_ = static_cast(params[0]); 35 | cli->c_from_ = url(cli->c_rurl_).user; 36 | return obj; 37 | } 38 | } -------------------------------------------------------------------------------- /src/flame/smtp/smtp.h: -------------------------------------------------------------------------------- 1 | #include "../../vendor.h" 2 | #include 3 | 4 | namespace flame::smtp { 5 | void declare(php::extension_entry &ext); 6 | php::value connect(php::parameters& params); 7 | } 8 | -------------------------------------------------------------------------------- /src/flame/tcp/server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::tcp { 5 | class server : public php::class_base { 6 | public: 7 | static void declare(php::extension_entry &ext); 8 | server(); 9 | php::value __construct(php::parameters ¶ms); 10 | php::value run(php::parameters ¶ms); 11 | php::value close(php::parameters ¶ms); 12 | private: 13 | boost::asio::ip::tcp::acceptor acceptor_; 14 | boost::asio::ip::tcp::socket socket_; 15 | boost::asio::ip::tcp::endpoint addr_; 16 | php::callable cb_; 17 | bool closed_; 18 | }; 19 | } // namespace flame::tcp 20 | -------------------------------------------------------------------------------- /src/flame/tcp/socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../../coroutine_mutex.h" 4 | #include "../coroutine.h" 5 | 6 | namespace flame::tcp { 7 | 8 | class socket : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | socket(); 12 | php::value read(php::parameters ¶m); 13 | php::value write(php::parameters ¶ms); 14 | php::value close(php::parameters ¶ms); 15 | 16 | private: 17 | boost::asio::ip::tcp::socket socket_; 18 | boost::asio::streambuf buffer_; 19 | coroutine_mutex rmutex_; 20 | coroutine_mutex wmutex_; 21 | 22 | friend class server; 23 | friend php::value connect(php::parameters ¶ms); 24 | }; 25 | } // namespace flame::tcp 26 | -------------------------------------------------------------------------------- /src/flame/tcp/tcp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::tcp { 5 | void declare(php::extension_entry &ext); 6 | php::value connect(php::parameters ¶ms); 7 | } // namespace flame::tcp 8 | -------------------------------------------------------------------------------- /src/flame/time/scheduler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | 5 | namespace flame::time { 6 | class scheduler; 7 | extern scheduler* sched_; 8 | class timer_base; 9 | /** 10 | * 使用堆存储各定时器时间,并按照指定时间进行唤醒回调 11 | */ 12 | class scheduler { 13 | public: 14 | static void declare(php::extension_entry& ext); 15 | scheduler(); 16 | void insert(std::shared_ptr tm); 17 | void remove(std::shared_ptr tm); 18 | void start(); 19 | void close(); 20 | 21 | static bool compare(const std::shared_ptr& tm1, const std::shared_ptr& tm2); 22 | private: 23 | boost::asio::io_context::strand strand_; 24 | boost::asio::steady_timer tm_; 25 | std::priority_queue, std::vector>, decltype(scheduler::compare)*> st_; 26 | coroutine_handler ch_; 27 | bool close_; 28 | 29 | void run(); 30 | }; 31 | } -------------------------------------------------------------------------------- /src/flame/time/time.cpp: -------------------------------------------------------------------------------- 1 | #include "time.h" 2 | #include "../coroutine.h" 3 | #include "timer.h" 4 | #include "scheduler.h" 5 | 6 | namespace flame::time { 7 | 8 | static std::chrono::time_point time_system; 9 | static std::chrono::time_point time_steady; 10 | 11 | static php::value sleep(php::parameters& params) { 12 | coroutine_handler ch {coroutine::current}; 13 | boost::asio::steady_timer tm(gcontroller->context_x); 14 | int ms = static_cast(params[0]); 15 | if (ms <= 0) ms = 1; 16 | tm.expires_from_now(std::chrono::milliseconds(ms)); 17 | tm.async_wait(ch); 18 | return nullptr; 19 | } 20 | 21 | std::chrono::time_point now() { 22 | std::chrono::milliseconds diff = std::chrono::duration_cast( 23 | std::chrono::steady_clock::now() - time_steady); 24 | return time_system + diff; 25 | } 26 | 27 | php::string iso(const std::chrono::time_point& now) { 28 | php::string data(19); 29 | std::time_t t = std::chrono::system_clock::to_time_t(now); 30 | struct tm *m = std::localtime(&t); 31 | sprintf(data.data(), "%04d-%02d-%02d %02d:%02d:%02d", 32 | 1900 + m->tm_year, 33 | 1 + m->tm_mon, 34 | m->tm_mday, 35 | m->tm_hour, 36 | m->tm_min, 37 | m->tm_sec); 38 | return std::move(data); 39 | } 40 | 41 | php::string iso() { 42 | return iso(now()); 43 | } 44 | 45 | static php::value now(php::parameters ¶ms) { 46 | return std::chrono::duration_cast(now().time_since_epoch()).count(); 47 | } 48 | 49 | static php::value iso(php::parameters ¶ms) { 50 | return iso(); 51 | } 52 | 53 | void declare(php::extension_entry& ext) { 54 | time_steady = std::chrono::steady_clock::now(); 55 | time_system = std::chrono::system_clock::now(); 56 | 57 | ext 58 | .function("flame\\time\\sleep", { 59 | {"duration", php::TYPE::INTEGER}, 60 | }) 61 | // 毫秒时间戳 62 | .function("flame\\time\\now") 63 | // 标准时间 YYYY-mm-dd HH:ii:ss 64 | .function("flame\\time\\iso"); 65 | 66 | scheduler::declare(ext); 67 | timer::declare(ext); 68 | // timer_invoke::declare(ext); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/flame/time/time.h: -------------------------------------------------------------------------------- 1 | #include "../../vendor.h" 2 | 3 | namespace flame::time { 4 | void declare(php::extension_entry &ext); 5 | std::chrono::time_point now(); 6 | php::string iso(); 7 | php::string iso(const std::chrono::time_point& now); 8 | } 9 | -------------------------------------------------------------------------------- /src/flame/time/timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../coroutine.h" 4 | 5 | namespace flame::time { 6 | class timer_base: public std::enable_shared_from_this { 7 | public: 8 | timer_base(std::size_t ms, php::callable cb, bool repeat = false); 9 | ~timer_base(); 10 | void start(); 11 | void close(); 12 | bool closed() { 13 | return repeat_ == false && resolv_ < 0; 14 | } 15 | void reschedule(); 16 | virtual void trigger(); 17 | const std::chrono::steady_clock::time_point& expire_at() const { 18 | return at_; 19 | } 20 | protected: 21 | std::chrono::steady_clock::time_point at_; 22 | php::callable cb_; 23 | coroutine_handler ch_; 24 | std::size_t interval_; 25 | int resolv_; 26 | bool repeat_; 27 | 28 | friend class scheduler; 29 | friend class timer; 30 | }; 31 | 32 | 33 | class timer: public php::class_base { 34 | public: 35 | static void declare(php::extension_entry& ext); 36 | php::value __construct(php::parameters& params); 37 | php::value start(php::parameters& params); 38 | php::value close(php::parameters& params); 39 | 40 | timer(); 41 | std::shared_ptr tb_; 42 | bool start_; 43 | }; 44 | 45 | } -------------------------------------------------------------------------------- /src/flame/toml/_decode.cpp: -------------------------------------------------------------------------------- 1 | #include "_decode.h" 2 | 3 | namespace flame::toml { 4 | void decode_inplace(php::string& str) { 5 | php_stripcslashes(str); 6 | } 7 | } -------------------------------------------------------------------------------- /src/flame/toml/_decode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::toml { 5 | void decode_inplace(php::string& str); 6 | } -------------------------------------------------------------------------------- /src/flame/toml/_executor.cpp: -------------------------------------------------------------------------------- 1 | #include "_executor.h" 2 | #include "_decode.h" 3 | 4 | namespace flame::toml { 5 | 6 | void _executor::operator ()(const toml_parser::parser& p, std::string_view chunk) { 7 | buffer_.append(chunk.data(), chunk.size()); 8 | } 9 | 10 | void _executor::operator ()(const toml_parser::parser& p) { 11 | php::string raw = std::move(buffer_); 12 | // std::cout << "(" << (int)p.value_type() << ") " << p.field() << " => [" << raw << "]\n"; 13 | php::value val = _executor::restore(raw, p.value_type()); 14 | 15 | // switch(p.container_type()) { 16 | // case toml_parser::CONTAINER_TYPE_ARRAY: 17 | // break; 18 | // case toml_parser::CONTAINER_TYPE_ARRAY_TABLE: 19 | // break; 20 | // case toml_parser::CONTAINER_TYPE_TABLE: 21 | // default: 22 | // ; 23 | // } 24 | set(root_, p.field(), p.value_array_index(), val); 25 | } 26 | 27 | php::value _executor::restore(php::string& raw, std::uint8_t value_type) { 28 | php::value val; 29 | // 根据类型还原数据 30 | switch(value_type) { 31 | case toml_parser::VALUE_TYPE_BOOLEAN: 32 | val = raw.to_boolean(); 33 | break; 34 | case toml_parser::VALUE_TYPE_INTEGER: 35 | val = raw.to_integer(); 36 | break; 37 | case toml_parser::VALUE_TYPE_HEX_INTEGER: 38 | val = std::strtol(raw.data(), nullptr, 16); 39 | break; 40 | case toml_parser::VALUE_TYPE_OCT_INTEGER: 41 | val = std::strtol(raw.data(), nullptr, 8); 42 | break; 43 | case toml_parser::VALUE_TYPE_BIN_INTEGER: 44 | val = std::strtol(raw.data(), nullptr, 2); 45 | break; 46 | case toml_parser::VALUE_TYPE_INFINITY: 47 | val = raw.data()[0] == '-' ? - std::numeric_limits::infinity() : std::numeric_limits::infinity(); 48 | break; 49 | case toml_parser::VALUE_TYPE_NAN: 50 | val = NAN; 51 | break; 52 | case toml_parser::VALUE_TYPE_FLOAT: 53 | val = raw.to_float(); 54 | break; 55 | case toml_parser::VALUE_TYPE_DATE: 56 | val = php::datetime(raw.c_str()); 57 | break; 58 | case toml_parser::VALUE_TYPE_BASIC_STRING: 59 | decode_inplace(raw); 60 | // fallthrough 61 | case toml_parser::VALUE_TYPE_UNKNOWN: // 未知类型的数据按原始字符串处理 62 | case toml_parser::VALUE_TYPE_RAW_STRING: 63 | default: 64 | val = raw; 65 | } 66 | 67 | return val; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/flame/toml/_executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../vendor.h" 4 | #include "toml.h" 5 | 6 | namespace flame::toml { 7 | 8 | class _executor { 9 | public: 10 | _executor(php::array& root) noexcept 11 | : root_(root) {} 12 | void operator ()(const toml_parser::parser& p, std::string_view chunk); 13 | void operator ()(const toml_parser::parser& p); 14 | 15 | private: 16 | php::array& root_; 17 | php::buffer buffer_; 18 | 19 | static php::value restore(php::string& raw, std::uint8_t value_type); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/flame/toml/toml.h: -------------------------------------------------------------------------------- 1 | #include "../../vendor.h" 2 | #include 3 | namespace toml_parser = llparse::toml; 4 | 5 | namespace flame::toml { 6 | void declare(php::extension_entry &ext); 7 | 8 | void set(php::array& r, std::string_view prefix, std::size_t index, const php::value& v); 9 | php::value get(php::array r, std::string_view prefix, std::size_t index); 10 | } 11 | -------------------------------------------------------------------------------- /src/flame/udp/server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../../coroutine_mutex.h" 4 | #include "../coroutine.h" 5 | 6 | namespace flame::udp { 7 | class server: public php::class_base { 8 | public: 9 | static void declare(php::extension_entry &ext); 10 | server(); 11 | php::value __construct(php::parameters& params); 12 | php::value run(php::parameters& params); 13 | php::value send_to(php::parameters& params); 14 | php::value close(php::parameters& params); 15 | private: 16 | boost::asio::ip::udp::socket socket_; 17 | boost::asio::ip::udp::endpoint addr_; 18 | int concurrent_; 19 | int max_; 20 | bool closed_; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/flame/udp/socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | #include "../../coroutine_mutex.h" 4 | #include "../coroutine.h" 5 | 6 | namespace flame::udp { 7 | 8 | class socket : public php::class_base { 9 | public: 10 | static void declare(php::extension_entry &ext); 11 | socket(); 12 | php::value __construct(php::parameters ¶m); 13 | php::value recv_from(php::parameters ¶m); 14 | php::value recv(php::parameters& params); 15 | php::value send_to(php::parameters ¶ms); 16 | php::value send(php::parameters& params); 17 | php::value close(php::parameters ¶ms); 18 | 19 | private: 20 | boost::asio::ip::udp::socket socket_; 21 | php::buffer buffer_; 22 | coroutine_mutex rmutex_; 23 | bool connected_; 24 | int max_; 25 | 26 | friend class server; 27 | friend php::value connect(php::parameters ¶ms); 28 | }; 29 | } // namespace flame::udp 30 | -------------------------------------------------------------------------------- /src/flame/udp/udp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../vendor.h" 3 | 4 | namespace flame::udp { 5 | extern std::unique_ptr resolver_; 6 | void declare(php::extension_entry &ext); 7 | php::value bind_and_listen(php::parameters& params); 8 | php::value connect(php::parameters ¶ms); 9 | std::pair addr2pair(const std::string& addr); 10 | } // namespace flame::tcp 11 | -------------------------------------------------------------------------------- /src/flame/version.cpp: -------------------------------------------------------------------------------- 1 | #include "version.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | // #include 9 | #include 10 | #include 11 | 12 | namespace flame { 13 | 14 | #define VERSION_MACRO(major, minor, patch) VERSION_JOIN(major, minor, patch) 15 | #define VERSION_JOIN(major, minor, patch) #major"."#minor"."#patch 16 | 17 | static std::string openssl_version_str() { 18 | std::string version = (boost::format("%d.%d.%d") 19 | % ((OPENSSL_VERSION_NUMBER & 0xff0000000L) >> 28) 20 | % ((OPENSSL_VERSION_NUMBER & 0x00ff00000L) >> 20) 21 | % ((OPENSSL_VERSION_NUMBER & 0x0000ff000L) >> 12) ).str(); 22 | char status = (OPENSSL_VERSION_NUMBER & 0x000000ff0L) >> 4; 23 | if (status > 0) { 24 | version.push_back('a' + status - 1); 25 | } 26 | return version; 27 | } 28 | 29 | void version::declare(php::extension_entry& ext) { 30 | ext 31 | .desc({"vendor/openssl", openssl_version_str()}) 32 | .desc({"vendor/boost", BOOST_LIB_VERSION}) 33 | // .desc({"vendor/phpext", php::version()}) 34 | .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)}) 35 | // .desc({"vendor/mysqlc", mysql_get_client_info()}) 36 | .desc({"vendor/mariac", MARIADB_PACKAGE_VERSION}) 37 | .desc({"vendor/amqpcpp", "4.1.5"}) 38 | .desc({"vendor/rdkafka", rd_kafka_version_str()}) 39 | .desc({"vendor/mongoc", MONGOC_VERSION_S}) 40 | .desc({"vendor/nghttp2", NGHTTP2_VERSION}) 41 | .desc({"vendor/curl", LIBCURL_VERSION}); 42 | } 43 | } -------------------------------------------------------------------------------- /src/flame/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../vendor.h" 3 | 4 | namespace flame { 5 | class version { 6 | public: 7 | static void declare(php::extension_entry& ext); 8 | }; 9 | } -------------------------------------------------------------------------------- /src/flame/worker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../vendor.h" 3 | #include "../worker_logger_manager.h" 4 | #include "../signal_watcher.h" 5 | #include "../worker_ipc.h" 6 | #include "../coroutine_queue.h" 7 | 8 | namespace flame { 9 | class worker: public std::enable_shared_from_this, public signal_watcher, public worker_ipc, public worker_logger_manager { 10 | public: 11 | static inline std::shared_ptr get() { 12 | return worker::ww_; 13 | } 14 | static void declare(php::extension_entry& ext); 15 | // 主流程相关 16 | static php::value init(php::parameters& params); 17 | static php::value go(php::parameters& params); 18 | static php::value on(php::parameters ¶ms); 19 | static php::value off(php::parameters& params); 20 | static php::value run(php::parameters& params); 21 | static php::value quit(php::parameters& params); 22 | // 协程相关 23 | static php::value co_id(php::parameters& params); 24 | static php::value co_ct(php::parameters& params); 25 | // 级联数组设置、读取 26 | static php::value get(php::parameters& params); 27 | static php::value set(php::parameters& params); 28 | // 传递消息到其他工作进程 29 | static php::value send(php::parameters& params); 30 | // 生成 snowflake 兼容唯一标识 31 | static php::value unique_id(php::parameters& params); 32 | 33 | worker(std::uint8_t idx); 34 | std::ostream& output() override; 35 | protected: 36 | virtual std::shared_ptr sw_self() override { 37 | return std::static_pointer_cast(shared_from_this()); 38 | } 39 | virtual std::shared_ptr ipc_self() override { 40 | return std::static_pointer_cast(shared_from_this()); 41 | } 42 | virtual std::shared_ptr lm_self() override { 43 | return std::static_pointer_cast(shared_from_this()); 44 | } 45 | bool on_signal(int sig) override; 46 | bool on_message(std::shared_ptr msg) override; 47 | private: 48 | static std::shared_ptr ww_; 49 | std::list msgq_; // notify queue 50 | coroutine_handler msgc_; // notify coroutine 51 | 52 | void msg_start(); 53 | void msg_close(); 54 | }; 55 | } -------------------------------------------------------------------------------- /src/http/extension/extension.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // 3 | extern "C" { 4 | // PHP 扩展模块入口 5 | ZEND_DLEXPORT zend_module_entry* get_module() { 6 | // 构建模块:在离开函数后保留 module 定义 7 | static php::module_entry module("flame-http", "v0.18.0"); 8 | module 9 | - php::require("flame-core"); 10 | 11 | return module; 12 | } 13 | } -------------------------------------------------------------------------------- /src/ipc.cpp: -------------------------------------------------------------------------------- 1 | #include "ipc.h" 2 | 3 | static boost::pool<> pool_(ipc::MESSAGE_INIT_CAPACITY); 4 | 5 | static void free_message(ipc::message_t* msg) { 6 | pool_.free(msg); 7 | } 8 | 9 | std::shared_ptr ipc::create_message(std::uint16_t length) { 10 | if(length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) { 11 | ipc::message_t* msg = (ipc::message_t*)pool_.malloc(); 12 | msg->length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t); 13 | return std::shared_ptr(msg, free_message); 14 | } 15 | else { 16 | length = (sizeof(ipc::message_t) + length + 4096 - 1) & ~(4096-1); 17 | ipc::message_t* msg = (ipc::message_t*)malloc(length); 18 | msg->length = length - sizeof(ipc::message_t); 19 | return std::shared_ptr(msg, free); 20 | } 21 | } 22 | 23 | void ipc::relloc_message(std::shared_ptr& msg, std::uint16_t copy, std::uint16_t length) { 24 | length = (sizeof(ipc::message_t) + length + 4096 - 1) & ~(4096-1); 25 | 26 | ipc::message_t* m = (ipc::message_t*)malloc(length); 27 | std::memcpy(m, msg.get(), sizeof(ipc::message_t) + copy); 28 | msg.reset(m, free); 29 | } 30 | 31 | std::uint32_t ipc::create_uid() { 32 | static std::uint32_t start = 0; 33 | return ++start; 34 | } 35 | -------------------------------------------------------------------------------- /src/ipc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include "coroutine.h" 4 | 5 | class ipc { 6 | public: 7 | // 传输协议:消息格式 8 | struct message_t { 9 | std::uint8_t command; 10 | std::uint8_t source; 11 | std::uint8_t target; 12 | std::uint8_t xdata[3]; 13 | std::uint16_t length; 14 | std::uint32_t unique_id; 15 | char payload[0]; 16 | }; 17 | // 传输协议:命令 18 | enum command_t { 19 | MESSAGE_INIT_CAPACITY = 2048, 20 | 21 | COMMAND_REGISTER = 0x01, 22 | COMMAND_LOGGER_CONNECT = 0x02, 23 | COMMAND_LOGGER_DESTROY = 0x03, 24 | COMMAND_LOGGER_DATA = 0x04, 25 | 26 | COMMAND_TRANSFER_TO_CHILD = 0x10, 27 | COMMAND_MESSAGE_STRING = 0x10, 28 | COMMAND_MESSAGE_JSON = 0x11, 29 | }; 30 | struct callback_t { 31 | coroutine_handler& cch; 32 | std::shared_ptr& res; 33 | }; 34 | // 消息容器构建 35 | static std::shared_ptr create_message(std::uint16_t length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)); 36 | // 消息容器长度调整 37 | static void relloc_message(std::shared_ptr& msg, std::uint16_t copy, std::uint16_t length); 38 | // 生成消息ID 39 | static std::uint32_t create_uid(); 40 | // 请求并等待响应 41 | virtual std::shared_ptr ipc_request(std::shared_ptr data, coroutine_handler& ch) = 0; 42 | // 请求(无响应) 43 | virtual void ipc_request(std::shared_ptr data) = 0; 44 | }; 45 | -------------------------------------------------------------------------------- /src/master_ipc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include "coroutine.h" 4 | #include "ipc.h" 5 | 6 | class master_ipc { 7 | public: 8 | using socket_ptr = std::shared_ptr; 9 | master_ipc(boost::asio::io_context& io); 10 | virtual ~master_ipc(); 11 | // 输出 12 | virtual std::ostream& output() = 0; 13 | void ipc_start(); 14 | // 监听连接及连接数据接收 15 | void ipc_run(coroutine_handler ch); 16 | void ipc_read(socket_ptr sock, coroutine_handler ch); 17 | // void ipc_read(socket_ptr sock, coroutine_handler& ch); 18 | void ipc_close(); 19 | // 请求并等待响应 20 | std::shared_ptr ipc_request(std::shared_ptr req, coroutine_handler& ch); 21 | // 请求(无响应) 22 | void ipc_request(std::shared_ptr data); 23 | protected: 24 | virtual bool on_message(std::shared_ptr msg, socket_ptr sock); 25 | virtual std::shared_ptr ipc_self() = 0; 26 | private: 27 | boost::asio::io_context& io_; 28 | std::filesystem::path svrsck_; 29 | boost::asio::local::stream_protocol::acceptor server_; 30 | std::map socket_; 31 | std::map callback_; 32 | std::list> sendq_; 33 | // 连接数据接收 34 | 35 | void send(std::shared_ptr msg); 36 | void send_next(); 37 | }; 38 | -------------------------------------------------------------------------------- /src/master_logger.cpp: -------------------------------------------------------------------------------- 1 | #include "master_logger.h" 2 | #include "master_logger_buffer.h" 3 | #include "util.h" 4 | 5 | void master_logger::reload(boost::asio::io_context& io) { 6 | oss_.reset(&std::clog, boost::null_deleter()); 7 | 8 | if(path_.string() != "") { 9 | auto fb = new master_logger_buffer(io, path_); 10 | // fb->open(path_, std::ios_base::app); 11 | if(fb->is_open()) { 12 | ssb_.reset(fb); 13 | oss_.reset(new std::ostream(fb)); 14 | fb->persync(); // 启动周期性文件缓冲刷新服务 15 | } 16 | else { // 文件打开失败时不会抛出异常,需要额外的状态检查 17 | std::cerr << "[" << util::system_time() << "] (WARNING) Failed to access / create logger file, fallback to 'clog'" << std::endl; 18 | } 19 | } 20 | } 21 | 22 | void master_logger::close() { 23 | oss_.reset(&std::clog, boost::null_deleter()); 24 | // ssb_->close(); 25 | ssb_.reset(); 26 | } -------------------------------------------------------------------------------- /src/master_logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include 4 | 5 | class master_logger { 6 | private: 7 | std::uint8_t idx_; 8 | unsigned int ref_; 9 | std::shared_ptr oss_; 10 | std::unique_ptr ssb_; 11 | std::filesystem::path path_; 12 | public: 13 | master_logger(std::filesystem::path path, int index): idx_(index), ref_(1), path_(path) {} 14 | std::uint8_t index() { 15 | return idx_; 16 | } 17 | void reload(boost::asio::io_context& io); 18 | std::ostream& stream() { 19 | return *oss_; 20 | } 21 | void close(); 22 | friend class master_logger_manager; 23 | }; 24 | -------------------------------------------------------------------------------- /src/master_logger_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "master_logger_buffer.h" 2 | #include "master_logger_manager.h" 3 | 4 | master_logger_buffer::master_logger_buffer(boost::asio::io_context& io, std::filesystem::path path) 5 | : tms_(io) { 6 | open(path, std::ios_base::app); 7 | } 8 | 9 | int master_logger_buffer::overflow(int ch) { 10 | char c = ch; 11 | ch = std::filebuf::overflow(ch); 12 | 13 | if(c == '\n' && ++lns_ > 64) { // 积攒的行数超过 64 行刷新 14 | lns_ = 0; 15 | pubsync(); 16 | persync(); 17 | } 18 | return ch; 19 | } 20 | 21 | long master_logger_buffer::xsputn(const char* s, long c) { 22 | c = std::filebuf::xsputn(s, c); 23 | 24 | if(s[c-1] == '\n' && ++lns_ > 64) { // 积攒的行数超过 64 行刷新 25 | lns_ = 0; 26 | pubsync(); 27 | persync(); 28 | } 29 | return c; 30 | } 31 | 32 | 33 | void master_logger_buffer::persync() { 34 | tms_.cancel(); 35 | tms_.expires_after(std::chrono::milliseconds(2400)); 36 | tms_.async_wait([this] (const boost::system::error_code& error) { 37 | if(error) return; 38 | pubsync(); 39 | persync(); // 每 2400 毫秒对文件进行一次刷新 40 | }); 41 | } 42 | 43 | // void master_logger_buffer::close() { 44 | // tms_.cancel(); 45 | // } -------------------------------------------------------------------------------- /src/master_logger_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "ipc.h" 5 | 6 | class master_logger_manager; 7 | // 实现行数或周期为同步、刷写标志的缓冲区 8 | class master_logger_buffer: public std::filebuf { 9 | public: 10 | master_logger_buffer(boost::asio::io_context& io, std::filesystem::path path); 11 | void persync(); 12 | // void close(); 13 | protected: 14 | int overflow(int ch = EOF) override; 15 | long xsputn(const char* s, long c) override; 16 | private: 17 | master_logger_manager* mgr_; 18 | boost::asio::steady_timer tms_; 19 | unsigned int lns_ = 0; 20 | }; 21 | -------------------------------------------------------------------------------- /src/master_logger_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "master_logger_manager.h" 2 | 3 | master_logger_manager::master_logger_manager(boost::asio::io_context& io) 4 | : io_(io) { 5 | 6 | } 7 | 8 | master_logger_manager::~master_logger_manager() { 9 | // std::cout << "~master_logger_manager\n"; 10 | } 11 | 12 | master_logger* master_logger_manager::lm_connect(std::string_view filepath) { 13 | std::filesystem::path path = filepath; 14 | 15 | for(auto i=logger_.begin();i!=logger_.end();++i) { 16 | if(static_cast(i->second.get())->path_ == path) { 17 | ++i->second->ref_; 18 | return i->second.get(); 19 | } 20 | } 21 | auto p = logger_.insert({index_, std::make_unique(path, index_)}); 22 | ++index_; 23 | p.first->second->reload(io_); 24 | return p.first->second.get(); 25 | } 26 | 27 | void master_logger_manager::lm_destroy(std::uint8_t idx) { 28 | auto i = logger_.find(idx); 29 | if(i == logger_.end()) return; 30 | assert(i->second->ref_ > 0 && "引用计数异常"); 31 | if(--i->second->ref_ > 0) return; 32 | logger_.erase(i); 33 | } 34 | 35 | void master_logger_manager::lm_reload() { 36 | for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload(io_); 37 | } 38 | 39 | void master_logger_manager::lm_close() { 40 | for(auto i=logger_.begin();i!=logger_.end();++i) i->second->close(); 41 | } -------------------------------------------------------------------------------- /src/master_logger_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include "master_logger.h" 4 | 5 | class master_logger_manager { 6 | public: 7 | master_logger_manager(boost::asio::io_context& io); 8 | virtual ~master_logger_manager(); 9 | // 日志引用 10 | master_logger* lm_connect(std::string_view filepath); 11 | master_logger* lm_get(std::uint8_t idx); 12 | std::ostream& lm_get(std::uint8_t idx, bool output); 13 | // 引用清理 14 | void lm_destroy(std::uint8_t idx); 15 | // 日志重载 16 | void lm_reload(); 17 | void lm_close(); 18 | virtual std::shared_ptr lm_self() = 0; 19 | private: 20 | boost::asio::io_context& io_; 21 | std::uint8_t index_ = 0; 22 | std::map> logger_; 23 | }; 24 | 25 | inline master_logger* master_logger_manager::lm_get(std::uint8_t idx) { 26 | auto i = logger_.find(idx); 27 | return i == logger_.end() ? nullptr : i->second.get(); 28 | } 29 | 30 | inline std::ostream& master_logger_manager::lm_get(std::uint8_t idx, bool output) { 31 | auto i = logger_.find(idx); 32 | return i == logger_.end() ? std::clog : i->second.get()->stream(); 33 | } 34 | -------------------------------------------------------------------------------- /src/master_process.cpp: -------------------------------------------------------------------------------- 1 | #include "master_process.h" 2 | #include "master_process_manager.h" 3 | #include "master_logger.h" 4 | #include "util.h" 5 | 6 | extern "C" { 7 | PHPAPI extern char *php_ini_opened_path; 8 | } 9 | 10 | static std::string php_cmd() { 11 | std::ostringstream ss; 12 | ss << php::constant("PHP_BINARY"); 13 | ss << " -c " << php_ini_opened_path; 14 | php::array argv = php::server("argv"); 15 | for (auto i = argv.begin(); i != argv.end(); ++i) ss << " " << i->second; 16 | return ss.str(); 17 | } 18 | 19 | master_process::master_process(boost::asio::io_context& io, master_process_manager* mgr, std::uint8_t idx) 20 | : mgr_(mgr), idx_(idx) 21 | , sout_(io), eout_(io) { 22 | // 准备命令行及环境变量(完全重新启动一个新的 PHP, 通过环境变量标识其为工作进程) 23 | boost::process::environment env = boost::this_process::environment(); 24 | env["FLAME_CUR_WORKER"] = std::to_string(idx + 1); 25 | // 构造进程 26 | proc_ = boost::process::child(io, php_cmd(), env, 27 | boost::process::std_out > sout_, boost::process::std_err > eout_, 28 | // boost::process::std_out > boost::process::null, boost::process::std_err > boost::process::null, 29 | // boost::process::std_out > stdout, boost::process::std_err > stdout, 30 | // 结束回调 31 | boost::process::on_exit = [this, &io] (int exit_code, const std::error_code &error) { 32 | if (error.value() == static_cast(std::errc::no_child_process)) return; 33 | boost::asio::post(io, [exit_code, this] () { 34 | mgr_->on_child_close(this, exit_code == 0); 35 | }); 36 | }); 37 | redirect_output(sout_, sbuf_); 38 | redirect_output(eout_, ebuf_); 39 | 40 | boost::asio::post(io, [this] () { 41 | mgr_->on_child_start(this); 42 | }); 43 | } 44 | 45 | void master_process::redirect_output(boost::process::async_pipe& pipe, std::string& data) { 46 | 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) { 47 | if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof) 48 | ; // std::cout << "redirect_output stopped" << std::endl; // 忽略 49 | else if (error) { 50 | mgr_->output() << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n"; 51 | } 52 | else { 53 | mgr_->output() << /*"-----" << */std::string_view(data.data(), nread); 54 | data.erase(0, nread); 55 | redirect_output(pipe, data); 56 | } 57 | }); 58 | } 59 | 60 | void master_process::close(bool force) { 61 | if(force) proc_.terminate(); 62 | else ::kill(proc_.id(), SIGTERM); 63 | } 64 | 65 | void master_process::signal(int sig) { 66 | ::kill(proc_.id(), sig); 67 | } 68 | 69 | void master_process::await() { 70 | if(proc_.joinable()) proc_.join(); 71 | } -------------------------------------------------------------------------------- /src/master_process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include 4 | #include 5 | 6 | class master_process_manager; 7 | class master_process { 8 | public: 9 | master_process(boost::asio::io_context& io, master_process_manager* m, std::uint8_t idx); 10 | void close(bool force = false); 11 | void signal(int sig); 12 | void await(); 13 | private: 14 | master_process_manager* mgr_; 15 | std::uint8_t idx_; 16 | 17 | boost::process::child proc_; 18 | boost::process::async_pipe sout_; 19 | boost::process::async_pipe eout_; 20 | std::string sbuf_; 21 | std::string ebuf_; 22 | void redirect_output(boost::process::async_pipe& pipe, std::string& buffer); 23 | 24 | friend class master_process_manager; 25 | }; -------------------------------------------------------------------------------- /src/master_process_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include "coroutine.h" 4 | 5 | class master_process; 6 | class master_process_manager { 7 | public: 8 | master_process_manager(boost::asio::io_context& io, unsigned int count, unsigned int timeout_ms); 9 | virtual ~master_process_manager(); 10 | // 输出 11 | virtual std::ostream& output() = 0; 12 | protected: 13 | virtual std::shared_ptr pm_self() = 0; 14 | // 启动子进程 15 | void pm_start(boost::asio::io_context& io); // 此 io 非彼 io 16 | // 重启子进程(陆续) 17 | void pm_reset(coroutine_handler& ch); 18 | // 停止子进程 19 | void pm_close(coroutine_handler& ch, bool now = false); 20 | // 发送信号 21 | void pm_kills(int sig); 22 | // 等待子进程结束 23 | void pm_await(); 24 | // 进程数量 25 | std::uint8_t pm_count() { 26 | return cmax_; 27 | } 28 | protected: 29 | // 进程启动回调 30 | virtual void on_child_start(master_process* w); 31 | // 进程停止回调 32 | virtual void on_child_close(master_process* w, bool normal); 33 | private: 34 | std::unique_ptr work_; 35 | boost::asio::io_context& io_; 36 | unsigned int cmax_; 37 | unsigned int crun_; 38 | unsigned int tquit_; 39 | 40 | std::vector> child_; 41 | boost::asio::steady_timer timer_; 42 | coroutine_handler ch_close_; 43 | coroutine_handler ch_start_; 44 | int status_; 45 | 46 | enum { 47 | STATUS_CLOSING = 0x01, 48 | STATUS_RSETING = 0x02, 49 | STATUS_QUITING = 0x04, 50 | STATUS_ACLOSED = 0x08, 51 | STATUS_TIMEOUT = 0x10, 52 | }; 53 | 54 | friend class master_process; 55 | }; 56 | -------------------------------------------------------------------------------- /src/sentry/config.txt: -------------------------------------------------------------------------------- 1 | dsn 'https://6b95e5db5f564a77afcfccd21da7bd8c@o428090.ingest.sentry.io/5373067' 2 | 3 | 4 | export SENTRY_KEY="6b95e5db5f564a77afcfccd21da7bd8c" 5 | export SENTRY_PRI="e242f5def7b1426d8862e7dea9ec3d63" 6 | export SENTRY_PRJ="5373067" 7 | 8 | curl -v -XPOST https://${SENTRY_KEY}@o428090.ingest.sentry.io/api/${SENTRY_PRJ}/store/ \ 9 | -H 'Content-Type: application/json' \ 10 | -H "X-Sentry-Auth: Sentry sentry_version=7,sentry_timestamp=$(date +"%s"),sentry_client=flame-sentry,sentry_key=${SENTRY_KEY}" \ 11 | -d '{"event_id":"fc6d8c0c43fc4630ad850ee518f1b9d0","timestamp":1596183542,"platform":"php","level":"error","server_name":"hello-sentry","extra":{"php":"8.0alpha2"},"exception":{"type":"error_exception","value":"uncaught exception: unknown error at xxxxxx:222","stacktrace":{"frames":[{"filename":"abc.php","lineno":12,"function":"hello","context_line":"throw new Exception(\"unknown error\")"}]}}}' -------------------------------------------------------------------------------- /src/sentry/exception.txt: -------------------------------------------------------------------------------- 1 | { 2 | "event_id": "fc6d8c0c43fc4630ad850ee518f1b9d0", 3 | "timestamp": 1596183542, 4 | "platform": "php", 5 | "level": "error", 6 | "logger": "flame", 7 | "server_name": "hello-sentry", 8 | "extra":{"php":"8.0alpha2"}, 9 | "exception": { 10 | "type": "error_exception", 11 | "value": "uncaught exception: unknown error at xxxxxx:222", 12 | "stacktrace": { 13 | "frames": [{"filename":"abc.php", "lineno": 12, "function": "hello", "context_line": "throw new Exception(\"unknown error\")"}], 14 | } 15 | } 16 | } 17 | 18 | 19 | {"event_id":"fc6d8c0c43fc4630ad850ee518f1b9d0","timestamp":1596183542,"platform":"php","level":"error","logger":"flame","server_name":"hello-sentry","extra":{"php":"8.0alpha2"},"exception":{"type":"error_exception","value":"uncaught exception: unknown error at xxxxxx:222","stacktrace":{"frames":[{"filename":"abc.php","lineno":12,"function":"hello","context_line":"throw new Exception(\"unknown error\")"}]}}} -------------------------------------------------------------------------------- /src/signal_watcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | 4 | class logger; 5 | class signal_watcher { 6 | public: 7 | explicit signal_watcher(boost::asio::io_context& io) 8 | : ss_(new boost::asio::signal_set(io)) { 9 | ss_->add(SIGINT); 10 | ss_->add(SIGTERM); 11 | ss_->add(SIGUSR1); 12 | ss_->add(SIGUSR2); 13 | ss_->add(SIGQUIT); 14 | } 15 | virtual ~signal_watcher() { 16 | ss_.reset(); 17 | // std::cout << "~signal_watcher\n"; 18 | } 19 | void sw_watch() { 20 | ss_->async_wait([this, self = sw_self()] (const boost::system::error_code& error, int sig) { 21 | if (error) return; 22 | if (!on_signal(sig)) { 23 | sw_close(); 24 | return; 25 | } 26 | sw_watch(); 27 | }); 28 | } 29 | void sw_close() { 30 | ss_.reset(); 31 | } 32 | virtual std::shared_ptr sw_self() = 0; 33 | protected: 34 | virtual bool on_signal(int sig) = 0; 35 | private: 36 | std::unique_ptr ss_; 37 | }; -------------------------------------------------------------------------------- /src/worker_ipc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include "ipc.h" 4 | 5 | class worker_ipc { 6 | public: 7 | worker_ipc(boost::asio::io_context& io, std::uint8_t idx); 8 | virtual ~worker_ipc(); 9 | using socket_ptr = std::shared_ptr; 10 | 11 | virtual std::ostream& output() = 0; 12 | // 请求并等待响应 13 | std::shared_ptr ipc_request(std::shared_ptr req, coroutine_handler& ch); 14 | // 请求(无响应) 15 | void ipc_request(std::shared_ptr data); 16 | // 简化 notify 请求 17 | void ipc_notify(std::uint8_t target, php::value data); 18 | void ipc_start(); 19 | // 读取及消息监听 20 | void ipc_run(coroutine_handler ch); 21 | // 关闭通道 22 | void ipc_close(); 23 | // IPC 检测 24 | bool ipc_enabled(); 25 | protected: 26 | virtual std::shared_ptr ipc_self() = 0; 27 | virtual bool on_message(std::shared_ptr msg) = 0; 28 | private: 29 | boost::asio::io_context& io_; 30 | std::filesystem::path svrsck_; 31 | std::uint8_t idx_; 32 | socket_ptr socket_; 33 | std::map callback_; 34 | std::list> sendq_; 35 | int status_ = -1; 36 | 37 | void send(std::shared_ptr msg); 38 | void send_next(); 39 | }; 40 | -------------------------------------------------------------------------------- /src/worker_logger.cpp: -------------------------------------------------------------------------------- 1 | #include "worker_logger.h" 2 | #include "worker_logger_manager.h" 3 | #include "worker_logger_buffer.h" 4 | 5 | worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx) 6 | : idx_(0) 7 | , path_(path) 8 | , wlb_(new worker_logger_buffer(mgr, idx)) 9 | , oss_(new std::ostream(wlb_.get())) { 10 | 11 | } 12 | 13 | worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx, bool local) 14 | : idx_(0) 15 | , path_(path) { 16 | if(path.has_filename() && path.string() != "") { 17 | auto fb = new std::filebuf(); 18 | fb->open(path, std::ios_base::app); 19 | if(fb->is_open()) { // 有可能无法打开 20 | oss_.reset(new std::ostream(fb)); 21 | wlb_.reset(fb); 22 | } 23 | } 24 | } 25 | 26 | std::ostream& worker_logger::stream() { 27 | return oss_ ? (*oss_) : std::clog; 28 | } 29 | -------------------------------------------------------------------------------- /src/worker_logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vendor.h" 3 | #include 4 | 5 | class coroutine_handler; 6 | class worker_logger_manager; 7 | class worker_logger_buffer; 8 | class worker_logger { 9 | public: 10 | worker_logger(worker_logger_manager* mgr, const std::filesystem::path& file, std::uint8_t index); 11 | worker_logger(worker_logger_manager* mgr, const std::filesystem::path& file, std::uint8_t index, bool local); 12 | std::ostream& stream(); 13 | private: 14 | std::uint8_t idx_; 15 | std::filesystem::path path_; 16 | std::unique_ptr wlb_; 17 | std::unique_ptr oss_; 18 | friend class worker_logger_manager; 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /src/worker_logger_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "worker_logger_buffer.h" 2 | #include "worker_logger_manager.h" 3 | #include "worker_ipc.h" 4 | 5 | worker_logger_buffer::worker_logger_buffer(worker_logger_manager* mgr, std::uint8_t idx) 6 | : mgr_(mgr) 7 | , msg_(ipc::create_message()) 8 | , idx_(idx) { 9 | cap_ = msg_->length; 10 | msg_->xdata[0] = idx_; 11 | msg_->length = 0; 12 | } 13 | 14 | int worker_logger_buffer::overflow(int ch) { 15 | char c = ch; 16 | msg_->payload[msg_->length] = c; 17 | ++msg_->length; 18 | if(c == '\n') transfer_msg(); 19 | return ch; 20 | } 21 | 22 | long worker_logger_buffer::xsputn(const char* s, long c) { 23 | if(cap_ - msg_->length < c) { 24 | assert(cap_ < 2048 * 1024); 25 | ipc::relloc_message(msg_, msg_->length, msg_->length + c); 26 | } 27 | std::memcpy(msg_->payload + msg_->length, s, c); 28 | msg_->length += c; 29 | 30 | if(s[c-1] == '\n') transfer_msg(); 31 | return c; 32 | } 33 | 34 | void worker_logger_buffer::transfer_msg() { 35 | msg_->command = ipc::COMMAND_LOGGER_DATA; 36 | msg_->target = 0; 37 | mgr_->ipc_->ipc_request(msg_); 38 | 39 | msg_ = ipc::create_message(); 40 | cap_ = msg_->length; 41 | msg_->xdata[0] = idx_; // 实际写入的日志文件 42 | msg_->length = 0; // 以 length 累计当前需要发送的数据 43 | } -------------------------------------------------------------------------------- /src/worker_logger_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "ipc.h" 5 | 6 | class worker_logger_manager; 7 | class worker_logger_buffer: public std::streambuf { 8 | public: 9 | worker_logger_buffer(worker_logger_manager* mgr, std::uint8_t idx); 10 | protected: 11 | int overflow(int ch = EOF) override; 12 | long xsputn(const char* s, long c) override; 13 | private: 14 | worker_logger_manager* mgr_; 15 | std::shared_ptr msg_; 16 | std::uint16_t cap_; 17 | std::uint8_t idx_; 18 | 19 | void transfer_msg(); 20 | }; 21 | -------------------------------------------------------------------------------- /src/worker_logger_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "worker_logger_manager.h" 2 | #include "worker_logger.h" 3 | #include "worker_ipc.h" 4 | 5 | worker_logger_manager::~worker_logger_manager() { 6 | // std::cout << "~worker_logger_manager\n"; 7 | } 8 | 9 | std::shared_ptr worker_logger_manager::lm_connect(const std::string& file, coroutine_handler& ch) { 10 | std::filesystem::path path = file; 11 | path = path.lexically_normal(); 12 | 13 | for (auto i=logger_.begin();i!=logger_.end();) { 14 | if(i->second.expired()) i = logger_.erase(i); 15 | else { 16 | std::shared_ptr lg = i->second.lock(); 17 | if(lg->path_ == path) return lg; 18 | ++i; 19 | } 20 | } 21 | 22 | std::shared_ptr wl; 23 | std::uint8_t index = 0; 24 | if (ipc_->ipc_enabled()) { 25 | auto msg = ipc::create_message(); 26 | msg->command = ipc::COMMAND_LOGGER_CONNECT; 27 | msg->target = 0; // 发送给主进程的消息 28 | msg->unique_id = ipc::create_uid(); 29 | msg->length = file.size(); 30 | std::memcpy(msg->payload, file.data(), msg->length); 31 | msg = ipc_->ipc_request(msg, ch); 32 | index = msg->xdata[0]; 33 | // 使用该日志文件标号创建 LOGGER 对象,以支持实际日志发送 34 | wl = std::make_shared(this, file, index); 35 | } 36 | else { 37 | index = lindex_; 38 | wl = std::make_shared(this, file, index, true); 39 | ++lindex_; 40 | } 41 | 42 | logger_.emplace(index, wl); 43 | return wl; 44 | } 45 | 46 | void worker_logger_manager::lm_destroy(std::uint8_t idx) { 47 | auto i = logger_.find(idx); 48 | if(i == logger_.end()) return; 49 | logger_.erase(i); 50 | 51 | // IPC: 通知父进程日志器不再使用 52 | auto msg = ipc::create_message(); 53 | msg->command = ipc::COMMAND_LOGGER_DESTROY; 54 | msg->target = idx; 55 | msg->length = 0; 56 | ipc_->ipc_request(msg); 57 | } 58 | 59 | void worker_logger_manager::lm_close() { 60 | 61 | } -------------------------------------------------------------------------------- /src/worker_logger_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ipc.h" 3 | 4 | class worker_ipc; 5 | class worker_logger; 6 | class worker_logger_manager { 7 | public: 8 | worker_logger_manager(worker_ipc* c): ipc_(c) {} 9 | virtual ~worker_logger_manager(); 10 | // 连接到指定的日志文件 11 | std::shared_ptr lm_connect(const std::string& filepath, coroutine_handler& ch); 12 | void lm_destroy(std::uint8_t idx); 13 | void lm_close(); 14 | protected: 15 | virtual std::shared_ptr lm_self() = 0; 16 | private: 17 | std::map> logger_; 18 | std::uint8_t lindex_ = 0; 19 | worker_ipc* ipc_; // 目前的继承实现方式,此字段与 this 相等 20 | friend class worker_logger; 21 | friend class worker_logger_buffer; 22 | }; 23 | -------------------------------------------------------------------------------- /tests/core/clock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../../src/core/clock.h" 3 | 4 | BOOST_AUTO_TEST_SUITE(core_clock) 5 | 6 | BOOST_AUTO_TEST_CASE(standard_string_representation) { 7 | const core::clock& clock = core::clock::get_const_instance(); 8 | std::string utc = clock.utc(), iso = clock.iso(); 9 | // 形如:2021-01-18T13:32:10.368Z 10 | BOOST_TEST(utc[ 0] == '2'); 11 | BOOST_TEST(utc[ 1] == '0'); 12 | BOOST_TEST(utc[ 4] == '-'); 13 | BOOST_TEST(utc[10] == 'T'); 14 | BOOST_TEST(utc[13] == ':'); 15 | BOOST_TEST(utc[23] == 'Z'); 16 | // 形如:2021-01-18 13:32:10 17 | BOOST_TEST(iso[ 0] == '2'); 18 | BOOST_TEST(iso[ 1] == '0'); 19 | BOOST_TEST(iso[ 4] == '-'); 20 | BOOST_TEST(iso[10] == ' '); 21 | BOOST_TEST(iso[13] == ':'); 22 | BOOST_TEST(iso[16] == ':'); 23 | } 24 | 25 | BOOST_AUTO_TEST_SUITE_END() -------------------------------------------------------------------------------- /tests/core/core.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE core-test 2 | #include 3 | 4 | #include "../../src/core/basic_snowflake.h" 5 | 6 | BOOST_AUTO_TEST_SUITE(core) 7 | 8 | BOOST_AUTO_TEST_CASE(basic_snowflake_node_id_should_remain_unchanged) { 9 | BOOST_TEST(sizeof(basic_snowflake) == 16); 10 | basic_snowflake snow {111, 1000000000000ll}; 11 | int64_t id1 = snow.next_id(); 12 | int64_t id2 = snow.next_id(); 13 | BOOST_TEST((id1 & (0x3FFl << 42)) >> 42 == 111); // little endian 14 | } 15 | 16 | BOOST_AUTO_TEST_CASE(basic_snowflake_unique_id_should_be_monotone_increasing) { 17 | BOOST_TEST(sizeof(basic_snowflake) == 16); 18 | basic_snowflake snow {111, 1000000000000ll}; 19 | int64_t id1 = snow.next_id(), id2 = snow.next_id(), id3 = snow.next_id(); 20 | BOOST_TEST(id1 < id2); 21 | BOOST_TEST(id2 < id3); 22 | } 23 | 24 | BOOST_AUTO_TEST_SUITE_END() -------------------------------------------------------------------------------- /tests/core/run.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | start framework by `flame\run()`; 3 | --FILE-- 4 | "abc", 7 | ], function() { 8 | flame\go(function() { 9 | flame\time\sleep(1000); 10 | echo "2\n"; 11 | }); 12 | echo "1\n"; 13 | flame\time\sleep(2000); 14 | echo "3\n"; 15 | }); 16 | ?> 17 | --EXPECT-- 18 | 1 19 | 2 20 | 3 21 | --------------------------------------------------------------------------------