bluth 0.5.3 → 0.6.0

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.
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: []