bluth 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bluth.rb CHANGED
@@ -1,10 +1,17 @@
1
1
  # encoding: utf-8
2
2
  BLUTH_LIB_HOME = File.expand_path File.dirname(__FILE__) unless defined?(BLUTH_LIB_HOME)
3
3
 
4
+ local_libs = %w{familia}
5
+ local_libs.each { |dir|
6
+ a = File.join(BLUTH_LIB_HOME, '..', '..', dir, 'lib')
7
+ $:.unshift a
8
+ }
9
+
4
10
  require 'sysinfo'
11
+ require 'storable'
12
+ require 'gibbler'
5
13
  require 'familia'
6
14
 
7
-
8
15
  module Bluth
9
16
  module VERSION
10
17
  def self.to_s
@@ -26,17 +33,15 @@ module Bluth
26
33
  class Maeby < Familia::Problem; end
27
34
  # A shutdown request. We burn down the banana stand.
28
35
  class Shutdown < Familia::Problem; end
29
-
30
- @db = 15
31
- @queues = {}
32
- @poptimeout = 60.seconds
36
+ @db = 0
37
+ @poptimeout = 60 #.seconds
33
38
  @handlers = []
34
39
  @locks = []
35
40
  @sysinfo = nil
36
41
  @priority = []
37
42
  @scheduler = nil
38
43
  class << self
39
- attr_reader :queues, :handlers, :db, :conf, :locks
44
+ attr_reader :handlers, :db, :conf, :locks
40
45
  attr_accessor :redis, :uri, :priority, :scheduler, :poptimeout
41
46
  def sysinfo
42
47
  @sysinfo ||= SysInfo.new.freeze
@@ -50,102 +55,45 @@ module Bluth
50
55
  Bluth.redis.del lock
51
56
  }
52
57
  end
58
+ def Bluth.find_locks
59
+ @locks = Bluth.redis.keys(Familia.rediskey('*', :lock))
60
+ end
53
61
 
54
62
  def Bluth.queue?(n)
55
- @queues.has_key?(n.to_sym)
63
+ Bluth::Queue.queues.collect(&:name).member?(n.to_s.to_sym)
56
64
  end
57
65
  def Bluth.queue(n)
58
- @queues[n.to_sym]
66
+ raise ArgumentError, "No such queue: #{n}" unless queue?(n)
67
+ Bluth::Queue.send n
59
68
  end
60
69
 
61
- def Bluth.conf=(conf={})
62
- @conf = conf.clone
63
- @conf[:db] = @db
64
- connect!
65
- @conf
66
- end
67
-
68
- def Bluth.connect!
69
- @uri = Redis.uri(@conf).freeze
70
- @redis = Familia.connect @uri
71
- end
72
-
73
- def Bluth.find_locks
74
- @locks = Bluth.redis.keys(Familia.key('*', :lock))
75
- end
70
+ require 'bluth/worker'
76
71
 
77
- class Queue
72
+ module Queue # if this is a module the
78
73
  include Familia
79
- prefix :queue
80
- def self.rangeraw(count=100)
81
- gobids = Queue.redis.lrange(key, 0, count-1) || []
82
- end
83
- def self.range(count=100)
84
- gobids = rangeraw count
85
- gobids.collect { |gobid|
86
- gob = Gob.from_redis gobid
87
- next if gob.nil?
88
- gob.current_queue = self
89
- gob
90
- }.compact
91
- end
92
- def self.dequeue(gobid)
93
- Queue.redis.lrem key, 0, gobid
94
- end
95
- def self.inherited(obj)
96
- obj.prefix self.prefix
97
- obj.suffix obj.to_s.split('::').last.downcase.to_sym
98
- raise Buster.new("Duplicate queue: #{obj.suffix}") if Bluth.queue?(obj.suffix)
99
- Bluth.queues[obj.suffix] = obj
100
- super(obj)
101
- end
102
- def self.key(pref=nil,suff=nil)
103
- Familia.key( pref || prefix, suff || suffix)
104
- end
105
- def self.report
106
- Bluth.queues.keys.collect { |q|
107
- klass = Bluth.queue(q)
108
- ("%10s: %4d" % [q, klass.size])
109
- }.join($/)
110
- end
111
- def self.from_string(str)
112
- raise Buster, "Unknown queue: #{str}" unless Bluth.queue?(str)
113
- Bluth.queue(str)
114
- end
115
- def self.any?
116
- size > 0
117
- end
118
-
119
- def self.empty?
120
- size == 0
121
- end
122
-
123
- def self.size
124
- begin
125
- Queue.redis.llen key
126
- rescue => ex
127
- STDERR.puts ex.message, ex.backtrace
128
- 0
74
+ prefix [:bluth, :queue]
75
+ class_list :critical #, :class => Bluth::Gob
76
+ class_list :high
77
+ class_list :low
78
+ class_list :running
79
+ class_list :successful
80
+ class_list :failed
81
+ class_list :orphaned
82
+ class << self
83
+ # The complete list of queues in the order they were defined
84
+ def queues
85
+ Bluth::Queue.class_lists.collect(&:name).collect do |qname|
86
+ self.send qname
87
+ end
129
88
  end
