skiplock 1.0.17 → 1.0.18

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: d9533e3df15a24e784000c160588b96207271986bf927bff095752e556c3376a
4
- data.tar.gz: dcc40ab796f55fc41363d671d5e4f73600c58c468d7c4543e496fad38c3e9a8a
3
+ metadata.gz: 8112589dcbdf58a743529ec9bf5ddd8371791e83e79c0d455ed42cbca05ee5eb
4
+ data.tar.gz: 149f826e93ecd8ac6165a8db46a8e7e234636a4481e68bf09d208b04a1dea1ce
5
5
  SHA512:
6
- metadata.gz: 0a0c21835b52f749022d7f3a135f041d122425defffaf6aebb6d8510d4dd0836aed2fb9977daff9e7d3bf2547e139d2dd3ea49f5f55a869ce851c93aaeb56a12
7
- data.tar.gz: d94c6801c82e1bddb12cff0152b71e8554d03de4d28f51ec0d742807311adecca1b3b142aeadf3cd5e81336416b03206d81848f6da6abf8e0f8c726631a5d898
6
+ metadata.gz: 437f622f2398a0a6c965ea1720f77f11b9241373f277e249e4f3d39afbd3bccc7d8ad2b919abd141ebc5bc6a2543408a7940e53f2968c3cda9fc0519ac387910
7
+ data.tar.gz: '0473939dc2497842ff66fb02e2f74371f7281d58cc5ac82f1999b669f5b45e526d5d75054a3cc95594e26fc5f4e4aafc42e15d2f7336b88223256d6f7ac46d4e'
data/README.md CHANGED
@@ -106,7 +106,7 @@ Inside the Rails application:
106
106
  MyJob.set(wait_until: Day.tomorrow.noon).perform_later(1,2,3)
107
107
  ```
108
108
  - Skiplock supports custom options which override the global `Skiplock` configuration options for specified jobs
109
- - **purge** (*boolean*): whether to remove this job after it has ran successfully
109
+ - **purge** (*boolean*): whether to remove this job after it has completed successfully
110
110
  - **max_retries** (*integer*): set maximum retry attempt for this job
111
111
  ```ruby
112
112
  MyJob.set(purge: false, max_retries: 5).perform_later(1,2,3)
@@ -119,7 +119,8 @@ Outside the Rails application:
119
119
  - with scheduling, priority, queue, arguments and custom options
120
120
  ```sql
121
121
  INSERT INTO skiplock.jobs(job_class, queue_name, priority, scheduled_at, data)
122
- VALUES ('MyJob', 'my_queue', 10, NOW() + INTERVAL '5 min', '{"arguments":[1,2,3],"options":{"purge":false,"max_retries":5}}');
122
+ VALUES ('MyJob', 'my_queue', 10, NOW() + INTERVAL '5 min',
123
+ '{"arguments":[1,2,3],"options":{"purge":false,"max_retries":5}}');
123
124
  ```
124
125
  ## Queue priority vs Job priority
125
126
  *Why do queues use priorities when jobs already have priorities?*
@@ -183,10 +184,14 @@ If the `retry_on` block is not defined, then the built-in retry system of `Skipl
183
184
  # supports multiple 'on_error' event callbacks
184
185
  ```
185
186
  ## ClassMethod extension
186
- `Skiplock` can add extension to allow class methods to be performed as a background job; it is disabled in the default configuration. To enable globally for all classes and modules, edit the `config/skiplock.yml` configuration file and change `extensions` to `true`; this can expose remote execution if the `skiplock.jobs` database table is not secured properly. To enable extension for specific classes and modules only then set the configuration to an array of names of the classes and modules eg. `['MyClass', 'MyModule']`
187
- - An example of remote execution if the extension is enabled globally (ie: configuration is set to `true`) and attacker can insert `skiplock.jobs`
187
+ `Skiplock` can add extension to allow class methods to be performed as a background job; it is disabled in the default configuration. To enable globally for all classes and modules, edit the `config/skiplock.yml` configuration file and change `extensions` to `true`; this can expose remote code execution if the `skiplock.jobs` database table is not secured properly.
188
+
189
+ To enable extension for specific classes and modules only then set the configuration to an array of names of the classes and modules eg. `['MyClass', 'MyModule']`
190
+ - An example of remote code execution if the extension is enabled globally (ie: configuration is set to `true`) and attacker can insert `skiplock.jobs`
188
191
  ```sql
189
- INSERT INTO skiplock.jobs(job_class, data) VALUES ('Skiplock::Extension::ProxyJob', '{"arguments":["---\n- !ruby/module ''Kernel''\n- :system\n- - rm -rf /tmp/*\n"]}');
192
+ INSERT INTO skiplock.jobs(job_class, data)
193
+ VALUES ('Skiplock::Extension::ProxyJob',
194
+ '{"arguments":["---\n- !ruby/module ''Kernel''\n- :system\n- - rm -rf /tmp/*\n"]}');
190
195
  ```
