cogworker 0.1.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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/exe/cogworker +6 -0
  3. data/exe/cogworkerswarm +6 -0
  4. data/lib/cogworker/basic_fetch.rb +27 -0
  5. data/lib/cogworker/cli.rb +64 -0
  6. data/lib/cogworker/client.rb +61 -0
  7. data/lib/cogworker/component.rb +19 -0
  8. data/lib/cogworker/config.rb +84 -0
  9. data/lib/cogworker/config_loader.rb +22 -0
  10. data/lib/cogworker/heartbeat.rb +150 -0
  11. data/lib/cogworker/history/middleware.rb +18 -0
  12. data/lib/cogworker/history/storage.rb +81 -0
  13. data/lib/cogworker/history.rb +33 -0
  14. data/lib/cogworker/job.rb +75 -0
  15. data/lib/cogworker/job_record.rb +24 -0
  16. data/lib/cogworker/job_util.rb +60 -0
  17. data/lib/cogworker/launcher.rb +93 -0
  18. data/lib/cogworker/logging.rb +18 -0
  19. data/lib/cogworker/manager.rb +71 -0
  20. data/lib/cogworker/middleware/chain.rb +64 -0
  21. data/lib/cogworker/periodic/claim.lua +27 -0
  22. data/lib/cogworker/periodic/entry.rb +24 -0
  23. data/lib/cogworker/periodic/manager.rb +28 -0
  24. data/lib/cogworker/periodic/release_middleware.rb +27 -0
  25. data/lib/cogworker/periodic/ticker.rb +123 -0
  26. data/lib/cogworker/process.rb +36 -0
  27. data/lib/cogworker/process_set.rb +29 -0
  28. data/lib/cogworker/processor.rb +129 -0
  29. data/lib/cogworker/prometheus/exporter.rb +62 -0
  30. data/lib/cogworker/queue.rb +60 -0
  31. data/lib/cogworker/redis_connection.rb +37 -0
  32. data/lib/cogworker/redis_keys.rb +32 -0
  33. data/lib/cogworker/scheduled.rb +67 -0
  34. data/lib/cogworker/signals.rb +15 -0
  35. data/lib/cogworker/stats.rb +47 -0
  36. data/lib/cogworker/status/client_middleware.rb +19 -0
  37. data/lib/cogworker/status/server_middleware.rb +31 -0
  38. data/lib/cogworker/status/storage.rb +30 -0
  39. data/lib/cogworker/status/worker.rb +27 -0
  40. data/lib/cogworker/status.rb +40 -0
  41. data/lib/cogworker/swarm.rb +169 -0
  42. data/lib/cogworker/testing.rb +109 -0
  43. data/lib/cogworker/unique_jobs/client_middleware.rb +31 -0
  44. data/lib/cogworker/unique_jobs/release_middleware.rb +30 -0
  45. data/lib/cogworker/unique_jobs.rb +32 -0
  46. data/lib/cogworker/version.rb +5 -0
  47. data/lib/cogworker/web/action.rb +63 -0
  48. data/lib/cogworker/web/application.rb +62 -0
  49. data/lib/cogworker/web/assets/ag-grid/ag-grid-community.min.js +1 -0
  50. data/lib/cogworker/web/assets/ag-grid/ag-grid.min.css +7 -0
  51. data/lib/cogworker/web/assets/ag-grid/ag-theme-alpine.min.css +2 -0
  52. data/lib/cogworker/web/assets/chart.umd.min.js +13 -0
  53. data/lib/cogworker/web/assets/htmx.min.js +1 -0
  54. data/lib/cogworker/web/assets/tailwind.css +1 -0
  55. data/lib/cogworker/web/layout.rb +352 -0
  56. data/lib/cogworker/web/router.rb +27 -0
  57. data/lib/cogworker/web/routes/busy.rb +99 -0
  58. data/lib/cogworker/web/routes/dead.rb +93 -0
  59. data/lib/cogworker/web/routes/history.rb +226 -0
  60. data/lib/cogworker/web/routes/periodic.rb +67 -0
  61. data/lib/cogworker/web/routes/queues.rb +101 -0
  62. data/lib/cogworker/web/routes/retries.rb +89 -0
  63. data/lib/cogworker/web/routes/save_session.rb +21 -0
  64. data/lib/cogworker/web/routes/scheduled.rb +49 -0
  65. data/lib/cogworker/web/routes/stats.rb +298 -0
  66. data/lib/cogworker/web/views.rb +25 -0
  67. data/lib/cogworker/web.rb +257 -0
  68. data/lib/cogworker/work.rb +19 -0
  69. data/lib/cogworker/work_set.rb +23 -0
  70. data/lib/cogworker/worker.rb +5 -0
  71. data/lib/cogworker/workers.rb +8 -0
  72. data/lib/cogworker.rb +114 -0
  73. metadata +300 -0
