Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ protected override async ValueTask CleanupAsync(Exception? error = null, Cancell
// so create an exception with details about that.
error ??= await GetUnexpectedExitExceptionAsync().ConfigureAwait(false);

// Closing the server's stdin is the portable graceful-shutdown signal for stdio transports.
// Do this before waiting so a well-behaved server can finish its own cleanup and exit.
try
{
_process.StandardInput.Close();
}
catch { }

// Ensure all pending ErrorDataReceived events are drained before detaching
// the handler. GetUnexpectedExitExceptionAsync does this when HasExited is
// true, but there is a narrow window on Linux where the process has closed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,43 @@ public class StdioClientTransportTests(ITestOutputHelper testOutputHelper) : Log
{
public static bool IsStdErrCallbackSupported => !PlatformDetection.IsMonoRuntime;

[Fact]
public async Task DisposeAsync_ClosesServerStandardInputForGracefulExit()
{
TimeSpan shutdownTimeout = TimeSpan.FromSeconds(4);
string testServerExecutable = Path.Combine(AppContext.BaseDirectory, "TestServer.exe");
string testServerDll = Path.Combine(AppContext.BaseDirectory, "TestServer.dll");

StdioClientTransport transport = new(new()
{
Name = "TestServer",
Command = (PlatformDetection.IsMonoRuntime, PlatformDetection.IsWindows) switch
{
(true, _) => "mono",
(_, true) => testServerExecutable,
_ => "dotnet",
},
Arguments = (PlatformDetection.IsMonoRuntime, PlatformDetection.IsWindows) switch
{
(true, _) => [testServerExecutable],
(_, true) => [],
_ => [testServerDll],
},
ShutdownTimeout = shutdownTimeout,
}, LoggerFactory);

await using ITransport session = await transport.ConnectAsync(TestContext.Current.CancellationToken);

await session.DisposeAsync();

var exception = await Assert.ThrowsAsync<ClientTransportClosedException>(
async () => await session.MessageReader.Completion);
var completionDetails = Assert.IsType<StdioClientCompletionDetails>(exception.Details);
// A zero exit code proves the server observed stdin EOF and exited on its own
// instead of being terminated after ShutdownTimeout.
Assert.Equal(0, completionDetails.ExitCode);
}

[Fact]
public async Task ConnectAsync_DoesNotLogEnvironmentVariablesAtTrace()
{
Expand Down
Loading