skiplock 1.0.23 → 1.0.24

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: 275e1ddd1a59648e86378889e2e2cc6a4e22943a3fb4211209f20a674744b1b3
4
- data.tar.gz: 7327f5893f36cc4b05163cfd0df47992344b2ca6b97c2ef10ca4f00b219a9ac9
3
+ metadata.gz: a43b01cedd94ea395b0b173e2da3c5c7ab0baf6ccbd35e599127544084ca5589
4
+ data.tar.gz: a908c4eea5b2b75727a70fe5efc248f0b0183e9fe97a37c068b185b9f259a4ba
5
5
  SHA512:
6
- metadata.gz: e9b8afd563ec4ff46838e73ae031a75a69142ee392a05d0e5b612008153b10a6a62a931e22643e817722d0017548573f9cafed6ba943f41da3cf8d0872d76481
7
- data.tar.gz: 8b04173649d9e4a21edb9d47e23d37d5c8d0b08ba8658f46c0fd8341270abf1148a2131e3f57251421caf024d5ce8d9bfb35a648ee9a904f999d6b21bf0fe5ec
6
+ metadata.gz: c5ae8b9bd79e37426710d707fadd2e4d658e541ffdd109a760687f2c14dacfa9d61614130d25be801264ecee86ea63d54ce4d6604c4cc80c7a47ac31752618f6
7
+ data.tar.gz: 42ba6d9047ac028c24389d1915f686f5d553751244bd557a98a8b28af311a53702d6c69078abafcb6696d5d00e441fd8eca43d2e18b11530945fa10a42d15cea
data/README.md CHANGED
@@ -50,12 +50,14 @@ The library is quite small compared to other PostgreSQL job queues (eg. *delay_j
50
50
  ```yaml
51
51
  # config/skiplock.yml (default settings)
52
52
  ---
53
+ graceful_shutdown: 15
53
54
  min_threads: 1
54
55
  max_threads: 10
55
56
  max_retries: 20
56
57
  logfile: skiplock.log
57
58
  loglevel: info
58
59
  notification: custom
60
+ actioncable: false
59
61
  extensions: false
60
62
  purge_completion: true
61
63
  queues:
@@ -64,12 +66,14 @@ The library is quite small compared to other PostgreSQL job queues (eg. *delay_j
64
66
  workers: 0
65
67
  ```
66
68
  Available configuration options are:
69
+ - **graceful_shutdown** (*integer*): sets the number of seconds to wait for jobs to finish before being killed during shutdown
67
70
  - **min_threads** (*integer*): sets minimum number of threads staying idle
68
71
  - **max_threads** (*integer*): sets the maximum number of threads allowed to run jobs
69
72
  - **max_retries** (*integer*): sets the maximum attempt a job will be retrying before it is marked expired. See `Retry system` for more details
70
73
  - **logfile** (*string*): filename for skiplock logs; empty logfile will disable logging
71
74
  - **loglevel** (*string*): sets logging level (`debug, info, warn, error, fatal, unknown`)
72
75
  - **notification** (*string*): sets the library to be used for notifying errors and exceptions (`auto, airbrake, bugsnag, exception_notification, custom`); using `auto` will detect library if available. See `Notification system` for more details
76
+ - **actioncable** (*boolean*): enable or disable usage of ActionCable notification
73
77
  - **extensions** (*multi*): enable or disable the class method extension. See `ClassMethod extension` for more details
74
78
  - **purge_completion** (*boolean*): when set to **true** will delete jobs after they were completed successfully; if set to **false** then the completed jobs should be purged periodically to maximize performance (eg. clean up old jobs after 3 months); queued jobs can manually override using `purge` option
75
79
  - **queues** (*hash*): defines the set of queues with priorities; lower priority takes precedence
@@ -83,6 +87,7 @@ The library is quite small compared to other PostgreSQL job queues (eg. *delay_j
83
87
  ```
84
88
  $ bundle exec skiplock -h
85
89
  Usage: skiplock [options]
90
+ -a, --actioncable YESNO Actioncable notification
86
91
  -e, --environment STRING Rails environment
87
92
  -l, --logfile STRING Log filename
88
93
  -L, --loglevel STRING Log level (debug, info, warn, error, fatal, unknown)
@@ -238,6 +243,10 @@ Code examples of gathering counters information:
238
243
  ```ruby
239
244
  Skiplock::Counter.sum(:expiries)
240
245
  ```
246
+ - get all information in one query
247
+ ```ruby
248
+ Skiplock::Counter.pluck("sum(completions), sum(dispatches), sum(expiries), sum(failures), sum(retries)").first
249
+ ```
241
250
 
242
251
  ## Contributing
243
252
 
data/bin/skiplock CHANGED
@@ -5,6 +5,7 @@ options = {}
5
5
  begin
6
6
  op = OptionParser.new do |opts|
7
7
  opts.banner = "Usage: #{File.basename($0)} [options]"
8
+ opts.on('-a', '--actioncable YESNO', TrueClass, 'Actioncable notification')
8
9
  opts.on('-e', '--environment STRING', String, 'Rails environment')
9
10
  opts.on('-l', '--logfile STRING', String, 'Log filename')
10
11
  opts.on('-L', '--loglevel STRING', String, 'Log level (debug, info, warn, error, fatal, unknown)')
data/lib/skiplock/cron.rb CHANGED
@@ -19,6 +19,7 @@ module Skiplock
19
19
  query = Job.where('cron IS NOT NULL')
20
20
  query = query.where('job_class NOT IN (?)', cronjobs) if cronjobs.count > 0
21
21
  query.delete_all
22
+ rescue
22
23
  end
23
24
 
24
25
  def self.next_schedule_at(cron)
@@ -10,6 +10,7 @@ module Skiplock
10
10
  elsif @config[:extensions].is_a?(Array)
11
11
  @config[:extensions].each { |n| n.constantize.__send__(:extend, Skiplock::Extension) if n.safe_constantize }
12
12
  end
13
+ ActiveJob::Base.__send__(:include, Skiplock::Patch)
13
14
  (caller.any?{ |l| l =~ %r{/rack/} } && @config[:workers] == 0) ? async : Cron.setup
14
15
  end
15
16
 
@@ -17,7 +18,7 @@ module Skiplock
17
18
  setup_logger
18
19
  configure
19
20
  Worker.cleanup(@hostname)
20
- @worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname)
21
+ @worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname, actioncable: @config[:actioncable])
21
22
  Cron.setup if @worker.master
22
23
  @worker.start(**@config)
23
24
  at_exit { @worker.shutdown }
@@ -39,13 +40,13 @@ module Skiplock
39
40
  Signal.trap('TERM') { @shutdown = true }
40
41
  Signal.trap('HUP') { setup_logger }
41
42
  Worker.cleanup(@hostname)
42
- @worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname)
43
+ @worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname, actioncable: @config[:actioncable])
43
44
  ActiveRecord::Base.connection.disconnect! if @config[:workers] > 1
44
45
  (@config[:workers] - 1).times do |n|
45
46
  fork do
46
47
  sleep(0.25*n + 1)
47
48
  ActiveRecord::Base.establish_connection
48
- worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname, master: false)
49
+ worker = Worker.generate(capacity: @config[:max_threads], hostname: @hostname, master: false, actioncable: @config[:actioncable])
49
50
  worker.start(worker_num: n + 1, **@config)
50
51
  loop do
51
52
  sleep 0.5
@@ -75,24 +76,25 @@ module Skiplock
75
76
  @logger.info "-"*(title.length)
76
77
  @logger.info title
77
78
  @logger.info "-"*(title.length)
78
- @logger.info "ClassMethod extensions: #{@config[:extensions]}"
79
- @logger.info " Purge completion: #{@config[:purge_completion]}"
80
- @logger.info " Notification: #{@config[:notification]}"
81
- @logger.info " Max retries: #{@config[:max_retries]}"
82
- @logger.info " Min threads: #{@config[:min_threads]}"
83
- @logger.info " Max threads: #{@config[:max_threads]}"
84
- @logger.info " Environment: #{Rails.env}"
85
- @logger.info " Loglevel: #{@config[:loglevel]}"
86
- @logger.info " Logfile: #{@config[:logfile] || '(disabled)'}"
87
- @logger.info " Workers: #{@config[:workers]}"
88
- @logger.info " Queues: #{@config[:queues].map {|k,v| k + '(' + v.to_s + ')'}.join(', ')}" if @config[:queues].is_a?(Hash)
89
- @logger.info " PID: #{Process.pid}"
79
+ @logger.info "ActionCable notification: #{@config[:actioncable]}#{' (not available)' unless defined?(ActionCable)}"
80
+ @logger.info " ClassMethod extensions: #{@config[:extensions]}"
81
+ @logger.info " Purge completion: #{@config[:purge_completion]}"
82
+ @logger.info " Notification: #{@config[:notification]}"
83
+ @logger.info " Max retries: #{@config[:max_retries]}"
84
+ @logger.info " Min threads: #{@config[:min_threads]}"
85
+ @logger.info " Max threads: #{@config[:max_threads]}"
86
+ @logger.info " Environment: #{Rails.env}"
87
+ @logger.info " Loglevel: #{@config[:loglevel]}"
88
+ @logger.info " Logfile: #{@config[:logfile] || '(disabled)'}"
89
+ @logger.info " Workers: #{@config[:workers]}"
90
+ @logger.info " Queues: #{@config[:queues].map {|k,v| k + '(' + v.to_s + ')'}.join(', ')}" if @config[:queues].is_a?(Hash)
91
+ @logger.info " PID: #{Process.pid}"
90
92
  @logger.info "-"*(title.length)
91
93
  @logger.warn "[Skiplock] Custom notification has no registered 'on_error' callback" if Skiplock.on_errors.count == 0
92
94
  end
93
95
 
94
96
  def configure
95
- @hostname = Socket.ip_address_list.reject(&:ipv4_loopback?).reject(&:ipv6?).map(&:ip_address).join('|')
97
+ @hostname = "#{`hostname -f`.strip}|#{Socket.ip_address_list.reject(&:ipv4_loopback?).reject(&:ipv6?).map(&:ip_address).join('|')}"
96
98
  @config.transform_values! {|v| v.is_a?(String) ? v.downcase : v}
97
99
  @config[:graceful_shutdown] = 300 if @config[:graceful_shutdown] > 300
98
100
  @config[:graceful_shutdown] = nil if @config[:graceful_shutdown] < 0
@@ -133,6 +135,7 @@ module Skiplock
133
135
  else
134
136
  @config[:notification] = 'custom'
135
137
  end
138
+ @logger.error 'ActionCable is not found!' if @config[:actioncable] && !defined?(ActionCable)
136
139
  Skiplock.on_errors.freeze
137
140
  end
138
141
 
@@ -1,4 +1,4 @@
1
1
  module Skiplock
2
- VERSION = Version = '1.0.23'
2
+ VERSION = Version = '1.0.24'
3
3
  end
4
4
 
@@ -12,16 +12,19 @@ module Skiplock
12
12
  self.where(id: delete_ids).delete_all if delete_ids.count > 0
13
13
  end
14
14
 
15
- def self.generate(capacity:, hostname:, master: true)
16
- self.create!(pid: Process.pid, sid: Process.getsid(), master: master, hostname: hostname, capacity: capacity)
15
+ def self.generate(capacity:, hostname:, master: true, actioncable: false)
16
+ worker = self.create!(pid: Process.pid, sid: Process.getsid(), master: master, hostname: hostname, capacity: capacity)
17
17
  rescue
18
- self.create!(pid: Process.pid, sid: Process.getsid(), master: false, hostname: hostname, capacity: capacity)
18
+ worker = self.create!(pid: Process.pid, sid: Process.getsid(), master: false, hostname: hostname, capacity: capacity)
19
+ ensure
20
+ ActionCable.server.broadcast('skiplock', { worker: { op: 'CREATE', id: worker.id, hostname: worker.hostname, master: worker.master, capacity: worker.capacity, pid: worker.pid, sid: worker.sid, created_at: worker.created_at.to_f, updated_at: worker.updated_at.to_f } }) if actioncable && defined?(ActionCable)
19
21
  end
20
22
 
21
23
  def shutdown
22
24
  @running = false
23
25
  @executor.shutdown
24
26
  @executor.kill unless @executor.wait_for_termination(@config[:graceful_shutdown])
27
+ ActionCable.server.broadcast('skiplock', { worker: { op: 'DELETE', id: self.id, hostname: self.hostname, master: self.master, capacity: self.capacity, pid: self.pid, sid: self.sid, created_at: self.created_at.to_f, updated_at: self.updated_at.to_f } }) if @config[:actioncable] && defined?(ActionCable)
25
28
  self.delete
26
29
  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."
27
30
  end
@@ -32,7 +35,15 @@ module Skiplock
32
35
  @pg_config = ActiveRecord::Base.connection.raw_connection.conninfo_hash.compact
33
36
  @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
34
37
  @running = true
35
- @executor = Concurrent::ThreadPoolExecutor.new(min_threads: @config[:min_threads] + 1, max_threads: @config[:max_threads] + 1, max_queue: @config[:max_threads] + 1, idletime: 60, auto_terminate: true, fallback_policy: :abort)
38
+ @map = ::PG::TypeMapByOid.new
39
+ @map.add_coder(::PG::TextDecoder::Boolean.new(oid: 16, name: 'bool'))
40
+ @map.add_coder(::PG::TextDecoder::Integer.new(oid: 20, name: 'int8'))
41
+ @map.add_coder(::PG::TextDecoder::Integer.new(oid: 21, name: 'int2'))
42
+ @map.add_coder(::PG::TextDecoder::Integer.new(oid: 23, name: 'int4'))
43
+ @map.add_coder(::PG::TextDecoder::TimestampUtc.new(oid: 1114, name: 'timestamp'))
44
+ @map.add_coder(::PG::TextDecoder::String.new(oid: 2950, name: 'uuid'))
45
+ @map.add_coder(::PG::TextDecoder::JSON.new(oid: 3802, name: 'jsonb'))
46
+ @executor = Concurrent::ThreadPoolExecutor.new(min_threads: @config[:min_threads] + 1, max_threads: @config[:max_threads] + 1, max_queue: @config[:max_threads] + 1, idletime: 60, auto_terminate: false, fallback_policy: :abort)
36
47
  @executor.post { run }
37
48
  if @config[:standalone]
38
49
  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}]")
@@ -43,17 +54,10 @@ module Skiplock
43
54
  private
44
55
 
45
56
  def establish_connection
46
- map = ::PG::TypeMapByOid.new
47
- map.add_coder(::PG::TextDecoder::Boolean.new(oid: 16, name: 'bool'))
48
- map.add_coder(::PG::TextDecoder::Integer.new(oid: 20, name: 'int8'))
49
- map.add_coder(::PG::TextDecoder::Integer.new(oid: 21, name: 'int2'))
50
- map.add_coder(::PG::TextDecoder::Integer.new(oid: 23, name: 'int4'))
51
- map.add_coder(::PG::TextDecoder::TimestampUtc.new(oid: 1114, name: 'timestamp'))
52
- map.add_coder(::PG::TextDecoder::String.new(oid: 2950, name: 'uuid'))
53
- map.add_coder(::PG::TextDecoder::JSON.new(oid: 3802, name: 'jsonb'))
54
57
  @connection = ::PG.connect(@pg_config)
55
- @connection.type_map_for_results = map
58
+ @connection.type_map_for_results = @map
56
59
  @connection.exec('LISTEN "skiplock::jobs"').clear
60
+ @connection.exec('LISTEN "skiplock::workers"').clear
57
61
  end
58
62
 
59
63
  def run
@@ -85,19 +89,26 @@ module Skiplock
85
89
  next_schedule_at = (result ? result['scheduled_at'].to_f : Float::INFINITY)
86
90
  end
87
91
  end
88
- job_notifications = []
92
+ notifications = { 'skiplock::jobs' => [], 'skiplock::workers' => [] }
89
93
  @connection.wait_for_notify(0.2) do |channel, pid, payload|
90
- job_notifications << payload if payload
94
+ notifications[channel] << payload if payload
91
95
  loop do
92
96
  payload = @connection.notifies
93
97
  break unless @running && payload
94
- job_notifications << payload[:extra]
98
+ notifications[payload[:relname]] << payload[:extra]
95
99
  end
96
- job_notifications.each do |n|
100
+ notifications['skiplock::jobs'].each do |n|
97
101
  op, id, worker_id, job_class, queue_name, running, expired_at, finished_at, scheduled_at = n.split(',')
102
+ ActionCable.server.broadcast('skiplock', { job: { op: op, id: id, worker_id: worker_id, job_class: job_class, queue_name: queue_name, running: (running == 'true'), expired_at: expired_at.to_f, finished_at: finished_at.to_f, scheduled_at: scheduled_at.to_f } }) if self.master && @config[:actioncable] && defined?(ActionCable)
98
103
  next if op == 'DELETE' || running == 'true' || expired_at.to_f > 0 || finished_at.to_f > 0
99
104
  next_schedule_at = scheduled_at.to_f if scheduled_at.to_f < next_schedule_at
100
105
  end
106
+ if self.master && @config[:actioncable] && defined?(ActionCable)
107
+ notifications['skiplock::workers'].each do |w|
108
+ op, id, hostname, master, capacity, pid, sid, created_at, updated_at = w.split(',')
109
+ ActionCable.server.broadcast('skiplock', { worker: { op: op, id: id, hostname: hostname, master: (master == 'true'), capacity: capacity.to_i, pid: pid.to_i, sid: sid.to_i, created_at: created_at.to_f, updated_at: updated_at.to_f } })
110
+ end
111
+ end
101
112
  end
102
113
  if Process.clock_gettime(Process::CLOCK_MONOTONIC) - timestamp > 60
103
114
  @connection.exec("UPDATE skiplock.workers SET updated_at = NOW() WHERE id = '#{self.id}'").clear
data/lib/skiplock.rb CHANGED
@@ -11,7 +11,7 @@ require 'skiplock/worker'
11
11
  require 'skiplock/version'
12
12
 
13
13
  module Skiplock
14
- DEFAULT_CONFIG = { 'extensions' => false, 'logfile' => 'skiplock.log', 'loglevel' => 'info', 'graceful_shutdown' => 15, 'min_threads' => 1, 'max_threads' => 10, 'max_retries' => 20, 'notification' => 'custom', 'purge_completion' => true, 'queues' => { 'default' => 100, 'mailers' => 999 }, 'workers' => 0 }.freeze
14
+ DEFAULT_CONFIG = { 'graceful_shutdown' => 15, 'min_threads' => 1, 'max_threads' => 10, 'max_retries' => 20, 'logfile' => 'skiplock.log', 'loglevel' => 'info', 'notification' => 'custom', 'actioncable' => false, 'extensions' => false, 'purge_completion' => true, 'queues' => { 'default' => 100, 'mailers' => 999 }, 'workers' => 0 }.freeze
15
15
 
16
16
  def self.logger=(l)
17
17
  @logger = l
@@ -34,5 +34,4 @@ module Skiplock
34
34
  def self.table_name_prefix
35
35
  'skiplock.'
36
36
  end
37
- end
38
- ActiveJob::Base.__send__(:include, Skiplock::Patch)
37
+ end
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.23
4
+ version: 1.0.24
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-18 00:00:00.000000000 Z
11
+ date: 2021-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob