monque 0.1.1

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