Skip to content

Commit 85a70c4

Browse files
authored
Attempt to fix DuplicateAndClose_TcpServerHandler flaky test (#103161)
* Ensure RemoteInvokeHandle is disposed * Make RunCommonHostLogic synchronous * Document DisposeAsync intent * Replace namedpipe IPC by Socket IPC * Setting NoDelay for ipc sockets
1 parent 3916efe commit 85a70c4

File tree

2 files changed

+44
-19
lines changed

2 files changed

+44
-19
lines changed

src/libraries/Common/tests/System/Net/RemoteExecutorExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ namespace Microsoft.DotNet.RemoteExecutor;
88

99
internal static class RemoteExecutorExtensions
1010
{
11+
/// <summary>
12+
/// Dispose the RemoteInvokeHandle synchronously can take considerable time, and cause other unrelated tests to fail on timeout
13+
/// because of depletion of xUnit synchronization context threads.
14+
/// Running dispose in a separate task on the thread pool can help alleviate this issue.
15+
/// </summary>
16+
/// <example>
17+
/// Executes the ServerCode in separate process and awaits its completion in a separate task outside of current synchronization context.
18+
/// <code>
19+
/// await RemoteExecutor.Invoke(ServerCode).DisposeAsync();
20+
/// </code>
21+
/// </example>
1122
public static async ValueTask DisposeAsync(this RemoteInvokeHandle handle)
1223
{
1324
await Task.Run(handle.Dispose);

src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketDuplicationTests.cs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.Globalization;
56
using System.IO;
67
using System.IO.Pipes;
78
using System.Runtime.Serialization.Formatters.Binary;
@@ -265,7 +266,6 @@ public void SocketCtr_SocketInformation_WhenProtocolInformationTooShort_Throws()
265266
public abstract class PolymorphicTests<T> where T : SocketHelperBase, new()
266267
{
267268
private static readonly T Helper = new T();
268-
private readonly string _ipcPipeName = Path.GetRandomFileName();
269269

270270
private static void WriteSocketInfo(Stream stream, SocketInformation socketInfo)
271271
{
@@ -317,44 +317,58 @@ public async Task DuplicateAndClose_TcpServerHandler(AddressFamily addressFamily
317317
// Async is allowed on the listener:
318318
using Socket handlerOriginal = await listener.AcceptAsync();
319319

320-
// pipe used to exchange socket info
321-
await using NamedPipeServerStream pipeServerStream =
322-
new NamedPipeServerStream(_ipcPipeName, PipeDirection.Out);
320+
// socket used to exchange duplicated socket info with server code
321+
using Socket ipcServerListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
322+
ipcServerListener.BindToAnonymousPort(IPAddress.Loopback);
323+
ipcServerListener.Listen(1);
324+
string ipcPortString = ((IPEndPoint)ipcServerListener.LocalEndPoint).Port.ToString(CultureInfo.InvariantCulture);
323325

324326
if (sameProcess)
325327
{
326-
Task handlerCode = Task.Run(() => HandlerServerCode(_ipcPipeName));
327-
await RunCommonHostLogic(Environment.ProcessId);
328+
Task handlerCode = Task.Run(() => HandlerServerCode(ipcPortString));
329+
RunCommonHostLogic(Environment.ProcessId);
328330
await handlerCode;
329331
}
330332
else
331333
{
332-
RemoteInvokeHandle hServerProc = RemoteExecutor.Invoke(HandlerServerCode, _ipcPipeName);
333-
await RunCommonHostLogic(hServerProc.Process.Id);
334-
await hServerProc.DisposeAsync();
334+
RemoteInvokeHandle hServerProc = RemoteExecutor.Invoke(HandlerServerCode, ipcPortString);
335+
336+
// Since RunCommonHostLogic can throw, we need to make sure the server process is disposed
337+
try
338+
{
339+
RunCommonHostLogic(hServerProc.Process.Id);
340+
}
341+
finally
342+
{
343+
await hServerProc.DisposeAsync();
344+
}
335345
}
336346

337-
async Task RunCommonHostLogic(int processId)
347+
void RunCommonHostLogic(int processId)
338348
{
339-
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
340-
341-
pipeServerStream.WaitForConnection();
349+
using Socket ipcServer = ipcServerListener.Accept();
350+
ipcServer.NoDelay = true;
342351

343352
// Duplicate the socket:
344353
SocketInformation socketInfo = handlerOriginal.DuplicateAndClose(processId);
345-
WriteSocketInfo(pipeServerStream, socketInfo);
354+
// Send socketInfo to server code
355+
using var ipcStream = new NetworkStream(ipcServer, false);
356+
WriteSocketInfo(new NetworkStream(ipcServer), socketInfo);
346357

347358
// Send client data:
348359
client.Send(TestBytes);
349360
}
350361

351-
static async Task<int> HandlerServerCode(string ipcPipeName)
362+
static async Task<int> HandlerServerCode(string ipcPortString)
352363
{
353-
await using NamedPipeClientStream pipeClientStream =
354-
new NamedPipeClientStream(".", ipcPipeName, PipeDirection.In);
355-
pipeClientStream.Connect();
364+
int ipcPort = int.Parse(ipcPortString, CultureInfo.InvariantCulture);
365+
using Socket ipcClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
366+
ipcClient.NoDelay = true;
367+
await ipcClient.ConnectAsync(IPAddress.Loopback, ipcPort);
368+
369+
await using var ipcStream = new NetworkStream(ipcClient, true);
370+
SocketInformation socketInfo = ReadSocketInfo(ipcStream);
356371

357-
SocketInformation socketInfo = ReadSocketInfo(pipeClientStream);
358372
using Socket handler = new Socket(socketInfo);
359373

360374
Assert.True(handler.IsBound);

0 commit comments

Comments
 (0)