From 47748297e81669d456474746ac87cc3eb0a78419 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 9 Jan 2020 11:04:00 -0800 Subject: [PATCH] SocketAsyncEngine.Unix: Use eventfd(2) to signal shutdown Since we don't really care what is being written to the pipe to wake up the event loop, we can substitute it for an eventfd(2) on Linux and save ourselves a file descriptor per SocketAsyncEngine. (For manycore systems, that's potentially dozens of file descriptors that aren't needed anymore.) (It's a tiny wee little bit more efficient to write to an eventfd than it is writing to a pipe, although in this case it doesn't matter that much.) --- .../Unix/System.Native/Interop.Eventfd.cs | 27 +++++++ .../Native/Unix/Common/pal_config.h.in | 1 + .../Native/Unix/System.Native/pal_io.c | 18 +++++ .../Native/Unix/System.Native/pal_io.h | 8 +++ src/libraries/Native/Unix/configure.cmake | 5 ++ .../src/System.Net.Sockets.csproj | 3 + .../Net/Sockets/SocketAsyncEngine.Unix.cs | 70 +++++++++++++------ 7 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.Eventfd.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Eventfd.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Eventfd.cs new file mode 100644 index 00000000000000..224eec39c187cc --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Eventfd.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + // + // Since eventfd is a Linux-only feature, there's no need to define our own + // flag values: pass these values through as the Linux system call expects. + // + [Flags] + internal enum EventFdFlags + { + EFD_SEMAPHORE = 0x1, + EFD_CLOEXEC = 0x80000, + EFD_NONBLOCK = 0x800, + } + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_EventFD", SetLastError = true)] + internal static extern unsafe int EventFD(uint initialVal, EventFdFlags flags = 0); + } +} diff --git a/src/libraries/Native/Unix/Common/pal_config.h.in b/src/libraries/Native/Unix/Common/pal_config.h.in index 701c2c5dc1a65c..c65dcf4669c649 100644 --- a/src/libraries/Native/Unix/Common/pal_config.h.in +++ b/src/libraries/Native/Unix/Common/pal_config.h.in @@ -96,6 +96,7 @@ #cmakedefine01 HAVE_TCP_H_TCP_KEEPALIVE #cmakedefine01 HAVE_BUILTIN_MUL_OVERFLOW #cmakedefine01 HAVE_DISCONNECTX +#cmakedefine01 HAVE_EVENTFD // Mac OS X has stat64, but it is deprecated since plain stat now // provides the same 64-bit aware struct when targeting OS X > 10.5 diff --git a/src/libraries/Native/Unix/System.Native/pal_io.c b/src/libraries/Native/Unix/System.Native/pal_io.c index dd491e5cf7e9a7..74b192009dda69 100644 --- a/src/libraries/Native/Unix/System.Native/pal_io.c +++ b/src/libraries/Native/Unix/System.Native/pal_io.c @@ -38,6 +38,9 @@ #if HAVE_INOTIFY #include #endif +#if HAVE_EVENTFD +#include +#endif #ifdef _AIX #include @@ -475,6 +478,21 @@ int32_t SystemNative_CloseDir(DIR* dir) return closedir(dir); } +#if HAVE_EVENTFD +int32_t SystemNative_EventFD(uint32_t initialVal, int32_t flags) +{ + return eventfd(initialVal, flags); +} +#else +int32_t SystemNative_EventFD(uint32_t initialVal, int32_t flags) +{ + (void)initialVal; + (void)flags; + errno = ENOTSUP; + return -1; +} +#endif + int32_t SystemNative_Pipe(int32_t pipeFds[2], int32_t flags) { switch (flags) diff --git a/src/libraries/Native/Unix/System.Native/pal_io.h b/src/libraries/Native/Unix/System.Native/pal_io.h index 147e64e8945857..c96e66f2674214 100644 --- a/src/libraries/Native/Unix/System.Native/pal_io.h +++ b/src/libraries/Native/Unix/System.Native/pal_io.h @@ -421,6 +421,14 @@ DLLEXPORT int32_t SystemNative_CloseDir(DIR* dir); DLLEXPORT int32_t SystemNative_Pipe(int32_t pipefd[2], // [out] pipefds[0] gets read end, pipefd[1] gets write end. int32_t flags); // 0 for defaults or PAL_O_CLOEXEC for close-on-exec +/** + * Creates an eventfd on Linux. Returns error everywhere else. + * + * Returns 0 for success, -1 for failure. Sets errno on failure. + */ +DLLEXPORT int32_t SystemNative_EventFD(uint32_t initialValue, + int32_t flags); // Passes through to eventfd() without conversion + // NOTE: Rather than a general fcntl shim, we opt to export separate functions // for each command. This allows use to have strongly typed arguments and saves // complexity around converting command codes. diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake index 139102ab852b36..bf200297819d8f 100644 --- a/src/libraries/Native/Unix/configure.cmake +++ b/src/libraries/Native/Unix/configure.cmake @@ -104,6 +104,11 @@ check_symbol_exists( ifaddrs.h HAVE_GETIFADDRS) +check_symbol_exists( + eventfd + sys/eventfd.h + HAVE_EVENTFD) + check_symbol_exists( lseek64 unistd.h diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index 5126d460fccdd2..f39587235f1d61 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -374,6 +374,9 @@ Common\Interop\Unix\System.Native\Interop.Pipe.cs + + Common\Interop\Unix\System.Native\Interop.Eventfd.cs + Common\Interop\Unix\System.Native\Interop.Write.cs diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index f8224e8c89a22a..69c66e5ac47f29 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -237,6 +237,32 @@ private void FreeHandle(IntPtr handle) } } + private void CreateShutdownPipeOrEventFD(out int readFD, out int writeFD) + { + // + // If this system supports eventfd(2), use it to signal shutdown, and save us + // a file descriptor per engine. Otherwise, fall back to using a pipe. + // + int eventFD = Interop.Sys.EventFD(0, Interop.Sys.EventFdFlags.EFD_CLOEXEC | Interop.Sys.EventFdFlags.EFD_SEMAPHORE); + + if (eventFD >= 0) + { + readFD = writeFD = eventFD; + } + else + { + int* pipeFds = stackalloc int[2]; + int pipeResult = Interop.Sys.Pipe(pipeFds, Interop.Sys.PipeFlags.O_CLOEXEC); + if (pipeResult != 0) + { + throw new InternalException(pipeResult); + } + + readFD = pipeFds[Interop.Sys.ReadEndOfPipe]; + writeFD = pipeFds[Interop.Sys.WriteEndOfPipe]; + } + } + private SocketAsyncEngine() { _port = (IntPtr)(-1); @@ -258,18 +284,7 @@ private SocketAsyncEngine() throw new InternalException(err); } - // - // Create the pipe for signaling shutdown, and register for "read" events for the pipe. Now writing - // to the pipe will send an event to the event loop. - // - int* pipeFds = stackalloc int[2]; - int pipeResult = Interop.Sys.Pipe(pipeFds, Interop.Sys.PipeFlags.O_CLOEXEC); - if (pipeResult != 0) - { - throw new InternalException(pipeResult); - } - _shutdownReadPipe = pipeFds[Interop.Sys.ReadEndOfPipe]; - _shutdownWritePipe = pipeFds[Interop.Sys.WriteEndOfPipe]; + CreateShutdownPipeOrEventFD(out _shutdownReadPipe, out _shutdownWritePipe); err = Interop.Sys.TryChangeSocketEventRegistration(_port, (IntPtr)_shutdownReadPipe, Interop.Sys.SocketEvents.None, Interop.Sys.SocketEvents.Read, ShutdownHandle); if (err != Interop.Error.SUCCESS) @@ -351,25 +366,40 @@ private void EventLoop() private void RequestEventLoopShutdown() { // - // Write to the pipe, which will wake up the event loop and cause it to exit. + // Write to the pipe or eventfd, which will wake up the event loop and cause it to exit. + // (Need to write 8 bytes in case we're on a system where eventfd is supported.) // - byte b = 1; - int bytesWritten = Interop.Sys.Write(_shutdownWritePipe, &b, 1); - if (bytesWritten != 1) + byte[] wakeThreadUp = {0, 0, 0, 0, 0, 0, 0, 1}; + fixed (byte *b = &wakeThreadUp[0]) { - throw new InternalException(bytesWritten); + int bytesWritten = Interop.Sys.Write(_shutdownWritePipe, b, 8); + if (bytesWritten != 8) + { + throw new InternalException(bytesWritten); + } } } private void FreeNativeResources() { - if (_shutdownReadPipe != -1) + if (_shutdownReadPipe == _shutdownWritePipe && _shutdownReadPipe != -1) { + // + // When using eventfd, read/write ends of the "pipe" have the same file + // descriptor. + // Interop.Sys.Close((IntPtr)_shutdownReadPipe); } - if (_shutdownWritePipe != -1) + else { - Interop.Sys.Close((IntPtr)_shutdownWritePipe); + if (_shutdownReadPipe != -1) + { + Interop.Sys.Close((IntPtr)_shutdownReadPipe); + } + if (_shutdownWritePipe != -1) + { + Interop.Sys.Close((IntPtr)_shutdownWritePipe); + } } if (_buffer != null) {