Skip to content

Commit

Permalink
THRIFT-4838: Add unix socket support for Swift
Browse files Browse the repository at this point in the history
  • Loading branch information
kinoroy committed Mar 5, 2023
1 parent 074a934 commit 5179bee
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Expand Up @@ -480,6 +480,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"]
}
}
]

0 comments on commit 5179bee

Please sign in to comment.