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 +4 -4
- data/README.md +10 -5
- data/lib/skiplock/cron.rb +1 -1
- data/lib/skiplock/extension.rb +4 -1
- data/lib/skiplock/job.rb +6 -3
- data/lib/skiplock/manager.rb +2 -3
- data/lib/skiplock/version.rb +1 -1
- data/lib/skiplock/worker.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8112589dcbdf58a743529ec9bf5ddd8371791e83e79c0d455ed42cbca05ee5eb
|
4
|
+
data.tar.gz: 149f826e93ecd8ac6165a8db46a8e7e234636a4481e68bf09d208b04a1dea1ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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',
|
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.
|
187
|
-
|
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)
|
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
|
data/lib/skiplock/extension.rb
CHANGED
@@ -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 = '
|
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
|
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
|
data/lib/skiplock/manager.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
module Skiplock
|
2
2
|
class Manager
|
3
|
-
def initialize
|
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
|
data/lib/skiplock/version.rb
CHANGED
data/lib/skiplock/worker.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Skiplock
|
2
2
|
class Worker < ActiveRecord::Base
|
3
|
-
self.implicit_order_column = '
|
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
|