arf 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +12 -0
- data/lib/arf/configuration.rb +67 -0
- data/lib/arf/context.rb +170 -0
- data/lib/arf/errors.rb +10 -0
- data/lib/arf/io/buffer.rb +25 -0
- data/lib/arf/io/compression.rb +44 -0
- data/lib/arf/io/limit_reader.rb +30 -0
- data/lib/arf/observer.rb +29 -0
- data/lib/arf/proto/array.rb +31 -0
- data/lib/arf/proto/boolean.rb +15 -0
- data/lib/arf/proto/bytes.rb +35 -0
- data/lib/arf/proto/decoder.rb +31 -0
- data/lib/arf/proto/encoder.rb +71 -0
- data/lib/arf/proto/float.rb +48 -0
- data/lib/arf/proto/map.rb +49 -0
- data/lib/arf/proto/registry.rb +27 -0
- data/lib/arf/proto/scalar.rb +55 -0
- data/lib/arf/proto/string.rb +36 -0
- data/lib/arf/proto/struct.rb +84 -0
- data/lib/arf/proto/types.rb +67 -0
- data/lib/arf/proto/union.rb +25 -0
- data/lib/arf/proto.rb +17 -0
- data/lib/arf/reactor.rb +270 -0
- data/lib/arf/rpc/base_message.rb +119 -0
- data/lib/arf/rpc/client_base.rb +110 -0
- data/lib/arf/rpc/end_stream.rb +9 -0
- data/lib/arf/rpc/enum.rb +51 -0
- data/lib/arf/rpc/message_kind.rb +27 -0
- data/lib/arf/rpc/metadata.rb +120 -0
- data/lib/arf/rpc/method_meta.rb +42 -0
- data/lib/arf/rpc/request.rb +39 -0
- data/lib/arf/rpc/responder.rb +186 -0
- data/lib/arf/rpc/response.rb +46 -0
- data/lib/arf/rpc/service_base.rb +137 -0
- data/lib/arf/rpc/start_stream.rb +9 -0
- data/lib/arf/rpc/stream_error.rb +23 -0
- data/lib/arf/rpc/stream_item.rb +16 -0
- data/lib/arf/rpc/stream_metadata.rb +16 -0
- data/lib/arf/rpc/struct.rb +255 -0
- data/lib/arf/rpc.rb +19 -0
- data/lib/arf/server.rb +123 -0
- data/lib/arf/status.rb +75 -0
- data/lib/arf/types/array_type.rb +24 -0
- data/lib/arf/types/base_type.rb +14 -0
- data/lib/arf/types/coercion.rb +36 -0
- data/lib/arf/types/in_out_stream.rb +28 -0
- data/lib/arf/types/input_stream.rb +8 -0
- data/lib/arf/types/map_type.rb +32 -0
- data/lib/arf/types/mixin.rb +29 -0
- data/lib/arf/types/output_stream.rb +8 -0
- data/lib/arf/types/streamer.rb +21 -0
- data/lib/arf/types.rb +69 -0
- data/lib/arf/version.rb +5 -0
- data/lib/arf/wire/base_connection.rb +177 -0
- data/lib/arf/wire/client.rb +101 -0
- data/lib/arf/wire/encoding.rb +49 -0
- data/lib/arf/wire/error_code.rb +35 -0
- data/lib/arf/wire/errors.rb +88 -0
- data/lib/arf/wire/frame.rb +111 -0
- data/lib/arf/wire/frame_kind.rb +23 -0
- data/lib/arf/wire/frame_reader.rb +104 -0
- data/lib/arf/wire/frames/base_frame.rb +108 -0
- data/lib/arf/wire/frames/configuration_frame.rb +33 -0
- data/lib/arf/wire/frames/data_frame.rb +21 -0
- data/lib/arf/wire/frames/go_away_frame.rb +29 -0
- data/lib/arf/wire/frames/make_stream_frame.rb +15 -0
- data/lib/arf/wire/frames/ping_frame.rb +18 -0
- data/lib/arf/wire/frames/reset_stream_frame.rb +19 -0
- data/lib/arf/wire/frames.rb +9 -0
- data/lib/arf/wire/server/peer.rb +85 -0
- data/lib/arf/wire/server.rb +63 -0
- data/lib/arf/wire/stream/state.rb +69 -0
- data/lib/arf/wire/stream.rb +128 -0
- data/lib/arf/wire/wait_signal.rb +24 -0
- data/lib/arf/wire.rb +14 -0
- data/lib/arf.rb +46 -0
- metadata +195 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module Proto
|
5
|
+
EMPTY_MAP_MASK = 0x01 << 4
|
6
|
+
|
7
|
+
def self.encode_map(v)
|
8
|
+
t = TYPE_MAP
|
9
|
+
if v.empty?
|
10
|
+
t |= EMPTY_MAP_MASK
|
11
|
+
return [t].pack("C*")
|
12
|
+
end
|
13
|
+
|
14
|
+
keys = []
|
15
|
+
values = []
|
16
|
+
v.each_pair do |key, value|
|
17
|
+
keys << encode(key)
|
18
|
+
values << encode(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
encoded_len = encode_uint64(v.length)
|
22
|
+
keys = keys.join
|
23
|
+
values = values.join
|
24
|
+
|
25
|
+
IO::Buffer.new
|
26
|
+
.write(t)
|
27
|
+
.write_raw(encode_uint64(keys.length + values.length + encoded_len.length))
|
28
|
+
.write_raw(encoded_len)
|
29
|
+
.write_raw(keys)
|
30
|
+
.write_raw(values)
|
31
|
+
.string
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.decode_map(header, io)
|
35
|
+
return {} if header.anybits?(EMPTY_MAP_MASK)
|
36
|
+
|
37
|
+
decode_uint64(io) # Discard full length
|
38
|
+
|
39
|
+
pairs_len = decode_uint64(io)
|
40
|
+
keys = []
|
41
|
+
values = []
|
42
|
+
|
43
|
+
pairs_len.times { keys << decode(io) }
|
44
|
+
pairs_len.times { values << decode(io) }
|
45
|
+
|
46
|
+
keys.zip(values).to_h
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module Proto
|
5
|
+
module Registry
|
6
|
+
def self.register!(cls)
|
7
|
+
@structs ||= {}
|
8
|
+
id = cls.arf_struct_id
|
9
|
+
fields = Proto.fields_from_struct(cls)
|
10
|
+
@structs[id] = {
|
11
|
+
id:,
|
12
|
+
fields:,
|
13
|
+
type: cls
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.reset!
|
18
|
+
@structs = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find(id)
|
22
|
+
@structs ||= {}
|
23
|
+
@structs[id]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module Proto
|
5
|
+
NUMERIC_SIGNED_MASK = 0x01 << 4
|
6
|
+
NUMERIC_ZERO_MASK = 0x01 << 5
|
7
|
+
NUMERIC_NEGATIVE_MASK = 0x01 << 6
|
8
|
+
|
9
|
+
def self.encode_scalar(v, signed: false)
|
10
|
+
type = TYPE_SCALAR
|
11
|
+
type |= NUMERIC_SIGNED_MASK if signed
|
12
|
+
type |= NUMERIC_ZERO_MASK if v.zero?
|
13
|
+
if v.negative?
|
14
|
+
type |= NUMERIC_NEGATIVE_MASK
|
15
|
+
v *= -1
|
16
|
+
end
|
17
|
+
|
18
|
+
[
|
19
|
+
[type].pack("C*"),
|
20
|
+
v.zero? ? nil : encode_uint64(v)
|
21
|
+
].compact.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.decode_scalar(header, io)
|
25
|
+
return 0 if header.anybits?(NUMERIC_ZERO_MASK)
|
26
|
+
|
27
|
+
v = decode_uint64(io)
|
28
|
+
v *= -1 if header.anybits?(NUMERIC_NEGATIVE_MASK)
|
29
|
+
v
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.encode_uint64(v)
|
33
|
+
bytes = []
|
34
|
+
while v >= 0x80
|
35
|
+
bytes << ((v & 0xFF) | 0x80)
|
36
|
+
v >>= 7
|
37
|
+
end
|
38
|
+
bytes << v
|
39
|
+
bytes.pack("C*")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.decode_uint64(io)
|
43
|
+
x = 0
|
44
|
+
s = 0
|
45
|
+
b = 0
|
46
|
+
loop do
|
47
|
+
b = io.read(1).getbyte(0)
|
48
|
+
return (x | (b << s)) if b < 0x80
|
49
|
+
|
50
|
+
x |= ((b & 0x7f) << s)
|
51
|
+
s += 7
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module Proto
|
5
|
+
STRING_EMPTY_MASK = 0x01 << 4
|
6
|
+
|
7
|
+
def self.encode_string(s)
|
8
|
+
t = TYPE_STRING
|
9
|
+
if s.nil? || s.empty?
|
10
|
+
t |= STRING_EMPTY_MASK
|
11
|
+
return [t].pack("C*")
|
12
|
+
end
|
13
|
+
|
14
|
+
s = s.to_s.encode("UTF-8")
|
15
|
+
|
16
|
+
[
|
17
|
+
[t].pack("C*"),
|
18
|
+
encode_uint64(s.bytesize),
|
19
|
+
s
|
20
|
+
].join
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.decode_string(header, io)
|
24
|
+
return "" if header.anybits?(STRING_EMPTY_MASK)
|
25
|
+
|
26
|
+
size = decode_uint64(io)
|
27
|
+
data = StringIO.new
|
28
|
+
until size.zero?
|
29
|
+
read = io.readpartial(size)
|
30
|
+
data.write(read)
|
31
|
+
size -= read.length
|
32
|
+
end
|
33
|
+
data.string.encode("UTF-8")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module Proto
|
5
|
+
def self.fields_from_struct(v)
|
6
|
+
base = v.is_a?(Class) ? v : v.class
|
7
|
+
fields = []
|
8
|
+
base.fields.each do |f|
|
9
|
+
if f[:id].is_a? Integer
|
10
|
+
fields << if f[:type].is_a?(Symbol) ||
|
11
|
+
f[:type].is_a?(Arf::Types::ArrayType) ||
|
12
|
+
f[:type].is_a?(Arf::Types::MapType)
|
13
|
+
f
|
14
|
+
else
|
15
|
+
{
|
16
|
+
id: f[:id],
|
17
|
+
name: f[:name],
|
18
|
+
type: base.find_type(f[:type])
|
19
|
+
}
|
20
|
+
end
|
21
|
+
else
|
22
|
+
raise UnsupportedNestedUnionError, "Nested unions are not supported" if base.union?
|
23
|
+
|
24
|
+
union_type = base.find_type(f[:type])
|
25
|
+
fields << {
|
26
|
+
id: union_type.fields.first[:id],
|
27
|
+
type: union_type,
|
28
|
+
name: f[:name]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
fields.sort_by! { _1[:id] }
|
34
|
+
fields
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.encode_struct(v)
|
38
|
+
return encode_union(v) if v.union?
|
39
|
+
|
40
|
+
struct_id = v.arf_struct_id
|
41
|
+
fields = fields_from_struct(v)
|
42
|
+
data = []
|
43
|
+
fields.each do |f|
|
44
|
+
data << encode_uint64(f[:id])
|
45
|
+
data << encode_as(v.instance_variable_get("@#{f[:name]}"), f[:type])
|
46
|
+
end
|
47
|
+
|
48
|
+
payload = data.join
|
49
|
+
|
50
|
+
[
|
51
|
+
[TYPE_STRUCT].pack("C*"),
|
52
|
+
encode_string(struct_id),
|
53
|
+
encode_uint64(payload.length),
|
54
|
+
payload
|
55
|
+
].join
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.decode_struct(_header, io)
|
59
|
+
id_type, id_header = read_type(io)
|
60
|
+
if id_type != TYPE_STRING
|
61
|
+
# :nocov:
|
62
|
+
raise DecodeFailedError, "cannot decode struct: expected String, found #{TYPE_NAME[id_type]}"
|
63
|
+
# :nocov:
|
64
|
+
end
|
65
|
+
|
66
|
+
struct_id = decode_string(id_header, io)
|
67
|
+
bytes_len = decode_uint64(io)
|
68
|
+
reader = IO::LimitReader.new(io, bytes_len)
|
69
|
+
fields = {}
|
70
|
+
loop do
|
71
|
+
id = decode_uint64(reader)
|
72
|
+
fields[id] = decode(reader)
|
73
|
+
rescue EOFError
|
74
|
+
break
|
75
|
+
end
|
76
|
+
|
77
|
+
meta_str = Registry.find(struct_id)
|
78
|
+
raise UnknownMeessageError, "Unknown message ID #{struct_id}" unless meta_str
|
79
|
+
|
80
|
+
inst = meta_str[:type].new
|
81
|
+
inst.decode_fields(fields)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module Proto
|
5
|
+
TYPE_VOID = 0b0000
|
6
|
+
TYPE_SCALAR = 0b0001
|
7
|
+
TYPE_BOOLEAN = 0b0010
|
8
|
+
TYPE_FLOAT = 0b0011
|
9
|
+
TYPE_STRING = 0b0100
|
10
|
+
TYPE_BYTES = 0b0101
|
11
|
+
TYPE_ARRAY = 0b0110
|
12
|
+
TYPE_MAP = 0b0111
|
13
|
+
TYPE_STRUCT = 0b1000
|
14
|
+
TYPE_UNION = 0b1001
|
15
|
+
|
16
|
+
ALL_PRIMITIVES = [
|
17
|
+
TYPE_VOID,
|
18
|
+
TYPE_SCALAR,
|
19
|
+
TYPE_BOOLEAN,
|
20
|
+
TYPE_FLOAT,
|
21
|
+
TYPE_STRING,
|
22
|
+
TYPE_BYTES,
|
23
|
+
TYPE_ARRAY,
|
24
|
+
TYPE_MAP,
|
25
|
+
TYPE_STRUCT,
|
26
|
+
TYPE_UNION
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
TYPE_NAME = {
|
30
|
+
TYPE_VOID => "Void",
|
31
|
+
TYPE_SCALAR => "Scalar",
|
32
|
+
TYPE_BOOLEAN => "Boolean",
|
33
|
+
TYPE_FLOAT => "Float",
|
34
|
+
TYPE_STRING => "String",
|
35
|
+
TYPE_BYTES => "Bytes",
|
36
|
+
TYPE_ARRAY => "Array",
|
37
|
+
TYPE_MAP => "Map",
|
38
|
+
TYPE_STRUCT => "Struct",
|
39
|
+
TYPE_UNION => "Union"
|
40
|
+
}.freeze
|
41
|
+
|
42
|
+
SIMPLE_PRIMITIVES = {
|
43
|
+
void: TYPE_VOID,
|
44
|
+
uint8: TYPE_SCALAR,
|
45
|
+
uint16: TYPE_SCALAR,
|
46
|
+
uint32: TYPE_SCALAR,
|
47
|
+
uint64: TYPE_SCALAR,
|
48
|
+
int8: TYPE_SCALAR,
|
49
|
+
int16: TYPE_SCALAR,
|
50
|
+
int32: TYPE_SCALAR,
|
51
|
+
int64: TYPE_SCALAR,
|
52
|
+
bool: TYPE_BOOLEAN,
|
53
|
+
float32: TYPE_FLOAT,
|
54
|
+
float64: TYPE_FLOAT,
|
55
|
+
string: TYPE_STRING,
|
56
|
+
bytes: TYPE_BYTES
|
57
|
+
}.freeze
|
58
|
+
|
59
|
+
def self.read_type(io)
|
60
|
+
b = io.read(1).getbyte(0)
|
61
|
+
decoded = b & 0xF
|
62
|
+
raise UnknownTypeError, format("Unknown type 0x%02x", b) unless ALL_PRIMITIVES.include? decoded
|
63
|
+
|
64
|
+
[decoded, b]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module Proto
|
5
|
+
def self.encode_union(v)
|
6
|
+
selected = v.__arf_union_set_id
|
7
|
+
f = fields_from_struct(v).find { _1[:id] == selected }
|
8
|
+
payload = encode_as(v.instance_variable_get("@#{f[:name]}"), f[:type])
|
9
|
+
|
10
|
+
[
|
11
|
+
[TYPE_UNION].pack("C*"),
|
12
|
+
encode_uint64(selected),
|
13
|
+
payload
|
14
|
+
].join
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.decode_union(_header, io)
|
18
|
+
{
|
19
|
+
union: true,
|
20
|
+
id: decode_uint64(io),
|
21
|
+
value: decode(io)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/arf/proto.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "proto/types"
|
4
|
+
require_relative "proto/registry"
|
5
|
+
|
6
|
+
require_relative "proto/scalar"
|
7
|
+
require_relative "proto/boolean"
|
8
|
+
require_relative "proto/bytes"
|
9
|
+
require_relative "proto/float"
|
10
|
+
require_relative "proto/string"
|
11
|
+
require_relative "proto/array"
|
12
|
+
require_relative "proto/map"
|
13
|
+
require_relative "proto/struct"
|
14
|
+
require_relative "proto/union"
|
15
|
+
|
16
|
+
require_relative "proto/encoder"
|
17
|
+
require_relative "proto/decoder"
|
data/lib/arf/reactor.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
class Reactor
|
5
|
+
BEAT_INTERVAL = 3
|
6
|
+
|
7
|
+
def self.instance = (@instance ||= new)
|
8
|
+
def self.connect(host, port, handler) = instance.connect(host, port, handler)
|
9
|
+
def self.post(task = nil, &) = instance.post(task, &)
|
10
|
+
def self.client_writes_pending(client) = instance.client_writes_pending(client)
|
11
|
+
def self.detach_client(client) = instance.detach_client(client)
|
12
|
+
def self.detach(io) = instance.detach(io)
|
13
|
+
|
14
|
+
def self.attach_server(server, handler_class, *handler_args)
|
15
|
+
instance.attach_server(server, handler_class, *handler_args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@nio = @executor = @thread = nil
|
20
|
+
@stopping = false
|
21
|
+
@todo = Queue.new
|
22
|
+
@spawn_lock = Monitor.new
|
23
|
+
@map = {}
|
24
|
+
@socket_source = {}
|
25
|
+
@log = Arf.configuration.logger.with_fields(subsystem: "Reactor")
|
26
|
+
end
|
27
|
+
|
28
|
+
def timer(interval, &block)
|
29
|
+
@log.debug("Attached timer", interval:, callable: block)
|
30
|
+
Concurrent::TimerTask.new(execution_interval: interval, &block).tap(&:execute)
|
31
|
+
end
|
32
|
+
|
33
|
+
def connect(host, port, handler)
|
34
|
+
@log.debug("Establishing connection", host:, port:, handler:)
|
35
|
+
handler = handler.new
|
36
|
+
@todo << lambda {
|
37
|
+
addr_info = Socket.getaddrinfo(host, port, nil, Socket::SOCK_STREAM).first
|
38
|
+
family = addr_info[4]
|
39
|
+
io = Socket.new(family, Socket::SOCK_STREAM, 0)
|
40
|
+
begin
|
41
|
+
io.connect_nonblock Socket.sockaddr_in(port, host)
|
42
|
+
rescue Errno::EINPROGRESS
|
43
|
+
@log.debug("Connection in progress", id: handler.object_id)
|
44
|
+
@map[io] = @nio.register(io, :w)
|
45
|
+
@map[io].value = [host, port, handler]
|
46
|
+
@socket_source[io] = :client
|
47
|
+
next
|
48
|
+
end
|
49
|
+
@log.debug("Connection succeeded", id: handler.object_id)
|
50
|
+
@map[io] = @nio.register(io, :r)
|
51
|
+
@map[io].value = handler
|
52
|
+
@socket_source[io] = :client
|
53
|
+
@log.debug("Async post_init dispatch", id: handler.object_id)
|
54
|
+
post { handler.registered! }
|
55
|
+
post { handler.post_init } if handler.respond_to?(:post_init)
|
56
|
+
}
|
57
|
+
wakeup
|
58
|
+
handler
|
59
|
+
end
|
60
|
+
|
61
|
+
def attach_server(server, handler_class, *handler_args)
|
62
|
+
@log.debug("Attach server", server:, handler_class:)
|
63
|
+
@todo << lambda {
|
64
|
+
@map[server] = @nio.register(server, :r)
|
65
|
+
@map[server].value = [handler_class, handler_args]
|
66
|
+
}
|
67
|
+
wakeup
|
68
|
+
end
|
69
|
+
|
70
|
+
def detach(io)
|
71
|
+
@log.debug("Detach IO", io:)
|
72
|
+
@todo << lambda {
|
73
|
+
@nio.deregister(io)
|
74
|
+
@map.delete(io)
|
75
|
+
@socket_source.delete(io)
|
76
|
+
io.close
|
77
|
+
}
|
78
|
+
wakeup
|
79
|
+
end
|
80
|
+
|
81
|
+
def detach_client(client)
|
82
|
+
@log.debug("Detach client", client: client.class.name, id: client.object_id)
|
83
|
+
@todo << lambda {
|
84
|
+
io = io_for_client(client)
|
85
|
+
next unless io
|
86
|
+
|
87
|
+
@nio.deregister(io)
|
88
|
+
@map.delete(io)
|
89
|
+
@socket_source.delete(io)
|
90
|
+
io.close
|
91
|
+
}
|
92
|
+
wakeup
|
93
|
+
end
|
94
|
+
|
95
|
+
def post(task = nil, &block)
|
96
|
+
task ||= block
|
97
|
+
spawn
|
98
|
+
source = caller[0]
|
99
|
+
@executor << lambda do
|
100
|
+
task.call
|
101
|
+
rescue Exception => e
|
102
|
+
@log.error("Async post execution failed", e, source:)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def io_for_client(client)
|
107
|
+
@map.each do |k, v|
|
108
|
+
return k if v.value == client
|
109
|
+
end
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def client_writes_pending(client)
|
114
|
+
@log.debug("Writes pending", client: client.class.name, id: client.object_id)
|
115
|
+
@todo << lambda {
|
116
|
+
if (monitor = @map[io_for_client(client)])
|
117
|
+
monitor.interests = :rw
|
118
|
+
else
|
119
|
+
@log.warn("No monitor for client", id: client.object_id)
|
120
|
+
end
|
121
|
+
}
|
122
|
+
wakeup
|
123
|
+
@log.debug("Writes pending registered")
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def spawn
|
129
|
+
return if @thread&.alive?
|
130
|
+
|
131
|
+
@spawn_lock.synchronize do
|
132
|
+
return if @thread&.alive?
|
133
|
+
|
134
|
+
@nio ||= NIO::Selector.new
|
135
|
+
|
136
|
+
@executor ||= Concurrent::ThreadPoolExecutor.new(
|
137
|
+
min_threads: 1,
|
138
|
+
max_threads: 10,
|
139
|
+
max_queue: 0
|
140
|
+
)
|
141
|
+
|
142
|
+
@thread = Thread.new { run }
|
143
|
+
@thread.name = "Arf::Wire::Reactor loop"
|
144
|
+
|
145
|
+
setup_ping_timer
|
146
|
+
true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def setup_ping_timer
|
151
|
+
@setup_ping_timer ||= timer(BEAT_INTERVAL) do
|
152
|
+
post do
|
153
|
+
@map.each_value do |v|
|
154
|
+
if v.is_a? Wire::BaseConnection
|
155
|
+
@log.debug("Dispatch ping", client: v)
|
156
|
+
v.ping
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def handle_server_monitor(monitor)
|
164
|
+
io = monitor.io
|
165
|
+
handler, handler_args = monitor.value
|
166
|
+
return unless monitor.readable?
|
167
|
+
|
168
|
+
client = io.accept_nonblock
|
169
|
+
inst = handler.new(*handler_args)
|
170
|
+
# Basically #attach, but without the round-trip.
|
171
|
+
@map[client] = @nio.register(client, :r)
|
172
|
+
@map[client].value = inst
|
173
|
+
@socket_source[client] = :server
|
174
|
+
post { inst.registered! }
|
175
|
+
post { inst.post_init } if inst.respond_to? :post_init
|
176
|
+
end
|
177
|
+
|
178
|
+
def handle_socket_monitor(monitor)
|
179
|
+
io = monitor.io
|
180
|
+
client = monitor.value
|
181
|
+
|
182
|
+
if client.is_a? Array
|
183
|
+
return unless monitor.writable?
|
184
|
+
|
185
|
+
# This is a socket waiting for connection
|
186
|
+
host, port, handler = client
|
187
|
+
begin
|
188
|
+
io.connect_nonblock Socket.sockaddr_in(port, host)
|
189
|
+
rescue Errno::EISCONN
|
190
|
+
monitor.value = handler
|
191
|
+
monitor.interests = :r
|
192
|
+
@log.debug("Async post_init dispatch", id: handler.object_id)
|
193
|
+
post { handler.registered! }
|
194
|
+
post { handler.post_init } if handler.respond_to?(:post_init)
|
195
|
+
end
|
196
|
+
return
|
197
|
+
end
|
198
|
+
|
199
|
+
begin
|
200
|
+
if monitor.writable?
|
201
|
+
if client.flush_write_buffer(io)
|
202
|
+
monitor.interests = :r
|
203
|
+
detach(io) if client.arf_close_after_writing?
|
204
|
+
end
|
205
|
+
return unless monitor.readable?
|
206
|
+
end
|
207
|
+
|
208
|
+
incoming = io.read_nonblock(4096, exception: false)
|
209
|
+
case incoming
|
210
|
+
when :wait_readable
|
211
|
+
nil
|
212
|
+
when nil
|
213
|
+
post do
|
214
|
+
client.close
|
215
|
+
rescue Exception => e
|
216
|
+
@log.error("Failed closing client", e, id: client.object_id)
|
217
|
+
@nio.deregister(io)
|
218
|
+
@map.delete(io)
|
219
|
+
@socket_source.delete(io)
|
220
|
+
end
|
221
|
+
else
|
222
|
+
post do
|
223
|
+
client.recv(incoming)
|
224
|
+
rescue Exception => e
|
225
|
+
@log.error("Failed calling client.recv", e, id: client.object_id)
|
226
|
+
begin
|
227
|
+
client.close
|
228
|
+
rescue Exception
|
229
|
+
# noop
|
230
|
+
ensure
|
231
|
+
@nio.deregister(io)
|
232
|
+
@map.delete(io)
|
233
|
+
@socket_source.delete(io)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
rescue Errno::EPIPE, Errno::ECONNRESET => e
|
238
|
+
@log.error("Failed reading from client", e, id: client.object_id)
|
239
|
+
@nio.deregister(io)
|
240
|
+
@map.delete(io)
|
241
|
+
@socket_source.delete(io)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def wakeup
|
246
|
+
spawn || @nio.wakeup
|
247
|
+
end
|
248
|
+
|
249
|
+
def run
|
250
|
+
loop do
|
251
|
+
if @stopping
|
252
|
+
@nio.close
|
253
|
+
break
|
254
|
+
end
|
255
|
+
|
256
|
+
@todo.pop(true).call until @todo.empty?
|
257
|
+
|
258
|
+
next unless (monitors = @nio.select)
|
259
|
+
|
260
|
+
monitors.each do |monitor|
|
261
|
+
case monitor.io
|
262
|
+
when TCPServer then handle_server_monitor(monitor)
|
263
|
+
when TCPSocket, Socket then handle_socket_monitor(monitor)
|
264
|
+
else raise "Unexpected monitor IO on reactor: #{monitor.io.inspect}"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|