postburner 0.4.0 → 0.6.2

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: 6faa3078340ee2bc8d90e931ee1db203114e32ff69b288ec69340e8b86a987cd
4
- data.tar.gz: 0a2ddf78a320e9b01c26a0e3fbd3158b78d37a89bca04a5fdd8c64b70eada1a7
3
+ metadata.gz: 6d47898f4be7a93d1643ba6ff32db238b341a2ceed09bbc18de2774d3048d9cd
4
+ data.tar.gz: 934f5a102f6644e679c2bf30d4708e171f01d88b5652fe6bcef2cac8b6fe4c50
5
5
  SHA512:
6
- metadata.gz: 5253309eb7aff502623040d49e923faff7c91df6aac407ec7a4e306a8317b1363db43a4b0408eb20fbdad6ee017ad68489480ac50d7e2ac68a612a1337488457
7
- data.tar.gz: 49d0253fe3ecde9cfd4de8b48a2ce95997245bea4310f7c751ccca3685fea8a42b29310a8058f222031ee8aa91f6ec61f990ec5c330157e39853c95830236cfa
6
+ metadata.gz: cb29ebe178b4675552a441c335fd95867a9a26c0a15235c42d5dff0f8ab72c40c417c728bc171ecf461ef3e78c9672364b89fae3b91419dceac59f80ad4c9986
7
+ data.tar.gz: 807cb26488522a5a3becdc53fbc43b44a9123d1355ee31d2b79dc2d1d479ce714a6b3a5f20c60b74e2f2fc22753736e6fde9822fe59e83feab69024e34e60deb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.6.2 - 2021-11-01
4
+
5
+ ### Fixed
6
+ - add #requeue method.
7
+ - check if AlreadyProcessed.
8
+
9
+ ## v0.6.1 - 2021-11-01
10
+
11
+ ### Updated
12
+ - update run_at logic to support only inserting after save commit.
13
+
14
+ ## v0.6.0 - 2021-11-01
15
+
16
+ ### Added
17
+ - update exception handling to better show where the error was raised.
18
+ - update logging to match errata format.
19
+ - update documentation.
20
+
21
+ ## v0.5.0 - 2021-10-27
22
+
23
+ ### Added
24
+ - add mailer, action, params accessors to Mailer for easy access in subclassed jobs.
25
+ - add logging to Mailer perform.
26
+ - add save to #queue! job if unsaved
27
+ - add block with logging if queued_at isn't set or isn't prior to run time.
28
+
3
29
  ## v0.4.0 - 2021-10-12
4
30
 
5
31
  ### Added
data/README.md CHANGED
@@ -47,6 +47,17 @@ RunDonation.create!(args: {donation_id: 123}).queue! delay: 1.hour
47
47
  => {:status=>"INSERTED", :id=>"1141"}
48
48
  ```
49
49
 
50
+ ### Mailers
51
+
52
+ ```ruby
53
+ j = Postburner::Mailer.
54
+ delivery(UserMailer, :welcome)
55
+ .with(name: 'Freddy')
56
+
57
+ j.queue!
58
+ => {:status=>"INSERTED", :id=>"1139"}
59
+ ```
60
+
50
61
  ### [Beaneater](https://github.com/beanstalkd/beaneater) and [beanstalkd](https://raw.githubusercontent.com/beanstalkd/beanstalkd/master/doc/protocol.txt) attributes and methods
51
62
  ```ruby
52
63
  # get the beanstalkd job id
@@ -1,4 +1,17 @@
1
1
  module Postburner
2
+ # Must implement a perform method, if an exception is raised the job
3
+ # doesn't complete.
4
+ #
5
+ # Job won't run unless queued_at is set, and is set to a time prior to
6
+ # the time the job runs.
7
+ #
8
+ # TODO Mailer uses ActiveJob::Arguments... probably should use that here
9
+ # as well. Decided how to migrate existing jobs or allow both - Opt to
10
+ # allow both: rescue from ActiveJob::DeserializationError and use the
11
+ # plain hash, probably log it too.
12
+ #
13
+ # Add `cancelled_at` that blocks jobs performing if present.
14
+ #
2
15
  class Job < ApplicationRecord
3
16
  include Backburner::Queue
4
17
 
@@ -11,36 +24,56 @@ module Postburner
11
24
 
12
25
  before_validation :ensure_sid!
13
26
  before_destroy :delete!
27
+ after_save_commit :insert_if_queued!
14
28
 
15
29
  validates :sid, presence: {strict: true}
16
30
 
17
31
  def queue!(options={})
18
32
  return if self.queued_at.present? && self.bkid.present?
33
+ raise ActiveRecord::RecordInvalid, "Can't queue unless valid." unless self.valid?
34
+ raise AlreadyProcessed, "Processed at #{self.processed_at}" if self.processed_at
19
35
 
