diff --git a/core/server/gen/libcore.proto b/core/server/gen/libcore.proto index 3e40550..4c07518 100644 --- a/core/server/gen/libcore.proto +++ b/core/server/gen/libcore.proto @@ -53,6 +53,7 @@ message TestReq { optional string url = 4 [default = ""]; optional bool test_current = 5 [default = false]; optional int32 max_concurrency = 6 [default = 0]; + optional int32 test_timeout_ms = 7 [default = 0]; } message TestResp { @@ -98,6 +99,7 @@ message SpeedTestRequest { optional bool test_upload = 6 [default = false]; optional bool simple_download = 7 [default = false]; optional string simple_download_addr = 8 [default = ""]; + optional int32 timeout_ms = 9 [default = 0]; } message SpeedTestResult { diff --git a/core/server/server.go b/core/server/server.go index dd05646..9deed14 100644 --- a/core/server/server.go +++ b/core/server/server.go @@ -182,7 +182,7 @@ func (s *server) Test(in *gen.TestReq, out *gen.TestResp) error { if maxConcurrency >= 500 || maxConcurrency == 0 { maxConcurrency = MaxConcurrentTests } - results := BatchURLTest(testCtx, testInstance, outboundTags, *in.Url, int(maxConcurrency), twice) + results := BatchURLTest(testCtx, testInstance, outboundTags, *in.Url, int(maxConcurrency), twice, time.Duration(*in.TestTimeoutMs)*time.Millisecond) res := make([]*gen.URLTestResp, 0) for idx, data := range results { @@ -339,7 +339,7 @@ func (s *server) SpeedTest(in *gen.SpeedTestRequest, out *gen.SpeedTestResponse) outboundTags = []string{outbound.Tag()} } - results := BatchSpeedTest(testCtx, testInstance, outboundTags, *in.TestDownload, *in.TestUpload, *in.SimpleDownload, *in.SimpleDownloadAddr) + results := BatchSpeedTest(testCtx, testInstance, outboundTags, *in.TestDownload, *in.TestUpload, *in.SimpleDownload, *in.SimpleDownloadAddr, time.Duration(*in.TimeoutMs)*time.Millisecond) res := make([]*gen.SpeedTestResult, 0) for _, data := range results { diff --git a/core/server/test_utils.go b/core/server/test_utils.go index 2743ca7..4da92d2 100644 --- a/core/server/test_utils.go +++ b/core/server/test_utils.go @@ -24,6 +24,7 @@ var SpTQuerier SpeedTestResultQuerier var URLReporter URLTestReporter const URLTestTimeout = 3 * time.Second +const FetchServersTimeout = 8 * time.Second const MaxConcurrentTests = 100 type URLTestResult struct { @@ -84,7 +85,10 @@ func (s *SpeedTestResultQuerier) setIsRunning(isRunning bool) { s.isRunning = isRunning } -func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url string, maxConcurrency int, twice bool) []*URLTestResult { +func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url string, maxConcurrency int, twice bool, timeout time.Duration) []*URLTestResult { + if timeout <= 0 { + timeout = URLTestTimeout + } outbounds := service.FromContext[adapter.OutboundManager](i.Context()) resMap := make(map[string]*URLTestResult) resAccess := sync.Mutex{} @@ -113,11 +117,11 @@ func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url } client := &http.Client{ Transport: &http.Transport{ - DialContext: func(_ context.Context, network string, addr string) (net.Conn, error) { + DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { return outbound.DialContext(ctx, "tcp", metadata.ParseSocksaddr(addr)) }, }, - Timeout: URLTestTimeout, + Timeout: timeout, } // to properly measure muxed configs, let's do the test twice duration, err := urlTest(ctx, client, url) @@ -148,8 +152,6 @@ func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url } func urlTest(ctx context.Context, client *http.Client, url string) (time.Duration, error) { - ctx, cancel := context.WithTimeout(ctx, URLTestTimeout) - defer cancel() begin := time.Now() req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { @@ -169,7 +171,7 @@ func getNetDialer(dialer func(ctx context.Context, network string, destination m } } -func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, testDl, testUl bool, simpleDL bool, simpleAddress string) []*SpeedTestResult { +func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, testDl, testUl bool, simpleDL bool, simpleAddress string, timeout time.Duration) []*SpeedTestResult { outbounds := service.FromContext[adapter.OutboundManager](i.Context()) results := make([]*SpeedTestResult, 0) @@ -189,9 +191,9 @@ func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, t var err error if simpleDL { - err = simpleDownloadTest(ctx, getNetDialer(outbound.DialContext), res, simpleAddress) + err = simpleDownloadTest(ctx, getNetDialer(outbound.DialContext), res, simpleAddress, timeout) } else { - err = speedTestWithDialer(ctx, getNetDialer(outbound.DialContext), res, testDl, testUl) + err = speedTestWithDialer(ctx, getNetDialer(outbound.DialContext), res, testDl, testUl, timeout) } if err != nil && !errors.Is(err, context.Canceled) { res.Error = err @@ -208,14 +210,17 @@ func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, t return results } -func simpleDownloadTest(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testURL string) error { +func simpleDownloadTest(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testURL string, timeout time.Duration) error { + if timeout <= 0 { + timeout = URLTestTimeout + } client := &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { return dialer(ctx, network, addr) }, }, - Timeout: URLTestTimeout, + Timeout: timeout, } res.ServerName = "N/A" @@ -268,13 +273,15 @@ func simpleDownloadTest(ctx context.Context, dialer func(ctx context.Context, ne } } -func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testDl, testUl bool) error { +func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testDl, testUl bool, timeout time.Duration) error { clt := speedtest.New(speedtest.WithUserConfig(&speedtest.UserConfig{ DialContextFunc: dialer, PingMode: speedtest.HTTP, MaxConnections: 8, })) - srv, err := clt.FetchServerListContext(ctx) + fetchCtx, cancel := context.WithTimeout(ctx, FetchServersTimeout) + defer cancel() + srv, err := clt.FetchServerListContext(fetchCtx) if err != nil { return err } @@ -297,14 +304,18 @@ func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, n go func() { defer func() { close(done) }() if testDl { - err = srv[0].DownloadTestContext(ctx) + timeoutCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + err = srv[0].DownloadTestContext(timeoutCtx) if err != nil && !errors.Is(err, context.Canceled) { res.Error = err return } } if testUl { - err = srv[0].UploadTestContext(ctx) + timeoutCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + err = srv[0].UploadTestContext(timeoutCtx) if err != nil && !errors.Is(err, context.Canceled) { res.Error = err return diff --git a/include/global/DataStore.hpp b/include/global/DataStore.hpp index 74c4fce..9918a38 100644 --- a/include/global/DataStore.hpp +++ b/include/global/DataStore.hpp @@ -79,6 +79,7 @@ namespace Configs { // Misc QString log_level = "info"; QString test_latency_url = "http://cp.cloudflare.com/"; + int url_test_timeout_ms = 3000; bool disable_tray = false; int test_concurrent = 10; bool disable_traffic_stats = false; @@ -99,6 +100,7 @@ namespace Configs { bool enable_stats = true; int stats_tab = 0; // either connection or log int speed_test_mode = TestConfig::FULL; + int speed_test_timeout_ms = 3000; QString simple_dl_url = "http://cachefly.cachefly.net/1mb.test"; bool allow_beta_update = false; diff --git a/include/ui/setting/dialog_basic_settings.ui b/include/ui/setting/dialog_basic_settings.ui index 29f8719..718f656 100644 --- a/include/ui/setting/dialog_basic_settings.ui +++ b/include/ui/setting/dialog_basic_settings.ui @@ -6,7 +6,7 @@ 0 0 - 728 + 767 472 @@ -20,7 +20,7 @@ Basic Settings - + Qt::Orientation::Horizontal @@ -165,27 +165,52 @@ - + - - - Latency Test URL - + + + + + + Latency Test URL + + + + + + + - - - - - - Concurrent - + + + + + + Concurrent + + + + + + + + + + <html><head/><body><p>Timeout for URLtest in ms<br/>Note that muxed connections take a much longer time for their initial request, and setting this value too low will cause the test to falsely report that the config is not working</p></body></html> + + + Timeout + + + + + + + - - - @@ -260,6 +285,19 @@ + + + + <html><head/><body><p>timeout in milliseconds<br/>applies to all tests individually</p></body></html> + + + Timeout + + + + + + @@ -534,80 +572,80 @@ Subscription - - - - - - - 0 - 0 - - - - Enable - - - - - - - Interval (minute, invalid if less than 30) - - - - - - - - 0 - 0 - - - - - - - - - - - - - Clear servers before updating subscription - - - - - - - <html><head/><body><p>HWID=%1</p><p>OS=%2</p><p>OS Version=%3</p><p>Model=%4</p></body></html> - - - Enable sending HWID, device model, and OS version when updating subscription - - - - - - - - 0 - 0 - - - - Automatic update - - - - - - - User Agent - - - + + + + + + + 0 + 0 + + + + Enable + + + + + + + Interval (minute, invalid if less than 30) + + + + + + + + 0 + 0 + + + + + + + + + + + + + Clear servers before updating subscription + + + + + + + <html><head/><body><p>HWID=%1</p><p>OS=%2</p><p>OS Version=%3</p><p>Model=%4</p></body></html> + + + Enable sending HWID, device model, and OS version when updating subscription + + + + + + + + 0 + 0 + + + + Automatic update + + + + + + + User Agent + + + diff --git a/src/global/Configs.cpp b/src/global/Configs.cpp index 697eb80..b54f650 100644 --- a/src/global/Configs.cpp +++ b/src/global/Configs.cpp @@ -318,6 +318,8 @@ namespace Configs { _add(new configItem("use_mozilla_certs", &use_mozilla_certs, itemType::boolean)); _add(new configItem("allow_beta_update", &allow_beta_update, itemType::boolean)); _add(new configItem("adblock_enable", &adblock_enable, itemType::boolean)); + _add(new configItem("speed_test_timeout_ms", &speed_test_timeout_ms, itemType::integer)); + _add(new configItem("url_test_timeout_ms", &url_test_timeout_ms, itemType::integer)); } void DataStore::UpdateStartedId(int id) { diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index e80e47c..4c54cae 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -2384,6 +2384,7 @@ void MainWindow::loadShortcuts() auto mp = Configs::dataStore->shortcuts->shortcuts; for (QList actions = findChildren(); QAction *action : actions) { + if (action->data().isNull() || action->data().toString().isEmpty()) continue; if (mp.count(action->data().toString()) == 0) action->setShortcut(QKeySequence()); else action->setShortcut(mp[action->data().toString()]); } diff --git a/src/ui/mainwindow_grpc.cpp b/src/ui/mainwindow_grpc.cpp index cb928ab..6ef7b60 100644 --- a/src/ui/mainwindow_grpc.cpp +++ b/src/ui/mainwindow_grpc.cpp @@ -43,6 +43,7 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin req.url = Configs::dataStore->test_latency_url.toStdString(); req.use_default_outbound = useDefault; req.max_concurrency = Configs::dataStore->test_concurrent; + req.test_timeout_ms = Configs::dataStore->url_test_timeout_ms; auto done = new QMutex; done->lock(); @@ -51,7 +52,7 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin bool ok; while (true) { - QThread::msleep(1500); + QThread::msleep(200); if (done->try_lock()) break; auto resp = defaultClient->QueryURLTest(&ok); if (!ok || resp.results.empty()) @@ -285,6 +286,7 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, bool testC req.simple_download = speedtestConf == Configs::TestConfig::SIMPLEDL; req.simple_download_addr = Configs::dataStore->simple_dl_url.toStdString(); req.test_current = testCurrent; + req.timeout_ms = Configs::dataStore->speed_test_timeout_ms; // loop query result auto doneMu = new QMutex; diff --git a/src/ui/setting/dialog_basic_settings.cpp b/src/ui/setting/dialog_basic_settings.cpp index 9297e7e..eb2369d 100644 --- a/src/ui/setting/dialog_basic_settings.cpp +++ b/src/ui/setting/dialog_basic_settings.cpp @@ -36,7 +36,9 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) D_LOAD_INT(test_concurrent) D_LOAD_STRING(test_latency_url) D_LOAD_BOOL(disable_tray) + ui->url_timeout->setText(Int2String(Configs::dataStore->url_test_timeout_ms)); ui->speedtest_mode->setCurrentIndex(Configs::dataStore->speed_test_mode); + ui->test_timeout->setText(Int2String(Configs::dataStore->speed_test_timeout_ms)); ui->simple_down_url->setText(Configs::dataStore->simple_dl_url); ui->allow_beta->setChecked(Configs::dataStore->allow_beta_update); @@ -184,6 +186,8 @@ void DialogBasicSettings::accept() { Configs::dataStore->proxy_scheme = ui->proxy_scheme->currentText().toLower(); Configs::dataStore->speed_test_mode = ui->speedtest_mode->currentIndex(); Configs::dataStore->simple_dl_url = ui->simple_down_url->text(); + Configs::dataStore->url_test_timeout_ms = ui->url_timeout->text().toInt(); + Configs::dataStore->speed_test_timeout_ms = ui->test_timeout->text().toInt(); Configs::dataStore->allow_beta_update = ui->allow_beta->isChecked(); // Style