191
196
  - Queue class method `generate_thumbnails` of class `Image` as background job to run as soon as possible
192
197
  ```ruby
data/lib/skiplock/cron.rb CHANGED
@@ -11,7 +11,7 @@ module Skiplock
11
11
  if time
12
12
  job.cron = cron
13
13
  job.running = false
14
- job.scheduled_at = Time.at(time)
14
+ job.scheduled_at = Time.at(time) unless job.try(:executions).to_i > 0 # do not update schedule of retrying cron jobs
15
15
  job.save
16
16
  cronjobs << j.name
17
17
  end
@@ -11,9 +11,12 @@ module Skiplock
11
11
  end
12
12
  end
13
13
 
14
+ class ProxyError < StandardError; end
15
+
14
16
  class ProxyJob < ActiveJob::Base
15
17
  def perform(yml)
16
- target, method_name, args = ::YAML.load(yml)
18
+ target, method_name, args = ::YAML.load(yml) rescue nil
19
+ raise ProxyError, "Skiplock extension is not allowed for:\n#{yml}" unless target.respond_to?(:skiplock)
17
20
  target.__send__(method_name, *args)
18
21
  end
19
22
  end
data/lib/skiplock/job.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Skiplock
2
2
  class Job < ActiveRecord::Base
3
- self.implicit_order_column = 'created_at'
3
+ self.implicit_order_column = 'updated_at'
4
4
  attr_accessor :activejob_retry
5
5
  belongs_to :worker, inverse_of: :jobs, required: false
6
6
 
@@ -22,6 +22,7 @@ module Skiplock
22
22
  timestamp = Time.at(timestamp) if timestamp
23
23
  if Thread.current[:skiplock_job].try(:id) == activejob.job_id
24
24
  Thread.current[:skiplock_job].activejob_retry = true
25
+ Thread.current[:skiplock_job].data['activejob_retry'] = true
25
26
  Thread.current[:skiplock_job].executions = activejob.executions
26
27
  Thread.current[:skiplock_job].exception_executions = activejob.exception_executions
27
28
  Thread.current[:skiplock_job].scheduled_at = timestamp
@@ -33,7 +34,7 @@ module Skiplock
33
34
  end
34
35
  end
35
36
 
36
- # resynchronize jobs that could not commit to database and retry any abandoned jobs
37
+ # resynchronize jobs that could not commit to database and reset any abandoned jobs for retry
37
38
  def self.flush
38
39
  Dir.mkdir('tmp/skiplock') unless Dir.exist?('tmp/skiplock')
39
40
  Dir.glob('tmp/skiplock/*').each do |f|
@@ -61,7 +62,7 @@ module Skiplock
61
62
  self.updated_at = Time.now > self.updated_at ? Time.now : self.updated_at + 1 # in case of clock drifting
62
63
  if @exception
63
64
  self.exception_executions["[#{@exception.class.name}]"] = self.exception_executions["[#{@exception.class.name}]"].to_i + 1 unless self.activejob_retry
64
- if (self.executions.to_i >= @max_retries + 1) || self.activejob_retry
65
+ if (self.executions.to_i >= @max_retries + 1) || self.data.key?('activejob_retry') || @exception.is_a?(Skiplock::Extension::ProxyError)
65
66
  self.expired_at = Time.now
66
67
  else
67
68
  self.scheduled_at = Time.now + (5 * 2**self.executions.to_i)
@@ -71,6 +72,7 @@ module Skiplock
71
72
  self.data['cron'] ||= {}
72
73
  self.data['cron']['executions'] = self.data['cron']['executions'].to_i + 1
73
74
  self.data['cron']['last_finished_at'] = self.finished_at.utc.to_s
75
+ self.data['cron']['last_result'] = self.data['result']
74
76
  next_cron_at = Cron.next_schedule_at(self.cron)
75
77
  if next_cron_at
76
78
  # update job to record completions counter before resetting finished_at to nil
@@ -78,6 +80,7 @@ module Skiplock
78
80
  self.finished_at = nil
79
81
  self.executions = nil
80
82
  self.exception_executions = nil
83
+ self.data.delete('result')
81
84
  self.scheduled_at = Time.at(next_cron_at)
82
85
  else
83
86
  Skiplock.logger.error("[Skiplock] ERROR: Invalid CRON '#{self.cron}' for Job #{self.job_class}") if Skiplock.logger
@@ -1,11 +1,10 @@
1
1
  module Skiplock
2
2
  class Manager
3
- def initialize(**config)
3
+ def initialize
4
4
  @config = Skiplock::DEFAULT_CONFIG.dup
5
5
  @config.merge!(YAML.load_file('config/skiplock.yml')) rescue nil
6
6
  @config.symbolize_keys!
7
7
  @config.transform_values! {|v| v.is_a?(String) ? v.downcase : v}
8
- @config.merge!(config)
9
8
  @hostname = Socket.gethostname
10
9
  configure
11
10
  setup_logger
@@ -22,7 +21,7 @@ module Skiplock
22
21
 
23
22
  def standalone(**options)
24
23
  @config.merge!(options)
25
- Rails.logger.reopen('/dev/null')
24
+ Rails.logger.reopen('/dev/null') rescue Rails.logger.reopen('NUL') # supports Windows NUL device
26
25
  Rails.logger.extend(ActiveSupport::Logger.broadcast(@logger))
27
26
  @config[:workers] = 1 if @config[:workers] <= 0
28
27
  @config[:standalone] = true
@@ -1,4 +1,4 @@
1
1
  module Skiplock
2
- VERSION = Version = '1.0.17'
2
+ VERSION = Version = '1.0.18'
3
3
  end
4
4
 
@@ -1,6 +1,6 @@
1
1
  module Skiplock
2
2
  class Worker < ActiveRecord::Base
3
- self.implicit_order_column = 'created_at'
3
+ self.implicit_order_column = 'updated_at'
4
4
  has_many :jobs, inverse_of: :worker
5
5
 
6
6
  def self.cleanup(hostname = nil)
@@ -42,7 +42,7 @@ module Skiplock
42
42
 
43
43
  def get_next_available_job
44
44
  @connection.transaction do
45
- job = Job.find_by_sql("SELECT id, scheduled_at FROM skiplock.jobs WHERE running = FALSE AND expired_at IS NULL AND finished_at IS NULL ORDER BY scheduled_at ASC NULLS FIRST,#{@queues_order_query ? ' CASE ' + @queues_order_query + ' ELSE NULL END ASC NULLS LAST,' : ''} priority ASC NULLS LAST, created_at ASC FOR UPDATE SKIP LOCKED LIMIT 1").first
45
+ job = Job.find_by_sql("SELECT id, running, scheduled_at FROM skiplock.jobs WHERE running = FALSE AND expired_at IS NULL AND finished_at IS NULL ORDER BY scheduled_at ASC NULLS FIRST,#{@queues_order_query ? ' CASE ' + @queues_order_query + ' ELSE NULL END ASC NULLS LAST,' : ''} priority ASC NULLS LAST, created_at ASC FOR UPDATE SKIP LOCKED LIMIT 1").first
46
46
  if job && job.scheduled_at.to_f <= Time.now.to_f
47
47
  job = Job.find_by_sql("UPDATE skiplock.jobs SET running = TRUE, worker_id = '#{self.id}', updated_at = NOW() WHERE id = '#{job.id}' RETURNING *").first
48
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skiplock
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.17
4
+ version: 1.0.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tin Vo