20
- case
21
- when options[:at].present?
22
- # this is rudimentary, add error handling
23
- options[:delay] ||= options[:at].to_i - Time.zone.now.to_i
24
- update_column :run_at, options[:at]
25
- options.delete(:at)
26
- when options[:delay].present?
27
- update_column :run_at, Time.zone.now + options[:delay].seconds
28
- end
36
+ at = options.delete(:at)
37
+ now = Time.zone.now
38
+
39
+ self.queued_at = now
40
+ self.run_at = case
41
+ when at.present?
42
+ # this is rudimentary, add error handling
43
+ options[:delay] ||= at.to_i - now.to_i
44
+ at
45
+ when options[:delay].present?
46
+ now + options[:delay].seconds
47
+ end
48
+
49
+ @_insert_options = options
50
+
51
+ self.save!
52
+ end
53
+
54
+ def requeue!(options={})
55
+ self.delete!
56
+ self.bkid, self.queued_at = nil, nil
29
57
 
30
- insert!(options)
58
+ self.queue! options
59
+ end
60
+
61
+ def will_insert?
62
+ @_insert_options.is_a? Hash
31
63
  end
32
64
 
33
65
  # tube: backburner.worker.queue.backburner-jobs
34
66
  #
35
67
  def self.perform(id, _={})
68
+ job = nil
36
69
  begin
37
70
  job = self.find(id)
38
- job.perform!(job.args)
39
71
  rescue ActiveRecord::RecordNotFound => e
40
72
  Rails.logger.warn <<-MSG
41
73
  [Postburner::Job] [#{id}] Not Found.
42
74
  MSG
43
75
  end
76
+ job&.perform!(job.args)
44
77
  end
45
78
 
46
79
  def perform!(args={})
@@ -55,6 +88,16 @@ module Postburner
55
88
  )
56
89
 
57
90
  begin
91
+ if self.queued_at.nil?
92
+ self.log! "Not Queued", level: :error
93
+ return
94
+ end
95
+
96
+ if self.queued_at > Time.zone.now
97
+ self.log! "Future Queued", level: :error
98
+ return
99
+ end
100
+
58
101
  if self.processed_at.present?
59
102
  self.log! "Already Processed", level: :error
60
103
  self.delete!
@@ -72,22 +115,24 @@ module Postburner
72
115
  return
73
116
  end
74
117
 
75
- self.log!('START')
118
+ self.log!("START (bkid #{self.bkid})")
76
119
 
77
- self.perform(args)
120
+ begin
121
+ self.perform(args)
122
+ rescue Exception => exception
123
+ self.persist_metadata!
124
+ self.log! '[Postburner] Exception raised during perform prevented completion.'
125
+ raise exception
126
+ end
78
127
 
79
- self.log!('DONE')
128
+ self.log!("DONE (bkid #{self.bkid})")
80
129
 
81
130
  begin
82
131
  now = Time.zone.now
83
132
  _duration = (now - self.processing_at) * 1000 rescue nil
