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 +4 -4
- data/README.md +9 -0
- data/bin/skiplock +1 -0
- data/lib/skiplock/cron.rb +1 -0
- data/lib/skiplock/manager.rb +19 -16
- data/lib/skiplock/version.rb +1 -1
- data/lib/skiplock/worker.rb +28 -17
- data/lib/skiplock.rb +2 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a43b01cedd94ea395b0b173e2da3c5c7ab0baf6ccbd35e599127544084ca5589
|
4
|
+
data.tar.gz: a908c4eea5b2b75727a70fe5efc248f0b0183e9fe97a37c068b185b9f259a4ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/lib/skiplock/manager.rb
CHANGED
@@ -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 "
|
79
|
-
@logger.info "
|
80
|
-
@logger.info "
|
81
|
-
@logger.info "
|
82
|
-
@logger.info "
|
83
|
-
@logger.info "
|
84
|
-
@logger.info "
|
85
|
-
@logger.info "
|
86
|
-
@logger.info "
|
87
|
-
@logger.info "
|
88
|
-
@logger.info "
|
89
|
-
@logger.info "
|
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
|
|
data/lib/skiplock/version.rb
CHANGED
data/lib/skiplock/worker.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
92
|
+
notifications = { 'skiplock::jobs' => [], 'skiplock::workers' => [] }
|
89
93
|
@connection.wait_for_notify(0.2) do |channel, pid, payload|
|
90
|
-
|
94
|
+
notifications[channel] << payload if payload
|
91
95
|
loop do
|
92
96
|
payload = @connection.notifies
|
93
97
|
break unless @running && payload
|
94
|
-
|
98
|
+
notifications[payload[:relname]] << payload[:extra]
|
95
99
|
end
|
96
|
-
|
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 = { '
|
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.
|
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-
|
11
|
+
date: 2021-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|