em-zmq-tp10 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ require 'em/protocols/zmq2/socket'
2
+ require 'em/protocols/zmq2/queue_per_peer'
3
+ module EventMachine
4
+ module Protocols
5
+ module Zmq2
6
+ # ZMQ socket which acts like Router but without outgoing message queueing.
7
+ # It counts first message string as peer identity when sending message and
8
+ # prepends socket identity to message on receiving.
9
+ class PreRouter < Socket
10
+ def receive_message_and_peer(message, peer_identity) # :nodoc:
11
+ message.unshift(peer_identity)
12
+ receive_message(message)
13
+ end
14
+
15
+ def receive_message(message)
16
+ raise NoMethodError
17
+ end
18
+
19
+ def send_message(message, even_if_busy = false)
20
+ if connect = choose_peer(message.first, even_if_busy)
21
+ connect.send_strings(message[1..-1])
22
+ true
23
+ end
24
+ end
25
+
26
+ private
27
+ # by default chooses peer by peer_identity, but you could wary it
28
+ def choose_peer(peer_identity, even_if_busy = false)
29
+ if (connect = @peers[peer_identity]) && !connect.error? &&
30
+ (even_if_busy || connect.not_too_busy?)
31
+ connect
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ # ZMQ socket which acts like Router.
38
+ # It counts first message string as peer identity when sending message and
39
+ # prepends socket identity to message on receiving.
40
+ class Router < PreRouter
41
+ include QueuePerPeer
42
+ def send_message(message)
43
+ peer_identity = message.first
44
+ unless (queue = @queues[peer_identity])
45
+ if generated_identity?(peer_identity)
46
+ return false
47
+ else
48
+ queue = @queues[peer_identity] = []
49
+ end
50
+ end
51
+ peer = choose_peer(peer_identity)
52
+ if peer && (queue.empty? || flush_queue(queue, peer)) &&
53
+ !peer.error? && peer.not_too_busy?
54
+ peer.send_strings(message[1..-1])
55
+ true
56
+ else
57
+ push_to_queue(queue, message)
58
+ end
59
+ end
60
+
61
+ private
62
+ def send_formed_message(peer, from_queue)
63
+ peer.send_strings(from_queue[1..-1])
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,236 @@
1
+ require 'em/protocols/zmq2/socket_connection'
2
+ require 'em/protocols/zmq2/inproc'
3
+ module EM
4
+ module Protocols
5
+ module Zmq2
6
+ # Base class for all ZMQ sockets.
7
+ # It implements address parsing, binding, connecting and reconnecting
8
+ # For implementing your own kind of socket you should override at least
9
+ # #receive_message, and define method which will choose peer from @peers,
10
+ # and call its peer#send_strings
11
+ class Socket
12
+ attr :identity, :hwm
13
+ attr_accessor :hwm_strategy
14
+ GENERATED = '%GN%'.freeze
15
+
16
+ # Accept options, which are dependend on socket type
17
+ # Common options are:
18
+ # [+:identity+] socket identity
19
+ # [+:hwm+] highwater mark
20
+ # [+:hwm_strategy+] what to do on send_message when :hwm reached, hwm_strategy could be:
21
+ # +:drop_last+ - do not accept message for sending - what Zmq does
22
+ # +:drop_first+ - remove message from queue head and add this message to queue tail
23
+ #
24
+ # this class provides convinient method +#push_to_queue+ for default strategy, but
25
+ # it is up to subclass how to use it.
26
+ #
27
+ # Another note concerning highwatermark: EventMachine does not allow precise control on
28
+ # outgoing data buffer, so that there is a bit more message will be lost, when outgoing
29
+ # peer disconnected.
30
+ def initialize(opts = {})
31
+ @hwm = opts[:hwm] || HWM_INFINITY
32
+ @hwm_strategy = opts[:hwm_strategy] || :drop_last
33
+ @identity = opts[:identity] || EMPTY
34
+ @peers = {}
35
+ @free_peers = {}
36
+ @connections = {}
37
+ @conn_addresses = {}
38
+ @reconnect_timers = {}
39
+ @bindings = []
40
+ @after_writting = nil
41
+ @uniq_identity = '%GN%aaaaaaaaaaaa' # ~ 100 years to overflow
42
+ end
43
+
44
+ # binding to port
45
+ # :call-seq:
46
+ # bind('tcp://host:port') - bind to tcp port
47
+ # bind('ipc://filename') - bind to unix port
48
+ def bind(addr)
49
+ kind, *socket = parse_address(addr)
50
+ EM.schedule {
51
+ @bindings << case kind
52
+ when :tcp, :ipc
53
+ EM.start_server(*socket, SocketConnection, self)
54
+ when :inproc
55
+ InProc.bind(addr, self)
56
+ end
57
+ }
58
+ end
59
+
60
+ # connect to port
61
+ # :call-seq:
62
+ # connect('tcp://host:port') - connect to tcp port
63
+ # connect('ipc://filename') - connect to unix port
64
+ def connect(addr)
65
+ kind, *socket = parse_address(addr)
66
+ EM.schedule lambda{
67
+ @reconnect_timers.delete addr
68
+ unless @conn_addresses[ addr ]
69
+ connection = case kind
70
+ when :tcp
71
+ EM.connect(*socket, SocketConnection, self)
72
+ when :ipc
73
+ begin
74
+ EM.connect_unix_domain(*socket, SocketConnection, self)
75
+ rescue RuntimeError
76
+ timer = EM.add_timer(SMALL_TIMEOUT) do
77
+ connect(addr)
78
+ end
79
+ @reconnect_timers[addr] = timer
80
+ break
81
+ end
82
+ when :inproc
83
+ InProc.connect(addr, self)
84
+ end
85
+ @connections[ connection ] = addr
86
+ @conn_addresses[ addr ] = connection
87
+ end
88
+ }
89
+ end
90
+
91
+ # :stopdoc:
92
+ def not_connected(connection)
93
+ if addr = @connections.delete(connection)
94
+ @conn_addresses.delete addr
95
+ timer = EM.add_timer(SMALL_TIMEOUT) do
96
+ connect(addr)
97
+ end
98
+ @reconnect_timers[addr] = timer
99
+ end
100
+ end
101
+
102
+ def register_peer(peer_identity, connection)
103
+ peer_identity = next_uniq_identity if peer_identity.empty?
104
+ @peers[peer_identity] = connection
105
+ EM.next_tick{ peer_free(peer_identity, connection) }
106
+ peer_identity
107
+ end
108
+
109
+ def unregister_peer(peer_identity)
110
+ @peers.delete peer_identity
111
+ @free_peers.delete peer_identity
112
+ if @peers.empty? && @after_writting.respond_to?(:call)
113
+ @after_writting.call
114
+ end
115
+ end
116
+
117
+ # :startdoc:
118
+
119
+ # close all connections
120
+ # if callback is passed, then it will be called after all messages written to sockets
121
+ def close(cb = nil, &block)
122
+ @connections.clear
123
+ @conn_addresses.clear
124
+ @reconnect_timers.each{|_, timer| EM.cancel_timer(timer)}
125
+ @after_writting = cb || block
126
+ flush_all_queue if @after_writting
127
+ @peers.values.each{|c| c.close_connection(!!@after_writting)}
128
+ @bindings.each{|c|
129
+ unless String === c
130
+ EM.stop_server c
131
+ else
132
+ InProc.unbind(c, self)
133
+ end
134
+ }
135
+ end
136
+
137
+ # override to make sure all messages are sent before socket closed
138
+ def flush_all_queue
139
+ true
140
+ end
141
+
142
+ # stub method for sending message to a socket
143
+ # note that every socket type should define proper behaviour here
144
+ # or/and define another useful, semantic clear methods
145
+ def send_message(message)
146
+ raise NoMethodError
147
+ end
148
+
149
+ # callback method called with underlied connection when
150
+ # some message arrives
151
+ def receive_message_and_peer(message, peer_identity)
152
+ raise NoMethodError
153
+ end
154
+
155
+ # Change hwm
156
+ def hwm=(new_hwm)
157
+ old_hwm, @hwm = @hwm, new_hwm
158
+ react_on_hwm_decrease if old_hwm > @hwm
159
+ @hwm
160
+ end
161
+
162
+ # callback method, called when underlying peer is free for writing in
163
+ # should be used in subclasses for proper reaction on network instability
164
+ def peer_free(peer_identity, peer) # :doc:
165
+ @free_peers[peer_identity] = peer
166
+ end
167
+
168
+ private
169
+ # splits message into envelope and message as defined by ZMQ 2.x
170
+ def split_message(message) # :doc:
171
+ i = message.index(EMPTY)
172
+ [message.slice(0, i), message.slice(i+1, message.size)]
173
+ end
174
+
175
+ # helper method for managing queue concerning @hwm setting
176
+ def push_to_queue(queue, message = nil) # :doc
177
+ if queue.size >= @hwm
178
+ case @hwm_strategy
179
+ when :drop_last
180
+ if queue.size > @hwm
181
+ queue.pop(queue.size - @hwm).each{|message|
182
+ cancel_message(message)
183
+ }
184
+ end
185
+ false
186
+ when :drop_first
187
+ hwm = @hwm - (message ? 1 : 0)
188
+ queue.shift(queue.size - hwm).each{|earlier|
189
+ cancel_message(earlier)
190
+ }
191
+ queue.push(message.dup) if message
192
+ true
193
+ end
194
+ else
195
+ queue.push(message.dup) if message
196
+ true
197
+ end
198
+ end
199
+
200
+ # override to correctly react on hwm decrease
201
+ def react_on_hwm_decrease # :doc:
202
+ true
203
+ end
204
+
205
+ # overried if you should react on dropped requests
206
+ def cancel_message(message) # :doc:
207
+ true
208
+ end
209
+
210
+ def next_uniq_identity
211
+ res = @uniq_identity
212
+ @uniq_identity = res.next
213
+ res
214
+ end
215
+
216
+ def generated_identity?(id)
217
+ id.start_with?(GENERATED)
218
+ end
219
+
220
+ def parse_address(addr)
221
+ case addr
222
+ when %r{tcp://([^:]+):(\d+)}
223
+ [:tcp, $1, $2.to_i]
224
+ when %r{ipc://(.+)}
225
+ [:ipc, $1]
226
+ when %r{inproc://(.+)}
227
+ [:inproc, $1]
228
+ else
229
+ raise 'Not supported ZMQ socket kind'
230
+ end
231
+ end
232
+
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,151 @@
1
+ require 'eventmachine'
2
+ require 'em/protocols/zmq2'
3
+ require 'em/protocols/zmq2/connection'
4
+ module EventMachine
5
+ module Protocols
6
+ module Zmq2
7
+ # Main implementation peace, heart of protocol
8
+ module PackString
9
+ BIG_PACK = 'CNNCa*'.freeze
10
+ SMALL_PACK = 'CCa*'.freeze
11
+ def pack_string(string, more = false)
12
+ bytesize = string.bytesize + 1
13
+ if bytesize <= 254
14
+ [bytesize, more ? 1 : 0, string].pack(SMALL_PACK)
15
+ else
16
+ [255, 0, bytesize, more ? 1 : 0, string].pack(BIG_PACK)
17
+ end
18
+ end
19
+
20
+ def prepare_message(message)
21
+ if String === message
22
+ pack_string(message, false)
23
+ else
24
+ message = Array(message)
25
+ buffer = ''
26
+ i = 0
27
+ last = message.size - 1
28
+ while i < last
29
+ buffer << pack_string(message[i].to_s, true)
30
+ i += 1
31
+ end
32
+ buffer << pack_string(message[last].to_s, false)
33
+ end
34
+ end
35
+ end
36
+
37
+ # Heavy duty worker class
38
+ # Implements ZMTP1.0 - ZMQ2.x transport protocol
39
+ #
40
+ # It calls following callbacks
41
+ #
42
+ # It is not for end user usage
43
+ class SocketConnection < EM::Connection
44
+ include ConnectionMixin
45
+ # :stopdoc:
46
+ def initialize(socket)
47
+ @socket = socket
48
+ @recv_buffer = ''
49
+ @recv_frames = [[]]
50
+ end
51
+
52
+ def unbind(err)
53
+ if @peer_identity
54
+ @socket.unregister_peer(@peer_identity)
55
+ end
56
+ @socket.not_connected(self)
57
+ end
58
+
59
+ # use watching on outbound queue when possible
60
+ # rely on https://github.com/eventmachine/eventmachine/pull/317 if were accepted
61
+ # or on https://github.com/funny-falcon/eventmachine/tree/sent_data
62
+ # use timers otherwise
63
+ def sent_data
64
+ @socket.peer_free(@peer_identity, self) if not_too_busy?
65
+ end
66
+
67
+ if method_defined?(:outbound_data_count)
68
+ def _not_too_busy?
69
+ outbound_data_count < 32
70
+ end
71
+ else
72
+ def _not_too_busy?
73
+ get_outbound_data_size < 2048
74
+ end
75
+ end
76
+
77
+ if method_defined?(:notify_sent_data=)
78
+ alias notify_when_free= notify_sent_data=
79
+ else
80
+ def notify_when_free=(v)
81
+ if v
82
+ @when_free_timer ||= EM.add_timer(SMALL_TIMEOUT) do
83
+ @when_free_timer = nil
84
+ sent_data
85
+ end
86
+ elsif @when_free_timer
87
+ EM.cancel_timer @when_free_timer
88
+ @when_free_timer = nil
89
+ end
90
+ end
91
+ end
92
+
93
+ def not_too_busy?
94
+ free = _not_too_busy?
95
+ self.notify_when_free = !free
96
+ free
97
+ end
98
+
99
+ def receive_data(data)
100
+ parse_frames(data)
101
+ while message = pop_message
102
+ receive_strings(message)
103
+ end
104
+ end
105
+
106
+ def pop_message
107
+ if @recv_frames.size > 1
108
+ @recv_frames.shift
109
+ end
110
+ end
111
+
112
+ FF = "\xff".freeze
113
+ BIG_UNPACK = 'CNNC'.freeze
114
+ SMALL_UNPACK = 'CC'.freeze
115
+ def parse_frames(data)
116
+ unless @recv_buffer.empty?
117
+ data = @recv_buffer << data
118
+ end
119
+ while data.bytesize >= 2
120
+ if data.start_with?(FF)
121
+ break if data.bytesize < 10
122
+ _, _, length, more = data.unpack(BIG_UNPACK)
123
+ start_at = 10
124
+ else
125
+ length = data.getbyte(0)
126
+ more = data.getbyte(1)
127
+ start_at = 2
128
+ end
129
+ length -= 1
130
+ break if data.bytesize < start_at + length
131
+ str = data.byteslice(start_at, length)
132
+ data[0, start_at + length] = EMPTY
133
+ @recv_frames.last << str
134
+ @recv_frames << [] if more & 1 == 0
135
+ end
136
+ @recv_buffer = data
137
+ end
138
+
139
+ include PackString
140
+
141
+ def send_strings(strings)
142
+ send_data prepare_message(strings)
143
+ end
144
+
145
+ def send_strings_or_prepared(strings, prepared)
146
+ send_data prepared
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end