Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

THRIFT-4838: Add unix socket support for Swift #2766

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Expand Up @@ -483,6 +483,9 @@ jobs:
test/rs/bin/* \
test/go/bin/*

- name: Create tmp domain socket folder
run: mkdir /tmp/v0.16

- name: Run cross test
env:
THRIFT_CROSSTEST_CONCURRENCY: 4
Expand Down
4 changes: 2 additions & 2 deletions LANGUAGES.md
Expand Up @@ -344,8 +344,8 @@ Thrift's core protocol is TBinary, supported by all languages except for JavaScr
<!-- Since -----------------><td>0.12.0</td>
<!-- Build Systems ---------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Language Levels -------><td colspan=2>5.7</td>
<!-- Field types -----------><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Low-Level Transports --><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
<!-- Field types -----------><td><img src="/doc/images/cgrn.png" alt=""/></td>
<!-- Low-Level Transports --><td><img src="/doc/images/cgrn.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
<!-- Transport Wrappers ----><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Protocols -------------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
<!-- Servers ---------------><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
Expand Down
2 changes: 1 addition & 1 deletion lib/swift/Sources/LinuxHelper.swift
Expand Up @@ -27,7 +27,7 @@ import CoreFoundation

extension UInt {
public static func &(lhs: UInt, rhs: Int) -> UInt {
let cast = unsafeBitCast(rhs, to: UInt.self)
let cast = UInt(bitPattern: rhs)
return lhs & cast
}
}
Expand Down
44 changes: 44 additions & 0 deletions lib/swift/Sources/TSocketServer.swift
Expand Up @@ -102,6 +102,49 @@ open class TSocketServer<InProtocol: TProtocol, OutProtocol: TProtocol, Processo
// tell socket to listen
acceptConnectionInBackgroundAndNotify(handle: socketFileHandle)
}

public init(path: String,
inProtocol: InProtocol.Type,
outProtocol: OutProtocol.Type,
processor: Processor) throws {
self.processor = processor
// create a socket
let socket = UnixSocket(path: path)
let fd = socket.fd

if fd == -1 {
print("TSocketServer: No server socket")
throw TTransportError(error: .notOpen, message: "Could not create socket")
}

// wrap it in a file handle so we can get messages from it
socketFileHandle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)

// register for notifications of accepted incoming connections
_ = NotificationCenter.default.addObserver(forName: .NSFileHandleConnectionAccepted,
object: nil, queue: nil) {
[weak self] notification in
guard let strongSelf = self else { return }
guard let clientSocket = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
strongSelf.connectionAccepted(clientSocket)
}

let bindRes = socket.bind()
guard bindRes == 0 else {
print("TServerSocket: bind failed")
throw TTransportError(error: .notOpen, message: "Could not create socket")
}
let listenRes = listen(fd, 1024)
guard listenRes == 0 else {
print("TServerSocket: listen failed")
throw TTransportError(error: .notOpen, message: "Could not create socket")
}

// tell socket to listen
acceptConnectionInBackgroundAndNotify(handle: socketFileHandle)

print("TSocketServer: Listening on unix path \(path)")
}

private func acceptConnectionInBackgroundAndNotify(handle: FileHandle) {
DispatchQueue(label: "TSocketServer.connectionAccept").async {
Expand All @@ -111,6 +154,7 @@ open class TSocketServer<InProtocol: TProtocol, OutProtocol: TProtocol, Processo
}
}
}

func connectionAccepted(_ clientSocket: FileHandle) {
// Now that we have a client connected, handle the request on queue
processingQueue.async {
Expand Down
9 changes: 9 additions & 0 deletions lib/swift/Sources/TSocketTransport.swift
Expand Up @@ -173,6 +173,15 @@ public class TSocketTransport : TTransport {

self.init(socketDescriptor: sock)
}

public convenience init(path: String) throws {
let socket = UnixSocket(path: path)
let errno = socket.connect()
guard errno == 0 else {
throw TTransportError(error: .notOpen, message: "Error binding to socket at path: \(path). Errno: \(errno)")
}
self.init(socketDescriptor: socket.fd)
}

deinit {
close()
Expand Down
92 changes: 92 additions & 0 deletions lib/swift/Sources/UnixSocket.swift
@@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
import Darwin
#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android)
import Glibc
import Dispatch
#endif
import Foundation

private struct Sys {
#if os(Linux)
static let read = Glibc.read
static let write = Glibc.write
static let close = Glibc.close
static let socket = Glibc.socket
static let connect = Glibc.connect
static let bind = Glibc.bind
static let recv = Glibc.recv
#else
static let read = Darwin.read
static let write = Darwin.write
static let close = Darwin.close
static let socket = Darwin.socket
static let connect = Darwin.connect
static let bind = Darwin.bind
static let recv = Darwin.recv
#endif
}


public class UnixSocket {
public var fd: Int32
private var socketAddress: sockaddr_un

public init(path: String) {
socketAddress = sockaddr_un()
socketAddress.sun_family = sa_family_t(AF_UNIX)

let lengthOfPath = path.withCString { Int(strlen($0)) }

guard lengthOfPath < MemoryLayout.size(ofValue: socketAddress.sun_path) else {
fatalError()
}

_ = withUnsafeMutablePointer(to: &socketAddress.sun_path.0) { ptr in
path.withCString {
strncpy(ptr, $0, lengthOfPath)
}
}

#if os(Linux)
fd = Sys.socket(AF_UNIX, 1 /*SOCK_STREAM*/, 0);
#else
fd = Sys.socket(AF_UNIX, SOCK_STREAM, 0);
#endif

}
public func connect() -> Int32 {
let socketAddressCasted = withUnsafePointer(to: &socketAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
return $0
}
}
return Sys.connect(fd, socketAddressCasted, socklen_t(MemoryLayout<sockaddr_un>.size(ofValue: socketAddress)))
}
public func bind() -> Int32 {
let socketAddressCasted = withUnsafePointer(to: &socketAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
return $0
}
}
return Sys.bind(fd, socketAddressCasted, socklen_t(MemoryLayout<sockaddr_un>.size(ofValue: socketAddress)))
}
}
7 changes: 6 additions & 1 deletion test/swift/CrossTests/Sources/TestClient/main.swift
Expand Up @@ -40,7 +40,12 @@ class TestClient {
}

