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.
@@ -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
- "with_state": {
163
- "internal": ["create_dynamic"]
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._bootstrap": {
189
+ "importlib": {
188
190
  "with_state": {
189
- "bootstrap": ["_load_unlocked", "_find_spec"]
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
- "replace": {
227
- "set": "retracesoftware_utils.set",
228
- "frozenset": "retracesoftware_utils.frozenset"
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
- return {name: func(self, mod_dict[name]) for name in spec if name in mod_dict}
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
- # system.set_thread_id(0)
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
- def start_new_thread(function, *args):
279
- # synchronized, replay shoudl yield correct number
280
- thread_id = self.thread_counter()
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
- def threadrunner(*args, **kwargs):
283
- nonlocal thread_id
284
- self.system.set_thread_id(thread_id)
313
+ # def threadrunner(*args, **kwargs):
314
+ # nonlocal thread_id
315
+ # self.system.set_thread_id(thread_id)
285
316
 
286
- with self.thread_state.select('internal'):
287
- # if self.tracing:
288
- # FrameTracer.install(self.thread_state.dispatch(noop, internal = self.checkpoint))
289
- return function(*args, **kwargs)
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
- return value(threadrunner, *args)
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
- return result
315
- else:
316
- return exec(source, *args,**kwargs)
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 exec_wrapper
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
- if self.thread_state.value == 'internal' and not hasattr(module, '__retrace__'):
478
- with self.thread_state.select('bootstrap'):
479
- configs = list(self.configs(module))
480
-
481
- if len(configs) > 0:
482
- if len(configs) > 1:
483
- raise Exception(f'TODO')
484
- else:
485
- module.__retrace__ = None
486
- try:
487
- self.patch_module_with_name(configs[0], module)
488
- except Exception as error:
489
- raise Exception(f'Error patching module: {configs[0]}') from error
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
- def __init__(self, id):
18
- self.id = id
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:
@@ -1,3 +1,3 @@
1
1
  from retracesoftware.proxy.record import RecordProxySystem
2
2
  from retracesoftware.proxy.replay import ReplayProxySystem
3
- from retracesoftware.proxy.thread import set_thread_id, per_thread_messages
3
+ from retracesoftware.proxy.thread import per_thread_messages
@@ -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
- value = getattr(cls, name)
218
- if is_descriptor(value):
219
- if utils.is_method_descriptor(value):
220
- spec[name] = wrap(value)
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
 
@@ -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
- self.thread_switch_monitor = \
106
- utils.thread_switch_monitor(
107
- functional.repeatedly(functional.sequence(utils.thread_id, self.writer)))
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 = watch
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)
@@ -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
- while True:
84
- next = self.messages()
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
- if next == 'CALL':
87
- func = self.messages()
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
- try:
92
- func(*args, **kwargs)
93
- except:
94
- pass
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
- elif next == 'RESULT':
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 sync(self, function):
200
- return utils.observer(on_call = functional.always(self.read_sync), function = function)
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(self.reader),
222
- deserialize)
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
- self.read_sync = functional.always(lambda: self.read_required('SYNC'))
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)
@@ -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 set_thread_id(writer, id):
15
- utils.set_thread_id(writer.handle(ThreadSwitch(id)))
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, current):
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
- demux = utils.demux(source = prefix_with_thread_id(messages, thread_id()), key_function = lambda obj: obj[0])
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: retracesoftware_proxy
3
- Version: 0.1.13
3
+ Version: 0.1.15
4
4
  License: Apache-2.0
5
5
  Requires-Dist: retracesoftware_utils
6
6
  Requires-Dist: retracesoftware_functional
@@ -1,28 +1,29 @@
1
1
  retracesoftware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- retracesoftware/config.json,sha256=WfqWtMeimFWl9aEs0V34Nshb77TmXBeTkwT6alWpN2Q,8779
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=IUkQx5kr4AnniKZ2gWnnx2is1j_y_eb7nz2UKyHyqSA,18894
9
+ retracesoftware/install/patcher.py,sha256=_J8YTDd0QNLgURRyrUFikNsImgMOBhCv8Nmps5rjkZI,20241
10
10
  retracesoftware/install/predicate.py,sha256=tX7NQc0rGkyyHYO3mduYHcJHbw1wczT53m_Dpkzo6do,2679
11
- retracesoftware/install/record.py,sha256=tseF_jV4k4HLPTgBPJdjcahl4EQqagoiisMAdGNC52Q,3257
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=ZlDZIuUmKFsE9Tvfd2EKGabTepqv8nrbr5pQhCM3IKc,193
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=ZX6eNT8go-YDbLWD2pq5j_V8DZAHteu-iNfkMO8jLWg,7567
20
- retracesoftware/proxy/proxytype.py,sha256=H6SJKX0izor1yLOK_QxRlfKLcJC08eMuIYJ1amnoZvQ,12812
21
- retracesoftware/proxy/record.py,sha256=vtNsoTHP5gsBVJ6dTGHR7QXHGbKm0eONfGcXOb9fhFk,4696
22
- retracesoftware/proxy/replay.py,sha256=Ckkl7v-n-4KWEJXENOsMjmtNLguYudVgLrguVLLCMEA,7742
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=-SvnyVbANkmX2lLRpOvFtkpdpAoF6DhnnYdOOs7Q8vo,1379
25
- retracesoftware_proxy-0.1.13.dist-info/METADATA,sha256=yQJQrwJgTMPNl8u68MMD7QycdnOOrIW04JersxwXHXk,203
26
- retracesoftware_proxy-0.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
- retracesoftware_proxy-0.1.13.dist-info/top_level.txt,sha256=hYHsR6txLidmqvjBMITpIHvmJJbmoCAgr76-IpZPRz8,16
28
- retracesoftware_proxy-0.1.13.dist-info/RECORD,,
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,,