cosmonats 0.7.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.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +219 -113
  3. data/lib/cosmo/active_job/adapter.rb +1 -2
  4. data/lib/cosmo/api/counter.rb +17 -17
  5. data/lib/cosmo/api/cron/entry.rb +10 -11
  6. data/lib/cosmo/api/cron.rb +6 -3
  7. data/lib/cosmo/api/job.rb +1 -1
  8. data/lib/cosmo/api/kv.rb +3 -3
  9. data/lib/cosmo/api/registry.rb +1 -1
  10. data/lib/cosmo/api/stats/busy.rb +6 -1
  11. data/lib/cosmo/api/stats/metrics.rb +109 -0
  12. data/lib/cosmo/api/stats/processes.rb +14 -4
  13. data/lib/cosmo/api/stats/totals.rb +4 -12
  14. data/lib/cosmo/api/stats.rb +18 -2
  15. data/lib/cosmo/api/stream.rb +7 -2
  16. data/lib/cosmo/batch/counters.rb +19 -0
  17. data/lib/cosmo/batch.rb +6 -6
  18. data/lib/cosmo/cli.rb +21 -5
  19. data/lib/cosmo/client.rb +37 -14
  20. data/lib/cosmo/config/cosmo.yml +58 -0
  21. data/lib/cosmo/config/settings.rb +20 -0
  22. data/lib/cosmo/config.rb +144 -19
  23. data/lib/cosmo/heartbeat.rb +11 -0
  24. data/lib/cosmo/job/data.rb +17 -12
  25. data/lib/cosmo/job/enqueuer.rb +53 -0
  26. data/lib/cosmo/job/limit.rb +2 -2
  27. data/lib/cosmo/job/processor.rb +32 -58
  28. data/lib/cosmo/job/requeue.rb +20 -0
  29. data/lib/cosmo/job.rb +8 -22
  30. data/lib/cosmo/logger.rb +15 -2
  31. data/lib/cosmo/middleware/busy.rb +15 -0
  32. data/lib/cosmo/middleware/chain.rb +137 -0
  33. data/lib/cosmo/middleware/limit.rb +50 -0
  34. data/lib/cosmo/middleware/metrics.rb +39 -0
  35. data/lib/cosmo/{sentry/job_processor_middleware.rb → middleware/sentry.rb} +26 -30
  36. data/lib/cosmo/middleware/totals.rb +29 -0
  37. data/lib/cosmo/middleware.rb +7 -0
  38. data/lib/cosmo/processor.rb +11 -5
  39. data/lib/cosmo/publisher.rb +1 -13
  40. data/lib/cosmo/railtie.rb +2 -2
  41. data/lib/cosmo/services.rb +69 -0
  42. data/lib/cosmo/stream/processor.rb +7 -1
  43. data/lib/cosmo/utils/hash.rb +7 -8
  44. data/lib/cosmo/utils.rb +0 -1
  45. data/lib/cosmo/version.rb +1 -1
  46. data/lib/cosmo/web/assets/app.css +271 -571
  47. data/lib/cosmo/web/assets/app.js +158 -0
  48. data/lib/cosmo/web/assets/fonts/OFL.txt +92 -0
  49. data/lib/cosmo/web/assets/fonts/geist-mono.woff2 +0 -0
  50. data/lib/cosmo/web/assets/fonts/geist.woff2 +0 -0
  51. data/lib/cosmo/web/assets/logo.svg +10 -0
  52. data/lib/cosmo/web/cache.rb +75 -0
  53. data/lib/cosmo/web/chart.rb +122 -0
  54. data/lib/cosmo/web/circuit.rb +55 -0
  55. data/lib/cosmo/web/context.rb +0 -4
  56. data/lib/cosmo/web/controllers/application.rb +16 -0
  57. data/lib/cosmo/web/controllers/batches.rb +1 -0
  58. data/lib/cosmo/web/controllers/crons.rb +17 -2
  59. data/lib/cosmo/web/controllers/jobs.rb +59 -54
  60. data/lib/cosmo/web/controllers/metrics.rb +49 -0
  61. data/lib/cosmo/web/controllers/overview.rb +70 -0
  62. data/lib/cosmo/web/controllers/processes.rb +33 -0
  63. data/lib/cosmo/web/controllers/streams.rb +26 -28
  64. data/lib/cosmo/web/helpers/application.rb +67 -0
  65. data/lib/cosmo/web/renderer.rb +19 -6
  66. data/lib/cosmo/web/throughput.rb +104 -0
  67. data/lib/cosmo/web/views/batches/_table.erb +42 -43
  68. data/lib/cosmo/web/views/batches/index.erb +3 -10
  69. data/lib/cosmo/web/views/crons/_table.erb +32 -53
  70. data/lib/cosmo/web/views/crons/index.erb +2 -9
  71. data/lib/cosmo/web/views/jobs/_args.erb +5 -0
  72. data/lib/cosmo/web/views/jobs/_busy.erb +25 -121
  73. data/lib/cosmo/web/views/jobs/_dead.erb +42 -82
  74. data/lib/cosmo/web/views/jobs/_enqueued.erb +33 -113
  75. data/lib/cosmo/web/views/jobs/_panel.erb +29 -0
  76. data/lib/cosmo/web/views/jobs/_queue_row.erb +18 -0
  77. data/lib/cosmo/web/views/jobs/_queues.erb +20 -0
  78. data/lib/cosmo/web/views/jobs/_scheduled.erb +20 -54
  79. data/lib/cosmo/web/views/jobs/index.erb +4 -17
  80. data/lib/cosmo/web/views/layout.erb +73 -22
  81. data/lib/cosmo/web/views/metrics/_chart.erb +41 -0
  82. data/lib/cosmo/web/views/metrics/_panel.erb +13 -0
  83. data/lib/cosmo/web/views/metrics/_table.erb +25 -0
  84. data/lib/cosmo/web/views/metrics/index.erb +9 -0
  85. data/lib/cosmo/web/views/overview/_badges.erb +3 -0
  86. data/lib/cosmo/web/views/overview/_kpis.erb +19 -0
  87. data/lib/cosmo/web/views/overview/_running.erb +18 -0
  88. data/lib/cosmo/web/views/overview/_throughput.erb +50 -0
  89. data/lib/cosmo/web/views/overview/_workers.erb +25 -0
  90. data/lib/cosmo/web/views/overview/index.erb +28 -0
  91. data/lib/cosmo/web/views/processes/_table.erb +67 -0
  92. data/lib/cosmo/web/views/processes/index.erb +1 -0
  93. data/lib/cosmo/web/views/shared/_pager.erb +22 -0
  94. data/lib/cosmo/web/views/shared/unavailable.erb +7 -0
  95. data/lib/cosmo/web/views/streams/_info.erb +28 -86
  96. data/lib/cosmo/web/views/streams/_list.erb +7 -0
  97. data/lib/cosmo/web/views/streams/_pause_banner.erb +8 -16
  98. data/lib/cosmo/web/views/streams/_stream.erb +52 -0
  99. data/lib/cosmo/web/views/streams/index.erb +3 -11
  100. data/lib/cosmo/web/views/streams/info.erb +3 -11
  101. data/lib/cosmo/web.rb +67 -33
  102. data/lib/cosmo.rb +38 -0
  103. data/sig/cosmo/api/counter.rbs +2 -3
  104. data/sig/cosmo/api/cron/entry.rbs +2 -1
  105. data/sig/cosmo/api/stats/busy.rbs +3 -1
  106. data/sig/cosmo/api/stats/metrics.rbs +42 -0
  107. data/sig/cosmo/api/stats/processes.rbs +3 -0
  108. data/sig/cosmo/api/stats/totals.rbs +3 -2
  109. data/sig/cosmo/api/stats.rbs +4 -0
  110. data/sig/cosmo/api/stream.rbs +2 -0
  111. data/sig/cosmo/batch/counters.rbs +10 -0
  112. data/sig/cosmo/batch.rbs +3 -4
  113. data/sig/cosmo/cli.rbs +2 -0
  114. data/sig/cosmo/client.rbs +4 -1
  115. data/sig/cosmo/config/settings.rbs +31 -0
  116. data/sig/cosmo/config.rbs +59 -5
  117. data/sig/cosmo/heartbeat.rbs +2 -0
  118. data/sig/cosmo/job/data.rbs +5 -3
  119. data/sig/cosmo/job/enqueuer.rbs +11 -0
  120. data/sig/cosmo/job/processor.rbs +3 -5
  121. data/sig/cosmo/job/requeue.rbs +9 -0
  122. data/sig/cosmo/job.rbs +0 -2
  123. data/sig/cosmo/logger.rbs +6 -0
  124. data/sig/cosmo/middleware/chain.rbs +51 -0
  125. data/sig/cosmo/middleware/sentry.rbs +14 -0
  126. data/sig/cosmo/middleware.rbs +35 -0
  127. data/sig/cosmo/processor.rbs +2 -0
  128. data/sig/cosmo/publisher.rbs +1 -9
  129. data/sig/cosmo/services.rbs +18 -0
  130. data/sig/cosmo/stream/processor.rbs +2 -0
  131. data/sig/cosmo/utils/hash.rbs +2 -3
  132. data/sig/cosmo.rbs +11 -0
  133. metadata +60 -20
  134. data/lib/cosmo/sentry/auto.rb +0 -6
  135. data/lib/cosmo/utils/overrides.rb +0 -206
  136. data/lib/cosmo/utils/warnings.rb +0 -17
  137. data/lib/cosmo/web/controllers/actions.rb +0 -16
  138. data/lib/cosmo/web/views/actions/index.erb +0 -7
  139. data/lib/cosmo/web/views/jobs/_processes.erb +0 -131
  140. data/lib/cosmo/web/views/jobs/_stats.erb +0 -73
  141. data/lib/cosmo/web/views/jobs/_tabs.erb +0 -8
  142. data/lib/cosmo/web/views/jobs/busy.erb +0 -18
  143. data/lib/cosmo/web/views/jobs/dead.erb +0 -18
  144. data/lib/cosmo/web/views/jobs/enqueued.erb +0 -18
  145. data/lib/cosmo/web/views/jobs/scheduled.erb +0 -18
  146. data/lib/cosmo/web/views/streams/_stream_row.erb +0 -42
  147. data/lib/cosmo/web/views/streams/_table.erb +0 -25
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cosmonats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Vorotilin
@@ -43,20 +43,20 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '2.5'
46
+ version: '2.7'
47
47
  - - "<"
