zmqmachine 0.6.0 → 0.7.1

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.
Files changed (44) hide show
  1. data/.gitignore +15 -0
  2. data/AUTHORS.txt +3 -0
  3. data/History.txt +19 -0
  4. data/examples/fake_ftp.rb +9 -1
  5. data/examples/one_handed_ping_pong.rb +2 -1
  6. data/examples/ping_pong.rb +2 -2
  7. data/examples/pub_sub.rb +10 -4
  8. data/examples/pubsub_forwarder.rb +8 -3
  9. data/examples/throttled_ping_pong.rb +2 -2
  10. data/lib/zm/configuration.rb +129 -0
  11. data/lib/zm/device.rb +4 -0
  12. data/lib/zm/device/configuration.rb +24 -0
  13. data/lib/zm/{devices → device}/forwarder.rb +48 -46
  14. data/lib/zm/{devices → device}/queue.rb +63 -44
  15. data/lib/zm/log_client.rb +2 -2
  16. data/lib/zm/log_server.rb +68 -0
  17. data/lib/zm/reactor.rb +42 -29
  18. data/lib/zm/server.rb +4 -0
  19. data/lib/zm/server/base.rb +139 -0
  20. data/lib/zm/server/configuration.rb +53 -0
  21. data/lib/zm/server/pair.rb +23 -0
  22. data/lib/zm/server/pub.rb +23 -0
  23. data/lib/zm/server/pull.rb +24 -0
  24. data/lib/zm/server/push.rb +23 -0
  25. data/lib/zm/server/rep.rb +42 -0
  26. data/lib/zm/server/req.rb +41 -0
  27. data/lib/zm/server/routing_envelope.rb +26 -0
  28. data/lib/zm/server/sub.rb +40 -0
  29. data/lib/zm/sockets.rb +1 -1
  30. data/lib/zm/sockets/base.rb +3 -12
  31. data/lib/zm/sockets/envelope_help.rb +49 -0
  32. data/lib/zm/sockets/pair.rb +0 -1
  33. data/lib/zm/sockets/pub.rb +0 -1
  34. data/lib/zm/sockets/rep.rb +45 -0
  35. data/lib/zm/sockets/req.rb +46 -1
  36. data/lib/zm/sockets/sub.rb +0 -1
  37. data/lib/zm/timers.rb +26 -13
  38. data/lib/zmqmachine.rb +1 -1
  39. data/version.txt +1 -1
  40. data/zmqmachine.gemspec +6 -6
  41. metadata +90 -91
  42. data/lib/zm/devices.rb +0 -4
  43. data/lib/zm/sockets/xrep.rb +0 -86
  44. data/lib/zm/sockets/xreq.rb +0 -86
@@ -0,0 +1,15 @@
1
+ html
2
+ pkg
3
+ doc
4
+ *.gem
5
+ *.rbc
6
+ *.tmproj
7
+ nbproject
8
+ *.bundle
9
+ *.o
10
+ .DS_Store
11
+ .rbx
12
+ examples/work
13
+ benchmark
14
+ *~
15
+ *#*
@@ -0,0 +1,3 @@
1
+ Chuck Remes, chuckremes (github)
2
+
3
+ Tim Carey-Smith, halorgium (github)
@@ -1,3 +1,22 @@
1
+ == 0.7.1 / 2011-11-30
2
+ * Updated the Devices to use a Device::Configuration instance
3
+ for configuration rather than that long method signature.
4
+ Makes all components conform to this new configuration
5
+ mechanism consistently.
6
+
7
+ == 0.7.0 / 2011-10-28
8
+ * Created a class for holding Reactor and Server configurations.
9
+ This is much nicer than passing in individual args or a hash.
10
+
11
+ * Added the ZM::Servers module to make building servers a little
12
+ easier. The biggest improvement is for making servers that
13
+ utilize XREP/ROUTER sockets since the module handles the
14
+ splitting of the routing envelope messages from the body.
15
+
16
+ * Fixed a bug where a timer that raises an exception prevented
17
+ other periodical timers from renewing. Thanks to Tim
18
+ Carey-Smith for the bug report!
19
+
1
20
  == 0.6.0 / 2011-10-04
