rspec-tracer 1.2.2 → 1.2.3

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: 0f6c19e285b64650bd4f02c5820fed75c10806ebb97900e17607b84532dfb483
4
- data.tar.gz: a4e17229c1efa3d43e1cc850b908cb9bbcb16fb6f217098348675ce2a798c993
3
+ metadata.gz: 1c0ed3fbdb4a082d56a4d83a528dff26bd174abeb28cf1669dc5dd79de521cee
4
+ data.tar.gz: ebc2fbdfde4c6ac82072b5464f5dc0441d73efd24b4410ed96d3892ea765f1a9
5
5
  SHA512:
6
- metadata.gz: 25571686f98b2c1939a0497f3c34a7f0508d856ba94ac2aabd6f293815a4e61ad8cb2b56d8a22c4ff211692a6609c1f8671d6b55f35d2123301d15bf8d70649b
7
- data.tar.gz: 1c86b20818bc6f18bc5a3faf69f6a7b7908b1ab6006a1301341883edb46f3d180db3cd6308978f9d12dbd6f9b427dcd478c3addc7888f78faa64dc1b325897ce
6
+ metadata.gz: 1c28a6a997cf74dbb8d910972d9dd8e0b6e9f36aaacc9670e8a50a6bf7c0d4100f6cf421befcba8349750c3ba179a05b86d2a0cbdf740280d490d4bd42c37512
7
+ data.tar.gz: ce834032d235ea1e2775fb127ac040d6f12f560e7cf9babe5fd208a68cb5dd7260a2ac1f2af78b2a782a622902d9b8291adb2ca339837de70b9f12259e05a456
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ## [1.2.3] - 2026-05-06
2
+
3
+ ### Fixed
4
+
5
+ - **Parallel-tests purge race when a sibling worker is still mid-flush**
6
+ — the elected worker trusted only `parallel_tests`'s pid-file barrier
7
+ (`ParallelTests.wait_for_other_processes_to_finish`), which under
8
+ specific scheduling/I/O timing on GHA Linux x86_64 can return while a
9
+ sibling's `parallel_tests_N/` dir hasn't fully flushed. The elected
10
+ then merged + purged, racing the in-progress sibling. Symptoms:
11
+ intermittent leftover `parallel_tests_N/` dir post-purge AND/OR
12
+ silently dropped peer caches in the merge.
13
+
14
+ Backport of upstream PR
15
+ [#168](https://github.com/avmnu-sng/rspec-tracer/pull/168). Adds a
16
+ filesystem barrier layered on top of the pid-file wait. Each worker
17
+ writes a `.rspec_tracer_boot` marker at `RSpecTracer.start` time and
18
+ a `.rspec_tracer_done` marker as the first step of its at_exit tasks;
19
+ the elected worker waits for every booted peer's `.done` to
20
+ materialize before proceeding to merge + purge. Two independent
21
+ signals (pid file + filesystem) must agree before the elected worker
22
+ declares the peer set stable. Bounded at 5 s with a graceful warn for
23
+ crashed peers — their dirs are purged regardless of completion state,
24
+ and the merge accepts whatever's on disk.
25
+
1
26
  ## [1.2.2] - 2026-05-04
2
27
 
3
28
  ### Fixed
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSpecTracer
4
- VERSION = '1.2.2'
4
+ VERSION = '1.2.3'
5
5
  end
data/lib/rspec_tracer.rb CHANGED
@@ -30,6 +30,17 @@ require_relative 'rspec_tracer/time_formatter'
30
30
  require_relative 'rspec_tracer/version'
31
31
 
32
32
  module RSpecTracer
33
+ # Filesystem barrier markers, layered on top of parallel_tests's
34
+ # pid-file wait to defend against the GHA-observed race where the
35
+ # gem's `wait_for_other_processes_to_finish` returns while a sibling
36
+ # worker hasn't fully flushed its `parallel_tests_N/` dir yet. Each
37
+ # worker writes BOOT at setup-time and DONE as the first step of its
38
+ # at_exit tasks; the elected worker waits for every booted peer's
39
+ # DONE marker (deadline-bounded) before proceeding to merge + purge.
40
+ PARALLEL_TESTS_BOOT_MARKER_FILENAME = '.rspec_tracer_boot'
41
+ PARALLEL_TESTS_DONE_MARKER_FILENAME = '.rspec_tracer_done'
42
+ PARALLEL_TESTS_PEER_DONE_DEADLINE_SECONDS = 5
43
+
33
44
  class << self
34
45
  attr_accessor :running, :pid, :no_examples, :duplicate_examples
35
46
 
@@ -186,6 +197,29 @@ module RSpecTracer
186
197
  RSpecTracer.logger.error "Failed to load parallel tests (Error: #{e.message})"
187
198
  ensure
188
199
  track_parallel_tests_test_env_number
200
+ parallel_tests_touch_boot!
201
+ end
202
+
203
+ # Per-worker boot marker. Source-of-truth for "this worker booted
204
+ # past `RSpecTracer.start`", consumed by the elected worker's
205
+ # finalize-time peer enumeration. Idempotent; failures are warned
206
+ # and absorbed (boot-marker write must never block test execution).
207
+ def parallel_tests_touch_boot!
208
+ return unless parallel_tests?
209
+
210
+ FileUtils.mkdir_p(RSpecTracer.cache_path)
211
+ File.write(
212
+ File.join(RSpecTracer.cache_path, PARALLEL_TESTS_BOOT_MARKER_FILENAME),
213
+ JSON.generate(
214
+ pid: Process.pid,
215
+ test_env_number: ENV.fetch('TEST_ENV_NUMBER', ''),
216
+ started_at: Time.now.utc.iso8601
217
+ )
218
+ )
219
+ rescue StandardError => e
220
+ RSpecTracer.logger.warn(
221
+ "RSpec tracer: failed to write boot marker (#{e.class}: #{e.message})"
222
+ )
189
223
  end
190
224
 
191
225
  def track_parallel_tests_test_env_number
@@ -315,6 +349,15 @@ module RSpecTracer
315
349
  end
316
350
 
317
351
  def run_parallel_tests_exit_tasks
352
+ # Every worker — elected or not — drops its `.done` marker as the
353
+ # first thing in finalize so the elected worker's
354
+ # `parallel_tests_wait_for_peer_done_markers!` can observe it.
355
+ # Non-elected workers stop here; the elected worker proceeds to
356
+ # the merge + purge sequence (gated by `parallel_tests_executed?`,
357
+ # which now layers the peer-done barrier on top of the existing
358
+ # pid-file wait).
359
+ parallel_tests_touch_done!
360
+
318
361
  return unless parallel_tests_executed?
319
362
 
320
363
  merge_parallel_tests_reports
@@ -324,6 +367,26 @@ module RSpecTracer
324
367
  purge_parallel_tests_reports
325
368
  end
326
369
 
370
+ # Per-worker done marker. Written by every worker (elected or not)
371
+ # as the first step of `run_parallel_tests_exit_tasks`. Pairs with
372
+ # the boot marker for the elected worker's peer-done barrier:
373
+ # presence of `.done` means "this worker has signalled completion
374
+ # of its own writes"; absence (with `.boot` present) means "still
375
+ # mid-flush or crashed". Idempotent; failures are warned + absorbed.
376
+ def parallel_tests_touch_done!
377
+ return unless parallel_tests?
378
+
379
+ FileUtils.mkdir_p(RSpecTracer.cache_path)
380
+ File.write(
381
+ File.join(RSpecTracer.cache_path, PARALLEL_TESTS_DONE_MARKER_FILENAME),
382
+ Time.now.utc.iso8601
383
+ )
384
+ rescue StandardError => e
385
+ RSpecTracer.logger.warn(
386
+ "RSpec tracer: failed to write done marker (#{e.class}: #{e.message})"
387
+ )
388
+ end
389
+
327
390
  def merge_parallel_tests_reports
328
391
  return unless parallel_tests_executed?
329
392
 
@@ -423,9 +486,64 @@ module RSpecTracer
423
486
 
424
487
  ParallelTests.wait_for_other_processes_to_finish
425
488
 
489
+ # Belt-and-suspenders barrier: pid-file said everyone's done, but
490
+ # the gem's `wait_for_other_processes_to_finish` has been observed
491
+ # on GHA Linux x86_64 to return while a sibling's `parallel_tests_N/`
492
+ # is still mid-flush. Cross-check via the `.boot`/`.done` filesystem
493
+ # markers before declaring the peer set stable. Idempotent: once
494
+ # all peers have flushed, subsequent calls just glob, find nothing
495
+ # missing, and return.
496
+ parallel_tests_wait_for_peer_done_markers!
497
+
426
498
  true
427
499
  end
428
500
 
501
+ # Block until every peer that wrote `.boot` has also written `.done`,
502
+ # or the deadline elapses. Polled at 50ms — fine enough to close the
503
+ # typical "barrier returned a tick early" case within a poll or two,
504
+ # coarse enough not to dominate CPU.
505
+ #
506
+ # On timeout we log a warn and proceed: a peer that never wrote
507
+ # `.done` either crashed (then its dir is orphan content; the
508
+ # subsequent purge cleans it) or is genuinely hung (the elected
509
+ # can't fix that — we choose merge correctness over indefinite wait).
510
+ def parallel_tests_wait_for_peer_done_markers!
511
+ base_dir = File.dirname(RSpecTracer.cache_path)
512
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + PARALLEL_TESTS_PEER_DONE_DEADLINE_SECONDS
513
+
514
+ loop do
515
+ missing = parallel_tests_peer_dirs_missing_done(base_dir)
516
+ return if missing.empty?
517
+
518
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
519
+ RSpecTracer.logger.warn(
520
+ 'RSpec tracer: peers booted without finishing within ' \
521
+ "#{PARALLEL_TESTS_PEER_DONE_DEADLINE_SECONDS}s: #{missing.inspect}; " \
522
+ 'proceeding (peer dirs will be purged regardless of completion state)'
523
+ )
524
+ return
525
+ end
526
+
527
+ sleep 0.05
528
+ end
529
+ end
530
+
531
+ # Set difference of `.boot`-bearing peer dirs and `.done`-bearing
532
+ # peer dirs under `base_dir`. A returned entry means "this peer
533
+ # registered but has not signalled completion yet" — either still
534
+ # mid-flush or crashed.
535
+ def parallel_tests_peer_dirs_missing_done(base_dir)
536
+ boot_dirs = parallel_tests_peer_dirs_with_marker(base_dir, PARALLEL_TESTS_BOOT_MARKER_FILENAME)
537
+ done_dirs = parallel_tests_peer_dirs_with_marker(base_dir, PARALLEL_TESTS_DONE_MARKER_FILENAME)
538
+ boot_dirs - done_dirs
539
+ end
540
+
541
+ def parallel_tests_peer_dirs_with_marker(base_dir, marker_filename)
542
+ Dir.glob(File.join(base_dir, 'parallel_tests_*', marker_filename)).map do |path|
543
+ File.dirname(path)
544
+ end
545
+ end
546
+
429
547
  # Elects the worker that performs the per-run merge. Delegates to
430
548
  # `::ParallelTests.first_process?`, which returns true iff
431
549
  # `TEST_ENV_NUMBER.to_i <= 1` — i.e. for exactly one worker
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abhimanyu Singh
@@ -111,7 +111,7 @@ licenses:
111
111
  - MIT
112
112
  metadata:
113
113
  homepage_uri: https://github.com/avmnu-sng/rspec-tracer
114
- source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.2.2
114
+ source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.2.3
115
115
  changelog_uri: https://github.com/avmnu-sng/rspec-tracer/blob/main/CHANGELOG.md
116
116
  bug_tracker_uri: https://github.com/avmnu-sng/rspec-tracer/issues
117
117
  rdoc_options: []