postburner 1.0.0.rc.2 → 1.0.0.rc.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87f24f24d68086fe55b2fe315d36bc3ec9104a242066d6a78f5b61e2633f673d
4
- data.tar.gz: 9ed74afc5b63a477ee254e4812f1db19fd7d9c94064afa7084d5ec6d62ee7af9
3
+ metadata.gz: 9aa20d7fa2c867942efc6c70f7ad56548e651192068c9505a2854310cbb95043
4
+ data.tar.gz: 74606e227872a914c3a806227d74206d4b66a33b7160753cbbb48a15d48a7d9c
5
5
  SHA512:
6
- metadata.gz: a1a9bca8fe3cc955cb9f60c6f459cf3514bfe127806c7dda710cdd0e89822b1d398887b72d23c1f414deb5575621d829e7496f51ac6491485e9444190e70f8f6
7
- data.tar.gz: d916eaaa41debc35e5460b9360e1bb5717bc93a1b16d180d774dd2a49b3fd106ffb1a80ee0c74595eb9b42939669273e623fac460e75d5c1706b2ccab23b834f
6
+ metadata.gz: cf4238e6bc83f1447de5c846032aad268d904fb5f74c1462424c69ffed8e40ffe316c51c2d248b2a6b46b7fb96df9b572f028368cc9e8e65e4baf8bffa7175b5
7
+ data.tar.gz: c93242ccd8501c5b337417f60633474104550360276c607a48308ed6bfcbcd5bd77aeafa9df7e6f0e6cb0f15094ffe4ada75ef13f2704e98c2fd31bc674fe791
data/README.md CHANGED
@@ -85,7 +85,7 @@ bundle exec rake postburner:work WORKER=default
85
85
 
86
86
  ## Table of Contents
87
87
 
