zmqmachine 0.3.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/.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
|