48
48
  - !ruby/object:Gem::Version
49
- version: '2.7'
49
+ version: '2.8'
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
- version: '2.5'
56
+ version: '2.7'
57
57
  - - "<"
58
58
  - !ruby/object:Gem::Version
59
- version: '2.7'
59
+ version: '2.8'
60
60
  description: Lightweight background and stream processing for Ruby
61
61
  email:
62
62
  - d.vorotilin@gmail.com
@@ -83,15 +83,19 @@ files:
83
83
  - lib/cosmo/api/registry.rb
84
84
  - lib/cosmo/api/stats.rb
85
85
  - lib/cosmo/api/stats/busy.rb
86
+ - lib/cosmo/api/stats/metrics.rb
86
87
  - lib/cosmo/api/stats/processes.rb
87
88
  - lib/cosmo/api/stats/totals.rb
88
89
  - lib/cosmo/api/stream.rb
89
90
  - lib/cosmo/batch.rb
90
91
  - lib/cosmo/batch/callback.rb
92
+ - lib/cosmo/batch/counters.rb
91
93
  - lib/cosmo/batch/dispatcher.rb
92
94
  - lib/cosmo/cli.rb
93
95
  - lib/cosmo/client.rb
94
96
  - lib/cosmo/config.rb
97
+ - lib/cosmo/config/cosmo.yml
98
+ - lib/cosmo/config/settings.rb
95
99
  - lib/cosmo/engine.rb
