Add bindings for {send,recv}mmsg.

This commit is contained in:
Cory Benfield 2018-01-17 09:18:28 +00:00
parent 8b74d21562
commit a2bb047224
5 changed files with 188 additions and 85 deletions

View File

@ -11,3 +11,19 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#define _GNU_SOURCE
#include <c_nio_linux.h>
_Static_assert(sizeof(CNIOLinux_mmsghdr) == sizeof(struct mmsghdr));
int CNIOLinux_sendmmsg(int sockfd, CNIOLinux_mmsghdr *msgvec, unsigned int vlen, int flags) {
// This is technically undefined behaviour, but it's basically fine because these types are the same size, and we
// don't think the compiler is inclined to blow anything up here.
return sendmmsg(sockfd, (struct mmsghdr *)msgvec, vlen, flags);
}
int CNIOLinux_recvmmsg(int sockfd, CNIOLinux_mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout) {
// This is technically undefined behaviour, but it's basically fine because these types are the same size, and we
// don't think the compiler is inclined to blow anything up here.
return recvmmsg(sockfd, (struct mmsghdr *)msgvec, vlen, flags, timeout);
}

View File

@ -18,6 +18,32 @@
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#endif
#include <sys/socket.h>
// Some explanation is required here.
//
// Due to SR-6772, we cannot get Swift code to directly see any of the mmsg structures or
// functions. However, we *can* get C code built by SwiftPM to see them. For this reason we
// elect to provide a selection of shims to enable Swift code to use recv_mmsg and send_mmsg.
// Mostly this is fine, but to minimise the overhead we want the Swift code to be able to
// create the msgvec directly without requiring further memory fussiness in our C shim.
// That requires us to also construct a C structure that has the same layout as struct mmsghdr.
//
// Conveniently glibc has pretty strict ABI stability rules, and this structure is part of the
// glibc ABI, so we can just reproduce the structure definition here and feel confident that it
// will be sufficient.
//
// If SR-6772 ever gets resolved we can remove this shim.
//
// https://bugs.swift.org/browse/SR-6772
typedef struct {
struct msghdr msg_hdr;
unsigned int msg_len;
} CNIOLinux_mmsghdr;
int CNIOLinux_sendmmsg(int sockfd, CNIOLinux_mmsghdr *msgvec, unsigned int vlen, int flags);
int CNIOLinux_recvmmsg(int sockfd, CNIOLinux_mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout);
#endif
#endif

115
Sources/NIO/Linux.swift Normal file
View File

