qup 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/qup/consumer.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Qup
|
2
|
+
# Public: Consumes items for a queue
|
3
|
+
#
|
4
|
+
# A Consumer is created from a Queue and consumes messages from the queue.
|
5
|
+
#
|
6
|
+
#
|
7
|
+
class Consumer
|
8
|
+
# Public: Create a new Consumer
|
9
|
+
#
|
10
|
+
# queue - the Queue this producer is for
|
11
|
+
#
|
12
|
+
# Returns a new Consumer
|
13
|
+
def initialize( queue )
|
14
|
+
@queue = queue
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Consume a message for the queue
|
18
|
+
#
|
19
|
+
# Yields the message
|
20
|
+
#
|
21
|
+
# Most of the time you will want to call this message with a block as the
|
22
|
+
# Message will be auto-acknowledged. If you do not consume messages with a
|
23
|
+
# block, then you are required to acknowledge the messages on your own.
|
24
|
+
#
|
25
|
+
# Returns a Message
|
26
|
+
def consume(&block)
|
27
|
+
@queue.consume(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: Acknowledge a consumed message
|
31
|
+
#
|
32
|
+
# message - The message you are acknowledging
|
33
|
+
#
|
34
|
+
# A consumed message must be acknowledge so the back end system can
|
35
|
+
# be assured that the message has been a fully processed.
|
36
|
+
#
|
37
|
+
# Returns nothing.
|
38
|
+
def acknowledge( message )
|
39
|
+
@queue.acknowledge( message )
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/qup/message.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Qup
|
2
|
+
# Public: A Message is an item that may be put on and taken off the queue.
|
3
|
+
#
|
4
|
+
class Message
|
5
|
+
|
6
|
+
# Public: The unique identifier of this message
|
7
|
+
attr_reader :key
|
8
|
+
|
9
|
+
# Public: The data in this Message
|
10
|
+
attr_reader :data
|
11
|
+
|
12
|
+
# Public: Create a Message from the given data
|
13
|
+
def initialize( key, data )
|
14
|
+
@key = key
|
15
|
+
@data = data
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/qup/producer.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Qup
|
2
|
+
# Public: Produces items for a queue
|
3
|
+
#
|
4
|
+
# Examples:
|
5
|
+
#
|
6
|
+
# producer = queue.producer
|
7
|
+
# producer.produce( my_message )
|
8
|
+
#
|
9
|
+
class Producer
|
10
|
+
# Public: Create a new Producer
|
11
|
+
#
|
12
|
+
# queue - the Queue this producer is for
|
13
|
+
#
|
14
|
+
# Returns a new Producer
|
15
|
+
def initialize( queue )
|
16
|
+
@queue = queue
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Produce a message for the queue
|
20
|
+
#
|
21
|
+
# message - the Object to put onto the queue
|
22
|
+
#
|
23
|
+
# Returns nothing.
|
24
|
+
def produce( message )
|
25
|
+
@queue.produce( message )
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Qup
|
2
|
+
# Public: A Publisher produces messages on a Topic
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# pub = topic.publisher
|
7
|
+
# pub.publish( my_message )
|
8
|
+
#
|
9
|
+
class Publisher
|
10
|
+
|
11
|
+
# Public: The Topic this Publisher publishes to.
|
12
|
+
attr_reader :topic
|
13
|
+
|
14
|
+
# Public: Create a new Publisher for a Topic
|
15
|
+
#
|
16
|
+
# Returns a Publisher
|
17
|
+
def initialize( topic )
|
18
|
+
@topic = topic
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Publish a Message to all the Subscribers
|
22
|
+
#
|
23
|
+
# message - the Object to send to all Subscribers.
|
24
|
+
#
|
25
|
+
# Returns nothing
|
26
|
+
def publish( message )
|
27
|
+
@topic.publish( message )
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'qup/producer'
|
2
|
+
require 'qup/consumer'
|
3
|
+
module Qup
|
4
|
+
#
|
5
|
+
# Public: A QueueAPI for use in a point-to-point messaging pattern.
|
6
|
+
#
|
7
|
+
# The Queue guarantees that each Message that is on the Queue is delivered and
|
8
|
+
# acknowledged only once.
|
9
|
+
#
|
10
|
+
# A very common pattern for Queue usage is a worker pattern where you have a
|
11
|
+
# Producer putting job Messages on the Queue and a collection of Consumers
|
12
|
+
# which work on those job Messages.
|
13
|
+
#
|
14
|
+
# This is the API that MUST be implemented in the adapter.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# session = Session.new( uri )
|
19
|
+
# queue = session.queue( 'my_queue' )
|
20
|
+
#
|
21
|
+
# queue.producer # => Producer
|
22
|
+
# queue.consumer # => Consumer
|
23
|
+
# queue.depth # => 4
|
24
|
+
#
|
25
|
+
module QueueAPI
|
26
|
+
|
27
|
+
# Public: create a Producer for this Queue
|
28
|
+
#
|
29
|
+
# Returns a new Producer
|
30
|
+
def producer
|
31
|
+
Producer.new( self )
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: create a Consumer for this Queue
|
35
|
+
#
|
36
|
+
# Returns a new Consumer
|
37
|
+
def consumer
|
38
|
+
Consumer.new( self )
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
#--------------------------------------------------------------------------
|
43
|
+
# The API that Adapters must implement
|
44
|
+
#--------------------------------------------------------------------------
|
45
|
+
|
46
|
+
# Public: the name of the Queue
|
47
|
+
#
|
48
|
+
# Returns the String name
|
49
|
+
def name
|
50
|
+
super
|
51
|
+
rescue NoMethodError
|
52
|
+
raise NotImplementedError, "please implement 'name'"
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Public: return the number of Messages on the Queue
|
57
|
+
#
|
58
|
+
# Returns an integer of the Queue depth
|
59
|
+
def depth
|
60
|
+
raise NotImplementedError, "please implement 'depth'"
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Public: empty all the messages from the Queue, this does not consume them,
|
65
|
+
# this removes the from the Queue
|
66
|
+
#
|
67
|
+
# Returns nothing
|
68
|
+
def flush
|
69
|
+
raise NotImplementedError, "please implement 'flush'"
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# Public: destroy the Queue if possible
|
74
|
+
#
|
75
|
+
# This will clear the Queue and remove it from the system if possible
|
76
|
+
#
|
77
|
+
# Returns nothing.
|
78
|
+
def destroy
|
79
|
+
super
|
80
|
+
rescue NoMethodError
|
81
|
+
raise NotImplementedError, "please implement 'destroy'"
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# Internal: Put an item onto the Queue
|
86
|
+
#
|
87
|
+
# message - the data to put onto the queue
|
88
|
+
#
|
89
|
+
# A user of the Qup API should use a Producer instance to put items onto the
|
90
|
+
# queue.
|
91
|
+
#
|
92
|
+
# Returns the Message that was put onto the Queue
|
93
|
+
def produce( message )
|
94
|
+
raise NotImplementedError, "please implement 'produce'"
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Internal: Retrieve an item from the Queue
|
99
|
+
#
|
100
|
+
# options - a Hash of options determining how long to wait for a Message
|
101
|
+
# :block - should we block until a Message is available
|
102
|
+
# (default: true )
|
103
|
+
# :timeout - how long to wait for a Message, only valid if
|
104
|
+
# :block is false
|
105
|
+
#
|
106
|
+
# A user of the Qup API should use a Consumer instance to retrieve items
|
107
|
+
# from the Queue.
|
108
|
+
#
|
109
|
+
# Returns a Message
|
110
|
+
def consume(&block)
|
111
|
+
raise NotImplementedError, "please implement 'consume'"
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# Internal: Acknowledge that message is completed and remove it from the
|
116
|
+
# Queue.
|
117
|
+
#
|
118
|
+
# Returns nothing
|
119
|
+
def acknowledge( message )
|
120
|
+
raise NotImplementedError, "please implement 'acknowledge'"
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
data/lib/qup/session.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'pathname'
|
3
|
+
module Qup
|
4
|
+
# Public: Manage communicating with a provider
|
5
|
+
#
|
6
|
+
# Examples:
|
7
|
+
#
|
8
|
+
# Session.new( 'maildir:///tmp/qup' )
|
9
|
+
# Session.new( 'kestrel://user:pass@host:port/' )
|
10
|
+
#
|
11
|
+
# session.queue( 'foo' ) # => Queue
|
12
|
+
# session.topic( 'bar' ) # => Topic
|
13
|
+
#
|
14
|
+
# At the moment, a Session is not considered thread safe, so each Thread should
|
15
|
+
# create its own Session.
|
16
|
+
class Session
|
17
|
+
class ClosedError < Qup::Error; end
|
18
|
+
|
19
|
+
# Public: The URI of this Session
|
20
|
+
attr_reader :uri
|
21
|
+
|
22
|
+
# Public: Create a new Session
|
23
|
+
#
|
24
|
+
# uri - The connection String used to connect to appropriate provider
|
25
|
+
# options - The Hash of options that are passed to the underlying Adapter
|
26
|
+
#
|
27
|
+
# Yields the created Session
|
28
|
+
#
|
29
|
+
# If a session is yielded, it is closed at the end of the block
|
30
|
+
#
|
31
|
+
# Returns a new Session
|
32
|
+
def self.open( uri, options = {}, &block )
|
33
|
+
session = ::Qup::Session.new( uri, options )
|
34
|
+
return session unless block_given?
|
35
|
+
yield session
|
36
|
+
ensure
|
37
|
+
session.close if block_given?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Create a new Session
|
41
|
+
#
|
42
|
+
# uri - The connection String used to connect to appropriate provider
|
43
|
+
# options - The Hash of options that are passed to the underlying Adapter
|
44
|
+
#
|
45
|
+
# Returns a new Session
|
46
|
+
def initialize( uri, options = {} )
|
47
|
+
@uri = URI.parse( uri )
|
48
|
+
@root_path = Pathname.new( @uri.path )
|
49
|
+
|
50
|
+
adapter_klass = Qup::Adapters[@uri.scheme]
|
51
|
+
@adapter = adapter_klass.new( @uri, options )
|
52
|
+
|
53
|
+
@queues = Hash.new
|
54
|
+
@topics = Hash.new
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Allocate a new Queue
|
58
|
+
#
|
59
|
+
# Connect to an existing, or Create a new Queue with the given name and
|
60
|
+
# options.
|
61
|
+
#
|
62
|
+
# Yields the Queue.
|
63
|
+
#
|
64
|
+
# name - The String name of the Queue to connect/create
|
65
|
+
#
|
66
|
+
# Returns a new Queue instance
|
67
|
+
def queue( name, &block )
|
68
|
+
destination( name, :queue, @queues, &block )
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Allocate a new Topic
|
72
|
+
#
|
73
|
+
# Connect to an existing, or Create a new Topic with the given name and
|
74
|
+
# options.
|
75
|
+
#
|
76
|
+
# Yields the Topic.
|
77
|
+
#
|
78
|
+
# name - The String name of the Topic to connect/create
|
79
|
+
#
|
80
|
+
# Returns a new Topic instance
|
81
|
+
def topic( name, &block )
|
82
|
+
destination( name, :topic, @topics, &block )
|
83
|
+
end
|
84
|
+
|
85
|
+
# Public: Close the Session
|
86
|
+
#
|
87
|
+
# Calling closed on an already closed Session does nothing.
|
88
|
+
#
|
89
|
+
# Returns nothing
|
90
|
+
def close
|
91
|
+
@adapter.close
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: Is the Session closed?
|
95
|
+
#
|
96
|
+
# Returns true if the session is closed, false otherwise.
|
97
|
+
def closed?
|
98
|
+
@adapter.closed?
|
99
|
+
end
|
100
|
+
|
101
|
+
#######
|
102
|
+
private
|
103
|
+
#######
|
104
|
+
def destination( name, type, collection, &block )
|
105
|
+
raise Qup::Session::ClosedError, "Session connected to #{@uri} is closed" if closed?
|
106
|
+
d = (collection[name] ||= @adapter.send( type, name ))
|
107
|
+
return d unless block_given?
|
108
|
+
yield d
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Qup
|
2
|
+
# Public: Receives Messages from a Topic
|
3
|
+
#
|
4
|
+
# All Subscribers to a Topic will receive each Message that is published to
|
5
|
+
# the Topic.
|
6
|
+
#
|
7
|
+
class Subscriber < Consumer
|
8
|
+
# Public: The name of this subscriber
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# Public: Create a new Subscriber on a Topic
|
12
|
+
#
|
13
|
+
# Returns a Subscriber
|
14
|
+
def initialize( topic, queue )
|
15
|
+
super( queue )
|
16
|
+
@topic = topic
|
17
|
+
@name = @queue.name
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Remove the Subscriber from the Topic
|
21
|
+
#
|
22
|
+
# This just means to destroy the Queue that it is attached to
|
23
|
+
#
|
24
|
+
def unsubscribe
|
25
|
+
@queue.destroy
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Qup
|
2
|
+
#
|
3
|
+
# Public: A TopicAPI 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
|
+
# This API MUST be implemented in each Adapter
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# session = Qup::Session.new( uri )
|
12
|
+
# topic = session.topic( 'news' )
|
13
|
+
#
|
14
|
+
# pub = topic.publisher
|
15
|
+
#
|
16
|
+
# sub1 = topic.subscriber( 'sub1' )
|
17
|
+
# sub2 = topic.subscriber( 'sub2' )
|
18
|
+
#
|
19
|
+
# pub.publish( 'some news' )
|
20
|
+
#
|
21
|
+
# message1 = sub1.consume
|
22
|
+
# message2 = sub2.consume
|
23
|
+
#
|
24
|
+
module TopicAPI
|
25
|
+
#--------------------------------------------------------------------------
|
26
|
+
# The API that Adapters must implement
|
27
|
+
#--------------------------------------------------------------------------
|
28
|
+
|
29
|
+
# Public: Creates a Publisher for the Topic
|
30
|
+
#
|
31
|
+
# Returns a new Publisher
|
32
|
+
def publisher
|
33
|
+
raise NotImplementedError, "please implement 'publisher'"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Public: Create a subscriber for the Topic
|
38
|
+
#
|
39
|
+
# name - the String name of the subscriber
|
40
|
+
#
|
41
|
+
# Creating a subscriber creates a new Subscriber that will receive a copy of
|
42
|
+
# every message that is published to the Topic.
|
43
|
+
#
|
44
|
+
# Subscribers are unique by name, two subscribers with the same name will
|
45
|
+
# act as individual Consumers on a queue of their name.
|
46
|
+
#
|
47
|
+
# Returns a Subscriber
|
48
|
+
def subscriber( name )
|
49
|
+
raise NotImplementedError, "please implement 'subscriber'"
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Public: the name of the Topic
|
54
|
+
#
|
55
|
+
# Returns the String name
|
56
|
+
def name
|
57
|
+
super
|
58
|
+
rescue NoMethodError
|
59
|
+
raise NotImplementedError, "please implement 'name'"
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Public: destroy the Topic if possible
|
64
|
+
#
|
65
|
+
# This will remove the Topic from the system if possible
|
66
|
+
#
|
67
|
+
# Returns nothing.
|
68
|
+
def destroy
|
69
|
+
super
|
70
|
+
rescue NoMethodError
|
71
|
+
raise NotImplementedError, "please implement 'destroy'"
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Public: Return the number of Subscribers to this Topic
|
76
|
+
#
|
77
|
+
# Returns integer
|
78
|
+
def subscriber_count
|
79
|
+
raise NotImplementedError, "please implement 'subscriber_count'"
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Internal: Publish a Message to all the Subscribers
|
84
|
+
#
|
85
|
+
# message - the Object to send to all subscribers
|
86
|
+
#
|
87
|
+
# Returns nothing
|
88
|
+
def publish( message )
|
89
|
+
raise NotImplementedError, "please implement 'publish'"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|