skiplock 1.0.10 → 1.0.11

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: 681b41b73f6ddf95620515f3d83454ba1f623d57740248bf84041829c225318d
4
- data.tar.gz: 0433b2e635174ae052b67d7502e4b2477c54736f79ec0acf0ad369a1be976256
3
+ metadata.gz: 432874dc801864f01a8c4de896f330ec62cecdb43d71ededc622ece7d9a17402
4
+ data.tar.gz: 6034dcb3cfa194b186465a5a84227899c8f00fdf4d62d5b20e0370c9c122cde5
5
5
  SHA512:
6
- metadata.gz: 6860b6cff70a8881277fd0ce7d2d76d211723e8c1974a1e2b70db7ea31b05268af6d38b68f2437cc9144d0d896751de205e21890219d3664b0f5ae128e1c6ab0
7
- data.tar.gz: ddf11fbb1b030dc437254f87142127fd135bacbb84301d03fbcb14e3029bc05736e9c02b1d08b6be8f2d6b90e3fbfdf8eff568c63c8d8c9b451c730a1a125b39
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
- notification: none
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 System` for more details
71
- - **notification** (*enumeration*): sets the library to be used for notifying errors and exceptions (`auto, airbrake, bugsnag, exception_notification, none`). Using `auto` will attempt to detect available gems in the application
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, --logging STRING Possible values: true, false, timestamp
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` as shown in the **Configuration** section above. Custom function can also be called whenever an exception occurs; it can be configured in an initializer like below:
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 perform method executions will not provide 'previous' exceptions
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', '--logging STRING', String, 'Possible values: true, false, timestamp')
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
@@ -2,6 +2,7 @@ require 'cron_parser'
2
2
  module Skiplock
3
3
  class Cron
4
4
  def self.setup
5
+ Rails.application.eager_load! if Rails.env.development?
5
6
  cronjobs = []
6
7
  ActiveJob::Base.descendants.each do |j|
7
8
  next unless j.const_defined?('CRON')
@@ -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[' + @worker_num.to_s + ']'}") if @config[:standalone]
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
- end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
25
- job_name = job.job_class
26
- if job.job_class == 'Skiplock::Extension::ProxyJob'
27
- target, method_name = ::YAML.load(job.data['arguments'].first)
28
- job_name = "'#{target.name}.#{method_name}'"
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
@@ -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 = "[Skiplock] V#{Skiplock::VERSION} (Rails #{Rails::VERSION::STRING} | Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
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 Extensions: #{@config[:extensions]}"
39
+ @logger.info "ClassMethod extensions: #{@config[:extensions]}"
38
40
  @logger.info " Purge completion: #{@config[:purge_completion]}"
39
- @logger.info " Notification: #{@config[:notification]}#{(' (' + @notification + ')') if @config[:notification] == 'auto'}"
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 " Logging: #{@config[:logging]}"
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
- @notification = @config[:notification]
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
- @logger.info "Unable to detect any known exception notification gem. Please define custom 'on_error' callback function and disable 'auto' notification in 'config/skiplock.yml'"
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
- end
110
- Skiplock.logger = ActiveSupport::Logger.new(STDOUT)
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
@@ -1,4 +1,4 @@
1
1
  module Skiplock
2
- VERSION = Version = '1.0.10'
2
+ VERSION = Version = '1.0.11'
3
3
  end
4
4
 
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, 'logging' => true, 'graceful_shutdown' => 15, 'min_threads' => 1, 'max_threads' => 5, 'max_retries' => 20, 'notification' => 'none', 'purge_completion' => true, 'queues' => { 'default' => 100, 'mailers' => 999 }, 'workers' => 0 }.freeze
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 || [].freeze
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.10
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-29 00:00:00.000000000 Z
11
+ date: 2021-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob