jober 0.0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
- define_method method_name do |msg = nil, &block|
15
- logger.send(method_name, msg, &block)
16
- end
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
@@ -21,11 +21,8 @@ class Jober::Manager
21
21
 
22
22
  def run!
23
23
  @allowed_classes.each do |klass|
24
- workers = klass.workers
25
- workers = [[5 * 60, :perform]] if workers.empty?
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, method)
80
- debug { "start worker for #{klass.to_s} #{method}" }
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, method)
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, method)
98
- info "invoke #{klass} #{method}"
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
- inst.execute(method)
111
+ catch do
112
+ inst.execute
113
+ end
116
114
  end
117
115
  end
118
116
 
@@ -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
- @queue_name = "Jober:queue:#{q}"
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.dump(args))
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(method)
38
+ def run
34
39
  cnt = 0
35
40
  while args = pop
36
- send(method, *args)
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
@@ -1,5 +1,10 @@
1
- class Jober::QueueBatch < Jober::Queue
2
- class << self
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
- def execute(method = :perform)
13
- cnt = 0
14
- batch = []
15
- while args = pop
16
- batch << args
17
- if batch.length >= self.class.get_batch_size
18
- execute_batch(method, batch)
19
- batch = []
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
- break if stopped
22
- cnt += 1
23
- end
24
- if batch.length > 0
25
- if stopped
26
- reschedule_batch(batch)
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
- def execute_batch(method, batch)
35
- send(method, batch)
36
- rescue Object => ex
37
- reschedule_batch(batch)
38
- Jober.exception(ex)
39
- end
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
- private
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
@@ -1,16 +1,29 @@
1
1
  class Jober::SharedObject
2
2
  class << self
3
3
  def set(name, obj, conn = Jober.redis)
4
- conn.set(key(name), Marshal.dump(obj))
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
- r = conn.get(key(name))
9
- Marshal.load(r) if r
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.redis.get(key(name))
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.redis.incrby(key(name), by)
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.redis.del key(name)
44
+ Jober.catch do
45
+ Jober.redis.del key(name)
46
+ end
30
47
  end
31
48
 
32
49
  def clear
33
- Jober.redis.keys(key('*')).each { |k| del(k) }
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.redis.keys(key(mask)).map { |key| get(key) }
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
@@ -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(/_?queue_?/, '').gsub(/_?fk_?/, '').gsub(/_?task_?/, '').split('::').last.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(method)
17
- send(method)
16
+ def run
17
+ perform
18
18
  end
19
19
 
20
20
  end
@@ -5,7 +5,7 @@ class Jober::UniqueQueue < Jober::Queue
5
5
  end
6
6
 
7
7
  def self.enqueue(*args)
8
- Jober.redis.sadd(queue_name, Jober.dump(args))
8
+ Jober.redis.sadd(queue_name, Jober.dump_args(*args))
9
9
  end
10
10
 
11
11
  def pop
@@ -0,0 +1,5 @@
1
+ require "#{File.dirname(__FILE__)}/queue_batch"
2
+
3
+ class Jober::UniqueQueueBatch < Jober::UniqueQueue
4
+ include Jober::QueueBatchFeature
5
+ end
@@ -1,3 +1,3 @@
1
1
  module Jober
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2"
3
3
  end
@@ -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