qup 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +41 -0
- data/.gemtest +1 -0
- data/ADAPTER_API.rdoc +97 -0
- data/HISTORY.rdoc +9 -0
- data/Manifest.txt +52 -0
- data/README.rdoc +156 -0
- data/Rakefile +246 -0
- data/lib/qup.rb +48 -0
- data/lib/qup/adapter.rb +57 -0
- data/lib/qup/adapter/kestrel.rb +56 -0
- data/lib/qup/adapter/kestrel/destination.rb +54 -0
- data/lib/qup/adapter/kestrel/queue.rb +101 -0
- data/lib/qup/adapter/kestrel/topic.rb +68 -0
- data/lib/qup/adapter/maildir.rb +57 -0
- data/lib/qup/adapter/maildir/queue.rb +123 -0
- data/lib/qup/adapter/maildir/topic.rb +85 -0
- data/lib/qup/adapter/redis.rb +55 -0
- data/lib/qup/adapter/redis/connection.rb +32 -0
- data/lib/qup/adapter/redis/queue.rb +97 -0
- data/lib/qup/adapter/redis/topic.rb +76 -0
- data/lib/qup/consumer.rb +42 -0
- data/lib/qup/message.rb +18 -0
- data/lib/qup/producer.rb +28 -0
- data/lib/qup/publisher.rb +30 -0
- data/lib/qup/queue_api.rb +124 -0
- data/lib/qup/session.rb +111 -0
- data/lib/qup/subscriber.rb +28 -0
- data/lib/qup/topic_api.rb +92 -0
- data/spec/qup/adapter/kestrel/queue_spec.rb +9 -0
- data/spec/qup/adapter/kestrel/topic_spec.rb +9 -0
- data/spec/qup/adapter/kestrel_context.rb +8 -0
- data/spec/qup/adapter/kestrel_spec.rb +8 -0
- data/spec/qup/adapter/maildir/queue_spec.rb +9 -0
- data/spec/qup/adapter/maildir/topic_spec.rb +9 -0
- data/spec/qup/adapter/maildir_context.rb +10 -0
- data/spec/qup/adapter/maildir_spec.rb +8 -0
- data/spec/qup/adapter/redis/queue_spec.rb +9 -0
- data/spec/qup/adapter/redis/topic_spec.rb +9 -0
- data/spec/qup/adapter/redis_context.rb +6 -0
- data/spec/qup/adapter/redis_spec.rb +8 -0
- data/spec/qup/adapter_spec.rb +28 -0
- data/spec/qup/consumer_spec.rb +40 -0
- data/spec/qup/message_spec.rb +13 -0
- data/spec/qup/producer_spec.rb +18 -0
- data/spec/qup/queue_api_spec.rb +21 -0
- data/spec/qup/session_spec.rb +81 -0
- data/spec/qup/shared_adapter_examples.rb +29 -0
- data/spec/qup/shared_queue_examples.rb +71 -0
- data/spec/qup/shared_topic_examples.rb +57 -0
- data/spec/qup/topic_api_spec.rb +21 -0
- data/spec/qup_spec.rb +37 -0
- data/spec/spec_helper.rb +26 -0
- metadata +281 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'qup/producer'
|
2
|
+
require 'qup/consumer'
|
3
|
+
|
4
|
+
class Qup::Adapter::Maildir
|
5
|
+
#
|
6
|
+
# Internal: The Qup Implementation in the Maildir Adapter
|
7
|
+
#
|
8
|
+
class Queue
|
9
|
+
include Qup::QueueAPI
|
10
|
+
# Internal: the name of the Queue
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# Internal: Create a new Queue
|
14
|
+
#
|
15
|
+
# root_path - the root_path for this Queue to create under
|
16
|
+
# name - the String name of the Queue
|
17
|
+
#
|
18
|
+
# Returns a new Queue.
|
19
|
+
def initialize( root_path, name )
|
20
|
+
@root_path = ::Pathname.new( root_path )
|
21
|
+
@name = name
|
22
|
+
@queue_path = @root_path + @name
|
23
|
+
@maildir = ::Maildir.new( @queue_path, true )
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Internal: Remove the Queue from the system
|
28
|
+
#
|
29
|
+
# Returns nothing.
|
30
|
+
def destroy
|
31
|
+
@queue_path.rmtree
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Internal: Remove all messages from the Queue
|
36
|
+
#
|
37
|
+
# Returns nothing.
|
38
|
+
def flush
|
39
|
+
::Maildir::SUBDIRS.each do |sub|
|
40
|
+
dir = Pathname.new( File.join( @maildir.path, sub.to_s ))
|
41
|
+
dir.children.each do |p|
|
42
|
+
p.delete if p.file?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Internal: return the number of Messages on the Queue
|
49
|
+
#
|
50
|
+
# Returns an integer of the Queue depth
|
51
|
+
def depth
|
52
|
+
total = 0
|
53
|
+
%w[ new cur ].each do |subdir|
|
54
|
+
search_path = File.join( @maildir.path, subdir, '*' )
|
55
|
+
keys = Dir.glob( search_path )
|
56
|
+
total += keys.size
|
57
|
+
end
|
58
|
+
return total
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Internal: Put an item onto the Queue
|
63
|
+
#
|
64
|
+
# message - the data to put onto the queue.
|
65
|
+
#
|
66
|
+
# The 'message' that is passed in is wrapped in a Qup::Message before being
|
67
|
+
# stored.
|
68
|
+
#
|
69
|
+
# A user of the Qup API should use a Producer instance to put items onto the
|
70
|
+
# queue.
|
71
|
+
#
|
72
|
+
# Returns the Message that was put onto the Queue
|
73
|
+
def produce( message )
|
74
|
+
msg = @maildir.add( message )
|
75
|
+
return ::Qup::Message.new( msg.key, msg.data )
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Internal: Retrieve a Message from the Queue
|
80
|
+
#
|
81
|
+
# Yields a Message
|
82
|
+
#
|
83
|
+
# A user of the Qup API should use a Consumer instance to retrieve items
|
84
|
+
# from the Queue.
|
85
|
+
#
|
86
|
+
# Returns a Message
|
87
|
+
def consume(&block)
|
88
|
+
msg = @maildir.list(:new, :limit => 1).first
|
89
|
+
return nil if msg.nil?
|
90
|
+
msg.process
|
91
|
+
msg.seen!
|
92
|
+
q_message = ::Qup::Message.new( msg.key, msg.data )
|
93
|
+
if block_given? then
|
94
|
+
yield_message( q_message, &block )
|
95
|
+
else
|
96
|
+
return q_message
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# Internal: Acknowledge that message is completed and remove it from the
|
102
|
+
# Queue.
|
103
|
+
#
|
104
|
+
# Returns nothing
|
105
|
+
def acknowledge( message )
|
106
|
+
md_message = @maildir.get( message.key )
|
107
|
+
msg = "Message #{message.key} has not been processed yet"
|
108
|
+
raise ::Qup::Error, msg unless md_message.dir == :cur
|
109
|
+
raise ::Qup::Error, msg unless md_message.seen?
|
110
|
+
md_message.destroy
|
111
|
+
end
|
112
|
+
|
113
|
+
#######
|
114
|
+
private
|
115
|
+
#######
|
116
|
+
|
117
|
+
def yield_message( message, &block )
|
118
|
+
yield message
|
119
|
+
ensure
|
120
|
+
acknowledge( message )
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Qup::Adapter::Maildir
|
2
|
+
#
|
3
|
+
# Internal: A Topic for use in a publish-subscribe Messaging
|
4
|
+
#
|
5
|
+
# The topic delivers each Message that it is give to each and every Subscriber
|
6
|
+
#
|
7
|
+
class Topic
|
8
|
+
include Qup::TopicAPI
|
9
|
+
|
10
|
+
# Internal: the name of the Topic
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# Internal: Create a new Topic
|
14
|
+
#
|
15
|
+
# root_path - the Session this Topic is attached to
|
16
|
+
# name - the String name of the Topic
|
17
|
+
#
|
18
|
+
# Returns a new Topic.
|
19
|
+
def initialize( root_path, name )
|
20
|
+
@root_path = ::Pathname.new( root_path )
|
21
|
+
@name = name
|
22
|
+
@topic_path = @root_path + @name
|
23
|
+
@subscribers = Hash.new
|
24
|
+
|
25
|
+
FileUtils.mkdir_p( @topic_path )
|
26
|
+
end
|
27
|
+
|
28
|
+
# Internal: Destroy the Topic
|
29
|
+
#
|
30
|
+
# If possible remove the existence of the Topic from the System
|
31
|
+
#
|
32
|
+
# Returns nothing.
|
33
|
+
def destroy
|
34
|
+
@topic_path.rmtree
|
35
|
+
end
|
36
|
+
|
37
|
+
# Internal: Creates a Publisher for the Topic
|
38
|
+
#
|
39
|
+
# Returns a new Publisher
|
40
|
+
def publisher
|
41
|
+
::Qup::Publisher.new( self )
|
42
|
+
end
|
43
|
+
|
44
|
+
# Internal: Create a subscriber for the Topic
|
45
|
+
#
|
46
|
+
# name - the String name of the subscriber
|
47
|
+
#
|
48
|
+
# Creating a subscriber creates a new Subscriber that will receive a copy of
|
49
|
+
# every message that is published to the Topic.
|
50
|
+
#
|
51
|
+
# Subscribers are unique by name, two subscribers with the same name will
|
52
|
+
# act as individual Consumers on a queue of their name.
|
53
|
+
#
|
54
|
+
# Returns a Subscriber
|
55
|
+
def subscriber( name )
|
56
|
+
::Qup::Subscriber.new( self, sub_queue( name ) )
|
57
|
+
end
|
58
|
+
|
59
|
+
# Internal: Return the number of Subscribers to this Topic
|
60
|
+
#
|
61
|
+
# Returns integer
|
62
|
+
def subscriber_count
|
63
|
+
@subscribers.size
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: Publish a Message to all the Subscribers
|
67
|
+
#
|
68
|
+
# message - the Object to send to all subscribers
|
69
|
+
#
|
70
|
+
# Returns nothing
|
71
|
+
def publish( message )
|
72
|
+
@subscribers.each do |name, sub|
|
73
|
+
sub.produce( message )
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#######
|
78
|
+
private
|
79
|
+
#######
|
80
|
+
|
81
|
+
def sub_queue( name )
|
82
|
+
@subscribers[name] ||= ::Qup::Adapter::Maildir::Queue.new( @topic_path, name )
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'qup/adapter'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
class Qup::Adapter
|
5
|
+
# Internal: The backing adapter for Qup that uses Redis as the messaging
|
6
|
+
# infrastructure
|
7
|
+
class Redis < ::Qup::Adapter
|
8
|
+
|
9
|
+
# Register this adapter as :redis
|
10
|
+
register :redis
|
11
|
+
|
12
|
+
# Internal: Create a new Redis Adapter
|
13
|
+
#
|
14
|
+
# uri - the URI instance for this adapter to use
|
15
|
+
def initialize( uri, options = {} )
|
16
|
+
@uri = uri
|
17
|
+
@options = options
|
18
|
+
@closed = false
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: Create a new Queue from this Adapter
|
22
|
+
#
|
23
|
+
# name - the String name of the Queue
|
24
|
+
#
|
25
|
+
# Returns a Qup::Queue
|
26
|
+
def queue( name )
|
27
|
+
Qup::Adapter::Redis::Queue.new( @uri, name )
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Create a new Topic from this Adapter
|
31
|
+
#
|
32
|
+
# name - the name of this Topic
|
33
|
+
#
|
34
|
+
# Returns a Qup::Topic
|
35
|
+
def topic( name )
|
36
|
+
Qup::Adapter::Redis::Topic.new( @uri, name )
|
37
|
+
end
|
38
|
+
|
39
|
+
# Internal: Close the Redis adapter
|
40
|
+
#
|
41
|
+
# Return nothing
|
42
|
+
def close
|
43
|
+
@closed = true
|
44
|
+
end
|
45
|
+
|
46
|
+
# Internal: Is the Redis Adapter closed
|
47
|
+
#
|
48
|
+
# Returns true or false
|
49
|
+
def closed?
|
50
|
+
@closed
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
require 'qup/adapter/redis/queue'
|
55
|
+
require 'qup/adapter/redis/topic'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Qup::Adapter::Redis
|
2
|
+
#
|
3
|
+
# Internal: The Common base class for Redis Topic and Queue
|
4
|
+
#
|
5
|
+
class Connection
|
6
|
+
|
7
|
+
# Public: the name of the Queue or Topic
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# Public: Create a new Connection
|
11
|
+
#
|
12
|
+
# uri - the connection uri for the Redis Client
|
13
|
+
# name - the String name of the Connection
|
14
|
+
#
|
15
|
+
# Returns a new Connection.
|
16
|
+
def initialize( uri, name )
|
17
|
+
@uri = uri
|
18
|
+
@client = Redis.new :host => @uri.host, :port => @uri.port
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
|
22
|
+
# Public: destroy the connection
|
23
|
+
#
|
24
|
+
# Closes the redis client connection.
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
27
|
+
def destroy
|
28
|
+
@client.client.disconnect
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'qup/adapter/redis/connection'
|
2
|
+
|
3
|
+
class Qup::Adapter::Redis
|
4
|
+
#
|
5
|
+
# Internal: The Qup implementation for a Redis Queue
|
6
|
+
#
|
7
|
+
class Queue < Connection
|
8
|
+
include Qup::QueueAPI
|
9
|
+
|
10
|
+
# Internal: create a new Queue
|
11
|
+
#
|
12
|
+
# uri - the connection uri for the Redis Client
|
13
|
+
# name - the String name of the Queue
|
14
|
+
#
|
15
|
+
# Returns a new Queue.
|
16
|
+
def initialize( uri, name )
|
17
|
+
super
|
18
|
+
@open_messages = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: Destroy the queue
|
22
|
+
#
|
23
|
+
# Removes the list from redis.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def destroy
|
27
|
+
@client.del name
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Empty the queue
|
31
|
+
#
|
32
|
+
# In redis, this is accomplished by merely deleting the list. If anything
|
33
|
+
# new is added to it, the key is re-created on the server
|
34
|
+
#
|
35
|
+
# Returns nothing
|
36
|
+
alias :flush :destroy
|
37
|
+
|
38
|
+
# Internal: return the number of Messages on the Queue
|
39
|
+
#
|
40
|
+
# Returns an integer of the Queue depth
|
41
|
+
def depth
|
42
|
+
@client.llen name
|
43
|
+
end
|
44
|
+
|
45
|
+
# Internal: Acknowledge that message is completed and remove it from the
|
46
|
+
# Queue.
|
47
|
+
#
|
48
|
+
# In redis, this doesn't do anything at all. The tracking is only performed
|
49
|
+
# to meet the API requirements.
|
50
|
+
#
|
51
|
+
# Returns nothing.
|
52
|
+
def acknowledge( message )
|
53
|
+
open_msg = @open_messages.delete( message.key )
|
54
|
+
raise Qup::Error, "Message #{message.key} is not currently being consumed" unless open_msg
|
55
|
+
end
|
56
|
+
|
57
|
+
# Internal: Put an item onto the Queue
|
58
|
+
#
|
59
|
+
# message - the data to put onto the queue.
|
60
|
+
#
|
61
|
+
# The 'message' that is passed in is wrapped in a Qup::Message before being
|
62
|
+
# stored.
|
63
|
+
#
|
64
|
+
# Returns the Message that was put onto the Queue
|
65
|
+
def produce( message )
|
66
|
+
@client.lpush name, message
|
67
|
+
return ::Qup::Message.new( message.object_id, message )
|
68
|
+
end
|
69
|
+
|
70
|
+
# Internal: Retrieve a Message from the Queue
|
71
|
+
#
|
72
|
+
# Yields a Message
|
73
|
+
#
|
74
|
+
# Returns a Message
|
75
|
+
def consume(&block)
|
76
|
+
queue_name, data = @client.brpop @name, 0 # blocking pop
|
77
|
+
message = ::Qup::Message.new( data.object_id, data )
|
78
|
+
@open_messages[message.key] = message
|
79
|
+
if block_given? then
|
80
|
+
yield_message( message, &block )
|
81
|
+
else
|
82
|
+
return message
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#######
|
87
|
+
private
|
88
|
+
#######
|
89
|
+
|
90
|
+
def yield_message( message, &block )
|
91
|
+
yield message
|
92
|
+
ensure
|
93
|
+
acknowledge( message )
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'qup/adapter/redis/connection'
|
2
|
+
|
3
|
+
class Qup::Adapter::Redis
|
4
|
+
#
|
5
|
+
# Internal: The Qup implementation for a Redis Topic
|
6
|
+
#
|
7
|
+
# Based on the API requirements, this is not implemented with Redis pub/sub,
|
8
|
+
# rather it guarantees durability of all published messages by using
|
9
|
+
# sub-queues internally to deliver messages to subscribers.
|
10
|
+
#
|
11
|
+
class Topic < Connection
|
12
|
+
include Qup::TopicAPI
|
13
|
+
|
14
|
+
# Internal: create a new Topic
|
15
|
+
#
|
16
|
+
# uri - the connection uri for the Redis Client
|
17
|
+
# name - the String name of the Topic
|
18
|
+
#
|
19
|
+
# Returns a new Topic.
|
20
|
+
def initialize(uri, name)
|
21
|
+
super
|
22
|
+
@subscribers = Hash.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# Internal: Creates a Publisher for the Topic
|
26
|
+
#
|
27
|
+
# Returns a new Publisher
|
28
|
+
def publisher
|
29
|
+
::Qup::Publisher.new( self )
|
30
|
+
end
|
31
|
+
|
32
|
+
# Internal: Create a subscriber for the Topic
|
33
|
+
#
|
34
|
+
# name - the String name of the subscriber
|
35
|
+
#
|
36
|
+
# Creating a subscriber creates a new Subscriber that will receive a copy of
|
37
|
+
# every message that is published to the Topic.
|
38
|
+
#
|
39
|
+
# Returns a Subscriber
|
40
|
+
def subscriber(name)
|
41
|
+
::Qup::Subscriber.new( self, subscriber_queue_for(name) )
|
42
|
+
end
|
43
|
+
|
44
|
+
# Internal: Return the number of Subscribers to this Topic
|
45
|
+
#
|
46
|
+
# Returns integer
|
47
|
+
def subscriber_count
|
48
|
+
@subscribers.size
|
49
|
+
end
|
50
|
+
|
51
|
+
# Internal: Publish a Message to all the Subscribers
|
52
|
+
#
|
53
|
+
# message - the Object to send to all subscribers
|
54
|
+
#
|
55
|
+
# Returns nothing
|
56
|
+
def publish( message )
|
57
|
+
@subscribers.values.each do |subscriber|
|
58
|
+
subscriber.produce message
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#######
|
63
|
+
private
|
64
|
+
#######
|
65
|
+
|
66
|
+
# Private: create and register new sub-Queue for the given subscriber
|
67
|
+
#
|
68
|
+
# name - the name of the subscriber
|
69
|
+
#
|
70
|
+
# Returns a Queue
|
71
|
+
def subscriber_queue_for(name)
|
72
|
+
@subscribers[name] ||=
|
73
|
+
::Qup::Adapter::Redis::Queue.new(@uri, "#{@name}-#{name}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|