Hi everyone,
I have encountered a strange Swift runtime crash/freeze when large data structures created from recursive enums with associated values are automatically deallocated. I am unable to debug this issue and I was wondering if anyone has some advice on what to do here?
Here's my code:
enum MyList {
case empty
indirect case pair(MyList, MyList)
}
var lst = MyList.empty
for _ in 0..<150000 {
lst = .pair(.empty, lst)
}
print("Done creating list")
lst = .empty
print("Completed program (this is never printed)")
This code uses an enum to represent a simple linear list. The loop creates 150,000 linearly linked nodes and ultimately prints "Done creating list". The following statement (which implicitly deallocates the long list) never returns. This is where I am stuck. It seems to be impossible to deallocate such a data structure without the Swift runtime freezing.
Any help is appreciated!
== Matthias
It seems to be impossible to deallocate such a data structure without the Swift runtime freezing.
True. Deallocating an enum instance with indirect child instances would cause deallocating the children recursively.
So, when deallocating long lasting list as in your code, it would make too deep recursive calls which may result any sort of unpredictable behaviors, crashing, freezing or anything you can imagine.
You may need to deallocate all the nodes in the list without consuming the native call stack -- you need non-recursive algorithm.
You cannot control the deallocations of structs and enums in Swift, so you may need to make it a class
and implement deinit
carefully.
My first trial looks like this:
class ListNode {
var left: ListNode?
var right: ListNode?
init(_ left: ListNode?, _ right: ListNode?) {
self.left = left
self.right = right
}
deinit {
if left != nil || right != nil {
var nodesToRelease: [ListNode] = [self]
ListNode.deallocateAll(&nodesToRelease)
}
}
private static func deallocateAll(_ nodesToRelease: inout [ListNode]) {
while let top = nodesToRelease.popLast() {
if let leftNode = top.left {
nodesToRelease.append(leftNode)
top.left = nil
}
if let rightNode = top.right {
nodesToRelease.append(rightNode)
top.right = nil
}
}
}
}
var node: ListNode? = nil
for _ in 0..<150_000 {
node = ListNode(nil, node)
}
print("Done creating list")
node = nil
print("Completed program (this is printed within a second)")
This shows Completed program (this is printed within a second)
within a second.
Changing 150_000
to 150_000_000
, you can observe increasing of the memory consumption first, and then decreasing after Done creating list
.
(Takes a few minutes to finish.)
I haven't tried many cases, so there may be some flaws in my code and may not be efficient. But I think my code is well explaining the problem.
If you know the recursion level of the data might get hundreds or more, you should better not use enum with indirect children.