Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2026-06-18 - [Optimization & Reliability]
**Инсайт:** Синхронные вызовы `Process.Kill()` и `WaitForExit()` в UI-потоке приводили к заметным «зависаниям» интерфейса при остановке нескольких процессов (Zapret + ByeDPI/Warp). Менеджер доменов игнорировал список исключений при синхронизации с движком.
**Действие:** Перевести все команды управления процессами в `async Task`. Использовать `WaitForExitAsync()` для неблокирующего ожидания завершения. Реализовать полную синхронизацию списков (цели + исключения) в `SyncCustomHostlist`. Внедрить debounced-сохранение настроек для предотвращения избыточной дисковой активности.
3 changes: 2 additions & 1 deletion FluxRoute.AI/Services/BanditSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ public BanditSelector(AiStrategyRegistry registry, Func<AiSettings>? aiSettings
return pareto[_rng.Next(pareto.Count)].g;
}

return scoredUsable.OrderByDescending(x => x.score).First().g;
// BOLT ⚡: Replacing O(N log N) OrderBy with O(N) MaxBy for better performance.
return scoredUsable.MaxBy(x => x.score).g;
}

public StrategyGenome? BestKnownForNetwork(IReadOnlyList<StrategyGenome> candidates, string networkHash)
Expand Down
48 changes: 24 additions & 24 deletions FluxRoute/ViewModels/MainViewModel.Ai.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,27 @@ public partial class MainViewModel
[ObservableProperty] private bool aiEnabled;
partial void OnAiEnabledChanged(bool value)
{
SaveSettings();
SaveSettingsDebounced();
if (value)
RebuildAiStrategyRows();
}

[ObservableProperty] private bool useUcb1;
partial void OnUseUcb1Changed(bool value) => SaveSettings();
partial void OnUseUcb1Changed(bool value) => SaveSettingsDebounced();

[ObservableProperty] private int aiExplorationPermil = 100;
partial void OnAiExplorationPermilChanged(int value) => SaveSettings();
partial void OnAiExplorationPermilChanged(int value) => SaveSettingsDebounced();

[ObservableProperty] private int aiAutoDeleteBelowScore = 60;
partial void OnAiAutoDeleteBelowScoreChanged(int value) => SaveSettings();
partial void OnAiAutoDeleteBelowScoreChanged(int value) => SaveSettingsDebounced();

[ObservableProperty] private bool useHybridMode;
partial void OnUseHybridModeChanged(bool value) => SaveSettings();
partial void OnUseHybridModeChanged(bool value) => SaveSettingsDebounced();

