Skip to content

Commit 122a8e4

Browse files
committed
LinuxContainer/LinuxPod: Make time sync configurable
Today our time syncing with the host lives in VZVirtualMachineInstance, which means if a user made their own vmm type they would have to roll their own time syncing. It likely makes more sense to hoist this up to LinuxContainer/LinuxPod so if they want it they can use it, and if they have their own synchronization they can just disable it and use whatever they want. This also lowers the default sync period to 20 seconds.
1 parent 41ffd07 commit 122a8e4

5 files changed

Lines changed: 56 additions & 14 deletions

File tree

Sources/Containerization/LinuxContainer.swift

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public final class LinuxContainer: Container, Sendable {
7878
/// Run the container with a minimal init process that handles signal
7979
/// forwarding and zombie reaping.
8080
public var useInit: Bool = false
81+
/// Interval for syncing the guest clock with the host. Set to nil
82+
/// to disable time synchronization.
83+
public var timeSyncInterval: Duration? = .seconds(20)
8184

8285
public init() {}
8386

@@ -95,7 +98,8 @@ public final class LinuxContainer: Container, Sendable {
9598
virtualization: Bool = false,
9699
bootLog: BootLog? = nil,
97100
ociRuntimePath: String? = nil,
98-
useInit: Bool = false
101+
useInit: Bool = false,
102+
timeSyncInterval: Duration? = .seconds(20)
99103
) {
100104
self.process = process
101105
self.cpus = cpus
@@ -111,6 +115,7 @@ public final class LinuxContainer: Container, Sendable {
111115
self.bootLog = bootLog
112116
self.ociRuntimePath = ociRuntimePath
113117
self.useInit = useInit
118+
self.timeSyncInterval = timeSyncInterval
114119
}
115120
}
116121

@@ -145,6 +150,7 @@ public final class LinuxContainer: Container, Sendable {
145150
let vm: any VirtualMachineInstance
146151
let relayManager: UnixSocketRelayManager
147152
var fileMountContext: FileMountContext
153+
let timeSyncer: TimeSyncer?
148154
}
149155

150156
struct StartedState: Sendable {
@@ -153,13 +159,15 @@ public final class LinuxContainer: Container, Sendable {
153159
let relayManager: UnixSocketRelayManager
154160
var vendedProcesses: [String: LinuxProcess]
155161
let fileMountContext: FileMountContext
162+
let timeSyncer: TimeSyncer?
156163

157164
init(_ state: CreatedState, process: LinuxProcess) {
158165
self.vm = state.vm
159166
self.relayManager = state.relayManager
160167
self.process = process
161168
self.vendedProcesses = [:]
162169
self.fileMountContext = state.fileMountContext
170+
self.timeSyncer = state.timeSyncer
163171
}
164172

165173
init(_ state: PausedState) {
@@ -168,6 +176,7 @@ public final class LinuxContainer: Container, Sendable {
168176
self.process = state.process
169177
self.vendedProcesses = state.vendedProcesses
170178
self.fileMountContext = state.fileMountContext
179+
self.timeSyncer = state.timeSyncer
171180
}
172181
}
173182

@@ -177,13 +186,15 @@ public final class LinuxContainer: Container, Sendable {
177186
let process: LinuxProcess
178187
var vendedProcesses: [String: LinuxProcess]
179188
let fileMountContext: FileMountContext
189+
let timeSyncer: TimeSyncer?
180190

181191
init(_ state: StartedState) {
182192
self.vm = state.vm
183193
self.relayManager = state.relayManager
184194
self.process = state.process
185195
self.vendedProcesses = state.vendedProcesses
186196
self.fileMountContext = state.fileMountContext
197+
self.timeSyncer = state.timeSyncer
187198
}
188199
}
189200

@@ -622,7 +633,16 @@ extension LinuxContainer {
622633
}
623634

624635
}
625-
state = .created(.init(vm: vm, relayManager: relayManager, fileMountContext: fileMountContextHolder.withLock { $0 }))
636+
637+
var timeSyncer: TimeSyncer?
638+
if let interval = self.config.timeSyncInterval {
639+
let ts = TimeSyncer(logger: self.logger)
640+
let tsAgent = try await vm.dialAgent()
641+
await ts.start(context: tsAgent, interval: interval)
642+
timeSyncer = ts
643+
}
644+
645+
state = .created(.init(vm: vm, relayManager: relayManager, fileMountContext: fileMountContextHolder.withLock { $0 }, timeSyncer: timeSyncer))
626646
} catch {
627647
try? await relayManager.stopAll()
628648
try? await vm.stop()
@@ -719,18 +739,28 @@ extension LinuxContainer {
719739

720740
let vm: any VirtualMachineInstance
721741
let relayManager: UnixSocketRelayManager
742+
let timeSyncer: TimeSyncer?
722743

723744
let startedState = try? state.startedState("stop")
724745
if let startedState {
725746
vm = startedState.vm
726747
relayManager = startedState.relayManager
748+
timeSyncer = startedState.timeSyncer
727749
} else {
728750
let createdState = try state.createdState("stop")
729751
vm = createdState.vm
730752
relayManager = createdState.relayManager
753+
timeSyncer = createdState.timeSyncer
731754
}
732755

733756
var firstError: Error?
757+
do {
758+
try await timeSyncer?.close()
759+
} catch {
760+
self.logger?.error("failed to close time syncer: \(error)")
761+
firstError = firstError ?? error
762+
}
763+
734764
do {
735765
try await relayManager.stopAll()
736766
} catch {

Sources/Containerization/LinuxPod.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ public final class LinuxPod: Sendable {
5959
/// The default hosts file configuration for all containers in the pod.
6060
/// Individual containers can override this by setting their own `hosts` configuration.
6161
public var hosts: Hosts?
62+
/// Interval for syncing the guest clock with the host. Set to nil
63+
/// to disable time synchronization.
64+
public var timeSyncInterval: Duration? = .seconds(20)
6265

6366
public init() {}
6467
}
@@ -134,6 +137,7 @@ public final class LinuxPod: Sendable {
134137
struct CreatedState: Sendable {
135138
let vm: any VirtualMachineInstance
136139
let relayManager: UnixSocketRelayManager
140+
let timeSyncer: TimeSyncer?
137141
}
138142

139143
func createdState(_ operation: String) throws -> CreatedState {
@@ -498,7 +502,15 @@ extension LinuxPod {
498502
state.containers[id]?.state = .created
499503
}
500504

501-
state.phase = .created(.init(vm: vm, relayManager: relayManager))
505+
var timeSyncer: TimeSyncer?
506+
if let interval = self.config.timeSyncInterval {
507+
let ts = TimeSyncer(logger: self.logger)
508+
let tsAgent = try await vm.dialAgent()
509+
await ts.start(context: tsAgent, interval: interval)
510+
timeSyncer = ts
511+
}
512+
513+
state.phase = .created(.init(vm: vm, relayManager: relayManager, timeSyncer: timeSyncer))
502514
} catch {
503515
try? await relayManager.stopAll()
504516
try? await vm.stop()
@@ -688,6 +700,7 @@ extension LinuxPod {
688700
let createdState = try state.phase.createdState("stop")
689701

690702
do {
703+
try? await createdState.timeSyncer?.close()
691704
try await createdState.relayManager.stopAll()
692705

693706
// Stop all containers

Sources/Containerization/TimeSyncer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Logging
1919

2020
actor TimeSyncer {
2121
private var task: Task<Void, Never>?
22-
private var context: Vminitd?
22+
private var context: (any VirtualMachineAgent)?
2323
private var paused: Bool
2424
private let logger: Logger?
2525

@@ -28,7 +28,7 @@ actor TimeSyncer {
2828
self.logger = logger
2929
}
3030

31-
func start(context: Vminitd, interval: Duration = .seconds(30)) {
31+
func start(context: some VirtualMachineAgent, interval: Duration = .seconds(20)) {
3232
guard self.task == nil else {
3333
return
3434
}

Sources/Containerization/VZVirtualMachineInstance.swift

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ struct VZVirtualMachineInstance: Sendable {
7474
private let lock: AsyncLock
7575
private let group: EventLoopGroup
7676
private let ownsGroup: Bool
77-
private let timeSyncer: TimeSyncer
7877
private let logger: Logger?
7978

8079
public init(
@@ -101,7 +100,6 @@ struct VZVirtualMachineInstance: Sendable {
101100
self.queue = DispatchQueue(label: "com.apple.containerization.vzvm.\(UUID().uuidString)")
102101
self.mounts = try config.mountAttachments()
103102
self.logger = logger
104-
self.timeSyncer = .init(logger: logger)
105103

106104
self.vm = VZVirtualMachine(
107105
configuration: try config.toVZ(),
@@ -139,9 +137,7 @@ extension VZVirtualMachineInstance: VirtualMachineInstance {
139137
throw error
140138
}
141139

142-
// Don't close our remote context as we are providing
143-
// it to our time sync routine.
144-
await self.timeSyncer.start(context: agent)
140+
try await agent.close()
145141
}
146142
}
147143

@@ -155,8 +151,6 @@ extension VZVirtualMachineInstance: VirtualMachineInstance {
155151
throw ContainerizationError(.invalidState, message: "vm is not running")
156152
}
157153

158-
try await self.timeSyncer.close()
159-
160154
if self.ownsGroup {
161155
try await self.group.shutdownGracefully()
162156
}
@@ -170,15 +164,13 @@ extension VZVirtualMachineInstance: VirtualMachineInstance {
170164

171165
func pause() async throws {
172166
try await lock.withLock { _ in
173-
await self.timeSyncer.pause()
174167
try await self.vm.pause(queue: self.queue)
175168
}
176169
}
177170

178171
func resume() async throws {
179172
try await lock.withLock { _ in
180173
try await self.vm.resume(queue: self.queue)
181-
await self.timeSyncer.resume()
182174
}
183175
}
184176

Sources/Containerization/VirtualMachineAgent.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ public protocol VirtualMachineAgent: Sendable {
7373
func configureDNS(config: DNS, location: String) async throws
7474
func configureHosts(config: Hosts, location: String) async throws
7575

76+
// Time
77+
func setTime(sec: Int64, usec: Int32) async throws
78+
7679
// Container statistics
7780
func containerStatistics(containerIDs: [String], categories: StatCategory) async throws -> [ContainerStatistics]
7881
}
@@ -97,4 +100,8 @@ extension VirtualMachineAgent {
97100
public func sync() async throws {
98101
throw ContainerizationError(.unsupported, message: "sync")
99102
}
103+
104+
public func setTime(sec: Int64, usec: Int32) async throws {
105+
throw ContainerizationError(.unsupported, message: "setTime")
106+
}
100107
}

0 commit comments

Comments
 (0)