jober 0.0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/Rakefile +6 -0
- data/bench/queue.rb +25 -0
- data/bin/jober +50 -5
- data/bin/{manager → jober_manager} +0 -0
- data/examples/classes.rb +3 -8
- data/jober.gemspec +2 -0
- data/lib/jober.rb +90 -26
- data/lib/jober/abstract_task.rb +105 -15
- data/lib/jober/ar_loop.rb +38 -0
- data/lib/jober/logger.rb +19 -3
- data/lib/jober/manager.rb +11 -13
- data/lib/jober/queue.rb +11 -6
- data/lib/jober/queue_batch.rb +42 -29
- data/lib/jober/shared_object.rb +37 -9
- data/lib/jober/task.rb +3 -3
- data/lib/jober/unique_queue.rb +1 -1
- data/lib/jober/unique_queue_batch.rb +5 -0
- data/lib/jober/version.rb +1 -1
- data/spec/ar_loop_spec.rb +69 -0
- data/spec/chain_spec.rb +2 -2
- data/spec/integration_spec.rb +17 -6
- data/spec/jober_spec.rb +25 -0
- data/spec/kill_task_spec.rb +1 -1
- data/spec/namespace_spec.rb +16 -0
- data/spec/pids_spec.rb +10 -3
- data/spec/queue_batch_spec.rb +2 -2
- data/spec/queue_parallel_spec.rb +3 -2
- data/spec/queue_spec.rb +2 -3
- data/spec/shared_object_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/stats_spec.rb +6 -4
- data/spec/task_loop_spec.rb +103 -0
- data/spec/task_spec.rb +10 -9
- data/spec/unique_queue_batch_spec.rb +28 -0
- data/spec/unique_queue_spec.rb +2 -2
- metadata +47 -4
@@ -0,0 +1,38 @@
|
|
1
|
+
class Jober::ARLoop < Jober::Task
|
2
|
+
class << self
|
3
|
+
def batch_size(bs)
|
4
|
+
@batch_size = bs
|
5
|
+
end
|
6
|
+
|
7
|
+
def get_batch_size
|
8
|
+
@batch_size ||= 1000
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def proxy
|
13
|
+
raise "implement me, should return AR.proxy, ex: User.where('years > 18')"
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
prox = proxy
|
18
|
+
|
19
|
+
if @worker_id && @workers_count && !@opts[:no_auto_proxy]
|
20
|
+
prox = prox.where("id % #{@workers_count} = #{@worker_id}")
|
21
|
+
end
|
22
|
+
|
23
|
+
prox = prox.where(@opts[:where]) if @opts[:where]
|
24
|
+
|
25
|
+
cnt = 0
|
26
|
+
count = prox.count
|
27
|
+
info { "full count to process #{count}" }
|
28
|
+
|
29
|
+
prox.find_in_batches(:batch_size => self.class.get_batch_size) do |batch|
|
30
|
+
res = perform(batch)
|
31
|
+
cnt += batch.size
|
32
|
+
info { "process batch #{res.inspect}, #{cnt} from #{count}, lastid #{batch.last.id}" }
|
33
|
+
break if stopped
|
34
|
+
end
|
35
|
+
|
36
|
+
info { "processed total #{cnt}" }
|
37
|
+
end
|
38
|
+
end
|
data/lib/jober/logger.rb
CHANGED
@@ -9,10 +9,26 @@ module Jober::Logger
|
|
9
9
|
@logger = logger
|
10
10
|
end
|
11
11
|
|
12
|
+
def logger_tag
|
13
|
+
@logger_tag ||= begin
|
14
|
+
tag = '[' + self.class.to_s
|
15
|
+
tag += " #{@worker_id}-#{@workers_count}" if @worker_id && @workers_count && @workers_count > 1
|
16
|
+
tag += ']'
|
17
|
+
tag
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
12
21
|
Logger::Severity.constants.each do |level|
|
13
22
|
method_name = level.to_s.downcase
|
14
|
-
|
15
|
-
|
16
|
-
|
23
|
+
|
24
|
+
class_eval <<-Q
|
25
|
+
def #{method_name}(msg = nil, &block)
|
26
|
+
if block
|
27
|
+
logger.send(:#{method_name}) { "\#{logger_tag} \#{block.call}" }
|
28
|
+
else
|
29
|
+
logger.send(:#{method_name}, "\#{logger_tag} \#{msg}", &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
Q
|
17
33
|
end
|
18
34
|
end
|
data/lib/jober/manager.rb
CHANGED
@@ -21,11 +21,8 @@ class Jober::Manager
|
|
21
21
|
|
22
22
|
def run!
|
23
23
|
@allowed_classes.each do |klass|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
workers.each do |interval, method|
|
28
|
-
Thread.new { start_worker(klass, interval, method) }
|
24
|
+
klass.get_workers.times do |idx|
|
25
|
+
Thread.new { start_worker(klass, klass.get_interval, idx, klass.get_workers) }
|
29
26
|
end
|
30
27
|
end
|
31
28
|
end
|
@@ -71,17 +68,16 @@ class Jober::Manager
|
|
71
68
|
yield
|
72
69
|
true
|
73
70
|
rescue Object => ex
|
74
|
-
p "exception #{ex.inspect} #{ex.backtrace}"
|
75
71
|
Jober.exception(ex)
|
76
72
|
nil
|
77
73
|
end
|
78
74
|
|
79
|
-
def start_worker(klass, interval,
|
80
|
-
debug { "start worker for #{klass.to_s}
|
75
|
+
def start_worker(klass, interval, idx, count)
|
76
|
+
debug { "start worker for #{klass.to_s}" }
|
81
77
|
loop do
|
82
78
|
pid = nil
|
83
79
|
res = catch do
|
84
|
-
pid = run_task_fork(klass,
|
80
|
+
pid = run_task_fork(klass, idx, count)
|
85
81
|
add_pid(pid)
|
86
82
|
Process.wait(pid)
|
87
83
|
del_pid(pid)
|
@@ -94,15 +90,15 @@ class Jober::Manager
|
|
94
90
|
end
|
95
91
|
end
|
96
92
|
|
97
|
-
def run_task_fork(klass,
|
98
|
-
info "invoke #{klass}
|
93
|
+
def run_task_fork(klass, idx, count)
|
94
|
+
info "invoke #{klass}"
|
99
95
|
fork do
|
100
96
|
$0 = "#{@name} manager #{klass}"
|
101
97
|
#$0 += " #{index}" if index > 0
|
102
98
|
Jober.call_after_fork
|
103
99
|
Jober.reset_redis
|
104
100
|
|
105
|
-
inst = klass.new # class_name parent of Jober::Task
|
101
|
+
inst = klass.new(:worker_id => idx, :workers_count => count) # class_name parent of Jober::Task
|
106
102
|
|
107
103
|
if @logger_path
|
108
104
|
logger_path = File.join(@logger_path, "#{klass.short_name}.log")
|
@@ -112,7 +108,9 @@ class Jober::Manager
|
|
112
108
|
inst.logger = ::Logger.new(logger_path)
|
113
109
|
end
|
114
110
|
|
115
|
-
|
111
|
+
catch do
|
112
|
+
inst.execute
|
113
|
+
end
|
116
114
|
end
|
117
115
|
end
|
118
116
|
|
data/lib/jober/queue.rb
CHANGED
@@ -6,10 +6,11 @@ class Jober::Queue < Jober::Task
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class << self
|
9
|
-
attr_accessor :queue_name
|
9
|
+
attr_accessor :queue_name, :queue_name_base
|
10
10
|
|
11
11
|
def set_queue_name(q)
|
12
|
-
@
|
12
|
+
@queue_name_base = q
|
13
|
+
@queue_name = Jober.key("queue:#{q}")
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
@@ -18,22 +19,26 @@ class Jober::Queue < Jober::Task
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def self.enqueue(*args)
|
21
|
-
Jober.redis.rpush(queue_name, Jober.
|
22
|
+
Jober.redis.rpush(queue_name, Jober.dump_args(*args))
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.len
|
25
26
|
Jober.redis.llen(self.queue_name)
|
26
27
|
end
|
27
28
|
|
29
|
+
def len
|
30
|
+
self.class.len
|
31
|
+
end
|
32
|
+
|
28
33
|
def pop
|
29
34
|
res = Jober.redis.lpop(queue_name)
|
30
35
|
Jober.load(res) if res
|
31
36
|
end
|
32
37
|
|
33
|
-
def run
|
38
|
+
def run
|
34
39
|
cnt = 0
|
35
40
|
while args = pop
|
36
|
-
|
41
|
+
perform(*args)
|
37
42
|
cnt += 1
|
38
43
|
|
39
44
|
if stopped
|
@@ -42,6 +47,6 @@ class Jober::Queue < Jober::Task
|
|
42
47
|
|
43
48
|
info { "processed #{cnt}" } if cnt % 1000 == 0
|
44
49
|
end
|
45
|
-
info { "processed #{cnt}" }
|
50
|
+
info { "processed total #{cnt}" }
|
46
51
|
end
|
47
52
|
end
|
data/lib/jober/queue_batch.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Jober::QueueBatchFeature
|
2
|
+
def self.included(base)
|
3
|
+
base.send(:extend, ClassMethods)
|
4
|
+
base.send(:include, InstanceMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
3
8
|
def batch_size(bs)
|
4
9
|
@batch_size = bs
|
5
10
|
end
|
@@ -9,39 +14,47 @@ class Jober::QueueBatch < Jober::Queue
|
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
module InstanceMethods
|
18
|
+
def run
|
19
|
+
cnt = 0
|
20
|
+
batch = []
|
21
|
+
while args = pop
|
22
|
+
batch << args
|
23
|
+
if batch.length >= self.class.get_batch_size
|
24
|
+
execute_batch(batch)
|
25
|
+
info { "execute batch #{batch.length}, #{cnt} from #{len}" }
|
26
|
+
batch = []
|
27
|
+
end
|
28
|
+
break if stopped
|
29
|
+
cnt += 1
|
20
30
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
else
|
28
|
-
execute_batch(method, batch)
|
31
|
+
if batch.length > 0
|
32
|
+
if stopped
|
33
|
+
reschedule_batch(batch)
|
34
|
+
else
|
35
|
+
execute_batch(batch)
|
36
|
+
end
|
29
37
|
end
|
38
|
+
info { "processes total #{cnt} " }
|
39
|
+
self
|
30
40
|
end
|
31
|
-
self
|
32
|
-
end
|
33
41
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
def execute_batch(batch)
|
43
|
+
perform(batch)
|
44
|
+
rescue Object => ex
|
45
|
+
reschedule_batch(batch)
|
46
|
+
Jober.exception(ex)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
40
50
|
|
41
|
-
|
51
|
+
def reschedule_batch(batch)
|
52
|
+
batch.reverse_each { |ev| Jober.redis.lpush(queue_name, Jober.dump(ev)) }
|
53
|
+
end
|
42
54
|
|
43
|
-
def reschedule_batch(batch)
|
44
|
-
batch.reverse_each { |ev| Jober.redis.lpush(queue_name, Jober.dump(ev)) }
|
45
55
|
end
|
56
|
+
end
|
46
57
|
|
58
|
+
class Jober::QueueBatch < Jober::Queue
|
59
|
+
include Jober::QueueBatchFeature
|
47
60
|
end
|
data/lib/jober/shared_object.rb
CHANGED
@@ -1,16 +1,29 @@
|
|
1
1
|
class Jober::SharedObject
|
2
2
|
class << self
|
3
3
|
def set(name, obj, conn = Jober.redis)
|
4
|
-
|
4
|
+
Jober.catch do
|
5
|
+
conn.set(key(name), Marshal.dump(obj))
|
6
|
+
end
|
5
7
|
end
|
6
8
|
|
7
9
|
def get(name, conn = Jober.redis)
|
8
|
-
|
9
|
-
|
10
|
+
Jober.catch do
|
11
|
+
r = conn.get(key(name))
|
12
|
+
Marshal.load(r) if r
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_by_pure_key(name, conn = Jober.redis)
|
17
|
+
Jober.catch do
|
18
|
+
r = conn.get(name)
|
19
|
+
Marshal.load(r) if r
|
20
|
+
end
|
10
21
|
end
|
11
22
|
|
12
23
|
def raw_get(name)
|
13
|
-
Jober.
|
24
|
+
Jober.catch do
|
25
|
+
Jober.redis.get(key(name))
|
26
|
+
end
|
14
27
|
end
|
15
28
|
|
16
29
|
def [](name)
|
@@ -22,27 +35,42 @@ class Jober::SharedObject
|
|
22
35
|
end
|
23
36
|
|
24
37
|
def inc(name, by = 1)
|
25
|
-
Jober.
|
38
|
+
Jober.catch do
|
39
|
+
Jober.redis.incrby(key(name), by)
|
40
|
+
end
|
26
41
|
end
|
27
42
|
|
28
43
|
def del(name)
|
29
|
-
Jober.
|
44
|
+
Jober.catch do
|
45
|
+
Jober.redis.del key(name)
|
46
|
+
end
|
30
47
|
end
|
31
48
|
|
32
49
|
def clear
|
33
|
-
Jober.
|
50
|
+
Jober.catch do
|
51
|
+
Jober.redis.keys(key('*')).each { |k| Jober.redis.del(k) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def keys(mask)
|
56
|
+
Jober.catch do
|
57
|
+
Jober.redis.keys(key(mask))
|
58
|
+
end
|
34
59
|
end
|
35
60
|
|
36
61
|
def values(mask)
|
37
|
-
Jober.
|
62
|
+
Jober.catch do
|
63
|
+
Jober.redis.keys(key(mask)).map { |key| get_by_pure_key(key) }
|
64
|
+
end
|
38
65
|
end
|
39
66
|
|
40
67
|
def key(name)
|
41
|
-
if name.start_with?('shared:')
|
68
|
+
k = if name.start_with?('shared:')
|
42
69
|
name
|
43
70
|
else
|
44
71
|
"shared:#{name}"
|
45
72
|
end
|
73
|
+
Jober.key(k)
|
46
74
|
end
|
47
75
|
end
|
48
76
|
end
|
data/lib/jober/task.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Jober::Task < Jober::AbstractTask
|
2
2
|
|
3
3
|
def self.extract_name(name)
|
4
|
-
Jober.underscore(name).gsub(/_?
|
4
|
+
Jober.underscore(name).gsub(/[_\/]?queue[_\/]?/, '').gsub(/[_\/]?task[_\/]?/, '').gsub(/[_\/]?jober[_\/]?/, '')
|
5
5
|
end
|
6
6
|
|
7
7
|
def self.inherited(base)
|
@@ -13,8 +13,8 @@ class Jober::Task < Jober::AbstractTask
|
|
13
13
|
raise "implement me"
|
14
14
|
end
|
15
15
|
|
16
|
-
def run
|
17
|
-
|
16
|
+
def run
|
17
|
+
perform
|
18
18
|
end
|
19
19
|
|
20
20
|
end
|
data/lib/jober/unique_queue.rb
CHANGED
data/lib/jober/version.rb
CHANGED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
require "active_record"
|
3
|
+
|
4
|
+
class MyAR < Jober::ARLoop
|
5
|
+
batch_size 10
|
6
|
+
|
7
|
+
def proxy
|
8
|
+
User.where("years > 18")
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform(batch)
|
12
|
+
SO["names"] += batch.map(&:name)
|
13
|
+
batch.size
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
conn = { 'adapter' => 'sqlite3', 'database' => File.dirname(__FILE__) + "/test.db" }
|
18
|
+
ActiveRecord::Base.establish_connection conn
|
19
|
+
|
20
|
+
def pg_create_schema
|
21
|
+
ActiveRecord::Migration.verbose = false
|
22
|
+
ActiveRecord::Migration.create_table :users do |t|
|
23
|
+
t.string :name
|
24
|
+
t.integer :years
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def pg_drop_data
|
29
|
+
ActiveRecord::Migration.verbose = false
|
30
|
+
ActiveRecord::Migration.drop_table :users
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_data
|
34
|
+
100.times do |i|
|
35
|
+
User.create! :name => "unknown #{i}", :years => i % 36 + 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class User < ActiveRecord::Base; end
|
40
|
+
|
41
|
+
describe "ARLoop" do
|
42
|
+
before :all do
|
43
|
+
pg_drop_data rescue nil
|
44
|
+
pg_create_schema
|
45
|
+
create_data
|
46
|
+
end
|
47
|
+
|
48
|
+
before :each do
|
49
|
+
SO["names"] = []
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should work" do
|
53
|
+
MyAR.new.execute
|
54
|
+
SO["names"].size.should == 46
|
55
|
+
SO["names"].last.should == "unknown 99"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "use auto proxy sharding" do
|
59
|
+
MyAR.new(:worker_id => 1, :workers_count => 4).execute
|
60
|
+
SO["names"].size.should == 10
|
61
|
+
SO["names"].last.should == "unknown 96"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "where" do
|
65
|
+
MyAR.new(:where => "years < 24").execute
|
66
|
+
SO["names"].size.should == 15
|
67
|
+
SO["names"].last.should == "unknown 94"
|
68
|
+
end
|
69
|
+
end
|