bitfab 0.51.4 → 0.51.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b739123699ec796d304175df24d0ff07ffa098b95c31d878370a07c7b9cb34c6
4
- data.tar.gz: 6895704bbaf7f045b704a2ac71a7502a5f5b205792abd80af1b9d4fa124879d7
3
+ metadata.gz: 208aed8da4ed9d2c748135161048c595f30507c946f3bb4086a560c55c1a2044
4
+ data.tar.gz: cfd965992d9304db89d53760c7e8ef8ea237394902aa33f5f0862a7388bf4c00
5
5
  SHA512:
6
- metadata.gz: c4b895369cabdcec196d957e92bc2cf13dfe972efa3342ffbb0bc705f7670050cf60a7f85c4132dd92b394c59c338acc41c7bbb5597cb1a8aa160f501a5d3895
7
- data.tar.gz: 810b845093e98709f56162422a9e87e1de46180e91224a0a60c67abc2f04f39ef1a3add5ac8f31e374c656920dc95c2aec79f6731c8160e0af98884791cf5450
6
+ metadata.gz: b76db7c1e008f877c79d6ab25eaacd2733f3e61564eb2211ac77418d050634503b31a4dd43ecd80bf7ea098fa4fec1975b36ec4c4b3125badbbc0a3ef61dfbba
7
+ data.tar.gz: '07281744cb252b83655686936532d9d58297537c35938c2eea468a5359005ba9c8ae97149e3465f990d005f5a80a2890d04662add468602e4ca9ddaaacd704e4'
data/lib/bitfab/client.rb CHANGED
@@ -470,6 +470,7 @@ module Bitfab
470
470
  span_contexts = nil
471
471
  span_prompt = nil
472
472
  completed = false
473
+ @http_client.trace_completion.start(trace_id, span_id)
473
474
 
474
475
  complete = lambda do |final_result, final_error|
475
476
  # Never crash the host app due to span building/sending. Idempotent:
@@ -549,6 +550,8 @@ module Bitfab
549
550
  # Silently ignore: user's result/exception takes priority
550
551
  # Catches Exception (not just StandardError) to handle SystemStackError
551
552
  # from deeply nested serialization
553
+ ensure
554
+ @http_client.trace_completion.finish(trace_id, span_id)
552
555
  end
553
556
  end
554
557
 
@@ -8,8 +8,9 @@ module Bitfab
8
8
 
9
9
  module_function
10
10
 
11
- def run(cwd, args, timeout:)
12
- Open3.popen3("git", *args, chdir: cwd, **spawn_options) do |stdin, stdout, stderr, wait_thread|
11
+ def run(cwd, args, timeout:, env: nil)
12
+ command = env ? [env, "git", *args] : ["git", *args]
13
+ Open3.popen3(*command, chdir: cwd, **spawn_options) do |stdin, stdout, stderr, wait_thread|
13
14
  stdin.close
14
15
  out = +""
15
16
  reader = Thread.new { out = read_all(stdout) }
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require_relative "git_command"
5
+
6
+ module Bitfab
7
+ module GitState
8
+ DISABLE_ENV = "BITFAB_DISABLE_GIT_STATE"
9
+ GIT_TIMEOUT_SECONDS = 30
10
+ SHA_PATTERN = /\A[0-9a-f]{40}\z/
11
+
12
+ module_function
13
+
14
+ def git(cwd, *args, env: nil)
15
+ GitCommand.run(cwd, args, timeout: GIT_TIMEOUT_SECONDS, env: env)&.strip
16
+ end
17
+
18
+ def text(value)
19
+ trimmed = value&.strip
20
+ (trimmed.nil? || trimmed.empty?) ? nil : trimmed
21
+ end
22
+
23
+ def sha(value)
24
+ trimmed = text(value)
25
+ (trimmed && SHA_PATTERN.match?(trimmed)) ? trimmed : nil
26
+ end
27
+
28
+ def empty?(state)
29
+ state.values.all?(&:nil?)
30
+ end
31
+
32
+ def resolve_working_tree_sha(root)
33
+ Dir.mktmpdir("bitfab-git-") do |dir|
34
+ env = {"GIT_INDEX_FILE" => File.join(dir, "index")}
35
+ return nil if git(root, "read-tree", "HEAD", env: env).nil?
36
+ return nil if git(root, "add", "-A", "--", root, env: env).nil?
37
+
38
+ sha(git(root, "write-tree", env: env))
39
+ end
40
+ rescue
41
+ nil
42
+ end
43
+
44
+ def resolve(cwd)
45
+ refs = git(cwd, "rev-parse", "--show-toplevel", "HEAD", "HEAD^{tree}")
46
+ lines = refs.to_s.split("\n")
47
+ root = text(lines[0])
48
+ return nil if root.nil?
49
+
50
+ state = {
51
+ "githubEmail" => text(git(cwd, "config", "user.email")),
52
+ "branch" => text(git(cwd, "symbolic-ref", "--short", "-q", "HEAD")),
53
+ "commitSha" => sha(lines[1]),
54
+ "baseSha" => sha(lines[2]),
55
+ "experimentSha" => resolve_working_tree_sha(root)
56
+ }
57
+ empty?(state) ? nil : state
58
+ end
59
+
60
+ def resolved
61
+ return nil unless ENV[DISABLE_ENV].to_s.empty?
62
+
63
+ safe_resolve
64
+ end
65
+
66
+ def safe_resolve
67
+ resolve(Dir.pwd)
68
+ rescue
69
+ nil
70
+ end
71
+ end
72
+ end
@@ -4,10 +4,12 @@ require "net/http"
4
4
  require "json"