static func getTransport(parameters: TestClientParameters) throws -> TTransport {
let socketTransport = try TSocketTransport(hostname: parameters.host!, port: parameters.port!)
let socketTransport: TTransport = try { () throws -> TTransport in
if let domainSocket = parameters.domainSocket {
return try TSocketTransport(path: domainSocket)
}
return try TSocketTransport(hostname: parameters.host!, port: parameters.port!)
}()
if parameters.transport == .framed {
return TFramedTransport(transport: socketTransport)
}
Expand Down
22 changes: 17 additions & 5 deletions test/swift/CrossTests/Sources/TestServer/main.swift
Expand Up @@ -34,19 +34,31 @@ class TestServer {
let processor = ThriftTestProcessor(service: service)


switch (parameters.proto, parameters.transport) {
case (.binary, .buffered):
switch (parameters.proto, parameters.transport, parameters.domainSocket) {
case (.binary, .buffered, .none):
let proto = TBinaryProtocol.self
server = try TSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
case (.binary, .framed):
case (.binary, .framed, .none):
let proto = TBinaryProtocol.self
server = try TFramedSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
case (.compact, .buffered):
case (.compact, .buffered, .none):
let proto = TCompactProtocol.self
server = try TSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
case (.compact, .framed):
case (.compact, .framed, .none):
let proto = TCompactProtocol.self
server = try TFramedSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
case (.binary, .buffered, .some(let domainSocket)):
let proto = TBinaryProtocol.self
server = try TSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
case (.binary, .framed, .some(let domainSocket)):
let proto = TBinaryProtocol.self
server = try TFramedSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
case (.compact, .buffered, .some(let domainSocket)):
let proto = TCompactProtocol.self
server = try TSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
case (.compact, .framed, .some(let domainSocket)):
let proto = TCompactProtocol.self
server = try TFramedSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
default:
throw ParserError.unsupportedOption
}
Expand Down
4 changes: 2 additions & 2 deletions test/tests.json
Expand Up @@ -765,14 +765,14 @@
"workdir": "swift/CrossTests/.build/x86_64-unknown-linux-gnu/debug",
"protocols": ["binary", "compact"],
"transports": ["buffered", "framed"],
"sockets": ["ip"]
"sockets": ["ip", "domain"]
},
"client": {
"command": ["TestClient"],
"workdir": "swift/CrossTests/.build/x86_64-unknown-linux-gnu/debug",
"protocols": ["binary", "compact"],
"transports": ["buffered", "framed"],
"sockets": ["ip"]
"sockets": ["ip", "domain"]
}
}
]