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
data/lib/cosmo/web.rb
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
require "rack"
|
|
4
4
|
require "json"
|
|
5
5
|
require "erb"
|
|
6
|
+
require "zlib"
|
|
7
|
+
require "cosmo/web/cache"
|
|
8
|
+
require "cosmo/web/circuit"
|
|
6
9
|
require "cosmo/web/context"
|
|
7
10
|
require "cosmo/web/renderer"
|
|
8
11
|
require "cosmo/web/controllers/application"
|
|
12
|
+
require "cosmo/web/controllers/overview"
|
|
9
13
|
require "cosmo/web/controllers/jobs"
|
|
14
|
+
require "cosmo/web/controllers/processes"
|
|
10
15
|
require "cosmo/web/controllers/streams"
|
|
11
|
-
require "cosmo/web/controllers/actions"
|
|
12
16
|
require "cosmo/web/controllers/crons"
|
|
13
17
|
require "cosmo/web/controllers/batches"
|
|
14
18
|
require "cosmo/web/controllers/metrics"
|
|
@@ -17,57 +21,84 @@ module Cosmo
|
|
|
17
21
|
class Web
|
|
18
22
|
include Renderer
|
|
19
23
|
|
|
24
|
+
FONT_HEADERS = { "cache-control" => "public, max-age=31536000, immutable" }.freeze
|
|
25
|
+
|
|
20
26
|
def self.call(env)
|
|
21
27
|
new.call(env)
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
# Serves the UI. An htmx refresh is shared through Cache with concurrent and recent identical ones; any other
|
|
31
|
+
# method than GET changes something, so it empties the cache. While Circuit is open, anything but an asset
|
|
32
|
+
# answers 503 at once instead of waiting on NATS.
|
|
33
|
+
def call(env) # rubocop:disable Metrics/AbcSize
|
|
25
34
|
@request = Rack::Request.new(env)
|
|
35
|
+
return route if @request.path_info.start_with?("/assets/", "/favicon.ico")
|
|
36
|
+
return guarded.tap { Cache.instance.clear } unless @request.get?
|
|
37
|
+
return guarded unless @request.get_header("HTTP_HX_REQUEST") == "true"
|
|
38
|
+
|
|
39
|
+
status, headers, body = Cache.instance.fetch(@request.fullpath) { guarded }
|
|
40
|
+
[status, headers.dup, body.dup]
|
|
41
|
+
rescue *Circuit::ERRORS
|
|
42
|
+
Circuit.instance.trip!
|
|
43
|
+
unavailable
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def route # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
26
49
|
path = @request.path_info
|
|
27
50
|
method = @request.request_method
|
|
28
51
|
|
|
29
52
|
response = case [method.downcase.to_sym, path]
|
|
30
|
-
in [:get, "/"]
|
|
31
|
-
in [:get, "/
|
|
32
|
-
in [:get, "/
|
|
33
|
-
in [:get, "/
|
|
34
|
-
in [:get, "/
|
|
35
|
-
in [:get, "/
|
|
36
|
-
in [:
|
|
37
|
-
in [:
|
|
38
|
-
in [:
|
|
39
|
-
in [:get, "/jobs/
|
|
40
|
-
in [:get, "/jobs/
|
|
41
|
-
in [:get, "/jobs/
|
|
42
|
-
in [:
|
|
43
|
-
in [:
|
|
44
|
-
in [:
|
|
45
|
-
in [:get, "/
|
|
46
|
-
in [:get, "/streams
|
|
47
|
-
in [:get, "/streams/
|
|
48
|
-
in [:
|
|
49
|
-
in [:patch, "/streams/
|
|
50
|
-
in [:
|
|
51
|
-
in [:get, "/crons"]
|
|
52
|
-
in [:
|
|
53
|
-
in [:
|
|
54
|
-
in [:
|
|
55
|
-
in [:get, "/metrics"]
|
|
56
|
-
in [:get, "/
|
|
57
|
-
in [:get, "/batches"]
|
|
58
|
-
in [:get, "/batches/_table"] then [Controllers::Batches, :_table]
|
|
59
|
-
in [:get, "/actions"] then [Controllers::Actions, :index]
|
|
53
|
+
in [:get, "/" | ""] then [Controllers::Overview, :index]
|
|
54
|
+
in [:get, "/_kpis"] then [Controllers::Overview, :_kpis]
|
|
55
|
+
in [:get, "/_throughput"] then [Controllers::Overview, :_throughput]
|
|
56
|
+
in [:get, "/_workers"] then [Controllers::Overview, :_workers]
|
|
57
|
+
in [:get, "/_running"] then [Controllers::Overview, :_running]
|
|
58
|
+
in [:get, "/_badges"] then [Controllers::Overview, :_badges]
|
|
59
|
+
in [:get, "/jobs"] then [Controllers::Jobs, :index]
|
|
60
|
+
in [:get, "/jobs/busy"] then [Controllers::Jobs, :busy]
|
|
61
|
+
in [:get, "/jobs/enqueued"] then [Controllers::Jobs, :enqueued]
|
|
62
|
+
in [:get, "/jobs/scheduled"] then [Controllers::Jobs, :scheduled]
|
|
63
|
+
in [:get, "/jobs/dead"] then [Controllers::Jobs, :dead]
|
|
64
|
+
in [:get, "/jobs/_queues"] then [Controllers::Jobs, :_queues]
|
|
65
|
+
in [:patch, %r{/jobs/retry/\d+}] then [Controllers::Jobs, :retry]
|
|
66
|
+
in [:delete, %r{/jobs/delete/\d+}] then [Controllers::Jobs, :delete]
|
|
67
|
+
in [:delete, %r{/jobs/enqueued/\d+}] then [Controllers::Jobs, :delete_enqueued]
|
|
68
|
+
in [:get, "/processes"] then [Controllers::Processes, :index]
|
|
69
|
+
in [:get, "/streams"] then [Controllers::Streams, :index]
|
|
70
|
+
in [:get, "/streams/info"] then [Controllers::Streams, :info]
|
|
71
|
+
in [:patch, "/streams/pause"] then [Controllers::Streams, :pause]
|
|
72
|
+
in [:patch, "/streams/unpause"] then [Controllers::Streams, :unpause]
|
|
73
|
+
in [:get, "/crons"] then [Controllers::Crons, :index]
|
|
74
|
+
in [:get, "/crons/_table"] then [Controllers::Crons, :_table]
|
|
75
|
+
in [:delete, "/crons/delete"] then [Controllers::Crons, :delete]
|
|
76
|
+
in [:post, "/crons/run"] then [Controllers::Crons, :run_now]
|
|
77
|
+
in [:get, "/metrics"] then [Controllers::Metrics, :index]
|
|
78
|
+
in [:get, "/metrics/_panel"] then [Controllers::Metrics, :_panel]
|
|
79
|
+
in [:get, "/batches"] then [Controllers::Batches, :index]
|
|
80
|
+
in [:get, "/batches/_table"] then [Controllers::Batches, :_table]
|
|
60
81
|
in [:get, "/assets/htmx.min.js.gz"] then serve("htmx.2.0.8.min.js.gz",
|
|
61
82
|
"application/javascript",
|
|
62
83
|
{ "content-encoding" => "gzip" })
|
|
63
|
-
in [:get, "/assets/app.css"]
|
|
64
|
-
in [:get, "/
|
|
84
|
+
in [:get, "/assets/app.css"] then serve("app.css", "text/css")
|
|
85
|
+
in [:get, "/assets/app.js"] then serve("app.js", "application/javascript")
|
|
86
|
+
in [:get, "/assets/logo.svg"] then serve("logo.svg", "image/svg+xml")
|
|
87
|
+
in [:get, "/assets/fonts/geist.woff2"] then serve("fonts/geist.woff2", "font/woff2", FONT_HEADERS)
|
|
88
|
+
in [:get, "/assets/fonts/geist-mono.woff2"] then serve("fonts/geist-mono.woff2", "font/woff2", FONT_HEADERS)
|
|
89
|
+
in [:get, "/favicon.ico"] then redirect_to("/assets/logo.svg", 301)
|
|
65
90
|
else not_found
|
|
66
91
|
end
|
|
67
92
|
handler(response)
|
|
68
93
|
end
|
|
69
94
|
|
|
70
|
-
|
|
95
|
+
def guarded
|
|
96
|
+
Circuit.instance.open? ? unavailable : route
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def unavailable
|
|
100
|
+
Controllers::Application.new(@request).unavailable
|
|
101
|
+
end
|
|
71
102
|
|
|
72
103
|
def handler(response)
|
|
73
104
|
if response[0].is_a?(Class)
|
|
@@ -19,7 +19,8 @@ module Cosmo
|
|
|
19
19
|
def schedule_subject: () -> ::String
|
|
20
20
|
def target_subject: () -> ::String
|
|
21
21
|
def dispatch_subject: () -> ::String
|
|
22
|
-
def
|
|
22
|
+
def schedule: () -> Hash[Symbol, ::String]
|
|
23
|
+
def headers: () -> Hash[::String, ::String]
|
|
23
24
|
def job_name: () -> ::String
|
|
24
25
|
def job_payload: () -> ::String
|
|
25
26
|
def as_json: () -> Hash[Symbol, untyped]
|
|
@@ -23,7 +23,7 @@ module Cosmo
|
|
|
23
23
|
|
|
24
24
|
def summary: (?days: Integer) -> Array[Hash[Symbol, untyped]]
|
|
25
25
|
|
|
26
|
-
def daily: (
|
|
26
|
+
def daily: (?::String? job_class, ?days: Integer) -> Array[Hash[Symbol, untyped]]
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
data/sig/cosmo/api/stats.rbs
CHANGED
|
@@ -16,6 +16,10 @@ module Cosmo
|
|
|
16
16
|
def self.scheduled: () -> ::Integer
|
|
17
17
|
|
|
18
18
|
def self.dead: () -> ::Integer
|
|
19
|
+
|
|
20
|
+
def self.queues: () -> Array[Hash[Symbol, untyped]]
|
|
21
|
+
|
|
22
|
+
def self.queue: (Stream stream, ?Hash[::String, ::Integer] busy) -> Hash[Symbol, untyped]
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|
data/sig/cosmo/api/stream.rbs
CHANGED
data/sig/cosmo/client.rbs
CHANGED
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.
|
|
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.
|
|
46
|
+
version: '2.7'
|
|
47
47
|
- - "<"
|
|
48
48
|
- !ruby/object:Gem::Version
|
|
49
|
-
version: '2.
|
|
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.
|
|
56
|
+
version: '2.7'
|
|
57
57
|
- - "<"
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
|
-
version: '2.
|
|
59
|
+
version: '2.8'
|
|
60
60
|
description: Lightweight background and stream processing for Ruby
|
|
61
61
|
email:
|
|
62
62
|
- d.vorotilin@gmail.com
|
|
@@ -131,54 +131,67 @@ files:
|
|
|
131
131
|
- lib/cosmo/utils/duration.rb
|
|
132
132
|
- lib/cosmo/utils/hash.rb
|
|
133
133
|
- lib/cosmo/utils/json.rb
|
|
134
|
-
- lib/cosmo/utils/overrides.rb
|
|
135
134
|
- lib/cosmo/utils/signal.rb
|
|
136
135
|
- lib/cosmo/utils/stopwatch.rb
|
|
137
136
|
- lib/cosmo/utils/string.rb
|
|
138
137
|
- lib/cosmo/utils/thread_pool.rb
|
|
139
138
|
- lib/cosmo/utils/ttl_cache.rb
|
|
140
|
-
- lib/cosmo/utils/warnings.rb
|
|
141
139
|
- lib/cosmo/version.rb
|
|
142
140
|
- lib/cosmo/web.rb
|
|
143
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
|
|
144
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
|
|
145
149
|
- lib/cosmo/web/chart.rb
|
|
150
|
+
- lib/cosmo/web/circuit.rb
|
|
146
151
|
- lib/cosmo/web/context.rb
|
|
147
|
-
- lib/cosmo/web/controllers/actions.rb
|
|
148
152
|
- lib/cosmo/web/controllers/application.rb
|
|
149
153
|
- lib/cosmo/web/controllers/batches.rb
|
|
150
154
|
- lib/cosmo/web/controllers/crons.rb
|
|
151
155
|
- lib/cosmo/web/controllers/jobs.rb
|
|
152
156
|
- lib/cosmo/web/controllers/metrics.rb
|
|
157
|
+
- lib/cosmo/web/controllers/overview.rb
|
|
158
|
+
- lib/cosmo/web/controllers/processes.rb
|
|
153
159
|
- lib/cosmo/web/controllers/streams.rb
|
|
154
160
|
- lib/cosmo/web/helpers/application.rb
|
|
155
161
|
- lib/cosmo/web/renderer.rb
|
|
156
|
-
- lib/cosmo/web/
|
|
162
|
+
- lib/cosmo/web/throughput.rb
|
|
157
163
|
- lib/cosmo/web/views/batches/_table.erb
|
|
158
164
|
- lib/cosmo/web/views/batches/index.erb
|
|
159
165
|
- lib/cosmo/web/views/crons/_table.erb
|
|
160
166
|
- lib/cosmo/web/views/crons/index.erb
|
|
167
|
+
- lib/cosmo/web/views/jobs/_args.erb
|
|
161
168
|
- lib/cosmo/web/views/jobs/_busy.erb
|
|
162
169
|
- lib/cosmo/web/views/jobs/_dead.erb
|
|
163
170
|
- lib/cosmo/web/views/jobs/_enqueued.erb
|
|
164
|
-
- lib/cosmo/web/views/jobs/
|
|
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
|
|
165
174
|
- lib/cosmo/web/views/jobs/_scheduled.erb
|
|
166
|
-
- lib/cosmo/web/views/jobs/_stats.erb
|
|
167
|
-
- lib/cosmo/web/views/jobs/_tabs.erb
|
|
168
|
-
- lib/cosmo/web/views/jobs/busy.erb
|
|
169
|
-
- lib/cosmo/web/views/jobs/dead.erb
|
|
170
|
-
- lib/cosmo/web/views/jobs/enqueued.erb
|
|
171
175
|
- lib/cosmo/web/views/jobs/index.erb
|
|
172
|
-
- lib/cosmo/web/views/jobs/scheduled.erb
|
|
173
176
|
- lib/cosmo/web/views/layout.erb
|
|
174
177
|
- lib/cosmo/web/views/metrics/_chart.erb
|
|
175
178
|
- lib/cosmo/web/views/metrics/_panel.erb
|
|
176
179
|
- lib/cosmo/web/views/metrics/_table.erb
|
|
177
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
|
|
178
191
|
- lib/cosmo/web/views/streams/_info.erb
|
|
192
|
+
- lib/cosmo/web/views/streams/_list.erb
|
|
179
193
|
- lib/cosmo/web/views/streams/_pause_banner.erb
|
|
180
|
-
- lib/cosmo/web/views/streams/
|
|
181
|
-
- lib/cosmo/web/views/streams/_table.erb
|
|
194
|
+
- lib/cosmo/web/views/streams/_stream.erb
|
|
182
195
|
- lib/cosmo/web/views/streams/index.erb
|
|
183
196
|
- lib/cosmo/web/views/streams/info.erb
|
|
184
197
|
- lib/cosmonats.rb
|
|
@@ -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
|
data/lib/cosmo/utils/warnings.rb
DELETED
|
@@ -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
|