130
- end
131
- def self.push(gobid)
132
- Queue.redis.lpush self.key, gobid
133
- end
134
-
135
- def self.pop
136
- gobid = Queue.redis.rpoplpush key, Bluth::Running.key
137
- return if gobid.nil?
138
- Familia.ld "FOUND gob #{gobid} from #{self.key}"
139
- gob = Gob.from_redis gobid
140
- if gob.nil?
141
- Familia.info "No such gob object: #{gobid}"
142
- Bluth::Running.dequeue gobid
143
- return
89
+ # The subset of queues that new jobs arrive in, in order of priority
90
+ def entry_queues
91
+ Bluth.priority.collect { |qname| self.send qname }
144
92
  end
145
- gob.current_queue = Bluth::Running
146
- gob.save
147
- gob
148
93
  end
94
+
95
+ # Set default priority
96
+ Bluth.priority = [:critical, :high, :low]
149
97
  end
150
98
 
151
99
  # Workers use a blocking pop and will wait for up to
@@ -155,57 +103,197 @@ module Bluth
155
103
  # value is use. See:
156
104
  #
157
105
  # http://code.google.com/p/redis/wiki/BlpopCommand
106
+ def Bluth.shift
107
+ blocking_queue_handler :blpop
108
+ end
109
+
158
110
  def Bluth.pop
159
- #Bluth.priority.each { |queue|
160
- # ret = queue.pop
161
- # return ret unless ret.nil?
162
- #}
111
+ blocking_queue_handler :brpop
112
+ end
113
+
114
+ private
115
+
116
+ # +meth+ is either :blpop or :brpop
117
+ def Bluth.blocking_queue_handler meth
118
+ gob = nil
163
119
  begin
164
- #Familia.ld :BRPOP, Queue.redis, self, caller[1] if Familia.debug?
165
- order = Bluth.priority.collect { |queue| queue.key }
120
+ order = Bluth::Queue.entry_queues.collect(&:rediskey)
166
121
  order << Bluth.poptimeout # We do it this way to support Ruby 1.8
167
- gobinfo = Bluth::Queue.redis.brpop *order
168
- unless gobinfo.nil?
169
- Familia.info "FOUND #{gobinfo.inspect}" if Familia.debug?
170
- gob = Gob.from_redis gobinfo[1]
171
- raise Bluth::Buster, "No such gob object: #{gobinfo[1]}" if gob.nil?
172
- Bluth::Running.push gob.id
173
- gob.current_queue = Bluth::Running
122
+ queue, gobid = *(Bluth::Queue.redis.send(meth, *order) || [])
123
+ unless queue.nil?
124
+ Familia.ld "FOUND #{gobid} id #{queue}"
125
+ gob = Gob.from_redis gobid
126
+ raise Bluth::Buster, "No such gob object: #{gobid}" if gob.nil?
127
+ Bluth::Queue.running << gob.jobid
128
+ gob.current_queue = :running
174
129
  gob.save
175
130
  end
176
131
  rescue => ex
177
- if gobinfo.nil?
132
+ if queue.nil?
178
133
  Familia.info "ERROR: #{ex.message}"
179
134
  else
180
- Familia.info "ERROR (#{ex.message}); putting #{gobinfo[1]} back on queue"
181
- Bluth::Orphaned.push gobinfo[1]
135
+ Familia.info "ERROR (#{ex.message}): #{gobid} is an orphan"
136
+ Bluth::Queue.orphaned << gobid
182
137
  end
