From b2e7037446e27f976677e9c3911fe69ff385d801 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Fri, 29 May 2026 21:21:39 +0200 Subject: [PATCH 1/3] test changes --- src/tools/LogRotator/LogRotator.cs | 172 +++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/src/tools/LogRotator/LogRotator.cs b/src/tools/LogRotator/LogRotator.cs index 4b69f778..516e4312 100644 --- a/src/tools/LogRotator/LogRotator.cs +++ b/src/tools/LogRotator/LogRotator.cs @@ -35,9 +35,23 @@ Console.WriteLine($"Control characters in output: {(includeControlChars ? "ENABLED" : "disabled")}"); Console.WriteLine("Press ENTER to perform a rotation (with oldest file deletion)."); Console.WriteLine("Press A to append a single live line (no rotation) for tail testing."); +Console.WriteLine("Press D to delete the log, wait, then recreate it AND start a 25 lines/s"); +Console.WriteLine(" background writer (repro for issue #568). Press D again to delete mid-stream."); +Console.WriteLine("Press F to flicker: delete, wait, briefly recreate, delete again mid-reload,"); +Console.WriteLine(" wait, then recreate + writer. Tries to land LogExpert's new reader's"); +Console.WriteLine(" ReadFiles inside a deletion window."); Console.WriteLine("Press Q to quit."); var rotationCount = 0; +var delayedDeleteCount = 0; +var flickerCount = 0; +const int delayedDeleteSeconds = 5; +const int liveWriterDelayMs = 40; // ~25 lines/s +const int flickerInitialAbsentMs = 5000; +const int flickerBriefVisibleMs = 200; +const int flickerSecondAbsentMs = 2500; +CancellationTokenSource? liveWriterCts = null; +Task? liveWriterTask = null; while (true) { @@ -45,6 +59,7 @@ if (key.Key == ConsoleKey.Q) { + StopLiveWriter(); break; } @@ -54,6 +69,20 @@ continue; } + if (key.Key == ConsoleKey.D) + { + delayedDeleteCount++; + DelayedDelete(Path.Join(baseDir, safeBaseName), delayedDeleteCount, delayedDeleteSeconds); + continue; + } + + if (key.Key == ConsoleKey.F) + { + flickerCount++; + FlickerRepro(Path.Join(baseDir, safeBaseName), flickerCount); + continue; + } + if (key.Key != ConsoleKey.Enter) { continue; @@ -131,6 +160,149 @@ void AppendLiveLine(string path) Console.WriteLine($" Appended live line to {name} ({new FileInfo(path).Length} bytes total)"); } +// Repro path for issue #568: stop any background writer, delete the file and +// keep it absent long enough (> LogExpert's 1.25s OpenStream retry budget) for +// the watcher to enter FileNotFound state, then recreate it AND start a +// continuous background writer (~25 lines/s). The next D press will delete the +// file while the writer is actively appending — that mid-stream delete is the +// scenario the reporter describes. +void DelayedDelete(string path, int iteration, int delaySeconds) +{ + var name = Path.GetFileName(path); + Console.WriteLine($"\n--- Delete + delay + recreate #{iteration} ---"); + + StopLiveWriter(); + + if (File.Exists(path)) + { + File.Delete(path); + Console.WriteLine($" Deleted: {name}"); + } + else + { + Console.WriteLine($" {name} was already missing."); + } + + Console.WriteLine($" File absent. Waiting {delaySeconds}s so LogExpert enters FileNotFound state..."); + for (var i = delaySeconds; i > 0; i--) + { + Console.Write($"\r Countdown: {i}s "); + Thread.Sleep(1000); + } + Console.WriteLine("\r Countdown: done."); + + WriteLogFile(path, fileId: 900 + iteration); + Console.WriteLine($" Recreated {name} with {linesPerFile} lines ({new FileInfo(path).Length} bytes)."); + StartLiveWriter(path, iteration); + Console.WriteLine($" Background writer started (~{1000 / liveWriterDelayMs} lines/s)."); + Console.WriteLine(" Watch LogExpert: lines should keep appearing."); + Console.WriteLine(" If they do NOT, the bug is reproduced. Press D again to delete mid-stream."); +} + +// Tighter race than DelayedDelete: after the file has been absent long enough +// for LogExpert to enter FileNotFound, we briefly recreate it (so the watcher +// fires OnRespawned and the LogWindow schedules a Reload), then delete it +// again before the new LogfileReader's first ReadFiles completes its +// OpenStream retries (5 x 250ms = 1.25s). If the hypothesis about issue #568 +// is correct, the new reader's ReadFiles catches IOException, _isDeleted is +// set, ReportLoadingFinished is skipped, and FileSizeChanged never gets wired +// up. After we recreate the file for real and start the writer, those writes +// should fail to propagate. +void FlickerRepro(string path, int iteration) +{ + var name = Path.GetFileName(path); + Console.WriteLine($"\n--- Flicker repro #{iteration} ---"); + + StopLiveWriter(); + + if (File.Exists(path)) + { + File.Delete(path); + Console.WriteLine($" Deleted: {name}"); + } + + Console.WriteLine($" Phase 1: file absent for {flickerInitialAbsentMs / 1000.0:0.0}s (LogExpert -> FileNotFound)"); + Thread.Sleep(flickerInitialAbsentMs); + + WriteLogFile(path, fileId: 700 + iteration); + Console.WriteLine($" Phase 2: briefly visible ({flickerBriefVisibleMs}ms) - LogExpert schedules a Reload"); + Thread.Sleep(flickerBriefVisibleMs); + + File.Delete(path); + Console.WriteLine($" Phase 3: deleted again, absent {flickerSecondAbsentMs / 1000.0:0.0}s"); + Console.WriteLine($" (exceeds 1.25s OpenStream retry budget - new reader's ReadFiles should fail)"); + Thread.Sleep(flickerSecondAbsentMs); + + WriteLogFile(path, fileId: 750 + iteration); + Console.WriteLine($" Phase 4: recreated with {linesPerFile} lines, starting writer."); + StartLiveWriter(path, iteration); + Console.WriteLine(" Watch LogExpert. If row count freezes, bug reproduced."); +} + +void StartLiveWriter(string path, int iteration) +{ + StopLiveWriter(); + liveWriterCts = new CancellationTokenSource(); + var token = liveWriterCts.Token; + var fileId = 800 + iteration; + liveWriterTask = Task.Run(() => LiveWriterLoop(path, fileId, token)); +} + +void StopLiveWriter() +{ + if (liveWriterCts == null) + { + return; + } + + liveWriterCts.Cancel(); + try + { + liveWriterTask?.Wait(TimeSpan.FromSeconds(2)); + } + catch (AggregateException) + { + // expected: task cancelled + } + + liveWriterCts.Dispose(); + liveWriterCts = null; + liveWriterTask = null; +} + +void LiveWriterLoop(string path, int fileId, CancellationToken token) +{ + var name = Path.GetFileName(path); + var lineIndex = 0; + while (!token.IsCancellationRequested) + { + try + { + using var fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); + using var writer = new StreamWriter(fs, Encoding.UTF8); + writer.WriteLine(BuildLine(fileId, ++lineIndex, name)); + } + catch (IOException) + { + // file may be momentarily inaccessible during a D-press; just keep + // trying so writes resume once it reappears. + } + + try + { + Task.Delay(liveWriterDelayMs, token).Wait(token); + } + catch (OperationCanceledException) + { + return; + } + catch (AggregateException) + { + return; + } + } +} + string BuildLine(int fileId, int lineIndex, string fileName) { var baseText = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [INFO] File#{fileId:D3} Line {lineIndex:D3} - {fileName} - Sample log message"; From cddf257cf4a25b4ffffc0b5e7c2a51bf2630eb20 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Mon, 1 Jun 2026 11:28:23 +0200 Subject: [PATCH 2/3] update settings --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5edcd1f3..bbde8a53 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,7 @@ "matchCommandLine": true }, "git rev-parse": true - } + }, + "dotnet.preferCSharpExtension": true, + "dotnet.defaultSolution": "src/LogExpert.sln" } \ No newline at end of file From edd5a666909e1e8b4aa8cd1aa8be25bf05af5e16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 1 Jun 2026 09:31:41 +0000 Subject: [PATCH 3/3] chore: update plugin hashes [skip ci] --- .../PluginHashGenerator.Generated.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index e387aad4..1849aa9e 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-05-28 19:52:00 UTC + /// Generated: 2026-06-01 09:31:40 UTC /// Configuration: Release /// Plugin count: 21 /// @@ -18,27 +18,27 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "EDF4B48F71CF2192A99F63B9FE493661521A2E671D9185CCEB181B513DF0C1A5", + ["AutoColumnizer.dll"] = "1E5C3388943F4EB34382324E6A2C54F93C89B88CFE1D69ACF1203FB7416E672F", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "397CCA331A6FD1687C8CBDF78DF6DF9C080B7A16860C511F2DC0E275A5257C4F", - ["CsvColumnizer.dll (x86)"] = "397CCA331A6FD1687C8CBDF78DF6DF9C080B7A16860C511F2DC0E275A5257C4F", - ["DefaultPlugins.dll"] = "58B2ED626556C1B0E1B02D62DF0A0658618FA05B309BFAA2C2582B180594B7E6", - ["FlashIconHighlighter.dll"] = "0861244E3F7B52D44B8487732724E277658143E5940AACFF977AA74048FF1B9D", - ["GlassfishColumnizer.dll"] = "B75D7808007E9CA1235E282B07431C70A16D378B647B58BAEE605075D7B8FF08", - ["JsonColumnizer.dll"] = "A469FEC6C1E6F7B209D960CE7C3416CF80EFD5A2A062BCAD7B0E8AE6A3988ABF", - ["JsonCompactColumnizer.dll"] = "F6735973634AE8A986BC9FD8B89F8817D36458E488B6A7BCD2320883DD4BA5BC", - ["Log4jXmlColumnizer.dll"] = "D148D1FEC9A0152714AAE8CBAB0A9BDDF9ACFBFED3FF0CEB8D5966908DC24760", - ["LogExpert.Resources.dll"] = "A25FDA182EECF5BCC828C0C3F145FD774530980E9C72AC17591043677FA9E201", + ["CsvColumnizer.dll"] = "0DB8949CCFB20468D5C934474EEC98B3BD1AD0802D2F79F9DF9013DAF2A037C9", + ["CsvColumnizer.dll (x86)"] = "0DB8949CCFB20468D5C934474EEC98B3BD1AD0802D2F79F9DF9013DAF2A037C9", + ["DefaultPlugins.dll"] = "FE68CE75E429D0F29ABB2D221A4BBBC16AD6C06B77FB2B709134591F276DE9DF", + ["FlashIconHighlighter.dll"] = "A8C733BBA980A364B3739EFBF866E85E166C15B79A6B704456C7F92884BECB27", + ["GlassfishColumnizer.dll"] = "86D49BC1EAC7F843893134F7F5B64BE37A94C914F389DB56598DBC670D849835", + ["JsonColumnizer.dll"] = "6A6B27428F647DF29D03F4F17AED89DDF1FF24636B59644FBD12FB3D92A26F18", + ["JsonCompactColumnizer.dll"] = "04A7169C087181B49189AB8DD9C9FB260189EB1CF394452EA02987FFE6934794", + ["Log4jXmlColumnizer.dll"] = "301E7805F9BA5BA211256119636B7A8D34848475C5B7885737FD8E61CB1B5233", + ["LogExpert.Resources.dll"] = "A2AFABDCFDA0B558426706336C11FB8B02770383419BA1E0192FFFEBEB091A38", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", ["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", - ["RegexColumnizer.dll"] = "C0DC4D43DB02C2015A490BC4C62549D7CDD1B107C6944F20DB0B4C4285371288", - ["SftpFileSystem.dll"] = "AAD426FB4E53916B8B26F427374EA3E6706B940564F4CD0E059E69A613CBE02B", - ["SftpFileSystem.dll (x86)"] = "6FEC9B0EC9B9241ACA09999C55AD96F33FD52A2A1A14DC513D06BC42BF2C1B86", - ["SftpFileSystem.Resources.dll"] = "0F8C4D65FE7E8A79A11DF521116E36E65AC28DE732C67264A3790AD2F1E4CBB8", - ["SftpFileSystem.Resources.dll (x86)"] = "0F8C4D65FE7E8A79A11DF521116E36E65AC28DE732C67264A3790AD2F1E4CBB8", + ["RegexColumnizer.dll"] = "AC1E977392AD7A291174C357211480121C49898C2258E8608E06AF8DDA48DE7E", + ["SftpFileSystem.dll"] = "E5B7DD9D7038F68B501BD8398141FD6E8EBE9878B205D346E3C36FCD40DA9CA8", + ["SftpFileSystem.dll (x86)"] = "8B892CC370EE3E01693F282C32245B55F3D225F48D6631102EB2DC79692F762B", + ["SftpFileSystem.Resources.dll"] = "20CFD22D83D1581FF63348F3F0D3E824A73887F80BCDEB04E6AAF3E5A200C036", + ["SftpFileSystem.Resources.dll (x86)"] = "20CFD22D83D1581FF63348F3F0D3E824A73887F80BCDEB04E6AAF3E5A200C036", }; }