skiplock 1.0.18 → 1.0.20
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 +2 -2
- data/bin/skiplock +1 -0
- data/lib/generators/skiplock/templates/migration.rb.erb +14 -12
- data/lib/skiplock/job.rb +12 -10
- data/lib/skiplock/manager.rb +25 -18
- data/lib/skiplock/patch.rb +1 -1
- data/lib/skiplock/version.rb +1 -1
- data/lib/skiplock/worker.rb +62 -64
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 982ed70ce969943c421cad0923a280c87c4c26ea90da5a3451ce73becd5db42e
|
4
|
+
data.tar.gz: 9f68becb9ab21e00816315d1e2ff20058d2f5942a3688ed78959018e3ccf940f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fb583418cf8c0a190f9b651ecd95effba2d4b415f91bf6fbd452d2271f02534e7c5823e360f1e7a258cdd0a22b14cc99d44915007e9c32a186371748c086144
|
7
|
+
data.tar.gz: 6aa546ad0a538213ae9587e667543027fb2ca080e7623b3f409fe6303c005085f39fd27c310a690a2da9fbb50321d97519ed9aee7bb4922a126c0a280f6ad5fe
|
data/README.md
CHANGED
@@ -201,9 +201,9 @@ To enable extension for specific classes and modules only then set the configura
|
|
201
201
|
```ruby
|
202
202
|
Session.skiplock(wait: 5.minutes, queue: 'maintenance').cleanup
|
203
203
|
```
|
204
|
-
- Queue class method `charge` of class `Subscription` as background job to run tomorrow at noon
|
204
|
+
- Queue class method `charge` of class `Subscription` as background job to run tomorrow at noon without purging
|
205
205
|
```ruby
|
206
|
-
Subscription.skiplock(wait_until: Date.tomorrow.noon).charge(amount: 100)
|
206
|
+
Subscription.skiplock(purge: false, wait_until: Date.tomorrow.noon).charge(amount: 100)
|
207
207
|
```
|
208
208
|
|
209
209
|
## Fault tolerant
|
data/bin/skiplock
CHANGED
@@ -7,6 +7,7 @@ begin
|
|
7
7
|
opts.banner = "Usage: #{File.basename($0)} [options]"
|
8
8
|
opts.on('-e', '--environment STRING', String, 'Rails environment')
|
9
9
|
opts.on('-l', '--logfile STRING', String, 'Log filename')
|
10
|
+
opts.on('-L', '--loglevel STRING', String, 'Log level (debug, info, warn, error, fatal, unknown)')
|
10
11
|
opts.on('-s', '--graceful-shutdown NUM', Integer, 'Number of seconds to wait for graceful shutdown')
|
11
12
|
opts.on('-r', '--max-retries NUM', Integer, 'Number of maxixum retries')
|
12
13
|
opts.on('-t', '--max-threads NUM', Integer, 'Number of maximum threads')
|
@@ -46,18 +46,20 @@ class CreateSkiplockSchema < ActiveRecord::Migration<%= "[#{ActiveRecord::VERSIO
|
|
46
46
|
IF (record.running = TRUE) THEN
|
47
47
|
INSERT INTO skiplock.counters (day,completions) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET completions = skiplock.counters.completions + 1;
|
48
48
|
END IF;
|
49
|
-
ELSIF (
|
50
|
-
IF (record.
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
ELSIF (TG_OP = 'UPDATE') THEN
|
50
|
+
IF (OLD.running = FALSE AND record.running = TRUE) THEN
|
51
|
+
IF (record.executions > 0) THEN
|
52
|
+
INSERT INTO skiplock.counters (day,retries) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET retries = skiplock.counters.retries + 1;
|
53
|
+
ELSE
|
54
|
+
INSERT INTO skiplock.counters (day,dispatches) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET dispatches = skiplock.counters.dispatches + 1;
|
55
|
+
END IF;
|
56
|
+
ELSIF (OLD.finished_at IS NULL AND record.finished_at IS NOT NULL) THEN
|
57
|
+
INSERT INTO skiplock.counters (day,completions) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET completions = skiplock.counters.completions + 1;
|
58
|
+
ELSIF (OLD.running = TRUE AND record.running = FALSE AND record.expired_at IS NOT NULL) THEN
|
59
|
+
INSERT INTO skiplock.counters (day,expiries) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET expiries = skiplock.counters.expiries + 1;
|
60
|
+
ELSIF (OLD.running = TRUE AND record.running = FALSE AND record.expired_at IS NULL AND record.finished_at IS NULL) THEN
|
61
|
+
INSERT INTO skiplock.counters (day,failures) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET failures = skiplock.counters.failures + 1;
|
54
62
|
END IF;
|
55
|
-
ELSIF (record.finished_at IS NOT NULL) THEN
|
56
|
-
INSERT INTO skiplock.counters (day,completions) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET completions = skiplock.counters.completions + 1;
|
57
|
-
ELSIF (record.expired_at IS NOT NULL) THEN
|
58
|
-
INSERT INTO skiplock.counters (day,expiries) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET expiries = skiplock.counters.expiries + 1;
|
59
|
-
ELSIF (TG_OP = 'UPDATE' AND OLD.running = TRUE AND NEW.running = FALSE) THEN
|
60
|
-
INSERT INTO skiplock.counters (day,failures) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET failures = skiplock.counters.failures + 1;
|
61
63
|
END IF;
|
62
64
|
PERFORM pg_notify('skiplock::jobs', CONCAT(TG_OP,',',record.id::TEXT,',',record.worker_id::TEXT,',',record.job_class,',',record.queue_name,',',record.running::TEXT,',',CAST(EXTRACT(EPOCH FROM record.expired_at) AS FLOAT)::TEXT,',',CAST(EXTRACT(EPOCH FROM record.finished_at) AS FLOAT)::TEXT,',',CAST(EXTRACT(EPOCH FROM CASE WHEN record.scheduled_at IS NULL THEN record.updated_at ELSE record.scheduled_at END) AS FLOAT)::TEXT));
|
63
65
|
RETURN NULL;
|
@@ -85,7 +87,7 @@ class CreateSkiplockSchema < ActiveRecord::Migration<%= "[#{ActiveRecord::VERSIO
|
|
85
87
|
execute "CREATE INDEX jobs_retry_index ON skiplock.jobs(scheduled_at) WHERE running = FALSE AND executions IS NOT NULL AND expired_at IS NULL AND finished_at IS NULL"
|
86
88
|
execute "CREATE INDEX jobs_cron_index ON skiplock.jobs(scheduled_at ASC NULLS FIRST, priority ASC NULLS LAST, created_at ASC) WHERE cron IS NOT NULL AND finished_at IS NULL"
|
87
89
|
execute "CREATE UNIQUE INDEX jobs_unique_cron_index ON skiplock.jobs (job_class) WHERE cron IS NOT NULL"
|
88
|
-
execute "CREATE UNIQUE INDEX workers_unique_master_index ON skiplock.workers(hostname) WHERE master =
|
90
|
+
execute "CREATE UNIQUE INDEX workers_unique_master_index ON skiplock.workers(hostname) WHERE master = TRUE"
|
89
91
|
end
|
90
92
|
|
91
93
|
def down
|
data/lib/skiplock/job.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Skiplock
|
2
2
|
class Job < ActiveRecord::Base
|
3
3
|
self.implicit_order_column = 'updated_at'
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :activejob_error
|
5
5
|
belongs_to :worker, inverse_of: :jobs, required: false
|
6
6
|
|
7
7
|
def self.dispatch(purge_completion: true, max_retries: 20)
|
@@ -19,16 +19,16 @@ module Skiplock
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.enqueue_at(activejob, timestamp)
|
22
|
+
options = activejob.instance_variable_get('@skiplock_options') || {}
|
22
23
|
timestamp = Time.at(timestamp) if timestamp
|
23
24
|
if Thread.current[:skiplock_job].try(:id) == activejob.job_id
|
24
|
-
Thread.current[:skiplock_job].
|
25
|
-
Thread.current[:skiplock_job].data['
|
25
|
+
Thread.current[:skiplock_job].activejob_error = options[:error]
|
26
|
+
Thread.current[:skiplock_job].data['activejob_error'] = true
|
26
27
|
Thread.current[:skiplock_job].executions = activejob.executions
|
27
28
|
Thread.current[:skiplock_job].exception_executions = activejob.exception_executions
|
28
29
|
Thread.current[:skiplock_job].scheduled_at = timestamp
|
29
30
|
Thread.current[:skiplock_job]
|
30
31
|
else
|
31
|
-
options = activejob.instance_variable_get('@skiplock_options') || {}
|
32
32
|
serialize = activejob.serialize
|
33
33
|
self.create!(serialize.slice(*self.column_names).merge('id' => serialize['job_id'], 'data' => { 'arguments' => serialize['arguments'], 'options' => options }, 'scheduled_at' => timestamp))
|
34
34
|
end
|
@@ -61,8 +61,8 @@ module Skiplock
|
|
61
61
|
self.worker_id = nil
|
62
62
|
self.updated_at = Time.now > self.updated_at ? Time.now : self.updated_at + 1 # in case of clock drifting
|
63
63
|
if @exception
|
64
|
-
self.exception_executions["[#{@exception.class.name}]"] = self.exception_executions["[#{@exception.class.name}]"].to_i + 1 unless self.
|
65
|
-
if (self.executions.to_i >= @max_retries + 1) || self.data.key?('
|
64
|
+
self.exception_executions["[#{@exception.class.name}]"] = self.exception_executions["[#{@exception.class.name}]"].to_i + 1 unless self.data.key?('activejob_error')
|
65
|
+
if (self.executions.to_i >= @max_retries + 1) || self.data.key?('activejob_error') || @exception.is_a?(Skiplock::Extension::ProxyError)
|
66
66
|
self.expired_at = Time.now
|
67
67
|
else
|
68
68
|
self.scheduled_at = Time.now + (5 * 2**self.executions.to_i)
|
@@ -103,11 +103,12 @@ module Skiplock
|
|
103
103
|
|
104
104
|
def execute(purge_completion: true, max_retries: 20)
|
105
105
|
raise 'Job has already been completed' if self.finished_at
|
106
|
+
self.update_columns(running: true, updated_at: Time.now) unless self.running
|
106
107
|
Skiplock.logger.info("[Skiplock] Performing #{self.job_class} (#{self.id}) from queue '#{self.queue_name || 'default'}'...") if Skiplock.logger
|
107
108
|
self.data ||= {}
|
108
109
|
self.data.delete('result')
|
109
110
|
self.exception_executions ||= {}
|
110
|
-
self.
|
111
|
+
self.activejob_error = nil
|
111
112
|
@max_retries = (self.data['options'].key?('max_retries') ? self.data['options']['max_retries'].to_i : max_retries) rescue max_retries
|
112
113
|
@max_retries = 20 if @max_retries < 0 || @max_retries > 20
|
113
114
|
@purge = (self.data['options'].key?('purge') ? self.data['options']['purge'] : purge_completion) rescue purge_completion
|
@@ -122,8 +123,8 @@ module Skiplock
|
|
122
123
|
Skiplock.on_errors.each { |p| p.call(@exception) }
|
123
124
|
end
|
124
125
|
if Skiplock.logger
|
125
|
-
if @exception || self.
|
126
|
-
Skiplock.logger.error("[Skiplock] Job #{self.job_class} (#{self.id}) was interrupted by an exception#{ ' (rescued and retried by ActiveJob)' if self.
|
126
|
+
if @exception || self.activejob_error
|
127
|
+
Skiplock.logger.error("[Skiplock] Job #{self.job_class} (#{self.id}) was interrupted by an exception#{ ' (rescued and retried by ActiveJob)' if self.activejob_error }")
|
127
128
|
if @exception
|
128
129
|
Skiplock.logger.error(@exception.to_s)
|
129
130
|
Skiplock.logger.error(@exception.backtrace.join("\n"))
|
@@ -138,8 +139,9 @@ module Skiplock
|
|
138
139
|
Skiplock.logger.info "[Skiplock] Performed #{job_name} (#{self.id}) from queue '#{self.queue_name || 'default'}' in #{end_time - start_time} seconds"
|
139
140
|
end
|
140
141
|
end
|
142
|
+
@exception || self.activejob_error || self.data['result']
|
141
143
|
ensure
|
142
|
-
self.finished_at ||= Time.now if self.data.key?('result') && !self.
|
144
|
+
self.finished_at ||= Time.now if self.data.key?('result') && !self.activejob_error
|
143
145
|
self.dispose
|
144
146
|
end
|
145
147
|
end
|
data/lib/skiplock/manager.rb
CHANGED
@@ -4,16 +4,16 @@ module Skiplock
|
|
4
4
|
@config = Skiplock::DEFAULT_CONFIG.dup
|
5
5
|
@config.merge!(YAML.load_file('config/skiplock.yml')) rescue nil
|
6
6
|
@config.symbolize_keys!
|
7
|
-
|
8
|
-
|
7
|
+
async if (caller.any?{ |l| l =~ %r{/rack/} } && @config[:workers] == 0)
|
8
|
+
end
|
9
|
+
|
10
|
+
def async
|
9
11
|
configure
|
10
12
|
setup_logger
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
at_exit { @worker.shutdown }
|
16
|
-
end
|
13
|
+
Worker.cleanup(@hostname)
|
14
|
+
@worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname)
|
15
|
+
@worker.start(**@config)
|
16
|
+
at_exit { @worker.shutdown }
|
17
17
|
rescue Exception => ex
|
18
18
|
@logger.error(ex.to_s)
|
19
19
|
@logger.error(ex.backtrace.join("\n"))
|
@@ -21,6 +21,8 @@ module Skiplock
|
|
21
21
|
|
22
22
|
def standalone(**options)
|
23
23
|
@config.merge!(options)
|
24
|
+
configure
|
25
|
+
setup_logger
|
24
26
|
Rails.logger.reopen('/dev/null') rescue Rails.logger.reopen('NUL') # supports Windows NUL device
|
25
27
|
Rails.logger.extend(ActiveSupport::Logger.broadcast(@logger))
|
26
28
|
@config[:workers] = 1 if @config[:workers] <= 0
|
@@ -34,6 +36,7 @@ module Skiplock
|
|
34
36
|
Signal.trap('TERM') { @shutdown = true }
|
35
37
|
Signal.trap('HUP') { setup_logger }
|
36
38
|
(@config[:workers] - 1).times do |n|
|
39
|
+
sleep 0.2
|
37
40
|
fork do
|
38
41
|
sleep 1
|
39
42
|
worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname, master: false)
|
@@ -53,7 +56,9 @@ module Skiplock
|
|
53
56
|
@logger.info "[Skiplock] Terminating signal... Waiting for jobs to finish (up to #{@config[:graceful_shutdown]} seconds)..." if @config[:graceful_shutdown]
|
54
57
|
Process.waitall
|
55
58
|
@worker.shutdown
|
56
|
-
|
59
|
+
rescue Exception => ex
|
60
|
+
@logger.error(ex.to_s)
|
61
|
+
@logger.error(ex.backtrace.join("\n"))
|
57
62
|
end
|
58
63
|
|
59
64
|
private
|
@@ -80,6 +85,8 @@ module Skiplock
|
|
80
85
|
end
|
81
86
|
|
82
87
|
def configure
|
88
|
+
@hostname = Socket.gethostname
|
89
|
+
@config.transform_values! {|v| v.is_a?(String) ? v.downcase : v}
|
83
90
|
@config[:graceful_shutdown] = 300 if @config[:graceful_shutdown] > 300
|
84
91
|
@config[:graceful_shutdown] = nil if @config[:graceful_shutdown] < 0
|
85
92
|
@config[:max_retries] = 20 if @config[:max_retries] > 20
|
@@ -100,6 +107,12 @@ module Skiplock
|
|
100
107
|
raise "Unable to detect any known exception notification library. Please define custom 'on_error' event callbacks and change to 'custom' notification in 'config/skiplock.yml'"
|
101
108
|
end
|
102
109
|
end
|
110
|
+
Rails.application.eager_load! if Rails.env.development?
|
111
|
+
if @config[:extensions] == true
|
112
|
+
Module.__send__(:include, Skiplock::Extension)
|
113
|
+
elsif @config[:extensions].is_a?(Array)
|
114
|
+
@config[:extensions].each { |n| n.constantize.__send__(:extend, Skiplock::Extension) if n.safe_constantize }
|
115
|
+
end
|
103
116
|
case @config[:notification]
|
104
117
|
when 'airbrake'
|
105
118
|
raise 'airbrake gem not found' unless defined?(Airbrake)
|
@@ -119,19 +132,13 @@ module Skiplock
|
|
119
132
|
else
|
120
133
|
@config[:notification] = 'custom'
|
121
134
|
end
|
122
|
-
|
123
|
-
if @config[:extensions] == true
|
124
|
-
Module.__send__(:include, Skiplock::Extension)
|
125
|
-
elsif @config[:extensions].is_a?(Array)
|
126
|
-
@config[:extensions].each { |n| n.constantize.__send__(:extend, Skiplock::Extension) if n.safe_constantize }
|
127
|
-
end
|
128
|
-
Skiplock.on_errors.freeze unless Skiplock.on_errors.frozen?
|
135
|
+
Skiplock.on_errors.freeze
|
129
136
|
end
|
130
137
|
|
131
138
|
def setup_logger
|
132
|
-
@config[:loglevel] = 'info' unless ['debug','info','warn','error','fatal','unknown'].include?(@config[:loglevel].to_s
|
139
|
+
@config[:loglevel] = 'info' unless ['debug','info','warn','error','fatal','unknown'].include?(@config[:loglevel].to_s)
|
133
140
|
@logger = ActiveSupport::Logger.new(STDOUT)
|
134
|
-
@logger.level = @config[:loglevel].
|
141
|
+
@logger.level = @config[:loglevel].to_sym
|
135
142
|
Skiplock.logger = @logger
|
136
143
|
if @config[:logfile].to_s.length > 0
|
137
144
|
@logger.extend(ActiveSupport::Logger.broadcast(::Logger.new(File.join(Rails.root, 'log', @config[:logfile].to_s), 'daily')))
|
data/lib/skiplock/patch.rb
CHANGED
data/lib/skiplock/version.rb
CHANGED
data/lib/skiplock/worker.rb
CHANGED
@@ -23,12 +23,13 @@ module Skiplock
|
|
23
23
|
Job.flush
|
24
24
|
Cron.setup
|
25
25
|
end
|
26
|
+
@num = worker_num
|
26
27
|
@config = config
|
27
28
|
@queues_order_query = @config[:queues].map { |q,v| "WHEN queue_name = '#{q}' THEN #{v}" }.join(' ') if @config[:queues].is_a?(Hash) && @config[:queues].count > 0
|
28
29
|
@running = true
|
29
30
|
@executor = Concurrent::ThreadPoolExecutor.new(min_threads: @config[:min_threads] + 1, max_threads: @config[:max_threads] + 1, max_queue: @config[:max_threads], idletime: 60, auto_terminate: true, fallback_policy: :discard)
|
30
31
|
@executor.post { run }
|
31
|
-
Process.setproctitle("skiplock
|
32
|
+
Process.setproctitle("skiplock: #{self.master ? 'master' : 'cluster'} worker#{(' ' + @num.to_s) if @num > 0 && @config[:workers] > 2} [#{Rails.application.class.name.deconstantize.downcase}:#{Rails.env}]") if @config[:standalone]
|
32
33
|
end
|
33
34
|
|
34
35
|
def shutdown
|
@@ -36,82 +37,79 @@ module Skiplock
|
|
36
37
|
@executor.shutdown
|
37
38
|
@executor.kill unless @executor.wait_for_termination(@config[:graceful_shutdown])
|
38
39
|
self.delete
|
40
|
+
Skiplock.logger.info "[Skiplock] Shutdown of #{self.master ? 'master' : 'cluster'} worker#{(' ' + @num.to_s) if @num > 0 && @config[:workers] > 2} (PID: #{self.pid}) was completed."
|
39
41
|
end
|
40
42
|
|
41
43
|
private
|
42
44
|
|
43
|
-
def get_next_available_job
|
44
|
-
@connection.transaction do
|
45
|
-
job = Job.find_by_sql("SELECT id, running, scheduled_at FROM skiplock.jobs WHERE running = FALSE AND expired_at IS NULL AND finished_at IS NULL ORDER BY scheduled_at ASC NULLS FIRST,#{@queues_order_query ? ' CASE ' + @queues_order_query + ' ELSE NULL END ASC NULLS LAST,' : ''} priority ASC NULLS LAST, created_at ASC FOR UPDATE SKIP LOCKED LIMIT 1").first
|
46
|
-
if job && job.scheduled_at.to_f <= Time.now.to_f
|
47
|
-
job = Job.find_by_sql("UPDATE skiplock.jobs SET running = TRUE, worker_id = '#{self.id}', updated_at = NOW() WHERE id = '#{job.id}' RETURNING *").first
|
48
|
-
end
|
49
|
-
job
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
45
|
def run
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
46
|
+
sleep 3
|
47
|
+
ActiveRecord::Base.connection_pool.with_connection do |connection|
|
48
|
+
Skiplock.logger.info "[Skiplock] Starting in #{@config[:standalone] ? 'standalone' : 'async'} mode (PID: #{self.pid}) with #{@config[:max_threads]} max threads as #{self.master ? 'master' : 'cluster'} worker#{(' ' + @num.to_s) if @num > 0 && @config[:workers] > 2}..."
|
49
|
+
connection.exec_query('LISTEN "skiplock::jobs"')
|
50
|
+
error = false
|
51
|
+
next_schedule_at = Time.now.to_f
|
52
|
+
timestamp = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
53
|
+
while @running
|
54
|
+
Rails.application.reloader.wrap do
|
55
|
+
begin
|
56
|
+
if error
|
57
|
+
unless connection.active?
|
58
|
+
connection.reconnect!
|
59
|
+
sleep(0.5)
|
60
|
+
connection.exec_query('LISTEN "skiplock::jobs"')
|
61
|
+
next_schedule_at = Time.now.to_f
|
62
|
+
end
|
63
|
+
Job.flush if self.master
|
64
|
+
error = false
|
72
65
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
66
|
+
if Time.now.to_f >= next_schedule_at && @executor.remaining_capacity > 0
|
67
|
+
job = nil
|
68
|
+
connection.transaction do
|
69
|
+
result = connection.select_all("SELECT id, running, scheduled_at FROM skiplock.jobs WHERE running = FALSE AND expired_at IS NULL AND finished_at IS NULL ORDER BY scheduled_at ASC NULLS FIRST,#{@queues_order_query ? ' CASE ' + @queues_order_query + ' ELSE NULL END ASC NULLS LAST,' : ''} priority ASC NULLS LAST, created_at ASC FOR UPDATE SKIP LOCKED LIMIT 1").first
|
70
|
+
result = connection.select_all("UPDATE skiplock.jobs SET running = TRUE, worker_id = '#{self.id}', updated_at = NOW() WHERE id = '#{result['id']}' RETURNING *").first if result && result['scheduled_at'].to_f <= Time.now.to_f
|
71
|
+
job = Job.instantiate(result) if result
|
72
|
+
end
|
73
|
+
if job.try(:running)
|
74
|
+
@executor.post do
|
75
|
+
Rails.application.executor.wrap { job.execute(purge_completion: @config[:purge_completion], max_retries: @config[:max_retries]) }
|
76
|
+
end
|
77
|
+
else
|
78
|
+
next_schedule_at = (job ? job.scheduled_at.to_f : Float::INFINITY)
|
79
|
+
end
|
82
80
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
81
|
+
job_notifications = []
|
82
|
+
connection.raw_connection.wait_for_notify(0.2) do |channel, pid, payload|
|
83
|
+
job_notifications << payload if payload
|
84
|
+
loop do
|
85
|
+
payload = connection.raw_connection.notifies
|
86
|
+
break unless @running && payload
|
87
|
+
job_notifications << payload[:extra]
|
88
|
+
end
|
89
|
+
job_notifications.each do |n|
|
90
|
+
op, id, worker_id, job_class, queue_name, running, expired_at, finished_at, scheduled_at = n.split(',')
|
91
|
+
next if op == 'DELETE' || running == 'true' || expired_at.to_f > 0 || finished_at.to_f > 0
|
92
|
+
next_schedule_at = scheduled_at.to_f if scheduled_at.to_f < next_schedule_at
|
93
|
+
end
|
91
94
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
next_schedule_at = scheduled_at.to_f if scheduled_at.to_f < next_schedule_at
|
95
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) - timestamp > 60
|
96
|
+
self.touch
|
97
|
+
timestamp = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
96
98
|
end
|
99
|
+
rescue Exception => ex
|
100
|
+
# most likely error with database connection
|
101
|
+
Skiplock.logger.error(ex.to_s)
|
102
|
+
Skiplock.logger.error(ex.backtrace.join("\n"))
|
103
|
+
Skiplock.on_errors.each { |p| p.call(ex, @last_exception) }
|
104
|
+
error = true
|
105
|
+
wait(5)
|
106
|
+
@last_exception = ex
|
97
107
|
end
|
98
|
-
|
99
|
-
self.touch
|
100
|
-
timestamp = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
101
|
-
end
|
102
|
-
rescue Exception => ex
|
103
|
-
# most likely error with database connection
|
104
|
-
Skiplock.logger.error(ex.to_s)
|
105
|
-
Skiplock.logger.error(ex.backtrace.join("\n"))
|
106
|
-
Skiplock.on_errors.each { |p| p.call(ex, @last_exception) }
|
107
|
-
error = true
|
108
|
-
wait(5)
|
109
|
-
@last_exception = ex
|
108
|
+
sleep(0.3)
|
110
109
|
end
|
111
|
-
sleep(0.3)
|
112
110
|
end
|
111
|
+
connection.exec_query('UNLISTEN *')
|
113
112
|
end
|
114
|
-
@connection.exec_query('UNLISTEN *')
|
115
113
|
end
|
116
114
|
|
117
115
|
def wait(timeout)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skiplock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tin Vo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
requirements: []
|
112
|
-
rubygems_version: 3.
|
112
|
+
rubygems_version: 3.2.22
|
113
113
|
signing_key:
|
114
114
|
specification_version: 4
|
115
115
|
summary: ActiveJob Queue Adapter for PostgreSQL
|