138
+ Familia.ld ex.backtrace
183
139
  end
184
140
  gob
141
+ end
142
+ end
143
+
144
+
145
+
146
+ module Bluth
147
+ module Handler
148
+
149
+ def self.extended(obj)
150
+ obj.send :include, Familia
151
+ obj.class_string :success
152
+ obj.class_string :failure
153
+ obj.class_string :running
154
+ Bluth.handlers << obj
155
+ end
156
+
157
+ [:success, :failure, :running].each do |name|
158
+ define_method "#{name}!" do
159
+ self.send(name).increment
160
+ end
161
+ end
162
+
163
+ def enqueue(data={},q=nil)
164
+ q = self.queue(q)
165
+ gob = Gob.create generate_id(data), self, data
166
+ gob.current_queue = q.name
167
+ gob.created
168
+ gob.attempts = 0
169
+ gob.save
170
+ Familia.ld "ENQUEUING: #{self} #{gob.jobid.short} to #{q}"
171
+ q << gob.jobid
172
+ gob
173
+ end
174
+ def queue(name=nil)
175
+ @queue = name if name
176
+ Bluth::Queue.send(@queue || :high)
177
+ end
178
+ def generate_id(*args)
179
+ [self, Process.pid, Bluth.sysinfo.hostname, Time.now.to_f, *args].gibbler
180
+ end
181
+ def all
182
+ Bluth::Gob.instances.select do |gob|
183
+ gob.handler == self
184
+ end
185
+ end
186
+ def prepare
187
+ end
188
+
185
189
  end
186
-
187
- class Critical < Queue
188
- end
189
- class High < Queue
190
- end
191
- class Low < Queue
192
- end
193
- class Running < Queue
194
- end
195
- class Failed < Queue
196
- end
197
- class Successful < Queue
198
- end
199
- class Scheduled < Queue
200
- end
201
- class Orphaned < Queue
202
- end
190
+ end
191
+
192
+
193
+ module Bluth
194
+ class Gob < Storable
195
+ MAX_ATTEMPTS = 3.freeze unless defined?(Gob::MAX_ATTEMPTS)
196
+ include Familia
197
+ prefix [:bluth, :gob]
198
+ ttl 3600 #.seconds
199
+ index :jobid
200
+ field :jobid => Gibbler::Digest
201
+ field :handler => String
202
+ field :data => Hash
203
+ field :messages => Array
204
+ field :attempts => Integer
205
+ field :create_time => Float
206
+ field :stime => Float
207
+ field :etime => Float
208
+ field :current_queue => Symbol
209
+ field :thread_id => Integer
210
+ field :cpu => Array
211
+ field :wid => Gibbler::Digest
212
+ include Familia::Stamps
203
213
 
