Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions Sources/HAP/Server/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,25 @@ public class Server: NSObject, NetServiceDelegate {
let listenSocket: Socket
let socketLockQueue = DispatchQueue(label: "hap.socketLockQueue")

var continueRunning = true
let continueRunningLock = DispatchSemaphore(value: 1)

// swiftlint:disable:next identifier_name
var _continueRunning = false
var continueRunning: Bool {
get {
continueRunningLock.wait()
defer {
continueRunningLock.signal()
}
return _continueRunning
}
set {
continueRunningLock.wait()
_continueRunning = newValue
continueRunningLock.signal()
}

}
var connectedSockets = [Int32: Socket]()

public init(device: Device, port: Int = 0) throws {
Expand Down Expand Up @@ -51,6 +69,9 @@ public class Server: NSObject, NetServiceDelegate {
}

public func start() {
if #available(OSX 10.12, *) {
dispatchPrecondition(condition: .onQueue(.main))
}
// TODO: make sure can only be started if not started

continueRunning = true
Expand All @@ -62,27 +83,44 @@ public class Server: NSObject, NetServiceDelegate {
do {
repeat {
let newSocket = try self.listenSocket.acceptClientConnection()
logger.info("Accepted connection from \(newSocket.remoteHostname)")
DispatchQueue.main.async {
logger.info("Accepted connection from \(newSocket.remoteHostname)")
}
self.addNewConnection(socket: newSocket)
} while self.continueRunning
} catch {
logger.error("Could not accept connections for listening socket", error: error)
}
self.stop()
self.tearDownConnections()
self.listenSocket.close()
}
}

public func stop() {
func tearDownConnections() {
service.stop()
continueRunning = false
listenSocket.close()

socketLockQueue.sync { [unowned self] in
for socket in self.connectedSockets {
socket.value.close()
Socket.forceClose(socketfd: socket.key)
}
self.connectedSockets = [:]
}

}

public func stop() {
if #available(OSX 10.12, *) {
dispatchPrecondition(condition: .onQueue(.main))
}
continueRunning = false

// Ideally we would call `.close()` on all open sockets. However
// BlueSocket doesn't like us doing that from another thread than
// the one that's currently listening. As a workaround, we'll
// force close the file descriptor instead.

Socket.forceClose(socketfd: listenSocket.socketfd)
}

func addNewConnection(socket: Socket) {
Expand Down
27 changes: 27 additions & 0 deletions Sources/HAP/Utils/Socket+forceClose.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation
import Socket

#if os(macOS) || os(iOS) || os(tvOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif

extension Socket {

// Avoid race conditions in BlueSocket when closing a socket from another
// thread by closing the sockets file descriptor using system calls.
//
// Assumes socket is being listened to and is connected
//
// swiftlint:disable:next identifier_name
static func forceClose(socketfd fd: Int32) {
#if os(Linux)
_ = Glibc.shutdown(fd, Int32(SHUT_RDWR))
_ = Glibc.close(fd)
#else
_ = Darwin.shutdown(fd, Int32(SHUT_RDWR))
_ = Darwin.close(fd)
#endif
}
}