[ObservableProperty] private int engineMode;
partial void OnEngineModeChanged(int value)
{
SaveSettings();
SaveSettingsDebounced();
var runMode = value switch
{
(int)DpiEngineMode.Zapret => DpiRunMode.Standalone,
Expand All @@ -50,58 +50,58 @@ partial void OnEngineModeChanged(int value)
}

[ObservableProperty] private int byeDpiSocksPort = 1080;
partial void OnByeDpiSocksPortChanged(int value) => SaveSettings();
partial void OnByeDpiSocksPortChanged(int value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiSplitPos;
partial void OnByeDpiSplitPosChanged(string? value) => SaveSettings();
partial void OnByeDpiSplitPosChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiDisorderPos = "1";
partial void OnByeDpiDisorderPosChanged(string? value) => SaveSettings();
partial void OnByeDpiDisorderPosChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiFakePos;
partial void OnByeDpiFakePosChanged(string? value) => SaveSettings();
partial void OnByeDpiFakePosChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiOobPos;
partial void OnByeDpiOobPosChanged(string? value) => SaveSettings();
partial void OnByeDpiOobPosChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiTlsrecPos;
partial void OnByeDpiTlsrecPosChanged(string? value) => SaveSettings();
partial void OnByeDpiTlsrecPosChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private int? byeDpiFakeTtl;
partial void OnByeDpiFakeTtlChanged(int? value) => SaveSettings();
partial void OnByeDpiFakeTtlChanged(int? value) => SaveSettingsDebounced();

[ObservableProperty] private bool byeDpiAutoTtl;
partial void OnByeDpiAutoTtlChanged(bool value) => SaveSettings();
partial void OnByeDpiAutoTtlChanged(bool value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiAutoMode = "torst";
partial void OnByeDpiAutoModeChanged(string? value) => SaveSettings();
partial void OnByeDpiAutoModeChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private int? byeDpiTimeout;
partial void OnByeDpiTimeoutChanged(int? value) => SaveSettings();
partial void OnByeDpiTimeoutChanged(int? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiHosts;
partial void OnByeDpiHostsChanged(string? value) => SaveSettings();
partial void OnByeDpiHostsChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiHostlist;
partial void OnByeDpiHostlistChanged(string? value) => SaveSettings();
partial void OnByeDpiHostlistChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiFakeTlsMod;
partial void OnByeDpiFakeTlsModChanged(string? value) => SaveSettings();
partial void OnByeDpiFakeTlsModChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiFakeSni;
partial void OnByeDpiFakeSniChanged(string? value) => SaveSettings();
partial void OnByeDpiFakeSniChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiFakeData;
partial void OnByeDpiFakeDataChanged(string? value) => SaveSettings();
partial void OnByeDpiFakeDataChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private string? byeDpiModHttp;
partial void OnByeDpiModHttpChanged(string? value) => SaveSettings();
partial void OnByeDpiModHttpChanged(string? value) => SaveSettingsDebounced();

[ObservableProperty] private int? byeDpiTlsminor;
partial void OnByeDpiTlsminorChanged(int? value) => SaveSettings();
partial void OnByeDpiTlsminorChanged(int? value) => SaveSettingsDebounced();

[ObservableProperty] private bool byeDpiMd5sig;
partial void OnByeDpiMd5sigChanged(bool value) => SaveSettings();
partial void OnByeDpiMd5sigChanged(bool value) => SaveSettingsDebounced();

[ObservableProperty] private string aiNetworkLabel = "—";
[ObservableProperty] private string aiGenerationText = "—";
Expand Down
39 changes: 20 additions & 19 deletions FluxRoute/ViewModels/MainViewModel.Orchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private void OnOrchestratorStatus(object? sender, OrchestratorEventArgs e)
if (e.Message.Contains("Сканирование завершено", StringComparison.OrdinalIgnoreCase))
{
SortProfileScores();
SaveSettings();
SaveSettingsDebounced();
}

if (e.IsSwitched && e.NewProfile is not null)
Expand Down Expand Up @@ -176,10 +176,10 @@ private async Task SwitchProfileAsync(ProfileItem? profile)
if (dispatcher is null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished)
return;

void SwitchOnUi()
async Task SwitchOnUi()
{
_suppressOrchestratorStop = true;
Stop();
await Stop();
_suppressOrchestratorStop = false;

if (profile is not null)
Expand All @@ -191,23 +191,23 @@ void SwitchOnUi()
}

if (dispatcher.CheckAccess())
SwitchOnUi();
await SwitchOnUi();
else
await dispatcher.InvokeAsync(SwitchOnUi).Task.ConfigureAwait(false);
await dispatcher.InvokeAsync(SwitchOnUi).Task.Unwrap().ConfigureAwait(false);

// Даём время на завершение WinDivert и winws.exe
await Task.Delay(1500).ConfigureAwait(false);

void StartOnUi()
async Task StartOnUi()
{
if (profile is not null)
Start();
await Start();
}

if (dispatcher.CheckAccess())
StartOnUi();
await StartOnUi();
else
await dispatcher.InvokeAsync(StartOnUi).Task.ConfigureAwait(false);
await dispatcher.InvokeAsync(StartOnUi).Task.Unwrap().ConfigureAwait(false);
}

private Task UpdateProfileScoreAsync(string fileName, int score)
Expand Down Expand Up @@ -317,19 +317,18 @@ private Task EnsureProtectionRunningAsync()
if (dispatcher is null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished)
return Task.CompletedTask;

void EnsureOnUi()
async Task EnsureOnUi()
{
if (SelectedProfile is not null && !IsTrackedProcessRunning())
Start();
await Start();
}

if (dispatcher.CheckAccess())
{
EnsureOnUi();
return Task.CompletedTask;
return EnsureOnUi();
}

return dispatcher.InvokeAsync(EnsureOnUi).Task;
return dispatcher.InvokeAsync(EnsureOnUi).Task.Unwrap();
}


Expand Down Expand Up @@ -375,7 +374,7 @@ private void DeleteAiStrategy(AiStrategyRowVm? row)
if (wasActive)
{
if (IsRunning)
Stop();
_ = Stop();
SelectedProfile = Profiles.FirstOrDefault();
}

Expand Down Expand Up @@ -465,7 +464,7 @@ private async Task ScanProfiles()
await _orchestrator.ScanAllProfilesAsync();
SortProfileScores();
ScanProgressText = "Сканирование завершено";
SaveSettings();
SaveSettingsDebounced();

var top = ProfileScores.FirstOrDefault(s => s.Score > 0);
if (top is not null)
Expand Down Expand Up @@ -561,9 +560,11 @@ internal void ApplyOrchestratorEnabledState()
{
Logs.Add("[Оркестратор] Переход в ручной режим — перезапуск Zapret...");
_suppressOrchestratorStop = true;
Stop();
_suppressOrchestratorStop = false;
Start();
_ = Stop().ContinueWith(_ =>
{
_suppressOrchestratorStop = false;
return Start();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
}
Expand Down
28 changes: 15 additions & 13 deletions FluxRoute/ViewModels/MainViewModel.Process.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,16 @@ private void RefreshProfiles()
}

[RelayCommand]
private void ToggleStartStop()
private async Task ToggleStartStop()
{
if (IsRunning)
Stop();
await Stop();
else
Start();
await Start();
}

[RelayCommand]
private void Start()
private async Task Start()
{
if (IsRunning)
{
Expand Down Expand Up @@ -111,13 +111,13 @@ private void Start()

if (ProfileBatLauncher.TryCreateLaunchPlan(SelectedProfile.FullPath, EngineDir, out var plan, out var parseError) && plan is not null)
{
StartWinwsDirect(plan);
await StartWinwsDirect(plan);
return;
}

Logs.Add($"⚠️ Прямой запуск winws.exe недоступен: {parseError}");
Logs.Add("⚠️ Использую совместимый запуск через BAT/cmd.exe.");
StartViaBatFallback();
await StartViaBatFallback();
}
catch (Exception ex)
{
Expand All @@ -126,7 +126,7 @@ private void Start()
}
}

private void StartWinwsDirect(WinwsLaunchPlan plan)
private async Task StartWinwsDirect(WinwsLaunchPlan plan)
{
var winws = ProfileBatLauncher.StartWinws(plan);
_startedViaBatFallback = false;
Expand All @@ -142,7 +142,7 @@ private void StartWinwsDirect(WinwsLaunchPlan plan)
TryStartOrchestratorIfEnabled();

if (UseHybridMode)
_ = StartByeDpiAsync();
await StartByeDpiAsync();

Application.Current?.Dispatcher.BeginInvoke(() =>
{
Expand Down Expand Up @@ -182,7 +182,7 @@ private async Task StartByeDpiAsync()
}
}

private void StartViaBatFallback()
private async Task StartViaBatFallback()
{
var psi = new ProcessStartInfo
{
Expand Down Expand Up @@ -223,7 +223,7 @@ private void StartViaBatFallback()
}, System.Windows.Threading.DispatcherPriority.Background);

if (UseHybridMode)
_ = StartByeDpiAsync();
await StartByeDpiAsync();
}

private async Task TrackDirectWinwsAsync(Process winws)
Expand Down Expand Up @@ -377,7 +377,7 @@ await RunOnUiThreadAsync(() =>
}

[RelayCommand]
private void Stop()
private async Task Stop()
{
_hideWindowsCts?.Cancel();

Expand Down Expand Up @@ -425,7 +425,9 @@ private void Stop()
{
using var p = Process.GetProcessById((int)pid);
p.Kill(entireProcessTree: true);
p.WaitForExit(1500);

using var cts = new CancellationTokenSource(2000);
await p.WaitForExitAsync(cts.Token);
killed++;
}
catch
Expand All @@ -448,7 +450,7 @@ private void Stop()
IsRunning = false;
_runStartedAt = null;

try { _ = _engineManager.StopAllAsync(); }
try { await _engineManager.StopAllAsync(); }
catch { }

if (!_suppressOrchestratorStop)
Expand Down
Loading