204
- require 'bluth/gob'
205
- require 'bluth/worker'
206
-
207
- Bluth.priority = [Bluth::Critical, Bluth::High, Bluth::Low]
208
- Bluth.scheduler = ScheduleWorker
214
+ def jobid
215
+ Gibbler::Digest.new(@jobid)
216
+ end
217
+ def clear!
218
+ @attempts = 0
219
+ @messages = []
220
+ save
221
+ end
222
+ def preprocess
223
+ @attempts ||= 0
224
+ @messages ||= []
225
+ @create_time ||= Time.now.utc.to_f
226
+ end
227
+ def attempt?
228
+ attempts < MAX_ATTEMPTS
229
+ end
230
+ def attempt!
231
+ @attempts = attempts + 1
232
+ end
233
+ def current_queue
234
+ @current_queue
235
+ end
236
+ def handler
237
+ eval "::#{@handler}" if @handler
238
+ end
239
+ def perform
240
+ @attempts += 1
241
+ Familia.ld "PERFORM: #{self.to_hash.inspect}"
242
+ @stime = Time.now.utc.to_f
243
+ save # update the time
244
+ self.handler.prepare if self.class.respond_to?(:prepare)
245
+ self.handler.perform @data
246
+ @etime = Time.now.utc.to_f
247
+ save # update the time
248
+ end
249
+ def delayed?
250
+ start = @stime || 0
251
+ start > Time.now.utc.to_f
252
+ end
253
+ def retry!(msg=nil)
254
+ move! :high, msg
255
+ end
256
+ def failure!(msg=nil)
257
+ @etime = Time.now.utc.to_i
258
+ self.handler.failure!
259
+ move! :failed, msg
260
+ end
261
+ def success!(msg=nil)
262
+ @etime = Time.now.utc.to_i
263
+ self.handler.success!
264
+ move! :successful, msg
265
+ end
266
+ def duration
267
+ return 0 if @stime.nil?
268
+ et = @etime || Time.now.utc.to_i
269
+ et - @stime
270
+ end
271
+ def queue
272
+ Bluth.queue(current_queue)
273
+ end
274
+ def dequeue!
275
+ Familia.ld "Deleting #{self.jobid} from #{queue.rediskey}"
276
+ queue.remove 0, self.jobid
277
+ end
278
+ def running!
279
+ move! :running
280
+ end
281
+ def move!(to, msg=nil)
282
+ @thread_id = $$
283
+ #if to.to_s == current_queue.to_s
284
+ # raise Bluth::Buster, "Cannot move job to the queue it's in: #{to}"
285
+ #end
286
+ from, to = Bluth.queue(current_queue), Bluth.queue(to)
287
+ Familia.ld "Moving #{self.jobid} from #{from.rediskey} to #{to.rediskey}"
288
+ @messages << msg unless msg.nil? || msg.empty?
289
+ # We push first to make sure we never lose a Gob ID. Instead
290
+ # there's the small chance of a job ID being in two queues.
291
+ to << @jobid
292
+ ret = from.remove @jobid, 0
293
+ @current_queue = to.name
294
+ save # update messages
295
+ end
296
+ end
209
297
 
210
298
  end
211
299
 
