async-background 0.7.1 → 0.7.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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +48 -7
- data/async-background.gemspec +2 -1
- data/lib/async/background/job.rb +5 -3
- data/lib/async/background/metrics.rb +157 -86
- data/lib/async/background/queue/client.rb +33 -15
- data/lib/async/background/queue/options.rb +70 -0
- data/lib/async/background/queue/schema.rb +160 -0
- data/lib/async/background/queue/sql.rb +205 -0
- data/lib/async/background/queue/store.rb +270 -148
- data/lib/async/background/runner/queue_execution.rb +199 -0
- data/lib/async/background/runner/schedule.rb +127 -0
- data/lib/async/background/runner.rb +99 -231
- data/lib/async/background/version.rb +1 -1
- metadata +27 -2
|
@@ -1,140 +1,146 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
4
6
|
require_relative '../clock'
|
|
7
|
+
require_relative 'options'
|
|
8
|
+
require_relative 'schema'
|
|
9
|
+
require_relative 'sql'
|
|
5
10
|
|
|
6
11
|
module Async
|
|
7
12
|
module Background
|
|
8
13
|
module Queue
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
DEFAULTS = { mmap: true, synchronous: :normal, wal_autocheckpoint: 1_000 }.freeze
|
|
12
|
-
MMAP_SIZE = 268_435_456
|
|
14
|
+
class Store
|
|
15
|
+
include Clock
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
SCHEMA_VERSION = Schema::VERSION
|
|
18
|
+
MIGRATION_BUSY_TIMEOUT_MS = Schema::MIGRATION_BUSY_TIMEOUT_MS
|
|
19
|
+
REQUIRED_INDEXES = Schema::REQUIRED_INDEXES
|
|
20
|
+
SCHEMA = SQL::CREATE_SCHEMA
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
CLEANUP_INTERVAL = 300
|
|
23
|
+
CLEANUP_AGE = 3600
|
|
24
|
+
FAILED_RETENTION_AGE = 7 * 24 * 3600
|
|
25
|
+
ERROR_MESSAGE_MAX_LEN = 2_000
|
|
26
|
+
EMPTY_ARGS_JSON = '[]'.freeze
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
raise ArgumentError,
|
|
24
|
-
"synchronous must be one of #{SYNCHRONOUS_LEVELS.keys.inspect}, got #{synchronous.inspect}"
|
|
25
|
-
end
|
|
28
|
+
attr_reader :path, :options
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
def self.migrate!(path: default_path, options: {})
|
|
31
|
+
store = new(path: path, options: options)
|
|
32
|
+
store.migrate!
|
|
33
|
+
SCHEMA_VERSION
|
|
34
|
+
ensure
|
|
35
|
+
store&.close
|
|
36
|
+
end
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
def self.prepare_dashboard!(path: default_path, options: {})
|
|
39
|
+
store = new(path: path, options: options)
|
|
40
|
+
store.prepare_dashboard!
|
|
41
|
+
SCHEMA_VERSION
|
|
42
|
+
ensure
|
|
43
|
+
store&.close
|
|
34
44
|
end
|
|
35
45
|
|
|
36
|
-
def
|
|
37
|
-
def mmap_size = mmap ? MMAP_SIZE : 0
|
|
46
|
+
def self.default_path = 'async_background_queue.db'
|
|
38
47
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
PRAGMA journal_size_limit = 67108864;
|
|
47
|
-
PRAGMA wal_autocheckpoint = #{wal_autocheckpoint};
|
|
48
|
-
SQL
|
|
48
|
+
def initialize(path: self.class.default_path, options: {})
|
|
49
|
+
@path = path
|
|
50
|
+
@options = StoreOptions.build(options)
|
|
51
|
+
@pragma_sql = @options.pragma_sql.freeze
|
|
52
|
+
@db = nil
|
|
53
|
+
@schema_checked = false
|
|
54
|
+
@last_cleanup_at = nil
|
|
49
55
|
end
|
|
50
|
-
end
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
def migrate!
|
|
58
|
+
raise SchemaError, 'close the Store before calling migrate!' if connected?
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class_name TEXT NOT NULL,
|
|
60
|
-
args TEXT NOT NULL DEFAULT '[]',
|
|
61
|
-
options TEXT,
|
|
62
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
63
|
-
created_at REAL NOT NULL,
|
|
64
|
-
run_at REAL NOT NULL,
|
|
65
|
-
locked_by INTEGER,
|
|
66
|
-
locked_at REAL
|
|
67
|
-
);
|
|
68
|
-
CREATE INDEX IF NOT EXISTS idx_jobs_pending ON jobs(run_at, id) WHERE status = 'pending';
|
|
69
|
-
SQL
|
|
60
|
+
with_database { |db| migrate_database!(db) }
|
|
61
|
+
@schema_checked = true
|
|
62
|
+
self
|
|
63
|
+
end
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
CLEANUP_AGE = 3600
|
|
65
|
+
alias ensure_database! migrate!
|
|
73
66
|
|
|
74
|
-
|
|
67
|
+
def prepare_dashboard!
|
|
68
|
+
raise SchemaError, 'close the Store before calling prepare_dashboard!' if connected?
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
@
|
|
78
|
-
|
|
79
|
-
@pragma_sql = @options.pragma_sql.freeze
|
|
80
|
-
@db = nil
|
|
81
|
-
@schema_checked = false
|
|
82
|
-
@last_cleanup_at = nil
|
|
70
|
+
with_database { |db| Schema.prepare_dashboard!(db) }
|
|
71
|
+
@schema_checked = true
|
|
72
|
+
self
|
|
83
73
|
end
|
|
84
74
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
db
|
|
88
|
-
configure_database(db)
|
|
89
|
-
db.execute_batch(SCHEMA)
|
|
90
|
-
db.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
91
|
-
db.close
|
|
92
|
-
@schema_checked = true
|
|
75
|
+
def schema_version
|
|
76
|
+
ensure_connection
|
|
77
|
+
@db.get_first_value(SQL::USER_VERSION).to_i
|
|
93
78
|
end
|
|
94
79
|
|
|
95
|
-
def enqueue(class_name, args =
|
|
80
|
+
def enqueue(class_name, args = EMPTY_ARGS, run_at = nil, options: EMPTY_OPTIONS)
|
|
96
81
|
ensure_connection
|
|
97
82
|
now = realtime_now
|
|
98
|
-
@enqueue_stmt.execute(class_name,
|
|
83
|
+
@enqueue_stmt.execute(class_name, dump_args(args), dump_options(options), now, run_at || now)
|
|
99
84
|
@db.last_insert_row_id
|
|
100
85
|
end
|
|
101
86
|
|
|
102
87
|
def fetch(worker_id)
|
|
103
88
|
ensure_connection
|
|
89
|
+
token = generate_claim_token
|
|
104
90
|
now = realtime_now
|
|
105
91
|
|
|
106
|
-
row = transaction
|
|
92
|
+
row = transaction do
|
|
93
|
+
with_statement(@fetch_stmt) { |statement| statement.execute(worker_id, now, token, now).first }
|
|
94
|
+
end
|
|
107
95
|
return unless row
|
|
108
96
|
|
|
109
97
|
maybe_cleanup
|
|
110
|
-
|
|
98
|
+
job_from_row(row, token)
|
|
111
99
|
end
|
|
112
100
|
|
|
113
|
-
def
|
|
101
|
+
def mark_started!(job_id, claim_token:, started_at: realtime_now)
|
|
114
102
|
ensure_connection
|
|
115
|
-
@
|
|
103
|
+
@mark_started_stmt.execute(started_at, job_id, claim_token)
|
|
104
|
+
@db.changes.positive?
|
|
116
105
|
end
|
|
117
106
|
|
|
118
|
-
def
|
|
107
|
+
def complete(job_id, claim_token:, finished_at: realtime_now, duration_ms: nil)
|
|
119
108
|
ensure_connection
|
|
120
|
-
@
|
|
109
|
+
@complete_stmt.execute(finished_at, duration_ms, job_id, claim_token)
|
|
110
|
+
@db.changes.positive?
|
|
121
111
|
end
|
|
122
112
|
|
|
123
|
-
def
|
|
113
|
+
def fail(job_id, claim_token:, error_class: nil, error_message: nil, finished_at: realtime_now, duration_ms: nil)
|
|
114
|
+
ensure_connection
|
|
115
|
+
@fail_stmt.execute(
|
|
116
|
+
finished_at,
|
|
117
|
+
duration_ms,
|
|
118
|
+
error_class&.to_s,
|
|
119
|
+
truncate_message(error_message),
|
|
120
|
+
job_id,
|
|
121
|
+
claim_token
|
|
122
|
+
)
|
|
123
|
+
@db.changes.positive?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def retry_or_fail(
|
|
127
|
+
job_id,
|
|
128
|
+
claim_token:,
|
|
129
|
+
error_class: nil,
|
|
130
|
+
error_message: nil,
|
|
131
|
+
fallback_options: nil,
|
|
132
|
+
finished_at: realtime_now,
|
|
133
|
+
duration_ms: nil
|
|
134
|
+
)
|
|
124
135
|
ensure_connection
|
|
125
136
|
|
|
126
137
|
transaction do
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
:retried
|
|
134
|
-
else
|
|
135
|
-
@fail_stmt.execute(job_id)
|
|
136
|
-
:failed
|
|
137
|
-
end
|
|
138
|
+
stored_options = stored_options_for(job_id, claim_token)
|
|
139
|
+
next unless lease_alive?(job_id, claim_token)
|
|
140
|
+
|
|
141
|
+
policy = retry_policy(stored_options, fallback_options)
|
|
142
|
+
policy_retries?(policy) ? retry_job!(job_id, claim_token, policy, error_class, error_message) :
|
|
143
|
+
fail_job!(job_id, claim_token, error_class, error_message, finished_at, duration_ms)
|
|
138
144
|
end
|
|
139
145
|
end
|
|
140
146
|
|
|
@@ -144,66 +150,172 @@ module Async
|
|
|
144
150
|
@db.changes
|
|
145
151
|
end
|
|
146
152
|
|
|
153
|
+
def next_pending_run_at
|
|
154
|
+
ensure_connection
|
|
155
|
+
with_statement(@next_pending_stmt) { |statement| statement.execute.first&.first }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def data_version
|
|
159
|
+
ensure_connection
|
|
160
|
+
@db.get_first_value(SQL::DATA_VERSION).to_i
|
|
161
|
+
end
|
|
162
|
+
|
|
147
163
|
def close
|
|
148
|
-
return unless
|
|
164
|
+
return unless connected?
|
|
149
165
|
|
|
150
166
|
finalize_statements
|
|
151
|
-
@db.execute(
|
|
167
|
+
@db.execute(SQL::OPTIMIZE) rescue nil
|
|
152
168
|
@db.close
|
|
153
169
|
@db = nil
|
|
170
|
+
@schema_checked = false
|
|
154
171
|
end
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
private
|
|
174
|
+
|
|
175
|
+
def connected?
|
|
176
|
+
@db && !@db.closed?
|
|
158
177
|
end
|
|
159
178
|
|
|
160
|
-
|
|
179
|
+
def open_database
|
|
180
|
+
require_sqlite3
|
|
181
|
+
db = SQLite3::Database.new(@path)
|
|
182
|
+
configure_database(db)
|
|
183
|
+
db
|
|
184
|
+
rescue StandardError
|
|
185
|
+
db&.close unless db&.closed?
|
|
186
|
+
raise
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def with_database
|
|
190
|
+
db = open_database
|
|
191
|
+
yield db
|
|
192
|
+
ensure
|
|
193
|
+
db&.close unless db&.closed?
|
|
194
|
+
end
|
|
161
195
|
|
|
162
196
|
def require_sqlite3
|
|
163
197
|
require 'sqlite3'
|
|
164
198
|
rescue LoadError
|
|
165
199
|
raise LoadError,
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
"sqlite3 gem is required for Async::Background::Queue. " \
|
|
201
|
+
"Add `gem 'sqlite3', '~> 2.0'` to your Gemfile."
|
|
168
202
|
end
|
|
169
203
|
|
|
170
204
|
def ensure_connection
|
|
171
205
|
return if @db && !@db.closed?
|
|
172
206
|
|
|
173
|
-
require_sqlite3
|
|
174
207
|
finalize_statements
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
@db.execute_batch(SCHEMA)
|
|
180
|
-
@db.execute("ALTER TABLE jobs ADD COLUMN options TEXT") rescue nil
|
|
181
|
-
@schema_checked = true
|
|
182
|
-
end
|
|
183
|
-
|
|
208
|
+
db = open_database
|
|
209
|
+
migrate_database!(db) unless @schema_checked
|
|
210
|
+
@schema_checked = true
|
|
211
|
+
@db = db
|
|
184
212
|
prepare_statements
|
|
185
213
|
@last_cleanup_at = monotonic_now
|
|
214
|
+
rescue StandardError
|
|
215
|
+
db&.close unless db&.equal?(@db) || db&.closed?
|
|
216
|
+
reset_connection!
|
|
217
|
+
raise
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def reset_connection!
|
|
221
|
+
@schema_checked = false
|
|
222
|
+
@db&.close unless @db&.closed?
|
|
223
|
+
@db = nil
|
|
186
224
|
end
|
|
187
225
|
|
|
188
226
|
def configure_database(db)
|
|
189
|
-
db.execute(
|
|
227
|
+
db.execute(SQL.busy_timeout(5000))
|
|
190
228
|
db.execute_batch(@pragma_sql)
|
|
191
229
|
end
|
|
192
230
|
|
|
231
|
+
def migrate_database!(db)
|
|
232
|
+
Schema.migrate!(db)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def job_from_row(row, claim_token)
|
|
236
|
+
{
|
|
237
|
+
id: row[0],
|
|
238
|
+
class_name: row[1],
|
|
239
|
+
args: JSON.parse(row[2]),
|
|
240
|
+
options: load_options(row[3]),
|
|
241
|
+
claim_token: claim_token
|
|
242
|
+
}
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def stored_options_for(job_id, claim_token)
|
|
246
|
+
with_statement(@retry_state_stmt) do |statement|
|
|
247
|
+
load_options(statement.execute(job_id, claim_token).first&.first)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def retry_policy(stored_options, fallback_options)
|
|
252
|
+
return Job::Options.new(**stored_options) unless stored_options.empty?
|
|
253
|
+
|
|
254
|
+
normalize_options(fallback_options)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def policy_retries?(policy)
|
|
258
|
+
policy&.retry? && policy.next_attempt <= policy.retry
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def retry_job!(job_id, claim_token, policy, error_class, error_message)
|
|
262
|
+
advanced = policy.with_attempt(policy.next_attempt)
|
|
263
|
+
@retry_stmt.execute(
|
|
264
|
+
realtime_now + advanced.next_retry_delay(advanced.attempt),
|
|
265
|
+
dump_options(advanced.to_h.compact),
|
|
266
|
+
error_class&.to_s,
|
|
267
|
+
truncate_message(error_message),
|
|
268
|
+
job_id,
|
|
269
|
+
claim_token
|
|
270
|
+
)
|
|
271
|
+
@db.changes.positive? ? :retried : nil
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def fail_job!(job_id, claim_token, error_class, error_message, finished_at, duration_ms)
|
|
275
|
+
@fail_stmt.execute(
|
|
276
|
+
finished_at,
|
|
277
|
+
duration_ms,
|
|
278
|
+
error_class&.to_s,
|
|
279
|
+
truncate_message(error_message),
|
|
280
|
+
job_id,
|
|
281
|
+
claim_token
|
|
282
|
+
)
|
|
283
|
+
@db.changes.positive? ? :failed : nil
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def generate_claim_token = SecureRandom.hex(16)
|
|
287
|
+
|
|
288
|
+
def truncate_message(message)
|
|
289
|
+
return if message.nil?
|
|
290
|
+
|
|
291
|
+
string = message.to_s
|
|
292
|
+
string.length > ERROR_MESSAGE_MAX_LEN ? string.byteslice(0, ERROR_MESSAGE_MAX_LEN) : string
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def lease_alive?(job_id, claim_token)
|
|
296
|
+
with_statement(@lease_check_stmt) do |statement|
|
|
297
|
+
!statement.execute(job_id, claim_token).first.nil?
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
193
301
|
def transaction
|
|
194
|
-
@db.execute(
|
|
302
|
+
@db.execute(SQL::BEGIN_IMMEDIATE)
|
|
195
303
|
result = yield
|
|
196
|
-
@db.execute(
|
|
304
|
+
@db.execute(SQL::COMMIT)
|
|
197
305
|
result
|
|
198
|
-
rescue
|
|
199
|
-
@db.execute(
|
|
306
|
+
rescue StandardError
|
|
307
|
+
@db.execute(SQL::ROLLBACK) rescue nil
|
|
200
308
|
raise
|
|
201
309
|
end
|
|
202
310
|
|
|
203
|
-
def
|
|
204
|
-
yield
|
|
311
|
+
def with_statement(statement)
|
|
312
|
+
yield statement
|
|
205
313
|
ensure
|
|
206
|
-
|
|
314
|
+
statement.reset! rescue nil
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def dump_args(args)
|
|
318
|
+
args.equal?(EMPTY_ARGS) ? EMPTY_ARGS_JSON : JSON.generate(args)
|
|
207
319
|
end
|
|
208
320
|
|
|
209
321
|
def dump_options(options)
|
|
@@ -222,43 +334,48 @@ module Async
|
|
|
222
334
|
end
|
|
223
335
|
|
|
224
336
|
def prepare_statements
|
|
225
|
-
@enqueue_stmt = @db.prepare(
|
|
226
|
-
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
@
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
RETURNING id, class_name, args, options
|
|
239
|
-
SQL
|
|
240
|
-
|
|
241
|
-
@complete_stmt = @db.prepare("UPDATE jobs SET status = 'done', locked_by = NULL, locked_at = NULL WHERE id = ?")
|
|
242
|
-
@fail_stmt = @db.prepare("UPDATE jobs SET status = 'failed', locked_by = NULL, locked_at = NULL WHERE id = ?")
|
|
243
|
-
@retry_state_stmt = @db.prepare("SELECT options FROM jobs WHERE id = ?")
|
|
244
|
-
@retry_stmt = @db.prepare(
|
|
245
|
-
"UPDATE jobs SET status = 'pending', locked_by = NULL, locked_at = NULL, run_at = ?, options = ? WHERE id = ?"
|
|
246
|
-
)
|
|
247
|
-
@requeue_stmt = @db.prepare(
|
|
248
|
-
"UPDATE jobs SET status = 'pending', locked_by = NULL, locked_at = NULL " \
|
|
249
|
-
"WHERE status = 'running' AND locked_by = ?"
|
|
250
|
-
)
|
|
251
|
-
@cleanup_stmt = @db.prepare("DELETE FROM jobs WHERE status = 'done' AND created_at < ?")
|
|
337
|
+
@enqueue_stmt = @db.prepare(SQL::INSERT_JOB)
|
|
338
|
+
@fetch_stmt = @db.prepare(SQL::FETCH_NEXT_JOB)
|
|
339
|
+
@mark_started_stmt = @db.prepare(SQL::MARK_STARTED)
|
|
340
|
+
@complete_stmt = @db.prepare(SQL::COMPLETE_JOB)
|
|
341
|
+
@fail_stmt = @db.prepare(SQL::FAIL_JOB)
|
|
342
|
+
@retry_state_stmt = @db.prepare(SQL::RETRY_STATE)
|
|
343
|
+
@lease_check_stmt = @db.prepare(SQL::LEASE_ALIVE)
|
|
344
|
+
@retry_stmt = @db.prepare(SQL::RETRY_JOB)
|
|
345
|
+
@requeue_stmt = @db.prepare(SQL::RECOVER_WORKER)
|
|
346
|
+
@cleanup_done_stmt = @db.prepare(SQL::CLEANUP_DONE)
|
|
347
|
+
@cleanup_failed_stmt = @db.prepare(SQL::CLEANUP_FAILED)
|
|
348
|
+
@next_pending_stmt = @db.prepare(SQL::NEXT_PENDING_RUN_AT)
|
|
252
349
|
end
|
|
253
350
|
|
|
254
351
|
def finalize_statements
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
352
|
+
statements.each { |statement| statement&.close rescue nil }
|
|
353
|
+
clear_statements
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def statements
|
|
357
|
+
[
|
|
358
|
+
@enqueue_stmt,
|
|
359
|
+
@fetch_stmt,
|
|
360
|
+
@mark_started_stmt,
|
|
361
|
+
@complete_stmt,
|
|
362
|
+
@fail_stmt,
|
|
363
|
+
@retry_state_stmt,
|
|
364
|
+
@lease_check_stmt,
|
|
365
|
+
@retry_stmt,
|
|
366
|
+
@requeue_stmt,
|
|
367
|
+
@cleanup_done_stmt,
|
|
368
|
+
@cleanup_failed_stmt,
|
|
369
|
+
@next_pending_stmt
|
|
370
|
+
]
|
|
371
|
+
end
|
|
259
372
|
|
|
260
|
-
|
|
261
|
-
@
|
|
373
|
+
def clear_statements
|
|
374
|
+
@enqueue_stmt = @fetch_stmt = @mark_started_stmt = nil
|
|
375
|
+
@complete_stmt = @fail_stmt = @retry_state_stmt = @lease_check_stmt = nil
|
|
376
|
+
@retry_stmt = @requeue_stmt = nil
|
|
377
|
+
@cleanup_done_stmt = @cleanup_failed_stmt = nil
|
|
378
|
+
@next_pending_stmt = nil
|
|
262
379
|
end
|
|
263
380
|
|
|
264
381
|
def maybe_cleanup
|
|
@@ -266,8 +383,13 @@ module Async
|
|
|
266
383
|
return if (now - @last_cleanup_at) < CLEANUP_INTERVAL
|
|
267
384
|
|
|
268
385
|
@last_cleanup_at = now
|
|
269
|
-
|
|
270
|
-
|
|
386
|
+
cleanup_finished_jobs(realtime_now)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def cleanup_finished_jobs(now)
|
|
390
|
+
@cleanup_done_stmt.execute(now - CLEANUP_AGE)
|
|
391
|
+
@cleanup_failed_stmt.execute(now - FAILED_RETENTION_AGE)
|
|
392
|
+
@db.execute(SQL::INCREMENTAL_VACUUM) if @db.changes > 100
|
|
271
393
|
end
|
|
272
394
|
end
|
|
273
395
|
end
|