2
21
  * Compatibility with ffi-rzmq 0.9.0 and added some conditional
3
22
  logic to support 0mq 2.1.x and 3.x APIs.
@@ -93,6 +93,10 @@ class FTPControlClient
93
93
 
94
94
  @data_client.should_expect reply['total_chunks']
95
95
  end
96
+
97
+ def on_readable_error socket, error
98
+ puts "on_readable_error, error [#{error}], descr [#{ZMQ::Util.error_string}]"
99
+ end
96
100
 
97
101
  def get filename, &blk
98
102
  sub_address = ZM::Address.new @address.host, 5556, @address.transport
@@ -204,6 +208,10 @@ class FTPControlServer
204
208
  @pub_handler = nil
205
209
  end
206
210
  end
211
+
212
+ def on_readable_error socket, error
213
+ puts "on_readable_error, error [#{error}], descr [#{ZMQ::Util.error_string}]"
214
+ end
207
215
 
208
216
  def on_writable socket
209
217
  puts "#{self.class.name}#on_writable ERROR, should never be called"
@@ -223,7 +231,7 @@ end
223
231
 
224
232
 
225
233
  # Run both handlers within the same reactor context
226
- ctx1 = ZM::Reactor.new(:test).run do |context|
234
+ ctx1 = ZM::Reactor.new.run do |context|
227
235
 
228
236
  @request_server = FTPControlServer.new context, server_address
229
237
  context.rep_socket @request_server
@@ -29,6 +29,7 @@ class PingPongHandler
29
29
  when :request
30
30
  rc = socket.connect address
31
31
  @context.register_readable socket
32
+ @context.register_writable socket
32
33
  end
33
34
  end
34
35
 
@@ -57,7 +58,7 @@ end
57
58
 
58
59
  handler = nil
59
60
  # Run both handlers within the same reactor context
60
- ctx1 = ZM::Reactor.new(:test).run do |context|
61
+ ctx1 = ZM::Reactor.new.run do |context|
61
62
  handler = PingPongHandler.new context
62
63
 
63
64
  context.rep_socket handler
@@ -60,7 +60,7 @@ class PingHandler
60
60
  end
61
61
 
62
62
  # Run both handlers within the same reactor context
63
- ctx1 = ZM::Reactor.new(:test).run do |context|
63
+ ctx1 = ZM::Reactor.new.run do |context|
64
64
  @pong_handler = PongHandler.new context
65
65
  context.rep_socket @pong_handler
66
66
 
@@ -76,7 +76,7 @@ end
76
76
 
77
77
  # Or, run each handler in separate contexts each with its
78
78
  # own thread.
79
- #ctx2 = ZM::Reactor.new(:test).run do |context|
79
+ #ctx2 = ZM::Reactor.new.run do |context|
80
80
  # @ping_handler = PingHandler.new context
81
81
  # context.req_socket @ping_handler
82
82
  #end
@@ -79,8 +79,14 @@ end
79
79
  # handler is only getting instantiated and run in one reactor at a time. The subscriber
80
80
  # handlers can be instantiated multiple times in each reactor if you so choose.
81
81
 
82
+ configuration = ZM::Configuration.new do
83
+ name 'publisher reactor'
84
+ poll_interval 3
85
+ exception_handler Proc.new { |exc| raise(exc) }
86
+ end
87
+
82
88
  # Run all handlers within the same reactor context
83
- ctx1 = ZM::Reactor.new(:A).run do |context|
89
+ ctx1 = ZM::Reactor.new(configuration).run do |context|
84
90
  @pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
85
91
  context.pub_socket @pub1_handler
86
92
 
@@ -105,7 +111,7 @@ end
105
111
 
106
112
  # Or, run each handler in separate contexts each with its
107
113
  # own thread.
108
- ctx2 = ZM::Reactor.new(:B).run do |context|
114
+ ctx2 = ZM::Reactor.new.run do |context|
109
115
  # @pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
110
116
  # context.pub_socket @pub1_handler
111
117
 
@@ -128,7 +134,7 @@ ctx2 = ZM::Reactor.new(:B).run do |context|
128
134
  # context.sub_socket @sub5_handler
