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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/LICENSE.txt +21 -0
  4. data/Rakefile +12 -0
  5. data/lib/arf/configuration.rb +67 -0
  6. data/lib/arf/context.rb +170 -0
  7. data/lib/arf/errors.rb +10 -0
  8. data/lib/arf/io/buffer.rb +25 -0
  9. data/lib/arf/io/compression.rb +44 -0
  10. data/lib/arf/io/limit_reader.rb +30 -0
  11. data/lib/arf/observer.rb +29 -0
  12. data/lib/arf/proto/array.rb +31 -0
  13. data/lib/arf/proto/boolean.rb +15 -0
  14. data/lib/arf/proto/bytes.rb +35 -0
  15. data/lib/arf/proto/decoder.rb +31 -0
  16. data/lib/arf/proto/encoder.rb +71 -0
  17. data/lib/arf/proto/float.rb +48 -0
  18. data/lib/arf/proto/map.rb +49 -0
  19. data/lib/arf/proto/registry.rb +27 -0
  20. data/lib/arf/proto/scalar.rb +55 -0
  21. data/lib/arf/proto/string.rb +36 -0
  22. data/lib/arf/proto/struct.rb +84 -0
  23. data/lib/arf/proto/types.rb +67 -0
  24. data/lib/arf/proto/union.rb +25 -0
  25. data/lib/arf/proto.rb +17 -0
  26. data/lib/arf/reactor.rb +270 -0
  27. data/lib/arf/rpc/base_message.rb +119 -0
  28. data/lib/arf/rpc/client_base.rb +110 -0
  29. data/lib/arf/rpc/end_stream.rb +9 -0
  30. data/lib/arf/rpc/enum.rb +51 -0
  31. data/lib/arf/rpc/message_kind.rb +27 -0
  32. data/lib/arf/rpc/metadata.rb +120 -0
  33. data/lib/arf/rpc/method_meta.rb +42 -0
  34. data/lib/arf/rpc/request.rb +39 -0
  35. data/lib/arf/rpc/responder.rb +186 -0
  36. data/lib/arf/rpc/response.rb +46 -0
  37. data/lib/arf/rpc/service_base.rb +137 -0
  38. data/lib/arf/rpc/start_stream.rb +9 -0
  39. data/lib/arf/rpc/stream_error.rb +23 -0
  40. data/lib/arf/rpc/stream_item.rb +16 -0
  41. data/lib/arf/rpc/stream_metadata.rb +16 -0
  42. data/lib/arf/rpc/struct.rb +255 -0
  43. data/lib/arf/rpc.rb +19 -0
  44. data/lib/arf/server.rb +123 -0
  45. data/lib/arf/status.rb +75 -0
  46. data/lib/arf/types/array_type.rb +24 -0
  47. data/lib/arf/types/base_type.rb +14 -0
  48. data/lib/arf/types/coercion.rb +36 -0
  49. data/lib/arf/types/in_out_stream.rb +28 -0
  50. data/lib/arf/types/input_stream.rb +8 -0
  51. data/lib/arf/types/map_type.rb +32 -0
  52. data/lib/arf/types/mixin.rb +29 -0
  53. data/lib/arf/types/output_stream.rb +8 -0
  54. data/lib/arf/types/streamer.rb +21 -0
  55. data/lib/arf/types.rb +69 -0
  56. data/lib/arf/version.rb +5 -0
  57. data/lib/arf/wire/base_connection.rb +177 -0
  58. data/lib/arf/wire/client.rb +101 -0
  59. data/lib/arf/wire/encoding.rb +49 -0
  60. data/lib/arf/wire/error_code.rb +35 -0
  61. data/lib/arf/wire/errors.rb +88 -0
  62. data/lib/arf/wire/frame.rb +111 -0
  63. data/lib/arf/wire/frame_kind.rb +23 -0
  64. data/lib/arf/wire/frame_reader.rb +104 -0
  65. data/lib/arf/wire/frames/base_frame.rb +108 -0
  66. data/lib/arf/wire/frames/configuration_frame.rb +33 -0
  67. data/lib/arf/wire/frames/data_frame.rb +21 -0
  68. data/lib/arf/wire/frames/go_away_frame.rb +29 -0
  69. data/lib/arf/wire/frames/make_stream_frame.rb +15 -0
  70. data/lib/arf/wire/frames/ping_frame.rb +18 -0
  71. data/lib/arf/wire/frames/reset_stream_frame.rb +19 -0
  72. data/lib/arf/wire/frames.rb +9 -0
  73. data/lib/arf/wire/server/peer.rb +85 -0
  74. data/lib/arf/wire/server.rb +63 -0
  75. data/lib/arf/wire/stream/state.rb +69 -0
  76. data/lib/arf/wire/stream.rb +128 -0
  77. data/lib/arf/wire/wait_signal.rb +24 -0
  78. data/lib/arf/wire.rb +14 -0
  79. data/lib/arf.rb +46 -0
  80. 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"
@@ -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