diff --git a/app/Tests/Tools/HTMLParserConcurrencyTests.cs b/app/Tests/Tools/HTMLParserConcurrencyTests.cs index d10e89b2..86600910 100644 --- a/app/Tests/Tools/HTMLParserConcurrencyTests.cs +++ b/app/Tests/Tools/HTMLParserConcurrencyTests.cs @@ -38,27 +38,16 @@ public sealed class HTMLParserConcurrencyTests // overlap for this test to mean anything, and only starting them together makes that // certain. // + // What a thread works with is handed over when it starts rather than captured. The barrier + // is disposed at the end of this method, and while the joins below make sure no thread is + // still at it by then, that is nothing one can see from inside a lambda. + // using var startSignal = new Barrier(THREAD_COUNT); var threads = new List(THREAD_COUNT); for (var threadIndex = 0; threadIndex < THREAD_COUNT; threadIndex++) { - var thread = new Thread(() => - { - startSignal.SignalAndWait(); - for (var conversion = 0; conversion < CONVERSIONS_PER_THREAD; conversion++) - { - try - { - results.Add(HTMLParser.ParseToMarkdown(html)); - } - catch (Exception exception) - { - failures.Add(exception); - } - } - }); - - thread.Start(); + var thread = new Thread(ConvertRepeatedly); + thread.Start(new ConversionRun(startSignal, html, results, failures)); threads.Add(thread); } @@ -75,6 +64,27 @@ public sealed class HTMLParserConcurrencyTests }); } + /// + /// Converts the same page over and over, once every thread has arrived at the barrier. + /// + private static void ConvertRepeatedly(object? state) + { + var run = (ConversionRun)state!; + run.StartSignal.SignalAndWait(); + + for (var conversion = 0; conversion < CONVERSIONS_PER_THREAD; conversion++) + { + try + { + run.Results.Add(HTMLParser.ParseToMarkdown(run.Html)); + } + catch (Exception exception) + { + run.Failures.Add(exception); + } + } + } + /// /// Builds a page out of the elements the reported stack traces named. /// @@ -118,4 +128,9 @@ public sealed class HTMLParserConcurrencyTests return string.Concat(Enumerable.Repeat(BLOCK, 20)); } + + /// + /// Everything one thread of this test needs, so that it is passed rather than captured. + /// + private sealed record ConversionRun(Barrier StartSignal, string Html, ConcurrentBag Results, ConcurrentBag Failures); } \ No newline at end of file