v0.19.0 — Bump default parallel segments 8→16 to shorten end-of-DL tail

Issue : on a 14 GB download with 8 segments, when 7 finish there's only
1 connection left handling its assigned 1.75 GB → apparent throughput
collapses to 1/8th of the peak for the final stretch (~5 min visible
slowdown at the end).

Quick fix : double the default segment count.
- 8 segments  : last has 1.75 GB to finish alone
- 16 segments : last has 875 MB → tail ~halved

Tuning surfaced in Settings → Avancés → Serveur as
« Connexions parallèles pour les téléchargements (1-32, défaut 16) ».
Power users on faster servers can push to 24-32. OVH mutualisé tolerates
up to 16-24 ; above that, soft rate-limiting kicks in.

Aligned :
- LocalConfig default : 8 → 16
- DownloadManager clamp : Math.Clamp(_, 1, 16) → 1, 32
- HttpClient handler MaxConnectionsPerServer : 16 → 32 (matches new ceiling)

Versions bumped to 0.19.0.

Existing config.json files will keep their previous value (8) untouched ;
delete config.json to pick up the new default, or just bump it in
Settings → Avancés.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 18:28:35 +02:00
parent 0bd4c8a4be
commit 3c8438f3fe
9 changed files with 51 additions and 13 deletions

View File

@@ -11,7 +11,7 @@
#define MyAppName "PROSERVE Launcher" #define MyAppName "PROSERVE Launcher"
#define MyAppShortName "PS_Launcher" #define MyAppShortName "PS_Launcher"
#define MyAppVersion "0.18.0" #define MyAppVersion "0.19.0"
#define MyAppPublisher "ASTERION VR" #define MyAppPublisher "ASTERION VR"
#define MyAppURL "https://asterionvr.com" #define MyAppURL "https://asterionvr.com"
#define MyAppExeName "PS_Launcher.exe" #define MyAppExeName "PS_Launcher.exe"

View File

