Skip to content

Commit c5edf0e

Browse files
Fix WaitForConnectionAsync when NamedPipeServerStream is disposed (#52825)
* Fix WaitForConnectionAsync when NamedPipeServerStream is disposed * Align Unix implementation on broken pipe IO exception as on Windows * Add missing methods to test against ObjectDisposedException * Apply suggestions from code review Co-authored-by: Stephen Toub <stoub@microsoft.com> * Rebase and fix suggestions * Cancel Accept on dispose * Improve test * Apply suggestions from code review Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent e8be7f2 commit c5edf0e

File tree

2 files changed

+96
-25
lines changed

2 files changed

+96
-25
lines changed

src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public sealed partial class NamedPipeServerStream : PipeStream
2020
private int _inBufferSize;
2121
private int _outBufferSize;
2222
private HandleInheritability _inheritability;
23+
private CancellationTokenSource _internalTokenSource = new CancellationTokenSource();
2324

2425
private void Create(string pipeName, PipeDirection direction, int maxNumberOfServerInstances,
2526
PipeTransmissionMode transmissionMode, PipeOptions options, int inBufferSize, int outBufferSize,
@@ -77,8 +78,25 @@ public Task WaitForConnectionAsync(CancellationToken cancellationToken)
7778
Task.FromCanceled(cancellationToken) :
7879
WaitForConnectionAsyncCore();
7980

80-
async Task WaitForConnectionAsyncCore() =>
81-
HandleAcceptedSocket(await _instance!.ListeningSocket.AcceptAsync(cancellationToken).ConfigureAwait(false));
81+
async Task WaitForConnectionAsyncCore()
82+
{
83+
Socket acceptedSocket;
84+
CancellationTokenSource linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_internalTokenSource.Token, cancellationToken);
85+
try
86+
{
87+
acceptedSocket = await _instance!.ListeningSocket.AcceptAsync(linkedTokenSource.Token).ConfigureAwait(false);
88+
}
89+
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
90+
{
91+
throw new IOException(SR.IO_PipeBroken);
92+
}
93+
finally
94+
{
95+
linkedTokenSource.Dispose();
96+
}
97+
98+
HandleAcceptedSocket(acceptedSocket);
99+
}
82100
}
83101

84102
private void HandleAcceptedSocket(Socket acceptedSocket)
@@ -116,9 +134,19 @@ private void HandleAcceptedSocket(Socket acceptedSocket)
116134
State = PipeState.Connected;
117135
}
118136

