zmqmachine 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.bnsignore +20 -0
- data/History.txt +21 -0
- data/README.rdoc +85 -0
- data/Rakefile +21 -0
- data/examples/fake_ftp.rb +242 -0
- data/examples/one_handed_ping_pong.rb +74 -0
- data/examples/ping_pong.rb +86 -0
- data/examples/pub_sub.rb +195 -0
- data/examples/throttled_ping_pong.rb +103 -0
- data/lib/zm/address.rb +70 -0
- data/lib/zm/deferrable.rb +209 -0
- data/lib/zm/exceptions.rb +45 -0
- data/lib/zm/message.rb +52 -0
- data/lib/zm/reactor.rb +458 -0
- data/lib/zm/sockets/base.rb +216 -0
- data/lib/zm/sockets/pair.rb +87 -0
- data/lib/zm/sockets/pub.rb +80 -0
- data/lib/zm/sockets/rep.rb +96 -0
- data/lib/zm/sockets/req.rb +96 -0
- data/lib/zm/sockets/sub.rb +83 -0
- data/lib/zm/sockets/xrep.rb +86 -0
- data/lib/zm/sockets/xreq.rb +86 -0
- data/lib/zm/sockets.rb +4 -0
- data/lib/zm/timers.rb +220 -0
- data/lib/zmqmachine.rb +76 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/zmqmachine_spec.rb +6 -0
- data/version.txt +1 -0
- data/zmqmachine.gemspec +48 -0
- metadata +133 -0
data/examples/pub_sub.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi-rzmq'
|
4
|
+
require '../lib/zmqmachine'
|
5
|
+
|
6
|
+
|
7
|
+
# Shows how to use a PUB socket to send messages in a fanout
|
8
|
+
# manner. A set of SUB sockets subscribe using different topic
|
9
|
+
# matches.
|
10
|
+
#
|
11
|
+
# The example also shows how to communicate between multiple
|
12
|
+
# reactors by running some operations in different contexts.
|
13
|
+
# Modify which lines are commented in/out within each context
|
14
|
+
# to see how performance changes in different scenarios.
|
15
|
+
#
|
16
|
+
|
17
|
+
class PublisherHandler
|
18
|
+
attr_reader :sent_count
|
19
|
+
|
20
|
+
def initialize context, port, topics
|
21
|
+
@context = context
|
22
|
+
@port = port
|
23
|
+
@topics = topics
|
24
|
+
@sent_count = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_attach socket
|
28
|
+
address = ZM::Address.new '127.0.0.1', @port, :tcp
|
29
|
+
rc = socket.bind address
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_writable socket
|
33
|
+
topic = @topics[rand(@topics.size)]
|
34
|
+
symbol = topic.split('.').first
|
35
|
+
|
36
|
+
if 'es' == symbol
|
37
|
+
payload = "#{topic}|#{rand(1200) + 1}|#{rand(4400)}"
|
38
|
+
else
|
39
|
+
payload = "#{topic}|#{rand(300) + 1}|#{rand(8000)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
message = ZMQ::Message.new payload
|
43
|
+
socket.send_message message
|
44
|
+
@sent_count += 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class SubscriberHandler
|
49
|
+
attr_reader :received_count, :topics
|
50
|
+
|
51
|
+
def initialize context, ports, topic = nil, sleep = false
|
52
|
+
@context = context
|
53
|
+
@received_count = 0
|
54
|
+
@ports = ports
|
55
|
+
(@topics ||= []) << topic.to_s
|
56
|
+
@sleep = sleep
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_attach socket
|
60
|
+
@ports.each do |port|
|
61
|
+
address = ZM::Address.new '127.0.0.1', port, :tcp
|
62
|
+
rc = socket.connect address
|
63
|
+
|
64
|
+
@topics.each do |topic|
|
65
|
+
puts "subscribe to [#{topic}]"
|
66
|
+
socket.subscribe topic
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_readable socket, messages
|
72
|
+
@received_count += 1
|
73
|
+
sleep 0.01 if @sleep
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Mix and match the handlers amongst the 5 reactors below. Make sure that a publisher
|
79
|
+
# handler is only getting instantiated and run in one reactor at a time. The subscriber
|
80
|
+
# handlers can be instantiated multiple times in each reactor if you so choose.
|
81
|
+
|
82
|
+
# Run all handlers within the same reactor context
|
83
|
+
ctx1 = ZM::Reactor.new(:A).run do |context|
|
84
|
+
@pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
|
85
|
+
context.pub_socket @pub1_handler
|
86
|
+
|
87
|
+
# @pub2_handler = PublisherHandler.new context, 5556, ['futures.us.nq.m.10', 'futures.us.nq.u.10']
|
88
|
+
# context.pub_socket @pub2_handler
|
89
|
+
#
|
90
|
+
# @sub1_handler = SubscriberHandler.new context, [5555, 5556]
|
91
|
+
# context.sub_socket @sub1_handler
|
92
|
+
#
|
93
|
+
# @sub2_handler = SubscriberHandler.new context, [5555], 'futures.us.es.m'
|
94
|
+
# context.sub_socket @sub2_handler
|
95
|
+
#
|
96
|
+
# @sub3_handler = SubscriberHandler.new context, [5555], 'futures.us.es.u'
|
97
|
+
# context.sub_socket @sub3_handler
|
98
|
+
#
|
99
|
+
# @sub4_handler = SubscriberHandler.new context, [5556], 'futures.us.nq.m'
|
100
|
+
# context.sub_socket @sub4_handler
|
101
|
+
#
|
102
|
+
# @sub5_handler = SubscriberHandler.new context, [5555, 5556], 'futures.us.'
|
103
|
+
# context.sub_socket @sub5_handler
|
104
|
+
end
|
105
|
+
|
106
|
+
# Or, run each handler in separate contexts each with its
|
107
|
+
# own thread.
|
108
|
+
ctx2 = ZM::Reactor.new(:B).run do |context|
|
109
|
+
# @pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
|
110
|
+
# context.pub_socket @pub1_handler
|
111
|
+
|
112
|
+
@pub2_handler = PublisherHandler.new context, 5556, ['futures.us.nq.m.10', 'futures.us.nq.u.10']
|
113
|
+
context.pub_socket @pub2_handler
|
114
|
+
|
115
|
+
# @sub1_handler = SubscriberHandler.new context, [5555, 5556]
|
116
|
+
# context.sub_socket @sub1_handler
|
117
|
+
#
|
118
|
+
# @sub2_handler = SubscriberHandler.new context, [5555], 'futures.us.es.m'
|
119
|
+
# context.sub_socket @sub2_handler
|
120
|
+
#
|
121
|
+
# @sub3_handler = SubscriberHandler.new context, [5555], 'futures.us.es.u'
|
122
|
+
# context.sub_socket @sub3_handler
|
123
|
+
#
|
124
|
+
# @sub4_handler = SubscriberHandler.new context, [5556], 'futures.us.nq.m'
|
125
|
+
# context.sub_socket @sub4_handler
|
126
|
+
#
|
127
|
+
# @sub5_handler = SubscriberHandler.new context, [5555, 5556], 'futures.us.'
|
128
|
+
# context.sub_socket @sub5_handler
|
129
|
+
end
|
130
|
+
|
131
|
+
ctx3 = ZM::Reactor.new(:C).run do |context|
|
132
|
+
# @pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
|
133
|
+
# context.pub_socket @pub1_handler
|
134
|
+
#
|
135
|
+
# @pub2_handler = PublisherHandler.new context, 5556, ['futures.us.nq.m.10', 'futures.us.nq.u.10']
|
136
|
+
# context.pub_socket @pub2_handler
|
137
|
+
#
|
138
|
+
@sub1_handler = SubscriberHandler.new context, [5555, 5556]
|
139
|
+
context.sub_socket @sub1_handler
|
140
|
+
|
141
|
+
@sub2_handler = SubscriberHandler.new context, [5555], 'futures.us.es.m'
|
142
|
+
context.sub_socket @sub2_handler
|
143
|
+
|
144
|
+
@sub3_handler = SubscriberHandler.new context, [5555], 'futures.us.es.u'
|
145
|
+
context.sub_socket @sub3_handler
|
146
|
+
|
147
|
+
@sub4_handler = SubscriberHandler.new context, [5556], 'futures.us.nq.m'
|
148
|
+
context.sub_socket @sub4_handler
|
149
|
+
|
150
|
+
# @sub5_handler = SubscriberHandler.new context, [5555, 5556], 'futures.us.'
|
151
|
+
# context.sub_socket @sub5_handler
|
152
|
+
end
|
153
|
+
|
154
|
+
ctx4 = ZM::Reactor.new(:D).run do |context|
|
155
|
+
# @pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
|
156
|
+
# context.pub_socket @pub1_handler
|
157
|
+
#
|
158
|
+
# @pub2_handler = PublisherHandler.new context, 5556, ['futures.us.nq.m.10', 'futures.us.nq.u.10']
|
159
|
+
# context.pub_socket @pub2_handler
|
160
|
+
#
|
161
|
+
# @sub1_handler = SubscriberHandler.new context, [5555, 5556]
|
162
|
+
# context.sub_socket @sub1_handler
|
163
|
+
#
|
164
|
+
# @sub2_handler = SubscriberHandler.new context, [5555], 'futures.us.es.m'
|
165
|
+
# context.sub_socket @sub2_handler
|
166
|
+
#
|
167
|
+
# @sub3_handler = SubscriberHandler.new context, [5555], 'futures.us.es.u'
|
168
|
+
# context.sub_socket @sub3_handler
|
169
|
+
#
|
170
|
+
# @sub4_handler = SubscriberHandler.new context, [5556], 'futures.us.nq.m'
|
171
|
+
# context.sub_socket @sub4_handler
|
172
|
+
|
173
|
+
@sub5_handler = SubscriberHandler.new context, [5555, 5556], 'futures.us.'
|
174
|
+
context.sub_socket @sub5_handler
|
175
|
+
end
|
176
|
+
|
177
|
+
# let's see how many messages we can publish in this many seconds
|
178
|
+
sleep_time = 5
|
179
|
+
puts "Started at [#{Time.now}]"
|
180
|
+
puts "main thread will sleep [#{sleep_time}] seconds before aborting the reactor context threads"
|
181
|
+
sleep sleep_time
|
182
|
+
|
183
|
+
# Exit each reactor after the sleep time
|
184
|
+
ctx1.stop
|
185
|
+
ctx2.stop
|
186
|
+
ctx3.stop
|
187
|
+
ctx4.stop
|
188
|
+
|
189
|
+
puts "sent [#{@pub1_handler.sent_count}]"
|
190
|
+
puts "sent [#{@pub2_handler.sent_count}]"
|
191
|
+
puts "* [#{@sub1_handler.received_count}]"
|
192
|
+
puts "futures.us.ep.m [#{@sub2_handler.received_count}]"
|
193
|
+
puts "futures.us.ep.u [#{@sub3_handler.received_count}]"
|
194
|
+
puts "futures.us.nq.m [#{@sub4_handler.received_count}]"
|
195
|
+
puts "futures.us.nq [#{@sub5_handler.received_count}]"
|
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi-rzmq'
|
4
|
+
require '../lib/zmqmachine'
|
5
|
+
|
6
|
+
# This example illustrates how to write a simple set of
|
7
|
+
# handlers for providing message ping-pong using
|
8
|
+
# a PAIR socket pair. All activity is asynchronous and
|
9
|
+
# relies on non-blocking I/O.
|
10
|
+
#
|
11
|
+
# The throttling aspect doesn't really kick in; need to
|
12
|
+
# ask the 0mq guys for clarity.
|
13
|
+
|
14
|
+
class PongHandler
|
15
|
+
attr_reader :sent_count, :received_count
|
16
|
+
|
17
|
+
def initialize context
|
18
|
+
@context = context
|
19
|
+
@sent_count = 0
|
20
|
+
@received_count = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_attach socket
|
24
|
+
address = ZM::Address.new '127.0.0.1', 5555, :tcp
|
25
|
+
rc = socket.bind address
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_readable socket, messages
|
29
|
+
@received_count += 1
|
30
|
+
pong socket, messages.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_writable socket
|
34
|
+
#puts "pong on_writable"
|
35
|
+
end
|
36
|
+
|
37
|
+
def pong socket, message
|
38
|
+
socket.send_message message
|
39
|
+
@sent_count += 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class PingHandler
|
44
|
+
attr_reader :sent_count, :received_count
|
45
|
+
|
46
|
+
def initialize context
|
47
|
+
@context = context
|
48
|
+
@sent_count = 0
|
49
|
+
@received_count = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_attach socket
|
53
|
+
address = ZM::Address.new '127.0.0.1', 5555, :tcp
|
54
|
+
rc = socket.connect address
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_readable socket, messages
|
58
|
+
@received_count += 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_writable socket
|
62
|
+
#puts "ping on_writable"
|
63
|
+
ping socket
|
64
|
+
end
|
65
|
+
|
66
|
+
# send as fast as possible until we hit our high water
|
67
|
+
# mark and get EAGAIN; then break
|
68
|
+
def ping socket
|
69
|
+
message = ZMQ::Message.new "#{'b' * 2048}"
|
70
|
+
rc = socket.send_message message
|
71
|
+
@sent_count += 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
# Run both handlers within the same reactor context
|
78
|
+
ctx1 = ZM::Reactor.new(:pong).run do |context|
|
79
|
+
@pong_handler = PongHandler.new context
|
80
|
+
context.pair_socket @pong_handler
|
81
|
+
|
82
|
+
# If you uncomment these 2 lines, comment out the
|
83
|
+
# +ctx2+ block below.
|
84
|
+
# @ping_handler = PingHandler.new context
|
85
|
+
# context.pair_socket @ping_handler
|
86
|
+
end
|
87
|
+
|
88
|
+
# Or, run each handler in separate contexts each with its
|
89
|
+
# own thread.
|
90
|
+
ctx2 = ZM::Reactor.new(:ping).run do |context|
|
91
|
+
@ping_handler = PingHandler.new context
|
92
|
+
context.pair_socket @ping_handler
|
93
|
+
end
|
94
|
+
|
95
|
+
# let's see how many messages we can transfer in this many seconds
|
96
|
+
sleep_time = 5
|
97
|
+
puts "Started at [#{Time.now}]"
|
98
|
+
puts "main thread will sleep [#{sleep_time}] seconds before aborting the context threads"
|
99
|
+
sleep sleep_time
|
100
|
+
|
101
|
+
ctx1.stop
|
102
|
+
ctx2.stop
|
103
|
+
puts "received [#{@pong_handler.received_count}], sent [#{@ping_handler.sent_count}]"
|
data/lib/zm/address.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Chuck Remes
|
4
|
+
# Homepage:: http://github.com/chuckremes/zmqmachine
|
5
|
+
# Date:: 20100602
|
6
|
+
#
|
7
|
+
#----------------------------------------------------------------------------
|
8
|
+
#
|
9
|
+
# Copyright (C) 2010 by Chuck Remes. All Rights Reserved.
|
10
|
+
# Email: cremes at mac dot com
|
11
|
+
#
|
12
|
+
# (The MIT License)
|
13
|
+
#
|
14
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
15
|
+
# a copy of this software and associated documentation files (the
|
16
|
+
# 'Software'), to deal in the Software without restriction, including
|
17
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
18
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
19
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
20
|
+
# the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be
|
23
|
+
# included in all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
26
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
27
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
28
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
29
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
30
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
31
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
32
|
+
#
|
33
|
+
#---------------------------------------------------------------------------
|
34
|
+
#
|
35
|
+
#
|
36
|
+
|
37
|
+
module ZMQMachine
|
38
|
+
|
39
|
+
#FIXME: needs to handle ipc and inproc transports better, e.g.
|
40
|
+
# there is no port component to either one.
|
41
|
+
#
|
42
|
+
class Address
|
43
|
+
attr_reader :host, :port, :transport
|
44
|
+
|
45
|
+
# +type+ : :tcp, :pgm or :inprocess
|
46
|
+
def initialize host, port, type = :tcp
|
47
|
+
@host = host
|
48
|
+
@port = port
|
49
|
+
@transport = determine_type type
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
"#{@transport}://#{@host}:#{@port}"
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def determine_type type
|
59
|
+
case type
|
60
|
+
when :inprocess
|
61
|
+
:inproc
|
62
|
+
when :tcp, :pgm
|
63
|
+
type
|
64
|
+
else
|
65
|
+
raise UnknownAddressError, "Unknown address transport type [#{type}]; must be :tcp, :pgm, or :inprocess"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end # class Address
|
70
|
+
end # module ZMQMachine
|
@@ -0,0 +1,209 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
5
|
+
# Date:: 16 Jul 2006
|
6
|
+
#
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
8
|
+
# usage examples.
|
9
|
+
#
|
10
|
+
#----------------------------------------------------------------------------
|
11
|
+
#
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
13
|
+
# Gmail: blackhedd
|
14
|
+
#
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
19
|
+
#
|
20
|
+
#---------------------------------------------------------------------------
|
21
|
+
#
|
22
|
+
#
|
23
|
+
|
24
|
+
module ZMQMachine
|
25
|
+
|
26
|
+
module Deferrable
|
27
|
+
|
28
|
+
# Specify a block to be executed if and when the Deferrable object receives
|
29
|
+
# a status of :succeeded. See #set_deferred_status for more information.
|
30
|
+
#
|
31
|
+
# Calling this method on a Deferrable object whose status is not yet known
|
32
|
+
# will cause the callback block to be stored on an internal list.
|
33
|
+
# If you call this method on a Deferrable whose status is :succeeded, the
|
34
|
+
# block will be executed immediately, receiving the parameters given to the
|
35
|
+
# prior #set_deferred_status call.
|
36
|
+
#
|
37
|
+
#--
|
38
|
+
# If there is no status, add a callback to an internal list.
|
39
|
+
# If status is succeeded, execute the callback immediately.
|
40
|
+
# If status is failed, do nothing.
|
41
|
+
#
|
42
|
+
def callback &block
|
43
|
+
return unless block
|
44
|
+
@deferred_status ||= :unknown
|
45
|
+
if @deferred_status == :succeeded
|
46
|
+
block.call(*@deferred_args)
|
47
|
+
elsif @deferred_status != :failed
|
48
|
+
@callbacks ||= []
|
49
|
+
@callbacks.unshift block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Cancels an outstanding callback to &block if any. Undoes the action of #callback.
|
54
|
+
#
|
55
|
+
def cancel_callback block
|
56
|
+
@callbacks ||= []
|
57
|
+
@callbacks.delete block
|
58
|
+
end
|
59
|
+
|
60
|
+
# Specify a block to be executed if and when the Deferrable object receives
|
61
|
+
# a status of :failed. See #set_deferred_status for more information.
|
62
|
+
#--
|
63
|
+
# If there is no status, add an errback to an internal list.
|
64
|
+
# If status is failed, execute the errback immediately.
|
65
|
+
# If status is succeeded, do nothing.
|
66
|
+
#
|
67
|
+
def errback &block
|
68
|
+
return unless block
|
69
|
+
@deferred_status ||= :unknown
|
70
|
+
if @deferred_status == :failed
|
71
|
+
block.call(*@deferred_args)
|
72
|
+
elsif @deferred_status != :succeeded
|
73
|
+
@errbacks ||= []
|
74
|
+
@errbacks.unshift block # << block
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Cancels an outstanding errback to &block if any. Undoes the action of #errback.
|
79
|
+
#
|
80
|
+
def cancel_errback block
|
81
|
+
@errbacks ||= []
|
82
|
+
@errbacks.delete block
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets the "disposition" (status) of the Deferrable object. See also the large set of
|
86
|
+
# sugarings for this method.
|
87
|
+
# Note that if you call this method without arguments,
|
88
|
+
# no arguments will be passed to the callback/errback.
|
89
|
+
# If the user has coded these with arguments, then the
|
90
|
+
# user code will throw an argument exception.
|
91
|
+
# Implementors of deferrable classes <b>must</b>
|
92
|
+
# document the arguments they will supply to user callbacks.
|
93
|
+
#
|
94
|
+
# OBSERVE SOMETHING VERY SPECIAL here: you may call this method even
|
95
|
+
# on the INSIDE of a callback. This is very useful when a previously-registered
|
96
|
+
# callback wants to change the parameters that will be passed to subsequently-registered
|
97
|
+
# ones.
|
98
|
+
#
|
99
|
+
# You may give either :succeeded or :failed as the status argument.
|
100
|
+
#
|
101
|
+
# If you pass :succeeded, then all of the blocks passed to the object using the #callback
|
102
|
+
# method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
|
103
|
+
# passed to the object using #errback will be discarded.
|
104
|
+
#
|
105
|
+
# If you pass :failed, then all of the blocks passed to the object using the #errback
|
106
|
+
# method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
|
107
|
+
# passed to the object using # callback will be discarded.
|
108
|
+
#
|
109
|
+
# If you pass any arguments to #set_deferred_status in addition to the status argument,
|
110
|
+
# they will be passed as arguments to any callbacks or errbacks that are executed.
|
111
|
+
# It's your responsibility to ensure that the argument lists specified in your callbacks and
|
112
|
+
# errbacks match the arguments given in calls to #set_deferred_status, otherwise Ruby will raise
|
113
|
+
# an ArgumentError.
|
114
|
+
#
|
115
|
+
#--
|
116
|
+
# We're shifting callbacks off and discarding them as we execute them.
|
117
|
+
# This is valid because by definition callbacks are executed no more than
|
118
|
+
# once. It also has the magic effect of permitting recursive calls, which
|
119
|
+
# means that a callback can call #set_deferred_status and change the parameters
|
120
|
+
# that will be sent to subsequent callbacks down the chain.
|
121
|
+
#
|
122
|
+
# Changed @callbacks and @errbacks from push/shift to unshift/pop, per suggestion
|
123
|
+
# by Kirk Haines, to work around the memory leak bug that still exists in many Ruby
|
124
|
+
# versions.
|
125
|
+
#
|
126
|
+
# Changed 15Sep07: after processing callbacks or errbacks, CLEAR the other set of
|
127
|
+
# handlers. This gets us a little closer to the behavior of Twisted's "deferred,"
|
128
|
+
# which only allows status to be set once. Prior to making this change, it was possible
|
129
|
+
# to "succeed" a Deferrable (triggering its callbacks), and then immediately "fail" it,
|
130
|
+
# triggering its errbacks! That is clearly undesirable, but it's just as undesirable
|
131
|
+
# to raise an exception is status is set more than once on a Deferrable. The latter
|
132
|
+
# behavior would invalidate the idiom of resetting arguments by setting status from
|
133
|
+
# within a callback or errback, but more seriously it would cause spurious errors
|
134
|
+
# if a Deferrable was timed out and then an attempt was made to succeed it. See the
|
135
|
+
# comments under the new method #timeout.
|
136
|
+
#
|
137
|
+
def set_deferred_status status, *args
|
138
|
+
cancel_timeout
|
139
|
+
@errbacks ||= nil
|
140
|
+
@callbacks ||= nil
|
141
|
+
@deferred_status = status
|
142
|
+
@deferred_args = args
|
143
|
+
case @deferred_status
|
144
|
+
when :succeeded
|
145
|
+
if @callbacks
|
146
|
+
while cb = @callbacks.pop
|
147
|
+
cb.call(*@deferred_args)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
@errbacks.clear if @errbacks
|
151
|
+
when :failed
|
152
|
+
if @errbacks
|
153
|
+
while eb = @errbacks.pop
|
154
|
+
eb.call(*@deferred_args)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
@callbacks.clear if @callbacks
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
# Setting a timeout on a Deferrable causes it to go into the failed state after
|
163
|
+
# the Timeout expires (passing no arguments to the object's errbacks).
|
164
|
+
# Setting the status at any time prior to a call to the expiration of the timeout
|
165
|
+
# will cause the timer to be cancelled.
|
166
|
+
def timeout seconds, *args
|
167
|
+
cancel_timeout
|
168
|
+
me = self
|
169
|
+
|
170
|
+
# we use milliseconds, not seconds, so adjust the passed in time
|
171
|
+
seconds *= 1000
|
172
|
+
@deferred_timeout = ZMQMachine::Timers.add_oneshot seconds {me.fail(*args)}
|
173
|
+
end
|
174
|
+
|
175
|
+
# Cancels an outstanding timeout if any. Undoes the action of #timeout.
|
176
|
+
#
|
177
|
+
def cancel_timeout
|
178
|
+
@deferred_timeout ||= nil
|
179
|
+
if @deferred_timeout
|
180
|
+
@deferred_timeout.cancel
|
181
|
+
@deferred_timeout = nil
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# Sugar for set_deferred_status(:succeeded, ...)
|
187
|
+
#
|
188
|
+
def succeed *args
|
189
|
+
set_deferred_status :succeeded, *args
|
190
|
+
end
|
191
|
+
alias set_deferred_success succeed
|
192
|
+
|
193
|
+
# Sugar for set_deferred_status(:failed, ...)
|
194
|
+
#
|
195
|
+
def fail *args
|
196
|
+
set_deferred_status :failed, *args
|
197
|
+
end
|
198
|
+
alias set_deferred_failure fail
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# DefaultDeferrable is an otherwise empty class that includes Deferrable.
|
203
|
+
# This is very useful when you just need to return a Deferrable object
|
204
|
+
# as a way of communicating deferred status to some other part of a program.
|
205
|
+
class DefaultDeferrable
|
206
|
+
include Deferrable
|
207
|
+
end
|
208
|
+
|
209
|
+
end # module ZMQMachine
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Chuck Remes
|
4
|
+
# Homepage:: http://github.com/chuckremes/zmqmachine
|
5
|
+
# Date:: 20100602
|
6
|
+
#
|
7
|
+
#----------------------------------------------------------------------------
|
8
|
+
#
|
9
|
+
# Copyright (C) 2010 by Chuck Remes. All Rights Reserved.
|
10
|
+
# Email: cremes at mac dot com
|
11
|
+
#
|
12
|
+
# (The MIT License)
|
13
|
+
#
|
14
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
15
|
+
# a copy of this software and associated documentation files (the
|
16
|
+
# 'Software'), to deal in the Software without restriction, including
|
17
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
18
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
19
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
20
|
+
# the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be
|
23
|
+
# included in all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
26
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
27
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
28
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
29
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
30
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
31
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
32
|
+
#
|
33
|
+
#---------------------------------------------------------------------------
|
34
|
+
#
|
35
|
+
#
|
36
|
+
|
37
|
+
module ZMQMachine
|
38
|
+
|
39
|
+
class TimeoutError < StandardError; end
|
40
|
+
|
41
|
+
class UnknownAddressError < StandardError; end
|
42
|
+
|
43
|
+
class NotReadyError < StandardError; end
|
44
|
+
|
45
|
+
end # module ZMQMachine
|
data/lib/zm/message.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Chuck Remes
|
4
|
+
# Homepage:: http://github.com/chuckremes/zmqmachine
|
5
|
+
# Date:: 20100602
|
6
|
+
#
|
7
|
+
#----------------------------------------------------------------------------
|
8
|
+
#
|
9
|
+
# Copyright (C) 2010 by Chuck Remes. All Rights Reserved.
|
10
|
+
# Email: cremes at mac dot com
|
11
|
+
#
|
12
|
+
# (The MIT License)
|
13
|
+
#
|
14
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
15
|
+
# a copy of this software and associated documentation files (the
|
16
|
+
# 'Software'), to deal in the Software without restriction, including
|
17
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
18
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
19
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
20
|
+
# the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be
|
23
|
+
# included in all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
26
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
27
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
28
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
29
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
30
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
31
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
32
|
+
#
|
33
|
+
#---------------------------------------------------------------------------
|
34
|
+
#
|
35
|
+
#
|
36
|
+
|
37
|
+
module ZMQMachine
|
38
|
+
|
39
|
+
module Message
|
40
|
+
|
41
|
+
# A unique identifier for this message
|
42
|
+
def identity
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def encode
|
47
|
+
raise NotImplementedError, "You must implement the #encode method in your Message class", caller
|
48
|
+
# FIXME: default to json encoding?
|
49
|
+
end
|
50
|
+
|
51
|
+
end # module Message
|
52
|
+
end # module ZMQMachine
|