skiplock 1.0.17 → 1.0.18

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