Null safety and memory, 5 of 5
Leak reports
Read the report a program prints when it ends with objects still alive, find the cycle, and fix it.
What you’ll build
A small task tracker in which each task knows its project and each project its tasks. Its first version leaks. You’ll read the report that names the cycle, fix it with a weak reference, and see the report go away.
A program that leaks
When a program ends with objects still alive, it prints how many of each type there are, on standard error. A debug build tracks every object, so it also shows each cycle, field by field, from an object back to itself.
Import std.core
Class Project
ReadOnly Field Name: String
Field Tasks: Task[] = []
New(name: String)
Name = name
End New
Method Add(title: String)
Tasks.Add(Task(title, Self))
End Method
End Class
Class Task
ReadOnly Field Title: String
Field Owner: Project
New(title: String, owner: Project)
Title = title
Owner = owner
End New
End Class
Local site = Project("website")
site.Add("write the copy")
site.Add("check the links")
Print("{site.Name} has {site.Tasks.Count} tasks")website has 2 tasks leak: 1 Project object was never released leak: 2 Task objects were never released leak: 1 Task[] object was never released leak: a cycle of 3 objects: Project.Tasks -> Task[] element -> Task.Owner -> the first Project
Each line of the report is a fact about what was left:
- The first three lines count what was still alive when the program ended, by type: the
Project, its two Tasks, and the
Task[]array that holds them. leak: a cycle of 3 objectsfollows the references around one loop. The Project’sTasksholds the array, the array holds a Task, and the Task’sOwnerholds the first Project again.
The cycle says which field to change: one of the links in it has to stop owning what it points
at. A task belongs to its project, not the other way round, so Owner is the link to weaken.
A release build prints the counts alone, without the cycles.
Fixing the cycle
With Owner a Weak<Project>, the tasks no longer keep their project alive. Reading it gives a
Project?, since the project could be gone by then.
Import std.core
Class Project
ReadOnly Field Name: String
Field Tasks: Task[] = []
New(name: String)
Name = name
End New
Method Add(title: String)
Tasks.Add(Task(title, Self))
End Method
End Class
Class Task
ReadOnly Field Title: String
Field Owner: Weak<Project>
New(title: String, owner: Project)
Title = title
Owner = owner
End New
End Class
Local site = Project("website")
site.Add("write the copy")
site.Add("check the links")
Print("{site.Name} has {site.Tasks.Count} tasks")
Print("the first belongs to {site.Tasks[0].Owner?.Name ?? "no project"}")website has 2 tasks the first belongs to website
The program ends without a report: every object was freed.
Leaks in tests
fb test checks for leaks too. In a debug build, a test that leaves objects alive fails, and
lists what it left, so the leak is found by the test that made it. A test’s list counts what was
left, without the cycles a program’s report follows. Here a test catches the first
version’s cycle, and fb test exits with code 1:
Import std.core
Import std.test
Class Project
Field Tasks: Task[] = []
Method Add()
Tasks.Add(Task(Self))
End Method
End Class
Class Task
Field Owner: Project
New(owner: Project)
Owner = owner
End New
End Class
@Test
Function ProjectsFreeTheirTasks()
Local project = Project()
project.Add()
Expect(project.Tasks.Count == 1)
End FunctionRunning 1 test test tasks.ProjectsFreeTheirTasks ... FAILED failures: ---- tasks.ProjectsFreeTheirTasks ---- leak: 1 Project object was never released leak: 1 Task[] object was never released leak: 1 Task object was never released test result: FAILED. 0 passed; 1 failed; 0 skipped; 0 filtered out; finished in 0.02s leak: 1 Project object was never released leak: 1 Task object was never released leak: 1 Task[] object was never released leak: a cycle of 3 objects: Project.Tasks -> Task[] element -> Task.Owner -> the first Project
What you learned
- A program that ends with objects alive prints a leak report, with counts by type.
- A debug build’s report also follows each cycle field by field.
- The cycle shows which reference to make weak: the one that shouldn’t own its object.
fb testfails a test that leaks.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.