thrift 0.23.0 → 0.24.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/README.md +93 -33
- data/benchmark/benchmark.rb +2 -1
- data/benchmark/client.rb +1 -0
- data/benchmark/server.rb +1 -0
- data/benchmark/thin_server.rb +1 -0
- data/ext/binary_protocol_accelerated.c +78 -23
- data/ext/bytes.c +14 -0
- data/ext/compact_protocol.c +59 -31
- data/ext/extconf.rb +8 -0
- data/ext/memory_buffer.c +37 -0
- data/ext/struct.c +14 -14
- data/ext/thrift_native.c +1 -0
- data/lib/thrift/bytes.rb +4 -1
- data/lib/thrift/client.rb +1 -12
- data/lib/thrift/exceptions.rb +1 -0
- data/lib/thrift/multiplexed_processor.rb +14 -4
- data/lib/thrift/processor.rb +1 -0
- data/lib/thrift/protocol/base_protocol.rb +15 -6
- data/lib/thrift/protocol/binary_protocol.rb +41 -12
- data/lib/thrift/protocol/binary_protocol_accelerated.rb +1 -0
- data/lib/thrift/protocol/compact_protocol.rb +15 -4
- data/lib/thrift/protocol/header_protocol.rb +1 -0
- data/lib/thrift/protocol/json_protocol.rb +18 -11
- data/lib/thrift/protocol/multiplexed_protocol.rb +1 -0
- data/lib/thrift/protocol/protocol_decorator.rb +1 -0
- data/lib/thrift/serializer/deserializer.rb +1 -0
- data/lib/thrift/serializer/serializer.rb +1 -0
- data/lib/thrift/server/base_server.rb +1 -0
- data/lib/thrift/server/mongrel_http_server.rb +1 -0
- data/lib/thrift/server/nonblocking_server.rb +28 -2
- data/lib/thrift/server/simple_server.rb +9 -1
- data/lib/thrift/server/thin_http_server.rb +1 -0
- data/lib/thrift/server/thread_pool_server.rb +1 -0
- data/lib/thrift/server/threaded_server.rb +9 -1
- data/lib/thrift/struct.rb +1 -0
- data/lib/thrift/struct_union.rb +4 -9
- data/lib/thrift/thrift_native.rb +2 -1
- data/lib/thrift/transport/base_server_transport.rb +3 -0
- data/lib/thrift/transport/base_transport.rb +6 -2
- data/lib/thrift/transport/buffered_transport.rb +1 -0
- data/lib/thrift/transport/framed_transport.rb +1 -0
- data/lib/thrift/transport/header_transport.rb +47 -1
- data/lib/thrift/transport/http_client_transport.rb +1 -0
- data/lib/thrift/transport/io_stream_transport.rb +1 -0
- data/lib/thrift/transport/memory_buffer_transport.rb +7 -0
- data/lib/thrift/transport/server_socket.rb +6 -3
- data/lib/thrift/transport/socket.rb +68 -29
- data/lib/thrift/transport/ssl_server_socket.rb +3 -2
- data/lib/thrift/transport/ssl_socket.rb +43 -11
- data/lib/thrift/transport/unix_server_socket.rb +5 -1
- data/lib/thrift/transport/unix_socket.rb +1 -0
- data/lib/thrift/types.rb +1 -0
- data/lib/thrift/union.rb +1 -0
- data/lib/thrift/uuid.rb +1 -0
- data/lib/thrift.rb +1 -0
- data/spec/ThriftSpec.thrift +16 -0
- data/spec/base_protocol_spec.rb +20 -0
- data/spec/base_transport_spec.rb +42 -1
- data/spec/binary_protocol_accelerated_spec.rb +1 -0
- data/spec/binary_protocol_spec.rb +1 -0
- data/spec/binary_protocol_spec_shared.rb +79 -15
- data/spec/bytes_spec.rb +19 -1
- data/spec/client_spec.rb +1 -0
- data/spec/compact_protocol_spec.rb +99 -0
- data/spec/constants_demo_spec.rb +1 -0
- data/spec/exception_spec.rb +1 -0
- data/spec/flat_spec.rb +1 -0
- data/spec/header_protocol_spec.rb +1 -0
- data/spec/header_transport_spec.rb +45 -0
- data/spec/http_client_spec.rb +1 -0
- data/spec/json_protocol_spec.rb +26 -4
- data/spec/multiplexed_processor_spec.rb +75 -0
- data/spec/namespaced_spec.rb +1 -0
- data/spec/nonblocking_server_spec.rb +86 -18
- data/spec/processor_spec.rb +1 -0
- data/spec/recursion_depth_spec.rb +223 -0
- data/spec/serializer_spec.rb +1 -0
- data/spec/server_socket_spec.rb +37 -0
- data/spec/server_spec.rb +52 -0
- data/spec/socket_spec.rb +119 -12
- data/spec/socket_spec_shared.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/ssl_server_socket_spec.rb +40 -0
- data/spec/ssl_socket_spec.rb +171 -10
- data/spec/struct_nested_containers_spec.rb +1 -0
- data/spec/struct_spec.rb +1 -0
- data/spec/support/header_protocol_helper.rb +2 -1
- data/spec/thin_http_server_spec.rb +1 -0
- data/spec/types_spec.rb +1 -0
- data/spec/union_spec.rb +1 -0
- data/spec/unix_socket_spec.rb +16 -0
- data/spec/uuid_validation_spec.rb +1 -0
- data/test/fuzz/Makefile +779 -0
- data/test/fuzz/Makefile.in +775 -0
- data/test/fuzz/fuzz_common.rb +1 -0
- data/test/fuzz/fuzz_parse_binary_protocol.rb +1 -0
- data/test/fuzz/fuzz_parse_binary_protocol_accelerated.rb +1 -0
- data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +1 -0
- data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +1 -0
- data/test/fuzz/fuzz_parse_compact_protocol.rb +1 -0
- data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +1 -0
- data/test/fuzz/fuzz_parse_json_protocol.rb +1 -0
- data/test/fuzz/fuzz_parse_json_protocol_harness.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_json_protocol.rb +1 -0
- data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +1 -0
- data/test/fuzz/fuzz_tracer.rb +1 -0
- metadata +12 -46
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
# or more contributor license agreements. See the NOTICE file
|
|
5
|
+
# distributed with this work for additional information
|
|
6
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
# to you under the Apache License, Version 2.0 (the
|
|
8
|
+
# "License"); you may not use this file except in compliance
|
|
9
|
+
# with the License. You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing,
|
|
14
|
+
# software distributed under the License is distributed on an
|
|
15
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
# KIND, either express or implied. See the License for the
|
|
17
|
+
# specific language governing permissions and limitations
|
|
18
|
+
# under the License.
|
|
19
|
+
#
|
|
20
|
+
|
|
21
|
+
require 'spec_helper'
|
|
22
|
+
|
|
23
|
+
describe Thrift::MultiplexedProcessor do
|
|
24
|
+
before(:each) do
|
|
25
|
+
@processor = Thrift::MultiplexedProcessor.new
|
|
26
|
+
@iprot = double('MockInputProtocol')
|
|
27
|
+
@oprot = double('MockOutputProtocol')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'dispatches multiplexed calls to the registered service processor' do
|
|
31
|
+
actual_processor = double('ActualProcessor')
|
|
32
|
+
@processor.register_processor('ThriftTest', actual_processor)
|
|
33
|
+
|
|
34
|
+
expect(@iprot).to receive(:read_message_begin).and_return(['ThriftTest:testVoid', Thrift::MessageTypes::CALL, 1])
|
|
35
|
+
expect(actual_processor).to receive(:process) do |stored_protocol, oprot|
|
|
36
|
+
expect(stored_protocol.read_message_begin).to eq(['testVoid', Thrift::MessageTypes::CALL, 1])
|
|
37
|
+
expect(oprot).to eq(@oprot)
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
expect(@processor.process(@iprot, @oprot)).to eq(true)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'dispatches non-multiplexed calls to the default processor' do
|
|
45
|
+
default_processor = double('DefaultProcessor')
|
|
46
|
+
@processor.register_default(default_processor)
|
|
47
|
+
|
|
48
|
+
expect(@iprot).to receive(:read_message_begin).and_return(['testVoid', Thrift::MessageTypes::CALL, 2])
|
|
49
|
+
expect(default_processor).to receive(:process) do |stored_protocol, oprot|
|
|
50
|
+
expect(stored_protocol.read_message_begin).to eq(['testVoid', Thrift::MessageTypes::CALL, 2])
|
|
51
|
+
expect(oprot).to eq(@oprot)
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
expect(@processor.process(@iprot, @oprot)).to eq(true)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'raises for non-multiplexed calls when no default processor is registered' do
|
|
59
|
+
expect(@iprot).to receive(:read_message_begin).and_return(['testVoid', Thrift::MessageTypes::CALL, 3])
|
|
60
|
+
|
|
61
|
+
expect { @processor.process(@iprot, @oprot) }.to raise_error(
|
|
62
|
+
Thrift::Exception,
|
|
63
|
+
'Service name not found in message name: testVoid. Did you forget to use a Thrift::Protocol::MultiplexedProtocol in your client?'
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'raises for unknown multiplexed service names' do
|
|
68
|
+
expect(@iprot).to receive(:read_message_begin).and_return(['Missing:testVoid', Thrift::MessageTypes::CALL, 4])
|
|
69
|
+
|
|
70
|
+
expect { @processor.process(@iprot, @oprot) }.to raise_error(
|
|
71
|
+
Thrift::Exception,
|
|
72
|
+
'Service name not found: Missing. Did you forget to call Thrift::MultiplexedProcessor#register_processor?'
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
data/spec/namespaced_spec.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
#
|
|
2
3
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
4
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -101,7 +102,7 @@ describe 'NonblockingServer' do
|
|
|
101
102
|
|
|
102
103
|
describe Thrift::NonblockingServer do
|
|
103
104
|
before(:each) do
|
|
104
|
-
@port =
|
|
105
|
+
@port = available_port
|
|
105
106
|
handler = Handler.new
|
|
106
107
|
processor = SpecNamespace::NonblockingService::Processor.new(handler)
|
|
107
108
|
queue = Queue.new
|
|
@@ -121,6 +122,7 @@ describe 'NonblockingServer' do
|
|
|
121
122
|
end
|
|
122
123
|
end
|
|
123
124
|
queue.pop
|
|
125
|
+
wait_until_listening(@transport, @server_thread)
|
|
124
126
|
|
|
125
127
|
@clients = []
|
|
126
128
|
@catch_exceptions = false
|
|
@@ -128,9 +130,11 @@ describe 'NonblockingServer' do
|
|
|
128
130
|
|
|
129
131
|
after(:each) do
|
|
130
132
|
@clients.each { |client, trans| trans.close }
|
|
131
|
-
|
|
132
|
-
@server_thread.
|
|
133
|
-
@
|
|
133
|
+
@server.shutdown(1, false) if @server
|
|
134
|
+
@server_thread.join(2) if @server_thread
|
|
135
|
+
@server_thread.kill if @server_thread && @server_thread.alive?
|
|
136
|
+
@server_thread.join(2) if @server_thread
|
|
137
|
+
@transport.close if @transport
|
|
134
138
|
end
|
|
135
139
|
|
|
136
140
|
def setup_client(queue = nil)
|
|
@@ -261,6 +265,70 @@ describe 'NonblockingServer' do
|
|
|
261
265
|
end
|
|
262
266
|
end
|
|
263
267
|
|
|
268
|
+
describe Thrift::NonblockingServer::IOManager do
|
|
269
|
+
def build_io_manager
|
|
270
|
+
logger = Logger.new(IO::NULL)
|
|
271
|
+
logger.level = Logger::FATAL
|
|
272
|
+
Thrift::NonblockingServer::IOManager.new(
|
|
273
|
+
double('processor'),
|
|
274
|
+
double('server_transport'),
|
|
275
|
+
Thrift::BaseTransportFactory.new,
|
|
276
|
+
Thrift::BinaryProtocolFactory.new,
|
|
277
|
+
1,
|
|
278
|
+
logger
|
|
279
|
+
)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it "closes tracked connections and signal pipes during forced cleanup" do
|
|
283
|
+
io_manager = build_io_manager
|
|
284
|
+
connection = double('connection', :close => nil)
|
|
285
|
+
pipe_a = double('pipe_a', :closed? => false, :close => nil)
|
|
286
|
+
pipe_b = double('pipe_b', :closed? => false, :close => nil)
|
|
287
|
+
|
|
288
|
+
io_manager.instance_variable_set(:@connections, [connection])
|
|
289
|
+
io_manager.instance_variable_set(:@buffers, { connection => 'frame' })
|
|
290
|
+
io_manager.instance_variable_set(:@signal_pipes, [pipe_a, pipe_b])
|
|
291
|
+
io_manager.instance_variable_set(:@worker_threads, [])
|
|
292
|
+
|
|
293
|
+
io_manager.ensure_closed
|
|
294
|
+
|
|
295
|
+
expect(connection).to have_received(:close)
|
|
296
|
+
expect(pipe_a).to have_received(:close)
|
|
297
|
+
expect(pipe_b).to have_received(:close)
|
|
298
|
+
expect(io_manager.instance_variable_get(:@connections)).to be_empty
|
|
299
|
+
expect(io_manager.instance_variable_get(:@buffers)).to be_empty
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it "continues closing remaining signal pipes when one close raises" do
|
|
303
|
+
io_manager = build_io_manager
|
|
304
|
+
pipe_a = double('pipe_a', :closed? => false)
|
|
305
|
+
pipe_b = double('pipe_b', :closed? => false, :close => nil)
|
|
306
|
+
|
|
307
|
+
allow(pipe_a).to receive(:close).and_raise(IOError)
|
|
308
|
+
|
|
309
|
+
io_manager.instance_variable_set(:@signal_pipes, [pipe_a, pipe_b])
|
|
310
|
+
io_manager.instance_variable_set(:@worker_threads, [])
|
|
311
|
+
|
|
312
|
+
io_manager.send(:close_signal_pipes)
|
|
313
|
+
|
|
314
|
+
expect(pipe_a).to have_received(:close)
|
|
315
|
+
expect(pipe_b).to have_received(:close)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it "drops removed connections from bookkeeping" do
|
|
319
|
+
io_manager = build_io_manager
|
|
320
|
+
connection = double('connection', :close => nil)
|
|
321
|
+
|
|
322
|
+
io_manager.instance_variable_set(:@connections, [connection])
|
|
323
|
+
io_manager.instance_variable_set(:@buffers, { connection => 'frame' })
|
|
324
|
+
|
|
325
|
+
io_manager.send(:remove_connection, connection)
|
|
326
|
+
|
|
327
|
+
expect(io_manager.instance_variable_get(:@connections)).to be_empty
|
|
328
|
+
expect(io_manager.instance_variable_get(:@buffers)).to be_empty
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
264
332
|
describe "#{Thrift::NonblockingServer} with TLS transport" do
|
|
265
333
|
before(:each) do
|
|
266
334
|
@port = available_port
|
|
@@ -282,7 +350,7 @@ describe 'NonblockingServer' do
|
|
|
282
350
|
end
|
|
283
351
|
|
|
284
352
|
@clients = []
|
|
285
|
-
wait_until_listening
|
|
353
|
+
wait_until_listening(@transport, @server_thread)
|
|
286
354
|
end
|
|
287
355
|
|
|
288
356
|
after(:each) do
|
|
@@ -313,19 +381,6 @@ describe 'NonblockingServer' do
|
|
|
313
381
|
client
|
|
314
382
|
end
|
|
315
383
|
|
|
316
|
-
def wait_until_listening
|
|
317
|
-
Timeout.timeout(2) do
|
|
318
|
-
until @transport.handle
|
|
319
|
-
raise "Server thread exited unexpectedly" unless @server_thread.alive?
|
|
320
|
-
sleep 0.01
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
def available_port
|
|
326
|
-
TCPServer.open('localhost', 0) { |server| server.addr[1] }
|
|
327
|
-
end
|
|
328
|
-
|
|
329
384
|
def ssl_keys_dir
|
|
330
385
|
File.expand_path('../../../test/keys', __dir__)
|
|
331
386
|
end
|
|
@@ -358,4 +413,17 @@ describe 'NonblockingServer' do
|
|
|
358
413
|
end
|
|
359
414
|
end
|
|
360
415
|
end
|
|
416
|
+
|
|
417
|
+
def wait_until_listening(server_transport, server_thread)
|
|
418
|
+
Timeout.timeout(2) do
|
|
419
|
+
until server_transport.handle
|
|
420
|
+
raise "Server thread exited unexpectedly" unless server_thread.alive?
|
|
421
|
+
sleep 0.01
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def available_port
|
|
427
|
+
TCPServer.open('localhost', 0) { |server| server.addr[1] }
|
|
428
|
+
end
|
|
361
429
|
end
|
data/spec/processor_spec.rb
CHANGED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
# or more contributor license agreements. See the NOTICE file
|
|
5
|
+
# distributed with this work for additional information
|
|
6
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
# to you under the Apache License, Version 2.0 (the
|
|
8
|
+
# "License"); you may not use this file except in compliance
|
|
9
|
+
# with the License. You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing,
|
|
14
|
+
# software distributed under the License is distributed on an
|
|
15
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
# KIND, either express or implied. See the License for the
|
|
17
|
+
# specific language governing permissions and limitations
|
|
18
|
+
# under the License.
|
|
19
|
+
#
|
|
20
|
+
|
|
21
|
+
require 'spec_helper'
|
|
22
|
+
|
|
23
|
+
# Round-trip test for struct/union/exception recursion depth, driving the
|
|
24
|
+
# *generated* read/write path (Thrift::Struct#read/#write,
|
|
25
|
+
# Thrift::Union#read/#write) over RecTree / RecUnion / RecError from
|
|
26
|
+
# ThriftSpec.thrift; a linear chain is one struct level deeper per node, so a
|
|
27
|
+
# chain of N nodes reaches depth N.
|
|
28
|
+
#
|
|
29
|
+
# NOTE: the Ruby library does not enforce a recursion-depth limit yet
|
|
30
|
+
# (THRIFT-6045). The round-trip / within-limit examples are active; the
|
|
31
|
+
# limit-enforcement (over-limit) examples are `pending` and will start passing
|
|
32
|
+
# once the limit is implemented, at which point RSpec flags them to be enabled.
|
|
33
|
+
describe 'recursion depth limit' do
|
|
34
|
+
# The intended struct/union nesting limit. The Ruby library does not enforce a
|
|
35
|
+
# recursion-depth limit yet (THRIFT-6045); the limit-enforcement examples below
|
|
36
|
+
# are therefore `pending` until it is implemented. 64 matches the limit other
|
|
37
|
+
# Thrift libraries use.
|
|
38
|
+
RECURSION_LIMIT = 64
|
|
39
|
+
|
|
40
|
+
# Attached to the pending (over-limit) examples.
|
|
41
|
+
PENDING_REASON = 'recursion-depth limit not implemented in the Ruby library yet (THRIFT-6045)'
|
|
42
|
+
|
|
43
|
+
def binary_protocol
|
|
44
|
+
Thrift::BinaryProtocol.new(Thrift::MemoryBufferTransport.new)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# A linearly nested RecTree that is `depth` struct levels deep.
|
|
48
|
+
def struct_chain(depth)
|
|
49
|
+
node = SpecNamespace::RecTree.new(item: depth, children: [])
|
|
50
|
+
node.children = [struct_chain(depth - 1)] if depth > 1
|
|
51
|
+
node
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tree_depth(node)
|
|
55
|
+
n = 0
|
|
56
|
+
until node.nil?
|
|
57
|
+
n += 1
|
|
58
|
+
break if node.children.nil? || node.children.empty?
|
|
59
|
+
node = node.children.first
|
|
60
|
+
end
|
|
61
|
+
n
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# A linearly nested RecUnion that is `depth` levels deep (each union holds the
|
|
65
|
+
# next; the innermost holds a scalar leaf).
|
|
66
|
+
def union_chain(depth)
|
|
67
|
+
if depth > 1
|
|
68
|
+
SpecNamespace::RecUnion.new(children: [union_chain(depth - 1)])
|
|
69
|
+
else
|
|
70
|
+
SpecNamespace::RecUnion.new(leaf: 0)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A linearly nested RecError exception that is `depth` struct levels deep.
|
|
75
|
+
# Exceptions read/write through the same generated path as structs, so
|
|
76
|
+
# tree_depth applies to the decoded chain too.
|
|
77
|
+
def error_chain(depth)
|
|
78
|
+
node = SpecNamespace::RecError.new(leaf: depth, children: [])
|
|
79
|
+
node.children = [error_chain(depth - 1)] if depth > 1
|
|
80
|
+
node
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Emit, via raw protocol calls (which carry no depth guard), the wire image of
|
|
84
|
+
# a RecTree chain `depth` levels deep. This lets the read tests feed an
|
|
85
|
+
# over-limit payload that the guarded writer would itself refuse to produce.
|
|
86
|
+
def write_raw_tree(oprot, depth)
|
|
87
|
+
oprot.write_struct_begin('RecTree')
|
|
88
|
+
oprot.write_field_begin('children', Thrift::Types::LIST, 1)
|
|
89
|
+
oprot.write_list_begin(Thrift::Types::STRUCT, depth > 1 ? 1 : 0)
|
|
90
|
+
write_raw_tree(oprot, depth - 1) if depth > 1
|
|
91
|
+
oprot.write_list_end
|
|
92
|
+
oprot.write_field_end
|
|
93
|
+
oprot.write_field_begin('item', Thrift::Types::I16, 2)
|
|
94
|
+
oprot.write_i16(depth)
|
|
95
|
+
oprot.write_field_end
|
|
96
|
+
oprot.write_field_stop
|
|
97
|
+
oprot.write_struct_end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Same as write_raw_tree but for the RecError exception (leaf is i32 here).
|
|
101
|
+
def write_raw_error(oprot, depth)
|
|
102
|
+
oprot.write_struct_begin('RecError')
|
|
103
|
+
oprot.write_field_begin('children', Thrift::Types::LIST, 1)
|
|
104
|
+
oprot.write_list_begin(Thrift::Types::STRUCT, depth > 1 ? 1 : 0)
|
|
105
|
+
write_raw_error(oprot, depth - 1) if depth > 1
|
|
106
|
+
oprot.write_list_end
|
|
107
|
+
oprot.write_field_end
|
|
108
|
+
oprot.write_field_begin('leaf', Thrift::Types::I32, 2)
|
|
109
|
+
oprot.write_i32(depth)
|
|
110
|
+
oprot.write_field_end
|
|
111
|
+
oprot.write_field_stop
|
|
112
|
+
oprot.write_struct_end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def expect_depth_limit
|
|
116
|
+
expect { yield }.to raise_error(Thrift::ProtocolException) { |e|
|
|
117
|
+
expect(e.type).to eq(Thrift::ProtocolException::DEPTH_LIMIT)
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe 'structs' do
|
|
122
|
+
it 'round-trips a chain at the limit' do
|
|
123
|
+
prot = binary_protocol
|
|
124
|
+
struct_chain(RECURSION_LIMIT).write(prot)
|
|
125
|
+
result = SpecNamespace::RecTree.new
|
|
126
|
+
result.read(prot)
|
|
127
|
+
expect(tree_depth(result)).to eq(RECURSION_LIMIT)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'rejects writing a chain past the limit' do
|
|
131
|
+
pending PENDING_REASON
|
|
132
|
+
expect_depth_limit { struct_chain(RECURSION_LIMIT + 1).write(binary_protocol) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'rejects reading a payload past the limit' do
|
|
136
|
+
pending PENDING_REASON
|
|
137
|
+
prot = binary_protocol
|
|
138
|
+
write_raw_tree(prot, RECURSION_LIMIT + 1)
|
|
139
|
+
expect_depth_limit { SpecNamespace::RecTree.new.read(prot) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'round-trips a wide shallow tree (counter unwinds per sibling)' do
|
|
143
|
+
width = RECURSION_LIMIT * 3
|
|
144
|
+
prot = binary_protocol
|
|
145
|
+
root = SpecNamespace::RecTree.new(
|
|
146
|
+
item: 0,
|
|
147
|
+
children: (1..width).map { |i| SpecNamespace::RecTree.new(item: i, children: []) }
|
|
148
|
+
)
|
|
149
|
+
root.write(prot)
|
|
150
|
+
result = SpecNamespace::RecTree.new
|
|
151
|
+
result.read(prot)
|
|
152
|
+
expect(result.children.size).to eq(width)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe 'unions' do
|
|
157
|
+
it 'round-trips a chain at the limit' do
|
|
158
|
+
prot = binary_protocol
|
|
159
|
+
union_chain(RECURSION_LIMIT).write(prot)
|
|
160
|
+
expect { SpecNamespace::RecUnion.new.read(prot) }.not_to raise_error
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'rejects writing a chain past the limit' do
|
|
164
|
+
pending PENDING_REASON
|
|
165
|
+
expect_depth_limit { union_chain(RECURSION_LIMIT + 1).write(binary_protocol) }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
describe 'exceptions' do
|
|
170
|
+
it 'round-trips a chain at the limit' do
|
|
171
|
+
prot = binary_protocol
|
|
172
|
+
error_chain(RECURSION_LIMIT).write(prot)
|
|
173
|
+
result = SpecNamespace::RecError.new
|
|
174
|
+
result.read(prot)
|
|
175
|
+
expect(tree_depth(result)).to eq(RECURSION_LIMIT)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'rejects writing a chain past the limit' do
|
|
179
|
+
pending PENDING_REASON
|
|
180
|
+
expect_depth_limit { error_chain(RECURSION_LIMIT + 1).write(binary_protocol) }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'rejects reading a payload past the limit' do
|
|
184
|
+
pending PENDING_REASON
|
|
185
|
+
prot = binary_protocol
|
|
186
|
+
write_raw_error(prot, RECURSION_LIMIT + 1)
|
|
187
|
+
expect_depth_limit { SpecNamespace::RecError.new.read(prot) }
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe 'protocol decorators' do
|
|
192
|
+
# A struct written through a decorator must still be bounded and must not
|
|
193
|
+
# crash: decorators (MultiplexedProtocol via ProtocolDecorator) do not chain
|
|
194
|
+
# BaseProtocol#initialize, so the depth counter starts unset on that object.
|
|
195
|
+
it 'round-trips a struct through a MultiplexedProtocol' do
|
|
196
|
+
mprot = Thrift::MultiplexedProtocol.new(binary_protocol, 'svc')
|
|
197
|
+
struct_chain(3).write(mprot)
|
|
198
|
+
result = SpecNamespace::RecTree.new
|
|
199
|
+
result.read(mprot)
|
|
200
|
+
expect(tree_depth(result)).to eq(3)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Only present when the native (thrift_native) extension is loaded, which
|
|
205
|
+
# also makes Thrift::Struct#read/#write the native implementations -- the path
|
|
206
|
+
# that must enforce the limit in C, not just in pure Ruby.
|
|
207
|
+
if defined? Thrift::BinaryProtocolAccelerated
|
|
208
|
+
describe 'accelerated binary protocol' do
|
|
209
|
+
it 'rejects writing a chain past the limit' do
|
|
210
|
+
pending PENDING_REASON
|
|
211
|
+
prot = Thrift::BinaryProtocolAccelerated.new(Thrift::MemoryBufferTransport.new)
|
|
212
|
+
expect_depth_limit { struct_chain(RECURSION_LIMIT + 1).write(prot) }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'rejects reading a payload past the limit' do
|
|
216
|
+
pending PENDING_REASON
|
|
217
|
+
prot = Thrift::BinaryProtocolAccelerated.new(Thrift::MemoryBufferTransport.new)
|
|
218
|
+
write_raw_tree(prot, RECURSION_LIMIT + 1)
|
|
219
|
+
expect_depth_limit { SpecNamespace::RecTree.new.read(prot) }
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
data/spec/serializer_spec.rb
CHANGED
data/spec/server_socket_spec.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
#
|
|
2
3
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
4
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -47,10 +48,46 @@ describe 'Thrift::ServerSocket' do
|
|
|
47
48
|
expect(handle).to receive(:accept).and_return(sock)
|
|
48
49
|
trans = double("Socket")
|
|
49
50
|
expect(Thrift::Socket).to receive(:new).and_return(trans)
|
|
51
|
+
expect(trans).to receive(:timeout=).with(Thrift::BaseServerTransport::DEFAULT_CLIENT_TIMEOUT)
|
|
50
52
|
expect(trans).to receive(:handle=).with(sock)
|
|
51
53
|
expect(@socket.accept).to eq(trans)
|
|
52
54
|
end
|
|
53
55
|
|
|
56
|
+
it "should default accepted sockets to a finite client timeout" do
|
|
57
|
+
expect(@socket.client_timeout).to eq(5)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "should accept a custom client timeout" do
|
|
61
|
+
@socket = Thrift::ServerSocket.new(1234, client_timeout: 2.5)
|
|
62
|
+
expect(@socket.client_timeout).to eq(2.5)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should allow blocking accepted sockets with nil or zero client timeout" do
|
|
66
|
+
expect(Thrift::ServerSocket.new(1234, client_timeout: nil).client_timeout).to be_nil
|
|
67
|
+
expect(Thrift::ServerSocket.new(1234, client_timeout: 0).client_timeout).to eq(0)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "should use the accepted socket timeout for writes" do
|
|
71
|
+
handle = double("TCPServer")
|
|
72
|
+
allow(TCPServer).to receive(:new).with(nil, 1234).and_return(handle)
|
|
73
|
+
@socket.listen
|
|
74
|
+
|
|
75
|
+
sock = double("sock", :closed? => false)
|
|
76
|
+
allow(sock).to receive(:setsockopt)
|
|
77
|
+
allow(handle).to receive(:accept).and_return(sock)
|
|
78
|
+
|
|
79
|
+
transport = @socket.accept
|
|
80
|
+
expect(transport.timeout).to eq(Thrift::BaseServerTransport::DEFAULT_CLIENT_TIMEOUT)
|
|
81
|
+
|
|
82
|
+
expect(sock).to receive(:write_nonblock).with("test data").and_raise(IO::EAGAINWaitWritable)
|
|
83
|
+
expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 100.0, 105.1)
|
|
84
|
+
expect(IO).to receive(:select).with(nil, [sock], nil, 5.0).and_return(nil)
|
|
85
|
+
|
|
86
|
+
expect { transport.write("test data") }.to raise_error(Thrift::TransportException) do |e|
|
|
87
|
+
expect(e.type).to eq(Thrift::TransportException::TIMED_OUT)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
54
91
|
it "should close the handle when closed" do
|
|
55
92
|
handle = double("TCPServer", :closed? => false)
|
|
56
93
|
expect(TCPServer).to receive(:new).with(nil, 1234).and_return(handle)
|
data/spec/server_spec.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
#
|
|
2
3
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
4
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
# under the License.
|
|
18
19
|
#
|
|
19
20
|
require 'spec_helper'
|
|
21
|
+
require 'openssl'
|
|
20
22
|
|
|
21
23
|
describe 'Server' do
|
|
22
24
|
describe Thrift::BaseServer do
|
|
@@ -80,6 +82,30 @@ describe 'Server' do
|
|
|
80
82
|
expect(@serverTrans).to receive(:close).ordered
|
|
81
83
|
expect { @server.serve }.to throw_symbol(:stop)
|
|
82
84
|
end
|
|
85
|
+
|
|
86
|
+
it "should continue serving after accept raises Errno::ECONNRESET" do
|
|
87
|
+
expect(@serverTrans).to receive(:listen).ordered
|
|
88
|
+
expect(@serverTrans).to receive(:accept).ordered.and_raise(Errno::ECONNRESET)
|
|
89
|
+
expect(@serverTrans).to receive(:accept).ordered.and_return(@client)
|
|
90
|
+
expect(@trans).to receive(:get_transport).once.with(@client).and_return(@trans)
|
|
91
|
+
expect(@prot).to receive(:get_protocol).once.with(@trans).and_return(@prot)
|
|
92
|
+
expect(@processor).to receive(:process).once.with(@prot, @prot) { throw :stop }
|
|
93
|
+
expect(@trans).to receive(:close).once
|
|
94
|
+
expect(@serverTrans).to receive(:close).ordered
|
|
95
|
+
expect { @server.serve }.to throw_symbol(:stop)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "should continue serving after accept raises OpenSSL::SSL::SSLError" do
|
|
99
|
+
expect(@serverTrans).to receive(:listen).ordered
|
|
100
|
+
expect(@serverTrans).to receive(:accept).ordered.and_raise(OpenSSL::SSL::SSLError)
|
|
101
|
+
expect(@serverTrans).to receive(:accept).ordered.and_return(@client)
|
|
102
|
+
expect(@trans).to receive(:get_transport).once.with(@client).and_return(@trans)
|
|
103
|
+
expect(@prot).to receive(:get_protocol).once.with(@trans).and_return(@prot)
|
|
104
|
+
expect(@processor).to receive(:process).once.with(@prot, @prot) { throw :stop }
|
|
105
|
+
expect(@trans).to receive(:close).once
|
|
106
|
+
expect(@serverTrans).to receive(:close).ordered
|
|
107
|
+
expect { @server.serve }.to throw_symbol(:stop)
|
|
108
|
+
end
|
|
83
109
|
end
|
|
84
110
|
|
|
85
111
|
describe Thrift::ThreadedServer do
|
|
@@ -117,6 +143,32 @@ describe 'Server' do
|
|
|
117
143
|
expect(@serverTrans).to receive(:close).ordered
|
|
118
144
|
expect { @server.serve }.to throw_symbol(:stop)
|
|
119
145
|
end
|
|
146
|
+
|
|
147
|
+
it "should continue serving after accept raises Errno::ECONNRESET" do
|
|
148
|
+
expect(@serverTrans).to receive(:listen).ordered
|
|
149
|
+
expect(@serverTrans).to receive(:accept).ordered.and_raise(Errno::ECONNRESET)
|
|
150
|
+
expect(@serverTrans).to receive(:accept).ordered.and_return(@client)
|
|
151
|
+
expect(@trans).to receive(:get_transport).once.with(@client).and_return(@trans)
|
|
152
|
+
expect(@prot).to receive(:get_protocol).once.with(@trans).and_return(@prot)
|
|
153
|
+
expect(Thread).to receive(:new).with(@prot, @trans).once.and_yield(@prot, @trans)
|
|
154
|
+
expect(@processor).to receive(:process).once.with(@prot, @prot) { throw :stop }
|
|
155
|
+
expect(@trans).to receive(:close).once
|
|
156
|
+
expect(@serverTrans).to receive(:close).ordered
|
|
157
|
+
expect { @server.serve }.to throw_symbol(:stop)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "should continue serving after accept raises OpenSSL::SSL::SSLError" do
|
|
161
|
+
expect(@serverTrans).to receive(:listen).ordered
|
|
162
|
+
expect(@serverTrans).to receive(:accept).ordered.and_raise(OpenSSL::SSL::SSLError)
|
|
163
|
+
expect(@serverTrans).to receive(:accept).ordered.and_return(@client)
|
|
164
|
+
expect(@trans).to receive(:get_transport).once.with(@client).and_return(@trans)
|
|
165
|
+
expect(@prot).to receive(:get_protocol).once.with(@trans).and_return(@prot)
|
|
166
|
+
expect(Thread).to receive(:new).with(@prot, @trans).once.and_yield(@prot, @trans)
|
|
167
|
+
expect(@processor).to receive(:process).once.with(@prot, @prot) { throw :stop }
|
|
168
|
+
expect(@trans).to receive(:close).once
|
|
169
|
+
expect(@serverTrans).to receive(:close).ordered
|
|
170
|
+
expect { @server.serve }.to throw_symbol(:stop)
|
|
171
|
+
end
|
|
120
172
|
end
|
|
121
173
|
|
|
122
174
|
describe Thrift::ThreadPoolServer do
|