postburner 1.0.0.pre.3 → 1.0.0.pre.4
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 +61 -67
- data/app/concerns/postburner/commands.rb +143 -0
- data/app/concerns/postburner/execution.rb +190 -0
- data/app/concerns/postburner/insertion.rb +170 -0
- data/app/concerns/postburner/logging.rb +181 -0
- data/{lib/postburner/queue_config.rb → app/concerns/postburner/properties.rb} +65 -4
- data/app/concerns/postburner/statistics.rb +125 -0
- data/app/models/postburner/job.rb +14 -756
- data/app/views/postburner/jobs/show.html.haml +2 -2
- data/lib/postburner/strategies/queue.rb +17 -7
- data/lib/postburner/version.rb +1 -1
- data/lib/postburner.rb +0 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a279e41bee615d867b4392c53c9cfe9535c1652842f166f2f6cd33b3fc6215e2
|
|
4
|
+
data.tar.gz: f11ad9028e92679bc5c5fc3d62b75d8a3a163394bb130dac4bb9a1bcd33c7cfb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70eeebf1007b699b58713d3a9fc107aabf118fe6c13c39d519e4b800416d9ea185bb96012be1e37a617ef7b4e52896e1387aa2f11e9b45077912bb154071f62f
|
|
7
|
+
data.tar.gz: 1e34ecbf3dfe03401f7280deca6939e056bceb21ed6d2303e87b4f5c53d5ea23f0d712814acc48a356b471dbd1754505a155e7830c304051bfb1faae2fe4fde1
|
data/README.md
CHANGED
|
@@ -3,13 +3,55 @@
|
|
|
3
3
|
Fast Beanstalkd-backed job queue with **optional PostgreSQL records** for ActiveJob.
|
|
4
4
|
|
|
5
5
|
Postburner provides dual-mode job execution:
|
|
6
|
-
- **
|
|
7
|
-
- **Tracked jobs**:
|
|
6
|
+
- **Fast jobs**: Fast execution via Beanstalkd
|
|
7
|
+
- **Tracked jobs**: Audited jobs logs, timing, errors, and statistics
|
|
8
8
|
|
|
9
9
|
Built for production environments where you want fast background processing for most jobs, but comprehensive auditing for critical operations.
|
|
10
10
|
|
|
11
11
|
Depends on Beanstalkd, ActiveRecord, ActiveJob, Postgres.
|
|
12
12
|
|
|
13
|
+
```ruby
|
|
14
|
+
# Default job (fast, no PostgreSQL overhead)
|
|
15
|
+
class SendSms < ApplicationJob
|
|
16
|
+
def perform(user_id)
|
|
17
|
+
user = User.find(user_id)
|
|
18
|
+
TextMessage.welcome(
|
|
19
|
+
to: user.phone_number,
|
|
20
|
+
body: "Welcome to our app!"
|
|
21
|
+
).deliver_now
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Default job with Beanstalkd configuration
|
|
26
|
+
class DoSomething < ApplicationJob
|
|
27
|
+
include Postburner::Beanstalkd # optional, allow access to beanstalkd
|
|
28
|
+
|
|
29
|
+
priority 5000 # 0=highest, 65536=default, can set per job
|
|
30
|
+
ttr 30 # 30 second timeout
|
|
31
|
+
|
|
32
|
+
def perform(user_id)
|
|
33
|
+
# Do something
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Tracked job (full audit trail, includes Beanstalkd automatically)
|
|
38
|
+
class ProcessPayment < ApplicationJob
|
|
39
|
+
include Postburner::Tracked # ← Opt-in to tracking (includes Beanstalkd)
|
|
40
|
+
|
|
41
|
+
priority 0 # Highest priority
|
|
42
|
+
ttr 600 # 10 minute timeout
|
|
43
|
+
|
|
44
|
+
def perform(payment_id)
|
|
45
|
+
log "Processing payment #{payment_id}"
|
|
46
|
+
Payment.find(payment_id).process!
|
|
47
|
+
log! "Payment processed successfully"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Run worker
|
|
52
|
+
bundle exec postburner --worker default
|
|
53
|
+
```
|
|
54
|
+
|
|
13
55
|
## Why
|
|
14
56
|
|
|
15
57
|
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.
|
|
@@ -23,6 +65,7 @@ Thus old-school [beanstalkd](https://beanstalkd.github.io/) is used with Postgre
|
|
|
23
65
|
- Store the jobs outside of the database, but also persist them to disk for disaster recovery (beanstalkd binlogs)
|
|
24
66
|
- Introspect the jobs either with ActiveRecord or Beanstalkd.
|
|
25
67
|
- Only one worker type, that can be single/multi-process, with optional threading, and optional GC (Garbage Collection) limits (kill fork after processing N jobs).
|
|
68
|
+
- Easy testing.
|
|
26
69
|
|
|
27
70
|
## Features
|
|
28
71
|
|
|
@@ -36,35 +79,17 @@ Thus old-school [beanstalkd](https://beanstalkd.github.io/) is used with Postgre
|
|
|
36
79
|
|
|
37
80
|
## Quick Start
|
|
38
81
|
|
|
39
|
-
```bash
|
|
40
|
-
sudo apt-get install beanstalkd # OR brew install beanstalkd
|
|
41
|
-
|
|
42
|
-
# Start beanstalkd (in-memory only)
|
|
43
|
-
beanstalkd -l 127.0.0.1 -p 11300
|
|
44
|
-
|
|
45
|
-
# OR with persistence (recommended for production)
|
|
46
|
-
mkdir -p /var/lib/beanstalkd
|
|
47
|
-
beanstalkd -l 127.0.0.1 -p 11300 -b /var/lib/beanstalkd
|
|
48
|
-
```
|
|
49
|
-
|
|
50
82
|
```ruby
|
|
51
83
|
# Gemfile
|
|
52
|
-
gem 'postburner', '~> 1.0.0.pre.
|
|
53
|
-
```
|
|
84
|
+
gem 'postburner', '~> 1.0.0.pre.3'
|
|
54
85
|
|
|
55
|
-
```bash
|
|
56
|
-
rails generate postburner:install
|
|
57
|
-
rails db:migrate
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
86
|
# config/application.rb
|
|
62
87
|
config.active_job.queue_adapter = :postburner
|
|
63
88
|
```
|
|
64
89
|
|
|
65
90
|
```yaml
|
|
66
91
|
# config/postburner.yml
|
|
67
|
-
|
|
92
|
+
development: # <- environment config, i.e. defaults
|
|
68
93
|
beanstalk_url: <%= ENV['BEANSTALK_URL'] || 'beanstalk://localhost:11300' %>
|
|
69
94
|
default_forks: 2
|
|
70
95
|
default_threads: 10
|
|
@@ -91,48 +116,15 @@ production: # <- environment config, i.e. defaults
|
|
|
91
116
|
- video
|
|
92
117
|
```
|
|
93
118
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```ruby
|
|
97
|
-
# Default job (fast, no PostgreSQL overhead)
|
|
98
|
-
class SendSms < ApplicationJob
|
|
99
|
-
def perform(user_id)
|
|
100
|
-
user = User.find(user_id)
|
|
101
|
-
TextMessage.welcome(
|
|
102
|
-
to: user.phone_number,
|
|
103
|
-
body: "Welcome to our app!"
|
|
104
|
-
).deliver_now
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Default job with Beanstalkd configuration
|
|
109
|
-
class DoSomething < ApplicationJob
|
|
110
|
-
include Postburner::Beanstalkd # optional, allow access to beanstalkd
|
|
111
|
-
|
|
112
|
-
priority 5000 # 0=highest, 65536=default, can set per job
|
|
113
|
-
ttr 30 # 30 second timeout
|
|
114
|
-
|
|
115
|
-
def perform(user_id)
|
|
116
|
-
# Do something
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Tracked job (full audit trail, includes Beanstalkd automatically)
|
|
121
|
-
class ProcessPayment < ApplicationJob
|
|
122
|
-
include Postburner::Tracked # ← Opt-in to tracking (includes Beanstalkd)
|
|
119
|
+
```bash
|
|
120
|
+
sudo apt-get install beanstalkd # OR brew install beanstalkd
|
|
123
121
|
|
|
124
|
-
|
|
125
|
-
ttr 600 # 10 minute timeout
|
|
122
|
+
beanstalkd -l 127.0.0.1 -p 11300 # Start beanstalkd
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Payment.find(payment_id).process!
|
|
130
|
-
log! "Payment processed successfully"
|
|
131
|
-
end
|
|
132
|
-
end
|
|
124
|
+
bundle exec rails generate postburner:install
|
|
125
|
+
bundle exec rails db:migrate
|
|
133
126
|
|
|
134
|
-
#
|
|
135
|
-
bundle exec postburner --worker default
|
|
127
|
+
bundle exec postburner # start
|
|
136
128
|
```
|
|
137
129
|
|
|
138
130
|
## Table of Contents
|
|
@@ -159,11 +151,14 @@ The [protocol](https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol
|
|
|
159
151
|
|
|
160
152
|
Here is a picture of the typical job lifecycle:
|
|
161
153
|
|
|
154
|
+
```
|
|
162
155
|
put reserve delete
|
|
163
|
-
-----> [READY] ---------> [RESERVED] --------> *poof
|
|
156
|
+
-----> [READY] ---------> [RESERVED] --------> *poof*`
|
|
157
|
+
```
|
|
164
158
|
|
|
165
159
|
Here is a picture with more possibilities:
|
|
166
160
|
|
|
161
|
+
```
|
|
167
162
|
put with delay release with delay
|
|
168
163
|
----------------> [DELAYED] <------------.
|
|
169
164
|
| |
|
|
@@ -182,13 +177,12 @@ Here is a picture with more possibilities:
|
|
|
182
177
|
|
|
|
183
178
|
| delete
|
|
184
179
|
`--------> *poof*
|
|
180
|
+
```
|
|
185
181
|
|
|
186
182
|
### Binlogs
|
|
187
183
|
|
|
188
184
|
Beanstalkd lets you persist jobs to disk in case of a crash or restart. Just restart beanstalkd and the jobs will be back in the queue.
|
|
189
185
|
|
|
190
|
-
**Setup:**
|
|
191
|
-
|
|
192
186
|
```bash
|
|
193
187
|
# Create binlog directory
|
|
194
188
|
sudo mkdir -p /var/lib/beanstalkd
|
|
@@ -198,7 +192,7 @@ sudo chown beanstalkd:beanstalkd /var/lib/beanstalkd # If running as service
|
|
|
198
192
|
beanstalkd -l 127.0.0.1 -p 11300 -b /var/lib/beanstalkd
|
|
199
193
|
```
|
|
200
194
|
|
|
201
|
-
**
|
|
195
|
+
**Other options:**
|
|
202
196
|
|
|
203
197
|
```bash
|
|
204
198
|
# Basic persistence
|
|
@@ -214,7 +208,7 @@ beanstalkd -b /var/lib/beanstalkd -f 200 # fsync at most every 200ms (default:
|
|
|
214
208
|
beanstalkd -b /var/lib/beanstalkd -F
|
|
215
209
|
```
|
|
216
210
|
|
|
217
|
-
**
|
|
211
|
+
**Beanstalkd switches:**
|
|
218
212
|
- `-b <dir>` - Enable binlog persistence in specified directory
|
|
219
213
|
- `-s <bytes>` - Maximum size of each binlog file (requires `-b`)
|
|
220
214
|
- `-f <ms>` - Call fsync at most once every N milliseconds (requires `-b`, default: 50ms)
|
|
@@ -1296,7 +1290,7 @@ Direct access to Beanstalkd for advanced operations:
|
|
|
1296
1290
|
job.bkid # => 12345
|
|
1297
1291
|
|
|
1298
1292
|
# Access Beaneater job object
|
|
1299
|
-
job.
|
|
1293
|
+
job.bk.stats
|
|
1300
1294
|
# => {"id"=>12345, "tube"=>"critical", "state"=>"ready", ...}
|
|
1301
1295
|
|
|
1302
1296
|
# Connection management
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
# Concern providing Beanstalkd command methods for Postburner jobs.
|
|
5
|
+
#
|
|
6
|
+
# Provides methods for interacting with Beanstalkd queue operations such as
|
|
7
|
+
# deleting, kicking, and extending TTR. All methods handle connection retries
|
|
8
|
+
# and gracefully handle missing bkid (e.g., in test mode).
|
|
9
|
+
#
|
|
10
|
+
# @example Basic Beanstalkd operations
|
|
11
|
+
# class MyJob < Postburner::Job
|
|
12
|
+
# def perform(args)
|
|
13
|
+
# # ... work ...
|
|
14
|
+
# extend! # Extend TTR during long operation
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# job.delete! # Remove from Beanstalkd
|
|
19
|
+
# job.kick! # Retry buried job
|
|
20
|
+
#
|
|
21
|
+
module Commands
|
|
22
|
+
extend ActiveSupport::Concern
|
|
23
|
+
|
|
24
|
+
# Kicks a buried job back into the ready queue in Beanstalkd.
|
|
25
|
+
#
|
|
26
|
+
# This is a Beanstalkd operation used to retry jobs that were buried due to
|
|
27
|
+
# repeated failures or explicit burial. Does not modify the ActiveRecord model.
|
|
28
|
+
#
|
|
29
|
+
# Automatically retries with fresh connection if Beanstalkd connection is stale.
|
|
30
|
+
#
|
|
31
|
+
# @return [void]
|
|
32
|
+
#
|
|
33
|
+
# @raise [Beaneater::NotConnected] if Beanstalkd connection fails after retry
|
|
34
|
+
#
|
|
35
|
+
# @note Does nothing if job has no bkid (e.g., in test mode)
|
|
36
|
+
# @note Only works on buried jobs - see Beanstalkd documentation
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
# job.kick! # Moves buried job back to ready queue
|
|
40
|
+
#
|
|
41
|
+
# @see #delete!
|
|
42
|
+
# @see #bk
|
|
43
|
+
#
|
|
44
|
+
def kick!
|
|
45
|
+
return unless self.bk
|
|
46
|
+
begin
|
|
47
|
+
self.bk.kick
|
|
48
|
+
rescue Beaneater::NotConnected => e
|
|
49
|
+
self.bk!.kick
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Extends the job's time-to-run (TTR) in Beanstalkd.
|
|
54
|
+
#
|
|
55
|
+
# Calls touch on the Beanstalkd job, extending the TTR by the original
|
|
56
|
+
# TTR value. Use this during long-running operations to prevent the job
|
|
57
|
+
# from timing out.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
#
|
|
61
|
+
# @note Does nothing if job has no bkid (e.g., in test mode)
|
|
62
|
+
#
|
|
63
|
+
# @example Process large file line by line
|
|
64
|
+
# def perform(args)
|
|
65
|
+
# file = File.find(args['file_id'])
|
|
66
|
+
# file.each_line do |line|
|
|
67
|
+
# # ... process line ...
|
|
68
|
+
# extend! # Extend TTR to prevent timeout
|
|
69
|
+
# end
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# @see #bk
|
|
73
|
+
#
|
|
74
|
+
def extend!
|
|
75
|
+
return unless self.bk
|
|
76
|
+
begin
|
|
77
|
+
self.bk.touch
|
|
78
|
+
rescue Beaneater::NotConnected => e
|
|
79
|
+
self.bk!.touch
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Deletes the job from the Beanstalkd queue.
|
|
84
|
+
#
|
|
85
|
+
# This is a Beanstalkd operation that removes the job from the queue but
|
|
86
|
+
# does NOT destroy the ActiveRecord model. Use {#destroy} or {#remove!} to
|
|
87
|
+
# also update the database record.
|
|
88
|
+
#
|
|
89
|
+
# Automatically retries with fresh connection if Beanstalkd connection is stale.
|
|
90
|
+
# Called automatically by before_destroy callback when using {#destroy}.
|
|
91
|
+
#
|
|
92
|
+
# @return [void]
|
|
93
|
+
#
|
|
94
|
+
# @raise [Beaneater::NotConnected] if Beanstalkd connection fails after retry
|
|
95
|
+
#
|
|
96
|
+
# @note Does nothing if job has no bkid (e.g., in test mode)
|
|
97
|
+
# @note Does not modify ActiveRecord model - only affects Beanstalkd
|
|
98
|
+
#
|
|
99
|
+
# @example
|
|
100
|
+
# job.delete! # Removes from Beanstalkd queue only
|
|
101
|
+
# job.reload # Job still exists in database
|
|
102
|
+
#
|
|
103
|
+
# @see #remove!
|
|
104
|
+
# @see #destroy
|
|
105
|
+
# @see #bk
|
|
106
|
+
#
|
|
107
|
+
def delete!
|
|
108
|
+
return unless self.bk
|
|
109
|
+
begin
|
|
110
|
+
self.bk.delete
|
|
111
|
+
rescue Beaneater::NotConnected => e
|
|
112
|
+
self.bk!.delete
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Soft-deletes the job by removing from Beanstalkd and setting removed_at timestamp.
|
|
117
|
+
#
|
|
118
|
+
# Unlike {#destroy}, this preserves the job record in the database for audit trails
|
|
119
|
+
# while removing it from the Beanstalkd queue and marking it as removed.
|
|
120
|
+
#
|
|
121
|
+
# @return [void]
|
|
122
|
+
#
|
|
123
|
+
# @raise [Beaneater::NotConnected] if Beanstalkd connection fails
|
|
124
|
+
#
|
|
125
|
+
# @note Idempotent - does nothing if already removed
|
|
126
|
+
# @note Does not destroy ActiveRecord model - only soft deletes
|
|
127
|
+
#
|
|
128
|
+
# @example
|
|
129
|
+
# job.remove!
|
|
130
|
+
# job.removed_at # => 2025-10-31 12:34:56 UTC
|
|
131
|
+
# job.persisted? # => true (still in database)
|
|
132
|
+
#
|
|
133
|
+
# @see #delete!
|
|
134
|
+
# @see #destroy
|
|
135
|
+
#
|
|
136
|
+
def remove!
|
|
137
|
+
return if self.removed_at
|
|
138
|
+
self.delete!
|
|
139
|
+
self.update_column(:removed_at, Time.zone.now)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
# Concern providing job execution methods for Postburner jobs.
|
|
5
|
+
#
|
|
6
|
+
# Handles the full job execution lifecycle including validation, callbacks,
|
|
7
|
+
# timing tracking, error handling, and state management. Provides both the
|
|
8
|
+
# class-level entry point for workers and the instance-level execution logic.
|
|
9
|
+
#
|
|
10
|
+
# @example Direct execution
|
|
11
|
+
# Postburner::Job.perform(job.id)
|
|
12
|
+
#
|
|
13
|
+
# @example Instance execution
|
|
14
|
+
# job.perform!(job.args)
|
|
15
|
+
#
|
|
16
|
+
module Execution
|
|
17
|
+
extend ActiveSupport::Concern
|
|
18
|
+
|
|
19
|
+
class_methods do
|
|
20
|
+
# Executes a job by ID, delegating to the current queue strategy.
|
|
21
|
+
#
|
|
22
|
+
# Loads the job from database by ID and delegates execution to the current
|
|
23
|
+
# queue strategy's {handle_perform!} method. This provides a unified API
|
|
24
|
+
# for job execution regardless of strategy (async, test, or null).
|
|
25
|
+
#
|
|
26
|
+
# Called automatically by Backburner workers in production. Can also be
|
|
27
|
+
# called manually for test/null strategies to trigger execution.
|
|
28
|
+
#
|
|
29
|
+
# @param id [Integer] Job ID to execute
|
|
30
|
+
# @param _ [Hash] Unused Backburner metadata parameter
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
33
|
+
#
|
|
34
|
+
# @example Backburner automatic execution (production)
|
|
35
|
+
# # Jobs execute in tube: backburner.worker.queue.backburner-jobs
|
|
36
|
+
# # Backburner calls: Postburner::Job.perform(job_id)
|
|
37
|
+
#
|
|
38
|
+
# @example Manual execution with NullQueue
|
|
39
|
+
# Postburner.null_strategy!
|
|
40
|
+
# job = MyJob.create!(args: {})
|
|
41
|
+
# job.queue!(delay: 1.hour)
|
|
42
|
+
# Postburner::Job.perform(job.id) # Time travels and executes
|
|
43
|
+
#
|
|
44
|
+
# @note Strategy-aware: delegates to Postburner.queue_strategy.handle_perform!
|
|
45
|
+
# @note For NullQueue, automatically handles time travel for scheduled jobs
|
|
46
|
+
#
|
|
47
|
+
# @see #perform!
|
|
48
|
+
# @see Queue.handle_perform!
|
|
49
|
+
# @see NullQueue.handle_perform!
|
|
50
|
+
#
|
|
51
|
+
def perform(id, _={})
|
|
52
|
+
job = nil
|
|
53
|
+
begin
|
|
54
|
+
job = self.find(id)
|
|
55
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
56
|
+
Rails.logger.warn <<-MSG
|
|
57
|
+
[Postburner::Job] [#{id}] Not Found.
|
|
58
|
+
MSG
|
|
59
|
+
end
|
|
60
|
+
#job.perform!(job.args)
|
|
61
|
+
Postburner.queue_strategy.handle_perform!(job)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Executes the job with full lifecycle management and error handling.
|
|
66
|
+
#
|
|
67
|
+
# This is the main execution method called by Backburner workers or test strategies.
|
|
68
|
+
# Performs validation checks, executes callbacks, calls the subclass {#perform} method,
|
|
69
|
+
# tracks timing and statistics, and handles errors.
|
|
70
|
+
#
|
|
71
|
+
# Execution flow:
|
|
72
|
+
# 1. Runs attempt callbacks (fires on every retry)
|
|
73
|
+
# 2. Updates attempting metadata (attempting_at, attempts, lag)
|
|
74
|
+
# 3. Validates job state (queued, not processed, not removed, not premature)
|
|
75
|
+
# 4. Runs processing callbacks
|
|
76
|
+
# 5. Calls subclass {#perform} method
|
|
77
|
+
# 6. Runs processed callbacks (only on success)
|
|
78
|
+
# 7. Updates completion metadata (processed_at, duration)
|
|
79
|
+
# 8. Logs and tracks any exceptions
|
|
80
|
+
#
|
|
81
|
+
# @param args [Hash] Arguments to pass to {#perform} method
|
|
82
|
+
#
|
|
83
|
+
# @return [void]
|
|
84
|
+
#
|
|
85
|
+
# @raise [Exception] Any exception raised by {#perform} is logged and re-raised
|
|
86
|
+
#
|
|
87
|
+
# @note Does not execute if queued_at is nil, in the future, already processed, or removed
|
|
88
|
+
# @note Premature execution (before run_at) is delegated to queue strategy
|
|
89
|
+
#
|
|
90
|
+
# @see #perform
|
|
91
|
+
# @see Postburner.queue_strategy
|
|
92
|
+
# @see Callbacks
|
|
93
|
+
#
|
|
94
|
+
def perform!(args={})
|
|
95
|
+
run_callbacks :attempt do
|
|
96
|
+
self.attempting
|
|
97
|
+
|
|
98
|
+
self.update_columns(
|
|
99
|
+
attempting_at: self.attempting_at,
|
|
100
|
+
attempts: self.attempts,
|
|
101
|
+
attempt_count: self.attempts.length,
|
|
102
|
+
lag: self.lag,
|
|
103
|
+
processing_at: Time.zone.now,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
begin
|
|
107
|
+
if self.queued_at.nil?
|
|
108
|
+
self.log! "Not Queued", level: :error
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
if self.queued_at > Time.zone.now
|
|
113
|
+
self.log! "Future Queued", level: :error
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if self.processed_at.present?
|
|
118
|
+
self.log! "Already Processed", level: :error
|
|
119
|
+
self.delete!
|
|
120
|
+
return
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if self.removed_at.present?
|
|
124
|
+
self.log! "Removed", level: :error
|
|
125
|
+
return
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if self.run_at && self.run_at.to_i > Time.zone.now.to_i
|
|
129
|
+
Postburner.queue_strategy.handle_premature_perform(self)
|
|
130
|
+
return
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
self.log!("START (bkid #{self.bkid})")
|
|
134
|
+
|
|
135
|
+
run_callbacks :processing do
|
|
136
|
+
begin
|
|
137
|
+
self.perform(args)
|
|
138
|
+
rescue Exception => exception
|
|
139
|
+
self.persist_metadata!
|
|
140
|
+
self.log! '[Postburner] Exception raised during perform prevented completion.'
|
|
141
|
+
raise exception
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
self.log!("DONE (bkid #{self.bkid})")
|
|
146
|
+
|
|
147
|
+
begin
|
|
148
|
+
now = Time.zone.now
|
|
149
|
+
_duration = (now - self.processing_at) * 1000 rescue nil
|
|
150
|
+
|
|
151
|
+
run_callbacks :processed do
|
|
152
|
+
persist_metadata!(
|
|
153
|
+
processed_at: now,
|
|
154
|
+
duration: _duration,
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
rescue Exception => e
|
|
158
|
+
self.log_exception!(e)
|
|
159
|
+
self.log! '[Postburner] Could not set data after processing.'
|
|
160
|
+
# TODO README doesn't retry if Postburner is to blame
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
rescue Exception => exception
|
|
164
|
+
self.log_exception!(exception)
|
|
165
|
+
raise exception
|
|
166
|
+
end
|
|
167
|
+
end # run_callbacks :attempt
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
# Records an attempt and calculates execution lag.
|
|
174
|
+
#
|
|
175
|
+
# Appends current time to attempts array, sets attempting_at on first attempt,
|
|
176
|
+
# and calculates lag (delay between intended and actual execution time).
|
|
177
|
+
#
|
|
178
|
+
# @return [Time] Current timestamp
|
|
179
|
+
#
|
|
180
|
+
# @api private
|
|
181
|
+
#
|
|
182
|
+
def attempting
|
|
183
|
+
now = Time.zone.now
|
|
184
|
+
self.attempts << now
|
|
185
|
+
self.attempting_at ||= now
|
|
186
|
+
self.lag ||= (self.attempting_at - self.intended_at) * 1000 rescue nil
|
|
187
|
+
now
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|