pgbus 0.5.0 → 0.6.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +238 -0
  4. data/Rakefile +8 -1
  5. data/app/controllers/pgbus/insights_controller.rb +6 -0
  6. data/app/helpers/pgbus/streams_helper.rb +115 -0
  7. data/app/javascript/pgbus/stream_source_element.js +212 -0
  8. data/app/models/pgbus/stream_stat.rb +118 -0
  9. data/app/views/pgbus/insights/show.html.erb +59 -0
  10. data/config/locales/en.yml +16 -0
  11. data/config/routes.rb +11 -0
  12. data/lib/generators/pgbus/add_presence_generator.rb +55 -0
  13. data/lib/generators/pgbus/add_stream_stats_generator.rb +54 -0
  14. data/lib/generators/pgbus/templates/add_presence.rb.erb +26 -0
  15. data/lib/generators/pgbus/templates/add_stream_stats.rb.erb +18 -0
  16. data/lib/pgbus/client/ensure_stream_queue.rb +54 -0
  17. data/lib/pgbus/client/read_after.rb +100 -0
  18. data/lib/pgbus/client.rb +6 -0
  19. data/lib/pgbus/configuration/capsule_dsl.rb +6 -20
  20. data/lib/pgbus/configuration.rb +126 -14
  21. data/lib/pgbus/engine.rb +31 -0
  22. data/lib/pgbus/process/dispatcher.rb +62 -4
  23. data/lib/pgbus/streams/cursor.rb +71 -0
  24. data/lib/pgbus/streams/envelope.rb +58 -0
  25. data/lib/pgbus/streams/filters.rb +98 -0
  26. data/lib/pgbus/streams/presence.rb +216 -0
  27. data/lib/pgbus/streams/signed_name.rb +69 -0
  28. data/lib/pgbus/streams/turbo_broadcastable.rb +53 -0
  29. data/lib/pgbus/streams/watermark_cache_middleware.rb +28 -0
  30. data/lib/pgbus/streams.rb +151 -0
  31. data/lib/pgbus/version.rb +1 -1
  32. data/lib/pgbus/web/data_source.rb +29 -0
  33. data/lib/pgbus/web/stream_app.rb +179 -0
  34. data/lib/pgbus/web/streamer/connection.rb +122 -0
  35. data/lib/pgbus/web/streamer/dispatcher.rb +467 -0
  36. data/lib/pgbus/web/streamer/heartbeat.rb +105 -0
  37. data/lib/pgbus/web/streamer/instance.rb +176 -0
  38. data/lib/pgbus/web/streamer/io_writer.rb +73 -0
  39. data/lib/pgbus/web/streamer/listener.rb +228 -0
  40. data/lib/pgbus/web/streamer/registry.rb +103 -0
  41. data/lib/pgbus/web/streamer.rb +53 -0
  42. data/lib/pgbus.rb +28 -0
  43. data/lib/puma/plugin/pgbus_streams.rb +54 -0
  44. data/lib/tasks/pgbus_streams.rake +52 -0
  45. metadata +29 -1
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "puma/plugin"
4
+
5
+ # Puma plugin that tears down the pgbus Streamer cleanly on worker
6
+ # shutdown (SIGTERM, SIGUSR2 phased restart, SIGINT). Without this
7
+ # hook, Puma closes hijacked SSE sockets abruptly during shutdown,
8
+ # which looks to the browser like a network error and causes an
9
+ # immediate reconnect attempt mid-deploy. With the hook, the Streamer
10
+ # writes a `pgbus:shutdown` sentinel event to each connection and
11
+ # closes them cleanly, and clients reconnect to the new worker via
12
+ # EventSource's built-in Last-Event-ID mechanism — picking up any
13
+ # messages that landed during the flip via the PGMQ archive replay path.
14
+ #
15
+ # Users opt in by adding `plugin :pgbus_streams` to their puma.rb:
16
+ #
17
+ # # config/puma.rb
18
+ # plugin :pgbus_streams
19
+ #
20
+ # Auto-registering at gem load time would be too magical and would
21
+ # break users who aren't running Puma (e.g. the gem also ships a CLI
22
+ # and a non-Rails use case). Explicit opt-in is safer.
23
+ Puma::Plugin.create do
24
+ def start(launcher)
25
+ launcher.events.register(:after_stopped) do
26
+ teardown_streamer(launcher)
27
+ end
28
+
29
+ launcher.events.register(:before_restart) do
30
+ teardown_streamer(launcher)
31
+ end
32
+ end
33
+
34
+ def teardown_streamer(launcher)
35
+ return unless defined?(Pgbus::Web::Streamer)
36
+
37
+ # Go through the public API so `@current_mutex` guards both the
38
+ # read and the clear. Bypassing it with instance_variable_get/set
39
+ # would race with any thread that's currently inside
40
+ # `Streamer.current` building a new Instance.
41
+ Pgbus::Web::Streamer.reset!
42
+ rescue StandardError => e
43
+ log_error(launcher, e)
44
+ end
45
+
46
+ def log_error(launcher, error)
47
+ message = "[Pgbus::Puma::Plugin] streamer teardown raised: #{error.class}: #{error.message}"
48
+ if launcher.respond_to?(:log_writer)
49
+ launcher.log_writer.log(message)
50
+ elsif defined?(Pgbus) && Pgbus.respond_to?(:logger)
51
+ Pgbus.logger.warn { message }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :pgbus do
4
+ namespace :streams do
5
+ desc "Fail if any pgbus controller or web component includes ActionController::Live"
6
+ task :lint_no_live do
7
+ # ActionController::Live has well-documented interactions with Puma
8
+ # that tie up worker threads for the lifetime of a streaming response
9
+ # (puma/puma#1009, puma/puma#938, puma/puma#569, rails/rails#10948).
10
+ # The pgbus streams subsystem is built on rack.hijack specifically to
11
+ # avoid this class of bug — hijack releases the worker thread as long
12
+ # as the Rack code returns promptly after hijacking. Any accidental
13
+ # reintroduction of ActionController::Live into the pgbus web layer
14
+ # would regress the architecture invisibly, so we guard against it
15
+ # in CI.
16
+ # The host app's Pgbus-namespaced controllers live under
17
+ # Rails.root/app/controllers/pgbus. The gem's own streaming
18
+ # code lives under gem_root/lib/pgbus/web. __dir__ here is
19
+ # gem_root/lib/tasks, which is why the app path needs
20
+ # Rails.root (or Dir.pwd when this task runs outside a Rails
21
+ # context) — resolving `app/controllers/pgbus` from __dir__
22
+ # would silently lint the gem itself and miss every consumer
23
+ # app's overrides.
24
+ app_root = defined?(Rails) && Rails.respond_to?(:root) ? Rails.root.to_s : Dir.pwd
25
+ roots = [
26
+ File.expand_path("app/controllers/pgbus", app_root),
27
+ File.expand_path("../pgbus/web", __dir__)
28
+ ].select { |p| File.directory?(p) }
29
+
30
+ offenders = []
31
+ roots.each do |root|
32
+ Dir.glob("#{root}/**/*.rb").each do |path|
33
+ content = File.read(path)
34
+ offenders << path if content.match?(/^\s*include\s+ActionController::Live\b/)
35
+ end
36
+ end
37
+
38
+ if offenders.any?
39
+ warn "\e[31m✗ ActionController::Live found in pgbus web code:\e[0m"
40
+ offenders.each { |p| warn " #{p}" }
41
+ warn ""
42
+ warn " Rationale: the pgbus streams subsystem uses rack.hijack to"
43
+ warn " avoid Puma thread pinning (puma/puma#1009). Including"
44
+ warn " ActionController::Live reintroduces the bug it was designed"
45
+ warn " to avoid. Use Pgbus::Web::StreamApp for SSE endpoints."
46
+ abort
47
+ end
48
+
49
+ puts "\e[32m✓ pgbus web code is free of ActionController::Live\e[0m"
50
+ end
51
+ end
52
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -143,6 +143,8 @@ files:
143
143
  - app/frontend/pgbus/vendor/apexcharts.js