5
5
  require "uri"
6
6
 
7
+ require_relative "git_state"
7
8
  require_relative "compress"
8
9
  require_relative "constants"
9
10
  require_relative "serialize"
10
11
  require_relative "transport"
12
+ require_relative "trace_completion"
11
13
  require_relative "version"
12
14
  require_relative "warn_once"
13
15
 
@@ -26,7 +28,7 @@ module Bitfab
26
28
  REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS = 300
27
29
  private_constant :REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS
28
30
 
29
- attr_reader :service_url
31
+ attr_reader :service_url, :trace_completion
30
32
 
31
33
  def initialize(api_key:, service_url: nil, timeout: 120)
32
34
  @api_key = api_key
@@ -37,6 +39,7 @@ module Bitfab
37
39
  # the export threads recording acks would hand each a different mutex.
38
40
  @delivery_mutex = Mutex.new
39
41
  @trace_deliveries = {}
42
+ @trace_completion = TraceCompletion.new
40
43
  @delivery_pid = Process.pid
41
44
  @carrier_seq_mutex = Mutex.new
42
45
  @carrier_seq = 0
@@ -125,6 +128,8 @@ module Bitfab
125
128
 
126
129
  # Queue an external span on this client's trace transport (fire-and-forget).
127
130
  def send_external_span(payload)
131
+ ref = carrier_ref(payload)
132
+ @trace_completion.record(ref.trace_id, ref.span_id) if ref&.span_id
128
133
  trace_transport&.submit(
129
134
  "external_span",
130
135
  payload.merge("sdkVersion" => VERSION),
@@ -207,6 +212,8 @@ module Bitfab
207
212
  end
208
213
  payload["graderIds"] = grader_ids unless grader_ids.nil?
209
214
  payload["dbBranchSettings"] = db_branch_settings unless db_branch_settings.nil?
215
+ git_state = GitState.resolved
216
+ payload["git"] = git_state if git_state
210
217
 
211
218
  # When DB branching is on, the server resolves a Neon preview branch per
212
219
  # item (snapshot + restore + poll), which can run several seconds each,
@@ -284,6 +291,18 @@ module Bitfab
284
291
 
285
292
  # Queue an external trace on this client's trace transport (fire-and-forget).
286
293
  def send_external_trace(payload)
294
+ ref = carrier_ref(payload)
295
+ if payload["completed"] == true && ref
296
+ @trace_completion.close(ref.trace_id, dropped: payload["dropped"] == true) do |count|
297
+ submit_external_trace(payload.merge("expectedSpanCount" => count))
298
+ end
299
+ else
300
+ @trace_completion.open(ref.trace_id) if ref
301
+ submit_external_trace(payload)
302
+ end
303
+ end
304
+
305
+ def submit_external_trace(payload)
287
306
  trace_transport&.submit(
288
307
  "external_trace",
289
308
  payload.merge("sdkVersion" => VERSION),
@@ -293,6 +312,8 @@ module Bitfab
293
312
  (payload["completed"] == true) ? carrier_ref(payload) : nil
294
313
  )
295
314
  )
