2026-07-03 14:19:06 +08:00

477 lines
17 KiB
C++
Raw 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.

#include <QCoreApplication>
#include <QTimer>
#include <QDir>
#include <QFileInfo>
#include <QFile>
#include <QJsonObject>
#include <iostream>
#include <chrono>
#if defined(__linux__)
#include <signal.h>
#include <execinfo.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <dlfcn.h>
#include <pthread.h>
#include <exception>
#include <typeinfo>
#include <cxxabi.h>
#include <thread>
#include <sstream>
#include <cstring>
#endif
#include "DroneScrewServerPresenter.h"
#include "DroneScrewZmqProtocol.h"
#include "Version.h"
#include "VrLog.h"
#include "VrError.h"
#if defined(__linux__)
// ========== exit() 拦截 ==========
// libff_media.so 内部线程在错误条件下会直接调用 exit() 导致整个进程崩溃。
// 我们拦截 exit() 调用:主线程的 exit 正常放行,子线程的 exit 改为只结束该线程。
namespace
{
// 真正的 exit 函数指针,启动时初始化
typedef void (*real_exit_t)(int);
real_exit_t g_real_exit = nullptr;
bool g_exitHookInstalled = false;
} // namespace
extern "C" void exit(int status)
{
// 递归保护:如果 exit hook 内部再次调用 exit直接走 _exit
static thread_local bool g_inExitHook = false;
if (g_inExitHook)
_exit(status);
g_inExitHook = true;
// 首次调用时获取真正的 exit 地址
if (!g_real_exit)
{
g_real_exit = reinterpret_cast<real_exit_t>(dlsym(RTLD_NEXT, "exit"));
if (!g_real_exit)
_exit(status); // 极端情况:拿不到真 exit直接用 _exit
}
// 判断是否为主线程(主线程的 tid == pid
const pid_t tid = static_cast<pid_t>(syscall(SYS_gettid));
const pid_t pid = getpid();
// 获取线程 ID 字符串
std::ostringstream oss;
oss << std::this_thread::get_id();
if (tid == pid)
{
// 主线程调用 exit() — 正常退出流程
LOG_ERROR("========================================\n");
LOG_ERROR("[EXIT-HOOK] exit(%d) called from MAIN thread tid=%d, passing through to real exit\n",
status, static_cast<int>(tid));
LOG_ERROR("========================================\n");
g_real_exit(status);
}
else
{
// 子线程调用 exit() — 记录 backtrace 并仅结束该线程
LOG_ERROR("========================================\n");
LOG_ERROR("[EXIT-HOOK] exit(%d) called from NON-MAIN thread! tid=%d c++id=%s\n",
status, static_cast<int>(tid), oss.str().c_str());
LOG_ERROR("[EXIT-HOOK] Preventing process exit — terminating this thread only.\n");
// 打印调用栈
void* array[30];
const int size = backtrace(array, 30);
LOG_ERROR("[EXIT-HOOK-BACKTRACE] %d frames:\n", size);
char** messages = backtrace_symbols(array, size);
if (messages)
{
for (int i = 0; i < size; i++)
LOG_ERROR(" [%d] %s\n", i, messages[i]);
free(messages);
}
LOG_ERROR("========================================\n");
// 仅退出当前线程,进程继续运行
pthread_exit(nullptr);
}
}
namespace
{
QString procStatusValue(const char* key)
{
#if defined(__linux__)
QFile f(QStringLiteral("/proc/self/status"));
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return QString();
const QByteArray prefix = QByteArray(key) + ':';
while (!f.atEnd())
{
const QByteArray line = f.readLine().trimmed();
if (line.startsWith(prefix))
return QString::fromLatin1(line.mid(prefix.size()).trimmed());
}
#else
Q_UNUSED(key);
#endif
return QString();
}
#if defined(__linux__)
std::string getThreadIdStr()
{
std::ostringstream oss;
oss << std::this_thread::get_id();
return oss.str();
}
void terminateHandler()
{
LOG_ERROR("========================================\n");
LOG_ERROR("[TERMINATE] std::terminate() called! thread=%s\n", getThreadIdStr().c_str());
LOG_ERROR("========================================\n");
// 尝试获取未捕获的异常信息
auto eptr = std::current_exception();
if (eptr)
{
try
{
std::rethrow_exception(eptr);
}
catch (const std::exception& e)
{
int status = 0;
char* demangled = abi::__cxa_demangle(typeid(e).name(), nullptr, nullptr, &status);
LOG_ERROR("[TERMINATE] uncaught exception type: %s\n",
(status == 0 && demangled) ? demangled : typeid(e).name());
LOG_ERROR("[TERMINATE] what(): %s\n", e.what());
if (demangled) free(demangled);
}
catch (...)
{
LOG_ERROR("[TERMINATE] uncaught exception (unknown type, not std::exception)\n");
}
}
else
{
LOG_ERROR("[TERMINATE] no active exception (terminate called without exception)\n");
}
// 打印调用栈
void* array[30];
const int size = backtrace(array, 30);
LOG_ERROR("[TERMINATE-BACKTRACE] Stack trace (%d frames):\n", size);
char** messages = backtrace_symbols(array, size);
if (messages)
{
for (int i = 0; i < size; i++)
LOG_ERROR(" [%d] %s\n", i, messages[i]);
free(messages);
}
else
{
LOG_ERROR(" (backtrace_symbols failed)\n");
}
LOG_ERROR("========================================\n");
LOG_ERROR("[TERMINATE] aborting...\n");
LOG_ERROR("========================================\n");
// 刷新日志后调用默认 abort
std::abort();
}
void atexitHandler()
{
LOG_ERROR("========================================\n");
LOG_ERROR("[ATEXIT] Program is exiting normally!\n");
LOG_ERROR("========================================\n");
// 打印调用栈看是谁调用了 exit
void* array[30];
const int size = backtrace(array, 30);
LOG_ERROR("[ATEXIT-BACKTRACE] Exit call stack (%d frames):\n", size);
char** messages = backtrace_symbols(array, size);
if (messages)
{
for (int i = 0; i < size; i++)
LOG_ERROR(" [%d] %s\n", i, messages[i]);
free(messages);
}
else
{
LOG_ERROR(" (backtrace_symbols failed)\n");
}
LOG_ERROR("========================================\n");
}
void signalHandler(int sig)
{
const char* sigName = "UNKNOWN";
switch (sig)
{
case SIGSEGV: sigName = "SIGSEGV (Segmentation Fault)"; break;
case SIGABRT: sigName = "SIGABRT (Abort)"; break;
case SIGTERM: sigName = "SIGTERM (Termination)"; break;
case SIGINT: sigName = "SIGINT (Interrupt)"; break;
case SIGILL: sigName = "SIGILL (Illegal Instruction)"; break;
case SIGFPE: sigName = "SIGFPE (Floating Point Exception)"; break;
case SIGBUS: sigName = "SIGBUS (Bus Error)"; break;
case SIGPIPE: sigName = "SIGPIPE (Broken Pipe)"; break;
default: break;
}
LOG_ERROR("========================================\n");
LOG_ERROR("[SIGNAL] Caught signal %d: %s\n", sig, sigName);
LOG_ERROR("========================================\n");
// 打印调用栈
void* array[30];
const int size = backtrace(array, 30);
LOG_ERROR("[BACKTRACE] Stack trace (%d frames):\n", size);
char** messages = backtrace_symbols(array, size);
if (messages)
{
for (int i = 0; i < size; i++)
LOG_ERROR(" [%d] %s\n", i, messages[i]);
free(messages);
}
else
{
LOG_ERROR(" (backtrace_symbols failed)\n");
}
LOG_ERROR("========================================\n");
// 对于致命信号,调用默认处理器
signal(sig, SIG_DFL);
raise(sig);
}
#endif
}
int main(int argc, char* argv[])
{
QCoreApplication app(argc, argv);
app.setApplicationName(DRONESCREWSERVER_PRODUCT_NAME);
app.setApplicationVersion(DRONESCREWSERVER_VERSION_STRING);
app.setOrganizationName(DRONESCREWSERVER_COMPANY_NAME);
VrLogUtils::InitLog();
VrLogUtils::EnableTime(false);
#if defined(__linux__)
// 注册 std::terminate 处理器(捕获未处理 C++ 异常,优先级最高)
std::set_terminate(terminateHandler);
LOG_INFO("[TERMINATE] std::terminate handler registered, main thread=%s\n",
getThreadIdStr().c_str());
// 注册 atexit 处理器(捕获正常退出)
atexit(atexitHandler);
LOG_INFO("[ATEXIT] Exit handler registered\n");
// 注册信号处理器
signal(SIGSEGV, signalHandler); // 段错误
signal(SIGABRT, signalHandler); // 异常终止
signal(SIGTERM, signalHandler); // 终止信号
signal(SIGINT, signalHandler); // 中断信号 (Ctrl+C)
signal(SIGILL, signalHandler); // 非法指令
signal(SIGFPE, signalHandler); // 浮点异常
signal(SIGBUS, signalHandler); // 总线错误
signal(SIGPIPE, signalHandler); // 管道破裂
LOG_INFO("[SIGNAL] Signal handlers registered\n");
// 屏蔽 ffmedia 库的 WARN 日志(仅在 ERROR 时打印)
extern unsigned int ff_log_level;
ff_log_level = 0; // 0=ERROR only, 1=WARN
// exit() 拦截已在链接时生效(本可执行文件定义的 exit 符号优先级高于 libc
// 子线程调用 exit() 时仅结束该线程,不会杀死整个进程
LOG_INFO("[EXIT-HOOK] exit() interceptor active: main-thread exit passes through, child-thread exit converts to pthread_exit\n");
#endif
LOG_DEBUG("===========================================\n");
LOG_INFO(" %s\n", DRONESCREWSERVER_PRODUCT_NAME);
LOG_INFO(" V%s_%s built %s %s\n", DRONESCREWSERVER_VERSION_STRING,
DRONESCREWSERVER_VERSION_BUILD, __DATE__, __TIME__);
LOG_DEBUG("===========================================\n");
qRegisterMetaType<DroneScrewResult>("DroneScrewResult");
qRegisterMetaType<DroneScrewAlgoParams>("DroneScrewAlgoParams");
qRegisterMetaType<MvsImageData>("MvsImageData");
DroneScrewServerPresenter presenter;
DroneScrewZmqProtocol zmq;
// 信号槽:协议 → presenter
QObject::connect(&zmq, &DroneScrewZmqProtocol::rawImagePublishEnabledRequested,
&presenter, &DroneScrewServerPresenter::setRawPubEnabled,
Qt::DirectConnection);
QObject::connect(&zmq, &DroneScrewZmqProtocol::detectModeRequested,
&presenter, &DroneScrewServerPresenter::setDetectMode,
Qt::DirectConnection);
QObject::connect(&zmq, &DroneScrewZmqProtocol::setExposureRequested,
&presenter, &DroneScrewServerPresenter::handleSetExposure);
QObject::connect(&zmq, &DroneScrewZmqProtocol::setGainRequested,
&presenter, &DroneScrewServerPresenter::handleSetGain);
QObject::connect(&zmq, &DroneScrewZmqProtocol::updateAlgoParamsRequested,
&presenter, &DroneScrewServerPresenter::handleUpdateAlgoParams);
// presenter → 协议(发布检测结果)
QObject::connect(&presenter, &DroneScrewServerPresenter::detectionResult,
&zmq, &DroneScrewZmqProtocol::publishDetectionResult);
// presenter → 协议(原始图像 PUB— 同步连接,回调内立即拷贝完成
QObject::connect(&presenter, &DroneScrewServerPresenter::rawImageReady,
&zmq, &DroneScrewZmqProtocol::publishRawImage,
Qt::DirectConnection);
zmq.setSingleDetectionHandler([&presenter]() {
return presenter.runSingleDetection();
});
zmq.setStartWorkHandler([&presenter]() {
return presenter.startDetectionWork();
});
zmq.setStopWorkHandler([&presenter]() {
return presenter.stopDetectionWork();
});
zmq.setFinalDetectionResultProvider([&presenter](DroneScrewResult& result) {
return presenter.getLastFinalDetectionResult(result);
});
zmq.setStartLiveStreamHandler([&presenter]() {
return presenter.startLiveStream();
});
zmq.setStopLiveStreamHandler([&presenter]() {
return presenter.stopLiveStream();
});
zmq.setSwapCameraRolesHandler([&presenter]() {
return presenter.swapCameraRoles();
});
zmq.setServerInfoProvider([&presenter]() {
return presenter.getRuntimeInfo();
});
zmq.setCalibrationInfoProvider([&presenter]() {
return presenter.getCalibrationInfo();
});
// 配置文件 —— 运行时配置优先runtime_config.xml 不会被部署覆盖),
// config/config.xml 仅作为出厂默认值。
const QString appDir = QCoreApplication::applicationDirPath();
const QString runtimeCfgPath = QDir(appDir).filePath("runtime_config.xml");
auto findDefaultConfig = [&]() -> QString {
const QString a = QDir(appDir).filePath("config/config.xml");
if (QFileInfo::exists(a)) return a;
const QString b = QDir::current().filePath("config/config.xml");
if (QFileInfo::exists(b)) return b;
return QString();
};
// 之后所有 saveConfiguration 调用都写到 runtime_config.xml
presenter.setConfigFilePath(runtimeCfgPath);
if (QFileInfo::exists(runtimeCfgPath))
{
// 正常路径:加载持久化的运行时配置
LOG_INFO("[CONFIG] loading runtime config: %s\n", runtimeCfgPath.toStdString().c_str());
if (!presenter.loadConfiguration(runtimeCfgPath))
{
LOG_WARN("[CONFIG] runtime config load failed, falling back to defaults\n");
const QString fallback = findDefaultConfig();
if (!fallback.isEmpty())
presenter.loadConfiguration(fallback);
// loadConfiguration 会覆盖 m_configFilePath需要重新指回 runtime 路径
presenter.setConfigFilePath(runtimeCfgPath);
}
}
else
{
// 首次部署:加载出厂默认配置,然后直接复制到 runtime_config.xml
const QString defaultCfg = findDefaultConfig();
if (!defaultCfg.isEmpty())
{
LOG_INFO("[CONFIG] first run, loading default: %s\n", defaultCfg.toStdString().c_str());
presenter.loadConfiguration(defaultCfg);
// loadConfiguration 会覆盖 m_configFilePath需要重新指回 runtime 路径
presenter.setConfigFilePath(runtimeCfgPath);
// 直接复制默认配置文件到运行时位置,保留所有元素
if (QFile::copy(defaultCfg, runtimeCfgPath))
LOG_INFO("[CONFIG] default config copied to: %s\n", runtimeCfgPath.toStdString().c_str());
else
LOG_WARN("[CONFIG] failed to copy default config to: %s\n", runtimeCfgPath.toStdString().c_str());
}
else
{
LOG_INFO("[CONFIG] no config file found, using defaults\n");
}
}
// 初始化各子系统
presenter.initAll();
// 启动 ZMQ
zmq.setRtspUrl(presenter.getRtspUrl());
if (!zmq.startServer(presenter.getZmqControlPort(),
presenter.getZmqResultPort(),
presenter.getZmqRawImagePort()))
{
LOG_ERROR("ZMQ start fail\n");
return ERR_CODE(NET_ERR_CREAT_INIT);
}
// raw 图像发布由 start.mode 控制pull_stream 关闭subscribe 开启。
presenter.setRawPubEnabled(false);
LOG_INFO("=== DroneScrewServer started, ZMQ ctrl=%d result=%d raw=%d, RTSP=%s ===\n",
presenter.getZmqControlPort(),
presenter.getZmqResultPort(),
presenter.getZmqRawImagePort(),
presenter.getRtspUrl().toStdString().c_str());
const auto startedAt = std::chrono::steady_clock::now();
QTimer heartbeatTimer;
heartbeatTimer.setInterval(60000);
QObject::connect(&heartbeatTimer, &QTimer::timeout, [&presenter, startedAt]() {
const auto now = std::chrono::steady_clock::now();
const auto uptimeSec =
std::chrono::duration_cast<std::chrono::seconds>(now - startedAt).count();
const QJsonObject info = presenter.getRuntimeInfo();
const QString mode = info["mode"].toString();
const QString rss = procStatusValue("VmRSS");
const QString vmPeak = procStatusValue("VmPeak");
const QString vmSize = procStatusValue("VmSize");
const QString threads = procStatusValue("Threads");
LOG_INFO("[HEARTBEAT] uptime=%llds mode=%s rss=%s vmPeak=%s vmSize=%s threads=%s\n",
static_cast<long long>(uptimeSec),
mode.toStdString().c_str(),
rss.isEmpty() ? "N/A" : rss.toStdString().c_str(),
vmPeak.isEmpty() ? "N/A" : vmPeak.toStdString().c_str(),
vmSize.isEmpty() ? "N/A" : vmSize.toStdString().c_str(),
threads.isEmpty() ? "N/A" : threads.toStdString().c_str());
});
heartbeatTimer.start();
return app.exec();
}
#else
int main()
{
return -1;
}
#endif