119-
internal override void DisposeCore(bool disposing) =>
137+
internal override void DisposeCore(bool disposing)
138+
{
120139
Interlocked.Exchange(ref _instance, null)?.Dispose(disposing); // interlocked to avoid shared state problems from erroneous double/concurrent disposes
121140

141+
if (disposing)
142+
{
143+
if (State != PipeState.Closed)
144+
{
145+
_internalTokenSource.Cancel();
146+
}
147+
}
148+
}
149+
122150
public void Disconnect()
123151
{
124152
CheckDisconnectOperations();

src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,16 @@ public abstract class NamedPipeStreamConformanceTests : PipeStreamConformanceTes
6363
{
6464
protected override bool BrokenPipePropagatedImmediately => OperatingSystem.IsWindows(); // On Unix, implemented on Sockets, where it won't propagate immediate
6565

66-
protected abstract (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams();
66+
protected abstract NamedPipeServerStream CreateServerStream(string pipeName, int maxInstances = 1);
67+
protected abstract NamedPipeClientStream CreateClientStream(string pipeName);
68+
69+
protected (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams()
70+
{
71+
string pipeName = GetUniquePipeName();
72+
NamedPipeServerStream server = CreateServerStream(pipeName);
73+
NamedPipeClientStream client = CreateClientStream(pipeName);
74+
return (server, client);
75+
}
6776

6877
protected sealed override async Task<StreamPair> CreateConnectedStreamsAsync()
6978
{
@@ -88,6 +97,15 @@ protected sealed override async Task<StreamPair> CreateConnectedStreamsAsync()
8897
return ((NamedPipeServerStream)streams.Stream2, (NamedPipeClientStream)streams.Stream1);
8998
}
9099

100+
protected async Task ValidateDisposedExceptionsAsync(NamedPipeServerStream server)
101+
{
102+
Assert.Throws<ObjectDisposedException>(() => server.Disconnect());
103+
Assert.Throws<ObjectDisposedException>(() => server.GetImpersonationUserName());
104+
Assert.Throws<ObjectDisposedException>(() => server.WaitForConnection());
105+
await Assert.ThrowsAsync<ObjectDisposedException>(() => server.WaitForConnectionAsync());
106+
await ValidateDisposedExceptionsAsync(server as Stream);
107+
}
108+
91109
/// <summary>
92110
/// Yields every combination of testing options for the OneWayReadWrites test
93111
/// </summary>
@@ -629,6 +647,37 @@ public async Task CancelTokenOn_Client_ReadWriteCancelledToken_Throws_OperationC
629647
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => clientWriteToken);
630648
}
631649
}
650+
651+
[Fact]
652+
public async Task TwoServerInstances_OnceDisposed_Throws()
653+
{
654+
string pipeName = GetUniquePipeName();
655+
NamedPipeServerStream server1 = CreateServerStream(pipeName, 2);
656+
using NamedPipeServerStream server2 = CreateServerStream(pipeName, 2);
657+
658+
Task wait1 = server1.WaitForConnectionAsync();
659+
Task wait2 = server2.WaitForConnectionAsync();
660+
server1.Dispose();
661+
await ValidateDisposedExceptionsAsync(server1);
662+
663+
using NamedPipeClientStream client = CreateClientStream(pipeName);
664+
await client.ConnectAsync();
665+
666+
await Assert.ThrowsAsync<IOException>(() => wait1);
667+
668+
await wait2;
669+
670+
foreach ((Stream writeable, Stream readable) in GetReadWritePairs((server2, client)))
671+
{
672+
byte[] sent = new byte[] { 123 };
673+
byte[] received = new byte[] { 0 };
674+
675+
Task t = Task.Run(() => writeable.Write(sent, 0, sent.Length));
676+
Assert.Equal(sent.Length, readable.Read(received, 0, sent.Length));
677+
Assert.Equal(sent, received);
678+
await t;
679+
}
680+
}
632681
}
633682

634683
public sealed class AnonymousPipeTest_ServerIn_ClientOut : AnonymousPipeStreamConformanceTests
@@ -653,34 +702,28 @@ protected override (AnonymousPipeServerStream Server, AnonymousPipeClientStream
653702

654703
public sealed class NamedPipeTest_ServerOut_ClientIn : NamedPipeStreamConformanceTests
655704
{
656-
protected override (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams()
657-
{
658-
string pipeName = PipeStreamConformanceTests.GetUniquePipeName();
659-
var server = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
660-
var client = new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous);
661-
return (server, client);
662-
}
705+
protected override NamedPipeServerStream CreateServerStream(string pipeName, int maxInstances = 1) =>
706+
new NamedPipeServerStream(pipeName, PipeDirection.Out, maxInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
707+
708+
protected override NamedPipeClientStream CreateClientStream(string pipeName) =>
709+
new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous);
663710
}
664711

665712
public sealed class NamedPipeTest_ServerIn_ClientOut : NamedPipeStreamConformanceTests
666713
{
667-
protected override (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams()
668-
{
669-
string pipeName = PipeStreamConformanceTests.GetUniquePipeName();
670-
var server = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
671-
var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous);
672-
return (server, client);
673-
}
714+
protected override NamedPipeServerStream CreateServerStream(string pipeName, int maxInstances = 1) =>
715+
new NamedPipeServerStream(pipeName, PipeDirection.In, maxInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
716+
717+
protected override NamedPipeClientStream CreateClientStream(string pipeName) =>
718+
new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous);
674719
}
675720

676721
public sealed class NamedPipeTest_ServerInOut_ClientInOut : NamedPipeStreamConformanceTests
677722
{
678-
protected override (NamedPipeServerStream Server, NamedPipeClientStream Client) CreateServerAndClientStreams()
679-
{
680-
string pipeName = PipeStreamConformanceTests.GetUniquePipeName();
681-
var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
682-
var client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
683-
return (server, client);
684-
}
723+
protected override NamedPipeServerStream CreateServerStream(string pipeName, int maxInstances = 1) =>
724+
new NamedPipeServerStream(pipeName, PipeDirection.InOut, maxInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
725+
726+
protected override NamedPipeClientStream CreateClientStream(string pipeName) =>
727+
new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
685728
}
686729
}

0 commit comments

Comments
 (0)