skiplock 1.0.10 → 1.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -11
- data/bin/skiplock +2 -2
- data/lib/skiplock/cron.rb +1 -0
- data/lib/skiplock/dispatcher.rb +1 -2
- data/lib/skiplock/job.rb +12 -7
- data/lib/skiplock/manager.rb +29 -24
- data/lib/skiplock/version.rb +1 -1
- data/lib/skiplock.rb +2 -2
- 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: 432874dc801864f01a8c4de896f330ec62cecdb43d71ededc622ece7d9a17402
|
4
|
+
data.tar.gz: 6034dcb3cfa194b186465a5a84227899c8f00fdf4d62d5b20e0370c9c122cde5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89f0ef53c0740cf5522e20aa1473ed9020bf5d116054acbb77f451472c1d9af2b123b47cdc593efb4545383829609537a4e2395b4c23c7482f04cff04b11ceab
|
7
|
+
data.tar.gz: 640fa077844855e312b6018d2858cb495915fba0f167c711540ff3b713c66dfc5aa76ca283d9dffaf3d778f37c7025a1f3f7ee3728d4120d87bc25079717bc60
|
data/README.md
CHANGED
@@ -50,12 +50,12 @@ 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
|
-
extensions: false
|
54
|
-
logging: true
|
55
53
|
min_threads: 1
|
56
54
|
max_threads: 5
|
57
55
|
max_retries: 20
|
58
|
-
|
56
|
+
logfile: log/skiplock.log
|
57
|
+
notification: custom
|
58
|
+
extensions: false
|
59
59
|
purge_completion: true
|
60
60
|
queues:
|
61
61
|
default: 200
|
@@ -63,12 +63,12 @@ The library is quite small compared to other PostgreSQL job queues (eg. *delay_j
|
|
63
63
|
workers: 0
|
64
64
|
```
|
65
65
|
Available configuration options are:
|
66
|
-
- **extensions** (*boolean*): enable or disable the class method extension. See `ClassMethod extension` for more details
|
67
|
-
- **logging** (*boolean*): enable or disable file logging capability; the log file is stored at log/skiplock.log
|
68
66
|
- **min_threads** (*integer*): sets minimum number of threads staying idle
|
69
67
|
- **max_threads** (*integer*): sets the maximum number of threads allowed to run jobs
|
70
|
-
- **max_retries** (*integer*): sets the maximum attempt a job will be retrying before it is marked expired. See `Retry
|
71
|
-
- **
|
68
|
+
- **max_retries** (*integer*): sets the maximum attempt a job will be retrying before it is marked expired. See `Retry system` for more details
|
69
|
+
- **logfile** (*string*): path filename for skiplock logs; empty logfile will disable logging
|
70
|
+
- **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
|
71
|
+
- **extensions** (*boolean*): enable or disable the class method extension. See `ClassMethod extension` for more details
|
72
72
|
- **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)
|
73
73
|
- **queues** (*hash*): defines the set of queues with priorities; lower priority takes precedence
|
74
74
|
- **workers** (*integer*) sets the maximum number of processes when running in standalone mode using the `skiplock` executable; setting this to **0** will enable **async mode**
|
@@ -82,7 +82,7 @@ The library is quite small compared to other PostgreSQL job queues (eg. *delay_j
|
|
82
82
|
$ bundle exec skiplock -h
|
83
83
|
Usage: skiplock [options]
|
84
84
|
-e, --environment STRING Rails environment
|
85
|
-
-l, --
|
85
|
+
-l, --logfile STRING Full path to logfile
|
86
86
|
-s, --graceful-shutdown NUM Number of seconds to wait for graceful shutdown
|
87
87
|
-r, --max-retries NUM Number of maxixum retries
|
88
88
|
-t, --max-threads NUM Number of maximum threads
|
@@ -158,17 +158,17 @@ If the retry attempt limit configured in ActiveJob has been reached, then the co
|
|
158
158
|
If the `retry_on` block is not defined, then the built-in retry system of `skiplock` will kick in automatically. The retrying schedule is using an exponential formula (5 + 2**attempt). The `skiplock` configuration `max_retries` determines the the limit of attempts before the failing job is marked as expired. The maximum retry limit can be set as high as 20; this allows up to 12 days of retrying before the job is marked as expired.
|
159
159
|
|
160
160
|
## Notification system
|
161
|
-
`Skiplock` can use existing exception notification library to notify errors and exceptions. It supports `airbrake`, `bugsnag`, and `exception_notification
|
161
|
+
`Skiplock` can use existing exception notification library to notify errors and exceptions. It supports `airbrake`, `bugsnag`, and `exception_notification`. Custom notification can also be called whenever an exception occurs; it can be configured in an initializer like below:
|
162
162
|
```ruby
|
163
163
|
# config/initializers/skiplock.rb
|
164
164
|
Skiplock.on_error do |ex, previous|
|
165
165
|
if ex.backtrace != previous.try(:backtrace)
|
166
166
|
# sends custom email on new exceptions only
|
167
167
|
# the same repeated exceptions will only be sent once to avoid SPAM
|
168
|
-
# NOTE: exceptions generated from Job
|
168
|
+
# NOTE: exceptions generated from Job executions will not provide 'previous' exceptions
|
169
169
|
end
|
170
170
|
end
|
171
|
-
# supports multiple on_error callbacks
|
171
|
+
# supports multiple 'on_error' event callbacks
|
172
172
|
```
|
173
173
|
## ClassMethod extension
|
174
174
|
`Skiplock` can add extension to allow all class methods to be performed as a background job; it is disabled in the default configuration. To enable, edit the `config/skiplock.yml` configuration file and change `extensions` to `true`.
|
data/bin/skiplock
CHANGED
@@ -5,7 +5,7 @@ begin
|
|
5
5
|
op = OptionParser.new do |opts|
|
6
6
|
opts.banner = "Usage: #{File.basename($0)} [options]"
|
7
7
|
opts.on('-e', '--environment STRING', String, 'Rails environment')
|
8
|
-
opts.on('-l', '--
|
8
|
+
opts.on('-l', '--logfile STRING', String, 'Full path to logfile')
|
9
9
|
opts.on('-s', '--graceful-shutdown NUM', Integer, 'Number of seconds to wait for graceful shutdown')
|
10
10
|
opts.on('-r', '--max-retries NUM', Integer, 'Number of maxixum retries')
|
11
11
|
opts.on('-t', '--max-threads NUM', Integer, 'Number of maximum threads')
|
@@ -25,4 +25,4 @@ options.transform_keys! { |k| k.to_s.gsub('-', '_').to_sym }
|
|
25
25
|
env = options.delete(:environment)
|
26
26
|
ENV['RAILS_ENV'] = env if env
|
27
27
|
require File.expand_path("config/environment.rb")
|
28
|
-
Skiplock::Manager.new(options.merge(standalone: true))
|
28
|
+
Skiplock::Manager.new(**options.merge(standalone: true))
|
data/lib/skiplock/cron.rb
CHANGED
data/lib/skiplock/dispatcher.rb
CHANGED
@@ -7,7 +7,7 @@ module Skiplock
|
|
7
7
|
@executor = Concurrent::ThreadPoolExecutor.new(min_threads: @config[:min_threads], max_threads: @config[:max_threads], max_queue: @config[:max_threads], idletime: 60, auto_terminate: true, fallback_policy: :discard)
|
8
8
|
@last_dispatch_at = 0
|
9
9
|
@next_schedule_at = Time.now.to_f
|
10
|
-
Process.setproctitle("skiplock-#{@worker.master ? 'master[0]' : 'worker[' +
|
10
|
+
Process.setproctitle("skiplock-#{@worker.master ? 'master[0]' : 'worker[' + worker_num.to_s + ']'}") if @config[:standalone]
|
11
11
|
end
|
12
12
|
|
13
13
|
def run
|
@@ -16,7 +16,6 @@ module Skiplock
|
|
16
16
|
ActiveRecord::Base.connection_pool.with_connection do |connection|
|
17
17
|
connection.exec_query('LISTEN "skiplock::jobs"')
|
18
18
|
if @worker.master
|
19
|
-
Rails.application.eager_load! if Rails.env.development?
|
20
19
|
Dir.mkdir('tmp/skiplock') unless Dir.exist?('tmp/skiplock')
|
21
20
|
check_sync_errors
|
22
21
|
Cron.setup
|
data/lib/skiplock/job.rb
CHANGED
@@ -21,13 +21,15 @@ module Skiplock
|
|
21
21
|
rescue Exception => ex
|
22
22
|
Skiplock.logger.error(ex)
|
23
23
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
unless ex
|
25
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
26
|
+
job_name = job.job_class
|
27
|
+
if job.job_class == 'Skiplock::Extension::ProxyJob'
|
28
|
+
target, method_name = ::YAML.load(job.data['arguments'].first)
|
29
|
+
job_name = "'#{target.name}.#{method_name}'"
|
30
|
+
end
|
31
|
+
Skiplock.logger.info "[Skiplock] Performed #{job_name} (#{job.id}) from queue '#{job.queue_name || 'default'}' in #{end_time - start_time} seconds"
|
29
32
|
end
|
30
|
-
Skiplock.logger.info "[Skiplock] Performed #{job_name} (#{job.id}) from queue '#{job.queue_name || 'default'}' in #{end_time - start_time} seconds"
|
31
33
|
job.dispose(ex, purge_completion: purge_completion, max_retries: max_retries)
|
32
34
|
ensure
|
33
35
|
Thread.current[:skiplock_dispatch_job] = nil
|
@@ -57,6 +59,7 @@ module Skiplock
|
|
57
59
|
def dispose(ex, purge_completion: true, max_retries: 20)
|
58
60
|
dup = self.dup
|
59
61
|
self.running = false
|
62
|
+
self.worker_id = nil
|
60
63
|
self.updated_at = (Time.now > self.updated_at ? Time.now : self.updated_at + 1)
|
61
64
|
if ex
|
62
65
|
self.exception_executions["[#{ex.class.name}]"] = (self.exception_executions["[#{ex.class.name}]"] || 0) + 1 unless self.exception_executions.key?('activejob_retry')
|
@@ -80,6 +83,7 @@ module Skiplock
|
|
80
83
|
self.scheduled_at = Time.at(next_cron_at)
|
81
84
|
self.save!
|
82
85
|
else
|
86
|
+
Skiplock.logger.error "[Skiplock] ERROR: Invalid CRON '#{self.cron}' for Job #{self.job_class}"
|
83
87
|
self.delete
|
84
88
|
end
|
85
89
|
elsif purge_completion
|
@@ -90,7 +94,8 @@ module Skiplock
|
|
90
94
|
self.save!
|
91
95
|
end
|
92
96
|
self
|
93
|
-
rescue
|
97
|
+
rescue Exception => e
|
98
|
+
Skiplock.logger.error(e)
|
94
99
|
File.write("tmp/skiplock/#{self.id}", [dup, ex].to_yaml)
|
95
100
|
nil
|
96
101
|
end
|
data/lib/skiplock/manager.rb
CHANGED
@@ -7,10 +7,10 @@ module Skiplock
|
|
7
7
|
@config.transform_values! {|v| v.is_a?(String) ? v.downcase : v}
|
8
8
|
@config.merge!(config)
|
9
9
|
Module.__send__(:include, Skiplock::Extension) if @config[:extensions] == true
|
10
|
-
return unless @config[:standalone] || (caller.any?{ |l| l =~ %r{/rack/} } && @config[:workers] == 0)
|
10
|
+
return unless @config[:standalone] || (caller.any?{ |l| l =~ %r{/rack/} } && (@config[:workers] == 0 || Rails.env.development?))
|
11
11
|
@config[:hostname] = `hostname -f`.strip
|
12
12
|
do_config
|
13
|
-
banner
|
13
|
+
banner if @config[:standalone]
|
14
14
|
cleanup_workers
|
15
15
|
create_worker
|
16
16
|
ActiveJob::Base.logger = nil
|
@@ -25,27 +25,30 @@ module Skiplock
|
|
25
25
|
@worker.delete
|
26
26
|
end
|
27
27
|
end
|
28
|
+
rescue Exception => ex
|
29
|
+
@logger.error(ex)
|
28
30
|
end
|
29
31
|
|
30
32
|
private
|
31
33
|
|
32
34
|
def banner
|
33
|
-
title = "
|
35
|
+
title = "Skiplock #{Skiplock::VERSION} (Rails #{Rails::VERSION::STRING} | Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
|
34
36
|
@logger.info "-"*(title.length)
|
35
37
|
@logger.info title
|
36
38
|
@logger.info "-"*(title.length)
|
37
|
-
@logger.info "ClassMethod
|
39
|
+
@logger.info "ClassMethod extensions: #{@config[:extensions]}"
|
38
40
|
@logger.info " Purge completion: #{@config[:purge_completion]}"
|
39
|
-
@logger.info " Notification: #{@config[:notification]}
|
41
|
+
@logger.info " Notification: #{@config[:notification]}"
|
40
42
|
@logger.info " Max retries: #{@config[:max_retries]}"
|
41
43
|
@logger.info " Min threads: #{@config[:min_threads]}"
|
42
44
|
@logger.info " Max threads: #{@config[:max_threads]}"
|
43
45
|
@logger.info " Environment: #{Rails.env}"
|
44
|
-
@logger.info "
|
46
|
+
@logger.info " Logfile: #{@config[:logfile] || '(disabled)'}"
|
45
47
|
@logger.info " Workers: #{@config[:workers]}"
|
46
48
|
@logger.info " Queues: #{@config[:queues].map {|k,v| k + '(' + v.to_s + ')'}.join(', ')}" if @config[:queues].is_a?(Hash)
|
47
49
|
@logger.info " PID: #{Process.pid}"
|
48
50
|
@logger.info "-"*(title.length)
|
51
|
+
@logger.warn "[Skiplock] Custom notification has no registered 'on_error' callback" if Skiplock.on_errors.count == 0
|
49
52
|
end
|
50
53
|
|
51
54
|
def cleanup_workers
|
@@ -76,21 +79,31 @@ module Skiplock
|
|
76
79
|
@config[:min_threads] = 0 if @config[:min_threads] < 0
|
77
80
|
@config[:workers] = 0 if @config[:workers] < 0
|
78
81
|
@config[:workers] = 1 if @config[:standalone] && @config[:workers] <= 0
|
82
|
+
@logger = ActiveSupport::Logger.new(STDOUT)
|
83
|
+
@logger.level = Rails.logger.level
|
84
|
+
Skiplock.logger = @logger
|
85
|
+
raise "Cannot create logfile '#{@config[:logfile]}'" if @config[:logfile] && !File.writable?(File.dirname(@config[:logfile]))
|
86
|
+
@config[:logfile] = nil if @config[:logfile].to_s.length == 0
|
87
|
+
if @config[:logfile]
|
88
|
+
@logger.extend(ActiveSupport::Logger.broadcast(::Logger.new(@config[:logfile])))
|
89
|
+
if @config[:standalone]
|
90
|
+
Rails.logger.reopen('/dev/null')
|
91
|
+
Rails.logger.extend(ActiveSupport::Logger.broadcast(@logger))
|
92
|
+
end
|
93
|
+
end
|
79
94
|
@config[:queues].values.each { |v| raise 'Queue value must be an integer' unless v.is_a?(Integer) } if @config[:queues].is_a?(Hash)
|
80
|
-
|
81
|
-
if @notification == 'auto'
|
95
|
+
if @config[:notification] == 'auto'
|
82
96
|
if defined?(Airbrake)
|
83
|
-
@notification = 'airbrake'
|
97
|
+
@config[:notification] = 'airbrake'
|
84
98
|
elsif defined?(Bugsnag)
|
85
|
-
@notification = 'bugsnag'
|
99
|
+
@config[:notification] = 'bugsnag'
|
86
100
|
elsif defined?(ExceptionNotifier)
|
87
|
-
@notification = 'exception_notification'
|
101
|
+
@config[:notification] = 'exception_notification'
|
88
102
|
else
|
89
|
-
|
90
|
-
exit
|
103
|
+
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'"
|
91
104
|
end
|
92
105
|
end
|
93
|
-
case @notification
|
106
|
+
case @config[:notification]
|
94
107
|
when 'airbrake'
|
95
108
|
raise 'airbrake gem not found' unless defined?(Airbrake)
|
96
109
|
Skiplock.on_error do |ex, previous|
|
@@ -106,16 +119,8 @@ module Skiplock
|
|
106
119
|
Skiplock.on_error do |ex, previous|
|
107
120
|
ExceptionNotifier.notify_exception(ex) unless ex.backtrace == previous.try(:backtrace)
|
108
121
|
end
|
109
|
-
|
110
|
-
|
111
|
-
Skiplock.logger.level = Rails.logger.level
|
112
|
-
@logger = Skiplock.logger
|
113
|
-
if @config[:logging]
|
114
|
-
Skiplock.logger.extend(ActiveSupport::Logger.broadcast(::Logger.new('log/skiplock.log')))
|
115
|
-
if @config[:standalone]
|
116
|
-
Rails.logger.reopen('/dev/null')
|
117
|
-
Rails.logger.extend(ActiveSupport::Logger.broadcast(Skiplock.logger))
|
118
|
-
end
|
122
|
+
else
|
123
|
+
@config[:notification] = 'custom'
|
119
124
|
end
|
120
125
|
Skiplock.on_errors.freeze unless Skiplock.on_errors.frozen?
|
121
126
|
end
|
data/lib/skiplock/version.rb
CHANGED
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, '
|
14
|
+
DEFAULT_CONFIG = { 'extensions' => false, 'logfile' => 'log/skiplock.log', 'graceful_shutdown' => 15, 'min_threads' => 1, 'max_threads' => 5, 'max_retries' => 20, 'notification' => 'custom', 'purge_completion' => true, 'queues' => { 'default' => 100, 'mailers' => 999 }, 'workers' => 0 }.freeze
|
15
15
|
|
16
16
|
def self.logger=(l)
|
17
17
|
@logger = l
|
@@ -28,7 +28,7 @@ module Skiplock
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.on_errors
|
31
|
-
@on_errors || []
|
31
|
+
@on_errors || []
|
32
32
|
end
|
33
33
|
|
34
34
|
def self.table_name_prefix
|
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.11
|
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-08-
|
11
|
+
date: 2021-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|