84
- self.update_columns(
133
+ persist_metadata!(
85
134
  processed_at: now,
86
135
  duration: _duration,
87
- errata: self.errata,
88
- error_count: self.errata.length,
89
- logs: self.logs,
90
- log_count: self.logs.length,
91
136
  )
92
137
  rescue Exception => e
93
138
  self.log_exception!(e)
@@ -97,7 +142,6 @@ module Postburner
97
142
 
98
143
  rescue Exception => exception
99
144
  self.log_exception!(exception)
100
- self.log! '[Postburner] Exception raised during perform prevented completion.'
101
145
  raise exception
102
146
  end
103
147
 
@@ -148,6 +192,7 @@ module Postburner
148
192
  self.errata << [
149
193
  Time.zone.now,
150
194
  {
195
+ bkid: self.bkid,
151
196
  class: exception.class,
152
197
  message: exception.message,
153
198
  backtrace: exception.backtrace,
@@ -165,30 +210,67 @@ module Postburner
165
210
  options[:level] = :error unless LOG_LEVELS.member?(options[:level])
166
211
 
167
212
  self.logs << [
168
- Time.zone.now,
169
- options[:level],
170
- message,
213
+ Time.zone.now, # time
214
+ {
215
+ bkid: self.bkid,
216
+ level: options[:level], # level
217
+ message: message, # message
218
+ elapsed: self.elapsed_ms, # ms from start
219
+ }
171
220
  ]
172
221
  end
173
222
 
223
+ # ms from attempting_at
224
+ #
225
+ def elapsed_ms
226
+ return unless self.attempting_at
227
+ (Time.zone.now - self.attempting_at) * 1000
228
+ end
229
+
174
230
  def log!(message, options={})
175
231
  self.log(message, options)
176
232
  self.update_column :logs, self.logs
177
233
  end
178
234
 
235
+ def intended_at
236
+ self.run_at ? self.run_at : self.queued_at
237
+ end
238
+
239
+ class AlreadyProcessed < StandardError; end
240
+
179
241
  private
180
242
 
243
+ def persist_metadata!(data={})
244
+ self.update_columns({
245
+ errata: self.errata,
246
+ error_count: self.errata.length,
247
+ logs: self.logs,
248
+ log_count: self.logs.length,
249
+ }.merge(data))
250
+ end
251
+
252
+ def insert_if_queued!
253
+ return unless self.will_insert?
254
+ insert!(@_insert_options)
255
+ end
256
+
181
257
  def insert!(options={})
182
- response = Backburner::Worker.enqueue(Postburner::Job, self.id, options)
258
+ response = nil
259
+
260
+ Job.transaction do
261
+ response = Backburner::Worker.enqueue(
262
+ Postburner::Job,
263
+ self.id,
264
+ options
265
+ )
266
+
267
+ persist_metadata!(
268
+ bkid: response[:id],
269
+ )
270
+ end
183
271
 
184
272
  self.log("QUEUED: #{response}")
185
273
 
186
- update_columns(
187
- queued_at: Time.zone.now,
188
- bkid: response[:id],
189
- logs: self.logs,
190
- )
191
-
192
274
  response
193
275
  end
194
276
 
@@ -196,14 +278,10 @@ module Postburner
196
278
  now = Time.zone.now
197
279
  self.attempts << now
198
280
  self.attempting_at ||= now
199
- self.lag ||= (self.attempting_at - self.intended_at) * 1000
281
+ self.lag ||= (self.attempting_at - self.intended_at) * 1000 rescue nil
200
282
  now
201
283
  end
202
284
 
203
- def intended_at
204
- self.run_at ? self.run_at : self.queued_at
205
- end
206
-
207
285
  def ensure_sid!
208
286
  self.sid ||= SecureRandom.uuid
209
287
  end
@@ -7,8 +7,8 @@ module Postburner
7
7
  class Mailer < Job
8
8
  #queue 'mailers'
9
9
 
10
- def self.deliver(mailer, action)
11
- job = self.create!(
10
+ def self.delivery(mailer, action)
11
+ job = self.new(
12
12
  args: {
13
13
  mailer: mailer.to_s,
14
14
  action: action.to_s,
@@ -17,32 +17,62 @@ module Postburner
17
17
  job
18
18
  end
19
19
 
20
+ def self.delivery!(mailer, action)
21
+ job = self.delivery(mailer, action)
22
+ job.save!
23
+ job
24
+ end
25
+
20
26
  # Similar to ActionMailer #with - set the parameters
21
27
  #
22
28
  def with(params={})
23
29
  self.args.merge!(
24
- params: ActiveJob::Arguments.serialize(params)
30
+ 'params' => ActiveJob::Arguments.serialize(params)
25
31
  )
32
+ self
33
+ end
34
+
35
+ def with!(params={})
36
+ self.with(params)
26
37
  self.save!
27
38
  self
28
39
  end
29
40
 
30
41
  # Build the mail but don't send.
31
42
  #
32
- def assemble(args=nil)
33
- _args = args || self.args
43
+ # Optional `args` argument for testing convenience.
44
+ #
45
+ def assemble
46
+ mail = self.mailer.with(self.params).send(self.action)
47
+ mail
48
+ end
34
49
 
35
- cls = _args['mailer'].constantize
50
+ # Get the mailer class.
51
+ #
52
+ def mailer
53
+ self.args['mailer'].constantize
54
+ end
36
55
 
37
- mail = cls.
38
- with(ActiveJob::Arguments.deserialize(_args['params']).to_h).
39
- send(_args['action'])
56
+ # Get the mailer action as a symbol.
57
+ #
58
+ def action
59
+ self.args['action']&.to_sym
60
+ end
40
61
 
41
- mail
62
+ # Get the deserialized params.
63
+ #
64
+ def params
65
+ ActiveJob::Arguments.deserialize(self.args['params']).to_h
42
66
  end
43
67
 
44
68
  def perform(args)
45
- self.assemble(args).deliver_now
69
+ self.log! "Building"
70
+ mail = self.assemble
71
+
72
+ self.log! "Delivering"
73
+ mail.deliver_now
74
+
75
+ self.log! "Delivered"
46
76
  end
47
77
  end
48
78
  end
@@ -1,3 +1,3 @@
1
1
  module Postburner
2
- VERSION = '0.4.0'
2
+ VERSION = '0.6.2'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postburner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-12 00:00:00.000000000 Z
11
+ date: 2021-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails