arf 0.1.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 +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
|