Skip to content

Commit d37738f

Browse files
thomhurstclaude
andcommitted
fix: address code review issues in immediate output feature
- Fix pause functionality in ProgressSession: progress loop now properly checks _isPaused flag and waits on _resumeSignal when paused - Remove dead code: MarkCompleted() and CompletedAtUtc from IModuleOutputBuffer and ModuleOutputBuffer (no longer needed with immediate flush) - Improve error handling in ModuleLogger.Dispose: add 30s timeout to prevent potential deadlocks, add specific exception handling for AggregateException and OperationCanceledException Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent aaafe2a commit d37738f

4 files changed

Lines changed: 39 additions & 30 deletions

File tree

src/ModularPipelines/Console/IModuleOutputBuffer.cs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ namespace ModularPipelines.Console;
1515
/// <b>Thread Safety:</b> All methods are thread-safe and can be called concurrently.
1616
/// </para>
1717
/// <para>
18-
/// <b>Flush Ordering:</b> Buffers are flushed in completion order (by CompletedAtUtc)
19-
/// to maintain logical output sequence.
18+
/// <b>Flush Behavior:</b> Buffers are flushed immediately when modules complete,
19+
/// via the OutputCoordinator which ensures ordered output.
2020
/// </para>
2121
/// </remarks>
2222
internal interface IModuleOutputBuffer
@@ -26,18 +26,6 @@ internal interface IModuleOutputBuffer
2626
/// </summary>
2727
Type ModuleType { get; }
2828

29-
/// <summary>
30-
/// Gets when the module completed (for ordering during flush).
31-
/// Null if not yet completed.
32-
/// </summary>
33-
DateTime? CompletedAtUtc { get; }
34-
35-
/// <summary>
36-
/// Records completion time for flush ordering.
37-
/// Called when the module finishes execution.
38-
/// </summary>
39-
void MarkCompleted();
40-
4129
/// <summary>
4230
/// Adds a plain string line to the buffer.
4331
/// Used for Console.WriteLine interceptions.

src/ModularPipelines/Console/ModuleOutputBuffer.cs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ internal class ModuleOutputBuffer : IModuleOutputBuffer
3131
/// <inheritdoc />
3232
public Type ModuleType { get; }
3333

34-
/// <inheritdoc />
35-
public DateTime? CompletedAtUtc { get; private set; }
36-
3734
/// <summary>
3835
/// Initializes a new buffer for the specified module type.
3936
/// </summary>
@@ -80,15 +77,6 @@ public void AddLogEvent(
8077
}
8178
}
8279

83-
/// <inheritdoc />
84-
public void MarkCompleted()
85-
{
86-
lock (_lock)
87-
{
88-
CompletedAtUtc ??= DateTime.UtcNow;
89-
}
90-
}
91-
9280
/// <inheritdoc />
9381
public void SetException(Exception exception)
9482
{
@@ -165,7 +153,7 @@ public void FlushTo(TextWriter console, IBuildSystemFormatter formatter, ILogger
165153

166154
private string FormatHeader(Exception? exception)
167155
{
168-
var duration = (CompletedAtUtc ?? DateTime.UtcNow) - _startTimeUtc;
156+
var duration = DateTime.UtcNow - _startTimeUtc;
169157
var durationStr = duration.TotalSeconds >= 60
170158
? $"{duration.TotalMinutes:F1}m"
171159
: $"{duration.TotalSeconds:F1}s";

src/ModularPipelines/Console/ProgressSession.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,27 @@ await AnsiConsole.Progress()
9898
// Keep alive until all modules complete or cancellation
9999
while (!ctx.IsFinished && !_cancellationToken.IsCancellationRequested)
100100
{
101+
// Check if we should pause for module output
102+
TaskCompletionSource? resumeSignal = null;
103+
await _pauseLock.WaitAsync().ConfigureAwait(false);
104+
try
105+
{
106+
if (_isPaused)
107+
{
108+
resumeSignal = _resumeSignal;
109+
}
110+
}
111+
finally
112+
{
113+
_pauseLock.Release();
114+
}
115+
116+
if (resumeSignal != null)
117+
{
118+
// Wait for resume signal
119+
await resumeSignal.Task.ConfigureAwait(false);
120+
}
121+
101122
await Task.Delay(100, CancellationToken.None).ConfigureAwait(false);
102123
}
103124
});

src/ModularPipelines/Logging/ModuleLogger.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,27 @@ public override void Dispose()
144144
}
145145

146146
// Flush output immediately instead of just marking completed
147-
// This blocks until the output is written
147+
// This blocks until the output is written, with a timeout to prevent deadlocks
148148
try
149149
{
150-
_outputCoordinator.EnqueueAndFlushAsync(_buffer).GetAwaiter().GetResult();
150+
var flushTask = _outputCoordinator.EnqueueAndFlushAsync(_buffer);
151+
152+
// Wait with timeout to prevent potential deadlocks
153+
// 30 seconds should be more than enough for any reasonable output
154+
if (!flushTask.Wait(TimeSpan.FromSeconds(30)))
155+
{
156+
// Timeout - output may be lost, but we can't block forever
157+
// This is a last resort safety measure
158+
}
151159
}
152-
catch
160+
catch (AggregateException)
153161
{
154162
// Best effort - don't fail disposal
155163
}
164+
catch (OperationCanceledException)
165+
{
166+
// Cancellation during shutdown is expected
167+
}
156168

157169
GC.SuppressFinalize(this);
158170
}

0 commit comments

Comments
 (0)