129
135
  end
130
136
 
131
- ctx3 = ZM::Reactor.new(:C).run do |context|
137
+ ctx3 = ZM::Reactor.new.run do |context|
132
138
  # @pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
133
139
  # context.pub_socket @pub1_handler
134
140
  #
@@ -151,7 +157,7 @@ ctx3 = ZM::Reactor.new(:C).run do |context|
151
157
  # context.sub_socket @sub5_handler
152
158
  end
153
159
 
154
- ctx4 = ZM::Reactor.new(:D).run do |context|
160
+ ctx4 = ZM::Reactor.new.run do |context|
155
161
  # @pub1_handler = PublisherHandler.new context, 5555, ['futures.us.es.m.10', 'futures.us.es.u.10']
156
162
  # context.pub_socket @pub1_handler
157
163
  #
@@ -78,17 +78,22 @@ sleep_time = 10
78
78
  # Run the forwarder device in a separate context. *Could* be run from
79
79
  # the same context as the publishers and subscribers too.
80
80
  #
81
- ctx1 = ZM::Reactor.new(:A).run do |context|
81
+ ctx1 = ZM::Reactor.new.run do |context|
82
82
  incoming = ZM::Address.new '127.0.0.1', 5555, :tcp
83
83
  outgoing = "tcp://127.0.0.1:5556"
84
+
85
+ config = ZM::Device::Configuration.new
86
+ config.reactor = context
87
+ config.incoming_endpoint = incoming
88
+ config.outgoing_endpoint = outgoing
84
89
 
85
- forwarder = ZM::Device::Forwarder.new context, incoming, outgoing
90
+ forwarder = ZM::Device::Forwarder.new(config)
86
91
  puts "forwarder started"
87
92
  end
88
93
 
89
94
  # Or, run each handler in separate contexts each with its
90
95
  # own thread.
91
- ctx2 = ZM::Reactor.new(:B).run do |context|
96
+ ctx2 = ZM::Reactor.new.run do |context|
92
97
  # start the publishers and subscribers after a 1 sec delay; give time
93
98
  # to the forwarder device to start up and get ready
94
99
  context.oneshot_timer(1000) do
@@ -75,7 +75,7 @@ end
75
75
 
76
76
 
77
77
  # Run both handlers within the same reactor context
78
- ctx1 = ZM::Reactor.new(:pong).run do |context|
78
+ ctx1 = ZM::Reactor.new.run do |context|
79
79
  @pong_handler = PongHandler.new context
80
80
  context.pair_socket @pong_handler
81
81
 
@@ -87,7 +87,7 @@ end
87
87
 
88
88
  # Or, run each handler in separate contexts each with its
89
89
  # own thread.
90
- ctx2 = ZM::Reactor.new(:ping).run do |context|
90
+ ctx2 = ZM::Reactor.new.run do |context|
91
91
  @ping_handler = PingHandler.new context
92
92
  context.pair_socket @ping_handler
93
93
  end
