1 | #!/usr/bin/env python2
|
2 | """
|
3 | gc_stack_roots.py
|
4 | """
|
5 | from __future__ import print_function
|
6 |
|
7 | import os
|
8 |
|
9 | from mycpp import mylib
|
10 |
|
11 | from typing import Any, List
|
12 | """
|
13 | Helpers
|
14 | """
|
15 |
|
16 |
|
17 | def print_list(l):
|
18 | # type: (List[str]) -> None
|
19 | for s in l:
|
20 | print(s)
|
21 |
|
22 |
|
23 | def calls_collect():
|
24 | # type: () -> None
|
25 | mylib.MaybeCollect()
|
26 |
|
27 |
|
28 | def ignore_and_collect(l):
|
29 | # type: (List[str]) -> None
|
30 | mylib.MaybeCollect()
|
31 |
|
32 |
|
33 | def collect_and_return(l):
|
34 | # type: (List[str]) -> List[str]
|
35 | mylib.MaybeCollect()
|
36 | return l
|
37 |
|
38 |
|
39 | def collect_and_slice(s):
|
40 | # type: (str) -> str
|
41 | mylib.MaybeCollect()
|
42 | return s[1:]
|
43 |
|
44 |
|
45 | class ctx_Stasher(object):
|
46 |
|
47 | def __init__(self, l):
|
48 | # type: (List[str]) -> None
|
49 | self.l = l
|
50 |
|
51 | def __enter__(self):
|
52 | # type: () -> None
|
53 | pass
|
54 |
|
55 | def __exit__(self, type, value, traceback):
|
56 | # type: (Any, Any, Any) -> None
|
57 | print_list(self.l)
|
58 |
|
59 |
|
60 | """
|
61 | Test cases
|
62 | """
|
63 |
|
64 |
|
65 | def no_collect():
|
66 | # type: () -> None
|
67 | """
|
68 | There's no need to gernate any stack roots in this case. There is no threat
|
69 | of anything being swept.
|
70 | """
|
71 | l = ['no', 'collect'] # type: List[str]
|
72 | print_list(l)
|
73 |
|
74 |
|
75 | def simple_collect():
|
76 | # type: () -> None
|
77 | """
|
78 | Only l1 needs to be rooted here. l2 is not live after the call to collect.
|
79 | """
|
80 | l1 = ['foo', 'bar'] # type: List[str]
|
81 | l2 = ['bing', 'bong'] # type: List[str]
|
82 | print_list(l2)
|
83 | if len(l1):
|
84 | mylib.MaybeCollect()
|
85 |
|
86 | print_list(l1)
|
87 |
|
88 |
|
89 | def indirect_collect():
|
90 | # type: () -> None
|
91 | """
|
92 | l should be rooted since it is live after an indirect call to collect.
|
93 | """
|
94 | l = ['indirect', 'collect']
|
95 | calls_collect()
|
96 | print_list(l)
|
97 |
|
98 |
|
99 | def arg_roots():
|
100 | # type: () -> None
|
101 | """
|
102 | If a function might collect it should unconditionally root its arguments.
|
103 | It should root them even if it doesn't use them directly because we can't
|
104 | gaurantee that the caller will even have been able to root them, e.g. in the
|
105 | case of function composition or an arugment being constructed inline.
|
106 | """
|
107 | l1 = ['OK'] # Should be rooted by ignore_and_collect().
|
108 | ignore_and_collect(l1)
|
109 | print_list(l1)
|
110 |
|
111 | # The temporary list should be rooted by collect_and_return().
|
112 | l2 = collect_and_return(['not', 'swept'])
|
113 | print_list(l2)
|
114 |
|
115 |
|
116 | def alias():
|
117 | # type: () -> None
|
118 | """
|
119 | Only one of l1 and l2 needs to be rooted here. In this case we should choose
|
120 | l2 since it is live after the collector runs.
|
121 | """
|
122 | l1 = ['foo', 'bar'] # type: List[str]
|
123 | l2 = l1
|
124 | mylib.MaybeCollect()
|
125 | print_list(l2)
|
126 |
|
127 |
|
128 | def collect_scoped_resource():
|
129 | # type: () -> None
|
130 | """
|
131 | Similar to function arguments, members of context managers should be rooted
|
132 | by their constructors. However, unlike normal functions these constructors
|
133 | should do so even if they don't cause a collection. The caller might trigger
|
134 | garbage collection while the manager is still in scope and the members will
|
135 | get swept if they weren't already rooted further up in the call stack.
|
136 | """
|
137 | with ctx_Stasher(['context', 'member']) as ctx:
|
138 | mylib.MaybeCollect()
|
139 |
|
140 |
|
141 | def collect_in_loop():
|
142 | # type: () -> None
|
143 | """
|
144 | Temporary variables used in loops should be rooted if a collection might
|
145 | happen within the loop body.
|
146 | """
|
147 | for s in ['watch', 'out']:
|
148 | mylib.MaybeCollect()
|
149 | print(s)
|
150 |
|
151 |
|
152 | def collect_in_comprehension():
|
153 | # type: () -> None
|
154 | """
|
155 | Temporary variables used in list comprehensions should be rooted if a
|
156 | collection might happen.
|
157 | """
|
158 | l = ['%s' % collect_and_slice(s) for s in ['foo', 'bar']] # type: List[str]
|
159 | for s in l:
|
160 | print(s)
|
161 |
|
162 |
|
163 | def run_tests():
|
164 | # type: () -> None
|
165 | no_collect()
|
166 | simple_collect()
|
167 | indirect_collect()
|
168 | arg_roots()
|
169 | alias()
|
170 | collect_scoped_resource()
|
171 | # TODO: maybe move these two to invalid examples if we decide to disallow.
|
172 | #collect_in_loop()
|
173 | #collect_in_comprehension()
|
174 |
|
175 |
|
176 | def run_benchmarks():
|
177 | # type: () -> None
|
178 | pass
|
179 |
|
180 |
|
181 | if __name__ == '__main__':
|
182 | if os.getenv('BENCHMARK'):
|
183 | log('Benchmarking...')
|
184 | run_benchmarks()
|
185 | else:
|
186 | run_tests()
|