88
- - [Why](#why)
88
+ - [Why Postburner?](#why-postburner)
89
89
  - [Quick Start](#quick-start)
90
90
  - [Usage](#usage)
91
91
  - [Standard Jobs](#standard-jobs)
@@ -106,7 +106,7 @@ bundle exec rake postburner:work WORKER=default
106
106
  - [Installation](#installation)
107
107
  - [Web UI - v2 Coming Soon](#web-ui)
108
108
 
109
- ## Why
109
+ ## Why Postburner?
110
110
 
111
111
  Postburner supports Async, Queues, Delayed, Priorities, Timeouts, and Retries from the [Backend Matrix](https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). But uniquely, priorities are per job, in addition to the class level. Timeouts are per job and class level as well, and can be extended dynamically. Postburner also supports scheduling jobs at fixed intervals, cron expressions, and calendar-aware anchor points.
112
112
 
@@ -927,6 +927,53 @@ test "tracked job logs execution" do
927
927
  end
928
928
  ```
929
929
 
930
+ ### Testing Emails
931
+
932
+ Standard ActiveJob test assertions (`assert_enqueued_emails`, `assert_enqueued_jobs`) work when using the `:test` ActiveJob adapter in your test environment, which is the Rails default:
933
+
934
+ ```ruby
935
+ class EmailTest < ActiveSupport::TestCase
936
+ # assert_enqueued_emails works — Rails' :test adapter tracks the enqueue
937
+ test "welcome email is enqueued" do
938
+ assert_enqueued_emails 1 do
939
+ UserMailer.welcome(user).deliver_later
940
+ end
941
+ end
942
+
943
+ # assert_emails works — the job executes inline, delivering the email
944
+ test "welcome email is sent" do
945
+ assert_emails 1 do
946
+ SendWelcomeEmail.perform_later(user.id)
947
+ end
948
+ end
949
+ end
950
+ ```
951
+
952
+ **How this works:** `assert_enqueued_emails` and `assert_enqueued_jobs` are powered by Rails' `:test` ActiveJob adapter, which tracks enqueued jobs in an in-memory array. Postburner's queue strategies (`InlineTestQueue`, etc.) control `Postburner::Job` subclasses separately. The two systems coexist — ActiveJob assertions use the `:test` adapter, `Postburner::Job` assertions use database queries.
953
+
954
+ By default, Rails uses the `:test` adapter in test mode unless you override it. Since you set `:postburner` as your adapter in `config/application.rb`, you should set it back to `:test` for your test environment:
955
+
956
+ ```ruby
957
+ # config/environments/test.rb
958
+ config.active_job.queue_adapter = :test
959
+ ```
960
+
961
+ This is standard practice for any ActiveJob adapter (Sidekiq, SolidQueue, etc.) — the production adapter handles real execution, and the `:test` adapter enables Rails' built-in assertion helpers. Without this, `assert_enqueued_emails` will not work because Postburner's adapter executes jobs rather than tracking them in an array.
962
+
963
+ **`Postburner::Mailer`** bypasses ActiveJob entirely, so `assert_enqueued_emails` cannot see these jobs regardless of adapter. Assert on side effects instead:
964
+
965
+ ```ruby
966
+ test "mailer job sends email" do
967
+ job = Postburner::Mailer.delivery(UserMailer, :welcome).with(user_id: 1)
968
+
969
+ assert_emails 1 do
970
+ job.queue! # Executes inline in test mode, delivering the email
971
+ end
972
+
973
+ assert job.reload.processed_at
974
+ end
975
+ ```
976
+
930
977
  ### Switching Queue Strategies
931
978
 
932
979
  For tests requiring specific queue behaviors, use `switch_queue_strategy!` and `restore_queue_strategy!`:
@@ -2031,13 +2078,15 @@ Postburner uses [Beaneater](https://github.com/beanstalkd/beaneater) as the Ruby
2031
2078
 
2032
2079
  ### Connection Methods
2033
2080
 
2081
+ Postburner uses **thread-local connections** — each thread gets its own Beanstalkd socket, matching the pattern used by worker threads. This is safe under multi-threaded servers like Puma.
2082
+
2034
2083
  ```ruby
2035
- # Get a cached Beaneater connection (returns Beaneater instance)
2084
+ # Get the thread-local Beaneater connection (returns Beaneater instance)
2036
2085
  conn = Postburner.connection
2037
2086
  conn.tubes.to_a # List all tubes
2038
2087
  conn.beanstalk.stats # Server statistics
2039
2088
 
2040
- # Block form - yields connection, recommended for one-off operations
2089
+ # Block form - yields thread-local connection
2041
2090
  Postburner.connected do |conn|
2042
2091
  conn.tubes.to_a # List all tubes
2043
2092
  conn.tubes['postburner.production.critical'].stats
@@ -2045,6 +2094,19 @@ Postburner.connected do |conn|
2045
2094
  end
2046
2095
  ```
2047
2096
 
2097
+ **Shutdown cleanup:**
2098
+
2099
+ On process shutdown, call `disconnect_all!` to close every thread's connection cleanly:
2100
+
2101
+ ```ruby
2102
+ # config/puma.rb
2103
+ on_worker_shutdown do
2104
+ Postburner.disconnect_all!
2105
+ end
2106
+ ```
2107
+
2108
+ This is optional — Ruby closes sockets on process exit — but gives you clean logs and explicit teardown.
2109
+
2048
2110
  ### Job-Level Access
2049
2111
 
2050
2112
  ```ruby
@@ -2248,7 +2310,7 @@ beanstalkd -l 127.0.0.1 -p 11300 -b /var/lib/beanstalkd
2248
2310
 
2249
2311
  ```ruby
2250
2312
  # Gemfile
2251
- gem 'postburner', '~> 1.0.0.pre.18'
2313
+ gem 'postburner', '~> 1.0.0.rc.2
2252
2314
  ```
2253
2315
 
2254
2316
  ```bash
@@ -50,6 +50,20 @@ module Postburner
50
50
  # @example Queue to specific queue
51
51
  # Postburner::Mailer.delivery(UserMailer, :welcome).with(user_id: 1).queue!(queue: 'bulk_mailers')
52
52
  #
53
+ # == Testing
54
+ #
55
+ # Because Postburner::Mailer bypasses ActiveJob, Rails' +assert_enqueued_emails+
56
+ # will not detect these jobs. Use +assert_emails+ (which checks actual deliveries)
57
+ # or assert on the job record:
58
+ #
59
+ # # Assert email was delivered (inline in test mode)
60
+ # assert_emails 1 do
61
+ # Postburner::Mailer.delivery(UserMailer, :welcome).with(user_id: 1).queue!
62
+ # end
63
+ #
64
+ # # Assert job was processed
65
+ # assert job.reload.processed_at
66
+ #
53
67
  # @see Postburner::Job Base class for tracked jobs
54
68
  # @see Postburner::Configuration#default_mailer_queue Configuration option
55
69
  class Mailer < Job
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+
1
3
  class Postburner::InstallGenerator < Rails::Generators::Base
2
4
  source_root File.expand_path('templates', __dir__)
3
5
  include Rails::Generators::Migration
@@ -28,11 +30,15 @@ class Postburner::InstallGenerator < Rails::Generators::Base
28
30
  _max = timestamps.max || timestamp[-2..-1].to_i
29
31
  _next = _max + 1
30
32
  raise "MISSING NEXT" if _next.blank?
31
- migration_number = "#{stem}#{_next}"
32
- if migration_number.length != 14
33
- raise "INCORRECT LENGTH stem=#{stem} _next=#{_next} _max=#{_max}"
33
+
34
+ # When seconds overflow past 99, roll forward by 1 minute and reset.
35
+ if _next > 99
36
+ rolled = Time.parse("#{stem}00 UTC") + 60
37
+ stem = rolled.strftime('%Y%m%d%H%M')
38
+ _next = 0
34
39
  end
35
- migration_number
40
+
41
+ "#{stem}#{_next.to_s.rjust(2, '0')}"
36
42
  end
37
43
 
38
44
  private
@@ -1,3 +1,3 @@
1
1
  module Postburner
2
- VERSION = '1.0.0.rc.2'
2
+ VERSION = '1.0.0.rc.3'
3
3
  end
data/lib/postburner.rb CHANGED
@@ -319,31 +319,31 @@ module Postburner
319
319
  # conn = Postburner.connection
320
320
  # conn.tubes['my.tube'].stats
321
321
  #
322
- # @note Connection is cached in @_connection instance variable
322
+ # @note Connection is cached in Thread.current[:postburner_connection] (thread-local)
323
323
  # @note Automatically reconnects if connection is not active
324
324
  #
325
325
  # @see #connected
326
326
  #
327
327
  def self.connection
328
- @_connection ||= Postburner::Connection.new
329
- @_connection.reconnect! unless @_connection.connected?
330
- @_connection
328
+ Thread.current[:postburner_connection] ||= Postburner::Connection.new
329
+ conn = Thread.current[:postburner_connection]
330
+ conn.reconnect! unless conn.connected?
331
+ conn
331
332
  end
332
333
 
333
334
  # Yields a Beanstalkd connection or returns cached connection.
334
335
  #
335
- # When called with a block, yields the connection and ensures it's closed
336
- # after the block completes. When called without a block, returns the
337
- # cached connection.
336
+ # When called with a block, yields the thread-local connection.
337
+ # When called without a block, returns the thread-local connection.
338
338
  #
339
339
  # @overload connected
340
340
  # Returns the cached Beanstalkd connection.
341
341
  # @return [Postburner::Connection] Beanstalkd connection
342
342
  #
343
343
  # @overload connected {|conn| ... }
344
- # Yields connection and ensures cleanup.
344
+ # Yields connection for the duration of the block.
345
345
  # @yieldparam conn [Postburner::Connection] Beanstalkd connection
346
- # @return [void]
346
+ # @return [Object] return value of the block
347
347
  #
348
348
  # @example With block (recommended)
349
349
  # Postburner.connected do |conn|
@@ -351,7 +351,6 @@ module Postburner
351
351
  # puts tube.name
352
352
  # end
353
353
  # end
354
- # # Connection automatically closed
355
354
  #
356
355
  # @example Without block
357
356
  # conn = Postburner.connected
@@ -364,19 +363,44 @@ module Postburner
364
363
  # end
365
364
  #
366
365
  # @see #connection
366
+ # @see #disconnect_all!
367
367
  #
368
368
  def self.connected
369
369
  if block_given?
370
- begin
371
- yield connection
372
- ensure
373
- connection.close if connection
374
- end
370
+ yield connection
375
371
  else
376
372
  connection
377
373
  end
378
374
  end
379
375
 
376
+ # Closes and clears Beanstalkd connections on all known threads.
377
+ #
378
+ # Call this on worker shutdown to release sockets cleanly. Because connections
379
+ # are thread-local, a single `connection.close` call from the main thread would
380
+ # only close that thread's socket. This method iterates every live thread and
381
+ # closes each thread's connection, if any.
382
+ #
383
+ # @return [void]
384
+ #
385
+ # @example Puma worker shutdown (config/puma.rb)
386
+ # on_worker_shutdown do
387
+ # Postburner.disconnect_all!
388
+ # end
389
+ #
390
+ def self.disconnect_all!
391
+ Thread.list.each do |thread|
392
+ if (conn = thread[:postburner_connection])
393
+ begin
394
+ conn.close if conn.respond_to?(:close) && conn.connected?
395
+ rescue => e
396
+ warn "Postburner: failed to close connection on shutdown: #{e.message}" if $VERBOSE
397
+ ensure
398
+ thread[:postburner_connection] = nil
399
+ end
400
+ end
401
+ end
402
+ end
403
+
380
404
  # Clears jobs from specified tubes or shows stats for all tubes.
381
405
  #
382
406
  # High-level method with formatted output. Delegates to Connection#clear_tubes!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postburner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc.2
4
+ version: 1.0.0.rc.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Smith