Compare commits

...

7 Commits

Author SHA1 Message Date
2dust d433aa3b04 Fix
https://github.com/2dust/v2rayN/issues/10236
2026-09-26 11:43:22 +08:00
dependabot[bot] 91a7ed65d5 Bump ReactiveUI from 24.2.0 to 24.3.0 (#10231)
---
updated-dependencies:
- dependency-name: ReactiveUI
  dependency-version: 24.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-26 11:12:07 +08:00
Miheichev Aleksandr Sergeevich 984e685ab3 chore(deps): update Downloader to 5.9.8 (#10222)
Downloader 5.9.6 -> 5.9.8

5.9.7 adds RemoteFileInfo.ContentType. 5.9.8 fixes stop/dispose races:
a cancelled multi-chunk download could report a NullReferenceException
as its error, and a Dispose racing the completion signal could swallow
DownloadFileCompleted. Nothing DownloaderHelper calls changed its API.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 11:11:20 +08:00
DHR60 29f99ceacc Fix speed test (#10219) 2026-09-26 11:10:30 +08:00
DHR60 93d8174dbe Fix async (#10218)
* Fix async

* Fix async for statistics

* Try fix pac async

* Fix
2026-09-26 11:08:36 +08:00
DHR60 fa1e201c76 Fix sing-box dns (#10234) 2026-09-26 10:39:29 +08:00
dependabot[bot] e1cb99cd6e Bump Avalonia.Desktop from 12.1.2 to 12.1.3 (#10216)
---
updated-dependencies:
- dependency-name: Avalonia.Desktop
  dependency-version: 12.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-23 11:03:05 +08:00
14 changed files with 198 additions and 187 deletions
+3 -3
View File
@@ -7,17 +7,17 @@
<ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
<PackageVersion Include="Avalonia.Desktop" Version="12.1.2" />
<PackageVersion Include="Avalonia.Desktop" Version="12.1.3" />
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.2" />
<PackageVersion Include="CliWrap" Version="3.10.5" />
<PackageVersion Include="Downloader" Version="5.9.6" />
<PackageVersion Include="Downloader" Version="5.9.8" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
<PackageVersion Include="QRCoder" Version="1.8.0" />
<PackageVersion Include="ReactiveUI" Version="24.2.0" />
<PackageVersion Include="ReactiveUI" Version="24.3.0" />
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="3.2.0" />
<PackageVersion Include="ReactiveUI.WPF" Version="24.2.0" />
<PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" />
@@ -3,6 +3,7 @@ namespace ServiceLib.Handler.SysProxy;
public static class SysProxyHandler
{
private static readonly string _tag = "SysProxyHandler";
private static readonly Lazy<PacManager> _pacManager = new(() => new PacManager());
public static async Task<bool> UpdateSysProxy(Config config, bool forceDisable)
{
@@ -56,7 +57,7 @@ public static class SysProxyHandler
if (type != ESysProxyType.Pac && Utils.IsWindows())
{
PacManager.Instance.Stop();
_pacManager.Value.Stop();
}
}
catch (Exception ex)
@@ -110,7 +111,7 @@ public static class SysProxyHandler
private static async Task SetWindowsProxyPac(int port)
{
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
await PacManager.Instance.StartAsync(port, portPac);
await _pacManager.Value.StartAsync(port, portPac);
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(strProxy, "", 4);
}
+7 -3
View File
@@ -22,7 +22,7 @@ public class HttpClientHelper
this.httpClient = httpClient;
}
public async Task<string?> TryGetAsync(string url)
public async Task<string?> TryGetAsync(string url, CancellationToken cancellationToken = default)
{
if (url.IsNullOrEmpty())
{
@@ -31,8 +31,12 @@ public class HttpClientHelper
try
{
var response = await httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
var response = await httpClient.GetAsync(url, cancellationToken);
return await response.Content.ReadAsStringAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
+20 -7
View File
@@ -75,15 +75,28 @@ public sealed class SQLiteHelper
public async Task DisposeDbConnectionAsync()
{
await Task.Factory.StartNew(() =>
await Task.Run(() =>
{
_db?.Close();
_db?.Dispose();
_db = null;
try
{
_db?.Close();
_db?.Dispose();
}
finally
{
_db = null;
}
_dbAsync?.GetConnection()?.Close();
_dbAsync?.GetConnection()?.Dispose();
_dbAsync = null;
try
{
var conn = _dbAsync?.GetConnection();
conn?.Close();
conn?.Dispose();
}
finally
{
_dbAsync = null;
}
});
}
}
+49 -64
View File
@@ -2,36 +2,37 @@ namespace ServiceLib.Manager;
public class PacManager
{
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
public static PacManager Instance => _instance.Value;
private int _httpPort;
private const string Tag = "PacManager";
private CancellationTokenSource? _cts;
private int _pacPort;
private TcpListener? _tcpListener;
private byte[] _writeContent;
private bool _isRunning;
private bool _needRestart = true;
private byte[] _writeContent = [];
public async Task StartAsync(int httpPort, int pacPort)
{
_needRestart = httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
var content = await InitText(httpPort);
_writeContent = content;
_httpPort = httpPort;
_pacPort = pacPort;
await InitText();
if (_needRestart)
if (_tcpListener is not null && _pacPort == pacPort)
{
Stop();
RunListener();
return;
}
Stop();
var cts = new CancellationTokenSource();
var listener = TcpListener.Create(pacPort);
listener.Start();
_cts = cts;
_pacPort = pacPort;
_tcpListener = listener;
_ = ListenLoopAsync(listener, cts.Token);
}
private async Task InitText()
private async Task<byte[]> InitText(int httpPort)
{
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyPacPath;
var fileName = (customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath))
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem.CustomSystemProxyPacPath;
var fileName = customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath)
? customSystemProxyPacPath
: Path.Combine(Utils.GetConfigPath(), "pac.txt");
@@ -45,7 +46,7 @@ public class PacManager
}
var pacText = await File.ReadAllTextAsync(fileName);
pacText = pacText.Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
pacText = pacText.Replace("__PROXY__", $"PROXY 127.0.0.1:{httpPort};DIRECT;");
var sb = new StringBuilder();
sb.AppendLine("HTTP/1.0 200 OK");
@@ -54,59 +55,43 @@ public class PacManager
sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(pacText));
sb.AppendLine();
sb.Append(pacText);
_writeContent = Encoding.UTF8.GetBytes(sb.ToString());
return Encoding.UTF8.GetBytes(sb.ToString());
}
private void RunListener()
private async Task ListenLoopAsync(TcpListener listener, CancellationToken token)
{
_tcpListener = TcpListener.Create(_pacPort);
_isRunning = true;
_tcpListener.Start();
Task.Factory.StartNew(async () =>
var buffer = new byte[1024];
try
{
while (_isRunning)
while (!token.IsCancellationRequested)
{
try
{
if (!_tcpListener.Pending())
{
await Task.Delay(10);
continue;
}
var client = await _tcpListener.AcceptTcpClientAsync();
await Task.Run(() => WriteContent(client));
}
catch
{
// ignored
}
using var client = await listener.AcceptTcpClientAsync(token).ConfigureAwait(false);
await using var stream = client.GetStream();
_ = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
await stream.WriteAsync(_writeContent, token).ConfigureAwait(false);
await stream.FlushAsync(token).ConfigureAwait(false);
}
}, TaskCreationOptions.LongRunning);
}
private void WriteContent(TcpClient client)
{
var stream = client.GetStream();
stream.Write(_writeContent, 0, _writeContent.Length);
stream.Flush();
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
}
catch (Exception ex)
{
Logging.SaveLog(Tag, ex);
}
finally
{
listener.Stop();
}
}
public void Stop()
{
if (_tcpListener == null)
{
return;
}
try
{
_isRunning = false;
_tcpListener.Stop();
_tcpListener = null;
}
catch
{
// ignored
}
_cts?.Cancel();
_tcpListener?.Stop();
_cts?.Dispose();
_cts = null;
_pacPort = 0;
_tcpListener = null;
}
}
+3 -9
View File
@@ -12,23 +12,17 @@ public class TaskManager
_config = config;
_updateFunc = updateFunc;
_ = Task.Factory.StartNew(
ScheduledTasks,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
Task.Run(ScheduledTasks);
}
private async Task ScheduledTasks()
{
Logging.SaveLog("Setup Scheduled Tasks");
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
var numOfExecuted = 1;
while (true)
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
{
//1 minute
await Task.Delay(1000 * 60);
//Execute once 1 minute
try
{
+1 -1
View File
@@ -2149,7 +2149,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Not Support 的本地化字符串。
/// 查找类似 Not Supported 的本地化字符串。
/// </summary>
public static string MsgNotSupport {
get {
@@ -365,9 +365,16 @@ public partial class CoreConfigSingboxService
rule4ExpectedIPs = JsonUtils.DeepCopy(rule);
rule4ExpectedIPs.geosite = regionGeosite;
}
if (rule.geosite?.Count > 0
|| rule.domain?.Count > 0
|| rule.domain_keyword?.Count > 0
|| rule.domain_regex?.Count > 0
|| rule.domain_suffix?.Count > 0)
{
AddRules(rule, item, directDnsList);
}
}
if (rule.geosite?.Count > 0 || rule.domain?.Count > 0)
else
{
AddRules(rule, item, directDnsList);
}
+20 -12
View File
@@ -8,7 +8,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private readonly Config? _config = config;
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
private readonly Lock _runLock = new();
private CancellationTokenSource? _runCts;
private readonly List<CancellationTokenSource> _runCtsList = [];
private readonly int _speedTestPageSize = config.SpeedTestItem.SpeedTestPageSize ?? Global.SpeedTestPageSize;
private readonly TimeSpan _delayInterval = TimeSpan.FromSeconds(config.SpeedTestItem.SpeedTestDelayInterval ?? 1);
@@ -18,11 +18,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
lock (_runLock)
{
_runCts?.Cancel();
runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
_runCts = runCts;
_runCtsList.Add(runCts);
}
return RunLoopAsync(actionType, selecteds, runCts);
@@ -30,17 +28,30 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
public void ExitLoop()
{
CancellationTokenSource? runCts;
var counter = 0;
List<CancellationTokenSource> listToCancel;
lock (_runLock)
{
runCts = _runCts;
listToCancel = _runCtsList.ToList();
counter = listToCancel.Count;
}
if (runCts is not null)
foreach (var cts in listToCancel)
{
try
{
cts.Cancel();
}
catch (ObjectDisposedException)
{
// Ignored
}
}
if (counter > 0)
{
_ = UpdateFunc("", ResUI.SpeedtestingStop);
runCts.Cancel();
}
}
@@ -67,10 +78,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
lock (_runLock)
{
if (ReferenceEquals(_runCts, runCts))
{
_runCts = null;
}
_runCtsList.Remove(runCts);
}
runCts.Dispose();
@@ -5,8 +5,7 @@ namespace ServiceLib.Services.Statistics;
public class StatisticsSingboxService
{
private readonly Config _config;
private bool _exitFlag;
private ClientWebSocket? webSocket;
private CancellationTokenSource? _cts;
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
private static readonly string _tag = "StatisticsSingboxService";
@@ -15,40 +14,17 @@ public class StatisticsSingboxService
{
_config = config;
_updateFunc = updateFunc;
_exitFlag = false;
_ = Task.Factory.StartNew(
Run,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
private async Task Init()
{
await Task.Delay(5000);
try
{
if (webSocket == null)
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(Url), CancellationToken.None);
}
}
catch { }
Task.Run(Run);
}
public void Close()
{
try
{
_exitFlag = true;
if (webSocket != null)
{
webSocket.Abort();
webSocket = null;
}
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
}
catch (Exception ex)
{
@@ -58,53 +34,63 @@ public class StatisticsSingboxService
private async Task Run()
{
await Init();
Close();
_cts = new CancellationTokenSource();
var token = _cts.Token;
while (!_exitFlag)
while (!token.IsCancellationRequested)
{
await Task.Delay(1000);
try
{
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
{
await Task.Delay(1000, token).ConfigureAwait(false);
continue;
}
if (webSocket != null)
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri(Url), token).ConfigureAwait(false);
var buffer = new byte[1024];
while (ws.State == WebSocketState.Open
&& !token.IsCancellationRequested)
{
if (webSocket.State is WebSocketState.Aborted or WebSocketState.Closed)
var res = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), token);
if (res.MessageType == WebSocketMessageType.Close)
{
webSocket.Abort();
webSocket = null;
await Init();
continue;
break;
}
using var ms = new MemoryStream();
ms.Write(buffer, 0, res.Count);
while (!res.EndOfMessage)
{
res = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), token).ConfigureAwait(false);
ms.Write(buffer, 0, res.Count);
}
if (webSocket.State != WebSocketState.Open)
var result = Encoding.UTF8.GetString(ms.ToArray());
if (!result.IsNotEmpty())
{
continue;
}
ParseOutput(result, out var up, out var down);
var buffer = new byte[1024];
var res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!res.CloseStatus.HasValue)
if (_updateFunc != null)
{
var result = Encoding.UTF8.GetString(buffer, 0, res.Count);
if (result.IsNotEmpty())
await _updateFunc.Invoke(new ServerSpeedItem
{
ParseOutput(result, out var up, out var down);
await _updateFunc?.Invoke(new ServerSpeedItem()
{
ProxyUp = (long)(up / 1000),
ProxyDown = (long)(down / 1000)
});
}
res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
ProxyUp = (long)(up / 1000),
ProxyDown = (long)(down / 1000),
}).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
break;
}
catch
{
await Task.Delay(3000, token).ConfigureAwait(false);
}
}
}
@@ -5,33 +5,42 @@ public class StatisticsXrayService
private const long linkBase = 1024;
private ServerSpeedItem _serverSpeedItem = new();
private readonly Config _config;
private bool _exitFlag;
private CancellationTokenSource? _cts;
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars";
private static readonly string _tag = "StatisticsXrayService";
public StatisticsXrayService(Config config, Func<ServerSpeedItem, Task> updateFunc)
{
_config = config;
_updateFunc = updateFunc;
_exitFlag = false;
_ = Task.Factory.StartNew(
Run,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
Task.Run(Run);
}
public void Close()
{
_exitFlag = true;
try
{
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private async Task Run()
{
while (!_exitFlag)
Close();
_cts = new CancellationTokenSource();
var token = _cts.Token;
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
await Task.Delay(1000);
try
{
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
@@ -39,16 +48,20 @@ public class StatisticsXrayService
continue;
}
var result = await HttpClientHelper.Instance.TryGetAsync(Url);
var result = await HttpClientHelper.Instance.TryGetAsync(Url, token);
if (result != null)
{
var server = ParseOutput(result) ?? new ServerSpeedItem();
await _updateFunc?.Invoke(server);
await _updateFunc!.Invoke(server);
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
break;
}
catch
{
// ignored
await Task.Delay(3000, token).ConfigureAwait(false);
}
}
}
@@ -42,11 +42,21 @@ public partial class CheckUpdateViewModel : MyReactiveObject
this.WhenAnyValue(x => x.EnableUpdateViaProxy)
.Subscribe(c => _ = OnUpdateViaProxyChanged());
RefreshCheckUpdateItems();
AppEvents.HasUpdateNotified
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(bl => RefreshCheckUpdateItems(bl));
RefreshCheckUpdateItems(true);
}
private void RefreshCheckUpdateItems()
private void RefreshCheckUpdateItems(bool hasUpdate)
{
if (!hasUpdate)
{
return;
}
var models = CoreInfoManager.Instance.GetCheckUpdateCoreTypes()
.Select(t => GetCheckUpdateModel(t))
.ToList();
@@ -30,12 +30,7 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
cancelDisposable.DisposeWith(disposables);
var token = cancelDisposable.Token;
Task.Factory.StartNew(
async () => await GetClashConnectionsTask(token),
token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default
);
Task.Run(() => GetClashConnectionsTask(token));
});
}
@@ -129,9 +124,9 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
try
{
var numOfExecuted = 1;
while (!token.IsCancellationRequested)
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
await Task.Delay(1000, token);
numOfExecuted++;
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
@@ -61,12 +61,7 @@ public partial class ClashProxiesViewModel : MyReactiveObject
cancelDisposable.DisposeWith(disposables);
var token = cancelDisposable.Token;
Task.Factory.StartNew(
async () => await GetClashProxiesTask(token),
token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default
);
Task.Run(() => GetClashProxiesTask(token));
});
}
@@ -113,9 +108,9 @@ public partial class ClashProxiesViewModel : MyReactiveObject
try
{
var numOfExecuted = 1;
while (!token.IsCancellationRequested)
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
{
await Task.Delay(1000, token);
numOfExecuted++;
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))