From f1451b547c00dd520ec5e5e2dcaedc69aae1e384 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 6 Jul 2026 10:03:44 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20Startet=20nicht=20/=20MySQL-Timeout=20oh?= =?UTF-8?q?ne=20UI=20(Config-=20&=20Startup-H=C3=A4rtung)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ursachen (App startete aus bin/ bzw. per Doppelklick ohne UI): 1. appsettings.Local.json (MySQL-Connection) wurde NICHT ins Output kopiert und nur relativ zum Arbeitsverzeichnis geladen -> leere Connection -> Pomelo fiel auf localhost:3306 zurueck -> "Connect Timeout expired". 2. ServerVersion.AutoDetect(conn) oeffnet beim Options-Bau eine blockierende DB-Verbindung -> haengt/crasht den Start, wenn die DB nicht erreichbar ist. 3. CopyTradingEngine.StartAsync lud den MarketCache ungeschuetzt -> DB-Fehler riss AppHost.Start() ab, bevor die UI erschien. Fixes: - csproj: appsettings.Local.json mit ins Output kopieren (CopyToOutputDirectory). - Program: UseContentRoot(AppContext.BaseDirectory) -> Config wird immer neben der EXE gesucht (Main + Smoke-Test). - ServerVersion fest gepinnt: DatabaseServerVersion.Value = MariaDB 11.8.6 (wie am Server erkannt); AddCorePersistence + CopyTradingModule nutzen sie statt AutoDetect. Kein blockierender Connect mehr beim Start. - CopyTradingEngine-Preload in try/catch: DB-Fehler bricht den Start nicht mehr ab. - Neuer Diagnose-CLI --db-version (gibt @@version aus). Verifiziert: --smoke-ui aus fremdem Arbeitsverzeichnis laeuft gruen (3 Accounts / 32 Trader hydriert, alle Views + Launcher OK). Co-Authored-By: Claude Opus 4.8 --- PolyTrader.App.csproj | 5 + Program.cs | 39 ++++++ .../Configuration/DatabaseOptions.cs | 14 ++ .../ServiceCollectionExtensions.cs | 2 +- .../CopyTradingModule.cs | 3 +- .../Services/CopyTradingEngine.cs | 9 +- .../Ui/AccountSettingsView.resx | 120 ++++++++++++++++++ 7 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 src/PolyTrader.Modules.CopyTrading/Ui/AccountSettingsView.resx diff --git a/PolyTrader.App.csproj b/PolyTrader.App.csproj index 5b762ff..c3ca23e 100644 --- a/PolyTrader.App.csproj +++ b/PolyTrader.App.csproj @@ -32,6 +32,11 @@ PreserveNewest + + + PreserveNewest + diff --git a/Program.cs b/Program.cs index 8309d27..d6951a1 100644 --- a/Program.cs +++ b/Program.cs @@ -48,6 +48,13 @@ internal static class Program return; } + // Diagnose: gibt die MySQL-Serverversion aus (für das ServerVersion-Pinning). Kein UI. + if (args.Length > 0 && string.Equals(args[0], "--db-version", StringComparison.OrdinalIgnoreCase)) + { + RunDbVersion(); + return; + } + ApplicationConfiguration.Initialize(); var modules = new System.Collections.Generic.List @@ -56,6 +63,9 @@ internal static class Program }; AppHost = Host.CreateDefaultBuilder() + // Config immer neben der EXE suchen (nicht im Arbeitsverzeichnis), damit die App + // auch beim Start aus bin/ oder per Doppelklick ihre appsettings findet. + .UseContentRoot(AppContext.BaseDirectory) // Host.CreateDefaultBuilder lädt appsettings.Local.json NICHT (nur appsettings.json // + appsettings.{Environment}.json). Die gitignorierte Local-Datei hält aber die // MySQL-Connection – daher hier explizit ergänzen, sonst bleibt sie leer. @@ -252,6 +262,34 @@ internal static class Program Services.ConfigMigrator.VerifyMySql(mySql); } + private static void RunDbVersion() + { + var config = new Microsoft.Extensions.Configuration.ConfigurationBuilder() + .SetBasePath(System.IO.Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true) + .AddJsonFile("appsettings.Local.json", optional: true) + .AddEnvironmentVariables() + .Build(); + + var conn = config["Database:MySqlConnectionString"] ?? string.Empty; + if (string.IsNullOrWhiteSpace(conn)) + { + Console.WriteLine("FEHLER: Database:MySqlConnectionString fehlt (appsettings.Local.json)."); + return; + } + + try + { + using var c = new MySqlConnector.MySqlConnection(conn); + c.Open(); + Console.WriteLine($"ServerVersion: {c.ServerVersion}"); + } + catch (Exception ex) + { + Console.WriteLine($"FEHLER: {ex.GetType().Name}: {ex.Message}"); + } + } + /// /// Headless-Smoke-Test: baut einen minimalen Host (Persistenz + State + Modul-Registrierung /// + Shell/Launcher), hydriert den State aus MySQL und konstruiert jede registrierte View @@ -265,6 +303,7 @@ internal static class Program var modules = new System.Collections.Generic.List { new CopyTradingModule() }; using var host = Host.CreateDefaultBuilder() + .UseContentRoot(AppContext.BaseDirectory) .ConfigureAppConfiguration((context, config) => config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: false)) .ConfigureServices((context, services) => diff --git a/src/PolyTrader.Core/Configuration/DatabaseOptions.cs b/src/PolyTrader.Core/Configuration/DatabaseOptions.cs index 7c074d1..7951631 100644 --- a/src/PolyTrader.Core/Configuration/DatabaseOptions.cs +++ b/src/PolyTrader.Core/Configuration/DatabaseOptions.cs @@ -1,3 +1,6 @@ +using System; +using Microsoft.EntityFrameworkCore; + namespace PolyTrader.Core.Configuration { /// @@ -11,4 +14,15 @@ namespace PolyTrader.Core.Configuration /// MySQL-Connection-String (aus gitignorierter appsettings.Local.json). public string MySqlConnectionString { get; set; } = string.Empty; } + + /// + /// Feste Ziel-Server-Version (MariaDB 11.8.6, wie am Server erkannt). Bewusst gepinnt statt + /// ServerVersion.AutoDetect: AutoDetect öffnet beim Bau der DbContext-Optionen eine + /// blockierende DB-Verbindung – ist die DB langsam/nicht erreichbar, hängt/crasht der Start, + /// bevor die UI erscheint. Mit fester Version startet die App unabhängig von der DB. + /// + public static class DatabaseServerVersion + { + public static ServerVersion Value => new MariaDbServerVersion(new Version(11, 8, 6)); + } } diff --git a/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs index e863b43..40b99fd 100644 --- a/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -17,7 +17,7 @@ namespace PolyTrader.Core.DependencyInjection public static IServiceCollection AddCorePersistence(this IServiceCollection services, DatabaseOptions options) { var conn = options.MySqlConnectionString; - services.AddDbContextFactory(o => o.UseMySql(conn, ServerVersion.AutoDetect(conn))); + services.AddDbContextFactory(o => o.UseMySql(conn, DatabaseServerVersion.Value)); services.AddSingleton(); services.AddSingleton(); diff --git a/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs b/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs index 9699fb7..8e83513 100644 --- a/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs +++ b/src/PolyTrader.Modules.CopyTrading/CopyTradingModule.cs @@ -5,6 +5,7 @@ using System.Threading.Channels; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using PolyTrader.Core.Configuration; using PolyTrader.Core.Modularity; using PolyTrader.Modules.CopyTrading.Persistence; using PolyTrader.Modules.CopyTrading.Persistence.Ef; @@ -39,7 +40,7 @@ namespace PolyTrader.Modules.CopyTrading // Modul-Persistenz: EF Core / Pomelo / MySQL (thread-safer DbContextFactory). var conn = configuration["Database:MySqlConnectionString"] ?? string.Empty; - services.AddDbContextFactory(o => o.UseMySql(conn, ServerVersion.AutoDetect(conn))); + services.AddDbContextFactory(o => o.UseMySql(conn, DatabaseServerVersion.Value)); services.AddSingleton(); services.AddSingleton(); diff --git a/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs b/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs index eb30a0c..75a2a74 100644 --- a/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs +++ b/src/PolyTrader.Modules.CopyTrading/Services/CopyTradingEngine.cs @@ -53,6 +53,7 @@ namespace PolyTraderSharp.Services public override async Task StartAsync(CancellationToken cancellationToken) { _logger.Info("Starte Preload des MarketCache aus der Datenbank um Flaschenhälse zu vermeiden..."); + try { // Initialize cache for EVERYTHING in DB that is not closed! var activeMarkets = _marketRepo.GetActive(); @@ -62,7 +63,7 @@ namespace PolyTraderSharp.Services { if (!string.IsNullOrEmpty(md.ClobTokenIds)) { - try + try { var tokenIds = System.Text.Json.JsonSerializer.Deserialize>(md.ClobTokenIds); if (tokenIds != null) @@ -79,6 +80,12 @@ namespace PolyTraderSharp.Services } _logger.Info($"MarketCache Preload abgeschlossen: {loaded} Token herangeführt."); } + catch (Exception ex) + { + // DB nicht erreichbar o.ä.: Start NICHT abbrechen (sonst erscheint keine UI). + // Der Cache füllt sich zur Laufzeit über den MarketSync/Signale nach. + _logger.Error($"MarketCache Preload übersprungen (DB nicht erreichbar?): {ex.Message}"); + } await base.StartAsync(cancellationToken); } diff --git a/src/PolyTrader.Modules.CopyTrading/Ui/AccountSettingsView.resx b/src/PolyTrader.Modules.CopyTrading/Ui/AccountSettingsView.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/src/PolyTrader.Modules.CopyTrading/Ui/AccountSettingsView.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file