mongo-dequeue 0.1.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/Gemfile +14 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +84 -0
- data/Rakefile +36 -0
- data/VERSION +1 -0
- data/lib/mongo-dequeue.rb +149 -0
- data/spec/mongo_dequeue_spec.rb +256 -0
- data/spec/spec_helper.rb +6 -0
- metadata +133 -0
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
gem 'json'
|
6
|
+
gem 'mongo'
|
7
|
+
# Add dependencies to develop your gem here.
|
8
|
+
# Include everything needed to run rake, tests, features, etc.
|
9
|
+
group :development do
|
10
|
+
gem "shoulda", ">= 0"
|
11
|
+
gem "bundler", "~> 1.0.0"
|
12
|
+
gem "jeweler", "~> 1.6.2"
|
13
|
+
gem "rcov", ">= 0"
|
14
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
bson (1.3.1)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.6.2)
|
7
|
+
bundler (~> 1.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
json (1.5.3)
|
11
|
+
mongo (1.3.1)
|
12
|
+
bson (>= 1.3.1)
|
13
|
+
rake (0.9.2)
|
14
|
+
rcov (0.9.9)
|
15
|
+
shoulda (2.11.3)
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
ruby
|
19
|
+
|
20
|
+
DEPENDENCIES
|
21
|
+
bundler (~> 1.0.0)
|
22
|
+
jeweler (~> 1.6.2)
|
23
|
+
json
|
24
|
+
mongo
|
25
|
+
rcov
|
26
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 TelegramSam
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
= Mongo::Dequeue
|
2
|
+
|
3
|
+
A de-duplicating priority queue that uses mongodb as the storage engine.
|
4
|
+
|
5
|
+
Heavily inspired by Mongo::Queue https://github.com/skiz/mongo_queue
|
6
|
+
|
7
|
+
== Features
|
8
|
+
* Atomic Locking (provided by Mongo)
|
9
|
+
* Thread Safe
|
10
|
+
* Priority Support
|
11
|
+
* Fully Tested
|
12
|
+
* Simple API
|
13
|
+
|
14
|
+
== Examples
|
15
|
+
|
16
|
+
=== Setting up a queue
|
17
|
+
Mongo::Dequeue requires a Mongo::Connection to your Mongo Server, and allows you to configure the database,
|
18
|
+
collection, and the default read timeout of items in your queue. Items popped from the queue must be confirmed
|
19
|
+
complete, or they will be reissued after the timeout specified.
|
20
|
+
|
21
|
+
db = Mongo::Connection.new
|
22
|
+
|
23
|
+
options = {
|
24
|
+
:database => 'whateverdb',
|
25
|
+
:collection => 'my_queue',
|
26
|
+
:timeout => 60
|
27
|
+
}
|
28
|
+
|
29
|
+
queue = Mongo::Dequeue.new(db, options)
|
30
|
+
|
31
|
+
=== Pushing Items
|
32
|
+
Items have a body, passed as the first argument to push(). A body can be a string, number, hash, or array. These
|
33
|
+
values are preserved in the Mongo collection used to store the queue, allowing you to inspect the queue and see these values.
|
34
|
+
The optional options argument is a hash of values. you can set a custom duplicate_key that will be used to de-duplicate
|
35
|
+
queue items. There are no keys or reserved values in the body of an item. Pass in anything you like.
|
36
|
+
|
37
|
+
|
38
|
+
options = {
|
39
|
+
:duplicate_key => 'match'
|
40
|
+
}
|
41
|
+
|
42
|
+
body = "foo"
|
43
|
+
body = 5
|
44
|
+
body = {
|
45
|
+
:stuff => "here"
|
46
|
+
}
|
47
|
+
body = ["a", "b", "c"]
|
48
|
+
|
49
|
+
queue.push(body,options)
|
50
|
+
|
51
|
+
=== Popping Items
|
52
|
+
Pop items with the pop() method. Specifying a timeout (in seconds) will override the default timeout set in
|
53
|
+
the queue options. After the timeout, the popped item will be available in another pop() call.
|
54
|
+
|
55
|
+
item = queue.pop(:timeout => 1)
|
56
|
+
|
57
|
+
The pop call will return a hash, with an :id and a :body. After the queue item is done, be sure to call the
|
58
|
+
complete() method, with the id of the message.
|
59
|
+
|
60
|
+
queue.complete(item[:id])
|
61
|
+
|
62
|
+
Note that completed items are not removed from the database immediately, allowing inspection of completion times,
|
63
|
+
duplication numbers, etc. Calling the cleanup() method removes completed items.
|
64
|
+
|
65
|
+
queue.cleanup()
|
66
|
+
|
67
|
+
=== Queue Status
|
68
|
+
You can call the +stats+ method to take a quick peek at your queue. Locked items are items that have been popped,
|
69
|
+
but not confirmed.
|
70
|
+
|
71
|
+
queue.stats
|
72
|
+
# => {:available => 3, :locked => 2, :complete => 5, :total => 10}
|
73
|
+
|
74
|
+
=== Mongo::Queue Comparison
|
75
|
+
|
76
|
+
* Items are not deleted immediately after completion.
|
77
|
+
* Item locks auto-expire
|
78
|
+
* Items are de-duplicated
|
79
|
+
* No reserved words in item body
|
80
|
+
|
81
|
+
=== TODO
|
82
|
+
|
83
|
+
* Adjust auto generated duplication_key to be more accurate.
|
84
|
+
* Full examples
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "mongo-dequeue"
|
18
|
+
gem.homepage = "http://github.com/TelegramSam/Dequeue"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Mongo based de-duplicating pritority queue}
|
21
|
+
gem.description = %Q{A de-duplicating priority queue that uses mongodb as the storage engine.}
|
22
|
+
gem.email = "telegramsam@gmail.com"
|
23
|
+
gem.authors = ["TelegramSam"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/rdoctask'
|
29
|
+
Rake::RDocTask.new do |rdoc|
|
30
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
31
|
+
|
32
|
+
rdoc.rdoc_dir = 'rdoc'
|
33
|
+
rdoc.title = "mongo-dequeue #{version}"
|
34
|
+
rdoc.rdoc_files.include('README*')
|
35
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
36
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'json'
|
3
|
+
require 'mongo'
|
4
|
+
|
5
|
+
# heavily inspired by https://github.com/skiz/mongo_queue
|
6
|
+
|
7
|
+
class Mongo::Dequeue
|
8
|
+
attr_reader :connection, :config
|
9
|
+
|
10
|
+
DEFAULT_CONFIG = {
|
11
|
+
:database => 'mongo_dequeue',
|
12
|
+
:collection => 'mongo_dequeue',
|
13
|
+
:timeout => 300,
|
14
|
+
:default_priority => 3
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# Create a new instance of MongoDequeue with the provided mongodb connection and optional configuration.
|
18
|
+
# See +DEFAULT_CONFIG+ for default configuration and possible configuration options.
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
# db = Mongo::Connection.new('localhost')
|
22
|
+
# config = {:timeout => 90, :attempts => 2}
|
23
|
+
# queue = Mongo::Queue.new(db, config)
|
24
|
+
#
|
25
|
+
def initialize(connection, opts={})
|
26
|
+
@connection = connection
|
27
|
+
@config = DEFAULT_CONFIG.merge(opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Remove all items from the queue. Use with caution!
|
31
|
+
def flush!
|
32
|
+
collection.drop
|
33
|
+
end
|
34
|
+
|
35
|
+
# Insert a new item into the queue.
|
36
|
+
#
|
37
|
+
# Example:
|
38
|
+
# queue.insert(:name => 'Billy', :email => 'billy@example.com', :message => 'Here is the thing you asked for')
|
39
|
+
def push(body, item_opts = {})
|
40
|
+
dup_key = item_opts[:duplicate_key] || Mongo::Dequeue.generate_duplicate_key(body)
|
41
|
+
|
42
|
+
selector = {
|
43
|
+
:duplicate_key => dup_key,
|
44
|
+
:complete => false,
|
45
|
+
:locked_at => nil
|
46
|
+
}
|
47
|
+
item = {
|
48
|
+
'$set' => {
|
49
|
+
:body => body,
|
50
|
+
:inserted_at => Time.now.utc,
|
51
|
+
:complete => false,
|
52
|
+
:locked_at => nil,
|
53
|
+
:completed_at => nil,
|
54
|
+
:priority => item_opts[:priority] || @config[:default_priority],
|
55
|
+
:duplicate_key => dup_key
|
56
|
+
},
|
57
|
+
'$inc' => {:count => 1 }
|
58
|
+
}
|
59
|
+
|
60
|
+
id = collection.update(selector, item, :upsert => true)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Lock and return the next queue message if one is available. Returns nil if none are available. Be sure to
|
64
|
+
# review the README.rdoc regarding proper usage of the locking process identifier (locked_by).
|
65
|
+
# Example:
|
66
|
+
# doc = queue.pop()
|
67
|
+
|
68
|
+
# {:body=>"foo", :id=>"4e039c372b70275e345206e4"}
|
69
|
+
|
70
|
+
def pop(opts = {})
|
71
|
+
begin
|
72
|
+
timeout = opts[:timeout] || @config[:timeout]
|
73
|
+
cmd = BSON::OrderedHash.new
|
74
|
+
cmd['findandmodify'] = @config[:collection]
|
75
|
+
cmd['update'] = {'$set' => {:locked_till => Time.now.utc+timeout}}
|
76
|
+
cmd['query'] = {:complete => false, '$or'=>[{:locked_till=> nil},{:locked_till=>{'$lt'=>Time.now.utc}}] }
|
77
|
+
cmd['sort'] = {:priority=>-1,:inserted_at=>1}
|
78
|
+
cmd['limit'] = 1
|
79
|
+
cmd['new'] = true
|
80
|
+
result = collection.db.command(cmd)
|
81
|
+
rescue Mongo::OperationFailure => of
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
return {
|
85
|
+
:body => result['value']['body'],
|
86
|
+
:id => result['value']['_id'].to_s
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# Remove the document from the queue. This should be called when the work is done and the document is no longer needed.
|
92
|
+
# You must provide the process identifier that the document was locked with to complete it.
|
93
|
+
def complete(id)
|
94
|
+
cmd = BSON::OrderedHash.new
|
95
|
+
cmd['findandmodify'] = @config[:collection]
|
96
|
+
cmd['query'] = {:_id => BSON::ObjectId.from_string(id), :complete => false}
|
97
|
+
cmd['update'] = {:completed_at => Time.now.utc, :complete => true}
|
98
|
+
cmd['limit'] = 1
|
99
|
+
collection.db.command(cmd)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Removes completed job history
|
103
|
+
def cleanup()
|
104
|
+
collection.remove({:complete=>true});
|
105
|
+
end
|
106
|
+
|
107
|
+
# Provides some information about what is in the queue. We are using an eval to ensure that a
|
108
|
+
# lock is obtained during the execution of this query so that the results are not skewed.
|
109
|
+
# please be aware that it will lock the database during the execution, so avoid using it too
|
110
|
+
# often, even though it it very tiny and should be relatively fast.
|
111
|
+
def stats
|
112
|
+
js = "function queue_stat(){
|
113
|
+
return db.eval(
|
114
|
+
function(){
|
115
|
+
var nowutc = new Date();
|
116
|
+
var a = db.#{config[:collection]}.count({'complete': false, '$or':[{'locked_till':null},{'locked_till':{'$lt':nowutc}}] });
|
117
|
+
var c = db.#{config[:collection]}.count({'complete': true});
|
118
|
+
var t = db.#{config[:collection]}.count();
|
119
|
+
var l = db.#{config[:collection]}.count({'complete': false, 'locked_till': {'$gte':nowutc} });
|
120
|
+
return [a, c, t, l];
|
121
|
+
}
|
122
|
+
);
|
123
|
+
}"
|
124
|
+
available, complete, total, locked = collection.db.eval(js)
|
125
|
+
{ :locked => locked.to_i,
|
126
|
+
:complete => complete.to_i,
|
127
|
+
:available => available.to_i,
|
128
|
+
:total => total.to_i }
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.generate_duplicate_key(body)
|
132
|
+
return Digest::MD5.hexdigest(body) if body.class == "String"
|
133
|
+
return Digest::MD5.hexdigest(body) if body.class == "Fixnum"
|
134
|
+
#else
|
135
|
+
return Digest::MD5.hexdigest(body.to_json) #won't ever match a duplicate. Need a better way to handle hashes and arrays.
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
protected
|
140
|
+
|
141
|
+
def value_of(result) #:nodoc:
|
142
|
+
result['okay'] == 0 ? nil : result['value']
|
143
|
+
end
|
144
|
+
|
145
|
+
def collection #:nodoc:
|
146
|
+
@connection.db(@config[:database]).collection(@config[:collection])
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
describe Mongo::Dequeue do
|
5
|
+
|
6
|
+
def insert_and_inspect(body, options={})
|
7
|
+
@queue.push(body,options)
|
8
|
+
@queue.send(:collection).find_one
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
opts = {
|
14
|
+
:database => 'mongo_queue_spec',
|
15
|
+
:collection => 'spec',
|
16
|
+
:timeout => 60}
|
17
|
+
@db = Mongo::Connection.new('localhost', nil, :pool_size => 4)
|
18
|
+
@queue = Mongo::Dequeue.new(@db, opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@queue.flush!
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "Configuration" do
|
26
|
+
|
27
|
+
it "should set the connection" do
|
28
|
+
@queue.connection.should be(@db)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should allow database option" do
|
32
|
+
@queue.config[:database].should eql('mongo_queue_spec')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should allow collection option" do
|
36
|
+
@queue.config[:collection].should eql('spec')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should allow timeout option" do
|
40
|
+
@queue.config[:timeout].should eql(60)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should have a sane set of defaults" do
|
44
|
+
q = Mongo::Dequeue.new(nil)
|
45
|
+
q.config[:collection].should eql 'mongo_dequeue'
|
46
|
+
q.config[:timeout].should eql 300
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "Inserting a standard Job" do
|
51
|
+
before(:each) do
|
52
|
+
@item = insert_and_inspect({:message => 'MongoQueueSpec', :foo => 5})
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should set priority to 3 by default" do
|
56
|
+
@item['priority'].should be(3)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not be complete" do
|
60
|
+
@item['complete'].should be false
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have a null completed_at" do
|
64
|
+
@item['completed_at'].should be nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should set a null locked_at" do
|
68
|
+
@item['locked_at'].should be nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should have no duplicates" do
|
72
|
+
@item['count'].should be 1
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should have a duplicate_key" do
|
76
|
+
@item['duplicate_key'].should_not be nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should return struct body properly" do
|
80
|
+
@item['body']['message'].should eql('MongoQueueSpec')
|
81
|
+
@item['body']['foo'].should be(5)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "Inserting different body types" do
|
86
|
+
before(:each) do
|
87
|
+
@queue.flush!
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should handle a struct" do
|
91
|
+
i = insert_and_inspect({:message => 'MongoQueueSpec', :foo => 5})
|
92
|
+
i['body']['message'].should eql('MongoQueueSpec')
|
93
|
+
i['body']['foo'].should be(5)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should handle a string" do
|
97
|
+
i = insert_and_inspect("foobarbaz")
|
98
|
+
i['body'].should eql "foobarbaz"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should handle a number" do
|
102
|
+
i = insert_and_inspect(42)
|
103
|
+
i['body'].should be 42
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "Deduplicating messages" do
|
108
|
+
before(:each) do
|
109
|
+
@queue.flush!
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should combine identical bodies of type string" do
|
113
|
+
a = insert_and_inspect("foo")
|
114
|
+
b = insert_and_inspect("foo")
|
115
|
+
@queue.send(:collection).count.should be 1
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should not combine different bodies of type string" do
|
119
|
+
a = insert_and_inspect("foo")
|
120
|
+
b = insert_and_inspect("bar")
|
121
|
+
@queue.send(:collection).count.should be 2
|
122
|
+
b['count'].should be 1
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should combine identical bodies of type struct" do
|
126
|
+
pending "Test after we have a better way of handling structs"
|
127
|
+
a = insert_and_inspect({:a=>'a',:b=>'b'})
|
128
|
+
b = insert_and_inspect({:a=>'a',:b=>'b'})
|
129
|
+
c = insert_and_inspect({:b=>'b',:a=>'a'})
|
130
|
+
@queue.send(:collection).count.should be 1
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should not combine different bodies of type struct" do
|
134
|
+
a = insert_and_inspect({:a=>'a',:b=>'b'})
|
135
|
+
b = insert_and_inspect({:a=>'a',:c=>'c'})
|
136
|
+
@queue.send(:collection).count.should be 2
|
137
|
+
b['count'].should be 1
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should combine based on duplication_key" do
|
141
|
+
a = insert_and_inspect({:a=>'a',:b=>'b'}, :duplicate_key => 'match')
|
142
|
+
b = insert_and_inspect({:a=>'a',:c=>'c'}, :duplicate_key => 'match')
|
143
|
+
@queue.send(:collection).count.should be 1
|
144
|
+
b['count'].should be 2
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should not combine based on duplication_key" do
|
148
|
+
a = insert_and_inspect("foo", :duplicate_key => 'match')
|
149
|
+
b = insert_and_inspect("foo", :duplicate_key => 'nomatch')
|
150
|
+
@queue.send(:collection).count.should be 2
|
151
|
+
b['count'].should be 1
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "Popping messages" do
|
157
|
+
before(:each) do
|
158
|
+
@queue.flush!
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should return message" do
|
162
|
+
a = insert_and_inspect("foo")
|
163
|
+
m = @queue.pop
|
164
|
+
m[:body].should eq "foo"
|
165
|
+
@queue.send(:collection).count.should be 1
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should return an id" do
|
169
|
+
a = insert_and_inspect("foo")
|
170
|
+
m = @queue.pop
|
171
|
+
m[:id].should_not be nil
|
172
|
+
@queue.send(:collection).count.should be 1
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should return nil when queue is empty" do
|
176
|
+
m = @queue.pop
|
177
|
+
m.should be nil
|
178
|
+
@queue.send(:collection).count.should be 0
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should complete ok" do
|
182
|
+
a = insert_and_inspect("foo")
|
183
|
+
m = @queue.pop
|
184
|
+
@queue.complete(m[:id])
|
185
|
+
m2 = @queue.pop
|
186
|
+
m2.should be nil
|
187
|
+
@queue.send(:collection).count.should be 1
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should pop again after timeout" do
|
191
|
+
a = insert_and_inspect("foo")
|
192
|
+
m = @queue.pop(:timeout => 1)
|
193
|
+
sleep(2)
|
194
|
+
m2 = @queue.pop
|
195
|
+
m2[:id].should eq m[:id]
|
196
|
+
@queue.send(:collection).count.should be 1
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should pop in order" do
|
200
|
+
@a = insert_and_inspect("a")
|
201
|
+
@b = insert_and_inspect("b")
|
202
|
+
@c = insert_and_inspect("c")
|
203
|
+
@d = insert_and_inspect("d")
|
204
|
+
|
205
|
+
@ap = @queue.pop
|
206
|
+
@bp = @queue.pop
|
207
|
+
@cp = @queue.pop
|
208
|
+
@dp = @queue.pop
|
209
|
+
|
210
|
+
@ap[:body].should eq "a"
|
211
|
+
@bp[:body].should eq "b"
|
212
|
+
@cp[:body].should eq "c"
|
213
|
+
@dp[:body].should eq "d"
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
describe "Stats" do
|
219
|
+
before(:all) do
|
220
|
+
@queue.flush!
|
221
|
+
@a = insert_and_inspect("a")
|
222
|
+
|
223
|
+
@b = insert_and_inspect("b")
|
224
|
+
@c = insert_and_inspect("c")
|
225
|
+
@d = insert_and_inspect("d")
|
226
|
+
@e = insert_and_inspect("e")
|
227
|
+
|
228
|
+
@ap = @queue.pop(:timeout => 1)
|
229
|
+
@bp = @queue.pop
|
230
|
+
@cp = @queue.pop
|
231
|
+
|
232
|
+
sleep(2)
|
233
|
+
|
234
|
+
@queue.complete(@bp[:id])
|
235
|
+
|
236
|
+
@stats = @queue.stats
|
237
|
+
end
|
238
|
+
#locked, complete, available, total
|
239
|
+
it "should count complete" do
|
240
|
+
@stats[:complete].should == 1
|
241
|
+
end
|
242
|
+
it "should count total" do
|
243
|
+
@stats[:total].should == 5
|
244
|
+
end
|
245
|
+
it "should count available" do
|
246
|
+
@stats[:available].should == 3
|
247
|
+
end
|
248
|
+
it "should count locked" do
|
249
|
+
@stats[:locked].should == 1
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongo-dequeue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- TelegramSam
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-29 00:00:00 -06:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: json
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mongo
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: shoulda
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: bundler
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.0.0
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: jeweler
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.6.2
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rcov
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: *id006
|
82
|
+
description: A de-duplicating priority queue that uses mongodb as the storage engine.
|
83
|
+
email: telegramsam@gmail.com
|
84
|
+
executables: []
|
85
|
+
|
86
|
+
extensions: []
|
87
|
+
|
88
|
+
extra_rdoc_files:
|
89
|
+
- LICENSE.txt
|
90
|
+
- README.rdoc
|
91
|
+
files:
|
92
|
+
- Gemfile
|
93
|
+
- Gemfile.lock
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.rdoc
|
96
|
+
- Rakefile
|
97
|
+
- VERSION
|
98
|
+
- lib/mongo-dequeue.rb
|
99
|
+
- spec/mongo_dequeue_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
has_rdoc: true
|
102
|
+
homepage: http://github.com/TelegramSam/Dequeue
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 4221506242229050956
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: "0"
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.5.3
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: Mongo based de-duplicating pritority queue
|
132
|
+
test_files: []
|
133
|
+
|