em-zmq-tp10 0.1.7

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.
@@ -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