zmachine 0.3.2 → 0.4.0
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 +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
|
|