96
100
  - lib/cosmo/heartbeat.rb
97
101
  - lib/cosmo/http_server.rb
@@ -100,16 +104,24 @@ files:
100
104
  - lib/cosmo/http_server/ping.rb
101
105
  - lib/cosmo/job.rb
102
106
  - lib/cosmo/job/data.rb
107
+ - lib/cosmo/job/enqueuer.rb
103
108
  - lib/cosmo/job/failure.rb
104
109
  - lib/cosmo/job/limit.rb
105
110
  - lib/cosmo/job/processor.rb
111
+ - lib/cosmo/job/requeue.rb
106
112
  - lib/cosmo/job/stream_filter.rb
107
113
  - lib/cosmo/logger.rb
114
+ - lib/cosmo/middleware.rb
115
+ - lib/cosmo/middleware/busy.rb
116
+ - lib/cosmo/middleware/chain.rb
117
+ - lib/cosmo/middleware/limit.rb
118
+ - lib/cosmo/middleware/metrics.rb
119
+ - lib/cosmo/middleware/sentry.rb
120
+ - lib/cosmo/middleware/totals.rb
108
121
  - lib/cosmo/processor.rb
109
122
  - lib/cosmo/publisher.rb
110
123
  - lib/cosmo/railtie.rb
111
- - lib/cosmo/sentry/auto.rb
112
- - lib/cosmo/sentry/job_processor_middleware.rb
124
+ - lib/cosmo/services.rb
113
125
  - lib/cosmo/stream.rb
114
126
  - lib/cosmo/stream/data.rb
115
127
  - lib/cosmo/stream/message.rb
@@ -119,48 +131,67 @@ files:
119
131
  - lib/cosmo/utils/duration.rb
120
132
  - lib/cosmo/utils/hash.rb
121
133
  - lib/cosmo/utils/json.rb
122
- - lib/cosmo/utils/overrides.rb
123
134
  - lib/cosmo/utils/signal.rb
124
135
  - lib/cosmo/utils/stopwatch.rb
125
136
  - lib/cosmo/utils/string.rb
126
137
  - lib/cosmo/utils/thread_pool.rb
127
138
  - lib/cosmo/utils/ttl_cache.rb
128
- - lib/cosmo/utils/warnings.rb
129
139
  - lib/cosmo/version.rb
130
140
  - lib/cosmo/web.rb
131
141
  - lib/cosmo/web/assets/app.css
142
+ - lib/cosmo/web/assets/app.js
143
+ - lib/cosmo/web/assets/fonts/OFL.txt
144
+ - lib/cosmo/web/assets/fonts/geist-mono.woff2
145
+ - lib/cosmo/web/assets/fonts/geist.woff2
132
146
  - lib/cosmo/web/assets/htmx.2.0.8.min.js.gz
147
+ - lib/cosmo/web/assets/logo.svg
148
+ - lib/cosmo/web/cache.rb
149
+ - lib/cosmo/web/chart.rb
150
+ - lib/cosmo/web/circuit.rb
133
151
  - lib/cosmo/web/context.rb
134
- - lib/cosmo/web/controllers/actions.rb
135
152
  - lib/cosmo/web/controllers/application.rb
