retracesoftware-proxy 0.1.13__py3-none-any.whl → 0.1.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- retracesoftware/config.json +33 -9
- retracesoftware/install/patcher.py +86 -44
- retracesoftware/install/record.py +9 -3
- retracesoftware/proxy/__init__.py +1 -1
- retracesoftware/proxy/globalref.py +31 -0
- retracesoftware/proxy/proxysystem.py +7 -2
- retracesoftware/proxy/proxytype.py +10 -4
- retracesoftware/proxy/record.py +34 -16
- retracesoftware/proxy/replay.py +79 -32
- retracesoftware/proxy/thread.py +64 -4
- {retracesoftware_proxy-0.1.13.dist-info → retracesoftware_proxy-0.1.15.dist-info}/METADATA +1 -1
- {retracesoftware_proxy-0.1.13.dist-info → retracesoftware_proxy-0.1.15.dist-info}/RECORD +14 -13
- {retracesoftware_proxy-0.1.13.dist-info → retracesoftware_proxy-0.1.15.dist-info}/WHEEL +0 -0
- {retracesoftware_proxy-0.1.13.dist-info → retracesoftware_proxy-0.1.15.dist-info}/top_level.txt +0 -0
retracesoftware/config.json
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
"internal", {"comment": "Default state when retrace is disabled for a thread"},
|
|
5
5
|
"external", {"comment": "When target thread is running outside the python system to be recorded"},
|
|
6
6
|
"retrace", {"comment": "When target thread is running outside the retrace system"},
|
|
7
|
-
"bootstrap", {"comment": "When the target thread is running in the low-level module load machinery"},
|
|
8
7
|
"gc", {"comment": "When the target thread is running inside the pyton garbage collector"}
|
|
9
8
|
],
|
|
10
9
|
|
|
@@ -159,8 +158,10 @@
|
|
|
159
158
|
"modules": {
|
|
160
159
|
"_imp": {
|
|
161
160
|
"patch_extension_exec": ["exec_dynamic", "exec_builtin"],
|
|
162
|
-
"
|
|
163
|
-
"
|
|
161
|
+
"comment": {
|
|
162
|
+
"with_state": {
|
|
163
|
+
"internal": ["create_dynamic"]
|
|
164
|
+
}
|
|
164
165
|
}
|
|
165
166
|
},
|
|
166
167
|
"bdb": {
|
|
@@ -173,6 +174,7 @@
|
|
|
173
174
|
"disabled": ["Bdb.trace_dispatch"]
|
|
174
175
|
}
|
|
175
176
|
},
|
|
177
|
+
|
|
176
178
|
"sys": {
|
|
177
179
|
"with_state": {
|
|
178
180
|
"disabled": ["excepthook"]
|
|
@@ -184,9 +186,21 @@
|
|
|
184
186
|
}
|
|
185
187
|
},
|
|
186
188
|
|
|
187
|
-
"importlib
|
|
189
|
+
"importlib": {
|
|
188
190
|
"with_state": {
|
|
189
|
-
"
|
|
191
|
+
"disabled": ["import_module"]
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
"importlib._bootstrap": {
|
|
196
|
+
"comment": {
|
|
197
|
+
"with_state": {
|
|
198
|
+
"internal": [
|
|
199
|
+
"_load_unlocked",
|
|
200
|
+
"_find_spec",
|
|
201
|
+
"_lock_unlock_module",
|
|
202
|
+
"_get_module_lock"]
|
|
203
|
+
}
|
|
190
204
|
}
|
|
191
205
|
},
|
|
192
206
|
|
|
@@ -218,14 +232,24 @@
|
|
|
218
232
|
]
|
|
219
233
|
},
|
|
220
234
|
"types": {
|
|
235
|
+
"patch_hash": ["FunctionType"],
|
|
221
236
|
"immutable_types": [
|
|
222
|
-
"TracebackType"
|
|
237
|
+
"TracebackType",
|
|
238
|
+
"ModuleType"
|
|
223
239
|
]
|
|
224
240
|
},
|
|
225
241
|
"builtins": {
|
|
226
|
-
"
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
"patch_hash": ["object"],
|
|
243
|
+
|
|
244
|
+
"with_state": {
|
|
245
|
+
"disabled": ["__import__"]
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
"comment": {
|
|
249
|
+
"replace": {
|
|
250
|
+
"set": "retracesoftware_utils.set",
|
|
251
|
+
"frozenset": "retracesoftware_utils.frozenset"
|
|
252
|
+
}
|
|
229
253
|
},
|
|
230
254
|
"immutable_types": [
|
|
231
255
|
"BaseException",
|
|
@@ -29,6 +29,8 @@ import retracesoftware.utils as utils
|
|
|
29
29
|
|
|
30
30
|
from retracesoftware.install import edgecases
|
|
31
31
|
from functools import partial
|
|
32
|
+
from retracesoftware.proxy.thread import start_new_thread_wrapper
|
|
33
|
+
|
|
32
34
|
# from retrace_utils.intercept import proxy
|
|
33
35
|
|
|
34
36
|
# from retrace_utils.intercept.typeutils import *
|
|
@@ -121,9 +123,22 @@ def patch(func):
|
|
|
121
123
|
if isinstance(spec, str):
|
|
122
124
|
return wrapper(self, [spec], mod_dict)
|
|
123
125
|
elif isinstance(spec, list):
|
|
124
|
-
|
|
126
|
+
res = {}
|
|
127
|
+
for name in spec:
|
|
128
|
+
if name in mod_dict:
|
|
129
|
+
value = func(self, mod_dict[name])
|
|
130
|
+
if value is not None:
|
|
131
|
+
res[name] = value
|
|
132
|
+
return res
|
|
125
133
|
elif isinstance(spec, dict):
|
|
126
|
-
return {name: func(self, mod_dict[name], value) for name, value in spec.items() if name in mod_dict}
|
|
134
|
+
# return {name: func(self, mod_dict[name], value) for name, value in spec.items() if name in mod_dict}
|
|
135
|
+
res = {}
|
|
136
|
+
for name,value in spec.items():
|
|
137
|
+
if name in mod_dict:
|
|
138
|
+
value = func(self, mod_dict[name], value)
|
|
139
|
+
if value is not None:
|
|
140
|
+
res[name] = value
|
|
141
|
+
return res
|
|
127
142
|
else:
|
|
128
143
|
raise Exception('TODO')
|
|
129
144
|
|
|
@@ -155,6 +170,11 @@ def wrap_method_descriptors(wrapper, prefix, base):
|
|
|
155
170
|
|
|
156
171
|
return extended
|
|
157
172
|
|
|
173
|
+
class PerThread(threading.local):
|
|
174
|
+
def __init__(self):
|
|
175
|
+
self.internal = utils.counter()
|
|
176
|
+
self.externak = utils.counter()
|
|
177
|
+
|
|
158
178
|
class Patcher:
|
|
159
179
|
|
|
160
180
|
def __init__(self, thread_state, config, system,
|
|
@@ -164,7 +184,7 @@ class Patcher:
|
|
|
164
184
|
post_commit = None):
|
|
165
185
|
|
|
166
186
|
# validate(config)
|
|
167
|
-
|
|
187
|
+
system.set_thread_id(0)
|
|
168
188
|
# utils.set_thread_id(0)
|
|
169
189
|
self.thread_counter = system.sync(utils.counter(1))
|
|
170
190
|
# self.set_thread_number = set_thread_number
|
|
@@ -181,6 +201,13 @@ class Patcher:
|
|
|
181
201
|
self.exclude_paths = [re.compile(s) for s in config.get('exclude_paths', [])]
|
|
182
202
|
self.typepatcher = {}
|
|
183
203
|
|
|
204
|
+
per_thread = PerThread()
|
|
205
|
+
|
|
206
|
+
self.hashfunc = self.thread_state.dispatch(
|
|
207
|
+
functional.constantly(None),
|
|
208
|
+
internal = functional.repeatedly(functional.partial(getattr, per_thread, 'internal')),
|
|
209
|
+
external = functional.repeatedly(functional.partial(getattr, per_thread, 'external')))
|
|
210
|
+
|
|
184
211
|
def is_phase(name): return getattr(getattr(self, name, None), "is_phase", False)
|
|
185
212
|
|
|
186
213
|
self.phases = [(name, getattr(self, name)) for name in Patcher.__dict__.keys() if is_phase(name)]
|
|
@@ -275,22 +302,29 @@ class Patcher:
|
|
|
275
302
|
|
|
276
303
|
@patch
|
|
277
304
|
def patch_start_new_thread(self, value):
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
305
|
+
return start_new_thread_wrapper(thread_state = self.thread_state,
|
|
306
|
+
on_exit = self.system.on_thread_exit,
|
|
307
|
+
start_new_thread = value)
|
|
308
|
+
|
|
309
|
+
# def start_new_thread(function, *args):
|
|
310
|
+
# # synchronized, replay shoudl yield correct number
|
|
311
|
+
# thread_id = self.thread_counter()
|
|
281
312
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
313
|
+
# def threadrunner(*args, **kwargs):
|
|
314
|
+
# nonlocal thread_id
|
|
315
|
+
# self.system.set_thread_id(thread_id)
|
|
285
316
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
317
|
+
# with self.thread_state.select('internal'):
|
|
318
|
+
# try:
|
|
319
|
+
# # if self.tracing:
|
|
320
|
+
# # FrameTracer.install(self.thread_state.dispatch(noop, internal = self.checkpoint))
|
|
321
|
+
# return function(*args, **kwargs)
|
|
322
|
+
# finally:
|
|
323
|
+
# print(f'exiting: {thread_id}')
|
|
290
324
|
|
|
291
|
-
|
|
325
|
+
# return value(threadrunner, *args)
|
|
292
326
|
|
|
293
|
-
return self.thread_state.dispatch(value, internal = start_new_thread)
|
|
327
|
+
# return self.thread_state.dispatch(value, internal = start_new_thread)
|
|
294
328
|
|
|
295
329
|
@phase
|
|
296
330
|
def wrappers(self, spec, mod_dict):
|
|
@@ -298,24 +332,22 @@ class Patcher:
|
|
|
298
332
|
|
|
299
333
|
@patch
|
|
300
334
|
def patch_exec(self, exec):
|
|
301
|
-
def is_module_exec(source):
|
|
302
|
-
return isinstance(source, types.CodeType) and \
|
|
303
|
-
source.co_name == '<module>' and inspect.getmodule(source)
|
|
304
|
-
|
|
305
|
-
def exec_wrapper(source, *args,**kwargs):
|
|
306
|
-
if self.thread_state.value != 'boostrap' and is_module_exec(source):
|
|
307
|
-
with self.thread_state.select('disabled'):
|
|
308
|
-
module = inspect.getmodule(source)
|
|
309
|
-
with self.thread_state.select('internal'):
|
|
310
|
-
result = exec(source, *args, **kwargs)
|
|
311
|
-
|
|
312
|
-
self(module)
|
|
313
335
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
336
|
+
def exec_wrapper2(source, *args,**kwargs):
|
|
337
|
+
if isinstance(source, types.CodeType) and source.co_name == '<module>':
|
|
338
|
+
self(inspect.getmodule(source))
|
|
317
339
|
|
|
318
|
-
return
|
|
340
|
+
def first(x): return x[0]
|
|
341
|
+
|
|
342
|
+
wrapped_exec = functional.sequence(
|
|
343
|
+
functional.vector(
|
|
344
|
+
self.thread_state.wrap('internal', exec),
|
|
345
|
+
exec_wrapper2),
|
|
346
|
+
first)
|
|
347
|
+
|
|
348
|
+
return self.thread_state.dispatch(exec, internal = wrapped_exec)
|
|
349
|
+
|
|
350
|
+
# self.thread_state.wrap(desired_state = 'disabled', function = exec_wrapper)
|
|
319
351
|
|
|
320
352
|
@patch
|
|
321
353
|
def sync_types(self, value):
|
|
@@ -372,6 +404,10 @@ class Patcher:
|
|
|
372
404
|
|
|
373
405
|
return updates
|
|
374
406
|
|
|
407
|
+
@patch
|
|
408
|
+
def patch_hash(self, cls):
|
|
409
|
+
utils.patch_hash(cls = cls, hashfunc = self.hashfunc)
|
|
410
|
+
|
|
375
411
|
@patch
|
|
376
412
|
def patch_extension_exec(self, exec):
|
|
377
413
|
|
|
@@ -450,6 +486,8 @@ class Patcher:
|
|
|
450
486
|
|
|
451
487
|
def patch_module_with_name(self, mod_name, module):
|
|
452
488
|
with self.disable:
|
|
489
|
+
print(f'Patching: {module}')
|
|
490
|
+
|
|
453
491
|
self.log('install.module', mod_name)
|
|
454
492
|
|
|
455
493
|
# self.system.log(f'patching module: {mod_name}')
|
|
@@ -474,19 +512,23 @@ class Patcher:
|
|
|
474
512
|
|
|
475
513
|
def __call__(self, module):
|
|
476
514
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
515
|
+
print(f'patcher called with: {module} {self.thread_state.value}')
|
|
516
|
+
|
|
517
|
+
if not hasattr(module, '__retrace__'):
|
|
518
|
+
|
|
519
|
+
print(f'patcher called with: {module} 1')
|
|
520
|
+
|
|
521
|
+
configs = list(self.configs(module))
|
|
522
|
+
|
|
523
|
+
if len(configs) > 0:
|
|
524
|
+
if len(configs) > 1:
|
|
525
|
+
raise Exception(f'TODO')
|
|
526
|
+
else:
|
|
527
|
+
module.__retrace__ = None
|
|
528
|
+
try:
|
|
529
|
+
self.patch_module_with_name(configs[0], module)
|
|
530
|
+
except Exception as error:
|
|
531
|
+
raise Exception(f'Error patching module: {configs[0]}') from error
|
|
490
532
|
|
|
491
533
|
return module
|
|
492
534
|
|
|
@@ -13,9 +13,15 @@ from datetime import datetime
|
|
|
13
13
|
import json
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
-
class ThreadSwitch:
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
# class ThreadSwitch:
|
|
17
|
+
# def __init__(self, id):
|
|
18
|
+
# self.id = id
|
|
19
|
+
|
|
20
|
+
# def __repr__(self):
|
|
21
|
+
# return f'ThreadSwitch<{self.id}>'
|
|
22
|
+
|
|
23
|
+
# def __str__(self):
|
|
24
|
+
# return f'ThreadSwitch<{self.id}>'
|
|
19
25
|
|
|
20
26
|
def write_files(recording_path):
|
|
21
27
|
with open(recording_path / 'env', 'w') as f:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import types
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def find_module_name(mod):
|
|
5
|
+
for name,value in sys.modules.items():
|
|
6
|
+
if value is mod:
|
|
7
|
+
return name
|
|
8
|
+
|
|
9
|
+
raise Exception(f"Cannot create GlobalRef as module {mod} not found in sys.modules")
|
|
10
|
+
|
|
11
|
+
class GlobalRef:
|
|
12
|
+
__slots__ = ['parts']
|
|
13
|
+
|
|
14
|
+
def __init__(self, obj):
|
|
15
|
+
if isinstance(obj, types.ModuleType):
|
|
16
|
+
self.parts = (find_module_name(obj),)
|
|
17
|
+
else:
|
|
18
|
+
raise Exception(f"Cannot create GlobalRef from {obj}")
|
|
19
|
+
|
|
20
|
+
def __call__(self):
|
|
21
|
+
module = self.parts[0]
|
|
22
|
+
|
|
23
|
+
if len(self.parts) == 0:
|
|
24
|
+
return module
|
|
25
|
+
else:
|
|
26
|
+
obj = getattr(module, self.parts[1])
|
|
27
|
+
|
|
28
|
+
if len(self.parts) == 1:
|
|
29
|
+
return obj
|
|
30
|
+
else:
|
|
31
|
+
raise Exception(f"TODO")
|
|
@@ -100,8 +100,8 @@ class ProxySystem:
|
|
|
100
100
|
default = tracer(name, unproxy_execute)
|
|
101
101
|
return thread_state.dispatch(default, internal = internal, external = external)
|
|
102
102
|
|
|
103
|
-
self.ext_handler = self.wrap_int_to_ext(int2ext)
|
|
104
|
-
self.int_handler = self.wrap_ext_to_int(ext2int)
|
|
103
|
+
self.ext_handler = thread_state.wrap('retrace', self.wrap_int_to_ext(int2ext))
|
|
104
|
+
self.int_handler = thread_state.wrap('retrace', self.wrap_ext_to_int(ext2int))
|
|
105
105
|
|
|
106
106
|
self.ext_dispatch = gateway('proxy.int.disabled.event', internal = self.ext_handler)
|
|
107
107
|
self.int_dispatch = gateway('proxy.ext.disabled.event', external = self.int_handler)
|
|
@@ -126,6 +126,9 @@ class ProxySystem:
|
|
|
126
126
|
self.thread_state.value = self.saved_thread_state
|
|
127
127
|
self.fork_counter += 1
|
|
128
128
|
|
|
129
|
+
def on_thread_exit(self, thread_id):
|
|
130
|
+
pass
|
|
131
|
+
|
|
129
132
|
# def create_stub(self): return False
|
|
130
133
|
|
|
131
134
|
def int_proxytype(self, cls):
|
|
@@ -156,6 +159,8 @@ class ProxySystem:
|
|
|
156
159
|
return utils.wrapped_function(handler = self.ext_handler, target = obj)
|
|
157
160
|
|
|
158
161
|
def patchtype(self, cls):
|
|
162
|
+
print(f'Proxying type: {cls}')
|
|
163
|
+
|
|
159
164
|
if cls in self.immutable_types or issubclass(cls, tuple):
|
|
160
165
|
return cls
|
|
161
166
|
|
|
@@ -214,10 +214,16 @@ def dynamic_proxytype(handler, cls):
|
|
|
214
214
|
|
|
215
215
|
for name in superdict(cls).keys():
|
|
216
216
|
if name not in blacklist:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if
|
|
220
|
-
|
|
217
|
+
try:
|
|
218
|
+
value = getattr(cls, name)
|
|
219
|
+
if is_descriptor(value):
|
|
220
|
+
if utils.is_method_descriptor(value):
|
|
221
|
+
spec[name] = wrap(value)
|
|
222
|
+
except Exception as error:
|
|
223
|
+
print(f'FOO! {cls} {name} {error}')
|
|
224
|
+
breakpoint()
|
|
225
|
+
raise
|
|
226
|
+
|
|
221
227
|
# else:
|
|
222
228
|
# spec[name] = DescriptorProxy(handler = handler, target = value)
|
|
223
229
|
|
retracesoftware/proxy/record.py
CHANGED
|
@@ -5,9 +5,10 @@ import retracesoftware.stream as stream
|
|
|
5
5
|
from retracesoftware.proxy.proxytype import *
|
|
6
6
|
# from retracesoftware.proxy.gateway import gateway_pair
|
|
7
7
|
from retracesoftware.proxy.proxysystem import ProxySystem
|
|
8
|
-
from retracesoftware.proxy.thread import write_thread_switch
|
|
8
|
+
from retracesoftware.proxy.thread import write_thread_switch, ThreadSwitch, thread_id
|
|
9
9
|
from retracesoftware.install.tracer import Tracer
|
|
10
10
|
from retracesoftware.proxy.stubfactory import StubRef, ExtendedRef
|
|
11
|
+
from retracesoftware.proxy.globalref import GlobalRef
|
|
11
12
|
|
|
12
13
|
import sys
|
|
13
14
|
import os
|
|
@@ -69,14 +70,11 @@ class RecordProxySystem(ProxySystem):
|
|
|
69
70
|
super().after_fork_in_parent()
|
|
70
71
|
self.thread_state.value = self.saved_thread_state
|
|
71
72
|
self.writer.reopen()
|
|
72
|
-
|
|
73
|
-
def sync(self, function):
|
|
74
|
-
write_sync = functional.always(self.writer.handle('SYNC'))
|
|
75
|
-
|
|
76
|
-
return utils.observer(
|
|
77
|
-
on_call = functional.firstof(self.thread_switch_monitor, write_sync),
|
|
78
|
-
function = function)
|
|
79
73
|
|
|
74
|
+
def set_thread_id(self, id):
|
|
75
|
+
utils.set_thread_id(self.writer.handle(ThreadSwitch(id)))
|
|
76
|
+
# utils.set_thread_id(id)
|
|
77
|
+
|
|
80
78
|
def __init__(self, thread_state,
|
|
81
79
|
immutable_types,
|
|
82
80
|
tracing_config,
|
|
@@ -89,8 +87,17 @@ class RecordProxySystem(ProxySystem):
|
|
|
89
87
|
|
|
90
88
|
self.pid = self.getpid()
|
|
91
89
|
|
|
92
|
-
self.writer = stream.writer(path)
|
|
90
|
+
self.writer = stream.writer(path = path, thread = thread_id)
|
|
93
91
|
|
|
92
|
+
# def on_switch():
|
|
93
|
+
# print(f"On thread switch!!!")
|
|
94
|
+
# # print(f"On thread switch!!!: {utils.thread_id().id}")
|
|
95
|
+
# # utils.sigtrap(utils.thread_id())
|
|
96
|
+
# self.writer(utils.thread_id())
|
|
97
|
+
# # utils.sigtrap(utils.thread_id())
|
|
98
|
+
|
|
99
|
+
# write = utils.observer(on_call = utils.thread_switch_monitor(on_switch), function = self.writer)
|
|
100
|
+
|
|
94
101
|
# w = self.writer.handle('TRACE')
|
|
95
102
|
# def trace_writer(*args):
|
|
96
103
|
# print(f'Trace: {args}')
|
|
@@ -102,12 +109,23 @@ class RecordProxySystem(ProxySystem):
|
|
|
102
109
|
|
|
103
110
|
serialize = functional.walker(self.bindings.get_else_key)
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
# def on_switch():
|
|
113
|
+
# print("On thread switch!!!")
|
|
114
|
+
# # utils.sigtrap(utils.thread_id())
|
|
115
|
+
# utils.thread_id()()
|
|
116
|
+
# # utils.sigtrap(utils.thread_id())
|
|
117
|
+
|
|
118
|
+
# self.thread_switch_monitor = utils.thread_switch_monitor(on_switch)
|
|
119
|
+
|
|
120
|
+
# self.thread_switch_monitor = utils.thread_switch_monitor(functional.repeatedly(utils.thread_id))
|
|
108
121
|
|
|
109
122
|
# self.sync = lambda function: functional.firstof(self.thread_switch_monitor, functional.always(self.writer.handle('SYNC')), function)
|
|
110
|
-
|
|
123
|
+
|
|
124
|
+
sync_handle = self.writer.handle('SYNC')
|
|
125
|
+
|
|
126
|
+
self.sync = lambda function: \
|
|
127
|
+
utils.observer(on_call = functional.lazy(sync_handle), function = function)
|
|
128
|
+
|
|
111
129
|
error = self.writer.handle('ERROR')
|
|
112
130
|
|
|
113
131
|
def write_error(cls, val, traceback):
|
|
@@ -115,8 +133,6 @@ class RecordProxySystem(ProxySystem):
|
|
|
115
133
|
|
|
116
134
|
# self.set_thread_id = functional.partial(utils.set_thread_id, self.writer)
|
|
117
135
|
|
|
118
|
-
def watch(f): return functional.either(self.thread_switch_monitor, f)
|
|
119
|
-
|
|
120
136
|
# w = self.writer.handle('TRACE')
|
|
121
137
|
# def foo(name, *args):
|
|
122
138
|
# print(f'writing: {self.writer.messages_written} {name} {args}')
|
|
@@ -125,7 +141,7 @@ class RecordProxySystem(ProxySystem):
|
|
|
125
141
|
# tracer = Tracer(tracing_config, writer = foo)
|
|
126
142
|
tracer = Tracer(tracing_config, writer = self.writer.handle('TRACE'))
|
|
127
143
|
|
|
128
|
-
self.wrap_int_to_ext =
|
|
144
|
+
# self.wrap_int_to_ext = self.sync
|
|
129
145
|
|
|
130
146
|
self.on_int_call = functional.mapargs(transform = serialize, function = self.writer.handle('CALL'))
|
|
131
147
|
|
|
@@ -135,6 +151,8 @@ class RecordProxySystem(ProxySystem):
|
|
|
135
151
|
|
|
136
152
|
self.ext_apply = self.int_apply = functional.apply
|
|
137
153
|
|
|
154
|
+
self.writer.type_serializer[types.ModuleType] = GlobalRef
|
|
155
|
+
|
|
138
156
|
super().__init__(thread_state = thread_state,
|
|
139
157
|
tracer = tracer,
|
|
140
158
|
immutable_types = immutable_types)
|
retracesoftware/proxy/replay.py
CHANGED
|
@@ -3,17 +3,19 @@ import retracesoftware_utils as utils
|
|
|
3
3
|
import retracesoftware.stream as stream
|
|
4
4
|
|
|
5
5
|
from retracesoftware.install.tracer import Tracer
|
|
6
|
-
from retracesoftware.proxy.thread import per_thread_messages
|
|
6
|
+
from retracesoftware.proxy.thread import per_thread_messages, thread_id
|
|
7
7
|
from retracesoftware.proxy.proxytype import *
|
|
8
8
|
# from retracesoftware.proxy.gateway import gateway_pair
|
|
9
9
|
from retracesoftware.proxy.record import StubRef, Placeholder
|
|
10
10
|
from retracesoftware.proxy.proxysystem import ProxySystem, RetraceError
|
|
11
|
-
from retracesoftware.proxy.stubfactory import StubFactory, StubFunction
|
|
11
|
+
from retracesoftware.proxy.stubfactory import StubFactory, Stub, StubFunction
|
|
12
|
+
from retracesoftware.proxy.globalref import GlobalRef
|
|
12
13
|
|
|
13
14
|
import os
|
|
14
15
|
import weakref
|
|
15
16
|
import traceback
|
|
16
17
|
import pprint
|
|
18
|
+
|
|
17
19
|
from itertools import count, islice
|
|
18
20
|
|
|
19
21
|
# we can have a dummy method descriptor, its has a __name__ and when called, returns the next element
|
|
@@ -60,6 +62,8 @@ def on_stack_mismatch(last_matching, record, replay):
|
|
|
60
62
|
print('Record stacktrace:')
|
|
61
63
|
for line in islice(record, 0, len(record) - matching):
|
|
62
64
|
print(line)
|
|
65
|
+
|
|
66
|
+
print(f'-----------')
|
|
63
67
|
else:
|
|
64
68
|
matching = count_matching(reversed(record), reversed(replay))
|
|
65
69
|
|
|
@@ -74,36 +78,52 @@ def on_stack_mismatch(last_matching, record, replay):
|
|
|
74
78
|
print('Record stacktrace:')
|
|
75
79
|
for line in islice(record, 0, len(record) - matching):
|
|
76
80
|
print(line)
|
|
81
|
+
|
|
77
82
|
|
|
78
83
|
|
|
79
84
|
class ReplayProxySystem(ProxySystem):
|
|
80
85
|
|
|
81
86
|
@utils.striptraceback
|
|
82
87
|
def next_result(self):
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
try:
|
|
89
|
+
while True:
|
|
90
|
+
next = self.messages()
|
|
91
|
+
|
|
92
|
+
if next == 'CALL':
|
|
93
|
+
func = self.messages()
|
|
94
|
+
args = self.messages()
|
|
95
|
+
kwargs = self.messages()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
func(*args, **kwargs)
|
|
99
|
+
except:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
elif next == 'RESULT':
|
|
103
|
+
return self.messages()
|
|
104
|
+
|
|
105
|
+
elif next == 'ERROR':
|
|
106
|
+
# breakpoint()
|
|
107
|
+
err_type = self.messages()
|
|
108
|
+
err_value = self.messages()
|
|
109
|
+
utils.raise_exception(err_type, err_value)
|
|
110
|
+
else:
|
|
111
|
+
assert type(next) is not str
|
|
112
|
+
return next
|
|
113
|
+
except TimeoutError:
|
|
114
|
+
print(f'timeout for reader, active thread: {self.reader.active_thread}, next_control: {self.reader.next_control}')
|
|
85
115
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
args = self.messages()
|
|
89
|
-
kwargs = self.messages()
|
|
116
|
+
for thread,stack in self.reader.stacktraces.items():
|
|
117
|
+
print(f'thread: {thread}')
|
|
90
118
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
formatted_frames = [
|
|
120
|
+
(frame.filename, frame.lineno, frame.function, frame.code_context[0].strip() if frame.code_context else None)
|
|
121
|
+
for frame in stack
|
|
122
|
+
]
|
|
123
|
+
formatted_trace = traceback.format_list(formatted_frames)
|
|
124
|
+
print("Traceback (most recent call last):\n" + "".join(formatted_trace))
|
|
95
125
|
|
|
96
|
-
|
|
97
|
-
return self.messages()
|
|
98
|
-
|
|
99
|
-
elif next == 'ERROR':
|
|
100
|
-
# breakpoint()
|
|
101
|
-
err_type = self.messages()
|
|
102
|
-
err_value = self.messages()
|
|
103
|
-
utils.raise_exception(err_type, err_value)
|
|
104
|
-
else:
|
|
105
|
-
assert type(next) is not str
|
|
106
|
-
return next
|
|
126
|
+
utils.sigtrap(None)
|
|
107
127
|
|
|
108
128
|
def bind(self, obj):
|
|
109
129
|
read = self.messages()
|
|
@@ -137,7 +157,6 @@ class ReplayProxySystem(ProxySystem):
|
|
|
137
157
|
def basetype(self, cls):
|
|
138
158
|
return self.stub_factory.create_stubtype(StubRef(cls))
|
|
139
159
|
|
|
140
|
-
|
|
141
160
|
def readnext(self):
|
|
142
161
|
with self.thread_state.select('disabled'):
|
|
143
162
|
try:
|
|
@@ -153,12 +172,16 @@ class ReplayProxySystem(ProxySystem):
|
|
|
153
172
|
def read_required(self, required):
|
|
154
173
|
obj = self.readnext()
|
|
155
174
|
if obj != required:
|
|
175
|
+
print('---------------------------------')
|
|
176
|
+
print('last matching stack')
|
|
177
|
+
print('---------------------------------')
|
|
156
178
|
if self.last_matching_stack:
|
|
157
179
|
for line in self.last_matching_stack:
|
|
158
180
|
print(line)
|
|
159
181
|
|
|
160
182
|
print('---------------------------------')
|
|
161
183
|
print(f'Replay: {required}')
|
|
184
|
+
print('---------------------------------')
|
|
162
185
|
for line in utils.stacktrace():
|
|
163
186
|
print(line)
|
|
164
187
|
print('---------------------------------')
|
|
@@ -196,8 +219,10 @@ class ReplayProxySystem(ProxySystem):
|
|
|
196
219
|
for arg in args:
|
|
197
220
|
self.read_required(arg)
|
|
198
221
|
|
|
199
|
-
def
|
|
200
|
-
|
|
222
|
+
def on_thread_exit(self, thread_id):
|
|
223
|
+
print(f'on_thread_exit!!!!')
|
|
224
|
+
self.reader.wake_pending()
|
|
225
|
+
|
|
201
226
|
|
|
202
227
|
def __init__(self,
|
|
203
228
|
thread_state,
|
|
@@ -207,28 +232,50 @@ class ReplayProxySystem(ProxySystem):
|
|
|
207
232
|
fork_path = []):
|
|
208
233
|
|
|
209
234
|
# self.messages_read = 0
|
|
210
|
-
self.reader = stream.reader(path)
|
|
211
235
|
|
|
236
|
+
self.reader = stream.reader(path,
|
|
237
|
+
thread = thread_id,
|
|
238
|
+
timeout_seconds = 5)
|
|
239
|
+
|
|
212
240
|
self.bindings = utils.id_dict()
|
|
213
|
-
self.set_thread_id = utils.set_thread_id
|
|
241
|
+
# self.set_thread_id = utils.set_thread_id
|
|
214
242
|
self.fork_path = fork_path
|
|
215
243
|
deserialize = functional.walker(self.bindings.get_else_key)
|
|
216
244
|
|
|
217
245
|
# def count(res):
|
|
218
246
|
# self.messages_read += 1
|
|
219
247
|
# return res
|
|
248
|
+
|
|
249
|
+
read_res = functional.vector(functional.lazy(getattr, self.reader, 'messages_read'), self.reader)
|
|
250
|
+
|
|
251
|
+
def foo():
|
|
252
|
+
try:
|
|
253
|
+
messages_read, res = read_res()
|
|
254
|
+
if issubclass(type(res), Stub):
|
|
255
|
+
print(f'res: {utils.thread_id()} {messages_read} stub: {type(res)}')
|
|
256
|
+
else:
|
|
257
|
+
print(f'res: {utils.thread_id()} {messages_read} {res}')
|
|
258
|
+
|
|
259
|
+
return res
|
|
260
|
+
except Exception as error:
|
|
261
|
+
print(f'Error reading next result: {error}')
|
|
262
|
+
raise(error)
|
|
220
263
|
|
|
221
|
-
self.messages = functional.sequence(per_thread_messages(
|
|
222
|
-
|
|
264
|
+
# self.messages = functional.sequence(per_thread_messages(foo), deserialize)
|
|
265
|
+
|
|
266
|
+
self.messages = functional.sequence(self.reader, deserialize)
|
|
223
267
|
|
|
224
268
|
self.stub_factory = StubFactory(thread_state = thread_state, next_result = self.next_result)
|
|
225
269
|
|
|
226
270
|
self.last_matching_stack = None
|
|
227
271
|
|
|
228
272
|
self.reader.type_deserializer[StubRef] = self.stub_factory
|
|
273
|
+
self.reader.type_deserializer[GlobalRef] = lambda ref: ref()
|
|
229
274
|
|
|
230
|
-
|
|
231
|
-
|
|
275
|
+
read_sync = functional.lazy(self.read_required, 'SYNC')
|
|
276
|
+
|
|
277
|
+
self.sync = lambda function: utils.observer(on_call = read_sync, function = function)
|
|
278
|
+
|
|
232
279
|
super().__init__(thread_state = thread_state,
|
|
233
280
|
tracer = Tracer(tracing_config, writer = self.trace_writer),
|
|
234
281
|
immutable_types = immutable_types)
|
retracesoftware/proxy/thread.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import retracesoftware.functional as functional
|
|
2
2
|
import retracesoftware_utils as utils
|
|
3
3
|
|
|
4
|
+
import os
|
|
5
|
+
import _thread
|
|
6
|
+
|
|
4
7
|
# def thread_aware_writer(writer):
|
|
5
8
|
# on_thread_switch = functional.sequence(utils.thread_id(), writer.handle('THREAD_SWITCH'))
|
|
6
9
|
# return utils.threadawareproxy(on_thread_switch = on_thread_switch, target = writer)
|
|
@@ -11,23 +14,35 @@ class ThreadSwitch:
|
|
|
11
14
|
def __init__(self, id):
|
|
12
15
|
self.id = id
|
|
13
16
|
|
|
14
|
-
def
|
|
15
|
-
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return f'ThreadSwitch<{self.id}>'
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return f'ThreadSwitch<{self.id}>'
|
|
22
|
+
|
|
23
|
+
# def set_thread_id(writer, id):
|
|
24
|
+
# utils.sigtrap(id)
|
|
25
|
+
# utils.set_thread_id(writer.handle(ThreadSwitch(id)))
|
|
16
26
|
|
|
17
27
|
def write_thread_switch(writer):
|
|
18
28
|
on_thread_switch = functional.repeatedly(functional.sequence(utils.thread_id, writer))
|
|
19
29
|
|
|
20
30
|
return lambda f: utils.thread_aware_proxy(target = f, on_thread_switch = on_thread_switch, sticky = False)
|
|
21
31
|
|
|
22
|
-
def prefix_with_thread_id(f,
|
|
32
|
+
def prefix_with_thread_id(f, thread_id):
|
|
33
|
+
current = None
|
|
34
|
+
|
|
23
35
|
def next():
|
|
24
36
|
nonlocal current, f
|
|
37
|
+
if current is None: current = thread_id()
|
|
38
|
+
|
|
25
39
|
obj = f()
|
|
26
40
|
|
|
27
41
|
while issubclass(type(obj), ThreadSwitch):
|
|
28
42
|
current = obj.id
|
|
29
43
|
obj = f()
|
|
30
44
|
|
|
45
|
+
# print(f'prefix_with_thread_id: {(current, obj)}')
|
|
31
46
|
return (current, obj)
|
|
32
47
|
|
|
33
48
|
return next
|
|
@@ -36,7 +51,15 @@ def per_thread_messages(messages):
|
|
|
36
51
|
thread_id = utils.thread_id
|
|
37
52
|
# thread_id = lambda: 'FOOOOO!!!'
|
|
38
53
|
|
|
39
|
-
|
|
54
|
+
def on_timeout(demux, key):
|
|
55
|
+
print(f'ON TIMEOUT!!!! {key} pending: {demux.pending} {demux.pending_keys}')
|
|
56
|
+
utils.sigtrap(demux)
|
|
57
|
+
os._exit(1)
|
|
58
|
+
|
|
59
|
+
demux = utils.demux(source = prefix_with_thread_id(messages, thread_id),
|
|
60
|
+
key_function = lambda obj: obj[0],
|
|
61
|
+
timeout_seconds = 60,
|
|
62
|
+
on_timeout = on_timeout)
|
|
40
63
|
|
|
41
64
|
# def next():
|
|
42
65
|
# thread,message = demux(thread_id())
|
|
@@ -44,3 +67,40 @@ def per_thread_messages(messages):
|
|
|
44
67
|
|
|
45
68
|
# return next
|
|
46
69
|
return functional.repeatedly(lambda: demux(thread_id())[1])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# _thread.start_new_thread(function, args[, kwargs])
|
|
73
|
+
counters = _thread._local()
|
|
74
|
+
counters.id = ()
|
|
75
|
+
counters.counter = 0
|
|
76
|
+
|
|
77
|
+
def with_thread_id(thread_id, on_exit, function):
|
|
78
|
+
def on_call(*args, **kwargs):
|
|
79
|
+
counters.id = thread_id
|
|
80
|
+
counters.counter = 0
|
|
81
|
+
|
|
82
|
+
def on_result(res):
|
|
83
|
+
on_exit(thread_id)
|
|
84
|
+
|
|
85
|
+
def on_error(*args):
|
|
86
|
+
on_exit(thread_id)
|
|
87
|
+
|
|
88
|
+
return utils.observer(on_call = on_call, on_result = on_result, on_error = on_error, function = function)
|
|
89
|
+
|
|
90
|
+
thread_id = functional.lazy(getattr, counters, 'id')
|
|
91
|
+
|
|
92
|
+
def start_new_thread_wrapper(thread_state, on_exit, start_new_thread):
|
|
93
|
+
|
|
94
|
+
def wrapper(function, *args):
|
|
95
|
+
|
|
96
|
+
next_id = counters.id + (counters.counter,)
|
|
97
|
+
counters.counter += 1
|
|
98
|
+
|
|
99
|
+
wrapped_function = with_thread_id(thread_id = next_id,
|
|
100
|
+
on_exit = on_exit,
|
|
101
|
+
function = thread_state.wrap('internal', function))
|
|
102
|
+
|
|
103
|
+
return start_new_thread(wrapped_function, *args)
|
|
104
|
+
|
|
105
|
+
return thread_state.dispatch(start_new_thread, internal = wrapper)
|
|
106
|
+
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
retracesoftware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
retracesoftware/config.json,sha256=
|
|
2
|
+
retracesoftware/config.json,sha256=yMAD8410r8VEnBToXNmtfaBz6MClBVEiKsq6vKgSI0c,9121
|
|
3
3
|
retracesoftware/config.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
retracesoftware/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
retracesoftware/install/config.py,sha256=EzE5ifQF2lo--hu2njI4T0FJ-zlnWDJV6i7x0DMkVTw,1364
|
|
6
6
|
retracesoftware/install/edgecases.py,sha256=NR3lyvad9sRsyeDv_Ya8V4xMgPsMPOi9rMcnFOJGOEA,6330
|
|
7
7
|
retracesoftware/install/globals.py,sha256=F8XvIoZQQ10gSRalk30dvdKllxlwxkaggYY6FogLDxY,510
|
|
8
8
|
retracesoftware/install/install.py,sha256=HCD_ji8XCr96b5fNzNdL_8qcEp0Jf05Em7T6GA6u8HU,4969
|
|
9
|
-
retracesoftware/install/patcher.py,sha256=
|
|
9
|
+
retracesoftware/install/patcher.py,sha256=_J8YTDd0QNLgURRyrUFikNsImgMOBhCv8Nmps5rjkZI,20241
|
|
10
10
|
retracesoftware/install/predicate.py,sha256=tX7NQc0rGkyyHYO3mduYHcJHbw1wczT53m_Dpkzo6do,2679
|
|
11
|
-
retracesoftware/install/record.py,sha256=
|
|
11
|
+
retracesoftware/install/record.py,sha256=R2GOIA_WAggrNmVwZJh9r1xp-GVu43iKq-ykQ1VKEHE,3408
|
|
12
12
|
retracesoftware/install/references.py,sha256=A-G651IDOfuo00MkbAdpbIQh_15ChvJ7uAVTSmE6zd4,1721
|
|
13
13
|
retracesoftware/install/replay.py,sha256=VUiHvQK3mgAJEGmtE2TFs9kXzxdWtsjibEcGkhZVCVE,1830
|
|
14
14
|
retracesoftware/install/tracer.py,sha256=cHEjiVxIp2iVTJEWndwSbMuiXVyGJQxJYZSGrfpSbCw,5723
|
|
15
15
|
retracesoftware/install/typeutils.py,sha256=_a1PuwdCsYjG1Nkd77V-flqYtwbD4RkJVKn6Z-xABL4,1813
|
|
16
|
-
retracesoftware/proxy/__init__.py,sha256=
|
|
16
|
+
retracesoftware/proxy/__init__.py,sha256=ntIyqKhBRkKEkcW_oOPodikh-mxYl8OXRnSaj-9-Xwc,178
|
|
17
17
|
retracesoftware/proxy/gateway.py,sha256=xESohWXkiNm4ZutU0RgWUwxjxcBWRQ4rQyxIGQXv_F4,1590
|
|
18
|
+
retracesoftware/proxy/globalref.py,sha256=yXtJsOeBHN9xoEgJWA3MJco-jD2SQUef_fDatA4A6rg,803
|
|
18
19
|
retracesoftware/proxy/proxyfactory.py,sha256=qhOqDfMJnLDNkQs26JqDB431MwjjRhGQi8xupJ45asg,12272
|
|
19
|
-
retracesoftware/proxy/proxysystem.py,sha256=
|
|
20
|
-
retracesoftware/proxy/proxytype.py,sha256=
|
|
21
|
-
retracesoftware/proxy/record.py,sha256=
|
|
22
|
-
retracesoftware/proxy/replay.py,sha256=
|
|
20
|
+
retracesoftware/proxy/proxysystem.py,sha256=ttzHcNH4O2GrlTjy3R386HTwRY7Z5QKWbMt6BOf7Jto,7722
|
|
21
|
+
retracesoftware/proxy/proxytype.py,sha256=hP-1vJmWBa9KgZFB3CnZy0UbC4u8Wr2e76lGI0JM2SA,12992
|
|
22
|
+
retracesoftware/proxy/record.py,sha256=47vhAYIUrR2bWo-rQ9xU_Xyg3kNrA5Lqq4-8Cxg4-zg,5463
|
|
23
|
+
retracesoftware/proxy/replay.py,sha256=RWfmTrbeLFCSvV26ow1iwitf1wWt6XfypMcKIxYXZEg,9669
|
|
23
24
|
retracesoftware/proxy/stubfactory.py,sha256=37UX1r8HCAbASTwPdz8QKCrg72NQmM5PsiCho7N2nzg,5129
|
|
24
|
-
retracesoftware/proxy/thread.py,sha256
|
|
25
|
-
retracesoftware_proxy-0.1.
|
|
26
|
-
retracesoftware_proxy-0.1.
|
|
27
|
-
retracesoftware_proxy-0.1.
|
|
28
|
-
retracesoftware_proxy-0.1.
|
|
25
|
+
retracesoftware/proxy/thread.py,sha256=T1ME6DHB8O0xVnX3Rt1lMl7oCJ2Y0aoFT91D76yNICk,3073
|
|
26
|
+
retracesoftware_proxy-0.1.15.dist-info/METADATA,sha256=5EIkVm31Txgu2-FN94NLguxj4YtbZ7pU_VaZLisxhCE,203
|
|
27
|
+
retracesoftware_proxy-0.1.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
28
|
+
retracesoftware_proxy-0.1.15.dist-info/top_level.txt,sha256=hYHsR6txLidmqvjBMITpIHvmJJbmoCAgr76-IpZPRz8,16
|
|
29
|
+
retracesoftware_proxy-0.1.15.dist-info/RECORD,,
|
|
File without changes
|
{retracesoftware_proxy-0.1.13.dist-info → retracesoftware_proxy-0.1.15.dist-info}/top_level.txt
RENAMED
|
File without changes
|