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
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
|