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.
@@ -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