jober 0.0.1 → 0.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.
- 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
|