feat(command): Add configurable timeout options for command execution#1773
feat(command): Add configurable timeout options for command execution#1773
Conversation
SummaryAdds configurable timeout options for command execution, replacing the hardcoded 30-second graceful shutdown timeout with configurable Timeout and GracefulShutdownTimeout properties. Critical IssuesNone found ✅ Suggestions1. Resource Disposal Order (Minor)In Command.cs:271-278, the disposal order could potentially cause issues. The timeoutCancellationToken is disposed before linkedCancellationToken, but linkedCancellationToken references it. While this likely works due to implementation details, it would be more defensive to reverse the disposal order by declaring linkedCancellationToken first. 2. Redundant CancellationTokenSource CreationWhen Timeout is null, a new CancellationTokenSource is created (Command.cs:274) but never used for cancellation - it just gets linked with the existing cancellationToken. This creates unnecessary allocations when no timeout is specified. However, the current approach favors code clarity over micro-optimization, which is reasonable. Previous Review StatusUnable to retrieve previous comments due to API token scope limitations. Verdict✅ APPROVE - No critical issues. The implementation is sound and addresses the feature request properly. The suggestions are minor optimizations that don't affect correctness. |
There was a problem hiding this comment.
Pull request overview
This PR adds configurable timeout options for command execution to ModularPipelines. It introduces two new properties to CommandLineOptions that allow users to control command execution timeouts and graceful shutdown behavior, replacing the previously hardcoded 30-second graceful shutdown timeout.
Key changes:
- Adds
Timeoutproperty (nullable TimeSpan) to specify maximum command execution time - Adds
GracefulShutdownTimeoutproperty (TimeSpan) with a 30-second default for configurable graceful termination - Updates command execution logic to use these configurable values with linked cancellation token sources
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/ModularPipelines/Options/CommandLineOptions.cs |
Adds two new timeout-related properties with comprehensive XML documentation |
src/ModularPipelines/Context/Command.cs |
Implements timeout functionality using linked cancellation token sources and applies configurable graceful shutdown timeout |
.gitignore |
Updates gitignore entry for worktrees directory (cosmetic change) |
| // Create a timeout cancellation token if Timeout is specified | ||
| using var timeoutCancellationToken = options.Timeout.HasValue | ||
| ? new CancellationTokenSource(options.Timeout.Value) | ||
| : new CancellationTokenSource(); | ||
|
|
||
| // Link the timeout token with the passed cancellation token | ||
| using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource( | ||
| timeoutCancellationToken.Token, cancellationToken); |
There was a problem hiding this comment.
When Timeout is not set (null), this creates a CancellationTokenSource that never cancels. This unnecessarily creates a linked token source that combines the never-cancelling timeout token with the actual cancellation token.
Consider only creating the timeout cancellation token source when Timeout has a value, and only creating the linked token source when needed. This simplifies the logic and avoids unnecessary object allocations when no timeout is specified.
| // Create a timeout cancellation token if Timeout is specified | |
| using var timeoutCancellationToken = options.Timeout.HasValue | |
| ? new CancellationTokenSource(options.Timeout.Value) | |
| : new CancellationTokenSource(); | |
| // Link the timeout token with the passed cancellation token | |
| using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource( | |
| timeoutCancellationToken.Token, cancellationToken); | |
| // Create a timeout CancellationTokenSource only if Timeout is specified | |
| using var timeoutCancellationToken = options.Timeout.HasValue | |
| ? new CancellationTokenSource(options.Timeout.Value) | |
| : null; | |
| // Link the timeout token (if any) with the passed cancellation token | |
| using var linkedCancellationToken = timeoutCancellationToken != null | |
| ? CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationToken.Token, cancellationToken) | |
| : CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |
| public TimeSpan? Timeout { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the time to wait for graceful shutdown before forcefully terminating the process. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para>When a command is cancelled (either via <see cref="Timeout"/> or an external cancellation token), | ||
| /// the process is first asked to terminate gracefully. If it does not terminate within this duration, | ||
| /// it will be forcefully killed.</para> | ||
| /// <para>Default is 30 seconds.</para> | ||
| /// </remarks> | ||
| public TimeSpan GracefulShutdownTimeout { get; init; } = TimeSpan.FromSeconds(30); |
There was a problem hiding this comment.
The new Timeout and GracefulShutdownTimeout properties lack test coverage. Based on the test structure in this repository (e.g., CommandTests.cs), tests should be added to verify:
- Command execution respects the Timeout value and cancels when exceeded
- Command execution with custom GracefulShutdownTimeout terminates appropriately
- Command execution without Timeout (null) runs to completion normally
The existing CommandEchoTimeoutModule test doesn't actually test the new Timeout property on CommandLineOptions.
90acea4 to
1f9ad8d
Compare
SummaryThis PR adds configurable timeout options to command execution, replacing the hardcoded 30-second graceful shutdown timeout with configurable values. Critical Issues1. Resource Leak and Unnecessary Allocation (src/ModularPipelines/Context/Command.cs)Issue: The code creates a Current code: using var timeoutCancellationToken = options.Timeout.HasValue
? new CancellationTokenSource(options.Timeout.Value)
: new CancellationTokenSource(); // ← Creates unnecessary object when no timeoutProblem: When Recommended fix: // Only create timeout token if Timeout is specified
CancellationTokenSource? timeoutCancellationToken = options.Timeout.HasValue
? new CancellationTokenSource(options.Timeout.Value)
: null;
// Link tokens only if we have a timeout, otherwise use original token
using var linkedCancellationToken = timeoutCancellationToken != null
? CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationToken.Token, cancellationToken)
: null;
var effectiveToken = linkedCancellationToken?.Token ?? cancellationToken;
// Dispose timeout token if created
timeoutCancellationToken?.Dispose();Or even simpler, if timeout exists: CancellationToken effectiveToken = cancellationToken;
CancellationTokenSource? timeoutCts = null;
CancellationTokenSource? linkedCts = null;
if (options.Timeout.HasValue)
{
timeoutCts = new CancellationTokenSource(options.Timeout.Value);
linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken);
effectiveToken = linkedCts.Token;
}
try
{
// ... use effectiveToken ...
}
finally
{
linkedCts?.Dispose();
timeoutCts?.Dispose();
}2. Disposal Pattern IssueIssue: The current The current code has the using statements in the right order, but it's fragile. If Suggestions1. Add XML documentation exampleConsider adding a usage example in the XML docs for the /// <example>
/// <code>
/// var result = await context.Command.ExecuteCommandLineTool(
/// new CommandLineToolOptions("long-running-command")
/// {
/// Timeout = TimeSpan.FromMinutes(5)
/// });
/// </code>
/// </example>2. Consider adding testsThe PR test plan mentions testing timeout scenarios, but I don't see test files in the diff. Consider adding unit tests for:
Verdict |
1f9ad8d to
52c60ee
Compare
SummaryThis PR adds configurable timeout options for command execution: Critical Issues1. Resource leak - CancellationTokenSource not properly disposed In Command.cs:271-276, the using var timeoutCancellationToken = options.ExecutionTimeout.HasValue
? new CancellationTokenSource(options.ExecutionTimeout.Value)
: new CancellationTokenSource();The problem: When Solution: Only create the timeout token when needed: using var timeoutCancellationToken = options.ExecutionTimeout.HasValue
? new CancellationTokenSource(options.ExecutionTimeout.Value)
: null;
using var linkedCancellationToken = timeoutCancellationToken != null
? CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationToken.Token, cancellationToken)
: CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);2. Inconsistent property naming The PR description mentions a /// <para>When a command is cancelled (either via <see cref="Timeout"/> or an external cancellation token),This should reference Suggestions
Verdict |
52c60ee to
ec7acdc
Compare
SummaryAdds configurable execution timeout and graceful shutdown timeout to command execution. Critical Issues1. Resource leak in conditional CancellationTokenSource creation // Current implementation (line 278-279):
using var linkedCancellationToken = timeoutCancellationToken != null
? CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationToken.Token, cancellationToken)
: CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); // ← Unnecessary allocationSuggested fix: using var linkedCancellationToken = options.ExecutionTimeout.HasValue
? CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationToken!.Token, cancellationToken)
: null;
var effectiveCancellationToken = linkedCancellationToken?.Token ?? cancellationToken;Then use 2. Potential ObjectDisposedException SuggestionsMinor: .gitignore comment clarity Verdict |
…#1485) Add Timeout and GracefulShutdownTimeout properties to CommandLineOptions to allow users to configure command execution timeouts. The Timeout property sets the maximum time for command completion, while GracefulShutdownTimeout controls how long to wait before forcefully terminating (default 30 seconds). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ec7acdc to
83e30e1
Compare
SummaryAdds configurable timeout options for command execution with Critical IssuesUnnecessary CancellationTokenSource allocation when no timeout is setLocation: src/ModularPipelines/Context/Command.cs:310-312 The current implementation always creates a using var linkedCancellationToken = timeoutCancellationToken != null
? CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationToken.Token, cancellationToken)
: CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);When there's no timeout, this creates an unnecessary Suggested fix: Only create the linked token when a timeout is specified, otherwise use the original // Only create timeout token if ExecutionTimeout is specified to avoid unnecessary allocations
using var timeoutCancellationToken = options.ExecutionTimeout.HasValue
? new CancellationTokenSource(options.ExecutionTimeout.Value)
: null;
// Link the timeout token with the passed cancellation token if timeout is specified
using var linkedCancellationToken = timeoutCancellationToken != null
? CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationToken.Token, cancellationToken)
: null;
var effectiveCancellationToken = linkedCancellationToken?.Token ?? cancellationToken;
using var forcefulCancellationToken = new CancellationTokenSource();
await using var registration = effectiveCancellationToken.Register(() =>
{
try
{
if (forcefulCancellationToken.Token.CanBeCanceled)
{
forcefulCancellationToken.CancelAfter(options.GracefulShutdownTimeout);
}
}
catch (ObjectDisposedException)
{
// Ignored
}
});
try
{
var result = await command
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(standardOutputStringBuilder))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(standardErrorStringBuilder))
.WithValidation(CommandResultValidation.None)
.ExecuteAsync(forcefulCancellationToken.Token, effectiveCancellationToken).ConfigureAwait(false);
// ... rest of the code
}This ensures that when Verdict |
Summary
Timeoutproperty (TimeSpan?) toCommandLineOptionsfor setting maximum command execution timeGracefulShutdownTimeoutproperty (TimeSpan) with 30-second default for configuring graceful termination wait timeCommand.csto use these configurable timeout values instead of hardcoded 30-second graceful shutdownCloses #1485
Test plan
🤖 Generated with Claude Code