skiplock 1.0.18 → 1.0.20

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8112589dcbdf58a743529ec9bf5ddd8371791e83e79c0d455ed42cbca05ee5eb
4
- data.tar.gz: 149f826e93ecd8ac6165a8db46a8e7e234636a4481e68bf09d208b04a1dea1ce
3
+ metadata.gz: 982ed70ce969943c421cad0923a280c87c4c26ea90da5a3451ce73becd5db42e
4
+ data.tar.gz: 9f68becb9ab21e00816315d1e2ff20058d2f5942a3688ed78959018e3ccf940f
5
5
  SHA512:
6
- metadata.gz: 437f622f2398a0a6c965ea1720f77f11b9241373f277e249e4f3d39afbd3bccc7d8ad2b919abd141ebc5bc6a2543408a7940e53f2968c3cda9fc0519ac387910
7
- data.tar.gz: '0473939dc2497842ff66fb02e2f74371f7281d58cc5ac82f1999b669f5b45e526d5d75054a3cc95594e26fc5f4e4aafc42e15d2f7336b88223256d6f7ac46d4e'
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 (record.running = TRUE) THEN
50
- IF (record.executions > 0) THEN
51
- INSERT INTO skiplock.counters (day,retries) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET retries = skiplock.counters.retries + 1;
52
- ELSE
53
- INSERT INTO skiplock.counters (day,dispatches) VALUES (NOW(),1) ON CONFLICT (day) DO UPDATE SET dispatches = skiplock.counters.dispatches + 1;
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 = 't'"
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 :activejob_retry
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].activejob_retry = true
25
- Thread.current[:skiplock_job].data['activejob_retry'] = true
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.activejob_retry
65
- if (self.executions.to_i >= @max_retries + 1) || self.data.key?('activejob_retry') || @exception.is_a?(Skiplock::Extension::ProxyError)
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.activejob_retry = false
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.activejob_retry
126
- Skiplock.logger.error("[Skiplock] Job #{self.job_class} (#{self.id}) was interrupted by an exception#{ ' (rescued and retried by ActiveJob)' if self.activejob_retry }")
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.activejob_retry
144
+ self.finished_at ||= Time.now if self.data.key?('result') && !self.activejob_error
143
145
  self.dispose
144
146
  end
145
147
  end
@@ -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
- @config.transform_values! {|v| v.is_a?(String) ? v.downcase : v}
8
- @hostname = Socket.gethostname
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
- if (caller.any?{ |l| l =~ %r{/rack/} } && @config[:workers] == 0)
12
- Worker.cleanup(@hostname)
13
- @worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname)
14
- @worker.start(**@config)
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
- @logger.info "[Skiplock] Shutdown completed."
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
- Rails.application.eager_load! if Rails.env.development?
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.downcase)
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].downcase.to_sym
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')))
@@ -1,7 +1,7 @@
1
1
  module Skiplock
2
2
  module Patch
3
3
  def enqueue(options = {})
4
- self.instance_variable_set('@skiplock_options', options)
4
+ @skiplock_options = options
5
5
  super
6
6
  end
7
7
  end
@@ -1,4 +1,4 @@
1
1
  module Skiplock
2
- VERSION = Version = '1.0.18'
2
+ VERSION = Version = '1.0.20'
3
3
  end
4
4
 
@@ -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-#{self.master ? 'master[0]' : 'worker[' + worker_num.to_s + ']'}") if @config[:standalone]
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
- error = false
55
- listen = false
56
- next_schedule_at = Time.now.to_f
57
- timestamp = Process.clock_gettime(Process::CLOCK_MONOTONIC)
58
- while @running
59
- Rails.application.reloader.wrap do
60
- begin
61
- unless listen
62
- @connection = self.class.connection
63
- @connection.exec_query('LISTEN "skiplock::jobs"')
64
- listen = true
65
- end
66
- if error
67
- unless @connection.active?
68
- @connection.reconnect!
69
- sleep(0.5)
70
- @connection.exec_query('LISTEN "skiplock::jobs"')
71
- next_schedule_at = Time.now.to_f
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
- Job.flush if self.master
74
- error = false
75
- end
76
- if Time.now.to_f >= next_schedule_at && @executor.remaining_capacity > 0
77
- job = get_next_available_job
78
- if job.try(:running)
79
- @executor.post { Rails.application.reloader.wrap { job.execute(purge_completion: @config[:purge_completion], max_retries: @config[:max_retries]) } }
80
- else
81
- next_schedule_at = (job ? job.scheduled_at.to_f : Float::INFINITY)
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
- end
84
- job_notifications = []
85
- @connection.raw_connection.wait_for_notify(0.4) do |channel, pid, payload|
86
- job_notifications << payload if payload
87
- loop do
88
- payload = @connection.raw_connection.notifies
89
- break unless @running && payload
90
- job_notifications << payload[:extra]
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
- job_notifications.each do |n|
93
- op, id, worker_id, job_class, queue_name, running, expired_at, finished_at, scheduled_at = n.split(',')
94
- next if op == 'DELETE' || running == 'true' || expired_at.to_f > 0 || finished_at.to_f > 0
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
- if Process.clock_gettime(Process::CLOCK_MONOTONIC) - timestamp > 60
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.18
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-12 00:00:00.000000000 Z
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.0.3
112
+ rubygems_version: 3.2.22
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: ActiveJob Queue Adapter for PostgreSQL