@@ -0,0 +1,129 @@
1
+
2
+
3
+ module ZMQMachine
4
+ module ConfigClassMaker
5
+ module MethodMaker
6
+
7
+ # Helper to create some accessor methods. We have a standard one where it returns
8
+ # the value with no arg, or sets the value with an arg. Then we create a second
9
+ # method with the explicit '=' on it to allow directly setting the value.
10
+ #
11
+ # This let's us do nice things like eval a block and set the instance vars.
12
+ # e.g.
13
+ # SomeClass.new do
14
+ # field1 'set to this string'
15
+ # end
16
+ #
17
+ # or
18
+ #
19
+ # some_class = SomeClass.new
20
+ # some_class.field1 = 'set to this string'
21
+ # puts some_class.field1 # => set to this string
22
+ #
23
+ # or
24
+ #
25
+ # some_class = SomeClass.new
26
+ # some_class.field1('set to this string')
27
+ # puts some_class.field1 # => set to this string
28
+ #
29
+ def self.create_accessors(mod, fields)
30
+ fields.each do |field_name|
31
+ code = <<-code
32
+ def #{ field_name } (value = nil)
33
+ if value
34
+ @#{field_name} = value
35
+ else
36
+ @#{field_name}
37
+ end
38
+ end
39
+
40
+ def #{ field_name }=(value)
41
+ @#{field_name} = value
42
+ end
43
+ code
44
+
45
+ mod.class_eval code
46
+ end
47
+ end
48
+
49
+ end # module MethodMaker
50
+
51
+ # Dynamically generates Configuration classes. It can also create subclasses.
52
+ # Allows us to easily build subclasses of Configuration within the library. This
53
+ # functionality is *also* provided for users of this library to create their
54
+ # own Configuration classes when writing client/server programs. All of the
55
+ # inheritance is taken care of.
56
+ #
57
+ # +klass_name+ - should usually be 'Configuration'
58
+ # +fields+ - an array of strings corresponding to the accessor names
59
+ # +parent+ - the parent of this subclass (Object at the top level)
60
+ # +mod+ - the module under which this subclass should be placed
61
+ #
62
+ # module ZMQMachine
63
+ # ZMQMachine::ConfigClassMaker.create_class('Configuration', %w( one two three), Object, ZMQMachine)
64
+ # end
65
+ #
66
+ # OR for a subclass
67
+ #
68
+ # module ZMQMachine
69
+ # module Server
70
+ # ZMQMachine::ConfigClassMaker.create_class('Configuration', %w( one two three), ZMQMachine::Configuration, ZMQMachine::Server)
71
+ # end
72
+ # end
73
+ #
74
+ def self.create_class(klass_name, fields, parent, mod)
75
+ # the "here doc" usage here confuses the syntax beautifier so the indentation
76
+ # is wrong
77
+ klass = <<-KLASS
78
+ #{klass_name} = Class.new(#{parent}) do
79
+
80
+ Fields = #{fields.inspect}
81
+
82
+ # Creates a Configuration object from another object that conforms
83
+ # to the Configuration protocol.
84
+ #
85
+ def self.create_from(other_config)
86
+ config = new
87
+ Fields.each do |name|
88
+ config.send(name, other_config.send(name.to_sym)) if config.respond_to?(name.to_sym)
89
+ end
90
+ config
91
+ end
92
+
93
+ def initialize(&blk)
94
+ instance_eval(&blk) if block_given?
95
+ end
96
+
97
+ ZMQMachine::ConfigClassMaker::MethodMaker.create_accessors(self, Fields)
98
+ end
99
+ KLASS
100
+
101
+ mod.module_eval(klass)
102
+ end
103
+ end
104
+ end
105
+
106
+ module ZMQMachine
107
+
108
+ ZMQMachine::ConfigClassMaker.create_class('Configuration', %w( name poll_interval context log_endpoint exception_handler ), Object, ZMQMachine)
109
+ end # ZMQMachine
110
+
111
+
112
+
113
+ # TEST
114
+ if $0 == __FILE__
115
+ ZMQMachine::ConfigClassMaker.create_class('Configuration', %w( name context log_endpoint exception_handler ), Object, ZMQMachine)
116
+
117
+ p (ZMQMachine::Configuration.methods - Object.methods).sort
118
+ puts "\n\n"
119
+ p (ZMQMachine::Configuration.new.methods - Object.new.methods).sort
120
+
121
+ module ZMQMachine
122
+ module Server
123
+ ZMQMachine::ConfigClassMaker.create_class('Configuration', %w( bind connect endpoint ), ZMQMachine::Configuration, ZMQMachine::Server)
124
+ end
125
+ end
126
+
127
+ puts "\n\n"
128
+ p (ZMQMachine::Server::Configuration.new.methods - Object.new.methods).sort
129
+ end
@@ -0,0 +1,4 @@
1
+
2
+ %w( configuration forwarder queue ).each do |rb_file|
3
+ require File.join(File.dirname(__FILE__), 'device', rb_file)
4
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module ZMQMachine
3
+ module Device
4
+ ZMQMachine::ConfigClassMaker.create_class('Configuration',
5
+ %w( reactor incoming_endpoint outgoing_endpoint topic hwm linger verbose ),
6
+ ZMQMachine::Configuration,
7
+ ZMQMachine::Device)
8
+
9
+ class Configuration
10
+
11
+ def initialize(&blk)
12
+ instance_eval(&blk) if block_given?
13
+
14
+ # set defaults
15
+ self.verbose ||= false
16
+ self.hwm ||= 1
17
+ self.linger ||= 0
18
+ self.topic ||= ''
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
@@ -35,10 +35,8 @@
35
35
  #