315
+ rescue => error
316
+ Bitfab.warn_once("trace-completion-submit", "trace completion could not be queued: #{error.message}")
296
317
  end
297
318
 
298
319
  # Read the replay traces the server has fully persisted so far.
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set" # standard:disable Lint/RedundantRequireStatement -- Required on Ruby 3.4.
4
+
5
+ module Bitfab
6
+ class TraceCompletion
7
+ def initialize
8
+ @pid = Process.pid
9
+ @mutex = Mutex.new
10
+ @traces = {}
11
+ @closed = {}
12
+ end
13
+
14
+ def start(trace_id, span_id)
15
+ synchronize do
16
+ next if @closed.key?(trace_id)
17
+
18
+ entry = state(trace_id)
19
+ entry[:span_ids].add(span_id)
20
+ entry[:active].add(span_id)
21
+ end
22
+ end
23
+
24
+ def open(trace_id)
25
+ synchronize { state(trace_id) unless @closed.key?(trace_id) }
26
+ end
27
+
28
+ def record(trace_id, span_id)
29
+ synchronize do
30
+ state(trace_id)[:span_ids].add(span_id) unless @closed.key?(trace_id)
31
+ end
32
+ end
33
+
34
+ def finish(trace_id, span_id)
35
+ ready = synchronize do
36
+ entry = @traces[trace_id]
37
+ next if entry.nil?
38
+
39
+ entry[:active].delete(span_id)
40
+ drain(trace_id, entry)
41
+ end
42
+ ready[0].call(ready[1]) unless ready.nil?
43
+ end
44
+
45
+ def close(trace_id, dropped: false, &complete)
46
+ ready = synchronize do
47
+ next [complete, @closed[trace_id]] if @closed.key?(trace_id)
48
+
49
+ entry = @traces[trace_id]
50
+ next if entry.nil?
51
+
52
+ entry[:complete] = complete
53
+ entry[:active].clear if dropped
54
+ drain(trace_id, entry)
55
+ end
56
+ ready[0].call(ready[1]) unless ready.nil?
57
+ end
58
+
59
+ private
60
+
61
+ def synchronize(&block)
62
+ if @pid != Process.pid
63
+ @pid = Process.pid
64
+ @mutex = Mutex.new
65
+ @traces = {}
66
+ @closed = {}
67
+ end
68
+ @mutex.synchronize(&block)
69
+ end
70
+
71
+ def state(trace_id)
72
+ @traces[trace_id] ||= {span_ids: Set.new, active: Set.new}
73
+ end
74
+
75
+ def drain(trace_id, entry)
76
+ return unless entry[:active].empty? && entry[:complete]
77
+
78
+ @traces.delete(trace_id)
79
+ count = entry[:span_ids].size
80
+ @closed[trace_id] = count
81
+ @closed.shift if @closed.size > 1024
82
+ [entry[:complete], count]
83
+ end
84
+ end
85
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.51.4"
4
+ VERSION = "0.51.6"
5
5
  end
data/lib/bitfab.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "bitfab/serialize"
10
10
  require_relative "bitfab/db_snapshot"
11
11
  require_relative "bitfab/git_command"
12
12
  require_relative "bitfab/commit_ref"
13
+ require_relative "bitfab/git_state"
13
14
  require_relative "bitfab/span_context"
14
15
  require_relative "bitfab/http_client"
15
16
  require_relative "bitfab/datasets"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitfab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.51.4
4
+ version: 0.51.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team
@@ -146,6 +146,7 @@ files:
146
146
  - lib/bitfab/datasets.rb
147
147
  - lib/bitfab/db_snapshot.rb
148
148
  - lib/bitfab/git_command.rb
149
+ - lib/bitfab/git_state.rb
149
150
  - lib/bitfab/http_client.rb
150
151
  - lib/bitfab/mock_override.rb
151
152
  - lib/bitfab/otel.rb
@@ -158,6 +159,7 @@ files:
158
159
  - lib/bitfab/span_context.rb
159
160
  - lib/bitfab/subtree.rb
160
161
  - lib/bitfab/timestamp.rb
162
+ - lib/bitfab/trace_completion.rb
161
163
  - lib/bitfab/traceable.rb
162
164
  - lib/bitfab/transport.rb
163
165
  - lib/bitfab/version.rb