mirror of https://github.com/cirruslabs/tart.git
Merge e898ce6297 into 4ce8a115f7
This commit is contained in:
commit
ee0b92bdf7
|
|
@ -570,8 +570,10 @@ struct Run: AsyncParsableCommand {
|
|||
}
|
||||
|
||||
if #available(macOS 14, *) {
|
||||
let controlSocket = try await ControlSocket(vmDir.controlSocketURL)
|
||||
|
||||
ErrorReportingTask("Failed to run control socket") {
|
||||
try await ControlSocket(vmDir.controlSocketURL).run()
|
||||
try await controlSocket.run()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,20 @@ import NIOPosix
|
|||
|
||||
@available(macOS 14, *)
|
||||
class ControlSocket {
|
||||
typealias ServerChannel = NIOAsyncChannel<NIOAsyncChannel<ByteBuffer, ByteBuffer>, Never>
|
||||
|
||||
let controlSocketURL: URL
|
||||
let vmPort: UInt32
|
||||
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
let eventLoopGroup: MultiThreadedEventLoopGroup
|
||||
let serverChannel: ServerChannel
|
||||
let logger: os.Logger = os.Logger(subsystem: "org.cirruslabs.tart.control-socket", category: "network")
|
||||
|
||||
init(_ controlSocketURL: URL, vmPort: UInt32 = 8080) {
|
||||
init(_ controlSocketURL: URL, vmPort: UInt32 = 8080) async throws {
|
||||
self.controlSocketURL = controlSocketURL
|
||||
self.vmPort = vmPort
|
||||
}
|
||||
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
self.eventLoopGroup = eventLoopGroup
|
||||
|
||||
func run() async throws {
|
||||
// Remove control socket file from previous "tart run" invocations,
|
||||
// if any, otherwise we may get the "address already in use" error
|
||||
try? FileManager.default.removeItem(atPath: controlSocketURL.path())
|
||||
|
|
@ -29,15 +32,22 @@ class ControlSocket {
|
|||
FileManager.default.changeCurrentDirectoryPath(baseURL.path())
|
||||
}
|
||||
|
||||
let serverChannel = try await ServerBootstrap(group: eventLoopGroup)
|
||||
.bind(unixDomainSocketPath: controlSocketURL.relativePath) { childChannel in
|
||||
childChannel.eventLoop.makeCompletedFuture {
|
||||
return try NIOAsyncChannel<ByteBuffer, ByteBuffer>(
|
||||
wrappingChannelSynchronously: childChannel
|
||||
)
|
||||
do {
|
||||
self.serverChannel = try await ServerBootstrap(group: eventLoopGroup)
|
||||
.bind(unixDomainSocketPath: controlSocketURL.relativePath) { childChannel in
|
||||
childChannel.eventLoop.makeCompletedFuture {
|
||||
return try NIOAsyncChannel<ByteBuffer, ByteBuffer>(
|
||||
wrappingChannelSynchronously: childChannel
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
try? await eventLoopGroup.shutdownGracefully()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func run() async throws {
|
||||
try await withThrowingDiscardingTaskGroup { group in
|
||||
try await serverChannel.executeThenClose { serverInbound in
|
||||
for try await clientChannel in serverInbound {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
import XCTest
|
||||
@testable import tart
|
||||
|
||||
@available(macOS 14, *)
|
||||
final class ControlSocketTests: XCTestCase {
|
||||
func testInitializerCreatesControlSocketBeforeReturning() async throws {
|
||||
let temporaryDirectory = try makeTemporaryDirectory()
|
||||
let originalDirectory = FileManager.default.currentDirectoryPath
|
||||
defer {
|
||||
FileManager.default.changeCurrentDirectoryPath(originalDirectory)
|
||||
try? FileManager.default.removeItem(at: temporaryDirectory)
|
||||
}
|
||||
|
||||
let socketURL = URL(fileURLWithPath: "control.sock", relativeTo: temporaryDirectory)
|
||||
var controlSocket: ControlSocket? = try await ControlSocket(socketURL)
|
||||
let eventLoopGroup = try XCTUnwrap(controlSocket?.eventLoopGroup)
|
||||
|
||||
do {
|
||||
let serverChannel = try XCTUnwrap(controlSocket?.serverChannel)
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: socketURL.path))
|
||||
|
||||
try await serverChannel.executeThenClose { _ in }
|
||||
}
|
||||
|
||||
controlSocket = nil
|
||||
try await eventLoopGroup.shutdownGracefully()
|
||||
}
|
||||
|
||||
func testInitializerPropagatesControlSocketCreationFailure() async throws {
|
||||
let temporaryDirectory = try makeTemporaryDirectory()
|
||||
let originalDirectory = FileManager.default.currentDirectoryPath
|
||||
defer {
|
||||
FileManager.default.changeCurrentDirectoryPath(originalDirectory)
|
||||
try? FileManager.default.removeItem(at: temporaryDirectory)
|
||||
}
|
||||
|
||||
let socketURL = URL(fileURLWithPath: "missing/control.sock", relativeTo: temporaryDirectory)
|
||||
|
||||
do {
|
||||
_ = try await ControlSocket(socketURL)
|
||||
XCTFail("Binding should fail when the socket's parent directory does not exist")
|
||||
} catch {
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath: socketURL.path))
|
||||
}
|
||||
}
|
||||
|
||||
private func makeTemporaryDirectory() throws -> URL {
|
||||
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(
|
||||
UUID().uuidString,
|
||||
isDirectory: true
|
||||
)
|
||||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false)
|
||||
return directory
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue