retracesoftware-proxy 0.0.0__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/__init__.py +0 -0
- retracesoftware/__main__.py +266 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +175 -0
- retracesoftware/config.yaml +0 -0
- retracesoftware/install/__init__.py +0 -0
- retracesoftware/install/config.py +59 -0
- retracesoftware/install/edgecases.py +242 -0
- retracesoftware/install/globals.py +17 -0
- retracesoftware/install/install.py +142 -0
- retracesoftware/install/patcher.py +122 -0
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/predicate.py +92 -0
- retracesoftware/install/record.py +174 -0
- retracesoftware/install/references.py +66 -0
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +102 -0
- retracesoftware/install/tracer.py +284 -0
- retracesoftware/install/typeutils.py +92 -0
- retracesoftware/modules.toml +384 -0
- retracesoftware/proxy/__init__.py +3 -0
- retracesoftware/proxy/gateway.py +49 -0
- retracesoftware/proxy/globalref.py +31 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxyfactory.py +357 -0
- retracesoftware/proxy/proxysystem.py +454 -0
- retracesoftware/proxy/proxytype.py +424 -0
- retracesoftware/proxy/record.py +211 -0
- retracesoftware/proxy/replay.py +138 -0
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +195 -0
- retracesoftware/proxy/thread.py +106 -0
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +373 -0
- retracesoftware/stackdifference.py +133 -0
- retracesoftware_proxy-0.0.0.dist-info/METADATA +8 -0
- retracesoftware_proxy-0.0.0.dist-info/RECORD +41 -0
- retracesoftware_proxy-0.0.0.dist-info/WHEEL +5 -0
- retracesoftware_proxy-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import retracesoftware.functional as functional
|
|
2
|
+
import retracesoftware_utils as utils
|
|
3
|
+
|
|
4
|
+
from retracesoftware.install.tracer import Tracer
|
|
5
|
+
from retracesoftware.proxy.thread import per_thread_messages, thread_id
|
|
6
|
+
from retracesoftware.proxy.messagestream import *
|
|
7
|
+
from retracesoftware.proxy.proxytype import *
|
|
8
|
+
# from retracesoftware.proxy.gateway import gateway_pair
|
|
9
|
+
from retracesoftware.proxy.record import StubRef
|
|
10
|
+
from retracesoftware.proxy.proxysystem import ProxySystem
|
|
11
|
+
from retracesoftware.proxy.stubfactory import StubFactory
|
|
12
|
+
from retracesoftware.proxy.globalref import GlobalRef
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
class ReplayProxySystem(ProxySystem):
|
|
17
|
+
|
|
18
|
+
def after_fork_in_child(self):
|
|
19
|
+
self.reader.path = self.new_child_path(self.reader.path)
|
|
20
|
+
super().after_fork_in_child()
|
|
21
|
+
|
|
22
|
+
# def dynamic_ext_proxytype(self, cls):
|
|
23
|
+
# raise Exception('dynamic_ext_proxytype should not be called in replay')
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def ext_apply(self):
|
|
27
|
+
return functional.repeatedly(self.next_result)
|
|
28
|
+
|
|
29
|
+
def proxy__new__(self, __new__, *args, **kwargs):
|
|
30
|
+
func = functional.repeatedly(self.next_result)
|
|
31
|
+
func.__name__ = '__new__'
|
|
32
|
+
return super().proxy__new__(func, *args, **kwargs)
|
|
33
|
+
|
|
34
|
+
def basetype(self, cls):
|
|
35
|
+
return self.stub_factory.create_stubtype(StubRef(cls))
|
|
36
|
+
|
|
37
|
+
def trace_writer(self, name, *args):
|
|
38
|
+
with self.thread_state.select('disabled'):
|
|
39
|
+
# read = self.messages_read
|
|
40
|
+
|
|
41
|
+
self.read_required('TRACE')
|
|
42
|
+
# self.read_required(read)
|
|
43
|
+
self.read_required(name)
|
|
44
|
+
|
|
45
|
+
if name == 'stacktrace':
|
|
46
|
+
print('FOOO!!!')
|
|
47
|
+
os._exit(1)
|
|
48
|
+
record = self.readnext()
|
|
49
|
+
if args[0] == record:
|
|
50
|
+
self.last_matching_stack = args[0]
|
|
51
|
+
else:
|
|
52
|
+
on_stack_mismatch(
|
|
53
|
+
last_matching = self.last_matching_stack,
|
|
54
|
+
record = record,
|
|
55
|
+
replay = args[0])
|
|
56
|
+
os._exit(1)
|
|
57
|
+
else:
|
|
58
|
+
# print(f'Trace: {self.reader.messages_read} {name} {args}')
|
|
59
|
+
for arg in args:
|
|
60
|
+
self.read_required(arg)
|
|
61
|
+
|
|
62
|
+
def on_thread_exit(self, thread_id):
|
|
63
|
+
# print(f'on_thread_exit!!!!')
|
|
64
|
+
self.reader.wake_pending()
|
|
65
|
+
|
|
66
|
+
def __init__(self,
|
|
67
|
+
reader,
|
|
68
|
+
thread_state,
|
|
69
|
+
immutable_types,
|
|
70
|
+
tracing_config,
|
|
71
|
+
traceargs,
|
|
72
|
+
verbose = False,
|
|
73
|
+
fork_path = [],
|
|
74
|
+
skip_weakref_callbacks = False):
|
|
75
|
+
|
|
76
|
+
self.reader = reader
|
|
77
|
+
# self.skip_weakref_callbacks = skip_weakref_callbacks
|
|
78
|
+
|
|
79
|
+
self.fork_path = fork_path
|
|
80
|
+
|
|
81
|
+
self.messages = MessageStream(
|
|
82
|
+
thread_state = thread_state,
|
|
83
|
+
source = reader,
|
|
84
|
+
skip_weakref_callbacks = skip_weakref_callbacks,
|
|
85
|
+
verbose = verbose)
|
|
86
|
+
|
|
87
|
+
self.checkpoint = self.messages.checkpoint
|
|
88
|
+
self.bind = self.messages.bind
|
|
89
|
+
self.next_result = self.messages.result
|
|
90
|
+
self.exclude_from_stacktrace = self.messages.excludes.add
|
|
91
|
+
|
|
92
|
+
self.stub_factory = StubFactory(thread_state = thread_state, next_result = self.next_result)
|
|
93
|
+
|
|
94
|
+
self.last_matching_stack = None
|
|
95
|
+
|
|
96
|
+
def run_ref(ref):
|
|
97
|
+
print(f'run_ref!!!! {ref}')
|
|
98
|
+
return ref()
|
|
99
|
+
|
|
100
|
+
self.reader.type_deserializer[StubRef] = self.stub_factory
|
|
101
|
+
# self.reader.type_deserializer[GlobalRef] = lambda ref: ref()
|
|
102
|
+
self.reader.type_deserializer[GlobalRef] = run_ref
|
|
103
|
+
|
|
104
|
+
excludes = [ReplayProxySystem.trace_writer]
|
|
105
|
+
|
|
106
|
+
for exclude in excludes:
|
|
107
|
+
self.messages.excludes.add(exclude)
|
|
108
|
+
|
|
109
|
+
sync = functional.lazy(self.messages.read_required, 'SYNC')
|
|
110
|
+
|
|
111
|
+
read_sync = thread_state.dispatch(utils.noop, internal = sync)
|
|
112
|
+
|
|
113
|
+
self.on_ext_call = sync
|
|
114
|
+
|
|
115
|
+
self.sync = lambda function: utils.observer(on_call = read_sync, function = function)
|
|
116
|
+
|
|
117
|
+
self.create_from_external = utils.noop
|
|
118
|
+
|
|
119
|
+
if skip_weakref_callbacks:
|
|
120
|
+
self.wrap_weakref_callback = \
|
|
121
|
+
lambda callback: \
|
|
122
|
+
thread_state.dispatch(
|
|
123
|
+
callback, internal = self.disable_for(callback))
|
|
124
|
+
else:
|
|
125
|
+
self.on_weakref_callback_start = functional.lazy(self.messages.read_required, 'ON_WEAKREF_CALLBACK_START')
|
|
126
|
+
self.on_weakref_callback_end = functional.lazy(self.messages.read_required, 'ON_WEAKREF_CALLBACK_END')
|
|
127
|
+
|
|
128
|
+
super().__init__(thread_state = thread_state,
|
|
129
|
+
tracer = Tracer(tracing_config, writer = self.trace_writer),
|
|
130
|
+
immutable_types = immutable_types,
|
|
131
|
+
traceargs = traceargs)
|
|
132
|
+
|
|
133
|
+
def write_trace(self, obj):
|
|
134
|
+
if 'TRACER' != self.messages():
|
|
135
|
+
utils.sigtrap(obj)
|
|
136
|
+
|
|
137
|
+
# self.read_required ('TRACER')
|
|
138
|
+
self.read_required(obj)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
def serializer(obj):
|
|
2
|
+
cls = type(obj)
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
if issubclass(cls, Enum):
|
|
6
|
+
return str(obj)
|
|
7
|
+
|
|
8
|
+
if issubclass(cls, (tuple, list)):
|
|
9
|
+
return list(map(serializer, obj))
|
|
10
|
+
elif issubclass(cls, dict):
|
|
11
|
+
return {str(serializer(k)):serializer(v) for k,v in obj.items()}
|
|
12
|
+
elif issubclass(cls, int):
|
|
13
|
+
if obj > 1000000 or obj < -1000000:
|
|
14
|
+
return "XXXX"
|
|
15
|
+
else:
|
|
16
|
+
return int(obj)
|
|
17
|
+
elif issubclass(cls, (bool, str, types.NoneType)):
|
|
18
|
+
return obj
|
|
19
|
+
elif issubclass(cls, types.FunctionType):
|
|
20
|
+
return obj.__qualname__
|
|
21
|
+
elif issubclass(cls, types.CellType):
|
|
22
|
+
return getattr(obj, '__qualname__', 'CellType')
|
|
23
|
+
elif issubclass(cls, types.GeneratorType):
|
|
24
|
+
return obj.__qualname__
|
|
25
|
+
else:
|
|
26
|
+
return cleanse(str(obj))
|
|
27
|
+
except:
|
|
28
|
+
return f'Unserializable object of type: {cls}'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from retracesoftware import functional
|
|
2
|
+
from retracesoftware import utils
|
|
3
|
+
|
|
4
|
+
import _thread
|
|
5
|
+
|
|
6
|
+
# _thread.start_new_thread(function, args[, kwargs])
|
|
7
|
+
|
|
8
|
+
# push a per thread executor function, as a context manager
|
|
9
|
+
|
|
10
|
+
counters = _thread._local()
|
|
11
|
+
counters.id = ()
|
|
12
|
+
counters.counter = 0
|
|
13
|
+
|
|
14
|
+
def with_thread_id(thread_id, function, *args, **kwargs):
|
|
15
|
+
counters.id = thread_id
|
|
16
|
+
counters.counter = 0
|
|
17
|
+
return function(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
thread_id = functional.lazy(getattr, counters, 'id')
|
|
20
|
+
|
|
21
|
+
def start_new_thread(original, wrapper, function, *args):
|
|
22
|
+
return original(wrapper(function), *args)
|
|
23
|
+
|
|
24
|
+
def wrap_thread_function(thread_state, function):
|
|
25
|
+
if thread_state.value == 'internal':
|
|
26
|
+
next_id = counters.id + (counters.counter,)
|
|
27
|
+
counters.counter += 1
|
|
28
|
+
return functional.partial(with_thread_id, next_id, thread_state.wrap('internal', function))
|
|
29
|
+
else:
|
|
30
|
+
return function
|
|
31
|
+
|
|
32
|
+
def patch_thread_start(thread_state):
|
|
33
|
+
|
|
34
|
+
wrapper = functional.partial(wrap_thread_function, thread_state)
|
|
35
|
+
|
|
36
|
+
_thread.start_new = functional.partial(start_new_thread, _thread.start_new, wrapper)
|
|
37
|
+
_thread.start_new_thread = functional.partial(start_new_thread, _thread.start_new_thread, wrapper)
|
|
38
|
+
|
|
39
|
+
import threading
|
|
40
|
+
threading._start_new_thread = _thread.start_new_thread
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import retracesoftware.functional as functional
|
|
5
|
+
import retracesoftware.utils as utils
|
|
6
|
+
|
|
7
|
+
class Stub:
|
|
8
|
+
__slots__ = []
|
|
9
|
+
|
|
10
|
+
class ExtendedRef:
|
|
11
|
+
def __init__(self, module, name):
|
|
12
|
+
self.type = type
|
|
13
|
+
self.module = module
|
|
14
|
+
|
|
15
|
+
class StubRef:
|
|
16
|
+
def __init__(self, cls):
|
|
17
|
+
blacklist = ['__class__', '__dict__', '__module__', '__doc__', '__new__']
|
|
18
|
+
|
|
19
|
+
self.methods = []
|
|
20
|
+
self.static_methods = []
|
|
21
|
+
self.class_methods = []
|
|
22
|
+
|
|
23
|
+
for key,value in cls.__dict__.items():
|
|
24
|
+
if key not in blacklist:
|
|
25
|
+
if isinstance(value, classmethod):
|
|
26
|
+
self.class_methods.append(key)
|
|
27
|
+
elif isinstance(value, staticmethod):
|
|
28
|
+
self.class_methods.append(key)
|
|
29
|
+
elif utils.is_method_descriptor(value):
|
|
30
|
+
self.methods.append(key)
|
|
31
|
+
|
|
32
|
+
self.name = cls.__name__
|
|
33
|
+
self.module = cls.__module__
|
|
34
|
+
|
|
35
|
+
# def __init__(self, module, name, methods, members):
|
|
36
|
+
# self.name = name
|
|
37
|
+
# self.module = module
|
|
38
|
+
# self.methods = methods
|
|
39
|
+
# self.members = members
|
|
40
|
+
|
|
41
|
+
def __str__(self):
|
|
42
|
+
return f'StubRef(module = {self.module}, name = {self.name}, methods = {self.methods})'
|
|
43
|
+
|
|
44
|
+
def resolve(module, name):
|
|
45
|
+
try:
|
|
46
|
+
return getattr(sys.modules[module], name)
|
|
47
|
+
except:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
class StubMethodDescriptor(functional.repeatedly):
|
|
51
|
+
def __init__(self, name, next_result):
|
|
52
|
+
super().__init__(next_result)
|
|
53
|
+
self.__name__ = name
|
|
54
|
+
|
|
55
|
+
# @utils.striptraceback
|
|
56
|
+
# def __call__(self, *args, **kwargs):
|
|
57
|
+
# super().__call__(*args, **kwargs)
|
|
58
|
+
|
|
59
|
+
#@utils.striptraceback
|
|
60
|
+
def __str__(self):
|
|
61
|
+
return f"stub - {__name__}"
|
|
62
|
+
|
|
63
|
+
class StubMemberDescriptor:
|
|
64
|
+
def __init__(self, name, next_result):
|
|
65
|
+
self.next_result = next_result
|
|
66
|
+
self.__name__ = name
|
|
67
|
+
|
|
68
|
+
#@utils.striptraceback
|
|
69
|
+
def __get__(self, instance, owner):
|
|
70
|
+
if instance is None:
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
return self.next_result()
|
|
74
|
+
|
|
75
|
+
#@utils.striptraceback
|
|
76
|
+
def __set__(self, instance, value):
|
|
77
|
+
return self.next_result()
|
|
78
|
+
|
|
79
|
+
#@utils.striptraceback
|
|
80
|
+
def __delete__(self, instance):
|
|
81
|
+
return self.next_result()
|
|
82
|
+
|
|
83
|
+
#@utils.striptraceback
|
|
84
|
+
def __str__(self):
|
|
85
|
+
return f"stub member - {__name__}"
|
|
86
|
+
|
|
87
|
+
class StubFactory:
|
|
88
|
+
|
|
89
|
+
__slots__ = ['next_result', 'thread_state', 'cache']
|
|
90
|
+
|
|
91
|
+
def __init__(self, thread_state, next_result):
|
|
92
|
+
self.next_result = next_result
|
|
93
|
+
self.thread_state = thread_state
|
|
94
|
+
self.cache = {}
|
|
95
|
+
|
|
96
|
+
def create_member(self, name):
|
|
97
|
+
def disabled(*args, **kwargs):
|
|
98
|
+
if self.thread_state.value == 'disabled' and name == '__repr__':
|
|
99
|
+
return f"stub member - {name}"
|
|
100
|
+
else:
|
|
101
|
+
print(f'Error trying to call member descriptor: {name} {args} {kwargs}, retrace mode: {self.thread_state.value}')
|
|
102
|
+
utils.sigtrap(None)
|
|
103
|
+
os._exit(1)
|
|
104
|
+
|
|
105
|
+
next_result = self.thread_state.dispatch(disabled, external = self.next_result)
|
|
106
|
+
|
|
107
|
+
return StubMemberDescriptor(name = name, next_result = next_result)
|
|
108
|
+
|
|
109
|
+
def create_method(self, name):
|
|
110
|
+
|
|
111
|
+
def disabled(*args, **kwargs):
|
|
112
|
+
if self.thread_state.value == 'disabled' and name == '__repr__':
|
|
113
|
+
return f"stub - {name}"
|
|
114
|
+
else:
|
|
115
|
+
return None
|
|
116
|
+
# print(f'Error trying to call descriptor: {name} {args} {kwargs}, retrace mode: {self.thread_state.value}')
|
|
117
|
+
|
|
118
|
+
# import traceback
|
|
119
|
+
# traceback.print_stack()
|
|
120
|
+
|
|
121
|
+
# utils.sigtrap(None)
|
|
122
|
+
# os._exit(1)
|
|
123
|
+
|
|
124
|
+
next_result = self.thread_state.dispatch(disabled, external = self.next_result)
|
|
125
|
+
|
|
126
|
+
func = functional.repeatedly(next_result)
|
|
127
|
+
func.__name__ = name
|
|
128
|
+
|
|
129
|
+
return func
|
|
130
|
+
|
|
131
|
+
def create_stubtype(self, spec):
|
|
132
|
+
|
|
133
|
+
assert self.thread_state.value == 'disabled'
|
|
134
|
+
|
|
135
|
+
slots = {
|
|
136
|
+
'__module__': spec.module,
|
|
137
|
+
'__qualname__': spec.name,
|
|
138
|
+
'__name__': spec.name,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for method in spec.methods:
|
|
142
|
+
slots[method] = self.create_method(method)
|
|
143
|
+
assert utils.is_method_descriptor(slots[method])
|
|
144
|
+
|
|
145
|
+
def on_disabled(name):
|
|
146
|
+
print(f'Error trying to get/set attribute: {name}, when retrace mode: {self.thread_state.value} was not external')
|
|
147
|
+
utils.sigtrap(None)
|
|
148
|
+
os._exit(1)
|
|
149
|
+
|
|
150
|
+
#@utils.striptraceback
|
|
151
|
+
def getattr(instance, name):
|
|
152
|
+
print('In stub getattr!!!')
|
|
153
|
+
if self.thread_state.value == 'external':
|
|
154
|
+
return self.next_result()
|
|
155
|
+
else:
|
|
156
|
+
print(f'Error trying to get attribute: {name}, when retrace mode: {self.thread_state.value} was not external')
|
|
157
|
+
utils.sigtrap(None)
|
|
158
|
+
os._exit(1)
|
|
159
|
+
|
|
160
|
+
#@utils.striptraceback
|
|
161
|
+
def setattr(instance, name, value):
|
|
162
|
+
if self.thread_state.value == 'external':
|
|
163
|
+
return self.next_result()
|
|
164
|
+
else:
|
|
165
|
+
print(f'Error trying to set attribute: {name}, to: {value} when retrace mode: {self.thread_state.value} was not external')
|
|
166
|
+
utils.sigtrap(None)
|
|
167
|
+
os._exit(1)
|
|
168
|
+
|
|
169
|
+
slots['__getattr__'] = self.thread_state.method_dispatch(on_disabled, external = self.next_result)
|
|
170
|
+
# slots['__getattr__'] = getattr
|
|
171
|
+
slots['__setattr__'] = self.thread_state.method_dispatch(on_disabled, external = self.next_result)
|
|
172
|
+
|
|
173
|
+
resolved = resolve(spec.module, spec.name)
|
|
174
|
+
|
|
175
|
+
# if isinstance(resolved, type):
|
|
176
|
+
# slots['__class__'] = property(functional.repeatedly(resolved))
|
|
177
|
+
|
|
178
|
+
# else:
|
|
179
|
+
# utils.sigtrap(f'{spec.module}.{spec.name}')
|
|
180
|
+
|
|
181
|
+
stubtype = type(spec.name, (Stub, ), slots)
|
|
182
|
+
|
|
183
|
+
for method in spec.methods:
|
|
184
|
+
slots[method].__objclass__ = stubtype
|
|
185
|
+
|
|
186
|
+
stubtype.__retrace_target_type__ = resolved
|
|
187
|
+
|
|
188
|
+
return stubtype
|
|
189
|
+
|
|
190
|
+
def __call__(self, spec):
|
|
191
|
+
if spec not in self.cache:
|
|
192
|
+
self.cache[spec] = self.create_stubtype(spec)
|
|
193
|
+
|
|
194
|
+
stubtype = self.cache[spec]
|
|
195
|
+
return stubtype.__new__(stubtype)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import retracesoftware.functional as functional
|
|
2
|
+
import retracesoftware_utils as utils
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import _thread
|
|
6
|
+
|
|
7
|
+
# def thread_aware_writer(writer):
|
|
8
|
+
# on_thread_switch = functional.sequence(utils.thread_id(), writer.handle('THREAD_SWITCH'))
|
|
9
|
+
# return utils.threadawareproxy(on_thread_switch = on_thread_switch, target = writer)
|
|
10
|
+
|
|
11
|
+
class ThreadSwitch:
|
|
12
|
+
__slots__ = ['id']
|
|
13
|
+
|
|
14
|
+
def __init__(self, id):
|
|
15
|
+
self.id = id
|
|
16
|
+
|
|
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)))
|
|
26
|
+
|
|
27
|
+
def write_thread_switch(writer):
|
|
28
|
+
on_thread_switch = functional.repeatedly(functional.sequence(utils.thread_id, writer))
|
|
29
|
+
|
|
30
|
+
return lambda f: utils.thread_aware_proxy(target = f, on_thread_switch = on_thread_switch, sticky = False)
|
|
31
|
+
|
|
32
|
+
def prefix_with_thread_id(f, thread_id):
|
|
33
|
+
current = None
|
|
34
|
+
|
|
35
|
+
def next():
|
|
36
|
+
nonlocal current, f
|
|
37
|
+
if current is None: current = thread_id()
|
|
38
|
+
|
|
39
|
+
obj = f()
|
|
40
|
+
|
|
41
|
+
while issubclass(type(obj), ThreadSwitch):
|
|
42
|
+
current = obj.id
|
|
43
|
+
obj = f()
|
|
44
|
+
|
|
45
|
+
# print(f'prefix_with_thread_id: {(current, obj)}')
|
|
46
|
+
return (current, obj)
|
|
47
|
+
|
|
48
|
+
return next
|
|
49
|
+
|
|
50
|
+
def per_thread_messages(messages):
|
|
51
|
+
thread_id = utils.thread_id
|
|
52
|
+
# thread_id = lambda: 'FOOOOO!!!'
|
|
53
|
+
|
|
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)
|
|
63
|
+
|
|
64
|
+
# def next():
|
|
65
|
+
# thread,message = demux(thread_id())
|
|
66
|
+
# return message
|
|
67
|
+
|
|
68
|
+
# return next
|
|
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
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import retracesoftware.stream as stream
|
|
2
|
+
from retracesoftware.run import install, run_with_retrace, ImmutableTypes, thread_states
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from retracesoftware.proxy.thread import thread_id
|
|
7
|
+
from retracesoftware.proxy.replay import ReplayProxySystem
|
|
8
|
+
import retracesoftware.utils as utils
|
|
9
|
+
import json
|
|
10
|
+
from retracesoftware.stackdifference import on_stack_difference
|
|
11
|
+
|
|
12
|
+
def parse_args():
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
prog=f"python -m {sys.argv[0]}",
|
|
15
|
+
description="Run a Python module with debugging, logging, etc."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
'--verbose',
|
|
20
|
+
action='store_true',
|
|
21
|
+
help='Enable verbose output'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'--timeout', # or '-r'
|
|
26
|
+
type = int, # ensures it's a string (optional, but safe)
|
|
27
|
+
default = 60, # default value if not provided
|
|
28
|
+
help = 'the directory to place the recording files'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
'--recording', # or '-r'
|
|
33
|
+
type = str, # ensures it's a string (optional, but safe)
|
|
34
|
+
default = '.', # default value if not provided
|
|
35
|
+
help = 'the directory to place the recording files'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return parser.parse_args()
|
|
39
|
+
|
|
40
|
+
def load_json(file):
|
|
41
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
42
|
+
return json.load(f)
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
# import debugpy
|
|
46
|
+
|
|
47
|
+
# port = 1977
|
|
48
|
+
# debugpy.listen(("127.0.0.1", port))
|
|
49
|
+
# debugpy.wait_for_client()
|
|
50
|
+
|
|
51
|
+
# self.reader = stream.reader(path,
|
|
52
|
+
# thread = thread_id,
|
|
53
|
+
# timeout_seconds = 60,
|
|
54
|
+
# verbose = verbose,
|
|
55
|
+
# on_stack_difference = thread_state.wrap('disabled', on_stack_difference),
|
|
56
|
+
# magic_markers = magic_markers)
|
|
57
|
+
|
|
58
|
+
# return ReplayProxySystem(thread_state = thread_state,
|
|
59
|
+
# immutable_types = immutable_types,
|
|
60
|
+
# tracing_config = tracing_config,
|
|
61
|
+
# mainscript = mainscript,
|
|
62
|
+
# path = recording_path / 'trace.bin',
|
|
63
|
+
# tracecalls = env_truthy('RETRACE_ALL', False),
|
|
64
|
+
# verbose = verbose,
|
|
65
|
+
# magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
|
|
66
|
+
|
|
67
|
+
args = parse_args()
|
|
68
|
+
path = Path(args.recording)
|
|
69
|
+
|
|
70
|
+
if not path.exists():
|
|
71
|
+
raise Exception(f"Recording path: {path} does not exist")
|
|
72
|
+
|
|
73
|
+
settings = load_json(path / "settings.json")
|
|
74
|
+
|
|
75
|
+
thread_state = utils.ThreadState(*thread_states)
|
|
76
|
+
|
|
77
|
+
with stream.reader(path = path / 'trace.bin',
|
|
78
|
+
thread = thread_id,
|
|
79
|
+
timeout_seconds = args.timeout,
|
|
80
|
+
verbose = args.verbose,
|
|
81
|
+
on_stack_difference = thread_state.wrap('disabled', on_stack_difference),
|
|
82
|
+
magic_markers = settings['magic_markers']) as reader:
|
|
83
|
+
|
|
84
|
+
tracing_config = {}
|
|
85
|
+
|
|
86
|
+
system = ReplayProxySystem(
|
|
87
|
+
reader = reader,
|
|
88
|
+
thread_state = thread_state,
|
|
89
|
+
immutable_types = ImmutableTypes(),
|
|
90
|
+
tracing_config = tracing_config,
|
|
91
|
+
tracecalls = settings['trace_inputs'])
|
|
92
|
+
|
|
93
|
+
install(system)
|
|
94
|
+
|
|
95
|
+
run_with_retrace(system, settings['argv'])
|
|
96
|
+
|
|
97
|
+
# install(system)
|
|
98
|
+
|
|
99
|
+
# run_with_retrace(system, args.rest[1:])
|
|
100
|
+
|
|
101
|
+
# runpy.run_module('foo', run_name="__main__", alter_sys=False)
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|