true_queue 0.9.5
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/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: []
|