144
144
  - app/frontend/pgbus/vendor/turbo.js
145
145
  - app/helpers/pgbus/application_helper.rb
146
+ - app/helpers/pgbus/streams_helper.rb
147
+ - app/javascript/pgbus/stream_source_element.js
146
148
  - app/models/pgbus/application_record.rb
147
149
  - app/models/pgbus/batch_entry.rb
148
150
  - app/models/pgbus/blocked_execution.rb
@@ -155,6 +157,7 @@ files:
155
157
  - app/models/pgbus/recurring_execution.rb
156
158
  - app/models/pgbus/recurring_task.rb
157
159
  - app/models/pgbus/semaphore.rb
160
+ - app/models/pgbus/stream_stat.rb
158
161
  - app/models/pgbus/uniqueness_key.rb
159
162
  - app/views/layouts/pgbus/application.html.erb
160
163
  - app/views/pgbus/dashboard/_processes_table.html.erb
@@ -203,8 +206,10 @@ files:
203
206
  - lib/generators/pgbus/add_job_stats_generator.rb
204
207
  - lib/generators/pgbus/add_job_stats_latency_generator.rb
205
208
  - lib/generators/pgbus/add_outbox_generator.rb
209
+ - lib/generators/pgbus/add_presence_generator.rb
206
210
  - lib/generators/pgbus/add_queue_states_generator.rb
