true_queue 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +185 -0
- data/TODO.md +65 -0
- data/bin/zeromq-memory-queue.rb +5 -0
- data/bin/zeromq-persistence-server.rb +5 -0
- data/lib/mobme/infrastructure/queue.rb +37 -0
- data/lib/mobme/infrastructure/queue/backend.rb +45 -0
- data/lib/mobme/infrastructure/queue/backends/amqp.rb +84 -0
- data/lib/mobme/infrastructure/queue/backends/memory.rb +95 -0
- data/lib/mobme/infrastructure/queue/backends/redis.rb +255 -0
- data/lib/mobme/infrastructure/queue/backends/zeromq.rb +70 -0
- data/lib/mobme/infrastructure/queue/exceptions.rb +6 -0
- data/lib/mobme/infrastructure/queue/zeromq/connection_handler.rb +50 -0
- data/lib/mobme/infrastructure/queue/zeromq/persistence_server.rb +80 -0
- data/lib/mobme/infrastructure/queue/zeromq/server.rb +107 -0
- data/lib/true_queue.rb +3 -0
- metadata +249 -0
data/README.md
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
|
2
|
+
## Overview
|
3
|
+
|
4
|
+
TrueQueue (as in "the one true queue") is a proxy to multiple queueing backends.
|
5
|
+
|
6
|
+
The most mature backend is one based on Redis which is a homegrown set of operations over Redis hashes and sorted sets that provides:
|
7
|
+
|
8
|
+
* A fast in-memory queue, with constant backups to disk.
|
9
|
+
* Atomic add and remove operations
|
10
|
+
* An inspectable queue: you can see what's in the queue or peek into the head of the queue without changing it.
|
11
|
+
* A reservable remove, where if the client quits halfway, the item is put back in.
|
12
|
+
* Priority queues
|
13
|
+
* And delayed retrieval for items, you can set a timestamp after which the entries are slated for removal.
|
14
|
+
|
15
|
+
Not to mention the biggest advantage of all: continue to use your existing Redis install!
|
16
|
+
|
17
|
+
There are other backends as well:
|
18
|
+
|
19
|
+
* memory: a simple in-process memory queue using a sorted set
|
20
|
+
* zermoq: an experimental backend built on zeromq (see bin/zeromq-memory-queue.rb)
|
21
|
+
* amqp: an AMQP backend to work with RabbitMQ
|
22
|
+
|
23
|
+
There are a set of uniform conventions regardless of the queue backend used:
|
24
|
+
|
25
|
+
* Queues are created when values are added to it. All input is encoded into JSON when stored and decoded when dequeued.
|
26
|
+
* When a queue is empty, nil is returned on remove
|
27
|
+
* There are always 9 standard methods: add, add\_bulk, remove, peek, list, empty, size, remove\_queues, list_queues.
|
28
|
+
|
29
|
+
Certain features (for e.g. a reservable remove) might not be available on all queue backends.
|
30
|
+
|
31
|
+
## Dependencies
|
32
|
+
|
33
|
+
Ruby version 1.9.2p290
|
34
|
+
|
35
|
+
All other dependencies are in the gemspec
|
36
|
+
|
37
|
+
## Install
|
38
|
+
|
39
|
+
$ gem install true_queue
|
40
|
+
|
41
|
+
## Spec
|
42
|
+
|
43
|
+
$ bundle exec guard
|
44
|
+
|
45
|
+
## Usage
|
46
|
+
|
47
|
+
### Connect
|
48
|
+
|
49
|
+
For the redis backend,
|
50
|
+
|
51
|
+
queue = TrueQueue.queue(:redis, options = { :redis_options => { } })
|
52
|
+
|
53
|
+
For the AMQP backend using bunny,
|
54
|
+
|
55
|
+
queue = TrueQueue.queue(:amqp, options = { :bunny_options => { } })
|
56
|
+
|
57
|
+
For the in-memory backend that only stores keys within a process space,
|
58
|
+
|
59
|
+
queue = TrueQueue.queue(:memory, options = {})
|
60
|
+
|
61
|
+
For the zeromq backend,
|
62
|
+
|
63
|
+
queue = TrueQueue.queue(:zeromq, options = {})
|
64
|
+
|
65
|
+
### Add an item
|
66
|
+
|
67
|
+
queue.add("queue_name", {:jobid => 23, :url => 'http://example.com/' })
|
68
|
+
|
69
|
+
Items can also have arbitrary metadata. They are stored alongside items and returned on a dequeue.
|
70
|
+
|
71
|
+
queue.add("publish", {:jobid => 23, :url => 'http://example.com/' }, {'importance' => low})
|
72
|
+
|
73
|
+
Certain metadata have special meaning. If you set a dequeue-timestamp to a Time object, the item will only be dequeued *after* that time. Note that it won't be dequeued exactly *at* the time, but at any time afterwards.
|
74
|
+
|
75
|
+
# only dequeue 5s after queueing
|
76
|
+
queue.add("publish", {:jobid => 23, :url => 'http://example.com/' }, {'dequeue-timestamp' => Time.now + 5 })
|
77
|
+
|
78
|
+
Another special metadata keyword is priority.
|
79
|
+
|
80
|
+
# priority is an integer from 1 to 100. Higher priority items are dequeued first.
|
81
|
+
queue.add("publish", {:jobid => 23, :url => 'http://example.com/' }, {'priority' => 5})
|
82
|
+
|
83
|
+
Items with priority set (or a higher priority) are always dequeued first.
|
84
|
+
|
85
|
+
Note that the AMQP backend doesn't support priorities or the dequeue timestamp.
|
86
|
+
|
87
|
+
### Remove an item
|
88
|
+
|
89
|
+
# dequeue
|
90
|
+
queue.remove("publish")
|
91
|
+
|
92
|
+
\#remove returns an array. The first element is the Ruby object in the queue, the second is the associated metadata (always a Hash).
|
93
|
+
|
94
|
+
=> {:jobid => 23, :url => 'http://example.com/' }, {'priority' => 5}
|
95
|
+
|
96
|
+
\#remove also can take a block. This is the recommended way to remove an item from a queue.
|
97
|
+
|
98
|
+
# dequeue into a block
|
99
|
+
queue.remove do |item|
|
100
|
+
#process item
|
101
|
+
...
|
102
|
+
end
|
103
|
+
|
104
|
+
When a block is passed, Queue ensures that the item is put back in case of an error within the block.
|
105
|
+
|
106
|
+
Inside a block, you can also manually raise {TrueQueue::RemoveAbort} to put back the item:
|
107
|
+
|
108
|
+
# dequeue into a block
|
109
|
+
queue.remove do |item|
|
110
|
+
#this item will be put back
|
111
|
+
raise TrueQueue::RemoveAbort
|
112
|
+
end
|
113
|
+
|
114
|
+
Note: you cannot pass in a block using the zeromq or amqp queue types.
|
115
|
+
|
116
|
+
Another thing to note is that unlike in other queues, **remove does not block and returns nil when the queue is empty**. So you'll have to manually call sleep(delay) and re-poll the queue. This implementation might change in the future:
|
117
|
+
|
118
|
+
loop do # dequeue into a block
|
119
|
+
queue.remove do |item|
|
120
|
+
next unless item
|
121
|
+
#this item will be put back
|
122
|
+
raise TrueQueue::RemoveAbort
|
123
|
+
end
|
124
|
+
|
125
|
+
sleep 1
|
126
|
+
end
|
127
|
+
|
128
|
+
### List all items in a queue
|
129
|
+
|
130
|
+
This is an expensive operation, but at times, very useful!
|
131
|
+
|
132
|
+
queue.list "queue"
|
133
|
+
|
134
|
+
This is not supported for the amqp queue type.
|
135
|
+
|
136
|
+
### List available queues
|
137
|
+
|
138
|
+
queue.list_queues
|
139
|
+
|
140
|
+
Returns an array of all queues stored in the Redis instance.
|
141
|
+
|
142
|
+
### Remove queues
|
143
|
+
|
144
|
+
This empties and removes all queues:
|
145
|
+
|
146
|
+
queue.remove_queues
|
147
|
+
|
148
|
+
To selectively remove queues:
|
149
|
+
|
150
|
+
queue.remove_queue "queue1"
|
151
|
+
queue.remove_queues "queue1", "queue2"
|
152
|
+
|
153
|
+
## Performance & Memory Usage
|
154
|
+
|
155
|
+
See detailed analysis in spec/performance.
|
156
|
+
|
157
|
+
### The Redis Backend
|
158
|
+
|
159
|
+
An indicative add performance is around 100,000 values stored in 20s: 5K/s write.
|
160
|
+
|
161
|
+
An indicative normal workflow performance is 200,000 values stored and retrieved in 1 minute: ~3K/s read-write
|
162
|
+
|
163
|
+
It's also reasonably memory efficient because it uses hashes instead of plain strings to store values. 200,000 values used 20MB (with each value 10 bytes).
|
164
|
+
|
165
|
+
### The AMQP Backend
|
166
|
+
|
167
|
+
The amqp backend uses the excellent bunny gem to connect to RabbitMQ.
|
168
|
+
|
169
|
+
This is slightly slower than the Redis backend: 200,000 values read-write in around 1m30s (~2K/s read-write)
|
170
|
+
|
171
|
+
### The Memory Backend
|
172
|
+
|
173
|
+
The memory backend only stores keys within the process space.
|
174
|
+
|
175
|
+
But performance is *very* good. It does 200,000 read/write in around 5s, which is ~40K/s read/write.
|
176
|
+
|
177
|
+
### The ZeroMQ Backend
|
178
|
+
|
179
|
+
The zeromq backend is currently experimental. It's meant to do these things:
|
180
|
+
|
181
|
+
* Very fast queue adds (5s for 100,000 keys)
|
182
|
+
* Consistent reads
|
183
|
+
* Eventual consistency via a persistence server
|
184
|
+
* A listener based queue interface where a client can request a message rather than messages being pushed down the wire (i.e. 'subscribe' to a queue) (not implemented yet)
|
185
|
+
|
data/TODO.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
## TODO
|
3
|
+
* zeromq-memory-queue:
|
4
|
+
* should store last ACK-ed at
|
5
|
+
* should expose a method to get at its stats
|
6
|
+
* read/s
|
7
|
+
* write/s
|
8
|
+
* transactions/s
|
9
|
+
* queue lengths
|
10
|
+
* memory used
|
11
|
+
* last ACK received time
|
12
|
+
* selectively tweak a queue (remove or edit items with a specified lkey from a queue)
|
13
|
+
* write tests that checks for contention cases, when we have a lot of queues, queue items and workers.
|
14
|
+
* flesh out the tests more for every edge case.
|
15
|
+
* write implementation tests and split tests into behavior and implementation.
|
16
|
+
|
17
|
+
## CHANGES
|
18
|
+
|
19
|
+
### 20120224 (vishnu@mobme.in)
|
20
|
+
* TrueQueue
|
21
|
+
|
22
|
+
### 20111203 (vishnu@mobme.in)
|
23
|
+
* Renaming redis_queue to queue
|
24
|
+
|
25
|
+
### 20111201 (vishnu@mobme.in)
|
26
|
+
* A persistence server for the zeromq queue based on redis
|
27
|
+
* The persistence server requests backlogs from the memory queue and
|
28
|
+
stores updates in Redis
|
29
|
+
* The persistence server sents an ACK for a backlog which says Redis
|
30
|
+
processing has been successful.
|
31
|
+
* The memory queue maintains a backlog of updates for which it hasn't received
|
32
|
+
an ACK.
|
33
|
+
|
34
|
+
### 20111130 (vishnu@mobme.in)
|
35
|
+
* An experimental zeromq backend and queue server.
|
36
|
+
|
37
|
+
### 20111128 (vishnu@mobme.in)
|
38
|
+
* Modularizing the code so that we can now use multiple backends.
|
39
|
+
* A memory based queue backend based on a C red-black tree implementation.
|
40
|
+
* BREAKING. Queues are now created using:
|
41
|
+
MobME::Infrastructure::RedisQueue.queue(backend) where backend is either one of :memory or :redis now.
|
42
|
+
|
43
|
+
### 20111118 (vishnu@mobme.in)
|
44
|
+
* Store values inside small key-ed hashes for maximum memory efficiency.
|
45
|
+
* Major reorganization, making everything far more modular & simple.
|
46
|
+
|
47
|
+
### 20111113 (vishnu@mobme.in)
|
48
|
+
* Initial documentation in Yardoc style.
|
49
|
+
* Adding #delete_queue(s) to clear & delete any and all queues permanently
|
50
|
+
* Adding #peek to peek at the first element in a queue without deleting it.
|
51
|
+
* \#remove is now multi thread and evented systems friendly.
|
52
|
+
* \#remove can now take a block that will auto add the item back into the queue when an error happens or a {MobME::Infrastructure::RedisQueueRemoveAbort} is raised.
|
53
|
+
* \#list to list every element in the queue. This is an expensive operation.
|
54
|
+
* Renaming #clear to #empty
|
55
|
+
* Renaming #delete\_queue(s) to #remove\_queue(s)
|
56
|
+
* More comprehensive documentation in README.md and TODO.md
|
57
|
+
* Using Yajl instead of native JSON
|
58
|
+
* Gemifying
|
59
|
+
|
60
|
+
### 20111111 (vishnu@mobme.in)
|
61
|
+
* Organized the project into the formal MobME ruby tdd template
|
62
|
+
* Wrote initial specs for everything implemented.
|
63
|
+
|
64
|
+
### Date Uncertain
|
65
|
+
* -- initial release --
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'yajl'
|
2
|
+
|
3
|
+
module MobME
|
4
|
+
module Infrastructure
|
5
|
+
module Queue
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'queue/backend'
|
11
|
+
require_relative 'queue/exceptions'
|
12
|
+
|
13
|
+
module MobME
|
14
|
+
module Infrastructure
|
15
|
+
module Queue
|
16
|
+
def self.queue(backend, options = {})
|
17
|
+
case backend
|
18
|
+
when :memory
|
19
|
+
require_relative 'queue/backends/memory'
|
20
|
+
MobME::Infrastructure::Queue::Backends::Memory.new(options)
|
21
|
+
when :redis
|
22
|
+
require_relative 'queue/backends/redis'
|
23
|
+
MobME::Infrastructure::Queue::Backends::Redis.new(options)
|
24
|
+
when :zeromq
|
25
|
+
require_relative 'queue/backends/zeromq'
|
26
|
+
MobME::Infrastructure::Queue::Backends::ZeroMQ.new(options)
|
27
|
+
when :amqp
|
28
|
+
require_relative 'queue/backends/amqp'
|
29
|
+
MobME::Infrastructure::Queue::Backends::AMQP.new(options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
module MobME::Infrastructure::Queue
|
3
|
+
module Backends
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class MobME::Infrastructure::Queue::Backend
|
8
|
+
protected
|
9
|
+
def score_from_metadata(dequeue_timestamp, priority)
|
10
|
+
if dequeue_timestamp
|
11
|
+
(dequeue_timestamp.to_f * 1000000).to_i
|
12
|
+
else
|
13
|
+
((Time.now.to_f * 1000000).to_i) / (priority || 1)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize_metadata(metadata)
|
18
|
+
dequeue_timestamp = metadata['dequeue-timestamp']
|
19
|
+
if dequeue_timestamp
|
20
|
+
unless dequeue_timestamp.is_a? Time
|
21
|
+
metadata['dequeue-timestamp'] = Time.now
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
priority = metadata['priority']
|
26
|
+
if priority
|
27
|
+
priority = priority.to_i
|
28
|
+
unless priority.between?(1, 100)
|
29
|
+
metadata['priority'] = 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
metadata
|
34
|
+
end
|
35
|
+
|
36
|
+
def serialize_item(item, metadata)
|
37
|
+
Yajl.dump([item, metadata])
|
38
|
+
end
|
39
|
+
|
40
|
+
def unserialize_item(value)
|
41
|
+
json_value = value || Yajl.dump(value) #handle nil to null
|
42
|
+
Yajl.load(json_value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
require "bunny"
|
3
|
+
|
4
|
+
class MobME::Infrastructure::Queue::Backends::AMQP < MobME::Infrastructure::Queue::Backend
|
5
|
+
def initialize(options = {})
|
6
|
+
@bunny_options = options[:bunny_options] || {}
|
7
|
+
@amqp_queues = {}
|
8
|
+
|
9
|
+
configure
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(queue, item, metadata = {})
|
13
|
+
metadata = normalize_metadata(metadata)
|
14
|
+
|
15
|
+
#register the queue if needed
|
16
|
+
queue_for(queue)
|
17
|
+
|
18
|
+
@amqp_exchange.publish(serialize_item(item, metadata), :key => queue)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adds many items together
|
22
|
+
def add_bulk(queue, items = [])
|
23
|
+
items.each do |item|
|
24
|
+
add(queue, item, {})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove(queue)
|
29
|
+
item = queue_for(queue).pop[:payload]
|
30
|
+
(:queue_empty == item) ? nil : unserialize_item(item)
|
31
|
+
end
|
32
|
+
|
33
|
+
def peek(queue)
|
34
|
+
raise NotImplementedError, "AMQP doesn't support peek!"
|
35
|
+
end
|
36
|
+
|
37
|
+
def size(queue)
|
38
|
+
queue_for(queue).message_count
|
39
|
+
end
|
40
|
+
|
41
|
+
def list(queue)
|
42
|
+
raise NotImplementedError, "AMQP doesn't support list!"
|
43
|
+
end
|
44
|
+
|
45
|
+
def empty(queue)
|
46
|
+
queue_for(queue).purge
|
47
|
+
end
|
48
|
+
|
49
|
+
def list_queues
|
50
|
+
@amqp_queues.keys
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove_queues(*queues)
|
54
|
+
queues = list_queues if queues.empty?
|
55
|
+
queues.each do |queue|
|
56
|
+
queue_for(queue).delete
|
57
|
+
@amqp_queues.delete(queue)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
alias :remove_queue :remove_queues
|
61
|
+
|
62
|
+
private
|
63
|
+
def queue_for(queue)
|
64
|
+
if @amqp_queues[queue]
|
65
|
+
@amqp_queues[queue]
|
66
|
+
else
|
67
|
+
@amqp_queues[queue] = @amqp_client.queue(queue)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def configure
|
72
|
+
exchange = @bunny_options.delete(:exchange) || ''
|
73
|
+
@amqp_client ||= Bunny.new(@bunny_options)
|
74
|
+
|
75
|
+
connect
|
76
|
+
@amqp_exchange ||= @amqp_client.exchange(exchange)
|
77
|
+
end
|
78
|
+
|
79
|
+
def connect
|
80
|
+
if :not_connected == @amqp_client.status
|
81
|
+
@amqp_client.start
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
require "algorithms"
|
3
|
+
|
4
|
+
class MobME::Infrastructure::Queue::Backends::Memory < MobME::Infrastructure::Queue::Backend
|
5
|
+
attr_accessor :scores
|
6
|
+
|
7
|
+
# Initialises the Queue
|
8
|
+
# @param [Hash] options all options to pass to the queue
|
9
|
+
def initialize(options = {})
|
10
|
+
@@queues ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def queues
|
14
|
+
@@queues
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(queue, item, metadata = {})
|
18
|
+
metadata = normalize_metadata(metadata)
|
19
|
+
score = score_from_metadata(metadata['dequeue-timestamp'], metadata['priority'])
|
20
|
+
|
21
|
+
queues[queue] ||= Containers::CRBTreeMap.new
|
22
|
+
queues[queue][score] = serialize_item(item, metadata)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds many items together
|
26
|
+
def add_bulk(queue, items = [])
|
27
|
+
items.each do |item|
|
28
|
+
add(queue, item)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove(queue, &block)
|
33
|
+
score = queues[queue].min_key
|
34
|
+
|
35
|
+
item = item_with_score(queue, score)
|
36
|
+
|
37
|
+
#If a block is given
|
38
|
+
if block_given?
|
39
|
+
begin
|
40
|
+
block.call(item)
|
41
|
+
rescue MobME::Infrastructure::Queue::RemoveAbort
|
42
|
+
return
|
43
|
+
end
|
44
|
+
queues[queue].delete(score) if item
|
45
|
+
else
|
46
|
+
queues[queue].delete(score) if item
|
47
|
+
return item
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def peek(queue)
|
52
|
+
score = queues[queue].min_key
|
53
|
+
|
54
|
+
item_with_score(queue, score)
|
55
|
+
end
|
56
|
+
|
57
|
+
def list(queue)
|
58
|
+
queues[queue].inject([]) { |collect, step| collect << item_with_score(queue, step[0]) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def empty(queue)
|
62
|
+
queues[queue] = nil
|
63
|
+
queues[queue] = Containers::CRBTreeMap.new
|
64
|
+
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
def size(queue)
|
69
|
+
queues[queue].size
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_queues(*queues_to_delete)
|
73
|
+
queues_to_delete = list_queues if queues_to_delete.empty?
|
74
|
+
queues_to_delete.each do |queue|
|
75
|
+
queues.delete(queue)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
alias :remove_queue :remove_queues
|
79
|
+
|
80
|
+
def list_queues
|
81
|
+
queues.keys
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def item_with_score(queue, score)
|
86
|
+
item = if not score
|
87
|
+
nil
|
88
|
+
elsif score > (Time.now.to_f * 1000000).to_i # We don't return future items!
|
89
|
+
nil
|
90
|
+
else
|
91
|
+
value = queues[queue][score]
|
92
|
+
unserialize_item(value)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
class MobME::Infrastructure::Queue::Backends::Redis < MobME::Infrastructure::Queue::Backend
|
4
|
+
|
5
|
+
# The namespace that all redis queue keys live inside Redis
|
6
|
+
NAMESPACE = 'redis:queue:'
|
7
|
+
|
8
|
+
# The set used to store all keys
|
9
|
+
QUEUESET = 'redis:queue:set'
|
10
|
+
|
11
|
+
# The UUID suffix for keys that store values
|
12
|
+
UUID_SUFFIX = ':uuid'
|
13
|
+
|
14
|
+
# The sorted set suffix for the list of all keys in a queue
|
15
|
+
QUEUE_SUFFIX = ':queue'
|
16
|
+
|
17
|
+
# The hash suffix for the hash that stores values of a queue
|
18
|
+
VALUE_SUFFIX = ':values'
|
19
|
+
|
20
|
+
# Initialises the Queue
|
21
|
+
# @param [Hash] options all options to pass to the queue
|
22
|
+
# @option options [Hash] :redis_options is passed on to the underlying Redis client
|
23
|
+
def initialize(options = {})
|
24
|
+
redis_options = options.delete(:redis_options) || {}
|
25
|
+
|
26
|
+
# Connect to Redis!
|
27
|
+
connect(redis_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Connect to Redis
|
31
|
+
# @param [Hash] options to pass to the Redis client as is
|
32
|
+
# @option options :connection Instead of making a new connection, the queue will reuse this existing Redis connection
|
33
|
+
def connect(options)
|
34
|
+
@redis = options.delete(:connection)
|
35
|
+
@redis ||= Redis.new(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add a value to a queue
|
39
|
+
# @param [String] queue_name The queue name to add to.
|
40
|
+
# @param [Object] item is the item to add
|
41
|
+
# @param [Hash] metadata is stored with the item and returned.
|
42
|
+
# @option metadata [Time] dequeue-timestamp An item with a dequeue-timestamp is only dequeued after this timestamp.
|
43
|
+
# @option metadata [Integer] priority An item with a higher priority is dequeued first. Always between 1 and 100.
|
44
|
+
# @return [String] A unique key in the queue name where the item is set.
|
45
|
+
def add(queue, item, metadata = {})
|
46
|
+
raise ArgumentError, "Metadata must be a hash, but #{metadata.class} given" unless metadata.is_a? Hash
|
47
|
+
|
48
|
+
metadata = normalize_metadata(metadata)
|
49
|
+
uuid = generate_uuid(queue)
|
50
|
+
|
51
|
+
add_to_queueset(queue)
|
52
|
+
write_value(queue, uuid, item, metadata)
|
53
|
+
add_to_queue(queue, uuid, metadata['dequeue-timestamp'], metadata['priority'])
|
54
|
+
|
55
|
+
uuid
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add values to the queue in bulk
|
59
|
+
# This works by pipelining writes to Redis, so results are generally much faster
|
60
|
+
# @param [String] queue The queue name to add to
|
61
|
+
# @param [Array] items The items to add
|
62
|
+
def add_bulk(queue, items = [])
|
63
|
+
metadata = {}
|
64
|
+
|
65
|
+
# UUIDs have to be in sync!
|
66
|
+
uuids = []
|
67
|
+
items.each do |item|
|
68
|
+
uuids << generate_uuid(queue)
|
69
|
+
end
|
70
|
+
|
71
|
+
add_to_queueset(queue)
|
72
|
+
|
73
|
+
@redis.pipelined do
|
74
|
+
items.each do |item|
|
75
|
+
uuid = uuids.shift
|
76
|
+
|
77
|
+
# write value
|
78
|
+
value_hash = "#{NAMESPACE}#{queue}#{VALUE_SUFFIX}"
|
79
|
+
@redis.hset value_hash, uuid, serialize_item(item, metadata)
|
80
|
+
|
81
|
+
# add to queue
|
82
|
+
queue_key = NAMESPACE + queue.to_s + QUEUE_SUFFIX
|
83
|
+
@redis.zadd queue_key, score_from_metadata(metadata['dequeue_timestamp'], metadata['priority']), uuid
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Remove an item from a queue.
|
89
|
+
# When a block is passed, the item is reserved instead and automatically put back in case of an error.
|
90
|
+
# Raise MobME::Infrastructure::QueueRemoveAbort within the block to manually abort the remove.
|
91
|
+
#
|
92
|
+
# @param [String] queue_name is the queue name
|
93
|
+
# @yield [[Object, Hash]] An optional block that is passed the item being remove alongside metadata.
|
94
|
+
# @return [[Object, Hash]] The item plus the metadata in the queue
|
95
|
+
def remove(queue, &block)
|
96
|
+
begin
|
97
|
+
# Remove the first item!
|
98
|
+
uuid = first_in_queue(queue)
|
99
|
+
if uuid
|
100
|
+
# If we're not able to remove the key from the set here, it means that
|
101
|
+
# some other thread (or evented operation) has done it before us, so
|
102
|
+
# the current remove is invalid and we should retry!
|
103
|
+
raise MobME::Infrastructure::Queue::RemoveConflictException unless remove_from_queue(queue, uuid)
|
104
|
+
|
105
|
+
queue_item = read_value(queue, uuid)
|
106
|
+
|
107
|
+
# When a block is given, safely reserve the queue item
|
108
|
+
if block_given?
|
109
|
+
begin
|
110
|
+
block.call(queue_item)
|
111
|
+
remove_value(queue, uuid)
|
112
|
+
rescue #generic error
|
113
|
+
put_back_in_queue(queue, uuid, queue_item)
|
114
|
+
|
115
|
+
# And now re-raise the error
|
116
|
+
raise
|
117
|
+
rescue MobME::Infrastructure::Queue::RemoveAbort
|
118
|
+
put_back_in_queue(queue, uuid, queue_item)
|
119
|
+
end
|
120
|
+
else
|
121
|
+
remove_value(queue, uuid)
|
122
|
+
queue_item
|
123
|
+
end
|
124
|
+
else
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
rescue MobME::Infrastructure::Queue::RemoveConflictException
|
128
|
+
retry
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Peek into the first item in a queue without removing it
|
133
|
+
# @param [String] queue_name is the queue name
|
134
|
+
# @return [[Object, Hash]] The item plus the metadata in the queue
|
135
|
+
def peek(queue)
|
136
|
+
uuid = first_in_queue(queue)
|
137
|
+
read_value(queue, uuid)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Find the size of a queue
|
141
|
+
# @param [String] queue_name is the queue name
|
142
|
+
# @return [Integer] The size of the queue
|
143
|
+
def size(queue)
|
144
|
+
queue = NAMESPACE + queue.to_s + QUEUE_SUFFIX
|
145
|
+
length = (@redis.zcard queue)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Lists all items in the queue. This is an expensive operation
|
149
|
+
# @param [String] queue_name is the queue name
|
150
|
+
# @return [Array<Object, Hash>] An array of list items, the first element the object stored and the second, metadata
|
151
|
+
def list(queue)
|
152
|
+
batch_size = 1_000 # keep this low as the time complexity of zrangebyscore is O(log(N)+M) : M -> the size
|
153
|
+
|
154
|
+
count = 0; values = []
|
155
|
+
(size(queue)/batch_size + 1).times do |i|
|
156
|
+
limit = [(batch_size * i), batch_size]
|
157
|
+
uuids = range_in_queue(queue, limit)
|
158
|
+
batch_values = uuids.map { |uuid| read_value(queue, uuid) }
|
159
|
+
values.push(*batch_values)
|
160
|
+
end
|
161
|
+
|
162
|
+
values
|
163
|
+
end
|
164
|
+
|
165
|
+
# Clear the queue
|
166
|
+
# @param [String] queue_name is the queue name to clear
|
167
|
+
def empty(queue)
|
168
|
+
# Delete key and value stores.
|
169
|
+
@redis.del "#{NAMESPACE}#{queue}#{VALUE_SUFFIX}"
|
170
|
+
@redis.del "#{NAMESPACE}#{queue}#{QUEUE_SUFFIX}"
|
171
|
+
end
|
172
|
+
|
173
|
+
# List all queues
|
174
|
+
# @return [Array] A list of queues (includes empty queues that were once available)
|
175
|
+
def list_queues
|
176
|
+
@redis.smembers QUEUESET
|
177
|
+
end
|
178
|
+
|
179
|
+
# Delete queues
|
180
|
+
# @param [optional String ...] queues A list of queues to delete. If empty, all queues are deleted.
|
181
|
+
def remove_queues(*queues)
|
182
|
+
queues = list_queues if queues.empty?
|
183
|
+
queues.each do |queue_name|
|
184
|
+
empty(queue_name)
|
185
|
+
remove_from_queueset(queue_name)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
alias :remove_queue :remove_queues
|
189
|
+
|
190
|
+
private
|
191
|
+
def add_to_queueset(queue)
|
192
|
+
@redis.sadd QUEUESET, queue
|
193
|
+
end
|
194
|
+
|
195
|
+
def remove_from_queueset(queue)
|
196
|
+
@redis.srem QUEUESET, queue
|
197
|
+
end
|
198
|
+
|
199
|
+
def first_in_queue(queue)
|
200
|
+
queue = NAMESPACE + queue.to_s + QUEUE_SUFFIX
|
201
|
+
(@redis.zrangebyscore queue, "-inf", (Time.now.to_f * 1000000).to_i, {:limit => [0, 1]}).first
|
202
|
+
end
|
203
|
+
|
204
|
+
def range_in_queue(queue, limit)
|
205
|
+
queue = NAMESPACE + queue.to_s + QUEUE_SUFFIX
|
206
|
+
(@redis.zrangebyscore queue, "-inf", (Time.now.to_f * 1000000).to_i, {:limit => limit})
|
207
|
+
end
|
208
|
+
|
209
|
+
def add_to_queue(queue, uuid, dequeue_timestamp, priority)
|
210
|
+
# zadd adds to a sorted set, which is sorted by score.
|
211
|
+
# When set, the dequeue_timestamp is used as the score. If not, it's just the current timestamp.
|
212
|
+
# When set, current timestamp is divided by the integer priority.
|
213
|
+
queue = NAMESPACE + queue.to_s + QUEUE_SUFFIX
|
214
|
+
@redis.zadd queue, score_from_metadata(dequeue_timestamp, priority), uuid
|
215
|
+
end
|
216
|
+
|
217
|
+
def remove_from_queue(queue, uuid)
|
218
|
+
queue = NAMESPACE + queue.to_s + QUEUE_SUFFIX
|
219
|
+
(@redis.zrem queue, uuid)
|
220
|
+
end
|
221
|
+
|
222
|
+
def put_back_in_queue(queue, uuid, queue_item)
|
223
|
+
# Put the item back in the queue
|
224
|
+
metadata = queue_item[1]
|
225
|
+
metadata = normalize_metadata(metadata)
|
226
|
+
add_to_queue(queue, uuid, metadata['dequeue_timestamp'], metadata['priority'])
|
227
|
+
end
|
228
|
+
|
229
|
+
def write_value(queue, uuid, item, metadata)
|
230
|
+
value_hash = "#{NAMESPACE}#{queue}#{VALUE_SUFFIX}"
|
231
|
+
|
232
|
+
@redis.hset value_hash, uuid, serialize_item(item, metadata)
|
233
|
+
end
|
234
|
+
|
235
|
+
def read_value(queue, uuid)
|
236
|
+
if uuid
|
237
|
+
value_hash = "#{NAMESPACE}#{queue}#{VALUE_SUFFIX}"
|
238
|
+
|
239
|
+
value = @redis.hget value_hash, uuid
|
240
|
+
unserialize_item(value)
|
241
|
+
else
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def remove_value(queue, uuid)
|
247
|
+
value_hash = "#{NAMESPACE}#{queue}#{VALUE_SUFFIX}"
|
248
|
+
|
249
|
+
@redis.hdel value_hash, uuid
|
250
|
+
end
|
251
|
+
|
252
|
+
def generate_uuid(queue)
|
253
|
+
@redis.incr NAMESPACE + queue.to_s + UUID_SUFFIX
|
254
|
+
end
|
255
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
require 'em-zeromq'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'mobme/infrastructure/queue/zeromq/connection_handler'
|
5
|
+
|
6
|
+
class MobME::Infrastructure::Queue::Backends::ZeroMQ < MobME::Infrastructure::Queue::Backend
|
7
|
+
def initialize(options = {})
|
8
|
+
@socket = options[:socket] || "ipc:///tmp/mobme-infrastructure-queue-messages.sock"
|
9
|
+
connect
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect
|
13
|
+
@context = EM::ZeroMQ::Context.new(1)
|
14
|
+
@pool = EM::Synchrony::ConnectionPool.new(:size => 20) do
|
15
|
+
@context.connect(ZMQ::REQ, @socket)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(queue, item, metadata = {})
|
20
|
+
dispatch(:add, queue, item, metadata)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Adds many items together
|
24
|
+
def add_bulk(queue, items = [])
|
25
|
+
dispatch(:add_bulk, queue, items)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Simple remove without reserving items
|
29
|
+
def remove(queue)
|
30
|
+
dispatch(:remove, queue)
|
31
|
+
end
|
32
|
+
|
33
|
+
def peek(queue)
|
34
|
+
dispatch(:peek, queue)
|
35
|
+
end
|
36
|
+
|
37
|
+
def size(queue)
|
38
|
+
dispatch(:size, queue)
|
39
|
+
end
|
40
|
+
|
41
|
+
def list(queue)
|
42
|
+
dispatch(:list, queue)
|
43
|
+
end
|
44
|
+
|
45
|
+
def empty(queue)
|
46
|
+
dispatch(:empty, queue)
|
47
|
+
end
|
48
|
+
|
49
|
+
def list_queues
|
50
|
+
dispatch(:list_queues)
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove_queues(*queues)
|
54
|
+
dispatch(:remove_queues, *queues)
|
55
|
+
end
|
56
|
+
alias :remove_queue :remove_queues
|
57
|
+
|
58
|
+
private
|
59
|
+
def dispatch(method, *args)
|
60
|
+
@pool.execute(false) do |connection|
|
61
|
+
handler = MobME::Infrastructure::Queue::ZeroMQ::ConnectionHandler.new(connection)
|
62
|
+
|
63
|
+
message = Marshal.dump([method, args])
|
64
|
+
|
65
|
+
handler.send_message(message)
|
66
|
+
|
67
|
+
Marshal.load(handler.receive_message)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
module MobME::Infrastructure::Queue::ZeroMQ
|
3
|
+
class ConnectionHandler
|
4
|
+
attr_accessor :request
|
5
|
+
|
6
|
+
def initialize(connection, identity = "")
|
7
|
+
@connection = connection
|
8
|
+
@connection.handler = self
|
9
|
+
@connection.identity = "#{identity ? "#{identity}:" : ""}#{@client_fiber.object_id}"
|
10
|
+
@connection.notify_readable = false
|
11
|
+
@connection.notify_writable = false
|
12
|
+
|
13
|
+
@send_messages_buffer = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive_message
|
17
|
+
EM.next_tick { @connection.register_readable }
|
18
|
+
|
19
|
+
@client_fiber = Fiber.current
|
20
|
+
Fiber.yield
|
21
|
+
end
|
22
|
+
|
23
|
+
def send_message(*messages)
|
24
|
+
@send_messages_buffer = messages
|
25
|
+
EM.next_tick { @connection.register_writable }
|
26
|
+
|
27
|
+
@client_fiber = Fiber.current
|
28
|
+
Fiber.yield
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_readable(connection, message)
|
32
|
+
request = message.map(&:copy_out_string).join
|
33
|
+
message.each { |part| part.close }
|
34
|
+
|
35
|
+
@connection.notify_readable = false
|
36
|
+
@connection.notify_writable = false
|
37
|
+
|
38
|
+
@client_fiber.resume(request)
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_writable(connection)
|
42
|
+
return_value = connection.send_msg *@send_messages_buffer
|
43
|
+
|
44
|
+
@connection.notify_readable = false
|
45
|
+
@connection.notify_writable = false
|
46
|
+
|
47
|
+
@client_fiber.resume(return_value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
require 'mobme/infrastructure/queue'
|
4
|
+
require 'mobme/infrastructure/queue/zeromq/connection_handler'
|
5
|
+
require 'redis/connection/synchrony'
|
6
|
+
require 'digest/sha1'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
module MobME::Infrastructure::Queue::ZeroMQ
|
10
|
+
class PersistenceServer
|
11
|
+
def initialize(options = {})
|
12
|
+
@queue = MobME::Infrastructure::Queue.queue(:redis)
|
13
|
+
@persistence_socket = options[:persistence_socket] || "ipc:///tmp/mobme-infrastructure-queue-persistence.sock"
|
14
|
+
@persistence_store_path = options[:persistence_store_path] || "/tmp"
|
15
|
+
@backlog_interval = options[:backlog_interval] || 10
|
16
|
+
|
17
|
+
EM.synchrony do
|
18
|
+
create_snapshot_directory
|
19
|
+
bind
|
20
|
+
|
21
|
+
send_backlog_requests
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def bind
|
26
|
+
@context = EM::ZeroMQ::Context.new(1)
|
27
|
+
|
28
|
+
@persistence_request_server = @context.connect(ZMQ::REQ, @persistence_socket)
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_backlog_requests
|
32
|
+
loop do
|
33
|
+
handler = MobME::Infrastructure::Queue::ZeroMQ::ConnectionHandler.new(@persistence_request_server)
|
34
|
+
handler.send_message Marshal.dump("BACKLOG")
|
35
|
+
puts "Sent BACKLOG"
|
36
|
+
|
37
|
+
snapshot = handler.receive_message
|
38
|
+
snapshot = Marshal.load(snapshot) rescue nil
|
39
|
+
|
40
|
+
case snapshot
|
41
|
+
when nil
|
42
|
+
when false
|
43
|
+
else
|
44
|
+
dump_snapshot_to_disk(snapshot)
|
45
|
+
|
46
|
+
if snapshot and !snapshot.empty?
|
47
|
+
handler.send_message Marshal.dump("ACK #{ack_signature(snapshot)}")
|
48
|
+
|
49
|
+
# We get an OK back from the server
|
50
|
+
handler.receive_message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
EM::Synchrony.sleep(@backlog_interval)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def create_snapshot_directory
|
60
|
+
@persistence_store_path = Pathname.new(@persistence_store_path).join("db")
|
61
|
+
FileUtils.mkdir_p(@persistence_store_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def dump_snapshot_to_disk(snapshot)
|
65
|
+
snapshot.each do |queue, items|
|
66
|
+
puts "Snapshotting: #{queue}"
|
67
|
+
marshalled_items = StringIO.new(Marshal.dump(items))
|
68
|
+
File.open(@persistence_store_path.join("#{queue}.marshal"), "w+") do |file|
|
69
|
+
file.write marshalled_items.read
|
70
|
+
end
|
71
|
+
puts "Done."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def ack_signature(snapshot)
|
76
|
+
marshaled_snapshot = Marshal.dump(snapshot)
|
77
|
+
Digest::SHA1.hexdigest(marshaled_snapshot)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
require 'mobme/infrastructure/queue'
|
6
|
+
require 'mobme/infrastructure/queue/zeromq/connection_handler'
|
7
|
+
|
8
|
+
require 'em-synchrony'
|
9
|
+
require 'em-zeromq'
|
10
|
+
|
11
|
+
module MobME::Infrastructure::Queue::ZeroMQ
|
12
|
+
class Server
|
13
|
+
def initialize(options = {})
|
14
|
+
@queue = MobME::Infrastructure::Queue.queue(:memory)
|
15
|
+
@messages_socket = options[:messages_socket] || "ipc:///tmp/mobme-infrastructure-queue-messages.sock"
|
16
|
+
@persistence_socket = options[:persistence_socket] || "ipc:///tmp/mobme-infrastructure-queue-persistence.sock"
|
17
|
+
|
18
|
+
EM.synchrony do
|
19
|
+
bind
|
20
|
+
|
21
|
+
Fiber.new { listen_to_messages }.resume
|
22
|
+
Fiber.new { listen_to_backlog_requests }.resume
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def bind
|
27
|
+
@context = EM::ZeroMQ::Context.new(1)
|
28
|
+
|
29
|
+
@messages_reply_server = @context.bind(ZMQ::REP, @messages_socket)
|
30
|
+
@persistence_reply_server = @context.bind(ZMQ::REP, @persistence_socket)
|
31
|
+
end
|
32
|
+
|
33
|
+
def listen_to_messages
|
34
|
+
loop do
|
35
|
+
handler = MobME::Infrastructure::Queue::ZeroMQ::ConnectionHandler.new(@messages_reply_server)
|
36
|
+
message = @messages_reply_server.handler.receive_message
|
37
|
+
|
38
|
+
message = Marshal.load(message) rescue nil
|
39
|
+
|
40
|
+
queue_return = if message
|
41
|
+
route_to_queue(message)
|
42
|
+
end
|
43
|
+
|
44
|
+
@messages_reply_server.handler.send_message(Marshal.dump(queue_return))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def listen_to_backlog_requests
|
49
|
+
loop do
|
50
|
+
handler = MobME::Infrastructure::Queue::ZeroMQ::ConnectionHandler.new(@persistence_reply_server)
|
51
|
+
message = @persistence_reply_server.handler.receive_message
|
52
|
+
|
53
|
+
|
54
|
+
message = Marshal.load(message) rescue nil
|
55
|
+
|
56
|
+
queue_return = if message == "BACKLOG"
|
57
|
+
queues_snapshot
|
58
|
+
elsif ack_message?(message)
|
59
|
+
puts "Got ACK: #{signature_from_ack_message(message)}"
|
60
|
+
true
|
61
|
+
else
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
@persistence_reply_server.handler.send_message(Marshal.dump(queue_return))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def route_to_queue(message)
|
71
|
+
method = method_from_message(message)
|
72
|
+
args = arguments_from_message(message)
|
73
|
+
|
74
|
+
begin
|
75
|
+
@queue.send(method, *args)
|
76
|
+
rescue NoMethodError
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def queues_snapshot
|
82
|
+
snapshot = {}
|
83
|
+
queues = @queue.list_queues
|
84
|
+
queues.each do |queue|
|
85
|
+
snapshot[queue] = @queue.list queue
|
86
|
+
end
|
87
|
+
|
88
|
+
snapshot
|
89
|
+
end
|
90
|
+
|
91
|
+
def method_from_message(message)
|
92
|
+
message[0]
|
93
|
+
end
|
94
|
+
|
95
|
+
def arguments_from_message(message)
|
96
|
+
message[1]
|
97
|
+
end
|
98
|
+
|
99
|
+
def ack_message?(message)
|
100
|
+
message.match(/^ACK (.*)/)
|
101
|
+
end
|
102
|
+
|
103
|
+
def signature_from_ack_message(message)
|
104
|
+
message.match(/^ACK (.*)/).to_a[1]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/true_queue.rb
ADDED
metadata
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: true_queue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Vishnu Gopal
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70255280344720 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70255280344720
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70255280344240 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70255280344240
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: guard
|
38
|
+
requirement: &70255280343780 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70255280343780
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: guard-rspec
|
49
|
+
requirement: &70255280343280 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70255280343280
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: simplecov
|
60
|
+
requirement: &70255280342820 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70255280342820
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: flog
|
71
|
+
requirement: &70255280342340 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70255280342340
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: yard
|
82
|
+
requirement: &70255280341900 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70255280341900
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: simplecov-rcov
|
93
|
+
requirement: &70255280341440 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70255280341440
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: diff-lcs
|
104
|
+
requirement: &70255280340980 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *70255280340980
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: rdiscount
|
115
|
+
requirement: &70255280340520 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *70255280340520
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: hiredis
|
126
|
+
requirement: &70255280340000 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.3.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: *70255280340000
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: redis
|
137
|
+
requirement: &70255280339440 !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ~>
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 2.2.0
|
143
|
+
type: :development
|
144
|
+
prerelease: false
|
145
|
+
version_requirements: *70255280339440
|
146
|
+
- !ruby/object:Gem::Dependency
|
147
|
+
name: algorithms
|
148
|
+
requirement: &70255280339040 !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
type: :development
|
155
|
+
prerelease: false
|
156
|
+
version_requirements: *70255280339040
|
157
|
+
- !ruby/object:Gem::Dependency
|
158
|
+
name: em-synchrony
|
159
|
+
requirement: &70255280338540 !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
161
|
+
requirements:
|
162
|
+
- - ! '>='
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
type: :development
|
166
|
+
prerelease: false
|
167
|
+
version_requirements: *70255280338540
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: em-zeromq
|
170
|
+
requirement: &70255280337980 !ruby/object:Gem::Requirement
|
171
|
+
none: false
|
172
|
+
requirements:
|
173
|
+
- - ! '>='
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
type: :development
|
177
|
+
prerelease: false
|
178
|
+
version_requirements: *70255280337980
|
179
|
+
- !ruby/object:Gem::Dependency
|
180
|
+
name: bunny
|
181
|
+
requirement: &70255280337200 !ruby/object:Gem::Requirement
|
182
|
+
none: false
|
183
|
+
requirements:
|
184
|
+
- - ~>
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: 0.7.4
|
187
|
+
type: :development
|
188
|
+
prerelease: false
|
189
|
+
version_requirements: *70255280337200
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: yajl-ruby
|
192
|
+
requirement: &70255280336680 !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :runtime
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: *70255280336680
|
201
|
+
description: ! 'Queue is a proxy to several queueing libraries: a homegrown queue
|
202
|
+
on top of Redis, an in-process memory queue for use in testing, a robust AMQP backend
|
203
|
+
based on Bunny, and an experimental zeromq backend'
|
204
|
+
email:
|
205
|
+
- vishnu@mobme.in
|
206
|
+
executables: []
|
207
|
+
extensions: []
|
208
|
+
extra_rdoc_files: []
|
209
|
+
files:
|
210
|
+
- lib/mobme/infrastructure/queue/backend.rb
|
211
|
+
- lib/mobme/infrastructure/queue/backends/amqp.rb
|
212
|
+
- lib/mobme/infrastructure/queue/backends/memory.rb
|
213
|
+
- lib/mobme/infrastructure/queue/backends/redis.rb
|
214
|
+
- lib/mobme/infrastructure/queue/backends/zeromq.rb
|
215
|
+
- lib/mobme/infrastructure/queue/exceptions.rb
|
216
|
+
- lib/mobme/infrastructure/queue/zeromq/connection_handler.rb
|
217
|
+
- lib/mobme/infrastructure/queue/zeromq/persistence_server.rb
|
218
|
+
- lib/mobme/infrastructure/queue/zeromq/server.rb
|
219
|
+
- lib/mobme/infrastructure/queue.rb
|
220
|
+
- lib/true_queue.rb
|
221
|
+
- bin/zeromq-memory-queue.rb
|
222
|
+
- bin/zeromq-persistence-server.rb
|
223
|
+
- README.md
|
224
|
+
- TODO.md
|
225
|
+
homepage: http://42.mobme.in/
|
226
|
+
licenses: []
|
227
|
+
post_install_message:
|
228
|
+
rdoc_options: []
|
229
|
+
require_paths:
|
230
|
+
- lib
|
231
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
232
|
+
none: false
|
233
|
+
requirements:
|
234
|
+
- - ! '>='
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
238
|
+
none: false
|
239
|
+
requirements:
|
240
|
+
- - ! '>='
|
241
|
+
- !ruby/object:Gem::Version
|
242
|
+
version: 1.3.6
|
243
|
+
requirements: []
|
244
|
+
rubyforge_project:
|
245
|
+
rubygems_version: 1.8.10
|
246
|
+
signing_key:
|
247
|
+
specification_version: 3
|
248
|
+
summary: A simple proxy to various queue backends.
|
249
|
+
test_files: []
|