zmachine 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|