From 28d2a029da4486fdaa50a31d3121d38b4a778336 Mon Sep 17 00:00:00 2001 From: arm64v8a <48624112+arm64v8a@users.noreply.github.com> Date: Wed, 17 May 2023 10:28:18 +0900 Subject: [PATCH] optmize start stop --- main/NekoRay.cpp | 1 + main/NekoRay_ConfigItem.hpp | 1 + main/NekoRay_DataStore.hpp | 2 +- rpc/gRPC.cpp | 4 +- sys/ExternalProcess.cpp | 4 +- translations/fa_IR.ts | 20 ++++ translations/zh_CN.ts | 20 ++++ ui/mainwindow.cpp | 49 +++++++--- ui/mainwindow.h | 8 +- ui/mainwindow_grpc.cpp | 187 +++++++++++++++++++++++++----------- ui/widget/MessageBoxTimer.h | 33 +++++++ 11 files changed, 254 insertions(+), 75 deletions(-) create mode 100644 ui/widget/MessageBoxTimer.h diff --git a/main/NekoRay.cpp b/main/NekoRay.cpp index df9a00b..9ddf01b 100644 --- a/main/NekoRay.cpp +++ b/main/NekoRay.cpp @@ -359,6 +359,7 @@ namespace NekoRay { bool JsonStore::Save() { if (callback_before_save != nullptr) callback_before_save(); + if (save_control_no_save) return false; auto save_content = ToJsonBytes(); auto changed = last_save_content != save_content; diff --git a/main/NekoRay_ConfigItem.hpp b/main/NekoRay_ConfigItem.hpp index 354e1d9..1691783 100644 --- a/main/NekoRay_ConfigItem.hpp +++ b/main/NekoRay_ConfigItem.hpp @@ -36,6 +36,7 @@ namespace NekoRay { QString fn; bool load_control_must = false; // must load from file bool save_control_compact = false; + bool save_control_no_save = false; QByteArray last_save_content; JsonStore() = default; diff --git a/main/NekoRay_DataStore.hpp b/main/NekoRay_DataStore.hpp index 101b885..002c27c 100644 --- a/main/NekoRay_DataStore.hpp +++ b/main/NekoRay_DataStore.hpp @@ -67,7 +67,7 @@ namespace NekoRay { int core_port = 19810; int started_id = -1919; bool core_running = false; - bool core_prepare_exit = false; + bool prepare_exit = false; bool spmode_vpn = false; bool spmode_system_proxy = false; bool need_keep_vpn_off = false; diff --git a/rpc/gRPC.cpp b/rpc/gRPC.cpp index 9fbcfc9..4131d37 100644 --- a/rpc/gRPC.cpp +++ b/rpc/gRPC.cpp @@ -220,7 +220,7 @@ namespace NekoRay::rpc { QString Client::Start(bool *rpcOK, const libcore::LoadConfigReq &request) { libcore::ErrorResp reply; - auto status = default_grpc_channel->Call("Start", request, &reply, 3000); + auto status = default_grpc_channel->Call("Start", request, &reply); if (status == QNetworkReply::NoError) { *rpcOK = true; @@ -234,7 +234,7 @@ namespace NekoRay::rpc { QString Client::Stop(bool *rpcOK) { libcore::EmptyReq request; libcore::ErrorResp reply; - auto status = default_grpc_channel->Call("Stop", request, &reply, 3000); + auto status = default_grpc_channel->Call("Stop", request, &reply); if (status == QNetworkReply::NoError) { *rpcOK = true; diff --git a/sys/ExternalProcess.cpp b/sys/ExternalProcess.cpp index f33eed4..de5cea2 100644 --- a/sys/ExternalProcess.cpp +++ b/sys/ExternalProcess.cpp @@ -97,12 +97,12 @@ namespace NekoRay::sys { connect(this, &QProcess::stateChanged, this, [&](QProcess::ProcessState state) { NekoRay::dataStore->core_running = state == QProcess::Running; - if (!dataStore->core_prepare_exit && state == QProcess::NotRunning) { + if (!dataStore->prepare_exit && state == QProcess::NotRunning) { if (failed_to_start) return; // no retry restart_id = NekoRay::dataStore->started_id; MW_dialog_message("ExternalProcess", "Crashed"); - MW_show_log("[Error] core exited, restarting.\n"); + MW_show_log("[Error] " + QObject::tr("Core exited, restarting.")); // Restart setTimeout( diff --git a/translations/fa_IR.ts b/translations/fa_IR.ts index 3059713..728246f 100644 --- a/translations/fa_IR.ts +++ b/translations/fa_IR.ts @@ -1454,6 +1454,22 @@ End: %2 Restart Proxy + + If there is no response for a long time, it is recommended to restart the software. + + + + Failed to start profile %1 + + + + Failed to stop, please restart the program. + + + + Core exits too frequently, stop automatic restart this profile. + + ProxyItem @@ -1586,6 +1602,10 @@ Direct: %2 Subscription request fininshed: %1 + + Core exited, restarting. + + Qv2ray::ui::widgets::AutoCompleteTextEdit diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index 12d7bb9..f3a1a8c 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -1461,6 +1461,22 @@ Split by line. Restart Proxy 重启代理 + + Failed to start profile %1 + 启动配置失败: %1 + + + Failed to stop, please restart the program. + 停止失败,请重启程序。 + + + If there is no response for a long time, it is recommended to restart the software. + 如果长时间没有反应,建议重启软件。 + + + Core exits too frequently, stop automatic restart this profile. + Core 退出太频繁,停止自动重启。 + ProxyItem @@ -1592,6 +1608,10 @@ Release note: Subscription request fininshed: %1 订阅请求完成: %1 + + Core exited, restarting. + Core 退出,正在重新启动。 + Qv2ray::ui::widgets::AutoCompleteTextEdit diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 22d6458..99e3458 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -46,6 +46,8 @@ #include #include +QElapsedTimer coreRestartTimer; + void UI_InitMainWindow() { mainwindow = new MainWindow; } @@ -564,6 +566,16 @@ void MainWindow::dialog_message_impl(const QString &sender, const QString &info) if (info == "Crashed") { neko_stop(); } else if (info.startsWith("CoreRestarted")) { + if (coreRestartTimer.isValid()) { + auto elasped = coreRestartTimer.restart(); + if (elasped < 10 * 1000) { + coreRestartTimer = QElapsedTimer(); + show_log_impl("[Error] " + tr("Core exits too frequently, stop automatic restart this profile.")); + return; + } + } else { + coreRestartTimer.start(); + } neko_start(info.split(",")[1].toInt()); } } @@ -615,7 +627,6 @@ void MainWindow::on_commitDataRequest() { NekoRay::dataStore->splitter_state = ui->splitter->saveState().toBase64(); // auto last_id = NekoRay::dataStore->started_id; - neko_stop(); if (NekoRay::dataStore->remember_enable && last_id >= 0) { NekoRay::dataStore->remember_id = last_id; } @@ -625,16 +636,32 @@ void MainWindow::on_commitDataRequest() { } void MainWindow::on_menu_exit_triggered() { - neko_set_spmode_system_proxy(false, false); - neko_set_spmode_vpn(false, false); - if (NekoRay::dataStore->spmode_vpn) return; - RegisterHotkey(true); - // - on_commitDataRequest(); - // - NekoRay::dataStore->core_prepare_exit = true; - hide(); - stop_core_daemon(); + if (mu_exit.tryLock()) { + NekoRay::dataStore->prepare_exit = true; + // + neko_set_spmode_system_proxy(false, false); + neko_set_spmode_vpn(false, false); + if (NekoRay::dataStore->spmode_vpn) { + mu_exit.unlock(); // retry + return; + } + RegisterHotkey(true); + // + on_commitDataRequest(); + // + NekoRay::dataStore->save_control_no_save = true; // don't change datastore after this line + neko_stop(false, true); + // + hide(); + runOnNewThread([=] { + sem_stopped.acquire(); + stop_core_daemon(); + runOnUiThread([=] { + on_menu_exit_triggered(); // continue exit progress + }); + }); + return; + } // MF_release_runguard(); if (exit_reason == 1) { diff --git a/ui/mainwindow.h b/ui/mainwindow.h index 3b7f557..d98d1ae 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include "GroupSort.hpp" @@ -49,7 +51,7 @@ public: void neko_start(int _id = -1); - void neko_stop(bool crash = false); + void neko_stop(bool crash = false, bool sem = false); void neko_set_spmode_system_proxy(bool enable, bool save = true); @@ -153,6 +155,10 @@ private: // int proxy_last_order = -1; bool select_mode = false; + QMutex mu_starting; + QMutex mu_stopping; + QMutex mu_exit; + QSemaphore sem_stopped; int exit_reason = 0; QMap> get_now_selected(); diff --git a/ui/mainwindow_grpc.cpp b/ui/mainwindow_grpc.cpp index 3e94909..d7815a4 100644 --- a/ui/mainwindow_grpc.cpp +++ b/ui/mainwindow_grpc.cpp @@ -5,6 +5,7 @@ #include "db/ConfigBuilder.hpp" #include "db/TrafficLooper.hpp" #include "rpc/gRPC.h" +#include "ui/widget/MessageBoxTimer.h" #include #include @@ -194,6 +195,8 @@ void MainWindow::stop_core_daemon() { } void MainWindow::neko_start(int _id) { + if (NekoRay::dataStore->prepare_exit) return; + auto ents = get_now_selected(); auto ent = (_id < 0 && !ents.isEmpty()) ? ents.first() : NekoRay::profileManager->GetProfile(_id); if (ent == nullptr) return; @@ -214,79 +217,147 @@ void MainWindow::neko_start(int _id) { return; } - if (NekoRay::dataStore->started_id >= 0) neko_stop(); - show_log_impl(">>>>>>>> " + tr("Starting profile %1").arg(ent->bean->DisplayTypeAndName())); - + auto neko_start_stage2 = [=] { #ifndef NKR_NO_GRPC - libcore::LoadConfigReq req; - req.set_core_config(QJsonObject2QString(result->coreConfig, true).toStdString()); - req.set_enable_nekoray_connections(NekoRay::dataStore->connection_statistics); - if (NekoRay::dataStore->traffic_loop_interval > 0) { - req.add_stats_outbounds("proxy"); - req.add_stats_outbounds("bypass"); - } - // - bool rpcOK; - QString error = defaultClient->Start(&rpcOK, req); - if (rpcOK && !error.isEmpty()) { - MessageBoxWarning("LoadConfig return error", error); - return; - } - // - NekoRay::traffic::trafficLooper->proxy = result->outboundStat.get(); - NekoRay::traffic::trafficLooper->items = result->outboundStats; - NekoRay::dataStore->ignoreConnTag = result->ignoreConnTag; - NekoRay::traffic::trafficLooper->loop_enabled = true; + libcore::LoadConfigReq req; + req.set_core_config(QJsonObject2QString(result->coreConfig, true).toStdString()); + req.set_enable_nekoray_connections(NekoRay::dataStore->connection_statistics); + if (NekoRay::dataStore->traffic_loop_interval > 0) { + req.add_stats_outbounds("proxy"); + req.add_stats_outbounds("bypass"); + } + // + bool rpcOK; + QString error = defaultClient->Start(&rpcOK, req); + if (rpcOK && !error.isEmpty()) { + runOnUiThread([=] { MessageBoxWarning("LoadConfig return error", error); }); + return false; + } + // + NekoRay::traffic::trafficLooper->proxy = result->outboundStat.get(); + NekoRay::traffic::trafficLooper->items = result->outboundStats; + NekoRay::dataStore->ignoreConnTag = result->ignoreConnTag; + NekoRay::traffic::trafficLooper->loop_enabled = true; #endif - for (const auto &ext: result->exts) { - NekoRay::sys::running_ext.push_back(ext.second); - ext.second->Start(); - } + for (const auto &ext: result->exts) { + NekoRay::sys::running_ext.push_back(ext.second); + ext.second->Start(); + } - NekoRay::dataStore->UpdateStartedId(ent->id); - running = ent; - refresh_status(); - refresh_proxy_list(ent->id); + NekoRay::dataStore->UpdateStartedId(ent->id); + running = ent; + + runOnUiThread([=] { + refresh_status(); + refresh_proxy_list(ent->id); + }); + + return true; + }; + + if (!mu_starting.tryLock()) return; + + // timeout message + auto restartMsgbox = new QMessageBox(QMessageBox::Question, software_name, tr("If there is no response for a long time, it is recommended to restart the software."), + QMessageBox::Yes | QMessageBox::No, this); + connect(restartMsgbox, &QMessageBox::accepted, this, [=] { MW_dialog_message("", "RestartProgram"); }); + auto restartMsgboxTimer = new MessageBoxTimer(this, restartMsgbox, 5000); + + runOnNewThread([=] { + // stop current running + if (NekoRay::dataStore->started_id >= 0) { + runOnUiThread([=] { neko_stop(false, true); }); + sem_stopped.acquire(); + } + // do start + MW_show_log(">>>>>>>> " + tr("Starting profile %1").arg(ent->bean->DisplayTypeAndName())); + if (!neko_start_stage2()) { + MW_show_log("<<<<<<<< " + tr("Failed to start profile %1").arg(ent->bean->DisplayTypeAndName())); + } + mu_starting.unlock(); + // cancel timeout + runOnUiThread([=] { + restartMsgboxTimer->cancel(); + restartMsgboxTimer->deleteLater(); + restartMsgbox->deleteLater(); + }); + }); } -void MainWindow::neko_stop(bool crash) { +void MainWindow::neko_stop(bool crash, bool sem) { auto id = NekoRay::dataStore->started_id; - if (id < 0) return; - show_log_impl(">>>>>>>> " + tr("Stopping profile %1").arg(running->bean->DisplayTypeAndName())); - - while (!NekoRay::sys::running_ext.isEmpty()) { - auto extC = NekoRay::sys::running_ext.takeFirst(); - extC->Kill(); + if (id < 0) { + if (sem) sem_stopped.release(); + return; } + auto neko_stop_stage2 = [=] { + while (!NekoRay::sys::running_ext.isEmpty()) { + auto extC = NekoRay::sys::running_ext.takeFirst(); + extC->Kill(); + } + #ifndef NKR_NO_GRPC - NekoRay::traffic::trafficLooper->loop_enabled = false; - NekoRay::traffic::trafficLooper->loop_mutex.lock(); - if (NekoRay::dataStore->traffic_loop_interval != 0) { - NekoRay::traffic::trafficLooper->UpdateAll(); - for (const auto &item: NekoRay::traffic::trafficLooper->items) { - NekoRay::profileManager->GetProfile(item->id)->Save(); - refresh_proxy_list(item->id); + NekoRay::traffic::trafficLooper->loop_enabled = false; + NekoRay::traffic::trafficLooper->loop_mutex.lock(); + if (NekoRay::dataStore->traffic_loop_interval != 0) { + NekoRay::traffic::trafficLooper->UpdateAll(); + for (const auto &item: NekoRay::traffic::trafficLooper->items) { + NekoRay::profileManager->GetProfile(item->id)->Save(); + runOnUiThread([=] { refresh_proxy_list(item->id); }); + } } - } - NekoRay::traffic::trafficLooper->loop_mutex.unlock(); + NekoRay::traffic::trafficLooper->loop_mutex.unlock(); - if (!crash) { - bool rpcOK; - QString error = defaultClient->Stop(&rpcOK); - if (rpcOK && !error.isEmpty()) { - MessageBoxWarning("Stop return error", error); - return; + if (!crash) { + bool rpcOK; + QString error = defaultClient->Stop(&rpcOK); + if (rpcOK && !error.isEmpty()) { + runOnUiThread([=] { MessageBoxWarning("Stop return error", error); }); + return false; + } } - } #endif - NekoRay::dataStore->UpdateStartedId(-1919); - NekoRay::dataStore->need_keep_vpn_off = false; - running = nullptr; - refresh_status(); - refresh_proxy_list(id); + NekoRay::dataStore->UpdateStartedId(-1919); + NekoRay::dataStore->need_keep_vpn_off = false; + running = nullptr; + + runOnUiThread([=] { + refresh_status(); + refresh_proxy_list(id); + }); + + return true; + }; + + if (!mu_stopping.tryLock()) { + if (sem) sem_stopped.release(); + return; + } + + // timeout message + auto restartMsgbox = new QMessageBox(QMessageBox::Question, software_name, tr("If there is no response for a long time, it is recommended to restart the software."), + QMessageBox::Yes | QMessageBox::No, this); + connect(restartMsgbox, &QMessageBox::accepted, this, [=] { MW_dialog_message("", "RestartProgram"); }); + auto restartMsgboxTimer = new MessageBoxTimer(this, restartMsgbox, 5000); + + runOnNewThread([=] { + // do stop + MW_show_log(">>>>>>>> " + tr("Stopping profile %1").arg(running->bean->DisplayTypeAndName())); + if (!neko_stop_stage2()) { + MW_show_log("<<<<<<<< " + tr("Failed to stop, please restart the program.")); + } + mu_stopping.unlock(); + if (sem) sem_stopped.release(); + // cancel timeout + runOnUiThread([=] { + restartMsgboxTimer->cancel(); + restartMsgboxTimer->deleteLater(); + restartMsgbox->deleteLater(); + }); + }); } void MainWindow::CheckUpdate() { diff --git a/ui/widget/MessageBoxTimer.h b/ui/widget/MessageBoxTimer.h new file mode 100644 index 0000000..7258770 --- /dev/null +++ b/ui/widget/MessageBoxTimer.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +class MessageBoxTimer : public QTimer { +public: + QMessageBox *msgbox = nullptr; + bool showed = false; + + explicit MessageBoxTimer(QObject *parent, QMessageBox *msgbox, int delayMs) : QTimer(parent) { + connect(this, &QTimer::timeout, this, &MessageBoxTimer::timeoutFunc, Qt::ConnectionType::QueuedConnection); + this->msgbox = msgbox; + setSingleShot(true); + setInterval(delayMs); + start(); + }; + + void cancel() { + QTimer::stop(); + if (msgbox != nullptr && showed) { + msgbox->reject(); // return the timeoutFunc + } + }; + +private: + void timeoutFunc() { + if (msgbox == nullptr) return; + showed = true; + msgbox->exec(); + msgbox = nullptr; + } +};