Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 56bbaf0

Browse files
committed
Merge pull request #1949 from stephentoub/pipe_buffer_size
Implement System.IO.Pipes buffer sizes on Linux
2 parents c07e6cf + 30e4f0a commit 56bbaf0

File tree

11 files changed

+169
-20
lines changed

11 files changed

+169
-20
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
internal static partial class Interop
5+
{
6+
internal static partial class libc
7+
{
8+
internal enum FcntlCommands
9+
{
10+
F_LINUX_SPECIFIC_BASE = 1024,
11+
F_SETPIPE_SZ = F_LINUX_SPECIFIC_BASE + 7,
12+
F_GETPIPE_SZ = F_LINUX_SPECIFIC_BASE + 8
13+
}
14+
}
15+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class libc
10+
{
11+
// fcntl takes a variable number of arguments, which is difficult to represent in C#.
12+
// Instead, since we only have a small and fixed number of call sites, we declare
13+
// an overload for each of the specific argument sets we need.
14+
15+
[DllImport(Libraries.Libc)]
16+
internal static extern unsafe int fcntl(int fd, FcntlCommands cmd);
17+
18+
[DllImport(Libraries.Libc)]
19+
internal static extern unsafe int fcntl(int fd, FcntlCommands cmd, int arg1);
20+
}
21+
}

src/System.IO.Pipes/src/System.IO.Pipes.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,16 +206,24 @@
206206
<!-- Linux -->
207207
<ItemGroup Condition="'$(TargetsLinux)' == 'true'">
208208
<Compile Include="System\IO\Pipes\AnonymousPipeServerStream.Linux.cs" />
209+
<Compile Include="System\IO\Pipes\PipeStream.Linux.cs" />
209210
<Compile Include="$(CommonPath)\Interop\Linux\Interop.Errors.cs">
210211
<Link>Common\Interop\Linux\Interop.Errors.cs</Link>
211212
</Compile>
213+
<Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.fcntl.cs">
214+
<Link>Common\Interop\Unix\Interop.fcntl.cs</Link>
215+
</Compile>
216+
<Compile Include="$(CommonPath)\Interop\Linux\libc\Interop.FcntlCommands.cs">
217+
<Link>Common\Interop\Linux\Interop.FcntlCommands.cs</Link>
218+
</Compile>
212219
<Compile Include="$(CommonPath)\Interop\Linux\libc\Interop.OpenFlags.cs">
213220
<Link>Common\Interop\Linux\Interop.OpenFlags.cs</Link>
214221
</Compile>
215222
</ItemGroup>
216223
<!-- OSX -->
217224
<ItemGroup Condition="'$(TargetsOSX)' == 'true'">
218225
<Compile Include="System\IO\Pipes\AnonymousPipeServerStream.OSX.cs" />
226+
<Compile Include="System\IO\Pipes\PipeStream.OSX.cs" />
219227
<Compile Include="$(CommonPath)\Interop\OSX\Interop.Errors.cs">
220228
<Link>Common\Interop\OSX\Interop.Errors.cs</Link>
221229
</Compile>

src/System.IO.Pipes/src/System/IO/Pipes/AnonymousPipeServerStream.Unix.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ private void Create(PipeDirection direction, HandleInheritability inheritability
3838
(IntPtr)fds[direction == PipeDirection.In ? Interop.libc.WriteEndOfPipe : Interop.libc.ReadEndOfPipe],
3939
ownsHandle: true);
4040

41+
// Configure the pipe. For buffer size, the size applies to the pipe, rather than to
42+
// just one end's file descriptor, so we only need to do this with one of the handles.
43+
InitializeBufferSize(serverHandle, bufferSize);
44+
4145
// We're connected. Finish initialization using the newly created handles.
4246
InitializeHandle(serverHandle, isExposed: false, isAsync: false);
4347
_clientHandle = clientHandle;

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ public void WaitForConnection()
9999
TranslateFlags(_direction, _options, _inheritability),
100100
(int)Interop.libc.Permissions.S_IRWXU);
101101

