monque 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/Rakefile +3 -0
  2. data/lib/monque.rb +155 -0
  3. data/lib/monque/tasks.rb +12 -0
  4. metadata +69 -0
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'monque'
3
+ require 'monque/tasks'
@@ -0,0 +1,155 @@
1
+ require 'rubygems'
2
+ require 'mongo'
3
+ require 'json'
4
+
5
+ module Kernel
6
+ # from http://redcorundum.blogspot.com/2006/05/kernelqualifiedconstget.html
7
+ def fetch_class(str)
8
+ path = str.to_s.split('::')
9
+ from_root = path[0].empty?
10
+ if from_root
11
+ from_root = []
12
+ path = path[1..-1]
13
+ else
14
+ start_ns = ((Class === self)||(Module === self)) ? self : self.class
15
+ from_root = start_ns.to_s.split('::')
16
+ end
17
+ until from_root.empty?
18
+ begin
19
+ return (from_root+path).inject(Object) { |ns,name| ns.const_get(name) }
20
+ rescue NameError
21
+ from_root.delete_at(-1)
22
+ end
23
+ end
24
+ path.inject(Object) { |ns,name| ns.const_get(name) }
25
+ end
26
+ end
27
+
28
+ module Monque
29
+ RETRY_DELAY = 1800 # wait this number secs before trying a job again
30
+ REPLICATION_FACTOR = 1
31
+
32
+ @mongo_host = '127.0.0.1'
33
+ @mongo_port = 27017
34
+
35
+ def self.mongo_host=(h); @mongo_host = h; end
36
+ def self.mongo_port=(p); @mongo_port = p; end
37
+
38
+ def self.jobs_collection(host=nil, port=nil)
39
+ host ||= @mongo_host
40
+ port ||= @mongo_port
41
+
42
+ Mongo::Connection.new(host, port)['monque']['jobs']
43
+ end
44
+
45
+ def self.queue_for_cls(cls)
46
+ if cls.kind_of?(Class) || cls.kind_of?(Module)
47
+ if qname = cls.instance_variable_get(:@queue)
48
+ qname.is_a?(Symbol) ? qname.to_s : qname
49
+ else
50
+ raise "You should define the @queue name for #{cls.inspect}"
51
+ end
52
+ else
53
+ raise TypeError.new("The first argument to enqueue should be a class")
54
+ end
55
+ end
56
+
57
+ def self.enqueue(cls, *args)
58
+ queue = queue_for_cls(cls)
59
+ raise NameError.new("Invalid queue name: #{queue.inspect}") unless queue.kind_of?(String)
60
+
61
+ @jobs ||= jobs_collection
62
+ @jobs.save({
63
+ 'queue' => queue,
64
+ 'class' => cls.name,
65
+ 'data' => args.to_json,
66
+ 'started' => 0,
67
+ 'created' => Time.now.to_f,
68
+ 'proc_attempts' => []},
69
+ {:safe => {:w => REPLICATION_FACTOR}}
70
+ )
71
+ end
72
+
73
+ class Worker
74
+ def initialize(host, port, queues)
75
+ @queues = queues
76
+ @jobs = Monque.jobs_collection(host, port)
77
+ end
78
+
79
+ def worker_id
80
+ @worker_id ||= "#{`hostname`.strip}-#{$$}-#{Time.now.to_f}"
81
+ end
82
+
83
+ def reserve
84
+ @queues.each do |q|
85
+ speculative_job = @jobs.find(
86
+ 'queue' => q.to_s,
87
+ 'started' => {'$lte' => (Time.now.to_f - RETRY_DELAY)},
88
+ 'finished' => {'$exists' => false}
89
+ ).sort('added', :ascending).limit(1).first
90
+
91
+ next unless speculative_job
92
+
93
+ old_procid = speculative_job['procid']
94
+
95
+ gotted_job = @jobs.find_and_modify(
96
+ :query => {
97
+ '_id' => speculative_job['_id'],
98
+ 'proc_attempts' => speculative_job['proc_attempts']
99
+ },
100
+ :update => {
101
+ '$set' => {'started' => Time.now.to_f},
102
+ '$push' => {
103
+ 'proc_attempts' => {
104
+ 'id' => sprintf("%x", rand(1024**3)),
105
+ 'by' => worker_id,
106
+ 'time' => Time.now.to_f
107
+ }
108
+ }
109
+ },
110
+ :new => true
111
+ )
112
+
113
+ if gotted_job
114
+ return gotted_job
115
+ else
116
+ nil
117
+ end
118
+ end
119
+
120
+ nil
121
+ end
122
+
123
+ def mark_finished(job)
124
+ ok = @jobs.find_and_modify(
125
+ :query => {
126
+ '_id' => job['_id'],
127
+ 'proc_attempts' => job['proc_attempts']
128
+ },
129
+ :update => job.merge({'finished' => Time.now.to_f})
130
+ )
131
+ rescue Mongo::OperationFailure => e
132
+ if e.message =~ /No matching object found/
133
+ raise "This shouldn't happen - job was marked as finished though I had it reserved."
134
+ else
135
+ raise e
136
+ end
137
+ end
138
+
139
+ def work
140
+ loop do
141
+ job = reserve
142
+
143
+ if job
144
+ puts "job: #{job.inspect}"
145
+ cls = Kernel.fetch_class(job['class'])
146
+ $stderr.puts "#{worker_id}: processing #{job.inspect}"
147
+ cls.send(:perform, *JSON.load(job['data']))
148
+ mark_finished(job)
149
+ end
150
+
151
+ sleep 5
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,12 @@
1
+ namespace :monque do
2
+ task :work do
3
+ raise "Must specify QUEUES env var" if ENV['QUEUES'].nil?
4
+ raise "Must specify mongo host:port" if ENV['MONGO'].nil?
5
+
6
+ queues = ENV['QUEUES'].split(',')
7
+ mongo = ENV['MONGO'].split(':')
8
+
9
+ worker = Monque::Worker.new(mongo[0], mongo[1], queues)
10
+ worker.work
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monque
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Patrick Collison
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-03 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Simple queue on top of MongoDB, conforming roughly to Resque's API
23
+ email: patrick@collison.ie
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/monque/tasks.rb
32
+ - lib/monque.rb
33
+ - Rakefile
34
+ has_rdoc: true
35
+ homepage: http://collison.ie/monque
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ hash: 3
49
+ segments:
50
+ - 0
51
+ version: "0"
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ requirements: []
62
+
63
+ rubyforge_project: monque
64
+ rubygems_version: 1.3.7
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Pre-release beta version of Monque
68
+ test_files: []
69
+