bitfab 0.36.10 → 0.36.11
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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/bitfab/client.rb +50 -40
- data/lib/bitfab/mock_override.rb +4 -4
- data/lib/bitfab/replay.rb +51 -33
- data/lib/bitfab/span_context.rb +22 -1
- data/lib/bitfab/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d23fea40d370a085bd028fbf7b605a6b66bc0732a1580683b3f027b5b2082e9
|
|
4
|
+
data.tar.gz: a477ee5856fc6fb25a39a6088f820337cd2bcb327c3b028c9051d0b0f84edd5d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb155e9ec03187b1f9f80a918b8df651bab41e181afcdd5c068c9645ab6b9c94884d7f8c41cab43bd50e878226e3d075962c02ac27e2b33ed2729e979b4e4464
|
|
7
|
+
data.tar.gz: 1da28622e46e44b40f5296ed699ee13e897e7bd0a54177e32f3c629fb8004ea5f87a86546a37d608541ff771ba648fffb128bd613d7aefea300c8898038c7e47
|
data/README.md
CHANGED
|
@@ -301,7 +301,7 @@ client.replay(pipeline, :process, trace_function_key: "my-fn", mock: "marked")
|
|
|
301
301
|
# "none": everything runs real code
|
|
302
302
|
client.replay(pipeline, :process, trace_function_key: "my-fn", mock: "none")
|
|
303
303
|
|
|
304
|
-
# "all": every child
|
|
304
|
+
# "all": every matched recorded child returns history; missing occurrences fail closed
|
|
305
305
|
client.replay(pipeline, :process, trace_function_key: "my-fn", mock: "all")
|
|
306
306
|
```
|
|
307
307
|
|
|
@@ -331,7 +331,7 @@ class Pipeline
|
|
|
331
331
|
end
|
|
332
332
|
```
|
|
333
333
|
|
|
334
|
-
Use the default `mock: "marked"` behavior when you want to iterate on `process`'s logic without paying for the LLM call each run. Use `mock: "all"` for the cheapest possible replay (every child span returns its recorded output).
|
|
334
|
+
Use the default `mock: "marked"` behavior when you want to iterate on `process`'s logic without paying for the LLM call each run. Use `mock: "all"` for the cheapest possible replay (every matched recorded child span returns its recorded output; a missing occurrence fails the item closed).
|
|
335
335
|
|
|
336
336
|
### Error Handling
|
|
337
337
|
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Bitfab
|
|
|
16
16
|
UUID_PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
|
17
17
|
|
|
18
18
|
# Sentinel returned by check_mock_replay when this span should run real
|
|
19
|
-
# code (no mock active
|
|
19
|
+
# code (no mock active or the base strategy did not select it).
|
|
20
20
|
# Using a sentinel rather than nil/false avoids confusing legitimate mocked
|
|
21
21
|
# outputs (which may themselves be nil or false).
|
|
22
22
|
MOCK_REPLAY_MISS = Object.new.freeze
|
|
@@ -87,9 +87,10 @@ module Bitfab
|
|
|
87
87
|
# @param receiver [Object, Class] an instance for instance methods, or a Class for class methods
|
|
88
88
|
# @param method_name [Symbol] the method to replay
|
|
89
89
|
# @param trace_function_key [String] the trace function key for this method
|
|
90
|
-
# @param limit [Integer, nil] maximum number of traces to replay (default: 5
|
|
91
|
-
#
|
|
92
|
-
# determines how many traces replay
|
|
90
|
+
# @param limit [Integer, nil] maximum number of traces to replay (default: 5;
|
|
91
|
+
# maximum: 5,000). Ignored when trace_ids or dataset_id is passed because
|
|
92
|
+
# either source already determines how many traces replay. Supplying
|
|
93
|
+
# trace_ids also emits a warning.
|
|
93
94
|
# @param trace_ids [Array<String>, nil] optional list of trace IDs to replay (max 100)
|
|
94
95
|
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
95
96
|
# @param code_change_description [String, nil] optional rationale for the
|
|
@@ -111,7 +112,9 @@ module Bitfab
|
|
|
111
112
|
# same org and trace function or the server rejects the replay
|
|
112
113
|
# @param mock [String] mock strategy for child spans: "marked" (default),
|
|
113
114
|
# "none", or "all". "marked" only mocks spans declared with
|
|
114
|
-
# mock_on_replay: true; "all" mocks every child span.
|
|
115
|
+
# mock_on_replay: true; "all" mocks every matched recorded child span. A selected
|
|
116
|
+
# occurrence that is unavailable errors the item without executing the
|
|
117
|
+
# real child.
|
|
115
118
|
# @param adapt_inputs [#call, nil] optional hook to reshape recorded inputs
|
|
116
119
|
# onto the method's current signature when its shape changed after the
|
|
117
120
|
# traces were captured. Receives (args, kwargs, ctx) where ctx is
|
|
@@ -122,16 +125,17 @@ module Bitfab
|
|
|
122
125
|
# match.call(node) selects spans to substitute (node is
|
|
123
126
|
# { trace_function_key:, span_name:, type:, original_span_id: }). value is
|
|
124
127
|
# EITHER a flat value injected directly OR a callable invoked with
|
|
125
|
-
# { node:, inputs:, get_original_output: }; its result becomes
|
|
126
|
-
# output (full replacement), so downstream real code runs against it. The
|
|
128
|
+
# { node:, inputs:, kwargs:, get_original_output: }; its result becomes
|
|
129
|
+
# the span's output (full replacement), so downstream real code runs against it. The
|
|
127
130
|
# first matching override wins. Per-call overrides take precedence over
|
|
128
131
|
# those registered via register_mock_override, and both take precedence
|
|
129
132
|
# over the base mock strategy. The root span is never overridden.
|
|
130
|
-
# @param db_branch [Hash, nil]
|
|
131
|
-
#
|
|
132
|
-
# +:min_cu+/+:max_cu+ size the branch compute
|
|
133
|
-
# +:warmup_sql+ warms its cache. Read the
|
|
134
|
-
# replayed method with
|
|
133
|
+
# @param db_branch [Boolean, Hash, nil] +true+ or a Hash enables database
|
|
134
|
+
# branching; +false+/+nil+ leave it off. An empty Hash uses the mirror
|
|
135
|
+
# project's own sizing. Keys +:min_cu+/+:max_cu+ size the branch compute
|
|
136
|
+
# in Neon Compute Units and +:warmup_sql+ warms its cache. Read the
|
|
137
|
+
# resolved branch inside the replayed method with
|
|
138
|
+
# +Bitfab.current_replay_branch+.
|
|
135
139
|
# @param on_item_finish [#call, nil] optional callback invoked exactly once
|
|
136
140
|
# per item as it finishes, always with a running-totals hash containing
|
|
137
141
|
# { completed:, total:, succeeded:, errored:, item: }. It never receives a
|
|
@@ -281,7 +285,7 @@ module Bitfab
|
|
|
281
285
|
)
|
|
282
286
|
resolved_capture_when = "always"
|
|
283
287
|
end
|
|
284
|
-
return yield if resolved_capture_when == "nested" && SpanContext.current.nil?
|
|
288
|
+
return yield if resolved_capture_when == "nested" && SpanContext.current.nil? && ReplayContext.current_span.nil?
|
|
285
289
|
|
|
286
290
|
# Span setup runs before the user's block. Tracing is a side-channel, so
|
|
287
291
|
# if anything here raises (id generation, trace-state bookkeeping, a
|
|
@@ -297,8 +301,8 @@ module Bitfab
|
|
|
297
301
|
resolved_test_run_id = nil
|
|
298
302
|
resolved_input_source_span_id = nil
|
|
299
303
|
begin
|
|
300
|
-
parent = SpanContext.current
|
|
301
304
|
replay_ctx = ReplayContext.current
|
|
305
|
+
parent = SpanContext.current || ReplayContext.current_span
|
|
302
306
|
trace_id = parent ? parent[:trace_id] : (replay_ctx&.dig(:trace_id) || SecureRandom.uuid)
|
|
303
307
|
span_id = SecureRandom.uuid
|
|
304
308
|
parent_span_id = parent&.dig(:span_id)
|
|
@@ -440,12 +444,14 @@ module Bitfab
|
|
|
440
444
|
end
|
|
441
445
|
|
|
442
446
|
begin
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
ReplayContext.with_span(trace_id:, span_id:) do
|
|
448
|
+
SpanContext.with_span(trace_id:, span_id:) do
|
|
449
|
+
result = yield
|
|
450
|
+
ensure
|
|
451
|
+
# Capture contexts before the span context is popped
|
|
452
|
+
span_contexts = SpanContext.current&.dig(:contexts)
|
|
453
|
+
span_prompt = SpanContext.current&.dig(:prompt)
|
|
454
|
+
end
|
|
449
455
|
end
|
|
450
456
|
rescue => e
|
|
451
457
|
error = e.message
|
|
@@ -466,12 +472,6 @@ module Bitfab
|
|
|
466
472
|
# collects yielded values as the span output, and finalizes the span
|
|
467
473
|
# once iteration completes (or errors).
|
|
468
474
|
#
|
|
469
|
-
# Limitation: when the source enumerator itself runs its body in a
|
|
470
|
-
# separate fiber (e.g. `Enumerator.new { |y| ... }` or `enum_for(...)`
|
|
471
|
-
# without a block), nested `bitfab_span` calls inside that body fiber
|
|
472
|
-
# still see an empty stack because `Thread.current[STACK_KEY]` is
|
|
473
|
-
# fiber-local. Lazy chains over collections (`.lazy.map`) and ordinary
|
|
474
|
-
# `each` callbacks DO run in the iterating fiber and nest correctly.
|
|
475
475
|
if result.is_a?(Enumerator)
|
|
476
476
|
return wrap_enumerator(result, trace_id:, span_id:, finalize:)
|
|
477
477
|
end
|
|
@@ -544,19 +544,24 @@ module Bitfab
|
|
|
544
544
|
Enumerator.new do |yielder|
|
|
545
545
|
# Push our span onto this fiber's stack so anything that runs in this
|
|
546
546
|
# fiber (including the user's lazy block and `each` callbacks) sees
|
|
547
|
-
# the right parent.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
547
|
+
# the right parent. The fiber bridge carries the same parent into
|
|
548
|
+
# Enumerator.new / enum_for source fibers on this thread.
|
|
549
|
+
ReplayContext.with_span(trace_id:, span_id:) do
|
|
550
|
+
SpanContext.with_fiber_bridge(trace_id:, span_id:) do
|
|
551
|
+
SpanContext.stack.push(span_entry)
|
|
552
|
+
begin
|
|
553
|
+
source.each do |value|
|
|
554
|
+
yielded << value
|
|
555
|
+
yielder << value
|
|
556
|
+
end
|
|
557
|
+
finalize.call(yielded, nil)
|
|
558
|
+
rescue => e
|
|
559
|
+
finalize.call(yielded, e.message)
|
|
560
|
+
raise
|
|
561
|
+
ensure
|
|
562
|
+
SpanContext.stack.pop
|
|
563
|
+
end
|
|
553
564
|
end
|
|
554
|
-
finalize.call(yielded, nil)
|
|
555
|
-
rescue => e
|
|
556
|
-
finalize.call(yielded, e.message)
|
|
557
|
-
raise
|
|
558
|
-
ensure
|
|
559
|
-
SpanContext.stack.pop
|
|
560
565
|
end
|
|
561
566
|
end
|
|
562
567
|
end
|
|
@@ -799,7 +804,11 @@ module Bitfab
|
|
|
799
804
|
return MOCK_REPLAY_MISS
|
|
800
805
|
end
|
|
801
806
|
|
|
802
|
-
|
|
807
|
+
unless mock_entry
|
|
808
|
+
raise StandardError,
|
|
809
|
+
"Replay selected span '#{trace_function_key}:#{span_name}' for mocking, " \
|
|
810
|
+
"but recorded occurrence #{call_index + 1} is unavailable. The real span was not executed."
|
|
811
|
+
end
|
|
803
812
|
|
|
804
813
|
[resolve_recorded_output(mock_entry, replay_ctx), "recorded"]
|
|
805
814
|
end
|
|
@@ -902,7 +911,8 @@ module Bitfab
|
|
|
902
911
|
# @param capture_when [String, Symbol] "always" to allow a root trace, or
|
|
903
912
|
# "nested" to capture only when another Bitfab span is active. Unknown
|
|
904
913
|
# values warn once and default to "always"
|
|
905
|
-
# @param mock_on_replay [Boolean] mark this span for the default "marked"
|
|
914
|
+
# @param mock_on_replay [Boolean] mark this span for the default "marked"
|
|
915
|
+
# mock strategy. A missing selected occurrence fails closed.
|
|
906
916
|
def wrap(klass, method_name, name: nil, type: "custom", capture_when: "always", mock_on_replay: false)
|
|
907
917
|
Bitfab::Traceable.wrap(
|
|
908
918
|
klass, method_name,
|
data/lib/bitfab/mock_override.rb
CHANGED
|
@@ -31,10 +31,10 @@ module Bitfab
|
|
|
31
31
|
# nil is a legitimate injected value. To inject a proc as the literal
|
|
32
32
|
# output, wrap it in a callable value: value: ->(ctx) { the_proc }.
|
|
33
33
|
#
|
|
34
|
-
# Unlike the TypeScript SDK,
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
34
|
+
# Unlike the TypeScript SDK, get_original_output is synchronous. Under
|
|
35
|
+
# non-"all" replay the first access may fetch the recorded output lazily;
|
|
36
|
+
# that fetch is memoized for the replay item. Under mock: "all" the output is
|
|
37
|
+
# inline even when overrides are present. Ruby has no async-span limitation.
|
|
38
38
|
module MockOverride
|
|
39
39
|
module_function
|
|
40
40
|
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -41,28 +41,47 @@ module Bitfab
|
|
|
41
41
|
# - "marked" : only spans declared with mock_on_replay: true return historical
|
|
42
42
|
# output; everything else runs real code (default)
|
|
43
43
|
# - "none" : every child span runs real code
|
|
44
|
-
# - "all" : every child span returns its historical
|
|
44
|
+
# - "all" : every matched recorded child span returns its historical
|
|
45
|
+
# output; a missing occurrence fails the item closed
|
|
45
46
|
MOCK_STRATEGIES = %w[none all marked].freeze
|
|
46
47
|
|
|
47
|
-
#
|
|
48
|
+
# Replay context shared by fibers on the current thread. Ruby's
|
|
49
|
+
# Thread#[] storage is fiber-local, so use true thread variables here: lazy
|
|
50
|
+
# Enumerator bodies run in another fiber but must retain replay mocking.
|
|
48
51
|
module ReplayContext
|
|
49
52
|
module_function
|
|
50
53
|
|
|
51
54
|
def current
|
|
52
|
-
Thread.current
|
|
55
|
+
Thread.current.thread_variable_get(REPLAY_CONTEXT_KEY)
|
|
53
56
|
end
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
def current_span
|
|
59
|
+
current&.dig(:span_stack)&.last
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def with_span(trace_id:, span_id:)
|
|
63
|
+
ctx = current
|
|
64
|
+
return yield unless ctx
|
|
65
|
+
|
|
66
|
+
entry = {trace_id:, span_id:}
|
|
67
|
+
ctx[:span_stack].push(entry)
|
|
68
|
+
yield
|
|
69
|
+
ensure
|
|
70
|
+
ctx[:span_stack].pop if ctx
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Execute a block with replay context set for every fiber on the current
|
|
74
|
+
# thread. Child threads and processes intentionally do not inherit it.
|
|
57
75
|
def with_context(test_run_id:, input_source_span_id: nil, input_source_trace_id: nil, trace_id: nil,
|
|
58
76
|
mock_tree: nil, mock_strategy: nil, mock_overrides: nil, fetch_span_output: nil,
|
|
59
77
|
db_branch_lease: nil, source_bitfab_trace_id: nil)
|
|
60
|
-
previous = Thread.current
|
|
78
|
+
previous = Thread.current.thread_variable_get(REPLAY_CONTEXT_KEY)
|
|
61
79
|
ctx = {
|
|
62
80
|
test_run_id:,
|
|
63
81
|
input_source_span_id:,
|
|
64
82
|
input_source_trace_id:,
|
|
65
|
-
trace_id
|
|
83
|
+
trace_id:,
|
|
84
|
+
span_stack: []
|
|
66
85
|
}
|
|
67
86
|
if mock_tree
|
|
68
87
|
ctx[:mock_tree] = mock_tree
|
|
@@ -73,8 +92,9 @@ module Bitfab
|
|
|
73
92
|
# is present. process_single_item fetches the tree whenever overrides
|
|
74
93
|
# exist, even under mock: "none".
|
|
75
94
|
ctx[:mock_overrides] = mock_overrides if mock_overrides && !mock_overrides.empty?
|
|
76
|
-
# Lazy per-span output fetcher for
|
|
77
|
-
#
|
|
95
|
+
# Lazy per-span output fetcher for a payload-free non-"all" tree path.
|
|
96
|
+
# Absent on the eager "all" path, whose outputs are inline even when
|
|
97
|
+
# overrides are present.
|
|
78
98
|
ctx[:fetch_span_output] = fetch_span_output if fetch_span_output
|
|
79
99
|
end
|
|
80
100
|
# The per-trace DB branch (resolved server-side) and the Bitfab trace ID
|
|
@@ -94,10 +114,10 @@ module Bitfab
|
|
|
94
114
|
# used" from "branch was offered".
|
|
95
115
|
ctx[:db_branch_lease] = db_branch_lease if db_branch_lease
|
|
96
116
|
ctx[:source_bitfab_trace_id] = source_bitfab_trace_id if source_bitfab_trace_id
|
|
97
|
-
Thread.current
|
|
117
|
+
Thread.current.thread_variable_set(REPLAY_CONTEXT_KEY, ctx)
|
|
98
118
|
yield
|
|
99
119
|
ensure
|
|
100
|
-
Thread.current
|
|
120
|
+
Thread.current.thread_variable_set(REPLAY_CONTEXT_KEY, previous)
|
|
101
121
|
end
|
|
102
122
|
end
|
|
103
123
|
|
|
@@ -163,8 +183,9 @@ module Bitfab
|
|
|
163
183
|
# @param method_name [Symbol] the method to replay
|
|
164
184
|
# @param trace_function_key [String] the trace function key for this method
|
|
165
185
|
# @param limit [Integer, nil] maximum number of traces to replay (default: 5).
|
|
166
|
-
# Ignored when trace_ids is passed
|
|
167
|
-
# already determines how many traces replay.
|
|
186
|
+
# Ignored when trace_ids or dataset_id is passed because either source
|
|
187
|
+
# already determines how many traces replay. Supplying trace_ids also
|
|
188
|
+
# emits a warning.
|
|
168
189
|
# @param trace_ids [Array<String>, nil] optional list of trace IDs to replay (max 100)
|
|
169
190
|
# @param name [String, nil] optional display name for the resulting experiment/test run
|
|
170
191
|
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
@@ -187,7 +208,7 @@ module Bitfab
|
|
|
187
208
|
# same org and trace function or the server rejects the replay
|
|
188
209
|
# @param mock [String] mock strategy for child spans: "marked" (default),
|
|
189
210
|
# "none", or "all". "marked" only mocks spans declared with
|
|
190
|
-
# mock_on_replay: true; "all" mocks every child span.
|
|
211
|
+
# mock_on_replay: true; "all" mocks every matched recorded child span.
|
|
191
212
|
# @param adapt_inputs [#call, nil] optional hook to reshape recorded inputs
|
|
192
213
|
# onto the method's current signature when its shape changed after the
|
|
193
214
|
# traces were captured. Receives (args, kwargs, ctx) where ctx is
|
|
@@ -206,11 +227,11 @@ module Bitfab
|
|
|
206
227
|
# errored:, item: } where item is { trace_id:, original_trace_id:,
|
|
207
228
|
# original_span_id:, error:, duration_ms: } for the single item that just
|
|
208
229
|
# finished (source_trace_id/source_span_id remain as deprecated aliases).
|
|
209
|
-
# trace_id is the new server replay trace id,
|
|
210
|
-
#
|
|
211
|
-
# ORIGINAL (historical) trace that was replayed,
|
|
212
|
-
# error or nil, and
|
|
213
|
-
#
|
|
230
|
+
# trace_id is the new server replay trace id, available after that item's
|
|
231
|
+
# trace is flushed (nil only when delivery could not be confirmed);
|
|
232
|
+
# original_trace_id is the ORIGINAL (historical) trace that was replayed,
|
|
233
|
+
# error is that item's replay error or nil, and duration_ms is the source
|
|
234
|
+
# trace's recorded duration. Use it to
|
|
214
235
|
# render replay progress (e.g. a per-trace log). A raising callback never
|
|
215
236
|
# crashes the run.
|
|
216
237
|
# @param on_progress [#call, nil] deprecated compatibility callback. It
|
|
@@ -807,8 +828,8 @@ module Bitfab
|
|
|
807
828
|
# (only when include_db_branch_lease was sent). Release it in the +ensure+
|
|
808
829
|
# below so any raise (span fetch, mock-tree build, or the replayed
|
|
809
830
|
# method) frees the Neon resource. Items whose source trace had no
|
|
810
|
-
# snapshot ref arrive without a lease
|
|
811
|
-
#
|
|
831
|
+
# snapshot ref arrive without a lease, so the app uses its normal DB
|
|
832
|
+
# path. Unsafe calls on that path still require replay mocking.
|
|
812
833
|
lease = include_db_branch_lease ? server_item["dbBranchLease"] : nil
|
|
813
834
|
# A resolve that was ATTEMPTED and failed is different: the caller asked
|
|
814
835
|
# for a branch, so running their method against live data would produce a
|
|
@@ -847,10 +868,11 @@ module Bitfab
|
|
|
847
868
|
# the tree must be fetched for them to fire even under mock: "none".
|
|
848
869
|
#
|
|
849
870
|
# Only mock: "all" needs every span's recorded output inline (it mocks
|
|
850
|
-
# every child), so it fetches an eager tree
|
|
851
|
-
#
|
|
852
|
-
#
|
|
853
|
-
#
|
|
871
|
+
# every matched recorded child), so it fetches an eager tree even when
|
|
872
|
+
# overrides exist.
|
|
873
|
+
# Non-"all" runs that need a tree ("marked", or "none" with overrides)
|
|
874
|
+
# fetch it payload-free (includeOutputs=false) and pull each mocked span's
|
|
875
|
+
# output lazily by externalSpanId, never dragging down unused outputs.
|
|
854
876
|
overrides_present = !mock_overrides.nil? && !mock_overrides.empty?
|
|
855
877
|
include_outputs = mock_strategy == "all"
|
|
856
878
|
mock_tree = nil
|
|
@@ -864,14 +886,10 @@ module Bitfab
|
|
|
864
886
|
mock_tree = build_mock_tree(tree["root"] || {})
|
|
865
887
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
866
888
|
raise if e.is_a?(SystemExit) || e.is_a?(SignalException)
|
|
867
|
-
|
|
868
|
-
#
|
|
869
|
-
#
|
|
870
|
-
|
|
871
|
-
# "marked" can fall back to real execution (its marked spans just
|
|
872
|
-
# re-run). Mirrors the Python SDK's has_overrides re-raise.
|
|
873
|
-
raise if mock_strategy == "all" || overrides_present
|
|
874
|
-
mock_tree = nil
|
|
889
|
+
raise unless mock_strategy == "marked" && !overrides_present
|
|
890
|
+
# Keep an active empty tree so the root can still run, while any
|
|
891
|
+
# marked child fails closed at its call site.
|
|
892
|
+
mock_tree = {}
|
|
875
893
|
end
|
|
876
894
|
end
|
|
877
895
|
|
data/lib/bitfab/span_context.rb
CHANGED
|
@@ -114,6 +114,7 @@ module Bitfab
|
|
|
114
114
|
# Each entry is a Hash with :trace_id and :span_id keys.
|
|
115
115
|
module SpanContext
|
|
116
116
|
STACK_KEY = :__bitfab_span_stack
|
|
117
|
+
FIBER_BRIDGE_STACK_KEY = :__bitfab_span_fiber_bridge_stack
|
|
117
118
|
|
|
118
119
|
module_function
|
|
119
120
|
|
|
@@ -122,7 +123,18 @@ module Bitfab
|
|
|
122
123
|
end
|
|
123
124
|
|
|
124
125
|
def current
|
|
125
|
-
stack.last
|
|
126
|
+
stack.last || fiber_bridge_stack.last
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Enumerator.new and enum_for run their source body in another Fiber on
|
|
130
|
+
# the same thread. Bridge only the active Enumerator parent across that
|
|
131
|
+
# boundary; ordinary fiber-local span stacks remain isolated.
|
|
132
|
+
def with_fiber_bridge(trace_id:, span_id:)
|
|
133
|
+
entry = {trace_id:, span_id:}
|
|
134
|
+
fiber_bridge_stack.push(entry)
|
|
135
|
+
yield
|
|
136
|
+
ensure
|
|
137
|
+
fiber_bridge_stack.pop
|
|
126
138
|
end
|
|
127
139
|
|
|
128
140
|
# Execute a block with a new span pushed onto the stack.
|
|
@@ -134,6 +146,15 @@ module Bitfab
|
|
|
134
146
|
ensure
|
|
135
147
|
stack.pop
|
|
136
148
|
end
|
|
149
|
+
|
|
150
|
+
def fiber_bridge_stack
|
|
151
|
+
current_thread = Thread.current
|
|
152
|
+
current_thread.thread_variable_get(FIBER_BRIDGE_STACK_KEY) || begin
|
|
153
|
+
value = []
|
|
154
|
+
current_thread.thread_variable_set(FIBER_BRIDGE_STACK_KEY, value)
|
|
155
|
+
value
|
|
156
|
+
end
|
|
157
|
+
end
|
|
137
158
|
end
|
|
138
159
|
|
|
139
160
|
# Global storage for trace states (trace_id -> state hash)
|
data/lib/bitfab/version.rb
CHANGED