@@ -238,17 +238,20 @@ public actor ContainersService {
238238 self . log. info ( " Using init image: \( initImage ?? ClientImage . initImageRef) " )
239239 let initFilesystem = try await self . getInitBlock ( for: systemPlatform. ociPlatform ( ) , imageRef: initImage)
240240
241- let bundle = try ContainerResource . Bundle. create (
242- path: path,
243- initialFilesystem: initFilesystem,
244- kernel: kernel,
245- containerConfiguration: configuration
246- )
247241 do {
248242 let containerImage = ClientImage ( description: configuration. image)
249243 let imageFs = try await containerImage. getCreateSnapshot ( platform: configuration. platform)
250- try bundle. setContainerRootFs ( cloning: imageFs, readonly: configuration. readOnly)
251- try bundle. write ( filename: " options.json " , value: options)
244+
245+ let completeMetadata = BundleMetadata (
246+ path: path,
247+ initialFilesystem: initFilesystem,
248+ kernel: kernel,
249+ containerConfiguration: configuration,
250+ containerRootFilesystem: imageFs,
251+ options: options
252+ )
253+
254+ try ContainerResource . BundleMetadata. writeMetadata ( completeMetadata)
252255
253256 let snapshot = ContainerSnapshot (
254257 configuration: configuration,
@@ -258,11 +261,6 @@ public actor ContainersService {
258261 )
259262 await self . setContainerState ( configuration. id, ContainerState ( snapshot: snapshot) , context: context)
260263 } catch {
261- do {
262- try bundle. delete ( )
263- } catch {
264- self . log. error ( " failed to delete bundle for container \( configuration. id) : \( error) " )
265- }
266264 throw error
267265 }
268266 }
@@ -282,8 +280,7 @@ public actor ContainersService {
282280 }
283281
284282 let path = self . containerRoot. appendingPathComponent ( id)
285- let bundle = ContainerResource . Bundle ( path: path)
286- let config = try bundle. configuration
283+ let config = try await self . getContainerConfiguration ( at: path)
287284
288285 do {
289286 try Self . registerService (
@@ -298,6 +295,7 @@ public actor ContainersService {
298295 id: id,
299296 runtime: runtime
300297 )
298+
301299 try await sandboxClient. bootstrap ( stdio: stdio)
302300
303301 try await self . exitMonitor. registerProcess (
@@ -602,15 +600,33 @@ public actor ContainersService {
602600 // the OCI runtime.
603601 await self . exitMonitor. stopTracking ( id: id)
604602 let path = self . containerRoot. appendingPathComponent ( id)
603+
604+ // Try to get config for service deregistration
605+ // Don't fail if bundle is incomplete
606+ var config : ContainerConfiguration ?
605607 let bundle = ContainerResource . Bundle ( path: path)
606- let config = try bundle. configuration
608+ do {
609+ config = try bundle. configuration
610+ } catch {
611+ self . log. warning ( " Unable to read bundle configuration during cleanup for container \( id) : \( error) " )
612+ }
613+
614+ // Only try to deregister service if we have a valid config
615+ if let config = config {
616+ let label = Self . fullLaunchdServiceLabel (
617+ runtimeName: config. runtimeHandler,
618+ instanceId: id
619+ )
620+ try ? ServiceManager . deregister ( fullServiceLabel: label)
621+ }
622+
623+ // Always try to delete the bundle directory, even if it's incomplete
624+ do {
625+ try bundle. delete ( )
626+ } catch {
627+ self . log. warning ( " Failed to delete bundle for container \( id) : \( error) " )
628+ }
607629
608- let label = Self . fullLaunchdServiceLabel (
609- runtimeName: config. runtimeHandler,
610- instanceId: id
611- )
612- try ServiceManager . deregister ( fullServiceLabel: label)
613- try bundle. delete ( )
614630 self . containers. removeValue ( forKey: id)
615631 }
616632
@@ -675,6 +691,35 @@ public actor ContainersService {
675691 private static func isInitProcess( id: String , processID: String ) -> Bool {
676692 id == processID
677693 }
694+
695+ /// Check if a bundle exists at the given path
696+ private func bundleExists( at path: URL ) async -> ContainerResource . ContainerConfiguration ? {
697+ guard FileManager . default. fileExists ( atPath: path. path) else {
698+ return nil
699+ }
700+
701+ let bundle = ContainerResource . Bundle ( path: path)
702+ do {
703+ let config = try bundle. configuration
704+ return config
705+ } catch {
706+ return nil
707+ }
708+ }
709+
710+ /// Get container configuration, either from existing bundle or from metadata
711+ private func getContainerConfiguration( at path: URL ) async throws -> ContainerConfiguration {
712+ guard let config = await bundleExists ( at: path) else {
713+ // Bundle doesn't exist, get config from metadata
714+ let metadata = try ContainerResource . BundleMetadata. readMetadata ( from: path)
715+ guard let config = metadata. containerConfiguration else {
716+ throw ContainerizationError ( . internalError, message: " metadata missing container configuration " )
717+ }
718+ return config
719+ }
720+ // Bundle exists, read config normally
721+ return config
722+ }
678723}
679724
680725extension XPCMessage {
0 commit comments