102-
// Ignore _inBufferSize and _outBufferSize. They're optional, and the fcntl F_SETPIPE_SZ for changing
103-
// a pipe's buffer size is Linux specific.
104-
102+
InitializeBufferSize(serverHandle, _outBufferSize); // there's only one capacity on Linux; just use the out buffer size
105103
InitializeHandle(serverHandle, isExposed: false, isAsync: (_options & PipeOptions.Asynchronous) != 0);
106104
State = PipeState.Connected;
107105
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.Win32.SafeHandles;
5+
6+
namespace System.IO.Pipes
7+
{
8+
public abstract partial class PipeStream
9+
{
10+
internal void InitializeBufferSize(SafePipeHandle handle, int bufferSize)
11+
{
12+
if (bufferSize > 0)
13+
{
14+
SysCall(handle, (fd, _, size) => Interop.libc.fcntl(fd, Interop.libc.FcntlCommands.F_SETPIPE_SZ, size),
15+
IntPtr.Zero, bufferSize);
16+
}
17+
}
18+
19+
private int InBufferSizeCore { get { return GetPipeBufferSize(); } }
20+
21+
private int OutBufferSizeCore { get { return GetPipeBufferSize(); } }
22+
23+
private int GetPipeBufferSize()
24+
{
25+
// If we have a handle, get the capacity of the pipe (there's no distinction between in/out direction).
26+
// If we don't, the pipe has been created but not yet connected (in the case of named pipes),
27+
// so just return the buffer size that was passed to the constructor.
28+
return _handle != null ?
29+
(int)SysCall(_handle, (fd, _, __) => Interop.libc.fcntl(fd, Interop.libc.FcntlCommands.F_GETPIPE_SZ)) :
30+
_outBufferSize;
31+
}
32+
}
33+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.Win32.SafeHandles;
5+
6+
namespace System.IO.Pipes
7+
{
8+
public abstract partial class PipeStream
9+
{
10+
internal void InitializeBufferSize(SafePipeHandle handle, int bufferSize)
11+
{
12+
// Nop. We can't configure the capacity, and as it's just advisory, ignore it.
13+
}
14+
15+
private int InBufferSizeCore { get { throw new PlatformNotSupportedException(); } }
16+
17+
private int OutBufferSizeCore { get { throw new PlatformNotSupportedException(); } }
18+
}
19+
}

src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public abstract partial class PipeStream : Stream
2020
// Windows, but can't assume a valid handle on Unix.
2121
internal const bool CheckOperationsRequiresSetHandle = false;
2222

23-
private const string PipeDirectoryPath = "/tmp/corefxnamedpipes/";
23+
private static readonly string PipeDirectoryPath = Path.Combine(Path.GetTempPath(), "corefxnamedpipes");
2424

2525
internal static string GetPipePath(string serverName, string pipeName)
2626
{
@@ -66,7 +66,7 @@ internal static string GetPipePath(string serverName, string pipeName)
6666
}
6767

6868
// Return the pipe path
69-
return PipeDirectoryPath + pipeName;
69+
return Path.Combine(PipeDirectoryPath, pipeName);
7070
}
7171

7272
/// <summary>Throws an exception if the supplied handle does not represent a valid pipe.</summary>
@@ -213,10 +213,7 @@ public virtual int InBufferSize
213213
{
214214
throw new NotSupportedException(SR.NotSupported_UnreadableStream);
215215
}
216-
217-
// On Linux this could be retrieved using F_GETPIPE_SZ with fcntl, but that's non-conforming
218-
// and works only on recent versions of Linux. For now, we'll leave this as unsupported.
219-
throw new PlatformNotSupportedException();
216+
return InBufferSizeCore;
220217
}
221218
}
222219

@@ -235,9 +232,7 @@ public virtual int OutBufferSize
235232
{
236233
throw new NotSupportedException(SR.NotSupported_UnwritableStream);
237234
}
238-
239-
// See comments in inBufferSize
240-
throw new PlatformNotSupportedException();
235+
return OutBufferSizeCore;
241236
}
242237
}
243238

