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