cosmonats 0.8.0 → 0.9.0
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 +21 -11
- data/lib/cosmo/api/counter.rb +1 -1
- data/lib/cosmo/api/cron/entry.rb +10 -11
- data/lib/cosmo/api/cron.rb +3 -3
- data/lib/cosmo/api/job.rb +1 -1
- data/lib/cosmo/api/kv.rb +3 -3
- data/lib/cosmo/api/stats/busy.rb +5 -0
- data/lib/cosmo/api/stats/metrics.rb +3 -3
- data/lib/cosmo/api/stats.rb +15 -0
- data/lib/cosmo/api/stream.rb +7 -2
- data/lib/cosmo/client.rb +10 -13
- data/lib/cosmo/job/data.rb +1 -1
- data/lib/cosmo/job/processor.rb +5 -6
- data/lib/cosmo/utils.rb +0 -1
- data/lib/cosmo/version.rb +1 -1
- data/lib/cosmo/web/assets/app.css +271 -629
- data/lib/cosmo/web/assets/app.js +158 -0
- data/lib/cosmo/web/assets/fonts/OFL.txt +92 -0
- data/lib/cosmo/web/assets/fonts/geist-mono.woff2 +0 -0
- data/lib/cosmo/web/assets/fonts/geist.woff2 +0 -0
- data/lib/cosmo/web/assets/logo.svg +10 -0
- data/lib/cosmo/web/cache.rb +75 -0
- data/lib/cosmo/web/chart.rb +62 -38
- data/lib/cosmo/web/circuit.rb +55 -0
- data/lib/cosmo/web/context.rb +0 -4
- data/lib/cosmo/web/controllers/application.rb +16 -0
- data/lib/cosmo/web/controllers/batches.rb +1 -0
- data/lib/cosmo/web/controllers/crons.rb +9 -2
- data/lib/cosmo/web/controllers/jobs.rb +50 -59
- data/lib/cosmo/web/controllers/metrics.rb +1 -0
- data/lib/cosmo/web/controllers/overview.rb +70 -0
- data/lib/cosmo/web/controllers/processes.rb +33 -0
- data/lib/cosmo/web/controllers/streams.rb +26 -28
- data/lib/cosmo/web/helpers/application.rb +57 -0
- data/lib/cosmo/web/renderer.rb +19 -6
- data/lib/cosmo/web/throughput.rb +104 -0
- data/lib/cosmo/web/views/batches/_table.erb +42 -54
- data/lib/cosmo/web/views/batches/index.erb +3 -10
- data/lib/cosmo/web/views/crons/_table.erb +32 -53
- data/lib/cosmo/web/views/crons/index.erb +2 -9
- data/lib/cosmo/web/views/jobs/_args.erb +5 -0
- data/lib/cosmo/web/views/jobs/_busy.erb +25 -121
- data/lib/cosmo/web/views/jobs/_dead.erb +42 -82
- data/lib/cosmo/web/views/jobs/_enqueued.erb +33 -113
- data/lib/cosmo/web/views/jobs/_panel.erb +29 -0
- data/lib/cosmo/web/views/jobs/_queue_row.erb +18 -0
- data/lib/cosmo/web/views/jobs/_queues.erb +20 -0
- data/lib/cosmo/web/views/jobs/_scheduled.erb +20 -54
- data/lib/cosmo/web/views/jobs/index.erb +4 -17
- data/lib/cosmo/web/views/layout.erb +73 -22
- data/lib/cosmo/web/views/metrics/_chart.erb +37 -32
- data/lib/cosmo/web/views/metrics/_panel.erb +3 -3
- data/lib/cosmo/web/views/metrics/_table.erb +24 -40
- data/lib/cosmo/web/views/metrics/index.erb +8 -16
- data/lib/cosmo/web/views/overview/_badges.erb +3 -0
- data/lib/cosmo/web/views/overview/_kpis.erb +19 -0
- data/lib/cosmo/web/views/overview/_running.erb +18 -0
- data/lib/cosmo/web/views/overview/_throughput.erb +50 -0
- data/lib/cosmo/web/views/overview/_workers.erb +25 -0
- data/lib/cosmo/web/views/overview/index.erb +28 -0
- data/lib/cosmo/web/views/processes/_table.erb +67 -0
- data/lib/cosmo/web/views/processes/index.erb +1 -0
- data/lib/cosmo/web/views/shared/_pager.erb +22 -0
- data/lib/cosmo/web/views/shared/unavailable.erb +7 -0
- data/lib/cosmo/web/views/streams/_info.erb +28 -86
- data/lib/cosmo/web/views/streams/_list.erb +7 -0
- data/lib/cosmo/web/views/streams/_pause_banner.erb +8 -16
- data/lib/cosmo/web/views/streams/_stream.erb +52 -0
- data/lib/cosmo/web/views/streams/index.erb +3 -11
- data/lib/cosmo/web/views/streams/info.erb +3 -11
- data/lib/cosmo/web.rb +66 -35
- data/sig/cosmo/api/cron/entry.rbs +2 -1
- data/sig/cosmo/api/stats/busy.rbs +2 -0
- data/sig/cosmo/api/stats/metrics.rbs +1 -1
- data/sig/cosmo/api/stats.rbs +4 -0
- data/sig/cosmo/api/stream.rbs +2 -0
- data/sig/cosmo/client.rbs +1 -0
- metadata +31 -18
- data/lib/cosmo/utils/overrides.rb +0 -206
- data/lib/cosmo/utils/warnings.rb +0 -17
- data/lib/cosmo/web/controllers/actions.rb +0 -16
- data/lib/cosmo/web/views/actions/index.erb +0 -7
- data/lib/cosmo/web/views/jobs/_processes.erb +0 -145
- data/lib/cosmo/web/views/jobs/_stats.erb +0 -77
- data/lib/cosmo/web/views/jobs/_tabs.erb +0 -14
- data/lib/cosmo/web/views/jobs/busy.erb +0 -18
- data/lib/cosmo/web/views/jobs/dead.erb +0 -18
- data/lib/cosmo/web/views/jobs/enqueued.erb +0 -18
- data/lib/cosmo/web/views/jobs/scheduled.erb +0 -18
- data/lib/cosmo/web/views/streams/_stream_row.erb +0 -42
- data/lib/cosmo/web/views/streams/_table.erb +0 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40e30ee2fff77d85db1a3deedf05e040c72dca39d64da01220517d3d5ae8048b
|
|
4
|
+
data.tar.gz: 6f0e5a50448674483f09f54938336940899ea81fe46ce66da23478eeee871bac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d57b79ee594fd7a7ace94e1e3f5d70de87b9a413b85389cddb9db51f39ed09542bd0f0a7d3c4308e7009b7a2d9997b9be0c2965efa61e259bd07baf9bfa4734
|
|
7
|
+
data.tar.gz: 8122f92e84e4176a403678225ab66b753270ab5e49334568bc3e88cfa292451bc63ea6f5383bdb458ab89f8b319a6ed33b7af471f636ffe7314978c956b6acb1
|
data/README.md
CHANGED
|
@@ -58,8 +58,6 @@ bundle exec cosmo -C config/cosmo.yml -c 20 jobs # Jobs only
|
|
|
58
58
|
bundle exec cosmo -C config/cosmo.yml -c 20 streams # Streams only
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-

|
|
62
|
-
|
|
63
61
|
## 📖 Index
|
|
64
62
|
|
|
65
63
|
- [Why?](#-why)
|
|
@@ -433,9 +431,11 @@ Cosmo leaves a custom logger's formatter alone, so its lines lack Cosmo's `jid`/
|
|
|
433
431
|
| `COSMO_CLIENT_NAME` | `cosmo-<program>-<host>-<pid>` | NATS connection name, shown in `nats server report connections` and `/connz` |
|
|
434
432
|
| `COSMO_JS_TIMEOUT` | `5` | JetStream API timeout in seconds; raise it for the web UI on a busy server |
|
|
435
433
|
| `COSMO_CONNECT_TIMEOUT` | `2` | NATS TCP connect timeout in seconds |
|
|
434
|
+
| `NATS_MAX_RECONNECT_ATTEMPTS` | `-1` | Reconnect attempts before giving up on NATS; `-1` keeps trying through any outage |
|
|
436
435
|
| `COSMO_JOBS_STREAMS` | all configured | Comma-separated job streams to subscribe to, when `--streams`/`--stream` isn't given |
|
|
437
436
|
| `COSMO_JOBS_FETCH_TIMEOUT` | `0.1` | Seconds a jobs worker waits on each fetch |
|
|
438
437
|
| `COSMO_WEB_POLL_INTERVAL` | `5` | Web UI auto-refresh interval in seconds |
|
|
438
|
+
| `COSMO_WEB_CACHE_TTL` | `2` | Seconds the web UI shares a refresh between viewers; `0` turns sharing off |
|
|
439
439
|
|
|
440
440
|
|
|
441
441
|
## 🔧 Advanced Usage
|
|
@@ -543,7 +543,7 @@ end
|
|
|
543
543
|
```
|
|
544
544
|
- Batch tracking data (pending counts, callbacks, results) expires automatically after
|
|
545
545
|
`config.batches.expiry` (default: 3 days), set in `Cosmo.configure`.
|
|
546
|
-
- Open and finished batches — with pending/succeeded/failed counts — are listed live
|
|
546
|
+
- Open and finished batches — with pending/succeeded/failed counts — are listed live on the web UI's **Batches** page.
|
|
547
547
|
- Only `Cosmo::Job`-based jobs are tracked; the ActiveJob adapter doesn't currently participate in
|
|
548
548
|
batches (see [`docs/active_job.md`](docs/active_job.md)).
|
|
549
549
|
|
|
@@ -896,19 +896,29 @@ sudo systemctl enable cosmo && sudo systemctl start cosmo
|
|
|
896
896
|
|
|
897
897
|
## 📊 Monitoring
|
|
898
898
|
|
|
899
|
-
**Web UI** — mount `Cosmo::Web` (see [Installation](#-installation)) for a live, htmx-powered dashboard
|
|
900
|
-
|
|
901
|
-
- **
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
- **
|
|
899
|
+
**Web UI** — mount `Cosmo::Web` (see [Installation](#-installation)) for a live, htmx-powered dashboard with a
|
|
900
|
+
light, dark, or automatic (system) theme. Fonts and assets are bundled, so it loads nothing from a CDN:
|
|
901
|
+
- **Overview** — processed, failed, busy, enqueued, retries, scheduled and dead totals; a 7- or 30-day throughput
|
|
902
|
+
chart of successful and failed runs; a grid of every worker thread, busy ones colored by stream; the queues; and the
|
|
903
|
+
jobs running right now, longest first
|
|
904
|
+
- **Jobs** — each job queue with its size, busy jobs and redeliveries, paused and resumed in place; then busy,
|
|
905
|
+
enqueued (per queue), scheduled and dead jobs, with retry and delete for dead ones and their error and backtrace
|
|
906
|
+
- **Processes** — every live worker (idle ones included): host, pid, IP, NATS connection name, command line,
|
|
907
|
+
subscribed streams, busy/concurrency, running/quiet/stopping state, memory, NATS RTT and reconnects. Each worker
|
|
908
|
+
beats into the `_cosmoprocesses` KV bucket every 10s, and a crashed worker drops out within 60s
|
|
909
|
+
- **Streams** — every stream with its subjects, messages, size, retention and replicas, its consumers' pending,
|
|
910
|
+
ack-pending and redelivered counts, and pause/resume
|
|
905
911
|
- **Crons** — every schedule deployed in NATS, with run-now and delete
|
|
906
|
-
- **Batches** — open and finished batches with
|
|
912
|
+
- **Batches** — open and finished batches with their progress, succeeded/failed/pending counts and callbacks
|
|
907
913
|
- **Metrics** — per job class over today, 7, or 30 days: runs, failures, average execution time (successful runs), and
|
|
908
914
|
average wait from enqueue to first delivery, plus a per-day chart of runs and average execution time. Recorded by
|
|
909
915
|
`Cosmo::Middleware::Metrics`, flushed by every worker's heartbeat into the internal `_cosmometrics` counters stream;
|
|
910
916
|
turn it off with `config.metrics.enabled = false`
|
|
911
|
-
|
|
917
|
+
|
|
918
|
+
Each panel refreshes `COSMO_WEB_POLL_INTERVAL` seconds after its previous response, never on top of one still
|
|
919
|
+
running, backs off while refreshes fail, and pauses in a hidden tab. Identical refreshes from several viewers share a
|
|
920
|
+
single NATS read for `COSMO_WEB_CACHE_TTL` seconds, and while NATS is unreachable the UI answers at once instead of
|
|
921
|
+
holding your app server's threads.
|
|
912
922
|
|
|
913
923
|
**Structured logs:**
|
|
914
924
|
```
|
data/lib/cosmo/api/counter.rb
CHANGED
|
@@ -58,7 +58,7 @@ module Cosmo
|
|
|
58
58
|
def publish(key, value, msg_id: nil)
|
|
59
59
|
rescued = nil
|
|
60
60
|
headers = { "Nats-Incr" => value }
|
|
61
|
-
headers[
|
|
61
|
+
headers[NATS::JetStream::Header::MSG_ID] = msg_id if msg_id
|
|
62
62
|
|
|
63
63
|
begin
|
|
64
64
|
ack = client.publish(subject(key), "", header: headers)
|
data/lib/cosmo/api/cron/entry.rb
CHANGED
|
@@ -66,17 +66,16 @@ module Cosmo
|
|
|
66
66
|
"jobs.#{@stream}.#{job_name}"
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
headers
|
|
69
|
+
# The +schedule:+ option of nats-pure's JetStream#publish that makes the template a schedule.
|
|
70
|
+
# @-shortcuts (+@daily+, +@every 5m+) travel as the cron expression, which the server understands.
|
|
71
|
+
def schedule
|
|
72
|
+
{ cron: @expression, target: target_subject, time_zone: @timezone }.compact
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Headers stored on the schedule template. NATS copies them onto every message it fires,
|
|
76
|
+
# which is how the scheduler learns where the job belongs.
|
|
77
|
+
def headers
|
|
78
|
+
{ "X-Stream" => @stream, "X-Subject" => dispatch_subject }
|
|
80
79
|
end
|
|
81
80
|
|
|
82
81
|
def job_name
|
data/lib/cosmo/api/cron.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Cosmo
|
|
|
38
38
|
|
|
39
39
|
e = Entry.new(class_name: class_name, stream: stream, expression: schedule,
|
|
40
40
|
args: args, timezone: timezone, name: name)
|
|
41
|
-
client.publish(e.schedule_subject, e.job_payload, stream: STREAM, header: e.
|
|
41
|
+
client.publish(e.schedule_subject, e.job_payload, stream: STREAM, header: e.headers, schedule: e.schedule)
|
|
42
42
|
build_from_nats(e.schedule_subject)
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -94,8 +94,8 @@ module Cosmo
|
|
|
94
94
|
{
|
|
95
95
|
class: body[:class],
|
|
96
96
|
stream: headers["X-Stream"],
|
|
97
|
-
schedule: headers[
|
|
98
|
-
timezone: headers[
|
|
97
|
+
schedule: headers[NATS::JetStream::Header::SCHEDULE],
|
|
98
|
+
timezone: headers[NATS::JetStream::Header::SCHEDULE_TIME_ZONE],
|
|
99
99
|
args: body[:args] || [],
|
|
100
100
|
name: name_from_subject(subject),
|
|
101
101
|
schedule_subject: subject,
|
data/lib/cosmo/api/job.rb
CHANGED
data/lib/cosmo/api/kv.rb
CHANGED
|
@@ -98,14 +98,14 @@ module Cosmo
|
|
|
98
98
|
results
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
# CAS = Compare-And-Swap: publish +value+ with a per-message
|
|
101
|
+
# CAS = Compare-And-Swap: publish +value+ with a per-message TTL of +ttl+ whole seconds (at least 1),
|
|
102
102
|
# but only if the subject's current last sequence matches +last_seq+
|
|
103
103
|
# (sent as the Nats-Expected-Last-Subject-Sequence header). Raises
|
|
104
104
|
# NATS::KeyValue::KeyWrongLastSequenceError if it doesn't match --
|
|
105
105
|
# e.g. last_seq: 0 means "only publish if nothing exists here yet".
|
|
106
106
|
def publish_cas(key, value, ttl, last_seq:)
|
|
107
|
-
headers = {
|
|
108
|
-
client.js.publish("$KV.#{@name}.#{key}", value, header: headers)
|
|
107
|
+
headers = { NATS::JetStream::Header::EXPECTED_LAST_SUBJECT_SEQUENCE => last_seq.to_s }
|
|
108
|
+
client.js.publish("$KV.#{@name}.#{key}", value, header: headers, ttl: [ttl.to_i, 1].max)
|
|
109
109
|
rescue NATS::JetStream::Error::APIError => e
|
|
110
110
|
raise NATS::KeyValue::KeyWrongLastSequenceError, e.description if e.err_code == 10_071
|
|
111
111
|
|
data/lib/cosmo/api/stats/busy.rb
CHANGED
|
@@ -43,6 +43,11 @@ module Cosmo
|
|
|
43
43
|
super.map { _1.merge(data: Utils::Json.parse(_1[:data])) }
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# @return [Hash{String => Integer}] the number of jobs running per stream, for the streams running any
|
|
47
|
+
def by_stream
|
|
48
|
+
all.group_by { _1[:stream] }.transform_values(&:size)
|
|
49
|
+
end
|
|
50
|
+
|
|
46
51
|
private
|
|
47
52
|
|
|
48
53
|
def heartbeat_loop
|
|
@@ -67,12 +67,12 @@ module Cosmo
|
|
|
67
67
|
totals.map { |job, sums| row(sums).merge(job: job) }.sort_by { -_1[:count] }
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# @param job_class [String]
|
|
70
|
+
# @param job_class [String, nil] nil sums every job class
|
|
71
71
|
# @param days [Integer]
|
|
72
72
|
# @return [Array<Hash>] one +{ date:, count:, failed:, exec_ms:, wait_ms: }+ per day, oldest first
|
|
73
|
-
def daily(job_class, days: 30)
|
|
73
|
+
def daily(job_class = nil, days: 30) # rubocop:disable Metrics/AbcSize
|
|
74
74
|
sums = Hash.new { |hash, day| hash[day] = Hash.new(0) }
|
|
75
|
-
read(days).each { |(day, job, field), value| sums[day][field] += value if job == job_class }
|
|
75
|
+
read(days).each { |(day, job, field), value| sums[day][field] += value if job_class.nil? || job == job_class }
|
|
76
76
|
dates(days).map { |day| row(sums[day]).merge(date: day) }
|
|
77
77
|
end
|
|
78
78
|
|
data/lib/cosmo/api/stats.rb
CHANGED
|
@@ -41,6 +41,21 @@ module Cosmo
|
|
|
41
41
|
def dead
|
|
42
42
|
Config.dead.enabled ? Stream.new("dead").size : 0
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
# @return [Array<Hash>] a {.queue} row per job stream
|
|
46
|
+
def queues
|
|
47
|
+
busy = Busy.instance.by_stream
|
|
48
|
+
Stream.jobs.map { queue(_1, busy) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param stream [Stream] a job stream
|
|
52
|
+
# @param busy [Hash{String => Integer}] running jobs per stream, as {Busy#by_stream} counts them
|
|
53
|
+
# @return [Hash] the stream's +:name+, waiting messages as +:size+, running jobs as +:busy+, redeliveries as
|
|
54
|
+
# +:retries+, and whether it is +:paused+
|
|
55
|
+
def queue(stream, busy = Busy.instance.by_stream)
|
|
56
|
+
{ name: stream.name, size: stream.size, busy: busy.fetch(stream.name, 0), retries: stream.retries,
|
|
57
|
+
paused: stream.paused? }
|
|
58
|
+
end
|
|
44
59
|
end
|
|
45
60
|
end
|
|
46
61
|
end
|
data/lib/cosmo/api/stream.rb
CHANGED
|
@@ -44,9 +44,14 @@ module Cosmo
|
|
|
44
44
|
alias size total
|
|
45
45
|
|
|
46
46
|
def retries
|
|
47
|
-
|
|
47
|
+
consumers.sum { _1["num_redelivered"].to_i }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Array<Hash>] the stream's consumers as JetStream reports them, with their config and delivery state
|
|
51
|
+
def consumers
|
|
52
|
+
client.list_consumers(name)
|
|
48
53
|
rescue NATS::Error
|
|
49
|
-
|
|
54
|
+
[]
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
def each
|
data/lib/cosmo/client.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "socket"
|
|
4
4
|
require "nats/client"
|
|
5
|
-
require "cosmo/utils/overrides"
|
|
6
5
|
|
|
7
6
|
module Cosmo
|
|
8
7
|
class Client
|
|
@@ -10,6 +9,9 @@ module Cosmo
|
|
|
10
9
|
JS_TIMEOUT = 5
|
|
11
10
|
# Seconds to wait for the TCP connect+handshake. Override with COSMO_CONNECT_TIMEOUT.
|
|
12
11
|
CONNECT_TIMEOUT = 2
|
|
12
|
+
# Reconnect attempts before the connection closes for good; -1 never gives up, so a process outlives a NATS
|
|
13
|
+
# outage. nats-pure lets NATS_MAX_RECONNECT_ATTEMPTS override it.
|
|
14
|
+
MAX_RECONNECT_ATTEMPTS = -1
|
|
13
15
|
|
|
14
16
|
def self.instance
|
|
15
17
|
@instance ||= Client.new
|
|
@@ -41,7 +43,8 @@ module Cosmo
|
|
|
41
43
|
def initialize(nats_url: ENV.fetch("NATS_URL", "nats://localhost:4222"), name: self.class.default_name)
|
|
42
44
|
@name = name
|
|
43
45
|
Logger.debug "Connecting to NATS server at #{nats_url} as #{@name}..."
|
|
44
|
-
@nc = NATS.connect(nats_url, name: @name, connect_timeout: self.class.connect_timeout
|
|
46
|
+
@nc = NATS.connect(nats_url, name: @name, connect_timeout: self.class.connect_timeout,
|
|
47
|
+
max_reconnect_attempts: MAX_RECONNECT_ATTEMPTS)
|
|
45
48
|
Logger.debug "Connection established"
|
|
46
49
|
@js = @nc.jetstream(timeout: self.class.js_timeout)
|
|
47
50
|
end
|
|
@@ -157,7 +160,7 @@ module Cosmo
|
|
|
157
160
|
messages = {}
|
|
158
161
|
loop do
|
|
159
162
|
msg = sub.next_msg(timeout: self.class.js_timeout)
|
|
160
|
-
subject = msg.header&.dig(
|
|
163
|
+
subject = msg.header&.dig(NATS::JetStream::Header::SUBJECT)
|
|
161
164
|
break unless subject
|
|
162
165
|
|
|
163
166
|
messages[subject] = msg.data
|
|
@@ -195,8 +198,9 @@ module Cosmo
|
|
|
195
198
|
|
|
196
199
|
private
|
|
197
200
|
|
|
198
|
-
# NOTE: nats-pure's #pull_subscribe
|
|
199
|
-
#
|
|
201
|
+
# NOTE: nats-pure's #pull_subscribe can't create a true ephemeral pull consumer: its rescue branch
|
|
202
|
+
# unconditionally sets `config[:durable_name] = durable` (jetstream.rb). So the consumer is created
|
|
203
|
+
# here, and #pull_subscribe only binds to it by name.
|
|
200
204
|
def ephemeral_subscribe(subject, config) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
201
205
|
config = config.dup
|
|
202
206
|
stream = config.delete(:stream) or raise ArgumentError, "stream required for ephemeral consumers"
|
|
@@ -211,14 +215,7 @@ module Cosmo
|
|
|
211
215
|
end
|
|
212
216
|
|
|
213
217
|
info = js.add_consumer(stream, consumer_config)
|
|
214
|
-
|
|
215
|
-
sub = nc.subscribe(nc.new_inbox)
|
|
216
|
-
sub.extend(NATS::JetStream.const_get(:PullSubscription))
|
|
217
|
-
sub.jsi = NATS::JetStream.const_get(:JS)::Sub.new(
|
|
218
|
-
js: js, stream: stream, consumer: info.name,
|
|
219
|
-
nms: "#{js.prefix}.CONSUMER.MSG.NEXT.#{stream}.#{info.name}"
|
|
220
|
-
)
|
|
221
|
-
sub
|
|
218
|
+
js.pull_subscribe(subject, nil, stream: stream, name: info.name)
|
|
222
219
|
end
|
|
223
220
|
|
|
224
221
|
# NOTE: KV manager in nats-pure hardcodes the fields it copies into StreamConfig,
|
data/lib/cosmo/job/data.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Cosmo
|
|
|
44
44
|
|
|
45
45
|
# @return [Hash{String => String, Integer}] dedup id, plus where to dispatch a delayed job once it is due
|
|
46
46
|
def headers
|
|
47
|
-
headers = {
|
|
47
|
+
headers = { NATS::JetStream::Header::MSG_ID => jid }
|
|
48
48
|
return headers unless @at
|
|
49
49
|
|
|
50
50
|
target = stream(target: true)
|
data/lib/cosmo/job/processor.rb
CHANGED
|
@@ -8,8 +8,8 @@ module Cosmo
|
|
|
8
8
|
# Headers the scheduler consumes rather than forwards. The +X-+ ones address the dispatch
|
|
9
9
|
# itself, and NATS sets +Nats-Scheduler+/+Nats-Schedule-Next+ on a message it fires and
|
|
10
10
|
# rejects a publish that carries them back.
|
|
11
|
-
DISPATCH_HEADERS =
|
|
12
|
-
|
|
11
|
+
DISPATCH_HEADERS = ["X-Stream", "X-Subject", "X-Execute-At", NATS::JetStream::Header::EXPECTED_STREAM,
|
|
12
|
+
NATS::JetStream::Header::SCHEDULER, NATS::JetStream::Header::SCHEDULE_NEXT].freeze
|
|
13
13
|
|
|
14
14
|
# @raise [UnknownJobStreamError] when --streams/--stream or COSMO_JOBS_STREAMS names an unconfigured stream
|
|
15
15
|
def self.validate_options!(options)
|
|
@@ -71,8 +71,7 @@ module Cosmo
|
|
|
71
71
|
messages&.each do |message|
|
|
72
72
|
headers = message.header.except(*DISPATCH_HEADERS)
|
|
73
73
|
stream, subject, execute_at = message.header.values_at("X-Stream", "X-Subject", "X-Execute-At")
|
|
74
|
-
|
|
75
|
-
scheduler = message.header["Nats-Scheduler"]
|
|
74
|
+
scheduler = message.header[NATS::JetStream::Header::SCHEDULER]
|
|
76
75
|
if scheduler
|
|
77
76
|
headers["X-Scheduled-By"] = scheduler
|
|
78
77
|
headers["X-Enqueued-At"] = message.metadata.timestamp.to_f.to_s
|
|
@@ -80,7 +79,7 @@ module Cosmo
|
|
|
80
79
|
execute_at = execute_at.to_i
|
|
81
80
|
|
|
82
81
|
if now >= execute_at
|
|
83
|
-
client.publish(subject, message.data, header: headers)
|
|
82
|
+
client.publish(subject, message.data, header: headers, stream:)
|
|
84
83
|
message.ack
|
|
85
84
|
else
|
|
86
85
|
message.nak(delay: Config.to_ns(execute_at - now))
|
|
@@ -262,7 +261,7 @@ module Cosmo
|
|
|
262
261
|
def scheduled_by(message)
|
|
263
262
|
header = message.header or return
|
|
264
263
|
|
|
265
|
-
header["X-Scheduled-By"] || header[
|
|
264
|
+
header["X-Scheduled-By"] || header[NATS::JetStream::Header::SCHEDULER]
|
|
266
265
|
end
|
|
267
266
|
|
|
268
267
|
# A cron message is enqueued when NATS fires it, not when the scheduler dispatches it on, so
|
data/lib/cosmo/utils.rb
CHANGED
data/lib/cosmo/version.rb
CHANGED