From d800334400af610ff631030155b5993b803c4204 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Sun, 23 Feb 2025 15:45:41 -0800 Subject: [PATCH] Reinstate additional leak detection --- Sources/Nodes/Core/AbstractContext.swift | 9 +++++ Sources/Nodes/Core/AbstractFlow.swift | 2 ++ Sources/Nodes/Utility/LeakDetector.swift | 45 ++++++++++++++++++++---- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/Sources/Nodes/Core/AbstractContext.swift b/Sources/Nodes/Core/AbstractContext.swift index 0931cf135..d226cd298 100644 --- a/Sources/Nodes/Core/AbstractContext.swift +++ b/Sources/Nodes/Core/AbstractContext.swift @@ -144,6 +144,7 @@ open class _BaseContext: Context { // swiftlint:disable:this type_name if isActive { assertionFailure("Lifecycle Violation: Expected `AbstractContext` to deactivate before it is deallocated.") } + LeakDetector.detect(self) { $0.workerController } #endif } } @@ -205,6 +206,14 @@ open class AbstractPresentableContext: Flow { if _isStarted { assertionFailure("Lifecycle Violation: Expected `AbstractFlow` to end before it is deallocated.") } + LeakDetector.detect(self) { $0._context } + LeakDetector.detect(flowController) #endif } } diff --git a/Sources/Nodes/Utility/LeakDetector.swift b/Sources/Nodes/Utility/LeakDetector.swift index 79f6a0fc6..b8a6a032c 100644 --- a/Sources/Nodes/Utility/LeakDetector.swift +++ b/Sources/Nodes/Utility/LeakDetector.swift @@ -34,12 +34,15 @@ public enum LeakDetector { return (info.kp_proc.p_flag & P_TRACED) != 0 } - /// Detects whether the given `object` deallocates from memory as expected. + /// Detects whether the given `object` instance deallocates from memory as expected. /// /// - Parameters: /// - object: The instance with which to detect the expected deallocation. /// - delay: The time interval in seconds to wait before leak detection occurs. - public static func detect(_ object: AnyObject, delay: TimeInterval = 1) { + public static func detect( + _ object: AnyObject, + delay: TimeInterval = 1 + ) { struct WeakBox: @unchecked Sendable { weak var object: AnyObject? } @@ -51,8 +54,8 @@ public enum LeakDetector { else { return } let message: String = "Expected object to deallocate: \(object)" DispatchQueue.main.async { - if let callStack: String = callStackSymbols?.joined(separator: "\n") { - print(callStack) + if let callStackSymbols: [String] { + print(callStackSymbols.joined(separator: "\n")) } guard isDebuggedProcessBeingTraced else { return assertionFailure(message) } @@ -62,6 +65,24 @@ public enum LeakDetector { } } + /// Detects whether the instance returned by the given `object` closure deallocates from memory as expected. + /// + /// - Parameters: + /// - root: The root instance to be passed to the `object` closure. + /// - delay: The time interval in seconds to wait before leak detection occurs. + /// - object: The closure returning the instance with which to detect the expected deallocation. + internal static func detect( + _ root: Root, + delay: TimeInterval = 1, + _ object: @MainActor (Root) -> AnyObject + ) { + typealias Object = (Root) -> AnyObject + withoutActuallyEscaping(object) { object in + let object: Object = unsafeBitCast(object, to: Object.self) + detect(object(root), delay: delay) + } + } + // swiftlint:disable:next discouraged_optional_collection private static func callStackSymbols() -> [String]? { let environment: [String: String] = ProcessInfo.processInfo.environment @@ -72,8 +93,20 @@ public enum LeakDetector { #else - // swiftlint:disable:next unused_parameter - public static func detect(_ object: AnyObject, delay: TimeInterval = 1) {} + // swiftlint:disable unused_parameter + + public static func detect( + _ object: AnyObject, + delay: TimeInterval = 1 + ) {} + + public static func detect( + _ root: Root, + delay: TimeInterval = 1, + _ object: @MainActor (Root) -> AnyObject + ) {} + + // swiftlint:enable unused_parameter #endif }