src/System.IO.Pipes/tests/AnonymousPipesSimpleTest.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.Win32.SafeHandles;
55
using System;
6+
using System.IO;
67
using System.IO.Pipes;
78
using System.Threading.Tasks;
89
using Xunit;
@@ -130,7 +131,7 @@ public static void StartClient(PipeDirection direction, SafePipeHandle clientPip
130131
public static async Task ServerPInvokeChecks()
131132
{
132133
// calling every API related to server and client to detect any bad PInvokes
133-
using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.Out))
134+
using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.None, 4096))
134135
{
135136
Task clientTask = Task.Run(() => StartClient(PipeDirection.In, server.ClientSafePipeHandle));
136137

@@ -141,9 +142,9 @@ public static async Task ServerPInvokeChecks()
141142
Assert.False(string.IsNullOrWhiteSpace(server.GetClientHandleAsString()));
142143
Assert.False(server.IsAsync);
143144
Assert.True(server.IsConnected);
144-
if (Interop.IsWindows)
145+
if (Interop.IsWindows || Interop.IsLinux)
145146
{
146-
Assert.Equal(0, server.OutBufferSize);
147+
Assert.True(server.OutBufferSize > 0);
147148
}
148149
else
149150
{
@@ -177,6 +178,10 @@ public static async Task ServerPInvokeChecks()
177178
{
178179
Assert.Equal(4096, server.InBufferSize);
179180
}
181+
else if (Interop.IsLinux)
182+
{
183+
Assert.True(server.InBufferSize > 0);
184+
}
180185
else
181186
{
182187
Assert.Throws<PlatformNotSupportedException>(() => server.InBufferSize);
@@ -194,7 +199,7 @@ public static async Task ServerPInvokeChecks()
194199
[Fact]
195200
public static async Task ClientPInvokeChecks()
196201
{
197-
using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.In))
202+
using (AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.In, System.IO.HandleInheritability.None, 4096))
198203
{
199204
using (AnonymousPipeClientStream client = new AnonymousPipeClientStream(PipeDirection.Out, server.ClientSafePipeHandle))
200205
{
@@ -210,6 +215,10 @@ public static async Task ClientPInvokeChecks()
210215
{
211216
Assert.Equal(0, client.OutBufferSize);
212217
}
218+
else if (Interop.IsLinux)
219+
{
220+
Assert.True(client.OutBufferSize > 0);
221+
}
213222
else
214223
{
215224
Assert.Throws<PlatformNotSupportedException>(() => client.OutBufferSize);
@@ -244,6 +253,10 @@ public static async Task ClientPInvokeChecks()
244253
{
245254
Assert.Equal(4096, client.InBufferSize);
246255
}
256+
else if (Interop.IsLinux)
257+
{
258+
Assert.True(client.InBufferSize > 0);
259+
}
247260
else
248261
{
249262
Assert.Throws<PlatformNotSupportedException>(() => client.InBufferSize);
@@ -258,4 +271,31 @@ public static async Task ClientPInvokeChecks()
258271
}
259272
}
260273
}
274+
275+
[Fact]
276+
[PlatformSpecific(PlatformID.Linux)]
277+
public static void BufferSizeRoundtrips()
278+
{
279+
int desiredBufferSize;
280+
using (var server = new AnonymousPipeServerStream(PipeDirection.Out))
281+
{
282+
desiredBufferSize = server.OutBufferSize * 2;
283+
Assert.True(desiredBufferSize > 0);
284+
}
285+
286+
using (var server = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.None, desiredBufferSize))
287+
using (var client = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle))
288+
{
289+
Assert.Equal(desiredBufferSize, server.OutBufferSize);
290+
Assert.Equal(desiredBufferSize, client.InBufferSize);
291+
}
292+
293+
using (var server = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.None, desiredBufferSize))
294+
using (var client = new AnonymousPipeClientStream(PipeDirection.Out, server.ClientSafePipeHandle))
295+
{
296+
Assert.Equal(desiredBufferSize, server.InBufferSize);
297+
Assert.Equal(desiredBufferSize, client.OutBufferSize);
298+
}
299+
}
300+
261301
}

src/System.IO.Pipes/tests/NamedPipesSimpleTest.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ public static async Task ServerPInvokeChecks()
223223
{
224224
Assert.Equal(0, server.OutBufferSize);
225225
}
226+
else if (Interop.IsLinux)
227+
{
228+
Assert.True(server.OutBufferSize > 0);
229+
}
226230
else
227231
{
228232
Assert.Throws<PlatformNotSupportedException>(() => server.OutBufferSize);
@@ -255,6 +259,10 @@ public static async Task ServerPInvokeChecks()
255259
{
256260
Assert.Equal(0, server.InBufferSize);
257261
}
262+
else if (Interop.IsLinux)
263+
{
264+
Assert.True(server.InBufferSize > 0);
265+
}
258266
else
259267
{
260268
Assert.Throws<PlatformNotSupportedException>(() => server.InBufferSize);
@@ -270,7 +278,7 @@ public static async Task ServerPInvokeChecks()
270278
[Fact]
271279
public static async Task ClientPInvokeChecks()
272280
{
273-
using (NamedPipeServerStream server = new NamedPipeServerStream("foo", PipeDirection.In))
281+
using (NamedPipeServerStream server = new NamedPipeServerStream("foo", PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.None, 4096, 4096))
274282
{
275283
using (NamedPipeClientStream client = new NamedPipeClientStream(".", "foo", PipeDirection.Out))
276284
{
@@ -287,6 +295,10 @@ public static async Task ClientPInvokeChecks()
287295
{
288296
Assert.Equal(0, client.OutBufferSize);
289297
}
298+
else if (Interop.IsLinux)
299+
{
300+
Assert.True(client.OutBufferSize > 0);
301+
}
290302
else
291303
{
292304
Assert.Throws<PlatformNotSupportedException>(() => client.OutBufferSize);
@@ -322,6 +334,10 @@ public static async Task ClientPInvokeChecks()
322334
{
323335
Assert.Equal(0, client.InBufferSize);
324336
}
337+
else if (Interop.IsLinux)
338+
{
339+
Assert.True(client.InBufferSize > 0);
340+
}
325341
else
326342
{
327343
Assert.Throws<PlatformNotSupportedException>(() => client.InBufferSize);

0 commit comments

Comments
 (0)