diff --git a/Package.resolved b/Package.resolved index 13cbccb..8ff9b80 100644 --- a/Package.resolved +++ b/Package.resolved @@ -54,6 +54,15 @@ "revision" : "f05e450f0b909c0e80670a47516c4b9700b9e5da" } }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "919eb1d83e02121cdb434c7bfc1f0c66ef17febe", + "version" : "1.0.2" + } + }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index d80b130..f199157 100644 --- a/Package.swift +++ b/Package.swift @@ -17,6 +17,7 @@ let package = Package( .package(url: "https://github.com/malcommac/SwiftDate", from: "6.3.1"), .package(url: "https://github.com/sushichop/Puppy", from: "0.5.1"), .package(url: "https://github.com/antlr/antlr4", branch: "dev"), + .package(url: "https://github.com/apple/swift-atomics.git", .upToNextMajor(from: "1.0.0")), ], targets: [ .executableTarget(name: "tart", dependencies: [ @@ -27,6 +28,7 @@ let package = Package( .product(name: "SwiftDate", package: "SwiftDate"), .product(name: "Puppy", package: "Puppy"), .product(name: "Antlr4Static", package: "Antlr4"), + .product(name: "Atomics", package: "swift-atomics"), ], exclude: [ "OCI/Reference/Makefile", "OCI/Reference/Reference.g4", diff --git a/Sources/tart/Network/Network.swift b/Sources/tart/Network/Network.swift index 7149624..7883305 100644 --- a/Sources/tart/Network/Network.swift +++ b/Sources/tart/Network/Network.swift @@ -2,6 +2,6 @@ import Virtualization protocol Network { func attachment() -> VZNetworkDeviceAttachment - func run() throws - func stop() throws + func run(_ sema: DispatchSemaphore) throws + func stop() async throws } diff --git a/Sources/tart/Network/NetworkBridged.swift b/Sources/tart/Network/NetworkBridged.swift index 6fe7fb1..eb1e379 100644 --- a/Sources/tart/Network/NetworkBridged.swift +++ b/Sources/tart/Network/NetworkBridged.swift @@ -12,11 +12,11 @@ class NetworkBridged: Network { VZBridgedNetworkDeviceAttachment(interface: interface) } - func run() throws { + func run(_ sema: DispatchSemaphore) throws { // no-op, only used for Softnet } - func stop() throws { + func stop() async throws { // no-op, only used for Softnet } } diff --git a/Sources/tart/Network/NetworkShared.swift b/Sources/tart/Network/NetworkShared.swift index 9abacc6..44f01d5 100644 --- a/Sources/tart/Network/NetworkShared.swift +++ b/Sources/tart/Network/NetworkShared.swift @@ -6,11 +6,11 @@ class NetworkShared: Network { VZNATNetworkDeviceAttachment() } - func run() throws { + func run(_ sema: DispatchSemaphore) throws { // no-op, only used for Softnet } - func stop() throws { + func stop() async throws { // no-op, only used for Softnet } } diff --git a/Sources/tart/Network/Softnet.swift b/Sources/tart/Network/Softnet.swift index 1144688..6f371e9 100644 --- a/Sources/tart/Network/Softnet.swift +++ b/Sources/tart/Network/Softnet.swift @@ -1,12 +1,16 @@ import Foundation import Virtualization +import Atomics enum SoftnetError: Error { case InitializationFailed(why: String) + case RuntimeFailed(why: String) } class Softnet: Network { private let process = Process() + private var monitorTask: Task? = nil + private let monitorTaskFinished = ManagedAtomic(false) let vmFD: Int32 @@ -35,13 +39,33 @@ class Softnet: Network { process.standardInput = FileHandle(fileDescriptor: softnetFD, closeOnDealloc: false) } - func run() throws { + func run(_ sema: DispatchSemaphore) throws { try process.run() + + monitorTask = Task { + // Wait for the Softnet to finish + process.waitUntilExit() + + // Signal to the caller that the Softnet has finished + sema.signal() + + // Signal to ourselves that the Softnet has finished + monitorTaskFinished.store(true, ordering: .sequentiallyConsistent) + } } - func stop() throws { - process.interrupt() - process.waitUntilExit() + func stop() async throws { + if monitorTaskFinished.load(ordering: .sequentiallyConsistent) { + // Consume the monitor task's value to ensure the task has finished + _ = try await monitorTask?.value + + throw SoftnetError.RuntimeFailed(why: "Softnet process terminated prematurely") + } else { + process.interrupt() + + // Consume the monitor task's value to ensure the task has finished + _ = try await monitorTask?.value + } } private func setSocketBuffers(_ fd: Int32, _ sizeBytes: Int) throws { diff --git a/Sources/tart/VM.swift b/Sources/tart/VM.swift index 62b55c6..12e6c15 100644 --- a/Sources/tart/VM.swift +++ b/Sources/tart/VM.swift @@ -209,7 +209,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { } func run(_ recovery: Bool) async throws { - try network.run() + try network.run(sema) DispatchQueue.main.sync { Task { @@ -239,7 +239,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { } } - try network.stop() + try await network.stop() } static func craftConfiguration(