zmachine 0.1.1 → 0.1.3
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.
- checksums.yaml +15 -0
- data/lib/zmachine/acceptor.rb +10 -2
- data/lib/zmachine/channel.rb +31 -2
- data/lib/zmachine/connection.rb +14 -1
- data/lib/zmachine/hashed_wheel.rb +77 -0
- data/lib/zmachine/reactor.rb +189 -30
- data/lib/zmachine/tcp_channel.rb +15 -3
- data/lib/zmachine/zmq_channel.rb +76 -5
- data/lib/zmachine.rb +9 -1
- data/spec/hashed_wheel_spec.rb +49 -0
- data/spec/spec_helper.rb +17 -0
- data/zmachine.gemspec +6 -5
- metadata +43 -30
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ODFlZWJhODg3YmM5NWMxYmJmZmRmNmM5ZGI0N2NkMzIzNWU3YjEwZg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NGMyYTk4ZmIwODI5OWZlMjY1ZDQxYjRkYTBlZGJiZjAyZGY0MDY4Mw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YTVjMmVhODBmZjU3NTA4MmQ4MDBkOTRlZmE4OTMzNjA4ZWM4NjgwZmM2ZWVm
|
10
|
+
YWRlYTM0Mzg0NTlhYzYwYzgyOWIwZmMzMzcyZTEzNDU2MjFjNGQyYjliMmFh
|
11
|
+
YzJmMWYwZWU5MTdjYmJmZjk0ZDgwZmQ2YzZhOGQxZjdmNTNmZTA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OGRmZjQ3YWI2NTllOWI2MzQzODVhOGVjYzAzMjI5ZDgwNTQ3NTU4MjM4NjVk
|
14
|
+
Yjk5MzU0MmJmM2QwNjUxYzllZTZlMWIyMTVlMGM5OTE1YWZmOTA1OTIzZGRm
|
15
|
+
Y2VjYjI4N2JkNzRjOTcxNDgzMTA0NTU1YmM0ZWYwOWFmMzQ1M2I=
|
data/lib/zmachine/acceptor.rb
CHANGED
@@ -4,8 +4,16 @@ module ZMachine
|
|
4
4
|
attr_reader :args
|
5
5
|
attr_reader :callback
|
6
6
|
|
7
|
-
def initialize(channel, klass, *args
|
8
|
-
@klass, @args, @callback = klass, args,
|
7
|
+
def initialize(channel, klass, *args )
|
8
|
+
@klass, @args, @callback = klass, args[0...-1], args.last
|
9
|
+
@channel = channel
|
10
|
+
end
|
11
|
+
|
12
|
+
def unbind
|
13
|
+
end
|
14
|
+
|
15
|
+
def close
|
16
|
+
@channel.close
|
9
17
|
end
|
10
18
|
|
11
19
|
end
|
data/lib/zmachine/channel.rb
CHANGED
@@ -1,13 +1,42 @@
|
|
1
1
|
module ZMachine
|
2
2
|
class Channel
|
3
3
|
|
4
|
-
|
5
|
-
attr_reader
|
4
|
+
attr_accessor :socket
|
5
|
+
attr_reader :selector
|
6
6
|
attr_accessor :handler
|
7
|
+
attr_accessor :reactor
|
8
|
+
|
9
|
+
attr_reader :comm_inactivity_timeout
|
10
|
+
attr_reader :last_comm_activity
|
7
11
|
|
8
12
|
def initialize(selector)
|
9
13
|
@selector = selector
|
10
14
|
@outbound_queue = []
|
15
|
+
@comm_inactivity_timeout = 0
|
16
|
+
@timedout = false
|
17
|
+
mark_active!
|
18
|
+
end
|
19
|
+
|
20
|
+
# assigned in seconds!!
|
21
|
+
def comm_inactivity_timeout=(value)
|
22
|
+
# we are in nanos
|
23
|
+
@comm_inactivity_timeout = value * 1000_000_000
|
24
|
+
end
|
25
|
+
|
26
|
+
def mark_active!
|
27
|
+
@last_comm_activity = System.nano_time
|
28
|
+
end
|
29
|
+
|
30
|
+
def timedout?
|
31
|
+
@timedout
|
32
|
+
end
|
33
|
+
|
34
|
+
def timedout!
|
35
|
+
@timedout = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def was_active?(now)
|
39
|
+
@last_comm_activity + @comm_inactivity_timeout >= now
|
11
40
|
end
|
12
41
|
|
13
42
|
end
|
data/lib/zmachine/connection.rb
CHANGED
@@ -30,9 +30,20 @@ module ZMachine
|
|
30
30
|
def receive_data(data)
|
31
31
|
end
|
32
32
|
|
33
|
+
def send3(a,b,c)
|
34
|
+
@channel.send3 a,b,c
|
35
|
+
end
|
36
|
+
def send2(a,b)
|
37
|
+
@channel.send2 a,b
|
38
|
+
end
|
39
|
+
|
33
40
|
def unbind
|
34
41
|
end
|
35
42
|
|
43
|
+
def reconnect(server, port)
|
44
|
+
ZMachine.reconnect server, port, self
|
45
|
+
end
|
46
|
+
|
36
47
|
# public API
|
37
48
|
|
38
49
|
def_delegator :@channel, :close_connection
|
@@ -42,9 +53,11 @@ module ZMachine
|
|
42
53
|
end
|
43
54
|
|
44
55
|
def comm_inactivity_timeout
|
56
|
+
channel.comm_inactivity_timeout
|
45
57
|
end
|
46
58
|
|
47
59
|
def comm_inactivity_timeout=(value)
|
60
|
+
channel.comm_inactivity_timeout = value
|
48
61
|
end
|
49
62
|
|
50
63
|
alias :set_comm_inactivity_timeout :comm_inactivity_timeout=
|
@@ -180,7 +193,7 @@ module ZMachine
|
|
180
193
|
private
|
181
194
|
|
182
195
|
def _not_implemented
|
183
|
-
raise RuntimeError.new("API call not implemented!")
|
196
|
+
raise RuntimeError.new("API call not implemented! #{caller[0]}")
|
184
197
|
end
|
185
198
|
end
|
186
199
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module ZMachine
|
2
|
+
|
3
|
+
class HashedWheelTimeout
|
4
|
+
attr_accessor :round
|
5
|
+
attr_reader :deadline
|
6
|
+
attr_reader :callback
|
7
|
+
def initialize(round, deadline, &block)
|
8
|
+
@round = round
|
9
|
+
@deadline = deadline
|
10
|
+
@callback = block
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class HashedWheel
|
15
|
+
|
16
|
+
attr_reader :slots
|
17
|
+
attr_accessor :last
|
18
|
+
|
19
|
+
def initialize(number_of_slots, tick_length, start_time = System.nano_time)
|
20
|
+
@slots = Array.new(number_of_slots) { [] }
|
21
|
+
@tick_length = tick_length * 1_000_000
|
22
|
+
@last = start_time
|
23
|
+
@current_tick = 0
|
24
|
+
|
25
|
+
@next = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_deadline
|
29
|
+
return Float::INFINITY if !@next
|
30
|
+
@next.deadline
|
31
|
+
end
|
32
|
+
|
33
|
+
def add(timeout, &block)
|
34
|
+
timeout *= 1_000_000 # ms to ns
|
35
|
+
idx = (timeout / @tick_length) % @slots.length
|
36
|
+
round = (timeout / @tick_length) / @slots.length
|
37
|
+
@slots[idx] << hwt = HashedWheelTimeout.new(round, System.nano_time + timeout, &block)
|
38
|
+
if !@next
|
39
|
+
@next = hwt
|
40
|
+
else
|
41
|
+
@next = hwt if @next.deadline > hwt.deadline
|
42
|
+
end
|
43
|
+
hwt
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset(time = System.nano_time)
|
47
|
+
@slots = Array.new(@slots.length) { [] }
|
48
|
+
@current_tick = 0
|
49
|
+
@last = time
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns all timeouts
|
53
|
+
def advance(now = System.nano_time)
|
54
|
+
# how many tickts have passed?
|
55
|
+
passed_ticks = (now - @last) / @tick_length
|
56
|
+
round = 0
|
57
|
+
result = []
|
58
|
+
begin
|
59
|
+
q, @current_tick = @current_tick.divmod slots.length
|
60
|
+
round += q
|
61
|
+
# collect timeouts
|
62
|
+
@slots[@current_tick].delete_if do |timeout|
|
63
|
+
timeout.round -= round
|
64
|
+
result << timeout if timeout.round <= 0 && timeout.deadline < now
|
65
|
+
end
|
66
|
+
@current_tick += 1
|
67
|
+
passed_ticks -= 1
|
68
|
+
end while passed_ticks > 0
|
69
|
+
@last = now
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
|
data/lib/zmachine/reactor.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
java_import java.lang.System
|
1
2
|
java_import java.io.FileDescriptor
|
2
3
|
java_import java.io.IOException
|
3
4
|
java_import java.net.InetSocketAddress
|
@@ -17,11 +18,46 @@ java_import org.zeromq.ZContext
|
|
17
18
|
require 'zmachine/acceptor'
|
18
19
|
require 'zmachine/tcp_channel'
|
19
20
|
require 'zmachine/zmq_channel'
|
21
|
+
require 'zmachine/hashed_wheel'
|
20
22
|
|
21
23
|
module ZMachine
|
24
|
+
|
25
|
+
class NotReactorOwner < Exception
|
26
|
+
end
|
27
|
+
|
28
|
+
class NoReactorError < Exception
|
29
|
+
end
|
30
|
+
|
22
31
|
class Reactor
|
23
32
|
|
33
|
+
@mutex = Mutex.new
|
34
|
+
|
35
|
+
def self.register_reactor(reactor)
|
36
|
+
@mutex.synchronize do
|
37
|
+
@reactors ||= []
|
38
|
+
@reactors << reactor
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.terminate_all_reactors
|
43
|
+
# should have a lock ....
|
44
|
+
@mutex.synchronize do
|
45
|
+
@reactors.each(&:stop_event_loop)
|
46
|
+
@reactors.clear
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.unregister_reactor(reactor)
|
51
|
+
@mutex.synchronize do
|
52
|
+
@reactors.delete(reactor)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
24
56
|
def initialize
|
57
|
+
# a 10 ms tick wheel with a 512 slots => ~5s for a round
|
58
|
+
@wheel = HashedWheel.new(512, 10)
|
59
|
+
@last = now = System.nano_time
|
60
|
+
@heartbeat_interval = 50_000_000 # 50 ms
|
25
61
|
@timers = TreeMap.new
|
26
62
|
@timer_callbacks = {}
|
27
63
|
@channels = []
|
@@ -40,13 +76,23 @@ module ZMachine
|
|
40
76
|
@shutdown_hooks << block
|
41
77
|
end
|
42
78
|
|
79
|
+
def xadd_timer(*args, &block)
|
80
|
+
check_reactor_thread
|
81
|
+
interval = args.shift
|
82
|
+
callback = args.shift || block
|
83
|
+
return unless callback
|
84
|
+
@wheel.add((interval.to_f * 1000).to_i, &callback)
|
85
|
+
end
|
86
|
+
|
87
|
+
|
43
88
|
def add_timer(*args, &block)
|
89
|
+
check_reactor_thread
|
44
90
|
interval = args.shift
|
45
91
|
callback = args.shift || block
|
46
92
|
return unless callback
|
47
93
|
|
48
94
|
signature = next_signature
|
49
|
-
deadline =
|
95
|
+
deadline = System.nano_time + (interval.to_f * 1000_000_000).to_i
|
50
96
|
|
51
97
|
if @timers.contains_key(deadline)
|
52
98
|
@timers.get(deadline) << signature
|
@@ -59,6 +105,12 @@ module ZMachine
|
|
59
105
|
signature
|
60
106
|
end
|
61
107
|
|
108
|
+
def unbind_channel(channel)
|
109
|
+
channel.handler = nil
|
110
|
+
ZMachine.logger.debug "zmachine:unbind_channel", channel:channel if ZMachine.debug
|
111
|
+
@unbound_channels << channel
|
112
|
+
end
|
113
|
+
|
62
114
|
def cancel_timer(timer_or_sig)
|
63
115
|
if timer_or_sig.respond_to?(:cancel)
|
64
116
|
timer_or_sig.cancel
|
@@ -70,11 +122,21 @@ module ZMachine
|
|
70
122
|
|
71
123
|
def connect(server, port_or_type=nil, handler=nil, *args, &block)
|
72
124
|
ZMachine.logger.debug("zmachine:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
|
125
|
+
check_reactor_thread
|
73
126
|
if server.nil? or server =~ %r{\w+://}
|
74
127
|
_connect_zmq(server, port_or_type, handler, *args, &block)
|
75
128
|
else
|
76
129
|
_connect_tcp(server, port_or_type, handler, *args, &block)
|
77
130
|
end
|
131
|
+
rescue java.nio.channels.UnresolvedAddressException
|
132
|
+
raise ZMachine::ConnectionError.new('unable to resolve server address')
|
133
|
+
end
|
134
|
+
|
135
|
+
def reconnect(server, port, handler)
|
136
|
+
# No reconnect for zmq ... handled by zmq itself anyway
|
137
|
+
return if handler && handler.channel.is_a?(ZMQChannel) #
|
138
|
+
# TODO : we might want to check if this connection is really dead?
|
139
|
+
connect server, port, handler
|
78
140
|
end
|
79
141
|
|
80
142
|
def connection_count
|
@@ -87,7 +149,7 @@ module ZMachine
|
|
87
149
|
|
88
150
|
def next_tick(callback=nil, &block)
|
89
151
|
@next_tick_queue << (callback || block)
|
90
|
-
signal_loopbreak if
|
152
|
+
signal_loopbreak if reactor_running?
|
91
153
|
end
|
92
154
|
|
93
155
|
def run(callback=nil, shutdown_hook=nil, &block)
|
@@ -98,9 +160,16 @@ module ZMachine
|
|
98
160
|
add_shutdown_hook(shutdown_hook) if shutdown_hook
|
99
161
|
|
100
162
|
begin
|
163
|
+
# list of active reactors
|
164
|
+
Reactor.register_reactor(self)
|
165
|
+
|
101
166
|
@running = true
|
102
167
|
|
103
|
-
|
168
|
+
if @callback
|
169
|
+
add_timer(0) do
|
170
|
+
@callback.call(self)
|
171
|
+
end
|
172
|
+
end
|
104
173
|
|
105
174
|
@selector = Selector.open
|
106
175
|
@run_reactor = true
|
@@ -111,11 +180,20 @@ module ZMachine
|
|
111
180
|
break unless @run_reactor
|
112
181
|
run_timers
|
113
182
|
break unless @run_reactor
|
183
|
+
# advance_timer_wheel
|
184
|
+
# break unless @run_reactor
|
185
|
+
remove_timed_out_channels
|
114
186
|
remove_unbound_channels
|
115
187
|
check_io
|
116
188
|
add_new_channels
|
117
189
|
process_io
|
118
190
|
end
|
191
|
+
rescue => e
|
192
|
+
puts e.message
|
193
|
+
raise e
|
194
|
+
# # maybe add error check callback here
|
195
|
+
# puts "FATAL reactor died : #{e.message}"
|
196
|
+
# puts e.backtrace
|
119
197
|
ensure
|
120
198
|
ZMachine.logger.debug("zmachine:#{__method__}", stop: :selector) if ZMachine.debug
|
121
199
|
@selector.close rescue nil
|
@@ -127,8 +205,16 @@ module ZMachine
|
|
127
205
|
@shutdown_hooks.pop.call until @shutdown_hooks.empty?
|
128
206
|
@next_tick_queue = ConcurrentLinkedQueue.new
|
129
207
|
@running = false
|
208
|
+
Reactor.unregister_reactor(self)
|
130
209
|
ZMachine.logger.debug("zmachine:#{__method__}", stop: :zcontext) if ZMachine.debug
|
131
210
|
ZMachine.context.destroy
|
211
|
+
ZMachine.clear_current_reactor
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def advance_timer_wheel
|
216
|
+
@wheel.advance.each do |timeout|
|
217
|
+
timeout.callback.call
|
132
218
|
end
|
133
219
|
end
|
134
220
|
|
@@ -156,11 +242,18 @@ module ZMachine
|
|
156
242
|
|
157
243
|
private
|
158
244
|
|
245
|
+
def check_reactor_thread
|
246
|
+
raise NoReactorError if !Thread.current[:reactor]
|
247
|
+
raise NotReactorOwner if Thread.current[:reactor] != self
|
248
|
+
end
|
249
|
+
|
250
|
+
|
159
251
|
def _bind_tcp(address, port, handler, *args, &block)
|
160
252
|
klass = _klass_from_handler(Connection, handler)
|
161
253
|
channel = TCPChannel.new(@selector)
|
162
254
|
channel.bind(address, port)
|
163
|
-
add_channel(channel, Acceptor, klass, *args
|
255
|
+
add_channel(channel, Acceptor, klass, *(args+[block]) )
|
256
|
+
channel.handler
|
164
257
|
end
|
165
258
|
|
166
259
|
def _bind_zmq(address, type, handler, *args, &block)
|
@@ -168,6 +261,7 @@ module ZMachine
|
|
168
261
|
channel = ZMQChannel.new(type, @selector)
|
169
262
|
channel.bind(address)
|
170
263
|
add_channel(channel, klass, *args, &block)
|
264
|
+
channel.handler
|
171
265
|
end
|
172
266
|
|
173
267
|
def _connect_tcp(address, port, handler=nil, *args, &block)
|
@@ -176,6 +270,8 @@ module ZMachine
|
|
176
270
|
channel.connect(address, port)
|
177
271
|
channel.connect_pending = true
|
178
272
|
add_channel(channel, klass, *args, &block)
|
273
|
+
channel.mark_active!
|
274
|
+
channel.handler
|
179
275
|
end
|
180
276
|
|
181
277
|
def _connect_zmq(address, type, handler=nil, *args, &block)
|
@@ -184,28 +280,43 @@ module ZMachine
|
|
184
280
|
add_channel(channel, klass, *args, &block)
|
185
281
|
channel.connect(address)
|
186
282
|
channel.handler.connection_completed
|
187
|
-
channel
|
283
|
+
channel.handler
|
188
284
|
end
|
189
285
|
|
190
286
|
def check_io
|
287
|
+
|
288
|
+
now = System.nano_time
|
289
|
+
|
191
290
|
if @new_channels.size > 0
|
192
291
|
timeout = -1
|
193
292
|
elsif !@timers.empty?
|
194
|
-
now = java.util.Date.new.time
|
195
293
|
timer_key = @timers.first_key
|
196
|
-
timeout = timer_key - now
|
294
|
+
timeout = (timer_key - now)
|
197
295
|
timeout = -1 if timeout <= 0
|
198
296
|
else
|
297
|
+
# puts "hb = #{@heartbeat_interval}"
|
298
|
+
#timeout = (@heartbeat_interval - (now - @last)) / 1_000_000
|
199
299
|
timeout = 0
|
200
300
|
end
|
201
301
|
|
202
|
-
|
203
|
-
|
302
|
+
# timeout = @heartbeat_interval - (now - @last)
|
303
|
+
# wnd = @wheel.next_deadline - now
|
304
|
+
# timeout = wnd if timeout > wnd
|
305
|
+
# timeout = -1 if timeout == 0
|
306
|
+
# @last = now
|
307
|
+
|
308
|
+
# sucks a bit ...
|
309
|
+
timeout = -1 if @channels.any?(&:has_more?) # iterate all channels????
|
310
|
+
|
311
|
+
# we have to convert the timeout from ns to ms
|
312
|
+
if timeout > 0
|
313
|
+
timeout /= 1_000_000
|
314
|
+
timeout = -1 if timeout == 0 # check if it would be below 1ms .. 0 would be an indefinite wait but we want to select NOW
|
204
315
|
end
|
205
316
|
|
206
317
|
ZMachine.logger.debug("zmachine:#{__method__}", timeout: timeout) if ZMachine.debug
|
207
318
|
|
208
|
-
if timeout
|
319
|
+
if timeout < 0
|
209
320
|
@selector.select_now
|
210
321
|
else
|
211
322
|
@selector.select(timeout)
|
@@ -218,7 +329,6 @@ module ZMachine
|
|
218
329
|
while it.has_next
|
219
330
|
selected_key = it.next
|
220
331
|
it.remove
|
221
|
-
|
222
332
|
if selected_key.connectable?
|
223
333
|
is_connectable(selected_key.attachment)
|
224
334
|
elsif selected_key.acceptable?
|
@@ -237,42 +347,66 @@ module ZMachine
|
|
237
347
|
|
238
348
|
def is_acceptable(channel)
|
239
349
|
ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
|
240
|
-
client_channel = channel.accept
|
350
|
+
client_channel = channel.accept
|
241
351
|
acceptor = channel.handler
|
242
|
-
add_channel(client_channel, acceptor.klass, *acceptor.args, &acceptor.callback)
|
243
|
-
|
352
|
+
new_channel = add_channel(client_channel, acceptor.klass, *acceptor.args, &acceptor.callback)
|
353
|
+
new_channel.mark_active!
|
354
|
+
new_channel
|
355
|
+
rescue IOException => e
|
356
|
+
ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
|
244
357
|
channel.close
|
245
358
|
end
|
246
359
|
|
247
360
|
def is_readable(channel)
|
248
361
|
ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
|
362
|
+
channel.mark_active!
|
249
363
|
data = channel.read_inbound_data(@read_buffer)
|
250
364
|
channel.handler.receive_data(data) if data
|
251
|
-
rescue IOException
|
365
|
+
rescue IOException => e
|
366
|
+
ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
|
252
367
|
@unbound_channels << channel
|
253
368
|
end
|
254
369
|
|
255
370
|
def is_writable(channel)
|
256
371
|
ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
|
372
|
+
channel.mark_active!
|
257
373
|
@unbound_channels << channel unless channel.write_outbound_data
|
258
|
-
rescue IOException
|
374
|
+
rescue IOException => e
|
375
|
+
ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
|
259
376
|
@unbound_channels << channel
|
260
377
|
end
|
261
378
|
|
262
379
|
def is_connectable(channel)
|
380
|
+
channel.mark_active!
|
263
381
|
ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
|
264
|
-
channel.finish_connecting
|
265
|
-
|
266
|
-
|
382
|
+
result = channel.finish_connecting
|
383
|
+
if !result
|
384
|
+
ZMachine.logger.warn("zmachine:finish_connecting failed", channel: channel) if !result
|
385
|
+
@unbound_channels << channel
|
386
|
+
else
|
387
|
+
channel.handler.connection_completed
|
388
|
+
end
|
389
|
+
rescue IOException => e
|
390
|
+
ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
|
267
391
|
@unbound_channels << channel
|
268
392
|
end
|
269
393
|
|
270
|
-
def add_channel(channel,
|
394
|
+
def add_channel(channel, klass_or_instance, *args, &block)
|
271
395
|
ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
|
272
|
-
|
396
|
+
if klass_or_instance.is_a?(Connection)
|
397
|
+
# if klass_or_instance is not a class but already an instance
|
398
|
+
channel.handler = klass_or_instance
|
399
|
+
klass_or_instance.channel = channel
|
400
|
+
else
|
401
|
+
channel.handler = klass_or_instance.new(channel, *args)
|
402
|
+
end
|
403
|
+
channel.reactor = self
|
273
404
|
@new_channels << channel
|
274
405
|
block.call(channel.handler) if block
|
275
406
|
channel
|
407
|
+
rescue => e
|
408
|
+
puts "ERROR adding channel #{e.message}"
|
409
|
+
puts e.backtrace
|
276
410
|
end
|
277
411
|
|
278
412
|
def add_new_channels
|
@@ -280,17 +414,40 @@ module ZMachine
|
|
280
414
|
begin
|
281
415
|
channel.register
|
282
416
|
@channels << channel
|
283
|
-
rescue ClosedChannelException
|
417
|
+
rescue ClosedChannelException => e
|
418
|
+
puts "UNBIND on add_channel #{e.message}"
|
284
419
|
@unbound_channels << channel
|
285
420
|
end
|
286
421
|
end
|
287
422
|
@new_channels.clear
|
288
423
|
end
|
289
424
|
|
425
|
+
def remove_timed_out_channels
|
426
|
+
# TODO : we want to replace all of our timer handling with hashed wheels in the future
|
427
|
+
now = now = System.nano_time
|
428
|
+
@channels.each do |channel|
|
429
|
+
next if channel.comm_inactivity_timeout == 0
|
430
|
+
if !channel.was_active?(now)
|
431
|
+
ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, now:now,last:channel.last_comm_activity, delta:(now - channel.last_comm_activity), timeout:timeout=channel.comm_inactivity_timeout) if ZMachine.debug
|
432
|
+
channel.timedout!
|
433
|
+
@unbound_channels << channel
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
290
438
|
def remove_unbound_channels
|
439
|
+
return if @unbound_channels.empty?
|
291
440
|
@unbound_channels.each do |channel|
|
292
|
-
|
293
|
-
channel.
|
441
|
+
reason = nil
|
442
|
+
channel, reason = *channel if channel.is_a?(Array)
|
443
|
+
begin
|
444
|
+
# puts "#{channel} unbound"
|
445
|
+
@channels.delete(channel)
|
446
|
+
channel.handler.unbind if channel.handler
|
447
|
+
channel.close
|
448
|
+
rescue Exception => e
|
449
|
+
ZMachine.logger.debug("zmachine:#{__method__} unbind error", channel: channel, reason:reason, klass:e.class, msg:e.message) if ZMachine.debug
|
450
|
+
end
|
294
451
|
end
|
295
452
|
@unbound_channels.clear
|
296
453
|
end
|
@@ -302,21 +459,20 @@ module ZMachine
|
|
302
459
|
begin
|
303
460
|
size -= 1
|
304
461
|
callback.call
|
305
|
-
ensure
|
306
|
-
|
462
|
+
# ensure
|
463
|
+
# ZMachine.next_tick {} if $!
|
307
464
|
end
|
308
465
|
end
|
309
466
|
end
|
310
467
|
|
468
|
+
# TODO : we should definitly optimize periodic timers ... right now they are wasting a hell of cycle es they are recreated on every invocation
|
311
469
|
def run_timers
|
312
|
-
now =
|
470
|
+
now = System.nano_time
|
313
471
|
until @timers.empty?
|
314
472
|
timer_key = @timers.first_key
|
315
473
|
break if timer_key > now
|
316
|
-
|
317
474
|
signatures = @timers.get(timer_key)
|
318
475
|
@timers.remove(timer_key)
|
319
|
-
|
320
476
|
# Fire all timers at this timestamp
|
321
477
|
signatures.each do |signature|
|
322
478
|
callback = @timer_callbacks.delete(signature)
|
@@ -338,8 +494,11 @@ module ZMachine
|
|
338
494
|
def _klass_from_handler(klass = Connection, handler = nil)
|
339
495
|
if handler and handler.is_a?(Class)
|
340
496
|
handler
|
497
|
+
elsif handler and handler.is_a?(Connection)
|
498
|
+
# can happen on reconnect
|
499
|
+
handler
|
341
500
|
elsif handler
|
342
|
-
|
501
|
+
_handler_from_module(klass, handler)
|
343
502
|
else
|
344
503
|
klass
|
345
504
|
end
|
data/lib/zmachine/tcp_channel.rb
CHANGED
@@ -11,6 +11,7 @@ module ZMachine
|
|
11
11
|
super(selector)
|
12
12
|
@close_scheduled = false
|
13
13
|
@connect_pending = false
|
14
|
+
@server_socket = false
|
14
15
|
end
|
15
16
|
|
16
17
|
def register
|
@@ -18,6 +19,7 @@ module ZMachine
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def bind(address, port)
|
22
|
+
@server_socket = true
|
21
23
|
address = InetSocketAddress.new(address, port)
|
22
24
|
@socket = ServerSocketChannel.open
|
23
25
|
@socket.configure_blocking(false)
|
@@ -28,7 +30,9 @@ module ZMachine
|
|
28
30
|
client_socket = socket.accept
|
29
31
|
return unless client_socket
|
30
32
|
client_socket.configure_blocking(false)
|
31
|
-
TCPChannel.new(
|
33
|
+
channel = TCPChannel.new(@selector)
|
34
|
+
channel.socket = client_socket
|
35
|
+
channel
|
32
36
|
end
|
33
37
|
|
34
38
|
def connect(address, port)
|
@@ -54,6 +58,10 @@ module ZMachine
|
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
61
|
+
def close_connection(flush = true)
|
62
|
+
@reactor.unbind_channel(self) if schedule_close(flush)
|
63
|
+
end
|
64
|
+
|
57
65
|
def close
|
58
66
|
if @channel_key
|
59
67
|
@channel_key.cancel
|
@@ -84,7 +92,6 @@ module ZMachine
|
|
84
92
|
until @outbound_queue.empty?
|
85
93
|
buffer = @outbound_queue.first
|
86
94
|
@socket.write(buffer) if buffer.remaining > 0
|
87
|
-
|
88
95
|
# Did we consume the whole outbound buffer? If yes,
|
89
96
|
# pop it off and keep looping. If no, the outbound network
|
90
97
|
# buffers are full, so break out of here.
|
@@ -145,12 +152,17 @@ module ZMachine
|
|
145
152
|
end
|
146
153
|
end
|
147
154
|
|
155
|
+
# these two are a bit misleading .. only used for the zmq channel
|
148
156
|
def has_more?
|
149
157
|
false
|
150
158
|
end
|
151
159
|
|
160
|
+
def can_send?
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
152
164
|
def current_events
|
153
|
-
if @socket.
|
165
|
+
if @socket.is_a?(ServerSocketChannel)
|
154
166
|
return SelectionKey::OP_ACCEPT
|
155
167
|
end
|
156
168
|
|
data/lib/zmachine/zmq_channel.rb
CHANGED
@@ -4,16 +4,79 @@ java_import org.zeromq.ZMQException
|
|
4
4
|
|
5
5
|
require 'zmachine/channel'
|
6
6
|
|
7
|
+
class ZMsg
|
8
|
+
# for performance reason we alias the method here (otherwise it uses reflections all the time!)
|
9
|
+
java_alias :post, :send, [org.zeromq.ZMQ::Socket, Java::boolean]
|
10
|
+
end
|
11
|
+
|
12
|
+
# this needs to be moved to a seperate file
|
13
|
+
class ZMQ
|
14
|
+
class Socket
|
15
|
+
def self.create_socket_with_opts(type, opts = {})
|
16
|
+
socket = ZMachine.context.create_socket(type)
|
17
|
+
socket.setLinger(opts[:linger]) if opts[:linger]
|
18
|
+
socket.setSndHWM(opts[:sndhwm]) if opts[:sndhwm]
|
19
|
+
socket.setRcvHWM(opts[:rcvhwm]) if opts[:rcvhwm]
|
20
|
+
socket.set_router_mandatory(true) if type == ZMQ::ROUTER
|
21
|
+
socket.connect(opts[:connect]) if opts[:connect]
|
22
|
+
socket.bind(opts[:bind]) if opts[:bind]
|
23
|
+
socket
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.pair(opts = {})
|
27
|
+
create_socket_with_opts(ZMQ::PAIR, opts)
|
28
|
+
end
|
29
|
+
def self.router(opts = {})
|
30
|
+
create_socket_with_opts(ZMQ::ROUTER, opts)
|
31
|
+
end
|
32
|
+
def self.pull(opts = {})
|
33
|
+
create_socket_with_opts(ZMQ::PULL, opts)
|
34
|
+
end
|
35
|
+
def self.push(opts = {})
|
36
|
+
create_socket_with_opts(ZMQ::PUSH, opts)
|
37
|
+
end
|
38
|
+
def self.dealer(opts = {})
|
39
|
+
create_socket_with_opts(ZMQ::DEALER, opts)
|
40
|
+
end
|
41
|
+
def self.pub(opts = {})
|
42
|
+
create_socket_with_opts(ZMQ::PUB, opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
def send2(a,b)
|
46
|
+
sent = send a, ZMQ::SNDMORE | ZMQ::DONTWAIT
|
47
|
+
sent &= send b, ZMQ::DONTWAIT
|
48
|
+
sent
|
49
|
+
end
|
50
|
+
|
51
|
+
def send3(a,b,c)
|
52
|
+
sent = send a, ZMQ::SNDMORE | ZMQ::DONTWAIT
|
53
|
+
sent &= send b, ZMQ::SNDMORE | ZMQ::DONTWAIT
|
54
|
+
sent &= send c, ZMQ::DONTWAIT
|
55
|
+
sent
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
7
60
|
module ZMachine
|
8
61
|
class ZMQChannel < Channel
|
9
62
|
|
10
63
|
attr_reader :port
|
64
|
+
attr_reader :socket
|
11
65
|
|
12
66
|
def initialize(type, selector)
|
13
67
|
super(selector)
|
14
|
-
@socket =
|
15
|
-
|
16
|
-
|
68
|
+
@socket = ZMQChannel.create_socket_with_opts type, linger: 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.create_socket_with_opts(type, opts = {})
|
72
|
+
socket = ZMachine.context.create_socket(type)
|
73
|
+
socket.setLinger(opts[:linger]) if opts[:linger]
|
74
|
+
socket.setSndHWM(opts[:sndhwm]) if opts[:sndhwm]
|
75
|
+
socket.setRcvHWM(opts[:rcvhwm]) if opts[:rcvhwm]
|
76
|
+
socket.set_router_mandatory(true) if type == ZMQ::ROUTER
|
77
|
+
socket.connect(opts[:connect]) if opts[:connect]
|
78
|
+
socket.bind(opts[:bind]) if opts[:bind]
|
79
|
+
socket
|
17
80
|
end
|
18
81
|
|
19
82
|
def register
|
@@ -45,10 +108,18 @@ module ZMachine
|
|
45
108
|
@outbound_queue << data unless send_msg(data)
|
46
109
|
end
|
47
110
|
|
111
|
+
def send2(a,b)
|
112
|
+
@socket.send2 a,b
|
113
|
+
end
|
114
|
+
|
115
|
+
def send3(a,b,c)
|
116
|
+
@socket.send3 a,b,c
|
117
|
+
end
|
118
|
+
|
48
119
|
def send_msg(msg)
|
49
|
-
msg.
|
120
|
+
msg.post(@socket, true)
|
50
121
|
return true
|
51
|
-
rescue ZMQException
|
122
|
+
rescue ZMQException => e
|
52
123
|
return false
|
53
124
|
end
|
54
125
|
|
data/lib/zmachine.rb
CHANGED
@@ -17,6 +17,10 @@ module ZMachine
|
|
17
17
|
attr_accessor :debug
|
18
18
|
end
|
19
19
|
|
20
|
+
def self.clear_current_reactor
|
21
|
+
Thread.current[:reactor] = nil
|
22
|
+
end
|
23
|
+
|
20
24
|
def self.reactor
|
21
25
|
Thread.current[:reactor] ||= Reactor.new
|
22
26
|
end
|
@@ -43,7 +47,7 @@ module ZMachine
|
|
43
47
|
end
|
44
48
|
|
45
49
|
def self._not_implemented
|
46
|
-
raise RuntimeError.new("API call not implemented!")
|
50
|
+
raise RuntimeError.new("API call not implemented! #{caller[0]}")
|
47
51
|
end
|
48
52
|
|
49
53
|
def self.add_periodic_timer(*args, &block)
|
@@ -112,6 +116,10 @@ module ZMachine
|
|
112
116
|
_not_implemented
|
113
117
|
end
|
114
118
|
|
119
|
+
def self.stop
|
120
|
+
Reactor.terminate_all_reactors
|
121
|
+
end
|
122
|
+
|
115
123
|
def self.run_block(&block)
|
116
124
|
pr = proc {
|
117
125
|
block.call
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'lib/zmachine/hashed_wheel'
|
3
|
+
|
4
|
+
describe ZMachine::HashedWheelTimeout do
|
5
|
+
it 'calculates the stop index correctly'
|
6
|
+
it 'calculates remaining rounds correctly'
|
7
|
+
it 'can be cancelled'
|
8
|
+
it 'supports an action'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ZMachine::HashedWheel do
|
12
|
+
let(:wheel) { ZMachine::HashedWheel.new(16, 100) }
|
13
|
+
|
14
|
+
it 'returns a timeout on add' do
|
15
|
+
expect(wheel.add(0)).to be_instance_of(ZMachine::HashedWheelTimeout)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'adds timeouts to the correct slot' do
|
19
|
+
wheel.add 0
|
20
|
+
wheel.add 90
|
21
|
+
wheel.add 110
|
22
|
+
wheel.add 1000
|
23
|
+
wheel.add 1600
|
24
|
+
wheel.add 3200
|
25
|
+
expect(wheel.slots[0].length).to eq(4)
|
26
|
+
expect(wheel.slots[1].length).to eq(1)
|
27
|
+
expect(wheel.slots[10].length).to eq(1)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'times out same slot timeouts correctly' do
|
31
|
+
now = wheel.reset
|
32
|
+
wheel.add 10
|
33
|
+
wheel.add 50
|
34
|
+
timedout = wheel.advance(now + 30 * 1_000_000)
|
35
|
+
expect(timedout.length).to eq(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'calculates the timeouted set correctly' do
|
39
|
+
now = wheel.reset
|
40
|
+
wheel.add 10
|
41
|
+
wheel.add 40
|
42
|
+
wheel.add 1900
|
43
|
+
wheel.add 3300
|
44
|
+
wheel.add 4000
|
45
|
+
timedout = wheel.advance( now + 3900 * 1_000_000)
|
46
|
+
expect(timedout).to be
|
47
|
+
expect(timedout.length).to eq(4)
|
48
|
+
end
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
data/zmachine.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "zmachine"
|
5
|
-
spec.version = "0.1.
|
6
|
-
spec.authors = ["
|
7
|
-
spec.email = ["tech@
|
8
|
-
spec.description = %q{pure JRuby multi-threaded EventMachine compatible event loop}
|
9
|
-
spec.summary = %q{pure JRuby multi-threaded EventMachine compatible event loop}
|
5
|
+
spec.version = "0.1.3"
|
6
|
+
spec.authors = ["LiquidM, Inc."]
|
7
|
+
spec.email = ["tech@liquidm.com"]
|
8
|
+
spec.description = %q{pure JRuby multi-threaded mostly EventMachine compatible event loop}
|
9
|
+
spec.summary = %q{pure JRuby multi-threaded mostly EventMachine compatible event loop}
|
10
10
|
spec.homepage = "https://github.com/madvertise/zmachine"
|
11
11
|
spec.license = "MIT"
|
12
12
|
|
@@ -17,4 +17,5 @@ Gem::Specification.new do |spec|
|
|
17
17
|
|
18
18
|
spec.add_development_dependency "bundler", "~> 1.3"
|
19
19
|
spec.add_development_dependency "rake"
|
20
|
+
spec.add_development_dependency "rspec"
|
20
21
|
end
|
metadata
CHANGED
@@ -1,51 +1,60 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zmachine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
-
|
9
|
-
autorequire:
|
7
|
+
- LiquidM, Inc.
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-11-05 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
16
|
-
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
17
16
|
requirements:
|
18
17
|
- - ~>
|
19
18
|
- !ruby/object:Gem::Version
|
20
19
|
version: '1.3'
|
21
|
-
|
22
|
-
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.3'
|
27
|
-
none: false
|
28
|
-
prerelease: false
|
29
|
-
type: :development
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
32
36
|
version_requirements: !ruby/object:Gem::Requirement
|
33
37
|
requirements:
|
34
|
-
- - '>='
|
38
|
+
- - ! '>='
|
35
39
|
- !ruby/object:Gem::Version
|
36
40
|
version: '0'
|
37
|
-
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
38
43
|
requirement: !ruby/object:Gem::Requirement
|
39
44
|
requirements:
|
40
|
-
- - '>='
|
45
|
+
- - ! '>='
|
41
46
|
- !ruby/object:Gem::Version
|
42
47
|
version: '0'
|
43
|
-
none: false
|
44
|
-
prerelease: false
|
45
48
|
type: :development
|
46
|
-
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: pure JRuby multi-threaded mostly EventMachine compatible event loop
|
47
56
|
email:
|
48
|
-
- tech@
|
57
|
+
- tech@liquidm.com
|
49
58
|
executables: []
|
50
59
|
extensions: []
|
51
60
|
extra_rdoc_files: []
|
@@ -62,36 +71,40 @@ files:
|
|
62
71
|
- lib/zmachine/channel.rb
|
63
72
|
- lib/zmachine/connection.rb
|
64
73
|
- lib/zmachine/deferrable.rb
|
74
|
+
- lib/zmachine/hashed_wheel.rb
|
65
75
|
- lib/zmachine/jeromq-0.3.0-SNAPSHOT.jar
|
66
76
|
- lib/zmachine/reactor.rb
|
67
77
|
- lib/zmachine/tcp_channel.rb
|
68
78
|
- lib/zmachine/timers.rb
|
69
79
|
- lib/zmachine/zmq_channel.rb
|
80
|
+
- spec/hashed_wheel_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
70
82
|
- zmachine.gemspec
|
71
83
|
homepage: https://github.com/madvertise/zmachine
|
72
84
|
licenses:
|
73
85
|
- MIT
|
74
|
-
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
75
88
|
rdoc_options: []
|
76
89
|
require_paths:
|
77
90
|
- lib
|
78
91
|
required_ruby_version: !ruby/object:Gem::Requirement
|
79
92
|
requirements:
|
80
|
-
- - '>='
|
93
|
+
- - ! '>='
|
81
94
|
- !ruby/object:Gem::Version
|
82
95
|
version: '0'
|
83
|
-
none: false
|
84
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
97
|
requirements:
|
86
|
-
- - '>='
|
98
|
+
- - ! '>='
|
87
99
|
- !ruby/object:Gem::Version
|
88
100
|
version: '0'
|
89
|
-
none: false
|
90
101
|
requirements: []
|
91
|
-
rubyforge_project:
|
92
|
-
rubygems_version:
|
93
|
-
signing_key:
|
94
|
-
specification_version:
|
95
|
-
summary: pure JRuby multi-threaded EventMachine compatible event loop
|
96
|
-
test_files:
|
97
|
-
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.0.6
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: pure JRuby multi-threaded mostly EventMachine compatible event loop
|
107
|
+
test_files:
|
108
|
+
- spec/hashed_wheel_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
has_rdoc:
|