Files
qing 81ba942b8e feat(libmsg): extract pymsg-compatible messaging library as shared lib
- Add libmsg: C++20 header-only pymsg protocol implementation
  - Msg.hpp: pmsg_send/pmsg_recv (blocking + deadline modes) + MsgClient
  - MsgServer.hpp: multi-client TCP server with select event loop + thread pool
  - AsyncQueue<T>: generic async batch processing queue
  - RpcClient/RpcServer: JSON RPC convention layer (requires boost::json)
  - Msg_Basic.hpp/cpp: exception hierarchy, constants, version
- Fix thread safety: MsgClient _sockfd/_running use std::atomic with exchange()
- Fix deadline recv: client call() uses shared deadline for header+payload
- Fix graceful shutdown: thread pool drains queued tasks before exit
- Fix pmsg_send size check: enforce 64MiB limit (was 4GiB no-op check)
- Align AsyncQueue batch semantics with Python: batch_size is threshold
- Refactor gateway/LogC.hpp to use libmsg AsyncQueue<T>
- Update gateway Makefile to link -lus_msg, DbC.hpp to use <us/Msg.hpp>
- Add libmsg to _inst.sh build order
2026-08-06 23:14:13 +08:00

290 lines
9.6 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
/** AsyncQueue — 通用异步批量发送队列(C++ 版,对应 Python 版 pymsg/pymsg/async_queue.py)
*
* 提供内存队列 + 后台线程的异步批量处理机制,适用于日志上报、指标采集、事件通知等
* 非关键路径场景。业务线程 push() 非阻塞立即返回,后台线程攒批处理(批量阈值或超时),
* 失败时通过错误回调通知,绝不阻塞或影响主业务流程。
*
* 设计要点(与 Python AsyncRpcQueue 对齐):
* - 非阻塞入队:push() 立即返回,不等待网络 I/O
* - 批量发送:攒到 batch_size 条或超过 flush_interval 秒自动 flush
* - 背压保护:max_queue_size 限制内存队列长度,满时丢最旧数据并计数
* - 优雅关闭:shutdown() flush 剩余数据后停止后台线程
* - RAII:析构自动 shutdown()
* - 可插拔:通过 Handler 回调自定义批量处理逻辑(RPC 调用、写文件、发消息等)
*
* Usage:
* #include <us/AsyncQueue.hpp>
*
* struct LogEntry { std::string service; std::string msg; };
*
* pmc::net::AsyncQueue<LogEntry> queue(
* [](std::vector<LogEntry>&& batch) {
* for (auto& e : batch) { send_to_server(e); }
* },
* {50, 2.0, 10000, 0.5},
* [](const std::exception& e, std::vector<LogEntry>&&) {
* std::cerr << "[queue] send failed: " << e.what() << std::endl;
* }
* );
* queue.start();
* queue.push(LogEntry{"gateway", "started"});
* queue.shutdown();
*/
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <exception>
#include <functional>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
namespace pmc {
namespace net {
// ============================================================================
// AsyncQueue 配置
// ============================================================================
struct AsyncQueueConfig {
size_t batch_size = 100; ///< 批量发送阈值,达到此数量立即 flush
double flush_interval = 2.0; ///< 定时 flush 间隔(秒)
size_t max_queue_size = 10000; ///< 队列最大长度,0 表示无上限;超过时丢最旧数据
double poll_timeout = 0.1; ///< 后台线程轮询超时(秒),越小关闭响应越快但CPU占用略高
};
// ============================================================================
// AsyncQueue<T>
// ============================================================================
/** 通用异步批量发送队列。
*
* @tparam T 队列元素类型,需支持移动构造(push 时会被 std::move 到 batch 中)。
*/
template <typename T>
class AsyncQueue {
public:
/// 批量处理回调:接收一个元素批次的右值引用,处理完后 batch 被销毁。
using Handler = std::function<void(std::vector<T>&& batch)>;
/// 错误回调:接收异常和失败的批次(可用于重试或记录)。
using ErrorHandler = std::function<void(const std::exception& e, std::vector<T>&& batch)>;
// ---- 构造 / 析构 -------------------------------------------------------
explicit AsyncQueue(Handler handler,
AsyncQueueConfig config = {},
ErrorHandler on_error = {})
: _handler(std::move(handler)),
_config(config),
_on_error(std::move(on_error)),
_running(false),
_stop(false),
_wakeup(false),
_dropped(0) {}
/// 析构函数:自动 shutdown(),保证队列中剩余数据被 flush。
~AsyncQueue() { shutdown(); }
AsyncQueue(const AsyncQueue&) = delete;
AsyncQueue& operator=(const AsyncQueue&) = delete;
// ---- 生命周期 ----------------------------------------------------------
/// 启动后台发送线程。幂等:已启动时无操作。
void start() {
std::lock_guard<std::mutex> lock(_mtx);
if (_running) return;
_stop = false;
_wakeup = false;
_running = true;
_thread = std::thread(&AsyncQueue::_run, this);
}
/// 优雅关闭:flush 剩余数据后停止后台线程。幂等。
void shutdown() {
{
std::lock_guard<std::mutex> lock(_mtx);
if (!_running) return;
_stop = true;
_wakeup = true;
}
_cv.notify_all();
if (_thread.joinable()) {
_thread.join();
}
// 线程结束后,flush 队列中剩余的所有数据
_flush_all();
{
std::lock_guard<std::mutex> lock(_mtx);
_running = false;
}
}
// ---- 入队 --------------------------------------------------------------
/** 非阻塞入队一条数据。
*
* 若队列已关闭(shutdown 后),则丢弃数据并返回 false。
* 若队列长度达到 max_queue_size,则丢弃最旧的数据并递增 dropped 计数。
*
* @return true 入队成功;false 队列已关闭。
*/
bool push(T item) {
{
std::lock_guard<std::mutex> lock(_mtx);
if (_stop) return false;
if (_config.max_queue_size > 0 && _queue.size() >= _config.max_queue_size) {
_queue.pop_front();
++_dropped;
}
_queue.push_back(std::move(item));
_wakeup = true;
}
_cv.notify_one();
return true;
}
// ---- 手动 flush --------------------------------------------------------
/// 立即触发一次 flush,在调用线程同步处理当前队列中的所有数据。
/// 对齐 Python:flush() 直接在调用线程执行 _send_batch(),阻塞直到处理完成。
void flush() {
_send_batch();
}
// ---- 状态查询 ----------------------------------------------------------
/// 当前队列中待处理的数据量。
size_t pending() const {
std::lock_guard<std::mutex> lock(_mtx);
return _queue.size();
}
/// 因队列满而丢弃的数据总数。
size_t dropped() const {
return _dropped.load();
}
/// 队列是否正在运行(已 start 且未 shutdown)。
bool is_running() const {
std::lock_guard<std::mutex> lock(_mtx);
return _running;
}
private:
// ---- 后台线程主循环(对齐 Python AsyncRpcQueue._run)--------------------
void _run() {
auto last_flush = std::chrono::steady_clock::now();
while (true) {
bool should_stop = false;
{
std::unique_lock<std::mutex> lock(_mtx);
_cv.wait_for(lock,
std::chrono::duration<double>(_config.poll_timeout),
[this] { return _stop || _wakeup; });
_wakeup = false; // 消费唤醒标志
should_stop = _stop;
}
const auto now = std::chrono::steady_clock::now();
const bool due = std::chrono::duration<double>(now - last_flush).count()
>= _config.flush_interval;
size_t qsize = 0;
{
std::lock_guard<std::mutex> lock(_mtx);
qsize = _queue.size();
}
// flush 条件:达到批量阈值 / 超时且有数据 / 停止时还有数据
if (qsize >= _config.batch_size || (due && qsize > 0) || (should_stop && qsize > 0)) {
_send_batch();
last_flush = std::chrono::steady_clock::now();
}
if (should_stop) break;
}
}
// ---- 取所有待处理数据并发送(对齐 Python:batch_size 是触发阈值,不是上限)-
void _send_batch() {
std::vector<T> batch;
{
std::lock_guard<std::mutex> lock(_mtx);
if (_queue.empty()) return;
batch.reserve(_queue.size());
while (!_queue.empty()) {
batch.push_back(std::move(_queue.front()));
_queue.pop_front();
}
}
try {
_handler(std::move(batch));
} catch (const std::exception& e) {
if (_on_error) {
try {
_on_error(e, std::move(batch));
} catch (...) {
// 错误回调本身不应传播异常
}
}
}
}
// ---- 关闭时 flush 所有剩余数据 ----------------------------------------
void _flush_all() {
while (true) {
std::vector<T> batch;
{
std::lock_guard<std::mutex> lock(_mtx);
if (_queue.empty()) break;
batch.reserve(_queue.size());
while (!_queue.empty()) {
batch.push_back(std::move(_queue.front()));
_queue.pop_front();
}
}
try {
_handler(std::move(batch));
} catch (const std::exception& e) {
if (_on_error) {
try {
_on_error(e, std::move(batch));
} catch (...) {}
}
}
}
}
// ---- 成员 --------------------------------------------------------------
Handler _handler;
AsyncQueueConfig _config;
ErrorHandler _on_error;
mutable std::mutex _mtx;
std::condition_variable _cv;
std::deque<T> _queue;
std::thread _thread;
bool _running;
bool _stop;
bool _wakeup; ///< 唤醒标志(push/shutdown 置 true,后台线程消费后置 false)
std::atomic<size_t> _dropped;
};
} // namespace net
} // namespace pmc