36
36
 
37
37
  module ZMQMachine
38
-
39
38
  module Device
40
39
 
41
-
42
40
  # Used in conjunction with PUB/SUB sockets to allow multiple publishers to
43
41
  # all publish to the same "bus."
44
42
  #
@@ -51,108 +49,112 @@ module ZMQMachine
51
49
  #
52
50
  # # the queue creates sockets and binds to both given addresses; all messages get
53
51
  # # republished from +incoming+ to +outgoing+
54
- # forwarder = ZM::Device::Forwarder.new reactor, "tcp://192.168.0.100:5050", "tcp://192.168.0.100:5051"
52
+ # config = ZM::Device::Configuration.new
53
+ # config.reactor = reactor
54
+ # config.incoming_endpoint = "tcp://192.168.0.100:5050"
55
+ # config.outgoing_endpoint = "tcp://192.168.0.100:5051"
56
+ # config.verbose = false
57
+ # config.linger = 10 # ms
58
+ # config.hwm = 0
59
+ # config.topic = '' # get everything
60
+ # forwarder = ZM::Device::Forwarder.new(config)
55
61
  #
56
62
  # # the +pub_handler+ internally calls "connect" to the incoming address given above
57
- # pub1 = reactor.pub_socket pub_handler
58
- # pub2 = reactor.pub_socket pub_handler
63
+ # pub1 = reactor.pub_socket(pub_handler)
64
+ # pub2 = reactor.pub_socket(pub_handler)
59
65
  #
60
66
  # # the +sub_handler+ internally calls "connect" to the outgoing address given above
61
- # subscriber = reactor.sub_socket sub_handler
67
+ # subscriber = reactor.sub_socket(sub_handler)
62
68
  #
63
69
  class Forwarder
64
70
 
65
71
  class Handler
66
72
  attr_accessor :socket_out
67
73
 
68
- def initialize reactor, address, opts = {}
69
- @reactor = reactor
74
+ def initialize(config, address)
75
+ @reactor = config.reactor
70
76
  @address = address
71
- @verbose = opts[:verbose] || false
72
- @opts = opts
77
+ @verbose = config.verbose
78
+ @config = config
73
79
 
74
80
  @messages = []
75
81
  end
76
82
 
77
- def on_attach socket
78
- set_options socket
79
- rc = socket.bind @address
83
+ def on_attach(socket)
84
+ set_options(socket)
85
+ rc = socket.bind(@address)
80
86
  error_check(rc)
81
- #FIXME: error handling!
87
+
82
88
  error_check(socket.subscribe_all) if :sub == socket.kind
83
89
  end
84
90
 
85
- def on_writable socket
86
- @reactor.deregister_writable socket
91
+ def on_writable(socket)
92
+ @reactor.deregister_writable(socket)
87
93
  end
88
94
 
89
- def on_readable socket, messages
95
+ def on_readable(socket, messages)
90
96
  messages.each { |msg| @reactor.log(:device, "[fwd] [#{msg.copy_out_string}]") } if @verbose
91
97
 
92
98
  if @socket_out
93
- rc = socket_out.send_messages messages
99
+ rc = socket_out.send_messages(messages)
94
100
  error_check(rc)
95
101
  messages.each { |message| message.close }
96
102
  end
97
103
  end
98
104
 
99
- def on_readable_error socket, return_code
100
- STDERR.puts "#{self.class}#on_readable_error, rc [#{return_code}], errno [#{ZMQ::Util.errno}], descr [#{ZMQ::Util.error_string}]"
105
+ def on_readable_error(socket, return_code)
106
+ @reactor.log(:error, "#{self.class}#on_readable_error, rc [#{return_code}], errno [#{ZMQ::Util.errno}], descr [#{ZMQ::Util.error_string}]")
101
107
  end
