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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -33
  3. data/benchmark/benchmark.rb +2 -1
  4. data/benchmark/client.rb +1 -0
  5. data/benchmark/server.rb +1 -0
  6. data/benchmark/thin_server.rb +1 -0
  7. data/ext/binary_protocol_accelerated.c +78 -23
  8. data/ext/bytes.c +14 -0
  9. data/ext/compact_protocol.c +59 -31
  10. data/ext/extconf.rb +8 -0
  11. data/ext/memory_buffer.c +37 -0
  12. data/ext/struct.c +14 -14
  13. data/ext/thrift_native.c +1 -0
  14. data/lib/thrift/bytes.rb +4 -1
  15. data/lib/thrift/client.rb +1 -12
  16. data/lib/thrift/exceptions.rb +1 -0
  17. data/lib/thrift/multiplexed_processor.rb +14 -4
  18. data/lib/thrift/processor.rb +1 -0
  19. data/lib/thrift/protocol/base_protocol.rb +15 -6
  20. data/lib/thrift/protocol/binary_protocol.rb +41 -12
  21. data/lib/thrift/protocol/binary_protocol_accelerated.rb +1 -0
  22. data/lib/thrift/protocol/compact_protocol.rb +15 -4
  23. data/lib/thrift/protocol/header_protocol.rb +1 -0
  24. data/lib/thrift/protocol/json_protocol.rb +18 -11
  25. data/lib/thrift/protocol/multiplexed_protocol.rb +1 -0
  26. data/lib/thrift/protocol/protocol_decorator.rb +1 -0
  27. data/lib/thrift/serializer/deserializer.rb +1 -0
  28. data/lib/thrift/serializer/serializer.rb +1 -0
  29. data/lib/thrift/server/base_server.rb +1 -0
  30. data/lib/thrift/server/mongrel_http_server.rb +1 -0
  31. data/lib/thrift/server/nonblocking_server.rb +28 -2
  32. data/lib/thrift/server/simple_server.rb +9 -1
  33. data/lib/thrift/server/thin_http_server.rb +1 -0
  34. data/lib/thrift/server/thread_pool_server.rb +1 -0
  35. data/lib/thrift/server/threaded_server.rb +9 -1
  36. data/lib/thrift/struct.rb +1 -0
  37. data/lib/thrift/struct_union.rb +4 -9
  38. data/lib/thrift/thrift_native.rb +2 -1
  39. data/lib/thrift/transport/base_server_transport.rb +3 -0
  40. data/lib/thrift/transport/base_transport.rb +6 -2
  41. data/lib/thrift/transport/buffered_transport.rb +1 -0
  42. data/lib/thrift/transport/framed_transport.rb +1 -0
  43. data/lib/thrift/transport/header_transport.rb +47 -1
  44. data/lib/thrift/transport/http_client_transport.rb +1 -0
  45. data/lib/thrift/transport/io_stream_transport.rb +1 -0
  46. data/lib/thrift/transport/memory_buffer_transport.rb +7 -0
  47. data/lib/thrift/transport/server_socket.rb +6 -3
  48. data/lib/thrift/transport/socket.rb +68 -29
  49. data/lib/thrift/transport/ssl_server_socket.rb +3 -2
  50. data/lib/thrift/transport/ssl_socket.rb +43 -11
  51. data/lib/thrift/transport/unix_server_socket.rb +5 -1
  52. data/lib/thrift/transport/unix_socket.rb +1 -0
  53. data/lib/thrift/types.rb +1 -0
  54. data/lib/thrift/union.rb +1 -0
  55. data/lib/thrift/uuid.rb +1 -0
  56. data/lib/thrift.rb +1 -0
  57. data/spec/ThriftSpec.thrift +16 -0
  58. data/spec/base_protocol_spec.rb +20 -0
  59. data/spec/base_transport_spec.rb +42 -1
  60. data/spec/binary_protocol_accelerated_spec.rb +1 -0
  61. data/spec/binary_protocol_spec.rb +1 -0
  62. data/spec/binary_protocol_spec_shared.rb +79 -15
  63. data/spec/bytes_spec.rb +19 -1
  64. data/spec/client_spec.rb +1 -0
  65. data/spec/compact_protocol_spec.rb +99 -0
  66. data/spec/constants_demo_spec.rb +1 -0
  67. data/spec/exception_spec.rb +1 -0
  68. data/spec/flat_spec.rb +1 -0
  69. data/spec/header_protocol_spec.rb +1 -0
  70. data/spec/header_transport_spec.rb +45 -0
  71. data/spec/http_client_spec.rb +1 -0
  72. data/spec/json_protocol_spec.rb +26 -4
  73. data/spec/multiplexed_processor_spec.rb +75 -0
  74. data/spec/namespaced_spec.rb +1 -0
  75. data/spec/nonblocking_server_spec.rb +86 -18
  76. data/spec/processor_spec.rb +1 -0
  77. data/spec/recursion_depth_spec.rb +223 -0
  78. data/spec/serializer_spec.rb +1 -0
  79. data/spec/server_socket_spec.rb +37 -0
  80. data/spec/server_spec.rb +52 -0
  81. data/spec/socket_spec.rb +119 -12
  82. data/spec/socket_spec_shared.rb +1 -0
  83. data/spec/spec_helper.rb +1 -0
  84. data/spec/ssl_server_socket_spec.rb +40 -0
  85. data/spec/ssl_socket_spec.rb +171 -10
  86. data/spec/struct_nested_containers_spec.rb +1 -0
  87. data/spec/struct_spec.rb +1 -0
  88. data/spec/support/header_protocol_helper.rb +2 -1
  89. data/spec/thin_http_server_spec.rb +1 -0
  90. data/spec/types_spec.rb +1 -0
  91. data/spec/union_spec.rb +1 -0
  92. data/spec/unix_socket_spec.rb +16 -0
  93. data/spec/uuid_validation_spec.rb +1 -0
  94. data/test/fuzz/Makefile +779 -0
  95. data/test/fuzz/Makefile.in +775 -0
  96. data/test/fuzz/fuzz_common.rb +1 -0
  97. data/test/fuzz/fuzz_parse_binary_protocol.rb +1 -0
  98. data/test/fuzz/fuzz_parse_binary_protocol_accelerated.rb +1 -0
  99. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +1 -0
  100. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +1 -0
  101. data/test/fuzz/fuzz_parse_compact_protocol.rb +1 -0
  102. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +1 -0
  103. data/test/fuzz/fuzz_parse_json_protocol.rb +1 -0
  104. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +1 -0
  105. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +1 -0
  106. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +1 -0
  107. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +1 -0
  108. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +1 -0
  109. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +1 -0
  110. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +1 -0
  111. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +1 -0
  112. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +1 -0
  113. data/test/fuzz/fuzz_tracer.rb +1 -0
  114. 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
@@ -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
@@ -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 = 43251
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
- # @server.shutdown(1)
132
- @server_thread.kill
133
- @transport.close
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
@@ -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
@@ -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
@@ -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
@@ -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