sidecloq 0.1.0
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 +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +18 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +54 -0
- data/Rakefile +27 -0
- data/lib/sidecloq/locker.rb +57 -0
- data/lib/sidecloq/runner.rb +48 -0
- data/lib/sidecloq/schedule.rb +79 -0
- data/lib/sidecloq/scheduler.rb +85 -0
- data/lib/sidecloq/utils.rb +58 -0
- data/lib/sidecloq/version.rb +3 -0
- data/lib/sidecloq/web.rb +25 -0
- data/lib/sidecloq.rb +75 -0
- data/sidetoq.gemspec +33 -0
- data/web/views/recurring.erb +38 -0
- metadata +204 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6d0f6f9c7d36841096162c3d1ec7775220e58438
|
4
|
+
data.tar.gz: 4d11bcc58feabca4f13c0cf05f6a8d34257e5d29
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 328c80251970cb9899a5106394a5a0c206ce5317fb88be4d4f01092e6521d7f8fcd487535403d6bd917636739be852b0a0051f51ea25ab739dfbebef040ce894
|
7
|
+
data.tar.gz: 3a956934c7415080ca8a68664471cab8e86707eb914b3ae4864f58b2ada039c47e7559c76054982f0e4780f58d87172079e8a4426be107f2d41e579e9c571bbd
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
cache: bundler
|
4
|
+
services:
|
5
|
+
- redis-server
|
6
|
+
rvm:
|
7
|
+
- 2.0.0
|
8
|
+
- 2.1.8
|
9
|
+
- 2.2.4
|
10
|
+
- ruby-head
|
11
|
+
- jruby-9.0.4.0
|
12
|
+
- jruby-head
|
13
|
+
- rbx-2.6
|
14
|
+
- rbx-2.7
|
15
|
+
matrix:
|
16
|
+
allow_failures:
|
17
|
+
- rvm: ruby-head
|
18
|
+
- rvm: jruby-head
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Matt Robinson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Sidecloq
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mattyr/sidecloq)
|
4
|
+
|
5
|
+
Another recurring job extension for Sidekiq.
|
6
|
+
|
7
|
+
## Why
|
8
|
+
|
9
|
+
TODO: design principles, differences, inspiration (sidetiq,
|
10
|
+
sidekiq-scheduler, resque-scheduler)
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'sidecloq'
|
18
|
+
```
|
19
|
+
|
20
|
+
Configure Sidecloq alongside your Sidekiq config. If using Rails, and
|
21
|
+
your schedule is located at config/sidecloq.yml, you don't have to do
|
22
|
+
anything (ie, omit this whole configuration block).
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
Sidcloq.configure do |config|
|
26
|
+
config[:schedule_file] =
|
27
|
+
File.join(Rails.root, "config/myschedule.yml")
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
TODO: configuration options
|
32
|
+
|
33
|
+
TODO: schedule file format (like resque-scheduler)
|
34
|
+
|
35
|
+
## Web Extension
|
36
|
+
|
37
|
+
Add Sidecloq::Web after Sidekiq::Web:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require 'sidekiq/web'
|
41
|
+
require 'sidecloq/web'
|
42
|
+
```
|
43
|
+
|
44
|
+
TODO: screenshot/directions
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
Bug reports and pull requests are welcome.
|
49
|
+
|
50
|
+
## License
|
51
|
+
|
52
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
53
|
+
|
54
|
+
TODO: project links
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList['test/**/test_*.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
desc "Run the Sidekiq web interface (w/Sidecloq)"
|
13
|
+
task :web do
|
14
|
+
require 'sidekiq'
|
15
|
+
require 'sidecloq'
|
16
|
+
|
17
|
+
Sidekiq.configure_client do |config|
|
18
|
+
config.redis = { url: 'redis://localhost:6379/0', size: 1, namespace: 'sidecloq' }
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'sidekiq/web'
|
22
|
+
require 'sidecloq/web'
|
23
|
+
|
24
|
+
Rack::Server.start(
|
25
|
+
app: Sidekiq::Web
|
26
|
+
)
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Sidecloq
|
2
|
+
# Locker obtains or waits for an exclusive lock on a key in redis
|
3
|
+
class Locker
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
DEFAULT_LOCK_KEY = "sidecloq_leader_lock"
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
# we keep a connection from the pool by default
|
10
|
+
@redis = options[:redis] || Sidekiq.redis_pool.checkout
|
11
|
+
@key = options[:lock_key] || DEFAULT_LOCK_KEY
|
12
|
+
@ttl = options[:ttl] || 60
|
13
|
+
@check_interval = options[:check_interval] || 15
|
14
|
+
@lock_manager = Redlock::Client.new([@redis])
|
15
|
+
@obtained_lock = Concurrent::Event.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# blocks until lock is obtained, then yields
|
19
|
+
def with_lock
|
20
|
+
start
|
21
|
+
@obtained_lock.wait
|
22
|
+
yield
|
23
|
+
stop
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop(timeout = nil)
|
27
|
+
if @check_task
|
28
|
+
logger.debug("Stopping locker check task")
|
29
|
+
@check_task.shutdown
|
30
|
+
@check_task.wait_for_termination(timeout)
|
31
|
+
logger.debug("Stopped locker check task")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_lock?
|
36
|
+
@obtained_lock.set?
|
37
|
+
end
|
38
|
+
|
39
|
+
private unless $TESTING
|
40
|
+
|
41
|
+
def start
|
42
|
+
logger.debug("Starting locker check task")
|
43
|
+
@check_task = Concurrent::TimerTask.new(execution_interval: @check_interval, run_now: true) do
|
44
|
+
get_or_refresh_lock
|
45
|
+
end
|
46
|
+
@check_task.execute
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_or_refresh_lock
|
50
|
+
# redlock is in ms, not seconds
|
51
|
+
@lock = @lock_manager.lock(@key, @ttl * 1000, extend: @lock)
|
52
|
+
@obtained_lock.set if @lock
|
53
|
+
logger.debug("Leader lock #{"not " if !@lock}held")
|
54
|
+
@lock
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Sidecloq
|
2
|
+
# Runner encapsulates a locker and a scheduler, running scheduler when
|
3
|
+
# "elected" leader
|
4
|
+
class Runner
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
attr_reader :locker, :scheduler
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@locker = extract_locker(options)
|
11
|
+
@scheduler = extract_scheduler(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
@thread = Thread.new do
|
16
|
+
logger.info("Runner starting")
|
17
|
+
@locker.with_lock do
|
18
|
+
# i am the leader
|
19
|
+
logger.info("Obtained leader lock")
|
20
|
+
@scheduler.run
|
21
|
+
end
|
22
|
+
logger.info("Runner ending")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop(timeout = nil)
|
27
|
+
logger.debug("Stopping runner")
|
28
|
+
if @locker.has_lock?
|
29
|
+
@scheduler.stop(timeout)
|
30
|
+
@locker.stop(timeout)
|
31
|
+
end
|
32
|
+
@thread.join if @thread
|
33
|
+
logger.debug("Stopped runner")
|
34
|
+
end
|
35
|
+
|
36
|
+
private unless $TESTING
|
37
|
+
|
38
|
+
def extract_locker(options)
|
39
|
+
return options[:locker] if options[:locker]
|
40
|
+
Locker.new(options[:locker_options] || {})
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_scheduler(options)
|
44
|
+
return options[:scheduler] if options[:scheduler]
|
45
|
+
Scheduler.new(options[:schedule], options[:scheduler_options] || {})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Sidecloq
|
2
|
+
# Schedule loads and parses recurring job specs from files, hashes, and redis
|
3
|
+
class Schedule
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
REDIS_KEY = :sidecloq_schedule
|
7
|
+
|
8
|
+
attr_reader :job_specs
|
9
|
+
|
10
|
+
def initialize(specs)
|
11
|
+
@job_specs = specs
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from_yaml(filename)
|
15
|
+
from_hash(YAML.load_file(filename))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_redis
|
19
|
+
specs = {}
|
20
|
+
|
21
|
+
if redis { |r| r.exists(REDIS_KEY) }
|
22
|
+
specs = {}
|
23
|
+
|
24
|
+
redis { |r| r.hgetall(REDIS_KEY) }.tap do |h|
|
25
|
+
h.each do |name, config|
|
26
|
+
specs[name] = MultiJson.decode(config)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
from_hash(specs)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.from_hash(hash)
|
35
|
+
if defined?(Rails) && hash.key?(Rails.env)
|
36
|
+
hash = hash[Rails.env]
|
37
|
+
end
|
38
|
+
|
39
|
+
specs = hash.inject({}) do |memo, (name, spec)|
|
40
|
+
memo[name] = spec.dup.tap do |s|
|
41
|
+
s['class'] = name unless spec.key?('class') || spec.key?(:class)
|
42
|
+
s['args'] = s['args'] || s[:args] || []
|
43
|
+
end
|
44
|
+
memo
|
45
|
+
end
|
46
|
+
|
47
|
+
new(specs)
|
48
|
+
end
|
49
|
+
|
50
|
+
def save_yaml(filename)
|
51
|
+
File.open(filename,'w') do |h|
|
52
|
+
h.write @job_specs.to_yaml
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def save_redis
|
57
|
+
reset_redis_schedule
|
58
|
+
save_all_to_redis
|
59
|
+
end
|
60
|
+
|
61
|
+
private unless $TESTING
|
62
|
+
|
63
|
+
def reset_redis_schedule
|
64
|
+
redis do |r|
|
65
|
+
r.hkeys(REDIS_KEY).each do |k|
|
66
|
+
r.hdel(REDIS_KEY, k)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def save_all_to_redis
|
72
|
+
redis do |r|
|
73
|
+
@job_specs.each do |name, spec|
|
74
|
+
r.hset(REDIS_KEY, name, MultiJson.encode(spec))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Sidecloq
|
2
|
+
# Scheduler enqeues jobs according to the given schedule
|
3
|
+
class Scheduler
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
def initialize(schedule, options = {})
|
7
|
+
@schedule = schedule
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
# run queues jobs per their schedules, blocking forever
|
12
|
+
def run
|
13
|
+
logger.info("Loading schedules into redis")
|
14
|
+
sync_with_redis
|
15
|
+
logger.info("Starting scheduler")
|
16
|
+
load_schedule_into_rufus
|
17
|
+
logger.debug("Joining rufus thread")
|
18
|
+
rufus.join
|
19
|
+
logger.debug("Scheduler run ended")
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop(timeout = nil)
|
23
|
+
logger.info("Stopping scheduler (timeout: #{timeout})")
|
24
|
+
if timeout
|
25
|
+
t = Concurrent::ScheduledTask.new(timeout) do
|
26
|
+
rufus.shutdown(:kill) if rufus.up?
|
27
|
+
end
|
28
|
+
Thread.new do
|
29
|
+
rufus.shutdown(:wait)
|
30
|
+
t.cancel
|
31
|
+
end
|
32
|
+
else
|
33
|
+
rufus.shutdown(:wait)
|
34
|
+
end
|
35
|
+
rufus.join
|
36
|
+
logger.info("Stopped scheduler")
|
37
|
+
end
|
38
|
+
|
39
|
+
private unless $TESTING
|
40
|
+
|
41
|
+
def rufus
|
42
|
+
@rufus ||= Rufus::Scheduler.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def sync_with_redis
|
46
|
+
@schedule.save_redis
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_schedule_into_rufus
|
50
|
+
logger.debug("Scheduling jobs")
|
51
|
+
@schedule.job_specs.each do |name, spec|
|
52
|
+
load_into_rufus(name, spec)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def load_into_rufus(name, spec)
|
57
|
+
# rufus will loop indefinitely trying to find the next event time if the
|
58
|
+
# cronline is impossible, like '0 5 31 2 *'
|
59
|
+
if will_never_run(spec['cron'])
|
60
|
+
logger.info("Impossible cronline detected, not scheduling #{name}: #{spec}")
|
61
|
+
else
|
62
|
+
logger.info("Scheduling #{name}: #{spec}")
|
63
|
+
rufus.cron(spec['cron']) do
|
64
|
+
safe_enqueue_job(name, spec)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def safe_enqueue_job(name, spec)
|
70
|
+
logger.info "enqueueing #{name}"
|
71
|
+
|
72
|
+
# failed enqeueuing should not b0rk stuff
|
73
|
+
begin
|
74
|
+
enqueue_job!(name, spec)
|
75
|
+
rescue => e
|
76
|
+
logger.info "error enqueuing #{name} - #{e.class.name}: #{e.message}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# can raise exceptions, but shouldn't
|
81
|
+
def enqueue_job!(name, spec)
|
82
|
+
Sidekiq::Client.push(spec)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Sidecloq
|
2
|
+
module Utils
|
3
|
+
class ContextLogger
|
4
|
+
def initialize(ctx)
|
5
|
+
@context = ctx
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(meth, *args)
|
9
|
+
Sidekiq::Logging.with_context(@context) do
|
10
|
+
Sidekiq.logger.send(meth, args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger
|
16
|
+
@logger ||= ContextLogger.new('Sidecloq')
|
17
|
+
end
|
18
|
+
|
19
|
+
def redis(&block)
|
20
|
+
self.class.redis(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# finds cron lines that are impossible, like '0 5 31 2 *'
|
24
|
+
# note: does not attempt to fully validate the cronline
|
25
|
+
def will_never_run(cronline)
|
26
|
+
# look for non-existent day of month
|
27
|
+
split = cronline.split(/\s+/)
|
28
|
+
if split.length > 3 &&
|
29
|
+
split[2] =~ /\d+/
|
30
|
+
split[3] =~ /\d+/
|
31
|
+
|
32
|
+
month = split[3].to_i
|
33
|
+
day = split[2].to_i
|
34
|
+
# special case for leap-year detection
|
35
|
+
return true if month == 2 && day <= 29
|
36
|
+
|
37
|
+
return !Date.valid_date?(0, month, day)
|
38
|
+
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
def redis(&block)
|
46
|
+
if block
|
47
|
+
Sidekiq.redis(&block)
|
48
|
+
else
|
49
|
+
Sidekiq.redis_pool.checkout
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.included(klass)
|
55
|
+
klass.extend(ClassMethods)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/sidecloq/web.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sidecloq
|
2
|
+
module Web
|
3
|
+
VIEW_PATH = File.expand_path('../../../web/views', __FILE__)
|
4
|
+
|
5
|
+
def self.registered(app)
|
6
|
+
|
7
|
+
app.get '/recurring' do
|
8
|
+
@schedule = Schedule.from_redis
|
9
|
+
|
10
|
+
erb File.read(File.join(VIEW_PATH, 'recurring.erb'))
|
11
|
+
end
|
12
|
+
|
13
|
+
app.post '/recurring/:name/enqueue' do |name|
|
14
|
+
if spec = Sidecloq::Schedule.from_redis.job_specs[name]
|
15
|
+
Sidekiq::Client.push(spec)
|
16
|
+
end
|
17
|
+
redirect "#{root_path}recurring"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Sidekiq::Web.register(Sidecloq::Web)
|
25
|
+
Sidekiq::Web.tabs['Recurring'] = 'recurring'
|
data/lib/sidecloq.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'concurrent'
|
3
|
+
require 'redlock'
|
4
|
+
require 'multi_json'
|
5
|
+
require 'rufus-scheduler'
|
6
|
+
|
7
|
+
require 'sidecloq/utils'
|
8
|
+
require 'sidecloq/schedule'
|
9
|
+
require 'sidecloq/locker'
|
10
|
+
require 'sidecloq/scheduler'
|
11
|
+
require 'sidecloq/runner'
|
12
|
+
require 'sidecloq/version'
|
13
|
+
|
14
|
+
module Sidecloq
|
15
|
+
def self.install
|
16
|
+
Sidekiq.configure_server do |config|
|
17
|
+
config.on(:startup) do
|
18
|
+
Sidecloq.startup
|
19
|
+
end
|
20
|
+
|
21
|
+
config.on(:shutdown) do
|
22
|
+
Sidecloq.shutdown
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.options
|
28
|
+
@options ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.options=(opts)
|
32
|
+
@options = opts
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.configure
|
36
|
+
yield self
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.startup
|
40
|
+
unless options[:scheduler]
|
41
|
+
options[:schedule] ||= extract_schedule
|
42
|
+
end
|
43
|
+
@runner = Runner.new(options)
|
44
|
+
@runner.run
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.shutdown
|
48
|
+
@runner.stop(options[:timeout] || 10) if @runner
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.extract_schedule
|
52
|
+
# do our best to do this automatically
|
53
|
+
|
54
|
+
# schedule handed to us
|
55
|
+
return options[:schedule] if options[:schedule]
|
56
|
+
|
57
|
+
# try for a file
|
58
|
+
options[:schedule_file] ||= "config/sidecloq.yml"
|
59
|
+
if File.exists?(options[:schedule_file])
|
60
|
+
return Schedule.from_yaml(options[:schedule_file])
|
61
|
+
elsif defined?(Rails)
|
62
|
+
# try rails-root-relative
|
63
|
+
full_path = File.join(Rails.root, options[:schedule_file])
|
64
|
+
if File.exists?(full_path)
|
65
|
+
options[:schedule_file] = full_path
|
66
|
+
return Schedule.from_yaml(options[:schedule_file])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# return an empty schedule
|
71
|
+
Schedule.new({})
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
Sidecloq.install
|
data/sidetoq.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sidecloq/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sidecloq"
|
8
|
+
spec.version = Sidecloq::VERSION
|
9
|
+
spec.authors = ["Matt Robinson"]
|
10
|
+
spec.email = ["robinson.matty@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Recurring jobs for Sidekiq}
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = "http://github.com/mattyr/sidecloq"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "bin"
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "sidekiq", "~> 4.0.1"
|
23
|
+
spec.add_dependency "redlock", "~> 0.1.2"
|
24
|
+
# mimics some dev dependencies of sidekiq:
|
25
|
+
spec.add_dependency "concurrent-ruby"
|
26
|
+
spec.add_dependency "sinatra", "~> 1.4", ">= 1.4.6"
|
27
|
+
spec.add_dependency "redis-namespace", "~> 1.5", ">= 1.5.2"
|
28
|
+
spec.add_dependency "multi_json", "~> 1.11"
|
29
|
+
spec.add_dependency "rufus-scheduler", "~> 3.1", ">= 3.1.10"
|
30
|
+
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "minitest"
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<h3>Recurring Jobs</h3>
|
2
|
+
|
3
|
+
<div class="table_container">
|
4
|
+
<table class="table table-hover table-bordered table-striped table-white">
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th>Name</th>
|
8
|
+
<th>Description</th>
|
9
|
+
<th>Interval</th>
|
10
|
+
<th>Class</th>
|
11
|
+
<th>Queue</th>
|
12
|
+
<th>Arguments</th>
|
13
|
+
<th>Actions</th>
|
14
|
+
</tr>
|
15
|
+
</thead>
|
16
|
+
|
17
|
+
<tbody>
|
18
|
+
<% @schedule.job_specs.each do |name, job_spec| %>
|
19
|
+
<tr>
|
20
|
+
<td><%= name %></td>
|
21
|
+
<td><%= job_spec['description'] %></td>
|
22
|
+
<td><%= job_spec.fetch 'cron', job_spec['every'] %></td>
|
23
|
+
<td><%= job_spec['class'] %></td>
|
24
|
+
<td>
|
25
|
+
<a href="<%= root_path %>queues/<%= job_spec.fetch('queue', 'default') %>"><%= job_spec.fetch('queue', 'default') %></a>
|
26
|
+
</td>
|
27
|
+
<td><%= job_spec['args'] %></td>
|
28
|
+
<td>
|
29
|
+
<form action="<%= root_path %>recurring/<%= CGI.escape(name).gsub('+', '%20') %>/enqueue" method="post">
|
30
|
+
<%= csrf_tag if respond_to?(:csrf_tag) %>
|
31
|
+
<input class='btn btn-xs pull-left' type="submit" name="enque" value="<%= t('Enqueue Now') %>"/>
|
32
|
+
</form>
|
33
|
+
</td>
|
34
|
+
</tr>
|
35
|
+
<% end %>
|
36
|
+
</tbody>
|
37
|
+
</table>
|
38
|
+
</div>
|
metadata
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sidecloq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Robinson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sidekiq
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redlock
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: concurrent-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.4'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 1.4.6
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '1.4'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 1.4.6
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: redis-namespace
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.5'
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 1.5.2
|
85
|
+
type: :runtime
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '1.5'
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 1.5.2
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: multi_json
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.11'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '1.11'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: rufus-scheduler
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '3.1'
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 3.1.10
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '3.1'
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 3.1.10
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: rake
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '10.0'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '10.0'
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: minitest
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
description: Recurring jobs for Sidekiq
|
158
|
+
email:
|
159
|
+
- robinson.matty@gmail.com
|
160
|
+
executables: []
|
161
|
+
extensions: []
|
162
|
+
extra_rdoc_files: []
|
163
|
+
files:
|
164
|
+
- ".gitignore"
|
165
|
+
- ".travis.yml"
|
166
|
+
- Gemfile
|
167
|
+
- LICENSE.txt
|
168
|
+
- README.md
|
169
|
+
- Rakefile
|
170
|
+
- lib/sidecloq.rb
|
171
|
+
- lib/sidecloq/locker.rb
|
172
|
+
- lib/sidecloq/runner.rb
|
173
|
+
- lib/sidecloq/schedule.rb
|
174
|
+
- lib/sidecloq/scheduler.rb
|
175
|
+
- lib/sidecloq/utils.rb
|
176
|
+
- lib/sidecloq/version.rb
|
177
|
+
- lib/sidecloq/web.rb
|
178
|
+
- sidetoq.gemspec
|
179
|
+
- web/views/recurring.erb
|
180
|
+
homepage: http://github.com/mattyr/sidecloq
|
181
|
+
licenses:
|
182
|
+
- MIT
|
183
|
+
metadata: {}
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project:
|
200
|
+
rubygems_version: 2.4.5.1
|
201
|
+
signing_key:
|
202
|
+
specification_version: 4
|
203
|
+
summary: Recurring jobs for Sidekiq
|
204
|
+
test_files: []
|