0
# Reference Chain Analysis
1
2
Tools for tracing reference chains between objects to understand how objects are connected, what keeps them alive, and how to reach specific objects. These functions help diagnose memory leaks and understand object relationships.
3
4
## Capabilities
5
6
### Forward Reference Chain Finding
7
8
Find a path from a starting object to any object matching a predicate by following object references.
9
10
```python { .api }
11
def find_ref_chain(obj, predicate, max_depth=20, extra_ignore=()):
12
"""
13
Find a shortest chain of references leading from obj.
14
15
Args:
16
obj: Starting object for the search
17
predicate (callable): Function taking object and returning bool; target match condition
18
max_depth (int): Maximum search depth to prevent infinite recursion
19
extra_ignore (tuple): Object IDs to exclude from the search
20
21
Returns:
22
list: Reference chain from obj to matching object, or [obj] if no chain found
23
24
Note:
25
The end of the chain will be some object that matches your predicate.
26
Returns the chain in forward direction (from source to target).
27
"""
28
```
29
30
Usage examples:
31
32
```python
33
import objgraph
34
import sys
35
36
# Find path from an object to any module
37
my_object = [1, 2, 3]
38
chain = objgraph.find_ref_chain(my_object, lambda x: objgraph.is_proper_module(x))
39
print(f"Chain length: {len(chain)}")
40
for i, obj in enumerate(chain):
41
print(f" {i}: {type(obj).__name__} - {repr(obj)[:50]}")
42
43
# Find path to a specific class
44
class MyTarget:
45
pass
46
47
target = MyTarget()
48
some_container = {'target': target, 'other': 'data'}
49
50
chain = objgraph.find_ref_chain(
51
some_container,
52
lambda x: isinstance(x, MyTarget)
53
)
54
if len(chain) > 1:
55
print("Found path to MyTarget instance")
56
57
# Find path to any function
58
def sample_function():
59
pass
60
61
namespace = {'func': sample_function, 'data': [1, 2, 3]}
62
chain = objgraph.find_ref_chain(
63
namespace,
64
lambda x: callable(x) and hasattr(x, '__name__')
65
)
66
67
# Ignore specific objects in the search
68
frame = sys._getframe()
69
chain = objgraph.find_ref_chain(
70
my_object,
71
lambda x: objgraph.is_proper_module(x),
72
extra_ignore=(id(frame), id(locals()))
73
)
74
```
75
76
### Backward Reference Chain Finding
77
78
Find a path from any object matching a predicate to a target object by following back-references.
79
80
```python { .api }
81
def find_backref_chain(obj, predicate, max_depth=20, extra_ignore=()):
82
"""
83
Find a shortest chain of references leading to obj.
84
85
Args:
86
obj: Target object for the search
87
predicate (callable): Function taking object and returning bool; source match condition
88
max_depth (int): Maximum search depth to prevent infinite recursion
89
extra_ignore (tuple): Object IDs to exclude from the search
90
91
Returns:
92
list: Reference chain from matching object to obj, or [obj] if no chain found
93
94
Note:
95
The start of the chain will be some object that matches your predicate.
96
Useful for finding what keeps an object alive.
97
"""
98
```
99
100
Usage examples:
101
102
```python
103
import objgraph
104
105
# Find what module keeps an object alive
106
my_object = [1, 2, 3]
107
# Store it in a global for this example
108
globals()['my_global_list'] = my_object
109
110
chain = objgraph.find_backref_chain(my_object, objgraph.is_proper_module)
111
if len(chain) > 1:
112
print(f"Object is reachable from module: {chain[0]}")
113
print(f"Chain length: {len(chain)}")
114
for i, obj in enumerate(chain):
115
print(f" {i}: {type(obj).__name__}")
116
117
# Find what keeps a custom object alive
118
class MyClass:
119
def __init__(self, name):
120
self.name = name
121
122
obj = MyClass("example")
123
container = {'my_obj': obj}
124
root_dict = {'container': container}
125
126
# Find path from any dict to our object
127
chain = objgraph.find_backref_chain(
128
obj,
129
lambda x: isinstance(x, dict) and len(x) > 0
130
)
131
132
# Find path from module to object (common for debugging)
133
import types
134
chain = objgraph.find_backref_chain(
135
obj,
136
lambda x: isinstance(x, types.ModuleType)
137
)
138
139
# Ignore current frame to avoid finding temporary references
140
import sys
141
chain = objgraph.find_backref_chain(
142
obj,
143
objgraph.is_proper_module,
144
extra_ignore=(id(sys._getframe()), id(locals()))
145
)
146
```
147
148
## Common Workflows
149
150
### Memory Leak Investigation
151
152
Use reference chains to understand what keeps objects alive when they should be garbage collected.
153
154
```python
155
import objgraph
156
157
# Suppose you have objects that should be garbage collected but aren't
158
class PotentiallyLeakedClass:
159
def __init__(self, data):
160
self.data = data
161
162
# Create some objects
163
leaked_candidates = [PotentiallyLeakedClass(f"data_{i}") for i in range(5)]
164
165
# Clear obvious references
166
leaked_candidates.clear()
167
168
# But suppose objects are still alive - find what keeps them alive
169
remaining_objects = objgraph.by_type('PotentiallyLeakedClass')
170
if remaining_objects:
171
print(f"Found {len(remaining_objects)} objects that should be gone")
172
173
# For each remaining object, find what root keeps it alive
174
for obj in remaining_objects[:3]: # Check first 3
175
chain = objgraph.find_backref_chain(obj, objgraph.is_proper_module)
176
if len(chain) > 1:
177
print(f"Object kept alive by: {chain[0]}")
178
print(f"Path length: {len(chain)}")
179
# Visualize the chain
180
objgraph.show_chain(chain)
181
```
182
183
### Object Reachability Analysis
184
185
Understand how objects are connected and reachable from different roots.
186
187
```python
188
import objgraph
189
190
# Create a complex object structure
191
class Node:
192
def __init__(self, value):
193
self.value = value
194
self.children = []
195
self.parent = None
196
197
def add_child(self, child):
198
child.parent = self
199
self.children.append(child)
200
201
# Create a tree structure
202
root = Node("root")
203
child1 = Node("child1")
204
child2 = Node("child2")
205
grandchild = Node("grandchild")
206
207
root.add_child(child1)
208
root.add_child(child2)
209
child1.add_child(grandchild)
210
211
# Find path from grandchild to root
212
chain = objgraph.find_ref_chain(
213
grandchild,
214
lambda x: isinstance(x, Node) and x.value == "root"
215
)
216
217
print(f"Path from grandchild to root: {len(chain)} steps")
218
for i, node in enumerate(chain):
219
if isinstance(node, Node):
220
print(f" {i}: Node({node.value})")
221
else:
222
print(f" {i}: {type(node).__name__}")
223
224
# Find what ultimately keeps grandchild alive
225
backchain = objgraph.find_backref_chain(grandchild, objgraph.is_proper_module)
226
print(f"Grandchild kept alive by chain of length: {len(backchain)}")
227
```
228
229
### Custom Predicate Examples
230
231
```python
232
import objgraph
233
import types
234
235
# Find path to any function
236
chain = objgraph.find_ref_chain(
237
some_object,
238
lambda x: isinstance(x, types.FunctionType)
239
)
240
241
# Find path to large containers
242
chain = objgraph.find_ref_chain(
243
some_object,
244
lambda x: hasattr(x, '__len__') and len(x) > 1000
245
)
246
247
# Find path to objects with specific attributes
248
chain = objgraph.find_ref_chain(
249
some_object,
250
lambda x: hasattr(x, 'connection') and hasattr(x, 'execute')
251
)
252
253
# Find path from specific module
254
import json
255
chain = objgraph.find_backref_chain(
256
target_object,
257
lambda x: x is json # Specifically the json module
258
)
259
260
# Find path from any class definition
261
chain = objgraph.find_backref_chain(
262
instance_object,
263
lambda x: isinstance(x, type) # Any class/type object
264
)
265
```
266
267
## Integration with Visualization
268
269
Reference chains work well with objgraph's visualization functions:
270
271
```python
272
import objgraph
273
274
# Find a chain and visualize it
275
my_object = [1, 2, 3]
276
chain = objgraph.find_backref_chain(my_object, objgraph.is_proper_module)
277
278
if len(chain) > 1:
279
# Show the entire chain
280
objgraph.show_chain(chain, filename='chain.png')
281
282
# Show backrefs from the object with chain context
283
objgraph.show_backrefs([my_object],
284
filter=lambda x: id(x) in {id(obj) for obj in chain},
285
filename='chain_context.png')
286
```