207
211
  - lib/generators/pgbus/add_recurring_generator.rb
212
+ - lib/generators/pgbus/add_stream_stats_generator.rb
208
213
  - lib/generators/pgbus/install_generator.rb
209
214
  - lib/generators/pgbus/migrate_job_locks_generator.rb
210
215
  - lib/generators/pgbus/templates/add_failed_events_unique_index.rb.erb
@@ -212,8 +217,10 @@ files:
212
217
  - lib/generators/pgbus/templates/add_job_stats.rb.erb
213
218
  - lib/generators/pgbus/templates/add_job_stats_latency.rb.erb
214
219
  - lib/generators/pgbus/templates/add_outbox.rb.erb
220
+ - lib/generators/pgbus/templates/add_presence.rb.erb
215
221
  - lib/generators/pgbus/templates/add_queue_states.rb.erb
216
222
  - lib/generators/pgbus/templates/add_recurring_tables.rb.erb
223
+ - lib/generators/pgbus/templates/add_stream_stats.rb.erb
217
224
  - lib/generators/pgbus/templates/add_uniqueness_keys.rb.erb
218
225
  - lib/generators/pgbus/templates/migrate_job_locks_to_uniqueness_keys.rb.erb
219
226
  - lib/generators/pgbus/templates/migration.rb.erb
@@ -231,6 +238,8 @@ files:
231
238
  - lib/pgbus/circuit_breaker.rb
232
239
  - lib/pgbus/cli.rb
233
240
  - lib/pgbus/client.rb
241
+ - lib/pgbus/client/ensure_stream_queue.rb
242
+ - lib/pgbus/client/read_after.rb
234
243
  - lib/pgbus/concurrency.rb
235
244
  - lib/pgbus/concurrency/blocked_execution.rb
236
245
  - lib/pgbus/concurrency/semaphore.rb
@@ -272,11 +281,30 @@ files:
272
281
  - lib/pgbus/recurring/task.rb
273
282
  - lib/pgbus/serializer.rb
274
283
  - lib/pgbus/stat_buffer.rb
284
+ - lib/pgbus/streams.rb
285
+ - lib/pgbus/streams/cursor.rb
286
+ - lib/pgbus/streams/envelope.rb
287
+ - lib/pgbus/streams/filters.rb
288
+ - lib/pgbus/streams/presence.rb
289
+ - lib/pgbus/streams/signed_name.rb
290
+ - lib/pgbus/streams/turbo_broadcastable.rb
291
+ - lib/pgbus/streams/watermark_cache_middleware.rb
275
292
  - lib/pgbus/uniqueness.rb
276
293
  - lib/pgbus/version.rb
277
294
  - lib/pgbus/web/authentication.rb
278
295
  - lib/pgbus/web/data_source.rb
296
+ - lib/pgbus/web/stream_app.rb
297
+ - lib/pgbus/web/streamer.rb
298
+ - lib/pgbus/web/streamer/connection.rb
299
+ - lib/pgbus/web/streamer/dispatcher.rb
300
+ - lib/pgbus/web/streamer/heartbeat.rb
301
+ - lib/pgbus/web/streamer/instance.rb
302
+ - lib/pgbus/web/streamer/io_writer.rb
303
+ - lib/pgbus/web/streamer/listener.rb
304
+ - lib/pgbus/web/streamer/registry.rb
305
+ - lib/puma/plugin/pgbus_streams.rb
279
306
  - lib/tasks/pgbus_pgmq.rake
307
+ - lib/tasks/pgbus_streams.rake
280
308
  homepage: https://github.com/mhenrixon/pgbus
281
309
  licenses:
282
310
  - MIT