private Task StartCore() { _stopwatch.Start(); // Spin up a thread dedicated to outputting stats for each defined interval new Thread(() => { while (!_cts.IsCancellationRequested) { Thread.Sleep(_config.DisplayInterval); lock (Console.Out) { _aggregator.PrintCurrentResults(_stopwatch.Elapsed, showAggregatesOnly: false); } } }) { IsBackground = true }.Start(); IEnumerable <Task> workers = CreateWorkerSeeds().Select(x => RunSingleWorker(x.workerId, x.random)); return(Task.WhenAll(workers)); async Task RunSingleWorker(int workerId, Random random) { StreamCounter counter = _aggregator.GetCounters(workerId); for (long jobId = 0; !_cts.IsCancellationRequested; jobId++) { TimeSpan connectionLifetime = _config.MinConnectionLifetime + random.NextDouble() * (_config.MaxConnectionLifetime - _config.MinConnectionLifetime); TimeSpan cancellationDelay = (random.NextBoolean(probability: _config.CancellationProbability)) ? connectionLifetime * random.NextDouble() : // cancel in a random interval within the lifetime connectionLifetime + TimeSpan.FromSeconds(10); // otherwise trigger cancellation 10 seconds after expected expiry using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token); cts.CancelAfter(cancellationDelay); bool isTestCompleted = false; using var _ = cts.Token.Register(CheckForStalledConnection); try { using var client = new TcpClient(); await client.ConnectAsync(_config.ServerEndpoint.Address, _config.ServerEndpoint.Port); var stream = new CountingStream(client.GetStream(), counter); using SslStream sslStream = await EstablishSslStream(stream, random, cts.Token); await HandleConnection(workerId, jobId, sslStream, client, random, connectionLifetime, cts.Token); _aggregator.RecordSuccess(workerId); } catch (OperationCanceledException) when(cts.IsCancellationRequested) { _aggregator.RecordCancellation(workerId); } catch (Exception e) { _aggregator.RecordFailure(workerId, e); } finally { isTestCompleted = true; } async void CheckForStalledConnection() { await Task.Delay(10_000); if (!isTestCompleted) { lock (Console.Out) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Worker #{workerId} test #{jobId} has stalled, terminating the stress app."); Console.WriteLine(); Console.ResetColor(); } Environment.Exit(1); } } } } IEnumerable <(int workerId, Random random)> CreateWorkerSeeds() { // deterministically generate random instance for each individual worker Random random = new Random(_config.RandomSeed); for (int workerId = 0; workerId < _config.MaxConnections; workerId++) { yield return(workerId, random.NextRandom()); } } }
public void Setup() { wordCounter = new StreamCounter(); }