using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ZB.MOM.WW.CBDDC.Core.Network; using ZB.MOM.WW.CBDDC.Network; using ZB.MOM.WW.CBDDC.Network.Security; using ZB.MOM.WW.CBDDC.Persistence.Lmdb; using ZB.MOM.WW.CBDDC.Persistence.Surreal; using ZB.MOM.WW.CBDDC.Sample.Console; namespace ZB.MOM.WW.CDBBC.E2E.Benchmark.Tests; internal sealed class BenchmarkPeerNode : IAsyncDisposable { private readonly ICBDDCNode _node; private readonly ServiceProvider _serviceProvider; private readonly string _workDir; private bool _started; /// /// Gets the active EF/Core context for generated users. /// public SampleDbContext Context { get; } /// /// Creates and initializes a benchmark peer node. /// /// Service provider containing node dependencies. /// Benchmark node abstraction. /// Live data context used by the benchmark. /// Temporary working directory for this node. private BenchmarkPeerNode( ServiceProvider serviceProvider, ICBDDCNode node, SampleDbContext context, string workDir) { _serviceProvider = serviceProvider; _node = node; Context = context; _workDir = workDir; } /// /// Creates and starts a benchmark peer node from configuration. /// /// Unique peer identifier. /// Local TCP port for the node. /// Authentication token shared across peers. /// Known peers to connect to at startup. public static BenchmarkPeerNode Create( string nodeId, int tcpPort, string authToken, IReadOnlyList knownPeers) { string workDir = Path.Combine(Path.GetTempPath(), $"cbddc-benchmark-{nodeId}-{Guid.NewGuid():N}"); Directory.CreateDirectory(workDir); string dbPath = Path.Combine(workDir, "node.rocksdb"); string databaseName = nodeId.Replace("-", "_", StringComparison.Ordinal); var configurationProvider = new StaticPeerNodeConfigurationProvider(new PeerNodeConfiguration { NodeId = nodeId, TcpPort = tcpPort, AuthToken = authToken, KnownPeers = knownPeers.ToList(), RetryDelayMs = 25, RetryAttempts = 5 }); var services = new ServiceCollection(); services.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Warning)); services.AddSingleton(configurationProvider); services.AddSingleton(configurationProvider); services.AddSingleton(); services.AddSingleton(); bool useLmdb = GetBoolEnv("CBDDC_BENCH_USE_LMDB", defaultValue: true); bool dualWrite = GetBoolEnv("CBDDC_BENCH_DUAL_WRITE", defaultValue: true); bool preferLmdbReads = GetBoolEnv("CBDDC_BENCH_PREFER_LMDB_READS", defaultValue: true); bool enableShadowValidation = GetBoolEnv("CBDDC_BENCH_SHADOW_READ_VALIDATE", defaultValue: false); int reconcileIntervalMs = GetIntEnv("CBDDC_BENCH_RECONCILE_INTERVAL_MS", defaultValue: 0); var registration = services.AddCBDDCCore() .AddCBDDCSurrealEmbedded(_ => new CBDDCSurrealEmbeddedOptions { Endpoint = "rocksdb://local", DatabasePath = dbPath, Namespace = "cbddc_benchmark", Database = databaseName, Cdc = new CBDDCSurrealCdcOptions { Enabled = true, ConsumerId = $"{nodeId}-benchmark", PollingInterval = TimeSpan.FromMilliseconds(50), EnableLiveSelectAccelerator = true } }); if (useLmdb) registration.AddCBDDCLmdbOplog( _ => new LmdbOplogOptions { EnvironmentPath = Path.Combine(workDir, "oplog-lmdb"), MapSizeBytes = 256L * 1024 * 1024, MaxDatabases = 16, PruneBatchSize = 512 }, flags => { flags.UseLmdbOplog = true; flags.DualWriteOplog = dualWrite; flags.PreferLmdbReads = preferLmdbReads; flags.EnableReadShadowValidation = enableShadowValidation; flags.ReconciliationInterval = TimeSpan.FromMilliseconds(Math.Max(0, reconcileIntervalMs)); }); registration.AddCBDDCNetwork(false); // Benchmark runs use explicit known peers; disable UDP discovery and handshake overhead. services.AddSingleton(); services.AddSingleton(); ServiceProvider provider = services.BuildServiceProvider(); ICBDDCNode node = provider.GetRequiredService(); SampleDbContext context = provider.GetRequiredService(); return new BenchmarkPeerNode(provider, node, context, workDir); } /// /// Starts the node asynchronously. /// public async Task StartAsync() { if (_started) return; await _node.Start(); _started = true; } /// /// Stops the node asynchronously. /// public async Task StopAsync() { if (!_started) return; try { await _node.Stop(); } catch (ObjectDisposedException) { } catch (AggregateException ex) when (ex.InnerExceptions.All(e => e is ObjectDisposedException)) { } _started = false; } /// /// Inserts or updates a user record. /// /// User payload. public async Task UpsertUserAsync(User user) { await Context.Users.UpdateAsync(user); await Context.SaveChangesAsync(); } /// /// Returns whether a user identifier exists. /// /// Target user identifier. public bool ContainsUser(string userId) { return Context.Users.Find(u => u.Id == userId).Any(); } /// /// Counts user identifiers matching the provided prefix. /// /// User identifier prefix. public int CountUsersWithPrefix(string prefix) { return Context.Users.FindAll().Count(u => u.Id.StartsWith(prefix, StringComparison.Ordinal)); } /// /// Disposes the node and any unmanaged resources. /// public async ValueTask DisposeAsync() { try { await StopAsync(); } finally { _serviceProvider.Dispose(); TryDeleteDirectory(_workDir); } } private static void TryDeleteDirectory(string path) { if (!Directory.Exists(path)) return; for (var attempt = 0; attempt < 5; attempt++) try { Directory.Delete(path, true); return; } catch when (attempt < 4) { Thread.Sleep(50); } } private static bool GetBoolEnv(string key, bool defaultValue) { string? raw = Environment.GetEnvironmentVariable(key); if (string.IsNullOrWhiteSpace(raw)) return defaultValue; if (bool.TryParse(raw, out bool parsed)) return parsed; return defaultValue; } private static int GetIntEnv(string key, int defaultValue) { string? raw = Environment.GetEnvironmentVariable(key); if (string.IsNullOrWhiteSpace(raw)) return defaultValue; if (int.TryParse(raw, out int parsed)) return parsed; return defaultValue; } private sealed class PassiveDiscoveryService : IDiscoveryService { /// /// Gets the current list of active peers. /// public IEnumerable GetActivePeers() { return Array.Empty(); } /// /// Starts discovery. /// public Task Start() { return Task.CompletedTask; } /// /// Stops discovery. /// public Task Stop() { return Task.CompletedTask; } } }