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;
+ }
+};