102
108
 
103
- if LibZMQ.version2?
109
+ if ZMQ::LibZMQ.version2?
104
110
 
105
- def set_options socket
106
- error_check(socket.raw_socket.setsockopt(ZMQ::HWM, (@opts[:hwm] || 1)))
107
- error_check(socket.raw_socket.setsockopt(ZMQ::LINGER, (@opts[:linger] || 0)))
111
+ def set_options(socket)
112
+ error_check(socket.raw_socket.setsockopt(ZMQ::HWM, @config.hwm))
113
+ error_check(socket.raw_socket.setsockopt(ZMQ::LINGER, @config.linger))
108
114
  end
109
115
 
110
- elsif LibZMQ.version3?
116
+ elsif ZMQ::LibZMQ.version3?
111
117
 
112
- def set_options socket
113
- error_check(socket.raw_socket.setsockopt(ZMQ::SNDHWM, (@opts[:hwm] || 1)))
114
- error_check(socket.raw_socket.setsockopt(ZMQ::RCVHWM, (@opts[:hwm] || 1)))
115
- error_check(socket.raw_socket.setsockopt(ZMQ::LINGER, (@opts[:linger] || 0)))
118
+ def set_options(socket)
119
+ error_check(socket.raw_socket.setsockopt(ZMQ::SNDHWM, @config.hwm))
120
+ error_check(socket.raw_socket.setsockopt(ZMQ::RCVHWM, @config.hwm))
121
+ error_check(socket.raw_socket.setsockopt(ZMQ::LINGER, @config.linger))
116
122
  end
117
123
 
118
124
  end
119
125
 
120
- def error_check rc
126
+ def error_check(rc)
121
127
  if ZMQ::Util.resultcode_ok?(rc)
122
128
  false
123
129
  else
124
- STDERR.puts "Operation failed, errno [#{ZMQ::Util.errno}] description [#{ZMQ::Util.error_string}]"
125
- caller(1).each { |callstack| STDERR.puts(callstack) }
130
+ @reactor.log(:error, "Operation failed, errno [#{ZMQ::Util.errno}] description [#{ZMQ::Util.error_string}]")
131
+ caller(1).each { |callstack| @reactor.log(:callstack, callstack) }
126
132
  true
127
133
  end
128
134
  end
129
-
130
135
  end # class Handler
131
136
 
132
137
 
133
- # Takes either a properly formatted string that can be converted into a ZM::Address
134
- # or takes a ZM::Address directly.
135
- #
136
138
  # Forwards all messages received by the +incoming+ address to the +outgoing+ address.
137
139
  #
138
- def initialize reactor, incoming, outgoing, opts = {:verbose => false}
139
- incoming = Address.from_string incoming if incoming.kind_of? String
140
- outgoing = Address.from_string outgoing if outgoing.kind_of? String
140
+ def initialize(config)
141
+ @reactor = config.reactor
142
+ incoming = Address.from_string(config.incoming_endpoint.to_s)
143
+ outgoing = Address.from_string(config.outgoing_endpoint.to_s)
141
144
 
142
145
  # setup the handlers for processing messages
143
- @handler_in = Handler.new reactor, incoming, opts
144
- @handler_out = Handler.new reactor, outgoing, opts
146
+ @handler_in = Handler.new(config, incoming)
147
+ @handler_out = Handler.new(config, outgoing)
145
148
 
146
149
  # create each socket and pass in the appropriate handler
147
- @incoming = reactor.sub_socket @handler_in
148
- @outgoing = reactor.pub_socket @handler_out
150
+ @incoming_sock = @reactor.sub_socket(@handler_in)
151
+ @outgoing_sock = @reactor.pub_socket(@handler_out)
149
152
 
150
153
  # set each handler's outgoing socket
151
- @handler_in.socket_out = @outgoing
152
- @handler_out.socket_out = @incoming
154
+ @handler_in.socket_out = @outgoing_sock
155
+ @handler_out.socket_out = @incoming_sock
153
156
  end
154
157
  end # class Forwarder
155
158
 
156
159
  end # module Device
157
-
158
160
  end # module ZMQMachine