forge_ops_tracker 0.15.0 → 0.15.1

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: 2c05c6f94c7c080f7c148cc315097b8e177d34dec772d580e21cf89a7120f1b3
4
- data.tar.gz: d486796b0f26b752872118cfaad2c9c184671bc596bd6c32a04cfb6a754dc171
3
+ metadata.gz: 81c08199bfeae97d20295b7c04a46dc3b9dd9be519d4790404b88e2ca514fbe1
4
+ data.tar.gz: dad1efa10164cebcbce9452c1f903812157143e871162076f09298ebf41a6ea7
5
5
  SHA512:
6
- metadata.gz: e99dddf991705420ceb4d69f21ad8d5e39885f5f2f782e0747976aa2c8cb2463a4d8f56652f23f6944c40d38bc89e95475e61ecaf2079b0f106ca401d4e25d06
7
- data.tar.gz: 1e5e0e69a95ddecb9411a024dcbf1817b688312131bbffc7359cb917f86d416e7d2811b1215feeb355b4ff4df3b1a2757bafe747ddc425e057e3248bb3572287
6
+ metadata.gz: 2a32b34300ee005cd09be7f188275091ec9ba8cf4ef2e65044176a7e6d1427feef170cbc44dbd9f7a25dcf0073b2afb6186db3db2069a5737bfdcb274e62cc7f
7
+ data.tar.gz: 4431641c5e95f757dd82e7af0015de824804610ee4ebf252cbe3a4db13ca5f5aa1cac5fda274a94833be652a9d6c5e25ab4406570d4790b165cf242f8490ff66
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.1 (2026-10-01)
4
+
5
+ - Errors reported just before a process exits are now sent before it ends. Delivery runs on a
6
+ background thread, and a short-lived process (a rake task, a cron script, `rails runner`,
7
+ including the test command on a project's setup page) could exit before that thread sent them,
8
+ so they never arrived. On a normal exit the queue now sends what's left, waiting at most 5
9
+ seconds, and only in a process that reported something.
10
+
3
11
  ## 0.15.0 (2026-09-29)
4
12
 
5
13
  - SQL masking now also catches strings with a backslash-escaped quote (`'o\'brien'`,
@@ -8,6 +8,11 @@ module ForgeOpsTracker
8
8
  # in each forked Puma/Passenger worker process rather than being carried
9
9
  # across a fork, which would leave it dead in the child.
10
10
  class DeliveryQueue
11
+ # The most a normal process exit waits for queued events to go out (see #drain): long enough for
12
+ # one delivery at the default open_timeout + read_timeout, short enough that an unreachable
13
+ # ForgeOps never holds up a deploy or a cron run for long.
14
+ DRAIN_TIMEOUT = 5
15
+
11
16
  # deliver_with names the Client method each payload goes through: :deliver (error events) by
12
17
  # default, :deliver_change for ForgeOpsTracker.record_change's own queue.
13
18
  def initialize(configuration, client: Client.new(configuration), deliver_with: :deliver)
@@ -17,6 +22,28 @@ module ForgeOpsTracker
17
22
  @queue = SizedQueue.new(configuration.queue_size)
18
23
  @mutex = Mutex.new
19
24
  @thread = nil
25
+ @in_flight = 0
26
+ @drain_registered = false
27
+ end
28
+
29
+ # Sends whatever is still queued, and lets a delivery already under way finish, before the
30
+ # process exits, within DRAIN_TIMEOUT. Without it a short-lived process (a rake task, a cron
31
+ # script, `rails runner`) lost every error it reported in its last moments: the worker thread
32
+ # dies with the process before it gets to them. Delivers here on the exiting thread rather than
33
+ # waiting on the worker, which in a forked child is a parent's thread that no longer runs.
34
+ def drain(timeout: DRAIN_TIMEOUT)
35
+ deadline = monotonic_now + timeout
36
+ while monotonic_now < deadline
37
+ payload = begin
38
+ @queue.pop(true)
39
+ rescue ThreadError
40
+ break
41
+ end
42
+ client.public_send(@deliver_with, payload)
43
+ end
44
+ sleep 0.01 while @in_flight.positive? && monotonic_now < deadline
45
+ rescue StandardError => e
46
+ log { "drain failed: #{e.class}: #{e.message}" }
20
47
  end
21
48
 
22
49
  # Enqueues a payload, dropping it silently (never blocking the caller)
@@ -35,6 +62,7 @@ module ForgeOpsTracker
35
62
  attr_reader :configuration, :client
36
63
 
37
64
  def ensure_worker_started
65
+ register_drain
38
66
  return if @thread&.alive?
39
67
 
40
68
  @mutex.synchronize do
@@ -47,12 +75,35 @@ module ForgeOpsTracker
47
75
 
48
76
  def run
49
77
  loop do
50
- client.public_send(@deliver_with, @queue.pop)
78
+ payload = @queue.pop
79
+ @in_flight += 1
80
+ begin
81
+ client.public_send(@deliver_with, payload)
82
+ ensure
83
+ @in_flight -= 1
84
+ end
51
85
  rescue StandardError => e
52
86
  log { "delivery thread error: #{e.class}: #{e.message}" }
53
87
  end
54
88
  end
55
89
 
90
+ # Registered on the first push, not at load, so a process that never reports anything never
91
+ # waits on exit at all.
92
+ def register_drain
93
+ return if @drain_registered
94
+
95
+ @mutex.synchronize do
96
+ return if @drain_registered
97
+
98
+ @drain_registered = true
99
+ at_exit { drain }
100
+ end
101
+ end
102
+
103
+ def monotonic_now
104
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
105
+ end
106
+
56
107
  def log
57
108
  configuration.logger&.debug { "[ForgeOpsTracker] #{yield}" }
58
109
  end
@@ -1,3 +1,3 @@
1
1
  module ForgeOpsTracker
2
- VERSION = "0.15.0"
2
+ VERSION = "0.15.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forge_ops_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ForgeOps