fix(management): 5-min Ask timeout for transport/deploy commands (arch-review S1)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -32,6 +32,31 @@ public static class ManagementEndpoints
|
|||||||
return DefaultAskTimeout;
|
return DefaultAskTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly TimeSpan DefaultLongRunningTimeout = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
/// <summary>Transport/deploy commands legitimately run for minutes (the CLI already
|
||||||
|
/// uses a 5-minute client timeout for bundles); a 30 s server-side Ask 504s while the
|
||||||
|
/// piped ProcessCommand keeps applying — the caller then retries and double-applies
|
||||||
|
/// (arch-review S1). These command types get LongRunningCommandTimeout instead.</summary>
|
||||||
|
private static readonly HashSet<Type> LongRunningCommands =
|
||||||
|
[
|
||||||
|
typeof(ImportBundleCommand), typeof(PreviewBundleCommand), typeof(ExportBundleCommand),
|
||||||
|
typeof(MgmtDeployArtifactsCommand), typeof(MgmtDeployInstanceCommand),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the ManagementActor Ask timeout for a specific command type. Long-running
|
||||||
|
/// transport/deploy commands use <see cref="ManagementServiceOptions.LongRunningCommandTimeout"/>
|
||||||
|
/// (default 5 min); all other commands fall through to <see cref="ResolveAskTimeout(ManagementServiceOptions?)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The management service options, or <c>null</c> if not configured.</param>
|
||||||
|
/// <param name="commandType">The runtime type of the command being dispatched.</param>
|
||||||
|
/// <returns>The resolved Ask timeout for the command.</returns>
|
||||||
|
public static TimeSpan ResolveAskTimeout(ManagementServiceOptions? options, Type commandType)
|
||||||
|
=> LongRunningCommands.Contains(commandType)
|
||||||
|
? (options is { LongRunningCommandTimeout.Ticks: > 0 } o ? o.LongRunningCommandTimeout : DefaultLongRunningTimeout)
|
||||||
|
: ResolveAskTimeout(options);
|
||||||
|
|
||||||
/// <summary>Registers the <c>POST /management</c> endpoint on the given route builder.</summary>
|
/// <summary>Registers the <c>POST /management</c> endpoint on the given route builder.</summary>
|
||||||
/// <param name="endpoints">The route builder to add the endpoint to.</param>
|
/// <param name="endpoints">The route builder to add the endpoint to.</param>
|
||||||
/// <returns>The same <paramref name="endpoints"/> instance for chaining.</returns>
|
/// <returns>The same <paramref name="endpoints"/> instance for chaining.</returns>
|
||||||
@@ -99,7 +124,8 @@ public static class ManagementEndpoints
|
|||||||
var envelope = new ManagementEnvelope(authenticatedUser, command, correlationId);
|
var envelope = new ManagementEnvelope(authenticatedUser, command, correlationId);
|
||||||
|
|
||||||
var askTimeout = ResolveAskTimeout(
|
var askTimeout = ResolveAskTimeout(
|
||||||
context.RequestServices.GetService<IOptions<ManagementServiceOptions>>()?.Value);
|
context.RequestServices.GetService<IOptions<ManagementServiceOptions>>()?.Value,
|
||||||
|
command.GetType());
|
||||||
|
|
||||||
object response;
|
object response;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -4,4 +4,7 @@ public class ManagementServiceOptions
|
|||||||
{
|
{
|
||||||
/// <summary>Maximum time to wait for a management command to complete before returning a timeout error; default 30 seconds.</summary>
|
/// <summary>Maximum time to wait for a management command to complete before returning a timeout error; default 30 seconds.</summary>
|
||||||
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
|
/// <summary>Maximum time to wait for a long-running transport/deploy command (import/preview/export bundle, deploy artifacts/instance) before returning a timeout error; default 5 minutes.</summary>
|
||||||
|
public TimeSpan LongRunningCommandTimeout { get; set; } = TimeSpan.FromMinutes(5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,4 +99,30 @@ public class ManagementEndpointsTests
|
|||||||
|
|
||||||
Assert.Equal(TimeSpan.FromSeconds(30), timeout);
|
Assert.Equal(TimeSpan.FromSeconds(30), timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Per-command long-running Ask timeout (arch-review S1)
|
||||||
|
//
|
||||||
|
// Transport/deploy commands legitimately run for minutes; they must use the
|
||||||
|
// LongRunningCommandTimeout (default 5 min) instead of the 30 s default.
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(ImportBundleCommand))]
|
||||||
|
[InlineData(typeof(ExportBundleCommand))]
|
||||||
|
[InlineData(typeof(PreviewBundleCommand))]
|
||||||
|
[InlineData(typeof(MgmtDeployArtifactsCommand))]
|
||||||
|
[InlineData(typeof(MgmtDeployInstanceCommand))]
|
||||||
|
public void ResolveAskTimeout_LongRunningCommand_UsesLongTimeout(Type cmd)
|
||||||
|
=> Assert.Equal(TimeSpan.FromMinutes(5), ManagementEndpoints.ResolveAskTimeout(null, cmd));
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveAskTimeout_OrdinaryCommand_UsesDefault()
|
||||||
|
=> Assert.Equal(TimeSpan.FromSeconds(30), ManagementEndpoints.ResolveAskTimeout(null, typeof(ListTemplatesCommand)));
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveAskTimeout_ConfiguredLongTimeout_Honored()
|
||||||
|
=> Assert.Equal(TimeSpan.FromMinutes(10), ManagementEndpoints.ResolveAskTimeout(
|
||||||
|
new ManagementServiceOptions { LongRunningCommandTimeout = TimeSpan.FromMinutes(10) },
|
||||||
|
typeof(ImportBundleCommand)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user