data/lib/cogworker.rb ADDED
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'securerandom'
5
+ require 'zeitwerk'
6
+
7
+ # Redis-backed background job processing: worker DSL, periodic (cron)
8
+ # scheduling, process introspection, a Web UI, and a Prometheus exporter.
9
+ module Cogworker
10
+ # `LOADER` autoloads everything under lib/cogworker/** — no internal
11
+ # `require_relative` between this gem's own files anywhere below; just
12
+ # reference the constant, Zeitwerk resolves file <-> constant by
13
+ # directory structure (lib/cogworker/foo/bar.rb <-> Cogworker::Foo::Bar).
14
+ # Two inflections don't follow the default snake_case -> CamelCase rule:
15
+ # `version.rb` defines the constant `VERSION` (conventional for gems, not
16
+ # `Version`), and `cli.rb` defines `CLI` (an acronym, not `Cli`).
17
+ LOADER = Zeitwerk::Loader.for_gem
18
+ LOADER.inflector.inflect('version' => 'VERSION', 'cli' => 'CLI')
19
+
20
+ # Reloading is opt-in and, by design, only ever triggered from
21
+ # `Cogworker::Web.call` (see web.rb) — never from a job-processing
22
+ # Processor thread. Redefining a job class's constant while another
23
+ # thread is mid-`perform` on an instance of it is exactly the kind of
24
+ # hazard Rails-style reloading is unsafe for in a worker process, which
25
+ # is why this must be enabled (if at all) only for a Web UI process, via
26
+ # `COGWORKER_RELOAD=true` — never unconditionally, never in `exe/cogworker`
27
+ # or `exe/cogworkerswarm`'s own boot path.
28
+ LOADER.enable_reloading if ENV['COGWORKER_RELOAD'] == 'true'
29
+ LOADER.setup
30
+
31
+ class << self
32
+ def logger
33
+ @logger ||= Logging.default_logger
34
+ end
35
+
36
+ attr_writer :logger
37
+
38
+ def config
39
+ @config ||= Config.new
40
+ end
41
+
42
+ # Skipping the block when `server?` is false is by design for a plain
43
+ # Web-only process (see `server?` below) — not necessarily a mistake, so
44
+ # this only logs at `debug` (silent under the default `info` level; bump
45
+ # `Cogworker.logger.level = Logger::DEBUG` while integrating to see it).
46
+ # It's the same skip either way whether that's intentional or the app's
47
+ # init file just happened to load before the CLI/swarm boot path called
48
+ # `server_process!` — this gem can't tell those apart, so it can only
49
+ # ever hint, not warn outright.
50
+ def configure_server
51
+ if server?
52
+ yield config
53
+ else
54
+ logger.debug do
55
+ 'Cogworker.configure_server skipped: Cogworker.server? is false right now — either ' \
56
+ 'intentional (a Web-only process), or configure_server ran before ' \
57
+ 'Cogworker.server_process! was set (check init/boot ordering).'
58
+ end
59
+ end
60
+ end
61
+
62
+ def configure_client
63
+ if server?
64
+ logger.debug do
65
+ 'Cogworker.configure_client skipped: Cogworker.server? is true right now — either ' \
66
+ 'intentional (a worker-only process with no client-side pushes), or configure_client ' \
67
+ 'ran after Cogworker.server_process! was already set (check init/boot ordering).'
68
+ end
69
+ else
70
+ yield config
71
+ end
72
+ end
73
+
74
+ # Whether this process is a Cogworker worker process (set by the CLI/swarm
75
+ # boot sequence before requiring app code). A plain web process loading
76
+ # the same initializer file should skip `configure_server` blocks (no
77
+ # Redis pool / middleware chain needed there) — this flag is what makes
78
+ # that possible without changing the initializer.
79
+ def server?
80
+ !!@server_process
81
+ end
82
+
83
+ def server_process!
84
+ @server_process = true
85
+ end
86
+
87
+ # No-op: exists so calling code doesn't need to change, but this gem
88
+ # simply never coerces job args, so there is nothing to toggle.
89
+ def strict_args!(_value = true)
90
+ true
91
+ end
92
+
93
+ # Recomputed fresh in every cogworkerswarm child (see Cogworker::Swarm) —
94
+ # never memoize this before a fork, or every child collapses onto the
95
+ # parent's identity in the heartbeat/ProcessSet/WorkSet Redis keys.
96
+ def identity
97
+ @identity ||= compute_identity
98
+ end
99
+
100
+ def reset_identity!
101
+ @identity = compute_identity
102
+ end
103
+
104
+ def hostname
105
+ ENV['DYNO'] || Socket.gethostname
106
+ end
107
+
108
+ private
109
+
110
+ def compute_identity
111
+ "#{hostname}:#{::Process.pid}:#{SecureRandom.hex(6)}"
112
+ end
113
+ end
114
+ end
metadata ADDED
@@ -0,0 +1,300 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cogworker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - vdonec
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: connection_pool
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.4'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.4'
26
+ - !ruby/object:Gem::Dependency
27
+ name: fugit
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.9'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.9'
40
+ - !ruby/object:Gem::Dependency
41
+ name: logger
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.6'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rack
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '2.2'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '2.2'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rack-session
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: redis
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '4.8'
89
+ - - "<"
90
+ - !ruby/object:Gem::Version
91
+ version: '6'
92
+ type: :runtime
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '4.8'
99
+ - - "<"
100
+ - !ruby/object:Gem::Version
101
+ version: '6'
102
+ - !ruby/object:Gem::Dependency
103
+ name: zeitwerk
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '2.6'
109
+ type: :runtime
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '2.6'
116
+ - !ruby/object:Gem::Dependency
117
+ name: capybara
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '3.40'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '3.40'
130
+ - !ruby/object:Gem::Dependency
131
+ name: cuprite
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '0.15'
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '0.15'
144
+ - !ruby/object:Gem::Dependency
145
+ name: puma
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '6.4'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '6.4'
158
+ - !ruby/object:Gem::Dependency
159
+ name: rackup
160
+ requirement: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "~>"
163
+ - !ruby/object:Gem::Version
164
+ version: '2.1'
165
+ type: :development
166
+ prerelease: false
167
+ version_requirements: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '2.1'
172
+ - !ruby/object:Gem::Dependency
173
+ name: rspec
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '3.13'
179
+ type: :development
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '3.13'
186
+ - !ruby/object:Gem::Dependency
187
+ name: rubocop
188
+ requirement: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - "~>"
191
+ - !ruby/object:Gem::Version
192
+ version: '1.65'
193
+ type: :development
194
+ prerelease: false
195
+ version_requirements: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - "~>"
198
+ - !ruby/object:Gem::Version
199
+ version: '1.65'
200
+ executables:
201
+ - cogworker
202
+ - cogworkerswarm
203
+ extensions: []
204
+ extra_rdoc_files: []
205
+ files:
206
+ - exe/cogworker
207
+ - exe/cogworkerswarm
208
+ - lib/cogworker.rb
209
+ - lib/cogworker/basic_fetch.rb
210
+ - lib/cogworker/cli.rb
211
+ - lib/cogworker/client.rb
212
+ - lib/cogworker/component.rb
213
+ - lib/cogworker/config.rb
214
+ - lib/cogworker/config_loader.rb
215
+ - lib/cogworker/heartbeat.rb
216
+ - lib/cogworker/history.rb
217
+ - lib/cogworker/history/middleware.rb
218
+ - lib/cogworker/history/storage.rb
219
+ - lib/cogworker/job.rb
220
+ - lib/cogworker/job_record.rb
221
+ - lib/cogworker/job_util.rb
222
+ - lib/cogworker/launcher.rb
223
+ - lib/cogworker/logging.rb
224
+ - lib/cogworker/manager.rb
225
+ - lib/cogworker/middleware/chain.rb
226
+ - lib/cogworker/periodic/claim.lua
227
+ - lib/cogworker/periodic/entry.rb
228
+ - lib/cogworker/periodic/manager.rb
229
+ - lib/cogworker/periodic/release_middleware.rb
230
+ - lib/cogworker/periodic/ticker.rb
231
+ - lib/cogworker/process.rb
232
+ - lib/cogworker/process_set.rb
233
+ - lib/cogworker/processor.rb
234
+ - lib/cogworker/prometheus/exporter.rb
235
+ - lib/cogworker/queue.rb
236
+ - lib/cogworker/redis_connection.rb
237
+ - lib/cogworker/redis_keys.rb
238
+ - lib/cogworker/scheduled.rb
239
+ - lib/cogworker/signals.rb
240
+ - lib/cogworker/stats.rb
241
+ - lib/cogworker/status.rb
242
+ - lib/cogworker/status/client_middleware.rb
243
+ - lib/cogworker/status/server_middleware.rb
244
+ - lib/cogworker/status/storage.rb
245
+ - lib/cogworker/status/worker.rb
246
+ - lib/cogworker/swarm.rb
247
+ - lib/cogworker/testing.rb
248
+ - lib/cogworker/unique_jobs.rb
249
+ - lib/cogworker/unique_jobs/client_middleware.rb
250
+ - lib/cogworker/unique_jobs/release_middleware.rb
251
+ - lib/cogworker/version.rb
252
+ - lib/cogworker/web.rb
253
+ - lib/cogworker/web/action.rb
254
+ - lib/cogworker/web/application.rb
255
+ - lib/cogworker/web/assets/ag-grid/ag-grid-community.min.js
256
+ - lib/cogworker/web/assets/ag-grid/ag-grid.min.css
257
+ - lib/cogworker/web/assets/ag-grid/ag-theme-alpine.min.css
258
+ - lib/cogworker/web/assets/chart.umd.min.js
259
+ - lib/cogworker/web/assets/htmx.min.js
260
+ - lib/cogworker/web/assets/tailwind.css
261
+ - lib/cogworker/web/layout.rb
262
+ - lib/cogworker/web/router.rb
263
+ - lib/cogworker/web/routes/busy.rb
264
+ - lib/cogworker/web/routes/dead.rb
265
+ - lib/cogworker/web/routes/history.rb
266
+ - lib/cogworker/web/routes/periodic.rb
267
+ - lib/cogworker/web/routes/queues.rb
268
+ - lib/cogworker/web/routes/retries.rb
269
+ - lib/cogworker/web/routes/save_session.rb
270
+ - lib/cogworker/web/routes/scheduled.rb
271
+ - lib/cogworker/web/routes/stats.rb
272
+ - lib/cogworker/web/views.rb
273
+ - lib/cogworker/work.rb
274
+ - lib/cogworker/work_set.rb
275
+ - lib/cogworker/worker.rb
276
+ - lib/cogworker/workers.rb
277
+ homepage: https://github.com/vdonec/cogworker
278
+ licenses:
279
+ - MIT
280
+ metadata:
281
+ source_code_uri: https://github.com/vdonec/cogworker
282
+ rdoc_options: []
283
+ require_paths:
284
+ - lib
285
+ required_ruby_version: !ruby/object:Gem::Requirement
286
+ requirements:
287
+ - - ">="
288
+ - !ruby/object:Gem::Version
289
+ version: '3.1'
290
+ required_rubygems_version: !ruby/object:Gem::Requirement
291
+ requirements:
292
+ - - ">="
293
+ - !ruby/object:Gem::Version
294
+ version: '0'
295
+ requirements: []
296
+ rubygems_version: 3.6.9
297
+ specification_version: 4
298
+ summary: 'Redis-backed background job processing: worker DSL, periodic scheduling,
299
+ and process introspection'
300
+ test_files: []