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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/forge_ops_tracker/delivery_queue.rb +52 -1
- data/lib/forge_ops_tracker/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: 81c08199bfeae97d20295b7c04a46dc3b9dd9be519d4790404b88e2ca514fbe1
|
|
4
|
+
data.tar.gz: dad1efa10164cebcbce9452c1f903812157143e871162076f09298ebf41a6ea7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|