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 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