136
153
  - lib/cosmo/web/controllers/batches.rb
137
154
  - lib/cosmo/web/controllers/crons.rb
138
155
  - lib/cosmo/web/controllers/jobs.rb
156
+ - lib/cosmo/web/controllers/metrics.rb
157
+ - lib/cosmo/web/controllers/overview.rb
158
+ - lib/cosmo/web/controllers/processes.rb
139
159
  - lib/cosmo/web/controllers/streams.rb
140
160
  - lib/cosmo/web/helpers/application.rb
141
161
  - lib/cosmo/web/renderer.rb
142
- - lib/cosmo/web/views/actions/index.erb
162
+ - lib/cosmo/web/throughput.rb
143
163
  - lib/cosmo/web/views/batches/_table.erb
144
164
  - lib/cosmo/web/views/batches/index.erb
145
165
  - lib/cosmo/web/views/crons/_table.erb
146
166
  - lib/cosmo/web/views/crons/index.erb
167
+ - lib/cosmo/web/views/jobs/_args.erb
147
168
  - lib/cosmo/web/views/jobs/_busy.erb
148
169
  - lib/cosmo/web/views/jobs/_dead.erb
149
170
  - lib/cosmo/web/views/jobs/_enqueued.erb
150
- - lib/cosmo/web/views/jobs/_processes.erb
171
+ - lib/cosmo/web/views/jobs/_panel.erb
172
+ - lib/cosmo/web/views/jobs/_queue_row.erb
173
+ - lib/cosmo/web/views/jobs/_queues.erb
151
174
  - lib/cosmo/web/views/jobs/_scheduled.erb
152
- - lib/cosmo/web/views/jobs/_stats.erb
153
- - lib/cosmo/web/views/jobs/_tabs.erb
154
- - lib/cosmo/web/views/jobs/busy.erb
155
- - lib/cosmo/web/views/jobs/dead.erb
156
- - lib/cosmo/web/views/jobs/enqueued.erb
157
175
  - lib/cosmo/web/views/jobs/index.erb
158
- - lib/cosmo/web/views/jobs/scheduled.erb
159
176
  - lib/cosmo/web/views/layout.erb
177
+ - lib/cosmo/web/views/metrics/_chart.erb
178
+ - lib/cosmo/web/views/metrics/_panel.erb
179
+ - lib/cosmo/web/views/metrics/_table.erb
180
+ - lib/cosmo/web/views/metrics/index.erb
181
+ - lib/cosmo/web/views/overview/_badges.erb
182
+ - lib/cosmo/web/views/overview/_kpis.erb
183
+ - lib/cosmo/web/views/overview/_running.erb
184
+ - lib/cosmo/web/views/overview/_throughput.erb
185
+ - lib/cosmo/web/views/overview/_workers.erb
186
+ - lib/cosmo/web/views/overview/index.erb
187
+ - lib/cosmo/web/views/processes/_table.erb
188
+ - lib/cosmo/web/views/processes/index.erb
189
+ - lib/cosmo/web/views/shared/_pager.erb
190
+ - lib/cosmo/web/views/shared/unavailable.erb
160
191
  - lib/cosmo/web/views/streams/_info.erb
192
+ - lib/cosmo/web/views/streams/_list.erb
161
193
  - lib/cosmo/web/views/streams/_pause_banner.erb
162
- - lib/cosmo/web/views/streams/_stream_row.erb
163
- - lib/cosmo/web/views/streams/_table.erb
194
+ - lib/cosmo/web/views/streams/_stream.erb
164
195
  - lib/cosmo/web/views/streams/index.erb
165
196
  - lib/cosmo/web/views/streams/info.erb
166
197
  - lib/cosmonats.rb
@@ -177,15 +208,18 @@ files:
177
208
  - sig/cosmo/api/registry.rbs
178
209
  - sig/cosmo/api/stats.rbs
179
210
  - sig/cosmo/api/stats/busy.rbs
211
+ - sig/cosmo/api/stats/metrics.rbs
180
212
  - sig/cosmo/api/stats/processes.rbs
181
213
  - sig/cosmo/api/stats/totals.rbs
182
214
  - sig/cosmo/api/stream.rbs
183
215
  - sig/cosmo/batch.rbs
184
216
  - sig/cosmo/batch/callback.rbs
217
+ - sig/cosmo/batch/counters.rbs
185
218
  - sig/cosmo/batch/dispatcher.rbs
186
219
  - sig/cosmo/cli.rbs
187
220
  - sig/cosmo/client.rbs
188
221
  - sig/cosmo/config.rbs
222
+ - sig/cosmo/config/settings.rbs
189
223
  - sig/cosmo/engine.rbs
190
224
  - sig/cosmo/heartbeat.rbs
191
225
  - sig/cosmo/http_server.rbs
@@ -194,14 +228,20 @@ files:
194
228
  - sig/cosmo/http_server/ping.rbs
195
229
  - sig/cosmo/job.rbs
196
230
  - sig/cosmo/job/data.rbs
