From e8aff29be3b97afa18ccc256126466ae5e611bea Mon Sep 17 00:00:00 2001 From: Danny Canter Date: Tue, 9 Dec 2025 09:47:38 -0800 Subject: [PATCH] Cgroup2Manager: Fix cgroup deletions (#439) If there's any nested cgroups in the one we made for the container (commonly seen for systemd images) removeItem didn't seem to be having a grand time, even though it states it should do recursive removals. Lets roll our own, and have a small EBUSY/EAGAIN retry loop as well. This fixes LinuxContainer.stop() for any containers with nested cgs. Context: https://github.com/apple/container/issues/928 --- vminitd/Sources/Cgroup/Cgroup2Manager.swift | 39 +++++++++++++++++- .../Sources/vminitd/ManagedContainer.swift | 41 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/vminitd/Sources/Cgroup/Cgroup2Manager.swift b/vminitd/Sources/Cgroup/Cgroup2Manager.swift index 7e7eac2f..fcfb03d9 100644 --- a/vminitd/Sources/Cgroup/Cgroup2Manager.swift +++ b/vminitd/Sources/Cgroup/Cgroup2Manager.swift @@ -285,7 +285,44 @@ package struct Cgroup2Manager: Sendable { if force { try self.kill() } - try FileManager.default.removeItem(at: self.path) + + // Recursively remove child cgroups first + try removeChildCgroups(at: self.path, force: force) + + let result = rmdir(self.path.path) + if result != 0 { + throw Error.errno(errno: errno, message: "failed to remove cgroup directory \(self.path.path)") + } + } + + private func removeChildCgroups(at path: URL, force: Bool) throws { + let fileManager = FileManager.default + + guard let contents = try? fileManager.contentsOfDirectory(atPath: path.path) else { + return + } + + // Remove child directories (potential nested cgroups) first + for item in contents { + let childPath = path.appending(path: item) + var isDirectory: ObjCBool = false + + if fileManager.fileExists(atPath: childPath.path, isDirectory: &isDirectory) && isDirectory.boolValue { + if force { + try Self.writeValue( + path: childPath, + value: "1", + fileName: Self.killFile + ) + } + + try removeChildCgroups(at: childPath, force: force) + let result = rmdir(childPath.path) + if result != 0 { + throw Error.errno(errno: errno, message: "failed to remove child cgroup \(childPath.path)") + } + } + } } package func stats() throws -> Cgroup2Stats { diff --git a/vminitd/Sources/vminitd/ManagedContainer.swift b/vminitd/Sources/vminitd/ManagedContainer.swift index d60973cb..91bff0fb 100644 --- a/vminitd/Sources/vminitd/ManagedContainer.swift +++ b/vminitd/Sources/vminitd/ManagedContainer.swift @@ -110,6 +110,45 @@ actor ManagedContainer { } extension ManagedContainer { + // removeCgroupWithRetry will remove a cgroup path handling EAGAIN and EBUSY errors and + // retrying the remove after an exponential timeout + private func removeCgroupWithRetry() async throws { + var delay = 10 // 10ms + let maxRetries = 5 + + for i in 0..