@@ -112,16 +112,16 @@ public partial class App : Application
services.AddSingleton(sp => services.AddSingleton(sp =>
{ {
// Handler tuné pour les gros téléchargements parallèles : // Handler tuné pour les gros téléchargements parallèles :
// - MaxConnectionsPerServer=16 pour autoriser le multi-segment (8 par défaut). // - MaxConnectionsPerServer=32 pour aligner avec le clamp DownloadManager (8-32).
// - AutomaticDecompression sur l'API JSON uniquement (None ici car les builds // Default WPF = 8 → trop bas pour notre multi-segment.
// sont des ZIPs déjà compressés ; éviter la CPU+RAM gaspillée par un éventuel // - AutomaticDecompression=None : les builds sont des ZIPs déjà compressés ;
// wrapping gzip côté serveur). // éviter la CPU+RAM gaspillée par un éventuel wrapping gzip côté serveur.
// - PooledConnectionLifetime court pour éviter qu'OVH ferme nos sockets sous nous. // - PooledConnectionLifetime court pour éviter qu'OVH ferme nos sockets sous nous.
var handler = new SocketsHttpHandler var handler = new SocketsHttpHandler
{ {
PooledConnectionLifetime = TimeSpan.FromMinutes(5), PooledConnectionLifetime = TimeSpan.FromMinutes(5),
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(60), PooledConnectionIdleTimeout = TimeSpan.FromSeconds(60),
MaxConnectionsPerServer = 16, MaxConnectionsPerServer = 32,
EnableMultipleHttp2Connections = true, EnableMultipleHttp2Connections = true,
AutomaticDecompression = System.Net.DecompressionMethods.None, AutomaticDecompression = System.Net.DecompressionMethods.None,
}; };

View File

@@ -15,9 +15,9 @@
<Product>PROSERVE Launcher</Product> <Product>PROSERVE Launcher</Product>
<Copyright>© 2026 ASTERION VR — All rights reserved</Copyright> <Copyright>© 2026 ASTERION VR — All rights reserved</Copyright>
<RootNamespace>PSLauncher.App</RootNamespace> <RootNamespace>PSLauncher.App</RootNamespace>
<Version>0.18.0</Version> <Version>0.19.0</Version>
<AssemblyVersion>0.18.0.0</AssemblyVersion> <AssemblyVersion>0.19.0.0</AssemblyVersion>
<FileVersion>0.18.0.0</FileVersion> <FileVersion>0.19.0.0</FileVersion>
<!-- Single-file self-contained publish profile (used by `dotnet publish`) --> <!-- Single-file self-contained publish profile (used by `dotnet publish`) -->
<PublishSingleFile>true</PublishSingleFile> <PublishSingleFile>true</PublishSingleFile>

View File

@@ -49,6 +49,7 @@ public sealed partial class SettingsViewModel : ObservableObject
[ObservableProperty] private string _language; [ObservableProperty] private string _language;
[ObservableProperty] private string _reportUrl; [ObservableProperty] private string _reportUrl;
[ObservableProperty] private string _documentationUrl; [ObservableProperty] private string _documentationUrl;
[ObservableProperty] private int _parallelSegments;
// ----- DB Migration settings ----- // ----- DB Migration settings -----
[ObservableProperty] private string _dbHost; [ObservableProperty] private string _dbHost;
@@ -173,6 +174,7 @@ public sealed partial class SettingsViewModel : ObservableObject
_language = string.IsNullOrWhiteSpace(config.Language) ? "auto" : config.Language; _language = string.IsNullOrWhiteSpace(config.Language) ? "auto" : config.Language;
_reportUrl = config.ReportUrl; _reportUrl = config.ReportUrl;
_documentationUrl = config.DocumentationUrl; _documentationUrl = config.DocumentationUrl;
_parallelSegments = config.ParallelDownloadSegments;
_dbHost = config.Database.Host; _dbHost = config.Database.Host;
_dbPort = config.Database.Port; _dbPort = config.Database.Port;
@@ -303,6 +305,7 @@ public sealed partial class SettingsViewModel : ObservableObject
_config.Language = Language ?? "auto"; _config.Language = Language ?? "auto";
_config.ReportUrl = ReportUrl?.Trim() ?? ""; _config.ReportUrl = ReportUrl?.Trim() ?? "";
_config.DocumentationUrl = DocumentationUrl?.Trim() ?? ""; _config.DocumentationUrl = DocumentationUrl?.Trim() ?? "";
_config.ParallelDownloadSegments = Math.Clamp(ParallelSegments, 1, 32);
// DB settings // DB settings
_config.Database.Host = DbHost.Trim(); _config.Database.Host = DbHost.Trim();

View File

@@ -230,6 +230,19 @@
Background="#1A1A20" Foreground="{StaticResource Brush.Text.Primary}" Background="#1A1A20" Foreground="{StaticResource Brush.Text.Primary}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1" Padding="8" BorderBrush="{StaticResource Brush.Border}" BorderThickness="1" Padding="8"
FontFamily="Consolas" /> FontFamily="Consolas" />
<!-- Connexions parallèles : tuning de la queue de fin de DL -->
<TextBlock Text="{x:Static loc:Strings.SettingsParallelSegments}"
Foreground="{StaticResource Brush.Text.Secondary}"
FontSize="12" Margin="0,16,0,4" />
<TextBox Text="{Binding ParallelSegments, UpdateSourceTrigger=PropertyChanged}"
Background="#1A1A20" Foreground="{StaticResource Brush.Text.Primary}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1" Padding="8"
Width="80" HorizontalAlignment="Left" />
<TextBlock Text="{x:Static loc:Strings.SettingsParallelSegmentsHint}"
Foreground="{StaticResource Brush.Text.Secondary}"
FontSize="11" FontStyle="Italic" Margin="0,4,0,0"
TextWrapping="Wrap" />
</StackPanel> </StackPanel>
</Border> </Border>

View File

@@ -176,7 +176,10 @@ public sealed class DownloadManager : IDownloadManager
} }
// 2. Choix single vs multi-segment // 2. Choix single vs multi-segment
var requestedSegments = Math.Clamp(_config.ParallelDownloadSegments, 1, 16); // Hard-cap à 32 : au-delà OVH mutualisé risque le rate-limit per-IP et le
// gain marginal devient négatif. 16 = défaut, 24-32 OK pour les serveurs
// qui le supportent. 1 = comportement single-segment legacy.
var requestedSegments = Math.Clamp(_config.ParallelDownloadSegments, 1, 32);
var useMultiSegment = requestedSegments > 1 var useMultiSegment = requestedSegments > 1
&& job.ExpectedSize >= MultiSegmentMinSize && job.ExpectedSize >= MultiSegmentMinSize
&& (existing is null || existing.Segments.Count > 0); && (existing is null || existing.Segments.Count > 0);

View File

@@ -177,6 +177,21 @@ public static class Strings
$"تعذر تحميل {url}\n\n{detail}\n\nتأكد من تشغيل XAMPP وأن العنوان صحيح (الإعدادات → متقدم)." $"تعذر تحميل {url}\n\n{detail}\n\nتأكد من تشغيل XAMPP وأن العنوان صحيح (الإعدادات → متقدم)."
); );
public static string SettingsParallelSegments => T(
"Connexions parallèles pour les téléchargements (1-32, défaut 16)",
"Parallel connections for downloads (1-32, default 16)",
"下载的并行连接数 (1-32默认 16)",
"การเชื่อมต่อแบบขนานสำหรับการดาวน์โหลด (1-32, ค่าเริ่มต้น 16)",
"اتصالات متوازية للتنزيلات (1-32، الافتراضي 16)"
);
public static string SettingsParallelSegmentsHint => T(
"Plus de connexions = débit pic plus élevé et queue de fin de DL plus courte. Au-delà de 16 le serveur peut throttler.",
"More connections = higher peak throughput and shorter end-of-DL tail. Above 16 the server may throttle.",
"更多连接 = 更高的峰值吞吐量和更短的下载尾部。超过 16 服务器可能会限速。",
"การเชื่อมต่อมากขึ้น = ปริมาณงานสูงสุดมากขึ้นและส่วนท้ายของการดาวน์โหลดสั้นลง สูงกว่า 16 เซิร์ฟเวอร์อาจจำกัด",
"اتصالات أكثر = إنتاجية ذروة أعلى وذيل تنزيل أقصر. فوق 16 قد يقيّد الخادم."
);
public static string SettingsAdvanced => T( public static string SettingsAdvanced => T(
"▸ PARAMÈTRES AVANCÉS (serveur, installation, base de données, logs)", "▸ PARAMÈTRES AVANCÉS (serveur, installation, base de données, logs)",
"▸ ADVANCED SETTINGS (server, installation, database, logs)", "▸ ADVANCED SETTINGS (server, installation, database, logs)",

View File

@@ -16,9 +16,13 @@ public sealed class LocalConfig
/// <summary> /// <summary>
/// Nombre de connexions HTTP parallèles utilisées pour télécharger un build. /// Nombre de connexions HTTP parallèles utilisées pour télécharger un build.
/// 1 = comportement legacy single-connection. 4-8 contourne le throttling /// 1 = comportement legacy single-connection. 4-8 contourne le throttling
/// per-connection d'Apache/OVH mutualisé. 8 est un bon défaut. /// per-connection d'Apache/OVH mutualisé. 16 = défaut depuis v0.19 pour
/// raccourcir la « queue » en fin de DL : avec 8 segments sur 14 Go, le
/// dernier segment doit finir seul ~1.75 Go (visible comme un effondrement
/// du débit en fin de course) ; avec 16, c'est ~875 Mo. Push à 24/32 si
/// ton serveur l'accepte (OVH mutualisé tient bien jusqu'à 16-24).
/// </summary> /// </summary>
public int ParallelDownloadSegments { get; set; } = 8; public int ParallelDownloadSegments { get; set; } = 16;
/// <summary> /// <summary>
/// Si false, ignore la vérification SHA-256 même si le manifest l'exige. /// Si false, ignore la vérification SHA-256 même si le manifest l'exige.

View File

@@ -8,7 +8,7 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<AssemblyName>PS_Launcher.Updater</AssemblyName> <AssemblyName>PS_Launcher.Updater</AssemblyName>
<RootNamespace>PSLauncher.Updater</RootNamespace> <RootNamespace>PSLauncher.Updater</RootNamespace>
<Version>0.18.0</Version> <Version>0.19.0</Version>
<ApplicationIcon>..\PSLauncher.App\Resources\favicon.ico</ApplicationIcon> <ApplicationIcon>..\PSLauncher.App\Resources\favicon.ico</ApplicationIcon>
<Company>ASTERION VR</Company> <Company>ASTERION VR</Company>
<Copyright>© 2026 ASTERION VR — All rights reserved</Copyright> <Copyright>© 2026 ASTERION VR — All rights reserved</Copyright>