em-zmq-tp10 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.swp
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in em-zmq-tp10.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Sokolov Yura 'funny-falcon'
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # EventMachine ZMTP1.0 protocol (ZMQ2.x protocol)
2
+
3
+ It is implementation of ZMTP 1.0 - ZMQ 2.x transport protocol using facilites provided by EventMachine.
4
+ There are implementations of ZMQ socket types which try to be similar to original, but not too hard. Moreover, you may create your own behaviour.
5
+ Library is tested against native ZMQ using ffi-rzmq.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'em-zmq-tp10'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install em-zmq-tp10
20
+
21
+ ## Usage
22
+
23
+ Library provides callback oriented classes which tries to emulate behaviour of standard ZMQ classes, but already integrated with EventMachine eventloop.
24
+
25
+ Main difference in behaviour is in highwatermark handling and balancing:
26
+ For DEALER and REQ zmq provides roundrobin load balancing until HighWaterMark reached, then send operation blocks or returns error `EAGAIN`.
27
+ This implementation do roundrobin until fixed internal highwatermark (2048bytes) reached, and then pushes to common queue until userdefined watermark is reached.
28
+
29
+ This internal per connect buffer is handled by EventMachine itself, and there is no precise control over it, so that, if peer is disconnected, all pushed messages to this buffer are lost. So that, compared to raw ZMQ, you loose not only content of OS's internal socket buffer, but EventMachine buffer as well :(
30
+
31
+ But since ZMQ is never pretended on durability, it is not big issue (for me).
32
+
33
+ There is two strategy of highwatermark handling: `drop_first` and `drop_last`.
34
+ `drop_last` - is ignoring any try to send message if queue is full - this is default strategy for ZMQ if you use nonblocking sending.
35
+ `drop_first` - dropping earliest message in a queue, so that newly inserted message will have more chanches to be sent. You can react on such dropping by overriding `cancel_message` (or `cancel_request` for Req). I like this strategy more cause old request tends to be less usefull, but `drop_last` is still default for "compatibility".
36
+
37
+ There is also simplified classes without internal queue (`PreDealer`, `PreReq`, `PreRouter`, `PreRep`, `PrePub`), so that you can implement your own strategy of queueing.
38
+
39
+ And you could do any crazy thing using base `EM::Protocols::Zmq2::Socket` class
40
+
41
+
42
+ ### Socket
43
+
44
+ Base class. It provides #connect and #bind methods for establishing endpoints.
45
+ This method could be called outside EM event loop (even before EM.run called), cause they use EM.schedule.
46
+ TCP and IPC endpoints are supported and fully interoperable with native ZMQ.
47
+ INPROC supported as well, but you should treat them as connections inside EventMachine's context, so that you could not connect to native ZMQ inproc endpoints.
48
+
49
+ ### Dealer
50
+
51
+ class MyPreDealer < EM::Protocols::Zmq2::PreDealer
52
+ def receive_message(message)
53
+ puts "Message received: #{message.inspect}"
54
+ end
55
+ end
56
+ dealer = MyPreDealer.new
57
+ dealer.connect('tcp://127.0.0.1:8000')
58
+ dealer.bind('unix://dealer.sock')
59
+ EM.schedule {
60
+ if !dealer.send_message(['asdf','fdas'])
61
+ puts "Could not send message (no free peers)"
62
+ end
63
+ }
64
+
65
+ class MyDealer < EM::Protocols::Zmq2::Dealer
66
+ def receive_message(message)
67
+ puts "Message received: #{message.inspect}"
68
+ end
69
+ end
70
+ dealer = MyDealer.new(hwm: 1000, hwm_strategy: :drop_last)
71
+ dealer.connect('tcp://127.0.0.1:8000')
72
+ EM.schedule {
73
+ if !dealer.send_message(['asdf','fdas'])
74
+ puts "No free peers and outgoing queue is full"
75
+ end
76
+ }
77
+
78
+ dealer = EM::Protocols::Zmq2::DealerCb.new do |message|
79
+ puts "Receive message #{message.inspect}"
80
+ end
81
+ dealer.connect('ipc://rep')
82
+ EM.schedule {
83
+ dealer.send_message(['hello','world'])
84
+ }
85
+
86
+ ### Req
87
+
88
+ class MyPreReq < EM::Protocols::Zmq2::PreReq
89
+ def receive_reply(message, data, request_id)
90
+ puts "Received message #{message} and stored data #{data}
91
+ end
92
+ end
93
+ req = MyPreReq.new
94
+ req.bind(...)
95
+ req.connect(...)
96
+ if request_id = req.send_request(['this is', 'message'], 'saved_data')
97
+ puts "Message sent"
98
+ else
99
+ puts "there is no free peer"
100
+ end
101
+
102
+ class MyReq < EM::Protocols::Zmq2::PreReq
103
+ def receive_reply(message, data, request_id)
104
+ puts "Received message #{message} and stored data #{data}
105
+ end
106
+ end
107
+ req = MyReq.new
108
+ req.bind(...)
109
+ req.connect(...)
110
+ if request_id = req.send_request(['hi'], 'ho')
111
+ puts "Message sent"
112
+ end
113
+
114
+ req = EM::Protocols::Zmq2::ReqCb.new
115
+ req.bind('ipc://req')
116
+ timer = nil
117
+ request_id = req.send_request(['hello', 'world']) do |message|
118
+ EM.cancel_timer(timer)
119
+ puts "Message #{message}"
120
+ end
121
+ if request_id
122
+ timer = EM.add_timer(1) {
123
+ req.cancel_request(request_id)
124
+ }
125
+ end
126
+
127
+ req = EM::Protocols::Zmq2::ReqDefer.new
128
+ req.bind('ipc://req')
129
+ data = {hi: 'ho'}
130
+ deferable = req.send_request(['hello', 'world'], data) do |reply, data|
131
+ puts "Reply received #{reply} #{data}"
132
+ end
133
+ deferable.timeout 1
134
+ deferable.errback do
135
+ puts "Message canceled"
136
+ end
137
+ deferable.callback do |reply, data|
138
+ puts "Another callback #{reply} #{data}"
139
+ end
140
+
141
+ ### Router
142
+
143
+ Router stores peer identity in a message, as ZMQ router do.
144
+ And it sends message to a peer, which idenitity equals to first message string.
145
+ `PreRouter` does no any queue caching, `Router` saves message in queue per peer, controlled by highwatermark strategy.
146
+
147
+ class MyPreRouter < EM::Protocols::Zmq2::PreRouter
148
+ def receive_message(message)
149
+ puts "Received message #{message} (and it contains envelope)"
150
+ end
151
+ end
152
+ router = MyPreRouter.new
153
+ router.bind(...)
154
+ router.send_message(message)
155
+
156
+ class MyRouter < EM::Protocols::Zmq2::Router
157
+ def receive_message(message)
158
+ puts "Received message #{message}"
159
+ message[-1] = 'reply'
160
+ send_message(message)
161
+ end
162
+ end
163
+ router = MyPreRouter.new(hwm: 1000, hwm_strategy: :drop_first)
164
+ router.bind(...)
165
+ router.send_message(message)
166
+
167
+ ### Rep
168
+
169
+ REP differs from Router mainly in methods signature.
170
+
171
+ class EchoBangPreRep < EM::Protocols::Zmq2::PreRep
172
+ def receive_request(message, envelope)
173
+ message << "!"
174
+ if send_reply(message, envelope)
175
+ puts "reply sent successfuly"
176
+ end
177
+ end
178
+ end
179
+ rep = EchoBangPreRep.new
180
+ rep.bind('ipc://rep')
181
+
182
+ class EchoBangRep < EM::Protocols::Zmq2::Rep
183
+ def receive_request(message, envelope)
184
+ message << "!"
185
+ if send_reply(message, envelope)
186
+ puts "reply sent successfuly"
187
+ end
188
+ end
189
+ end
190
+ rep = EchoBangRep.new
191
+ rep.bind('ipc://rep')
192
+
193
+ ### Sub
194
+
195
+ Unless ZMQ sub, this `Sub` accepts not only strings, but also RegExps and procs for subscribing.
196
+ Note that as in ZMQ 2.x filtering occurs on Sub side.
197
+
198
+ Since subscriptions could be defined with callback passed to `:subscribe` option, `subscribe` or `subscribe_many` methods, you could use this class without overloading.
199
+
200
+ class MySub < EM::Protocols::Zmq2::Sub
201
+ def receive_message(message)
202
+ puts "default handler"
203
+ end
204
+ end
205
+ sub = MySub.new(subscribe: ['this', 'that'])
206
+ sub.subscribe /^callback/i, do |message|
207
+ puts "Callback subscribe #{message}"
208
+ end
209
+ sub.subscribe_many(
210
+ proc{|s| s.end_with?("END")} => proc{|message| puts "TILL END #{message}"},
211
+ '' => nil # also to default
212
+ )
213
+
214
+ ### Pub
215
+
216
+ `PrePub` sends messages only to connected and not busy peers. `send_message` returns true, if there is at least one peer with short EventMachine's outgoing queue, to which message is scheduled.
217
+
218
+ `Pub` tries to queue messages for all connected and for disconnected peers with explicit identity set.
219
+
220
+ Since there is no incoming data, there is no need to overload methods.
221
+
222
+ pub = EM::Protocols::Zmq2::PrePub.new
223
+ pub.bind(...)
224
+ pub.send_message(['hi', 'you'])
225
+
226
+ pub = EM::Protocols::Zmq2::Pub.new
227
+ pub.bind(...)
228
+ pub.send_message(['hi', 'you'])
229
+
230
+
231
+ ## Contributing
232
+
233
+ 1. Fork it
234
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
235
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
236
+ 4. Push to the branch (`git push origin my-new-feature`)
237
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs.push "lib"
7
+ t.test_files = FileList['tests/test_*.rb']
8
+ t.verbose = true
9
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/em/protocols/zmq2/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Sokolov Yura 'funny-falcon'"]
6
+ gem.email = ["funny.falcon@gmail.com"]
7
+ gem.description = %q{Implementation of ZMQ2.x transport protocol - ZMTP1.0}
8
+ gem.summary = %q{
9
+ Implementation of ZMQ2.1 transport protocol and main socket types using EventMachine
10
+ for managing TCP/UNIX connections, doing nonblocking writes and calling callback on
11
+ incoming messages.}
12
+ gem.homepage = "https://github.com/funny-falcon/em-zmq-tp10"
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "em-zmq-tp10"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = EventMachine::Protocols::Zmq2::VERSION
20
+ gem.add_runtime_dependency 'eventmachine'
21
+ gem.add_development_dependency 'ffi-rzmq'
22
+ gem.required_ruby_version = '>= 1.9.2'
23
+ end
@@ -0,0 +1 @@
1
+ require "em/protocols/zmq2"
@@ -0,0 +1,25 @@
1
+ module EventMachine
2
+ module Protocols
3
+ module Zmq2
4
+ EMPTY = ''.freeze
5
+ SMALL_TIMEOUT = 0.03
6
+ HWM_INFINITY = 2**20
7
+ autoload :PreDealer, 'em/protocols/zmq2/dealer.rb'
8
+ autoload :Dealer, 'em/protocols/zmq2/dealer.rb'
9
+ autoload :DealerCb, 'em/protocols/zmq2/dealer.rb'
10
+ autoload :PreReq, 'em/protocols/zmq2/req.rb'
11
+ autoload :Req, 'em/protocols/zmq2/req.rb'
12
+ autoload :ReqCb, 'em/protocols/zmq2/req.rb'
13
+ autoload :ReqDefer, 'em/protocols/zmq2/req.rb'
14
+ autoload :PreRouter, 'em/protocols/zmq2/router.rb'
15
+ autoload :Router, 'em/protocols/zmq2/router.rb'
16
+ autoload :PreRep, 'em/protocols/zmq2/rep.rb'
17
+ autoload :Rep, 'em/protocols/zmq2/rep.rb'
18
+ autoload :Sub, 'em/protocols/zmq2/pub_sub.rb'
19
+ autoload :PrePub, 'em/protocols/zmq2/pub_sub.rb'
20
+ autoload :Pub, 'em/protocols/zmq2/pub_sub.rb'
21
+ end
22
+ end
23
+ end
24
+ require 'em/protocols/zmq2/socket_connection'
25
+ require 'em/protocols/zmq2/socket'
@@ -0,0 +1,37 @@
1
+ module EventMachine
2
+ module Protocols
3
+ module Zmq2
4
+ module ConnectionMixin
5
+ def sent_data
6
+ @socket.peer_free(@peer_identity, self) if not_too_busy?
7
+ end
8
+
9
+ def not_too_busy?
10
+ free = _not_too_busy?
11
+ self.notify_when_free = !free
12
+ free
13
+ end
14
+
15
+ def post_init
16
+ send_strings @socket.identity
17
+ end
18
+
19
+ def receive_strings(message)
20
+ unless @peer_identity
21
+ peer_identity = message.first
22
+ @peer_identity = @socket.register_peer(peer_identity, self)
23
+ else
24
+ @socket.receive_message_and_peer message, @peer_identity
25
+ end
26
+ end
27
+
28
+ def unbind
29
+ if @peer_identity
30
+ @socket.unregister_peer(@peer_identity)
31
+ end
32
+ @socket.not_connected(self)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,133 @@
1
+ require 'em/protocols/zmq2/socket'
2
+ module EventMachine
3
+ module Protocols
4
+ module Zmq2
5
+ # ZMQ socket which acts like a Dealer, but without any message queueing
6
+ # (except for EventMachine write buffers). So that, it is up to you
7
+ # to react when message could not be send
8
+ #
9
+ # @example
10
+ # class MyDealer < EM::Protocols::Zmq2::PreDealer
11
+ # def receive_message(message)
12
+ # puts "Message received: #{message.inspect}"
13
+ # end
14
+ # end
15
+ # dealer = MyDealer.new
16
+ # dealer.connect('tcp://127.0.0.1:8000')
17
+ # if !dealer.send_message(['asdf','fdas'])
18
+ # puts "Could not send message (no free peers)"
19
+ # end
20
+ class PreDealer < Socket
21
+ # @private
22
+ def choose_peer(even_if_busy = false)
23
+ peers = even_if_busy ? @peers : @free_peers
24
+ i = peers.size
25
+ while i > 0
26
+ ident, connect = peers.shift
27
+ if even_if_busy || connect.not_too_busy?
28
+ peers[ident] = connect # use the fact, that hash is ordered in Ruby 1.9
29
+ return connect unless connect.error?
30
+ end
31
+ i -= 1
32
+ end
33
+ end
34
+
35
+ # overrides default callback to call +#receive_message+
36
+ def receive_message_and_peer(message, peer_identity) # :nodoc:
37
+ receive_message(message)
38
+ end
39
+
40
+ # override to have desired reaction on message
41
+ def receive_message(message)
42
+ raise NoMethodError
43
+ end
44
+
45
+ # tries to send message.
46
+ # @param [Array] message - message to be sent (Array of Strings or String)
47
+ # @param [Boolean] even_if_busy - ignore busyness of connections
48
+ # @return [Boolean] whether message were sent
49
+ def send_message(message, even_if_busy = false)
50
+ if connect = choose_peer(even_if_busy)
51
+ connect.send_strings(message)
52
+ true
53
+ end
54
+ end
55
+ end
56
+
57
+ # ZMQ socket which tries to be a lot like a Dealer. It stores messages into outgoing
58
+ # queue, it tries to balance on socket busyness (which is slightely more, than ZMQ do).
59
+ # The only visible change from PreDealer is less frequent +send_message+ false return.
60
+ #
61
+ # @example
62
+ # class MyDealer < EM::Protocols::Zmq2::Dealer
63
+ # def receive_message(message)
64
+ # puts "Message received: #{message.inspect}"
65
+ # end
66
+ # end
67
+ # dealer = MyDealer.new
68
+ # dealer.connect('tcp://127.0.0.1:8000')
69
+ # if !dealer.send_message(['asdf','fdas'])
70
+ # puts "No free peers and outgoing queue is full"
71
+ # end
72
+ class Dealer < PreDealer
73
+ # :stopdoc:
74
+ def initialize(opts = {})
75
+ super
76
+ @write_queue = []
77
+ end
78
+
79
+ alias raw_send_message send_message
80
+ def flush_queue(even_if_busy = false)
81
+ until @write_queue.empty?
82
+ return false unless raw_send_message(@write_queue.first, even_if_busy)
83
+ @write_queue.shift
84
+ end
85
+ true
86
+ end
87
+
88
+ def send_message(message)
89
+ flush_queue && super(message) || push_to_queue(@write_queue, message)
90
+ end
91
+
92
+ def peer_free(peer_identity, connection)
93
+ super
94
+ flush_queue
95
+ end
96
+
97
+ private
98
+ def flush_all_queue
99
+ flush_queue(true)
100
+ end
101
+
102
+ def react_on_hwm_decrease
103
+ push_to_queue(@write_queue)
104
+ end
105
+ # :startdoc:
106
+ end
107
+
108
+ # Convinient Dealer class which accepts callback in constructor, which will be called
109
+ # on every incoming message (instead of #receive_message)
110
+ #
111
+ # @example
112
+ # dealer = EM::Protocols::Zmq2::DealerCb.new do |message|
113
+ # puts "Receive message #{message.inspect}"
114
+ # end
115
+ # dealer.connect('ipc://rep')
116
+ # dealer.send_message(['hello','world'])
117
+ class DealerCb < Dealer
118
+ # Accepts callback as second parameter or block
119
+ # :callsec:
120
+ # new(opts) do |message| end
121
+ # new(opts, proc{|message| })
122
+ def initialize(opts = {}, cb = nil, &block)
123
+ super opts
124
+ @read_callback = cb || block
125
+ end
126
+
127
+ def receive_message(message) # :nodoc:
128
+ @read_callback.call message
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end