@ -0,0 +1,115 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
// This is a companion to System.swift that provides only Linux specials: either things that exist
// only on Linux, or things that have Linux-specific extensions.
import CNIOLinux
#if os(Linux)
internal enum TimerFd {
public static let TFD_CLOEXEC = CNIOLinux.TFD_CLOEXEC
public static let TFD_NONBLOCK = CNIOLinux.TFD_NONBLOCK
@inline(never)
public static func timerfd_settime(fd: Int32, flags: Int32, newValue: UnsafePointer<itimerspec>, oldValue: UnsafeMutablePointer<itimerspec>?) throws {
_ = try wrapSyscall({
CNIOLinux.timerfd_settime(fd, flags, newValue, oldValue)
})
}
@inline(never)
public static func timerfd_create(clockId: Int32, flags: Int32) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.timerfd_create(clockId, flags)
})
}
}
internal enum EventFd {
public static let EFD_CLOEXEC = CNIOLinux.EFD_CLOEXEC
public static let EFD_NONBLOCK = CNIOLinux.EFD_NONBLOCK
public typealias eventfd_t = CNIOLinux.eventfd_t
@inline(never)
public static func eventfd_write(fd: Int32, value: UInt64) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.eventfd_write(fd, value)
})
}
@inline(never)
public static func eventfd_read(fd: Int32, value: UnsafeMutablePointer<UInt64>) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.eventfd_read(fd, value)
})
}
@inline(never)
public static func eventfd(initval: Int32, flags: Int32) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.eventfd(0, Int32(EFD_CLOEXEC | EFD_NONBLOCK))
})
}
}
internal enum Epoll {
public typealias epoll_event = CNIOLinux.epoll_event
public static let EPOLL_CTL_ADD = CNIOLinux.EPOLL_CTL_ADD
public static let EPOLL_CTL_MOD = CNIOLinux.EPOLL_CTL_MOD
public static let EPOLL_CTL_DEL = CNIOLinux.EPOLL_CTL_DEL
public static let EPOLLIN = CNIOLinux.EPOLLIN
public static let EPOLLOUT = CNIOLinux.EPOLLOUT
public static let EPOLLERR = CNIOLinux.EPOLLERR
public static let EPOLLRDHUP = CNIOLinux.EPOLLRDHUP
public static let EPOLLET = CNIOLinux.EPOLLET
@inline(never)
public static func epoll_create(size: Int32) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.epoll_create(size)
})
}
@inline(never)
public static func epoll_ctl(epfd: Int32, op: Int32, fd: Int32, event: UnsafeMutablePointer<epoll_event>) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.epoll_ctl(epfd, op, fd, event)
})
}
@inline(never)
public static func epoll_wait(epfd: Int32, events: UnsafeMutablePointer<epoll_event>, maxevents: Int32, timeout: Int32) throws -> Int32 {
return try wrapSyscall({
return CNIOLinux.epoll_wait(epfd, events, maxevents, timeout)
})
}
}
internal enum LinuxSocket {
@inline(never)
public static func sendmmsg(sockfd: CInt, msgvec: UnsafeMutablePointer<CNIOLinux_mmsghdr>, vlen: CUnsignedInt, flags: CInt) throws -> IOResult<Int> {
return try wrapSyscallMayBlock({
return Int(CNIOLinux.CNIOLinux_sendmmsg(sockfd, msgvec, vlen, flags))
})
}
@inline(never)
public static func recvmmsg(sockfd: CInt, msgvec: UnsafeMutablePointer<CNIOLinux_mmsghdr>, vlen: CUnsignedInt, flags: CInt, timeout: UnsafeMutablePointer<timespec>?) throws -> IOResult<Int> {
return try wrapSyscallMayBlock({
return Int(CNIOLinux.CNIOLinux_recvmmsg(sockfd, msgvec, vlen, flags, timeout))
})
}
}
#endif

View File