@@ -0,0 +1,30 @@
1
+ require 'bluth'
2
+ require 'bluth/test_helpers'
3
+
4
+ Bluth::Queue.critical.clear
5
+
6
+ ## Knows queue names
7
+ Bluth::Queue.queues.collect(&:name)
8
+ #=> [:critical, :high, :low, :running, :successful, :failed, :orphaned]
9
+
10
+ ## Knows queue keys
11
+ Bluth::Queue.queues.collect(&:rediskey)
12
+ #=> ["bluth:queue:critical", "bluth:queue:high", "bluth:queue:low", "bluth:queue:running", "bluth:queue:successful", "bluth:queue:failed", "bluth:queue:orphaned"]
13
+
14
+ ## Knows a queue
15
+ ret = Bluth::Queue.critical
16
+ ret.class
17
+ #=> Familia::List
18
+
19
+ ## Can push on to a queue
20
+ Bluth::Queue.critical.push 'job1'
21
+ Bluth::Queue.critical.push 'job2'
22
+ Bluth::Queue.critical.push 'job3'
23
+ Bluth::Queue.critical.size
24
+ #=> 3
25
+
26
+ ## Can shift from a queue
27
+ job = Bluth::Queue.critical.shift
28
+ #=> 'job1'
29
+
30
+ Bluth::Queue.critical.clear
@@ -0,0 +1,23 @@
1
+ require 'bluth'
2
+ require 'bluth/test_helpers'
3
+
4
+
5
+ ## Can create a Worker
6
+ @worker = Bluth::Worker.new 'host', 'user', 'wid'
7
+ @worker.index
8
+ #=> "host:user:wid"
9
+
10
+ ## Worker has a redis key
11
+ @worker.rediskey
12
+ #=> 'worker:host:user:wid:object'
13
+
14
+ ## Worker counts success
15
+ @worker.success!
16
+ @worker.success!
17
+ @worker.success.to_i
18
+ #=> 2
19
+
20
+
21
+ if @worker
22
+ @worker.destroy!
23
+ end
data/try/17_gob_try.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'bluth'
2
+ require 'bluth/test_helpers'
3
+
4
+ Familia.debug = true
5
+
6
+ ## Can enqueue a job
7
+ @job = ExampleHandler.enqueue :arg1 => :val1
8
+ @job.class
9
+ #=> Bluth::Gob
10
+
11
+ ## Job knows it's on critical queue
12
+ @job.current_queue
13
+ #=> :critical
14
+
15
+ ## Job knows it's handler
16
+ @job.handler
17
+ #=> ExampleHandler
18
+
19
+ ## Bluth::Critical has job id
20
+ Bluth::Queue.critical.range.member? @job.jobid
21
+ #=> true
22
+
23
+ ## Can fetch a job from queue
24
+ @gobid = Bluth::Queue.critical.pop
25
+ #=> @job.jobid
26
+
27
+ ## Create Gob
28
+ @popped_job = Bluth::Gob.from_redis @gobid
29
+ @popped_job.jobid
30
+ #=> @job.jobid
31
+
32
+ ## Popped job has args
33
+ @popped_job.data['arg1']
34
+ #=> 'val1'
35
+
36
+ ## Popped job is still critical
37
+ @popped_job.current_queue
38
+ #=> :critical
39
+
40
+ ## Move job to another queue
41
+ @popped_job.running!
42
+ #=> true
43
+
44
+ ## Popped job is still critical
45
+ @popped_job.current_queue
46
+ #=> :running
47
+
48
+
49
+ @job.destroy!
File without changes
@@ -0,0 +1,41 @@
1
+ require 'bluth'
2
+ require 'bluth/test_helpers'
3
+
4
+ #Familia.debug = true
5
+
6
+ ExampleHandler.enqueue :item => :val1
7
+ ExampleHandler.enqueue :item => :val2
8
+ ExampleHandler.enqueue :item => :val3
9
+
10
+
11
+ ## Critical queue should have 3 items
12
+ Bluth::Queue.critical.size
13
+ #=> 3
14
+
15
+ ## Can set poptimeout
16
+ Bluth.poptimeout = 2
17
+ #=> 2
18
+
19
+ ## Bluth.shift returns first value
20
+ @job1 = Bluth.shift
21
+ @job1.data['item']
22
+ #=> 'val1'
23
+
24
+ ## Bluth.pop returns last value
25
+ @job2 = Bluth.pop
26
+ @job2.data['item']
27
+ #=> 'val3'
28
+
29
+ ## Bluth.pop returns remaining value
30
+ @job3 = Bluth.pop
31
+ @job3.data['item']
32
+ #=> 'val2'
33
+
34
+ ## Bluth.pop returns nil after waiting for poptimeout
35
+ Bluth.pop
36
+ #=> nil
37
+
38
+ Bluth::Queue.critical.clear
39
+ @job1.destroy! if @job1
40
+ @job2.destroy! if @job2
41
+ @job3.destroy! if @job3
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bluth
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 5
9
- - 3
10
- version: 0.5.3
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Delano Mandelbaum
@@ -15,8 +15,8 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-17 00:00:00 -05:00
19
- default_executable:
18
+ date: 2010-12-30 00:00:00 -05:00
19
+ default_executable: bluth
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: familia
@@ -26,12 +26,12 @@ dependencies:
26
26
  requirements:
27
27
  - - "="
28
28
  - !ruby/object:Gem::Version
29
- hash: 13
29
+ hash: 1
30
30
  segments:
31
31
  - 0
32
- - 5
32
+ - 6
33
33
  - 3
34
- version: 0.5.3
34
+ version: 0.6.3
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
@@ -50,10 +50,24 @@ dependencies:
50
50
  version: 0.7.3
51
51
  type: :runtime
52
52
  version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: annoy
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ type: :runtime
66
+ version_requirements: *id003
53
67
  description: A Redis queuing system built on top of Familia
54
68
  email: delano@solutious.com
55
- executables: []
56
-
69
+ executables:
70
+ - bluth
57
71
  extensions: []
58
72
 
59
73
  extra_rdoc_files:
@@ -65,11 +79,19 @@ files:
65
79
  - README.rdoc
66
80
  - Rakefile
67
81
  - VERSION.yml
82
+ - bin/bluth
68
83
  - bluth.gemspec
69
84
  - lib/bluth.rb
85
+ - lib/bluth/cli.rb
70
86
  - lib/bluth/gob.rb
87
+ - lib/bluth/test_helpers.rb
71
88
  - lib/bluth/worker.rb
72
89
  - lib/daemonizing.rb
90
+ - try/15_queue_try.rb
91
+ - try/16_worker_try.rb
92
+ - try/17_gob_try.rb
93
+ - try/18_handler_try.rb
94
+ - try/19_bluth_try.rb
73
95
  has_rdoc: true
74
96
  homepage: http://github.com/delano/bluth
75
97
  licenses: []