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