@ -12,8 +12,16 @@
//
//===----------------------------------------------------------------------===//
#if os(Linux)
import struct CNIOLinux.CNIOLinux_mmsghdr
#endif
public typealias IOVector = iovec
#if os(Linux)
internal typealias MMsgHdr = CNIOLinux_mmsghdr
#endif
// TODO: scattering support
final class Socket : BaseSocket {
static var writevLimitBytes: Int {
@ -90,4 +98,22 @@ final class Socket : BaseSocket {
return try Posix.sendfile(descriptor: self.descriptor, fd: fd, offset: offset, count: count)
}
#if os(Linux)
func recvmmsg(msgs: UnsafeMutableBufferPointer<MMsgHdr>) throws -> IOResult<Int> {
guard self.open else {
throw IOError(errnoCode: EBADF, reason: "can't read from socket as it's not open anymore.")
}
return try LinuxSocket.recvmmsg(sockfd: self.descriptor, msgvec: msgs.baseAddress!, vlen: CUnsignedInt(msgs.count), flags: 0, timeout: nil)
}
func sendmmsg(msgs: UnsafeMutableBufferPointer<MMsgHdr>) throws -> IOResult<Int> {
guard self.open else {
throw IOError(errnoCode: EBADF, reason: "can't write to socket as it's not open anymore.")
}
return try LinuxSocket.sendmmsg(sockfd: self.descriptor, msgvec: msgs.baseAddress!, vlen: CUnsignedInt(msgs.count), flags: 0)
}
#endif
}

View File

@ -25,7 +25,6 @@
#else
let badOS = { fatalError("unsupported OS") }()
#endif
import CNIOLinux
private func isBlacklistedErrno(_ code: Int32) -> Bool {
switch code {
@ -40,7 +39,7 @@ private func isBlacklistedErrno(_ code: Int32) -> Bool {
/* Sorry, we really try hard to not use underscored attributes. In this case however we seem to break the inlining threshold which makes a system call take twice the time, ie. we need this exception. */
@inline(__always)
private func wrapSyscallMayBlock<T: FixedWidthInteger>(_ fn: () throws -> T , where function: StaticString = #function) throws -> IOResult<T> {
internal func wrapSyscallMayBlock<T: FixedWidthInteger>(_ fn: () throws -> T , where function: StaticString = #function) throws -> IOResult<T> {
while true {
let res = try fn()
if res == -1 {
@ -62,7 +61,7 @@ private func wrapSyscallMayBlock<T: FixedWidthInteger>(_ fn: () throws -> T , wh
/* Sorry, we really try hard to not use underscored attributes. In this case however we seem to break the inlining threshold which makes a system call take twice the time, ie. we need this exception. */
@inline(__always)
private func wrapSyscall<T: FixedWidthInteger>(_ fn: () throws -> T, where function: StaticString = #function) throws -> T {
internal func wrapSyscall<T: FixedWidthInteger>(_ fn: () throws -> T, where function: StaticString = #function) throws -> T {
while true {
let res = try fn()
if res == -1 {
@ -303,89 +302,10 @@ internal enum Posix {
throw err
}
}
}
#if os(Linux)
internal enum TimerFd {
public static let TFD_CLOEXEC = CNIOLinux.TFD_CLOEXEC
public static let TFD_NONBLOCK = CNIOLinux.TFD_NONBLOCK
@inline(never)
public static func timerfd_settime(fd: Int32, flags: Int32, newValue: UnsafePointer<itimerspec>, oldValue: UnsafeMutablePointer<itimerspec>?) throws {
_ = try wrapSyscall({
CNIOLinux.timerfd_settime(fd, flags, newValue, oldValue)
})
}
@inline(never)
public static func timerfd_create(clockId: Int32, flags: Int32) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.timerfd_create(clockId, flags)
})
}
}
internal enum EventFd {
public static let EFD_CLOEXEC = CNIOLinux.EFD_CLOEXEC
public static let EFD_NONBLOCK = CNIOLinux.EFD_NONBLOCK
public typealias eventfd_t = CNIOLinux.eventfd_t
@inline(never)
public static func eventfd_write(fd: Int32, value: UInt64) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.eventfd_write(fd, value)
})
}
@inline(never)
public static func eventfd_read(fd: Int32, value: UnsafeMutablePointer<UInt64>) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.eventfd_read(fd, value)
})
}
@inline(never)
public static func eventfd(initval: Int32, flags: Int32) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.eventfd(0, Int32(EFD_CLOEXEC | EFD_NONBLOCK))
})
}
}
internal enum Epoll {
public typealias epoll_event = CNIOLinux.epoll_event
public static let EPOLL_CTL_ADD = CNIOLinux.EPOLL_CTL_ADD
public static let EPOLL_CTL_MOD = CNIOLinux.EPOLL_CTL_MOD
public static let EPOLL_CTL_DEL = CNIOLinux.EPOLL_CTL_DEL
public static let EPOLLIN = CNIOLinux.EPOLLIN
public static let EPOLLOUT = CNIOLinux.EPOLLOUT
public static let EPOLLERR = CNIOLinux.EPOLLERR
public static let EPOLLRDHUP = CNIOLinux.EPOLLRDHUP
public static let EPOLLET = CNIOLinux.EPOLLET
@inline(never)
public static func epoll_create(size: Int32) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.epoll_create(size)
})
}
@inline(never)
public static func epoll_ctl(epfd: Int32, op: Int32, fd: Int32, event: UnsafeMutablePointer<epoll_event>) throws -> Int32 {
return try wrapSyscall({
CNIOLinux.epoll_ctl(epfd, op, fd, event)
})
}
@inline(never)
public static func epoll_wait(epfd: Int32, events: UnsafeMutablePointer<epoll_event>, maxevents: Int32, timeout: Int32) throws -> Int32 {
return try wrapSyscall({
return CNIOLinux.epoll_wait(epfd, events, maxevents, timeout)
})
}
}
#else
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
internal enum KQueue {
// TODO: Figure out how to specify a typealias to the kevent struct without run into trouble with the swift compiler