sidetiq 0.1.2
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.
- data/.gitignore +9 -0
- data/.travis.yml +9 -0
- data/Gemfile +17 -0
- data/LICENSE +20 -0
- data/README.md +146 -0
- data/Rakefile +11 -0
- data/examples/simple.rb +18 -0
- data/ext/sidetiq_ext/extconf.rb +4 -0
- data/ext/sidetiq_ext/sidetiq_ext.c +97 -0
- data/ext/sidetiq_ext/sidetiq_ext.h +17 -0
- data/lib/sidetiq/clock.rb +105 -0
- data/lib/sidetiq/config.rb +14 -0
- data/lib/sidetiq/middleware.rb +20 -0
- data/lib/sidetiq/schedulable.rb +17 -0
- data/lib/sidetiq/schedule.rb +22 -0
- data/lib/sidetiq/version.rb +10 -0
- data/lib/sidetiq/views/sidetiq.slim +23 -0
- data/lib/sidetiq/views/sidetiq_details.slim +40 -0
- data/lib/sidetiq/web.rb +47 -0
- data/lib/sidetiq.rb +20 -0
- data/sidetiq.gemspec +24 -0
- data/test/helper.rb +26 -0
- data/test/test_clock.rb +65 -0
- data/test/test_config.rb +21 -0
- data/test/test_errors.rb +7 -0
- data/test/test_middleware.rb +18 -0
- data/test/test_schedule.rb +25 -0
- data/test/test_version.rb +21 -0
- data/test/test_web.rb +58 -0
- metadata +117 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem 'rake'
|
5
|
+
gem 'rake-compiler'
|
6
|
+
gem 'sinatra', require: false
|
7
|
+
gem 'slim', require: false
|
8
|
+
end
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'simplecov'
|
12
|
+
gem 'mocha'
|
13
|
+
gem 'rack-test'
|
14
|
+
end
|
15
|
+
|
16
|
+
gemspec
|
17
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2012 by Tobias Svensson <tobias@musicglue.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
Sidetiq
|
2
|
+
=======
|
3
|
+
|
4
|
+
[](https://travis-ci.org/tobiassvn/sidetiq)
|
5
|
+
|
6
|
+
Recurring jobs for [Sidekiq](http://mperham.github.com/sidekiq/).
|
7
|
+
|
8
|
+
## DESCRIPTION
|
9
|
+
|
10
|
+
Sidetiq provides a simple API for defining recurring workers for Sidekiq.
|
11
|
+
|
12
|
+
- Flexible DSL based on [ice_cube](http://seejohnrun.github.com/ice_cube/)
|
13
|
+
|
14
|
+
- High-resolution timer using `clock_gettime(3)` (or `mach_absolute_time()` on
|
15
|
+
Apple Mac OS X), allowing for accurate sub-second clock ticks.
|
16
|
+
|
17
|
+
- Sidetiq uses a locking mechanism (based on `setnx` and `pexpire`) internally
|
18
|
+
so Sidetiq clocks can run in each Sidekiq process without interfering with
|
19
|
+
each other (tested with sub-second polling of scheduled jobs by Sidekiq and
|
20
|
+
Sidetiq clock rates above 100hz).
|
21
|
+
|
22
|
+
## DEPENDENCIES
|
23
|
+
|
24
|
+
- [Sidekiq](http://mperham.github.com/sidekiq/)
|
25
|
+
- [ice_cube](http://seejohnrun.github.com/ice_cube/)
|
26
|
+
|
27
|
+
## INSTALLATION
|
28
|
+
|
29
|
+
The best way to install Sidetiq is with RubyGems:
|
30
|
+
|
31
|
+
$ [sudo] gem install sidetiq
|
32
|
+
|
33
|
+
If you're installing from source, you can use [Bundler](http://gembundler.com/)
|
34
|
+
to pick up all the gems ([more info](http://gembundler.com/bundle_install.html)):
|
35
|
+
|
36
|
+
$ bundle install
|
37
|
+
|
38
|
+
## GETTING STARTED
|
39
|
+
|
40
|
+
Defining recurring jobs is simple:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class MyWorker
|
44
|
+
include Sidekiq::Worker
|
45
|
+
include Sidetiq::Schedulable
|
46
|
+
|
47
|
+
# Daily at midnight
|
48
|
+
tiq { daily }
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
It also is possible to define multiple scheduling rules for a worker:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class MyWorker
|
56
|
+
include Sidekiq::Worker
|
57
|
+
include Sidetiq::Schedulable
|
58
|
+
|
59
|
+
tiq do
|
60
|
+
# Every third year in March
|
61
|
+
yearly(3).month_of_year(:march)
|
62
|
+
|
63
|
+
# Every second year in February
|
64
|
+
yearly(2).month_of_year(:february)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
Or complex schedules:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class MyWorker
|
73
|
+
include Sidekiq::Worker
|
74
|
+
include Sidetiq::Schedulable
|
75
|
+
|
76
|
+
# Every other month on the first monday and last tuesday at 12 o'clock.
|
77
|
+
tiq { monthly(2).day_of_week(1 => [1], 2 => [-1]).hour_of_day(12) }
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
To start Sidetiq, simply call `Sidetiq::Clock.start!` in a server specific
|
82
|
+
configuration block:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
Sidekiq.configure_server do |config|
|
86
|
+
Sidetiq::Clock.start!
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Additionally, Sidetiq includes a middleware that will check if the clock
|
91
|
+
thread is still alive and restart it if necessary.
|
92
|
+
|
93
|
+
## CONFIGURATION
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
Sidetiq.configure do |config|
|
97
|
+
# Thread priority of the clock thread (default: Thread.main.priority as
|
98
|
+
# defined when Sidetiq is loaded).
|
99
|
+
config.priority = 2
|
100
|
+
|
101
|
+
# Clock tick resolution in seconds (default: 1).
|
102
|
+
config.resolution = 0.5
|
103
|
+
|
104
|
+
# Clock locking key expiration in ms (default: 1000).
|
105
|
+
config.lock_expire = 100
|
106
|
+
|
107
|
+
# When `true` uses UTC instead of local times (default: false)
|
108
|
+
config.utc = false
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
## WEB EXTENSION
|
113
|
+
|
114
|
+
Sidetiq includes an extension for Sidekiq's web interface. It will not be
|
115
|
+
loaded by default, so it will have to be required manually:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
require 'sidetiq/web'
|
119
|
+
```
|
120
|
+
|
121
|
+
### SCREENSHOT
|
122
|
+
|
123
|
+

|
124
|
+
|
125
|
+
## CONTRIBUTE
|
126
|
+
|
127
|
+
If you'd like to contribute to Sidetiq, start by forking my repo on GitHub:
|
128
|
+
|
129
|
+
[http://github.com/tobiassvn/sidetiq](http://github.com/tobiassvn/sidetiq)
|
130
|
+
|
131
|
+
To get all of the dependencies, install the gem first. The best way to get
|
132
|
+
your changes merged back into core is as follows:
|
133
|
+
|
134
|
+
1. Clone down your fork
|
135
|
+
1. Create a thoughtfully named topic branch to contain your change
|
136
|
+
1. Write some code
|
137
|
+
1. Add tests and make sure everything still passes by running `rake`
|
138
|
+
1. If you are adding new functionality, document it in the README
|
139
|
+
1. Do not change the version number, I will do that on my end
|
140
|
+
1. If necessary, rebase your commits into logical chunks, without errors
|
141
|
+
1. Push the branch up to GitHub
|
142
|
+
1. Send a pull request to the tobiassvn/sidetiq project.
|
143
|
+
|
144
|
+
## LICENSE
|
145
|
+
|
146
|
+
Sidetiq is released under the MIT License. See LICENSE for further details.
|
data/Rakefile
ADDED
data/examples/simple.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Run with `sidekiq -r /path/to/simple.rb`
|
2
|
+
|
3
|
+
require 'sidekiq'
|
4
|
+
require 'sidetiq'
|
5
|
+
|
6
|
+
Sidekiq.options[:poll_interval] = 1
|
7
|
+
|
8
|
+
class MyWorker
|
9
|
+
include Sidekiq::Worker
|
10
|
+
include Sidetiq::Schedulable
|
11
|
+
|
12
|
+
tiq { secondly }
|
13
|
+
|
14
|
+
def perform(*args)
|
15
|
+
Sidekiq.logger.info "#perform"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <assert.h>
|
3
|
+
#include "sidetiq_ext.h"
|
4
|
+
|
5
|
+
#ifdef __APPLE__
|
6
|
+
#include <sys/time.h>
|
7
|
+
#include <sys/resource.h>
|
8
|
+
#include <mach/mach.h>
|
9
|
+
#include <mach/clock.h>
|
10
|
+
#include <mach/mach_time.h>
|
11
|
+
#include <errno.h>
|
12
|
+
#include <unistd.h>
|
13
|
+
#include <sched.h>
|
14
|
+
#else
|
15
|
+
#include <time.h>
|
16
|
+
#endif
|
17
|
+
|
18
|
+
VALUE msidetiq;
|
19
|
+
VALUE esidetiq_error;
|
20
|
+
VALUE csidetiq_clock;
|
21
|
+
|
22
|
+
#ifdef __APPLE__
|
23
|
+
static mach_timebase_info_data_t clock_gettime_inf;
|
24
|
+
|
25
|
+
typedef enum {
|
26
|
+
CLOCK_REALTIME,
|
27
|
+
CLOCK_MONOTONIC,
|
28
|
+
CLOCK_PROCESS_CPUTIME_ID,
|
29
|
+
CLOCK_THREAD_CPUTIME_ID
|
30
|
+
} clockid_t;
|
31
|
+
|
32
|
+
int clock_gettime(clockid_t clk_id, struct timespec *tp)
|
33
|
+
{
|
34
|
+
kern_return_t ret;
|
35
|
+
clock_serv_t clk;
|
36
|
+
clock_id_t clk_serv_id;
|
37
|
+
mach_timespec_t tm;
|
38
|
+
uint64_t start, end, delta, nano;
|
39
|
+
int retval = -1;
|
40
|
+
|
41
|
+
switch (clk_id) {
|
42
|
+
case CLOCK_REALTIME:
|
43
|
+
case CLOCK_MONOTONIC:
|
44
|
+
clk_serv_id = clk_id == CLOCK_REALTIME ? CALENDAR_CLOCK : SYSTEM_CLOCK;
|
45
|
+
if (KERN_SUCCESS == (ret = host_get_clock_service(mach_host_self(), clk_serv_id, &clk))) {
|
46
|
+
if (KERN_SUCCESS == (ret = clock_get_time(clk, &tm))) {
|
47
|
+
tp->tv_sec = tm.tv_sec;
|
48
|
+
tp->tv_nsec = tm.tv_nsec;
|
49
|
+
retval = 0;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
if (KERN_SUCCESS != ret) {
|
53
|
+
errno = EINVAL;
|
54
|
+
retval = -1;
|
55
|
+
}
|
56
|
+
break;
|
57
|
+
case CLOCK_PROCESS_CPUTIME_ID:
|
58
|
+
case CLOCK_THREAD_CPUTIME_ID:
|
59
|
+
start = mach_absolute_time();
|
60
|
+
if (clk_id == CLOCK_PROCESS_CPUTIME_ID) {
|
61
|
+
getpid();
|
62
|
+
} else {
|
63
|
+
sched_yield();
|
64
|
+
}
|
65
|
+
end = mach_absolute_time();
|
66
|
+
delta = end - start;
|
67
|
+
if (0 == clock_gettime_inf.denom) {
|
68
|
+
mach_timebase_info(&clock_gettime_inf);
|
69
|
+
}
|
70
|
+
nano = delta * clock_gettime_inf.numer / clock_gettime_inf.denom;
|
71
|
+
tp->tv_sec = nano * 1e-9;
|
72
|
+
tp->tv_nsec = nano - (tp->tv_sec * 1e9);
|
73
|
+
retval = 0;
|
74
|
+
break;
|
75
|
+
default:
|
76
|
+
errno = EINVAL;
|
77
|
+
retval = -1;
|
78
|
+
}
|
79
|
+
return retval;
|
80
|
+
}
|
81
|
+
#endif
|
82
|
+
|
83
|
+
static VALUE sidetiq_gettime(VALUE self)
|
84
|
+
{
|
85
|
+
struct timespec time;
|
86
|
+
assert(clock_gettime(CLOCK_REALTIME, &time) == 0);
|
87
|
+
return rb_time_nano_new(time.tv_sec, time.tv_nsec);
|
88
|
+
}
|
89
|
+
|
90
|
+
void Init_sidetiq_ext()
|
91
|
+
{
|
92
|
+
msidetiq = rb_define_module("Sidetiq");
|
93
|
+
esidetiq_error = rb_define_class_under(msidetiq, "Error", rb_eStandardError);
|
94
|
+
csidetiq_clock = rb_define_class_under(msidetiq, "Clock", rb_cObject);
|
95
|
+
rb_define_private_method(csidetiq_clock, "clock_gettime", sidetiq_gettime, 0);
|
96
|
+
}
|
97
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#ifndef __SIDETIQ_EXT_H__
|
2
|
+
#define __SIDETIQ_EXT_H__
|
3
|
+
#include <ruby.h>
|
4
|
+
|
5
|
+
void Init_sidetiq_ext();
|
6
|
+
static VALUE sidetiq_gettime(VALUE self);
|
7
|
+
|
8
|
+
/* module Sidetiq */
|
9
|
+
extern VALUE msidetiq;
|
10
|
+
|
11
|
+
/* class Sidetiq::Error < StandardError */
|
12
|
+
extern VALUE esidetiq_error;
|
13
|
+
|
14
|
+
/* class Sidetiq::Clock */
|
15
|
+
extern VALUE csidetiq_clock;
|
16
|
+
|
17
|
+
#endif
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
configure do |config|
|
3
|
+
config.priority = Thread.main.priority
|
4
|
+
config.resolution = 1
|
5
|
+
config.lock_expire = 1000
|
6
|
+
config.utc = false
|
7
|
+
end
|
8
|
+
|
9
|
+
class Clock
|
10
|
+
include Singleton
|
11
|
+
include MonitorMixin
|
12
|
+
|
13
|
+
START_TIME = Sidetiq.config.utc ? Time.utc(2010, 1, 1) : Time.local(2010, 1, 1)
|
14
|
+
|
15
|
+
attr_reader :schedules, :thread
|
16
|
+
|
17
|
+
def self.method_missing(meth, *args, &block)
|
18
|
+
instance.__send__(meth, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super
|
23
|
+
@schedules = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def schedule_for(worker)
|
27
|
+
schedules[worker] ||= Sidetiq::Schedule.new(START_TIME)
|
28
|
+
end
|
29
|
+
|
30
|
+
def tick
|
31
|
+
@tick = gettime
|
32
|
+
synchronize do
|
33
|
+
schedules.each do |worker, schedule|
|
34
|
+
if schedule.schedule_next?(@tick)
|
35
|
+
enqueue(worker, schedule.next_occurrence(@tick))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def gettime
|
42
|
+
Sidetiq.config.utc ? clock_gettime.utc : clock_gettime
|
43
|
+
end
|
44
|
+
|
45
|
+
def start!
|
46
|
+
return if ticking?
|
47
|
+
|
48
|
+
Sidekiq.logger.info "Sidetiq::Clock start"
|
49
|
+
@thread = Thread.start { clock { tick } }
|
50
|
+
@thread.abort_on_exception = true
|
51
|
+
@thread.priority = Sidetiq.config.resolution
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop!
|
55
|
+
if ticking?
|
56
|
+
@thread.kill
|
57
|
+
Sidekiq.logger.info "Sidetiq::Clock stop"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ticking?
|
62
|
+
@thread && @thread.alive?
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def enqueue(worker, time)
|
68
|
+
key = "sidetiq:#{worker.name}"
|
69
|
+
|
70
|
+
synchronize_clockworks("#{key}:lock") do |redis|
|
71
|
+
status = redis.get(key)
|
72
|
+
|
73
|
+
if status.nil? || status.to_f < time.to_f
|
74
|
+
time_f = time.to_f
|
75
|
+
Sidekiq.logger.info "Sidetiq::Clock enqueue #{worker.name} (at: #{time_f})"
|
76
|
+
redis.set(key, time_f)
|
77
|
+
worker.perform_at(time)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def synchronize_clockworks(lock)
|
83
|
+
Sidekiq.redis do |redis|
|
84
|
+
if redis.setnx(lock, 1)
|
85
|
+
Sidekiq.logger.debug "Sidetiq::Clock lock #{lock} #{Thread.current.inspect}"
|
86
|
+
|
87
|
+
redis.pexpire(lock, Sidetiq.config.lock_expire)
|
88
|
+
yield redis
|
89
|
+
redis.del(lock)
|
90
|
+
|
91
|
+
Sidekiq.logger.debug "Sidetiq::Clock unlock #{lock} #{Thread.current.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def clock
|
97
|
+
loop do
|
98
|
+
yield
|
99
|
+
Thread.pass
|
100
|
+
sleep Sidetiq.config.resolution
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
class Middleware
|
3
|
+
def initialize
|
4
|
+
@clock = Sidetiq::Clock.instance
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(*args)
|
8
|
+
# Restart the clock if the thread died.
|
9
|
+
@clock.start! if !@clock.ticking?
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Sidekiq.configure_server do |config|
|
16
|
+
config.server_middleware do |chain|
|
17
|
+
chain.add Sidetiq::Middleware
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
module Schedulable
|
3
|
+
module ClassMethods
|
4
|
+
def tiq(&block)
|
5
|
+
clock = Sidetiq::Clock.instance
|
6
|
+
clock.synchronize do
|
7
|
+
clock.schedule_for(self).instance_eval(&block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(klass)
|
13
|
+
klass.extend(Sidetiq::Schedulable::ClassMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sidetiq
|
2
|
+
class Schedule < IceCube::Schedule
|
3
|
+
def method_missing(meth, *args, &block)
|
4
|
+
if IceCube::Rule.respond_to?(meth)
|
5
|
+
rule = IceCube::Rule.send(meth, *args, &block)
|
6
|
+
add_recurrence_rule(rule)
|
7
|
+
rule
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def schedule_next?(time)
|
14
|
+
if @last_scheduled != (no = next_occurrence(time))
|
15
|
+
@last_scheduled = no
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
header.row
|
2
|
+
.span5
|
3
|
+
h3 Recurring Jobs
|
4
|
+
|
5
|
+
- if @schedules.length > 0
|
6
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
7
|
+
thead
|
8
|
+
th style="width: 50%" Worker
|
9
|
+
th style="width: 20%" Queue
|
10
|
+
th style="width: 20%" Next Run
|
11
|
+
th style="width: 10%" Actions
|
12
|
+
- @schedules.each do |worker, schedule|
|
13
|
+
tr
|
14
|
+
td
|
15
|
+
= worker.name
|
16
|
+
td= worker.get_sidekiq_options['queue']
|
17
|
+
td
|
18
|
+
== relative_time(schedule.next_occurrence(@time))
|
19
|
+
td
|
20
|
+
a href="#{root_path}sidetiq/#{worker.name}" Details
|
21
|
+
- else
|
22
|
+
.alert.alert-success No recurring jobs found.
|
23
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
header.row
|
2
|
+
.span5
|
3
|
+
h3
|
4
|
+
' Recurring Job:
|
5
|
+
= @worker.name
|
6
|
+
|
7
|
+
- if (recurrences = @schedule.recurrence_rules).length > 0
|
8
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
9
|
+
thead
|
10
|
+
th Recurrences
|
11
|
+
- recurrences.each do |rule|
|
12
|
+
tr
|
13
|
+
td
|
14
|
+
= rule.to_s
|
15
|
+
|
16
|
+
br
|
17
|
+
|
18
|
+
- if (exceptions = @schedule.exception_rules).length > 0
|
19
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
20
|
+
thead
|
21
|
+
th Exceptions
|
22
|
+
- exceptions.each do |rule|
|
23
|
+
tr
|
24
|
+
td
|
25
|
+
= rule.to_s
|
26
|
+
|
27
|
+
br
|
28
|
+
|
29
|
+
table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
|
30
|
+
thead
|
31
|
+
th style="width: 25%" Next 10 runs
|
32
|
+
th style="width: 75%"
|
33
|
+
- @schedule.next_occurrences(10, @time).each do |time|
|
34
|
+
tr
|
35
|
+
td
|
36
|
+
time= time.getutc
|
37
|
+
td
|
38
|
+
== relative_time(time)
|
39
|
+
|
40
|
+
br
|
data/lib/sidetiq/web.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'sidekiq/web'
|
2
|
+
|
3
|
+
module Sidetiq
|
4
|
+
module Web
|
5
|
+
|
6
|
+
def self.registered(app)
|
7
|
+
app.helpers do
|
8
|
+
def find_template(view, *args, &block)
|
9
|
+
path = File.expand_path(File.join('..', 'views'), __FILE__)
|
10
|
+
super(path, *args, &block)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
app.get "/sidetiq" do
|
16
|
+
clock = Sidetiq::Clock.instance
|
17
|
+
@schedules = clock.schedules
|
18
|
+
@time = clock.gettime
|
19
|
+
slim :sidetiq
|
20
|
+
end
|
21
|
+
|
22
|
+
app.get "/sidetiq/:name" do
|
23
|
+
halt 404 unless (name = params[:name])
|
24
|
+
|
25
|
+
clock = Sidetiq::Clock.instance
|
26
|
+
schedules = clock.schedules
|
27
|
+
|
28
|
+
@time = clock.gettime
|
29
|
+
|
30
|
+
@worker, @schedule = schedules.select do |worker, schedule|
|
31
|
+
worker.name == name
|
32
|
+
end.flatten
|
33
|
+
|
34
|
+
slim :sidetiq_details
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Sidekiq::Web.register(Sidetiq::Web)
|
41
|
+
|
42
|
+
if Sidekiq::Web.tabs.is_a?(Array)
|
43
|
+
Sidekiq::Web.tabs << "sidetiq"
|
44
|
+
else
|
45
|
+
Sidekiq::Web.tabs["Sidetiq"] = "sidetiq"
|
46
|
+
end
|
47
|
+
|
data/lib/sidetiq.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# stdlib
|
2
|
+
require 'monitor'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
# gems
|
7
|
+
require 'ice_cube'
|
8
|
+
require 'sidekiq'
|
9
|
+
|
10
|
+
# c extensions
|
11
|
+
require 'sidetiq_ext'
|
12
|
+
|
13
|
+
# internal
|
14
|
+
require 'sidetiq/config'
|
15
|
+
require 'sidetiq/clock'
|
16
|
+
require 'sidetiq/middleware'
|
17
|
+
require 'sidetiq/schedule'
|
18
|
+
require 'sidetiq/schedulable'
|
19
|
+
require 'sidetiq/version'
|
20
|
+
|
data/sidetiq.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path(File.join('..', 'lib'), __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sidetiq/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "sidetiq"
|
8
|
+
gem.version = Sidetiq::VERSION::STRING
|
9
|
+
gem.authors = ["Tobias Svensson"]
|
10
|
+
gem.email = ["tob@tobiassvensson.co.uk"]
|
11
|
+
gem.description = "High-resolution job scheduler for Sidekiq"
|
12
|
+
gem.summary = gem.description
|
13
|
+
gem.homepage = "http://github.com/tobiassvn/sidetiq"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
gem.extensions = ['ext/sidetiq_ext/extconf.rb']
|
21
|
+
|
22
|
+
gem.add_dependency 'sidekiq'
|
23
|
+
gem.add_dependency 'ice_cube'
|
24
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start { add_filter "/test/" }
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'mocha/setup'
|
6
|
+
require 'rack/test'
|
7
|
+
|
8
|
+
require 'sidekiq'
|
9
|
+
require 'sidekiq/testing'
|
10
|
+
|
11
|
+
require 'sidetiq'
|
12
|
+
require 'sidetiq/web'
|
13
|
+
|
14
|
+
# Keep the test output clean
|
15
|
+
Sidekiq.logger = Logger.new(nil)
|
16
|
+
|
17
|
+
class Sidetiq::TestCase < MiniTest::Unit::TestCase
|
18
|
+
def setup
|
19
|
+
Sidekiq.redis { |r| r.flushall }
|
20
|
+
end
|
21
|
+
|
22
|
+
def clock
|
23
|
+
@clock ||= Sidetiq::Clock.instance
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/test/test_clock.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestClock < Sidetiq::TestCase
|
4
|
+
class FakeWorker;
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_delegates_to_instance
|
8
|
+
Sidetiq::Clock.instance.expects(:foo).once
|
9
|
+
Sidetiq::Clock.foo
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_start_stop
|
13
|
+
refute clock.ticking?
|
14
|
+
assert_nil clock.thread
|
15
|
+
|
16
|
+
clock.start!
|
17
|
+
Thread.pass
|
18
|
+
sleep 0.01
|
19
|
+
|
20
|
+
assert clock.ticking?
|
21
|
+
assert_kind_of Thread, clock.thread
|
22
|
+
|
23
|
+
clock.stop!
|
24
|
+
Thread.pass
|
25
|
+
sleep 0.01
|
26
|
+
|
27
|
+
refute clock.ticking?
|
28
|
+
refute clock.thread.alive?
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_gettime_seconds
|
32
|
+
assert_equal clock.gettime.tv_sec, Time.now.tv_sec
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_gettime_nsec
|
36
|
+
refute_nil clock.gettime.tv_nsec
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_gettime_utc
|
40
|
+
refute clock.gettime.utc?
|
41
|
+
Sidetiq.config.utc = true
|
42
|
+
assert clock.gettime.utc?
|
43
|
+
Sidetiq.config.utc = false
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_enqueues_jobs_by_schedule
|
47
|
+
schedule = Sidetiq::Schedule.new(Sidetiq::Clock::START_TIME)
|
48
|
+
schedule.daily
|
49
|
+
|
50
|
+
clock.stubs(:schedules).returns(FakeWorker => schedule)
|
51
|
+
|
52
|
+
FakeWorker.expects(:perform_at).times(10)
|
53
|
+
|
54
|
+
10.times do |i|
|
55
|
+
clock.stubs(:gettime).returns(Time.local(2011, 1, i + 1, 1))
|
56
|
+
clock.tick
|
57
|
+
end
|
58
|
+
|
59
|
+
clock.stubs(:gettime).returns(Time.local(2011, 1, 10, 2))
|
60
|
+
clock.tick
|
61
|
+
clock.tick
|
62
|
+
clock.tick
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/test/test_config.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestConfig < Sidetiq::TestCase
|
4
|
+
def setup
|
5
|
+
@saved = Sidetiq.config
|
6
|
+
Sidetiq.config = OpenStruct.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
Sidetiq.config = @saved
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_configure
|
14
|
+
Sidetiq.configure do |config|
|
15
|
+
config.test = 42
|
16
|
+
end
|
17
|
+
|
18
|
+
assert_equal 42, Sidetiq.config.test
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/test/test_errors.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestMiddleware < Sidetiq::TestCase
|
4
|
+
def middleware
|
5
|
+
Sidetiq::Middleware.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_restarts_clock
|
9
|
+
clock.stubs(:ticking?).returns(false)
|
10
|
+
clock.expects(:start!).once
|
11
|
+
middleware.call {}
|
12
|
+
|
13
|
+
clock.stubs(:ticking?).returns(true)
|
14
|
+
clock.expects(:start!).never
|
15
|
+
middleware.call {}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestSchedule < Sidetiq::TestCase
|
4
|
+
def test_super
|
5
|
+
assert_equal IceCube::Schedule, Sidetiq::Schedule.superclass
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_method_missing
|
9
|
+
sched = Sidetiq::Schedule.new
|
10
|
+
sched.daily
|
11
|
+
assert_equal "Daily", sched.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_schedule_next?
|
15
|
+
sched = Sidetiq::Schedule.new
|
16
|
+
|
17
|
+
sched.daily
|
18
|
+
|
19
|
+
assert sched.schedule_next?(Time.now + (24 * 60 * 60))
|
20
|
+
refute sched.schedule_next?(Time.now + (24 * 60 * 60))
|
21
|
+
assert sched.schedule_next?(Time.now + (2 * 24 * 60 * 60))
|
22
|
+
refute sched.schedule_next?(Time.now + (2 * 24 * 60 * 60))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestVersion < Sidetiq::TestCase
|
4
|
+
def test_major
|
5
|
+
assert_instance_of Fixnum, Sidetiq::VERSION::MAJOR
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_minor
|
9
|
+
assert_instance_of Fixnum, Sidetiq::VERSION::MINOR
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_patch
|
13
|
+
assert_instance_of Fixnum, Sidetiq::VERSION::PATCH
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_string
|
17
|
+
assert_equal Sidetiq::VERSION::STRING, [Sidetiq::VERSION::MAJOR,
|
18
|
+
Sidetiq::VERSION::MINOR, Sidetiq::VERSION::PATCH].compact.join('.')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/test/test_web.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestWeb < Sidetiq::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class Worker
|
7
|
+
include Sidekiq::Worker
|
8
|
+
include Sidetiq::Schedulable
|
9
|
+
|
10
|
+
tiq do
|
11
|
+
daily(1)
|
12
|
+
yearly(2)
|
13
|
+
monthly(3)
|
14
|
+
|
15
|
+
add_exception_rule yearly.month_of_year(:february)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def app
|
20
|
+
Sidekiq::Web
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_home_tab
|
24
|
+
get '/'
|
25
|
+
assert_equal 200, last_response.status
|
26
|
+
assert_match last_response.body, /Sidekiq/
|
27
|
+
assert_match last_response.body, /Sidetiq/
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_sidetiq_page
|
31
|
+
get '/sidetiq'
|
32
|
+
assert_equal 200, last_response.status
|
33
|
+
|
34
|
+
clock.schedules.each do |worker, schedule|
|
35
|
+
assert_match last_response.body, /#{worker.name}/
|
36
|
+
assert_match last_response.body, /#{worker.get_sidekiq_options['queue']}/
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_details_page
|
41
|
+
get "/sidetiq/#{Worker.name}"
|
42
|
+
assert_equal 200, last_response.status
|
43
|
+
schedule = clock.schedules[Worker]
|
44
|
+
|
45
|
+
schedule.recurrence_rules.each do |rule|
|
46
|
+
assert_match last_response.body, /#{rule.to_s}/
|
47
|
+
end
|
48
|
+
|
49
|
+
schedule.exception_rules.each do |rule|
|
50
|
+
assert_match last_response.body, /#{rule.to_s}/
|
51
|
+
end
|
52
|
+
|
53
|
+
schedule.next_occurrences(10).each do |time|
|
54
|
+
assert_match last_response.body, /#{time.getutc.to_s}/
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sidetiq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tobias Svensson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
none: false
|
21
|
+
name: sidekiq
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
requirement: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
none: false
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
none: false
|
37
|
+
name: ice_cube
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
none: false
|
46
|
+
description: High-resolution job scheduler for Sidekiq
|
47
|
+
email:
|
48
|
+
- tob@tobiassvensson.co.uk
|
49
|
+
executables: []
|
50
|
+
extensions:
|
51
|
+
- ext/sidetiq_ext/extconf.rb
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .travis.yml
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- examples/simple.rb
|
61
|
+
- ext/sidetiq_ext/extconf.rb
|
62
|
+
- ext/sidetiq_ext/sidetiq_ext.c
|
63
|
+
- ext/sidetiq_ext/sidetiq_ext.h
|
64
|
+
- lib/sidetiq.rb
|
65
|
+
- lib/sidetiq/clock.rb
|
66
|
+
- lib/sidetiq/config.rb
|
67
|
+
- lib/sidetiq/middleware.rb
|
68
|
+
- lib/sidetiq/schedulable.rb
|
69
|
+
- lib/sidetiq/schedule.rb
|
70
|
+
- lib/sidetiq/version.rb
|
71
|
+
- lib/sidetiq/views/sidetiq.slim
|
72
|
+
- lib/sidetiq/views/sidetiq_details.slim
|
73
|
+
- lib/sidetiq/web.rb
|
74
|
+
- sidetiq.gemspec
|
75
|
+
- test/helper.rb
|
76
|
+
- test/test_clock.rb
|
77
|
+
- test/test_config.rb
|
78
|
+
- test/test_errors.rb
|
79
|
+
- test/test_middleware.rb
|
80
|
+
- test/test_schedule.rb
|
81
|
+
- test/test_version.rb
|
82
|
+
- test/test_web.rb
|
83
|
+
homepage: http://github.com/tobiassvn/sidetiq
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
none: false
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
none: false
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.23
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: High-resolution job scheduler for Sidekiq
|
108
|
+
test_files:
|
109
|
+
- test/helper.rb
|
110
|
+
- test/test_clock.rb
|
111
|
+
- test/test_config.rb
|
112
|
+
- test/test_errors.rb
|
113
|
+
- test/test_middleware.rb
|
114
|
+
- test/test_schedule.rb
|
115
|
+
- test/test_version.rb
|
116
|
+
- test/test_web.rb
|
117
|
+
has_rdoc:
|