zmachine 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.jrubyrc +732 -0
- data/java/com/liquidm/zmachine/HashedWheel$Timeout.class +0 -0
- data/java/com/liquidm/zmachine/HashedWheel.class +0 -0
- data/java/com/liquidm/zmachine/HashedWheel.java +120 -0
- data/lib/zmachine.rb +4 -4
- data/lib/zmachine/channel.rb +4 -17
- data/lib/zmachine/connection.rb +11 -7
- data/lib/zmachine/connection_manager.rb +33 -24
- data/lib/zmachine/hashed_wheel.rb +2 -67
- data/lib/zmachine/reactor.rb +6 -7
- data/lib/zmachine/tcp_channel.rb +11 -6
- data/spec/channel_spec.rb +3 -13
- data/spec/connection_spec.rb +2 -2
- data/spec/hashed_wheel_spec.rb +16 -20
- data/zmachine.gemspec +1 -1
- metadata +7 -3
Binary file
|
Binary file
|
@@ -0,0 +1,120 @@
|
|
1
|
+
package com.liquidm.zmachine;
|
2
|
+
|
3
|
+
import java.lang.System;
|
4
|
+
import java.util.ArrayList;
|
5
|
+
import java.util.Iterator;
|
6
|
+
import java.util.concurrent.Callable;
|
7
|
+
|
8
|
+
public class HashedWheel
|
9
|
+
{
|
10
|
+
private class Timeout
|
11
|
+
{
|
12
|
+
long deadline;
|
13
|
+
Object callback;
|
14
|
+
boolean canceled;
|
15
|
+
|
16
|
+
public Timeout(long deadline, Object callback)
|
17
|
+
{
|
18
|
+
this.deadline = deadline;
|
19
|
+
this.callback = callback;
|
20
|
+
this.canceled = false;
|
21
|
+
}
|
22
|
+
|
23
|
+
public Object getCallback()
|
24
|
+
{
|
25
|
+
return this.callback;
|
26
|
+
}
|
27
|
+
|
28
|
+
public void cancel()
|
29
|
+
{
|
30
|
+
this.canceled = true;
|
31
|
+
}
|
32
|
+
|
33
|
+
public boolean isCanceled()
|
34
|
+
{
|
35
|
+
return this.canceled;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
int number_of_slots;
|
40
|
+
long tick_length;
|
41
|
+
Object[] slots;
|
42
|
+
int current_tick;
|
43
|
+
long last;
|
44
|
+
|
45
|
+
public HashedWheel(int number_of_slots, long tick_length)
|
46
|
+
{
|
47
|
+
this.number_of_slots = number_of_slots;
|
48
|
+
this.tick_length = tick_length * 1000000; // ms to ns
|
49
|
+
reset();
|
50
|
+
}
|
51
|
+
|
52
|
+
@SuppressWarnings("unchecked")
|
53
|
+
public Object[] getSlots()
|
54
|
+
{
|
55
|
+
return this.slots;
|
56
|
+
}
|
57
|
+
|
58
|
+
public Timeout add(int timeout)
|
59
|
+
{
|
60
|
+
return add(timeout, null);
|
61
|
+
}
|
62
|
+
|
63
|
+
@SuppressWarnings("unchecked")
|
64
|
+
public Timeout add(long timeout, Object callback)
|
65
|
+
{
|
66
|
+
timeout = timeout * 1000000; // ms to ns
|
67
|
+
long ticks = timeout / this.tick_length;
|
68
|
+
int slot = (int)((this.current_tick + ticks) % this.number_of_slots);
|
69
|
+
long deadline = System.nanoTime() + timeout;
|
70
|
+
Timeout hwt = new Timeout(deadline, callback);
|
71
|
+
ArrayList<Timeout> list = (ArrayList<Timeout>) this.slots[slot];
|
72
|
+
list.add(hwt);
|
73
|
+
return hwt;
|
74
|
+
}
|
75
|
+
|
76
|
+
public long reset()
|
77
|
+
{
|
78
|
+
return reset(System.nanoTime());
|
79
|
+
}
|
80
|
+
|
81
|
+
public long reset(long last)
|
82
|
+
{
|
83
|
+
this.slots = new Object[this.number_of_slots];
|
84
|
+
for (int i = 0; i < this.number_of_slots; i++) {
|
85
|
+
this.slots[i] = new ArrayList<Timeout>();
|
86
|
+
}
|
87
|
+
this.current_tick = 0;
|
88
|
+
this.last = last;
|
89
|
+
return last;
|
90
|
+
}
|
91
|
+
|
92
|
+
public ArrayList<Timeout> advance()
|
93
|
+
{
|
94
|
+
return advance(System.nanoTime());
|
95
|
+
}
|
96
|
+
|
97
|
+
@SuppressWarnings("unchecked")
|
98
|
+
public ArrayList<Timeout> advance(long now)
|
99
|
+
{
|
100
|
+
long passed_ticks = (now - this.last) / this.tick_length;
|
101
|
+
ArrayList<Timeout> result = new ArrayList<Timeout>();
|
102
|
+
do {
|
103
|
+
this.current_tick = this.current_tick % this.number_of_slots;
|
104
|
+
Iterator<Timeout> it = ((ArrayList<Timeout>)this.slots[this.current_tick]).iterator();
|
105
|
+
while (it.hasNext()) {
|
106
|
+
Timeout timeout = it.next();
|
107
|
+
if (timeout.deadline < now) {
|
108
|
+
if (!timeout.isCanceled()) {
|
109
|
+
result.add(timeout);
|
110
|
+
}
|
111
|
+
it.remove();
|
112
|
+
}
|
113
|
+
}
|
114
|
+
this.current_tick += 1;
|
115
|
+
passed_ticks -= 1;
|
116
|
+
} while (passed_ticks > 0);
|
117
|
+
this.last = now;
|
118
|
+
return result;
|
119
|
+
}
|
120
|
+
}
|
data/lib/zmachine.rb
CHANGED
@@ -46,8 +46,8 @@ module ZMachine
|
|
46
46
|
timer_or_sig.cancel # we do not support signatures
|
47
47
|
end
|
48
48
|
|
49
|
-
def self.close_connection(connection, reason = nil)
|
50
|
-
reactor.close_connection(connection, reason)
|
49
|
+
def self.close_connection(connection, after_writing = false, reason = nil)
|
50
|
+
reactor.close_connection(connection, after_writing, reason)
|
51
51
|
end
|
52
52
|
|
53
53
|
def self.connect(server, port_or_type=nil, handler=nil, *args, &block)
|
@@ -59,11 +59,11 @@ module ZMachine
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def self.heartbeat_interval
|
62
|
-
|
62
|
+
@heartbeat_interval
|
63
63
|
end
|
64
64
|
|
65
65
|
def self.heartbeat_interval=(time)
|
66
|
-
|
66
|
+
@heartbeat_interval = time
|
67
67
|
end
|
68
68
|
|
69
69
|
def self.next_tick(callback=nil, &block)
|
data/lib/zmachine/channel.rb
CHANGED
@@ -8,7 +8,6 @@ module ZMachine
|
|
8
8
|
attr_accessor :raw
|
9
9
|
|
10
10
|
def initialize
|
11
|
-
@inbound_buffer = ByteBuffer.allocate(1024 * 1024)
|
12
11
|
@outbound_queue = ConcurrentLinkedQueue.new
|
13
12
|
@raw = false
|
14
13
|
end
|
@@ -30,7 +29,7 @@ module ZMachine
|
|
30
29
|
# write_outbound_data
|
31
30
|
|
32
31
|
def can_send?
|
33
|
-
!@outbound_queue.empty?
|
32
|
+
connected? && !@outbound_queue.empty?
|
34
33
|
end
|
35
34
|
|
36
35
|
def send_data(data)
|
@@ -55,25 +54,13 @@ module ZMachine
|
|
55
54
|
break if buffer.has_remaining
|
56
55
|
@outbound_queue.poll
|
57
56
|
end
|
58
|
-
maybe_close_with_callback
|
59
57
|
end
|
60
58
|
|
61
|
-
def close
|
59
|
+
def close
|
62
60
|
return true if closed?
|
63
|
-
ZMachine.logger.debug("zmachine:channel:#{__method__}", channel: self,
|
64
|
-
@
|
65
|
-
@closed_callback = block if block
|
66
|
-
@outbound_queue.clear unless after_writing
|
67
|
-
maybe_close_with_callback
|
68
|
-
end
|
69
|
-
|
70
|
-
def maybe_close_with_callback
|
71
|
-
ZMachine.logger.debug("zmachine:channel:#{__method__}", channel: self, can_send: can_send?) if ZMachine.debug
|
72
|
-
return false if can_send?
|
73
|
-
return true unless @close_scheduled
|
61
|
+
ZMachine.logger.debug("zmachine:channel:#{__method__}", channel: self, caller: caller[0].inspect) if ZMachine.debug
|
62
|
+
@outbound_queue.clear
|
74
63
|
close!
|
75
|
-
@closed_callback.call if @closed_callback
|
76
|
-
return true
|
77
64
|
end
|
78
65
|
|
79
66
|
end
|
data/lib/zmachine/connection.rb
CHANGED
@@ -10,6 +10,7 @@ module ZMachine
|
|
10
10
|
extend Forwardable
|
11
11
|
|
12
12
|
attr_accessor :channel
|
13
|
+
attr_reader :timer
|
13
14
|
|
14
15
|
def self.new(*args)
|
15
16
|
allocate.instance_eval do
|
@@ -61,14 +62,13 @@ module ZMachine
|
|
61
62
|
# EventMachine Connection API
|
62
63
|
|
63
64
|
def_delegator :@channel, :bound?
|
65
|
+
def_delegator :@channel, :can_send?
|
64
66
|
def_delegator :@channel, :closed?
|
65
67
|
def_delegator :@channel, :connected?
|
66
68
|
def_delegator :@channel, :connection_pending?
|
67
69
|
|
68
70
|
def close_connection(after_writing = false)
|
69
|
-
|
70
|
-
ZMachine.close_connection(self)
|
71
|
-
end
|
71
|
+
ZMachine.close_connection(self, after_writing)
|
72
72
|
end
|
73
73
|
|
74
74
|
alias :close :close_connection
|
@@ -79,6 +79,11 @@ module ZMachine
|
|
79
79
|
|
80
80
|
alias :close_after_writing close_connection_after_writing
|
81
81
|
|
82
|
+
def close!
|
83
|
+
@timer.cancel if @timer
|
84
|
+
@channel.close!
|
85
|
+
end
|
86
|
+
|
82
87
|
def comm_inactivity_timeout
|
83
88
|
@inactivity_timeout
|
84
89
|
end
|
@@ -213,8 +218,7 @@ module ZMachine
|
|
213
218
|
readable! if @channel_key.readable?
|
214
219
|
end
|
215
220
|
rescue Java::JavaNioChannels::CancelledKeyException
|
216
|
-
|
217
|
-
# wait for cleanup
|
221
|
+
ZMachine.close_connection(self)
|
218
222
|
end
|
219
223
|
|
220
224
|
def mark_active!
|
@@ -225,9 +229,9 @@ module ZMachine
|
|
225
229
|
def renew_timer
|
226
230
|
@timer.cancel if @timer
|
227
231
|
if connection_pending? && @connect_timeout
|
228
|
-
@timer = ZMachine.add_timer(@connect_timeout) { ZMachine.close_connection(self, Errno::ETIMEDOUT) }
|
232
|
+
@timer = ZMachine.add_timer(@connect_timeout) { ZMachine.close_connection(self, true, Errno::ETIMEDOUT) }
|
229
233
|
elsif @inactivity_timeout
|
230
|
-
@timer = ZMachine.add_timer(@inactivity_timeout) { ZMachine.close_connection(self, Errno::ETIMEDOUT) }
|
234
|
+
@timer = ZMachine.add_timer(@inactivity_timeout) { ZMachine.close_connection(self, true, Errno::ETIMEDOUT) }
|
231
235
|
end
|
232
236
|
end
|
233
237
|
|
@@ -15,7 +15,7 @@ module ZMachine
|
|
15
15
|
@connections = Set.new
|
16
16
|
@zmq_connections = Set.new
|
17
17
|
@new_connections = Set.new
|
18
|
-
@
|
18
|
+
@closing_connections = []
|
19
19
|
end
|
20
20
|
|
21
21
|
def idle?
|
@@ -25,7 +25,7 @@ module ZMachine
|
|
25
25
|
|
26
26
|
def shutdown
|
27
27
|
ZMachine.logger.debug("zmachine:connection_manager:#{__method__}") if ZMachine.debug
|
28
|
-
@
|
28
|
+
@closing_connections += @connections.to_a
|
29
29
|
cleanup
|
30
30
|
end
|
31
31
|
|
@@ -73,12 +73,12 @@ module ZMachine
|
|
73
73
|
new_connection = connection.process_events
|
74
74
|
@new_connections << new_connection if new_connection
|
75
75
|
rescue IOException => e
|
76
|
-
close_connection(connection, e)
|
76
|
+
close_connection(connection, false, e)
|
77
77
|
end
|
78
78
|
|
79
|
-
def close_connection(connection, reason = nil)
|
80
|
-
ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", connection: connection, reason: reason.inspect) if ZMachine.debug
|
81
|
-
@
|
79
|
+
def close_connection(connection, after_writing = false, reason = nil)
|
80
|
+
ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", connection: connection, after_writing: after_writing, reason: reason.inspect) if ZMachine.debug
|
81
|
+
@closing_connections << [connection, after_writing, reason]
|
82
82
|
end
|
83
83
|
|
84
84
|
def add_new_connections
|
@@ -92,7 +92,7 @@ module ZMachine
|
|
92
92
|
connection.connection_completed
|
93
93
|
end
|
94
94
|
rescue ClosedChannelException => e
|
95
|
-
@
|
95
|
+
@closing_connections << [connection, false, e]
|
96
96
|
end
|
97
97
|
end
|
98
98
|
@new_connections.clear
|
@@ -103,25 +103,34 @@ module ZMachine
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def cleanup
|
106
|
-
return if @
|
106
|
+
return if @closing_connections.empty?
|
107
107
|
ZMachine.logger.debug("zmachine:connection_manager:#{__method__}") if ZMachine.debug
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
108
|
+
closing_connections = @closing_connections
|
109
|
+
@closing_connections = []
|
110
|
+
closing_connections.each do |connection|
|
111
|
+
unbind_connection(connection)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def unbind_connection(connection)
|
116
|
+
after_writing = false
|
117
|
+
reason = nil
|
118
|
+
connection, after_writing, reason = *connection if connection.is_a?(Array)
|
119
|
+
if connection.method(:unbind).arity != 0
|
120
|
+
connection.unbind(reason)
|
121
|
+
else
|
122
|
+
connection.unbind
|
123
|
+
end
|
124
|
+
ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", connection: connection, after_writing: after_writing, can_send: connection.can_send?) if ZMachine.debug
|
125
|
+
if after_writing && connection.can_send?
|
126
|
+
ZMachine.close_connection(connection, true)
|
127
|
+
else
|
128
|
+
connection.close!
|
129
|
+
@connections.delete(connection)
|
130
|
+
@zmq_connections.delete(connection)
|
123
131
|
end
|
124
|
-
|
132
|
+
rescue Exception => e
|
133
|
+
ZMachine.logger.exception(e, "failed to unbind connection") if ZMachine.debug
|
125
134
|
end
|
126
135
|
|
127
136
|
private
|
@@ -1,70 +1,5 @@
|
|
1
|
-
|
1
|
+
$CLASSPATH << File.expand_path("../../../java", __FILE__)
|
2
2
|
|
3
3
|
module ZMachine
|
4
|
-
|
5
|
-
class HashedWheelTimeout
|
6
|
-
attr_reader :deadline
|
7
|
-
attr_reader :callback
|
8
|
-
|
9
|
-
def initialize(deadline, &block)
|
10
|
-
@deadline = deadline
|
11
|
-
@callback = block
|
12
|
-
@canceled = false
|
13
|
-
end
|
14
|
-
|
15
|
-
def cancel
|
16
|
-
@canceled = true
|
17
|
-
end
|
18
|
-
|
19
|
-
def canceled?
|
20
|
-
@canceled
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class HashedWheel
|
25
|
-
attr_reader :slots
|
26
|
-
attr_accessor :last
|
27
|
-
|
28
|
-
def initialize(number_of_slots, tick_length, start_time = System.nano_time)
|
29
|
-
@slots = Array.new(number_of_slots) { [] }
|
30
|
-
@tick_length = tick_length * 1_000_000_000
|
31
|
-
@last = start_time
|
32
|
-
@current_tick = 0
|
33
|
-
end
|
34
|
-
|
35
|
-
def add(timeout, &block)
|
36
|
-
timeout *= 1_000_000_000 # s to ns
|
37
|
-
ticks = timeout / @tick_length
|
38
|
-
slot = (@current_tick + ticks) % @slots.length
|
39
|
-
deadline = System.nano_time + timeout
|
40
|
-
@slots[slot] << hwt = HashedWheelTimeout.new(deadline, &block)
|
41
|
-
hwt
|
42
|
-
end
|
43
|
-
|
44
|
-
def reset(time = nil)
|
45
|
-
@slots = Array.new(@slots.length) { [] }
|
46
|
-
@current_tick = 0
|
47
|
-
@last = time || System.nano_time
|
48
|
-
end
|
49
|
-
|
50
|
-
# returns all timeouts
|
51
|
-
def advance(now = nil)
|
52
|
-
now ||= System.nano_time
|
53
|
-
passed_ticks = (now - @last) / @tick_length
|
54
|
-
result = []
|
55
|
-
begin
|
56
|
-
@current_tick %= @slots.length
|
57
|
-
@slots[@current_tick].delete_if do |timeout|
|
58
|
-
result << timeout if timeout.deadline < now
|
59
|
-
end
|
60
|
-
@current_tick += 1
|
61
|
-
passed_ticks -= 1
|
62
|
-
end while passed_ticks > 0
|
63
|
-
@last = now
|
64
|
-
result.reject do |timeout|
|
65
|
-
timeout.canceled?
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
4
|
+
HashedWheel = Java::ComLiquidmZmachine::HashedWheel
|
70
5
|
end
|
data/lib/zmachine/reactor.rb
CHANGED
@@ -35,12 +35,12 @@ module ZMachine
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def initialize
|
38
|
-
|
39
|
-
@heartbeat_interval = 0.5 # coarse grained by default
|
38
|
+
@heartbeat_interval = ZMachine.heartbeat_interval || 0.5 # coarse grained by default
|
40
39
|
@next_tick_queue = ConcurrentLinkedQueue.new
|
41
40
|
@running = false
|
42
41
|
@shutdown_hooks = []
|
43
|
-
|
42
|
+
# a 10 ms tick wheel with 512 slots => ~5s for a round
|
43
|
+
@wheel = HashedWheel.new(512, 10)
|
44
44
|
end
|
45
45
|
|
46
46
|
def add_shutdown_hook(&block)
|
@@ -53,7 +53,7 @@ module ZMachine
|
|
53
53
|
callback = args.shift || block
|
54
54
|
ZMachine.logger.debug("zmachine:reactor:#{__method__}", interval: interval, callback: callback) if ZMachine.debug
|
55
55
|
return unless callback
|
56
|
-
@wheel.add(interval, &callback)
|
56
|
+
@wheel.add((interval * 1000).to_i, &callback)
|
57
57
|
end
|
58
58
|
|
59
59
|
def bind(server, port_or_type=nil, handler=nil, *args, &block)
|
@@ -62,9 +62,9 @@ module ZMachine
|
|
62
62
|
@connection_manager.bind(server, port_or_type, handler, *args, &block)
|
63
63
|
end
|
64
64
|
|
65
|
-
def close_connection(connection, reason = nil)
|
65
|
+
def close_connection(connection, after_writing = false, reason = nil)
|
66
66
|
return true unless @connection_manager
|
67
|
-
@connection_manager.close_connection(connection, reason)
|
67
|
+
@connection_manager.close_connection(connection, after_writing, reason)
|
68
68
|
end
|
69
69
|
|
70
70
|
def connect(server, port_or_type=nil, handler=nil, *args, &block)
|
@@ -94,7 +94,6 @@ module ZMachine
|
|
94
94
|
def reconnect(server, port_or_type, handler)
|
95
95
|
return handler if handler && handler.channel.is_a?(ZMQChannel)
|
96
96
|
ZMachine.logger.debug("zmachine:reactor:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
|
97
|
-
return handler if handler.connected?
|
98
97
|
connect(server, port_or_type, handler)
|
99
98
|
end
|
100
99
|
|