OILS / mycpp / examples / gc_stack_roots.py View on Github | oilshell.org

186 lines, 75 significant
1#!/usr/bin/env python2
2"""
3gc_stack_roots.py
4"""
5from __future__ import print_function
6
7import os
8
9from mycpp import mylib
10
11from typing import Any, List
12"""
13Helpers
14"""
15
16
17def print_list(l):
18 # type: (List[str]) -> None
19 for s in l:
20 print(s)
21
22
23def calls_collect():
24 # type: () -> None
25 mylib.MaybeCollect()
26
27
28def ignore_and_collect(l):
29 # type: (List[str]) -> None
30 mylib.MaybeCollect()
31
32
33def collect_and_return(l):
34 # type: (List[str]) -> List[str]
35 mylib.MaybeCollect()
36 return l
37
38
39def collect_and_slice(s):
40 # type: (str) -> str
41 mylib.MaybeCollect()
42 return s[1:]
43
44
45class 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"""
61Test cases
62"""
63
64
65def 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
75def 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
89def 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
99def 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
116def 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
128def 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
141def 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
152def 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
163def 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
176def run_benchmarks():
177 # type: () -> None
178 pass
179
180
181if __name__ == '__main__':
182 if os.getenv('BENCHMARK'):
183 log('Benchmarking...')
184 run_benchmarks()
185 else:
186 run_tests()