231
+ - sig/cosmo/job/enqueuer.rbs
197
232
  - sig/cosmo/job/failure.rbs
198
233
  - sig/cosmo/job/limit.rbs
199
234
  - sig/cosmo/job/processor.rbs
235
+ - sig/cosmo/job/requeue.rbs
200
236
  - sig/cosmo/job/stream_filter.rbs
201
237
  - sig/cosmo/logger.rbs
238
+ - sig/cosmo/middleware.rbs
239
+ - sig/cosmo/middleware/chain.rbs
240
+ - sig/cosmo/middleware/sentry.rbs
202
241
  - sig/cosmo/processor.rbs
203
242
  - sig/cosmo/publisher.rbs
204
243
  - sig/cosmo/railtie.rbs
244
+ - sig/cosmo/services.rbs
205
245
  - sig/cosmo/stream.rbs
206
246
  - sig/cosmo/stream/data.rbs
207
247
  - sig/cosmo/stream/message.rbs
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cosmo/job/processor"
4
- require "cosmo/sentry/job_processor_middleware"
5
-
6
- Cosmo::Job::Processor.prepend Cosmo::Sentry::JobProcessorMiddleware
@@ -1,206 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Cosmo::Utils::Warnings.silence do
4
- unless NATS::JetStream::API::StreamConfig.members.include?(:allow_msg_schedules)
5
- members = NATS::JetStream::API::StreamConfig.members + %i[allow_msg_counter allow_msg_schedules]
6
- NATS::JetStream::API::StreamConfig = Struct.new(*members, keyword_init: true) do
7
- def initialize(opts = {})
8
- rem = opts.keys - members
9
- opts.delete_if { |k| rem.include?(k) }
10
- super
11
- end
12
- end
13
- end
14
-
15
- unless NATS::JetStream::PubAck.members.include?(:val)
16
- members = NATS::JetStream::PubAck.members + [:val]
17
- NATS::JetStream::PubAck = Struct.new(*members, keyword_init: true)
18
- end
19
- end
20
-
21
- # Upstream bug in nats-pure 2.5.0 and 2.6.0 (https://github.com/nats-io/nats-pure.rb):
22
- # NATS::JetStream::PullSubscription#fetch only assigns its `start_time` local in the
23
- # `batch > 1` branch, but the final "did we time out" check outside the case statement
24
- # reads it unconditionally. For `batch == 1` (what Cosmo::Processor always uses),
25
- # `start_time` is nil there -- normally masked because the `batch == 1` branch has its
26
- # own timeout check (using a properly-set `t`) that raises first.
27
- #
28
- # It only surfaces once two threads call #fetch concurrently on the *same* subscription:
29
- # @pending_queue/wait_for_msgs_cond are shared per-subscription state, and #dispatch's
30
- # `wait_for_msgs_cond.signal` wakes exactly one arbitrary waiter -- not necessarily the
31
- # one whose own request produced that wakeup. A thread woken by someone else's signal
32
- # finds nothing left in the (already-drained) queue, so it skips its own inner timeout
33
- # check and falls into the buggy tail check, raising `TypeError: nil can't be coerced
34
- # into Float` instead of the intended timeout/empty result. Reproduced deterministically
35
- # with N threads fetching in a loop against the same PullSubscription.
36
- #
37
- # Second, separate upstream bug in the same method: the post-wait timeout check below
38
- # (`raise ... if MonotonicTime.since(t) > timeout`) fires unconditionally, unlike every
39
- # other timeout check in this method (all guarded with `msgs.empty? &&`). If the unsynchronized
40
- # pop just above it succeeded in grabbing a real, already-delivered message -- which can happen
41
- # even after `timeout` has technically elapsed, since #dispatch's signal and this thread actually
42
- # resuming are two different moments -- that message is already gone from @pending_queue with
43
- # nowhere else to go, yet gets discarded here and a plain NATS::Timeout raised instead. Callers
44
- # (including Cosmo::Processor#fetch) treat NATS::Timeout as "no messages", so the message is lost
45
- # silently: never processed, never acked, and not redelivered until the consumer's ack_wait expires.
46
- # Reproduces reliably with several threads fetching (batch: 1) concurrently against the same
47
- # subscription, e.g. Cosmo::Job::Processor's thread pool.
48
- #
49
- # Vendored copy of nats-pure's PullSubscription#fetch, kept as close to
50
- # upstream as possible (two one-line fixes, see comments above), so it's easy to diff against the
51
- # next nats-pure release and drop once fixed there. Fixed on nats-pure main by the fetch rewrite
52
- # (#181), recognized by its private #take_pending, in which case this copy is not loaded.
53
- return if NATS::JetStream.const_get(:PullSubscription).private_method_defined?(:take_pending)
54
-
55
- # rubocop:disable all
56
- module NATS
57
- class JetStream
58
- module PullSubscription
59
- def fetch(batch = 1, params = {})
60
- raise ::NATS::JetStream::Error.new("nats: invalid batch size") if batch < 1
61
-
62
- t = MonotonicTime.now
63
- start_time = t # fixes the upstream bug -- see comment above
64
- timeout = params[:timeout] ||= 5
65
- expires = (timeout * 1_000_000_000) - 100_000
66
- next_req = { batch: batch }
67
-
68
- msgs = []
69
- case
70
- when batch == 1
71
- synchronize do
72
- unless @pending_queue.empty?
73
- msg = @pending_queue.pop
74
- @pending_size -= msg.data.size
75
- if JS.is_status_msg(msg)
76
- case msg.header["Status"]
77
- when JS::Status::NoMsgs then nil
78
- when JS::Status::RequestTimeout then nil # Skip
79
- else raise JS.from_msg(msg)
80
- end
81
- else
82
- msgs << msg
83
- end
84
- end
85
- end
86
-
87
- next_req[:expires] = expires
88
- if msgs.empty?
89
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
90
- synchronize { wait_for_msgs_cond.wait(timeout) }
91
-
92
- unless @pending_queue.empty?
93
- msg = @pending_queue.pop
94
- @pending_size -= msg.data.size
95
- msgs << msg
96
- end
97
-
98
- raise ::NATS::Timeout.new("nats: fetch timeout") if msgs.empty? && (MonotonicTime.since(t) > timeout)
99
-
100
- if JS.is_status_msg(msgs.first)
101
- msg = msgs.first
102
- case msg.header[JS::Header::Status]
103
- when JS::Status::RequestTimeout then raise NATS::Timeout.new("nats: fetch request timeout")
104
- else raise JS.from_msg(msgs.first)
105
- end
106
- end
107
- end
108
- when batch > 1
109
- synchronize do
110
- if batch <= @pending_queue.size
111
- batch.times do
112
- msg = @pending_queue.pop
113
- @pending_size -= msg.data.size
114
- if JS.is_status_msg(msg)
115
- case msg.header[JS::Header::Status]
116
- when JS::Status::NoMsgs, JS::Status::RequestTimeout then next
117
- else raise JS.from_msg(msg)
118
- end
119
- else
120
- msgs << msg
121
- end
122
- end
123
- return msgs
124
- end
125
- end
126
-
127
- next_req[:no_wait] = true
128
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
129
-
130
- start_time = MonotonicTime.now
131
- msg = nil
132
-
133
- synchronize do
134
- wait_for_msgs_cond.wait(timeout)
135
- unless @pending_queue.empty?
136
- msg = @pending_queue.pop
137
- @pending_size -= msg.data.size
138
- end
139
- end
140
-
141
- if !msg.nil? && JS.is_status_msg(msg)
142
- case msg.header[JS::Header::Status]
143
- when JS::Status::NoMsgs
144
- next_req[:expires] = expires
145
- next_req.delete(:no_wait)
146
- @nc.publish(@jsi.nms, JS.next_req_to_json(next_req), @subject)
147
- when JS::Status::RequestTimeout
148
- raise NATS::Timeout.new("nats: fetch request timeout")
149
- else
150
- raise JS.from_msg(msg)
151
- end
152
- else
153
- msgs << msg unless msg.nil?
154
- end
155
-
156
- duration = MonotonicTime.since(start_time)
157
- raise NATS::Timeout.new("nats: fetch timeout") if msgs.empty? && (duration > timeout)
158
-
159
- needed = batch - msgs.count
160
- while (needed > 0) && (MonotonicTime.since(start_time) < timeout)
161
- duration = MonotonicTime.since(start_time)
162
-
163
- synchronize do
164
- if @pending_queue.empty?
165
- deadline = timeout - duration
166
- wait_for_msgs_cond.wait(deadline) if deadline > 0
167
-
168
- duration = MonotonicTime.since(start_time)
169
- if msgs.empty? && @pending_queue.empty? && (duration > timeout)
170
- raise NATS::Timeout.new("nats: fetch timeout")
171
- end
172
- end
173
-
174
- unless @pending_queue.empty?
175
- msg = @pending_queue.pop
176
- @pending_size -= msg.data.size
177
-
178
- if JS.is_status_msg(msg)
179
- case msg.header[JS::Header::Status]
180
- when JS::Status::NoMsgs, JS::Status::RequestTimeout
181
- duration = MonotonicTime.since(start_time)
182
- if duration > timeout
183
- raise NATS::Timeout.new("nats: fetch timeout") if msgs.empty?
184
-
185
- return msgs
186
- end
187
- else
188
- raise JS.from_msg(msg)
189
- end
190
- else
191
- msgs << msg
192
- needed -= 1
193
- end
194
- end
195
- end
196
- end
197
- end
198
-
199
- raise ::NATS::Timeout.new("nats: fetch timeout") if msgs.empty? && (MonotonicTime.since(start_time) > timeout)
200
-
201
- msgs
202
- end
203
- end
204
- end
205
- end
206
- # rubocop:enable all
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cosmo
4
- module Utils
5
- module Warnings
6
- module_function
7
-
8
- def silence
9
- verbose = $VERBOSE
10
- $VERBOSE = nil
11
- yield
12
- ensure
13
- $VERBOSE = verbose
14
- end
15
- end
16
- end
17
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cosmo/web/controllers/application"
4
-
5
- module Cosmo
6
- class Web
7
- module Controllers
8
- class Actions < Application
9
- def index
10
- content_for :title, "Actions"
11
- ok render("actions/index", layout: true)
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,7 +0,0 @@
1
- <section>
2
- <header></header>
3
-
4
- <div>
5
- <div class="alert alert-info">Coming soon, stay tuned</div>
6
- </div>
7
- </section>
@@ -1,131 +0,0 @@
1
- <% nav = @polling ? { limit: @limit } : { limit: @limit, poll: 0 } -%>
2
- <% states = { "running" => "badge-success", "quiet" => "badge-warning", "stopping" => "badge-danger" } -%>
3
- <div id="processes-poller"
4
- <% if @polling -%>
5
- hx-get="<%= url_for('/jobs/_processes', page: @page, limit: @limit) %>"
6
- hx-trigger="every <%= poll_interval %>s"
7
- hx-swap="outerHTML"
8
- <% end -%>
9
- >
10
- <div class="pending">
11
- <span></span>
12
- <span class="poll-status">
13
- <span class="text-muted"><%= @total %> process(es) alive</span>
14
- <% if @polling -%>
15
- <a class="btn btn-sm"
16
- hx-get="<%= url_for('/jobs/_processes', nav.merge(page: @page, poll: 0)) %>"
17
- hx-target="#processes-poller"
18
- hx-swap="outerHTML"
19
- hx-indicator="#global-spinner">&#9209; Stop Polling</a>
20
- <% else -%>
21
- <a class="btn btn-sm btn-primary"
22
- hx-get="<%= url_for('/jobs/_processes', page: @page, limit: @limit) %>"
23
- hx-target="#processes-poller"
24
- hx-swap="outerHTML"
25
- hx-indicator="#global-spinner">&#x25B6; Live Poll</a>
26
- <% end -%>
27
- </span>
28
- </div>
29
-
30
- <% if @processes.empty? -%>
31
- <div class="alert alert-warning">No worker processes are running</div>
32
- <% else -%>
33
- <div class="table-container">
34
- <table>
35
- <thead>
36
- <tr>
37
- <th>Process</th>
38
- <th>Command</th>
39
- <th>Subscriptions</th>
40
- <th>Busy</th>
41
- <th>State</th>
42
- <th>RTT</th>
43
- <th>Reconnects</th>
44
- <th>Memory</th>
45
- <th>Uptime</th>
46
- <th>Last beat</th>
47
- </tr>
48
- </thead>
49
- <tbody>
50
- <% @processes.each do |process| -%>
51
- <% nats = Hash(process[:nats]) -%>
52
- <tr>
53
- <td>
54
- <div class="job-class"><%= h(process[:hostname]) %>:<%= h(process[:pid]) %></div>
55
- <div class="text-muted" style="font-size: var(--font-size-small);">
56
- <%= h(nats[:ip] || "unknown IP") %>
57
- <% if nats[:name] -%>&middot; <code><%= h(nats[:name]) %></code><% end -%>
58
- </div>
59
- </td>
60
- <td>
61
- <% cmdline = process[:cmdline].to_s -%>
62
- <div class="job-id">
63
- <details>
64
- <summary>
65
- <code><%= h(cmdline.length > 40 ? "#{cmdline[0, 40]}…" : cmdline) %></code>
66
- </summary>
67
- <code style="font-size: var(--font-size-small);"><%= h(cmdline) %></code>
68
- <div class="text-muted" style="font-size: var(--font-size-small);">
69
- cosmo <%= h(process[:version]) %> &middot; <%= h(process[:ruby]) %>
70
- <% if process[:http_port] -%>&middot; http :<%= h(process[:http_port]) %><% end -%>
71
- </div>
72
- </details>
73
- </div>
74
- </td>
75
- <td>
76
- <% Hash(process[:subscriptions]).each do |type, names| -%>
77
- <div><span class="text-muted"><%= h(type) %>:</span> <code><%= h(Array(names).join(", ")) %></code></div>
78
- <% end -%>
79
- </td>
80
- <td><%= h(process[:busy]) %> / <%= h(process[:concurrency]) %></td>
81
- <td><span class="badge <%= states.fetch(process[:state].to_s, 'badge-info') %>"><%= h(process[:state]) %></span></td>
82
- <td><%= nats[:rtt] ? "#{h(nats[:rtt])}ms" : '&mdash;' %></td>
83
- <td><%= h(nats[:reconnects].to_i) %></td>
84
- <td><%= process[:rss] ? format_bytes(process[:rss].to_i * 1024) : '&mdash;' %></td>
85
- <td><%= elapsed(process[:started_at]) %></td>
86
- <td><%= elapsed(process[:beat_at]) %> ago</td>
87
- </tr>
88
- <% end -%>
89
- </tbody>
90
- </table>
91
- </div>
92
- <% end -%>
93
-
94
- <% if @total_pages.to_i > 1 -%>
95
- <div class="pagination">
96
- <% if @page > 1 -%>
97
- <a hx-get="<%= url_for('/jobs/_processes', nav.merge(page: @page - 1)) %>"
98
- hx-target="#processes-poller"
99
- hx-swap="outerHTML"
100
- hx-indicator="#global-spinner"
101
- class="btn">&#x2190; Prev</a>
102
- <% else -%>
103
- <span class="btn btn-disabled">&#x2190; Prev</span>
104
- <% end -%>
105
-
106
- <% pages(@page, @total_pages).each do |p| -%>
107
- <% if p == :gap -%>
108
- <span class="pagination-gap">&hellip;</span>
109
- <% elsif p == @page -%>
110
- <span class="btn btn-primary btn-disabled"><%= p %></span>
111
- <% else -%>
112
- <a hx-get="<%= url_for('/jobs/_processes', nav.merge(page: p)) %>"
113
- hx-target="#processes-poller"
114
- hx-swap="outerHTML"
115
- hx-indicator="#global-spinner"
116
- class="btn"><%= p %></a>
117
- <% end -%>
118
- <% end -%>
119
-
120
- <% if @page < @total_pages -%>
121
- <a hx-get="<%= url_for('/jobs/_processes', nav.merge(page: @page + 1)) %>"
122
- hx-target="#processes-poller"
123
- hx-swap="outerHTML"
124
- hx-indicator="#global-spinner"
125
- class="btn">Next &#x2192;</a>
126
- <% else -%>
127
- <span class="btn btn-disabled">Next &#x2192;</span>
128
- <% end -%>
129
- </div>
130
- <% end -%>
131
- </div>
@@ -1,73 +0,0 @@
1
- <div class="cards-container">
2
- <article class="stat-card">
3
- <div class="row">
4
- <p>&#x1F4AA; Processed</p>
5
- <h4><%= format_numbers(@processed) %></h4>
6
- </div>
7
- <div class="row">
8
- <p>&#x1F625; Failed</p>
9
- <h4><%= format_numbers(@failed) %></h4>
10
- </div>
11
- <div class="row">
12
- <p>&#x1F504; Retries</p>
13
- <h4><%= format_numbers(@retries) %></h4>
14
- </div>
15
- </article>
16
- <article class="stat-card">
17
- <h3><%= format_numbers(@busy) %></h3>
18
- <p>
19
- <a href="<%= url_for('/jobs/busy') %>"
20
- hx-get="<%= url_for('/jobs/busy') %>"
21
- hx-target="#content"
22
- hx-push-url="true"
23
- hx-indicator="#global-spinner">&#x23F3; Busy</a>
24
- </p>
25
- </article>
26
- <article class="stat-card">
27
- <h3><%= format_numbers(@enqueued) %></h3>
28
- <p>
29
- <a href="<%= url_for('/jobs/enqueued') %>"
30
- hx-get="<%= url_for('/jobs/enqueued') %>"
31
- hx-target="#content"
32
- hx-push-url="true"
33
- hx-indicator="#global-spinner"
34
- class="<%= 'active' if referrer?('/jobs/enqueued') %>">&#x1F4EC; Enqueued</a>
35
- </p>
36
- </article>
37
- <article class="stat-card">
38
- <h3><%= format_numbers(@scheduled) %></h3>
39
- <p>
40
- <a href="<%= url_for('/jobs/scheduled') %>"
41
- hx-get="<%= url_for('/jobs/scheduled') %>"
42
- hx-target="#content"
43
- hx-push-url="true"
44
- hx-indicator="#global-spinner"
45
- class="<%= 'active' if referrer?('/jobs/scheduled') %>">&#x23F0; Scheduled</a>
46
- </p>
47
- </article>
48
- <article class="stat-card">
49
- <h3><%= format_numbers(@dead) %></h3>
50
- <p>
51
- <a href="<%= url_for('/jobs/dead') %>"
52
- hx-get="<%= url_for('/jobs/dead') %>"
53
- hx-target="#content"
54
- hx-push-url="true"
55
- hx-indicator="#global-spinner"
56
- class="<%= 'active' if referrer?('/jobs/dead') %>">&#x1F480; Dead</a>
57
- </p>
58
- </article>
59
- </div>
60
-
61
- <script>
62
- (function () {
63
- const links = document.querySelectorAll('.cards-container a');
64
-
65
- links.forEach(link => {
66
- link.addEventListener('click', () => {
67
- links.forEach(item => item.classList.remove('active'));
68
-
69
- link.classList.add('active');
70
- });
71
- });
72
- })();
73
- </script>
@@ -1,8 +0,0 @@
1
- <nav class="tabs">
2
- <a href="<%= url_for('/jobs') %>"
3
- class="<%= 'active' if path_prefix?('/jobs') %>">&#x1F4CB; Dashboard</a>
4
- <a href="<%= url_for('/batches') %>"
5
- class="<%= 'active' if path_prefix?('/batches') %>">&#x1F4E6; Batches</a>
6
- <a href="<%= url_for('/crons') %>"
7
- class="<%= 'active' if path_prefix?('/crons') %>">&#x23F0; Crons</a>
8
- </nav>