thrift 0.22.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +235 -17
  3. data/benchmark/benchmark.rb +23 -8
  4. data/benchmark/client.rb +50 -6
  5. data/benchmark/server.rb +46 -7
  6. data/benchmark/thin_server.rb +2 -0
  7. data/ext/binary_protocol_accelerated.c +154 -42
  8. data/ext/bytes.c +14 -0
  9. data/ext/compact_protocol.c +138 -45
  10. data/ext/constants.h +12 -0
  11. data/ext/extconf.rb +17 -8
  12. data/ext/memory_buffer.c +44 -7
  13. data/ext/protocol.c +29 -0
  14. data/ext/protocol.h +35 -0
  15. data/ext/struct.c +48 -17
  16. data/ext/thrift_native.c +28 -3
  17. data/lib/thrift/bytes.rb +70 -100
  18. data/lib/thrift/client.rb +54 -13
  19. data/lib/thrift/exceptions.rb +6 -5
  20. data/lib/thrift/multiplexed_processor.rb +20 -10
  21. data/lib/thrift/processor.rb +7 -6
  22. data/lib/thrift/protocol/base_protocol.rb +52 -21
  23. data/lib/thrift/protocol/binary_protocol.rb +65 -20
  24. data/lib/thrift/protocol/binary_protocol_accelerated.rb +6 -5
  25. data/lib/thrift/protocol/compact_protocol.rb +76 -41
  26. data/lib/thrift/protocol/header_protocol.rb +321 -0
  27. data/lib/thrift/protocol/json_protocol.rb +44 -27
  28. data/lib/thrift/protocol/multiplexed_protocol.rb +6 -5
  29. data/lib/thrift/protocol/protocol_decorator.rb +13 -4
  30. data/lib/thrift/serializer/deserializer.rb +6 -5
  31. data/lib/thrift/serializer/serializer.rb +5 -5
  32. data/lib/thrift/server/base_server.rb +5 -4
  33. data/lib/thrift/server/mongrel_http_server.rb +7 -6
  34. data/lib/thrift/server/nonblocking_server.rb +35 -9
  35. data/lib/thrift/server/simple_server.rb +13 -5
  36. data/lib/thrift/server/thin_http_server.rb +4 -3
  37. data/lib/thrift/server/thread_pool_server.rb +7 -6
  38. data/lib/thrift/server/threaded_server.rb +13 -5
  39. data/lib/thrift/struct.rb +12 -11
  40. data/lib/thrift/struct_union.rb +14 -9
  41. data/lib/thrift/thrift_native.rb +3 -2
  42. data/lib/thrift/transport/base_server_transport.rb +8 -5
  43. data/lib/thrift/transport/base_transport.rb +18 -14
  44. data/lib/thrift/transport/buffered_transport.rb +7 -6
  45. data/lib/thrift/transport/framed_transport.rb +8 -7
  46. data/lib/thrift/transport/header_transport.rb +562 -0
  47. data/lib/thrift/transport/http_client_transport.rb +2 -1
  48. data/lib/thrift/transport/io_stream_transport.rb +4 -3
  49. data/lib/thrift/transport/memory_buffer_transport.rb +13 -6
  50. data/lib/thrift/transport/server_socket.rb +14 -8
  51. data/lib/thrift/transport/socket.rb +126 -60
  52. data/lib/thrift/transport/ssl_server_socket.rb +4 -3
  53. data/lib/thrift/transport/ssl_socket.rb +45 -13
  54. data/lib/thrift/transport/unix_server_socket.rb +9 -5
  55. data/lib/thrift/transport/unix_socket.rb +7 -6
  56. data/lib/thrift/types.rb +10 -6
  57. data/lib/thrift/union.rb +15 -8
  58. data/lib/thrift/uuid.rb +50 -0
  59. data/lib/thrift.rb +4 -1
  60. data/spec/ThriftSpec.thrift +21 -1
  61. data/spec/base_protocol_spec.rb +21 -2
  62. data/spec/base_transport_spec.rb +48 -8
  63. data/spec/binary_protocol_accelerated_spec.rb +1 -0
  64. data/spec/binary_protocol_spec.rb +1 -2
  65. data/spec/binary_protocol_spec_shared.rb +206 -155
  66. data/spec/bytes_spec.rb +71 -114
  67. data/spec/client_spec.rb +86 -19
  68. data/spec/compact_protocol_spec.rb +153 -16
  69. data/spec/constants_demo_spec.rb +102 -0
  70. data/spec/exception_spec.rb +1 -1
  71. data/spec/flat_spec.rb +1 -0
  72. data/spec/header_protocol_spec.rb +476 -0
  73. data/spec/header_transport_spec.rb +431 -0
  74. data/spec/http_client_spec.rb +5 -6
  75. data/spec/json_protocol_spec.rb +69 -47
  76. data/spec/multiplexed_processor_spec.rb +75 -0
  77. data/spec/namespaced_spec.rb +1 -1
  78. data/spec/nonblocking_server_spec.rb +174 -8
  79. data/spec/processor_spec.rb +1 -1
  80. data/spec/recursion_depth_spec.rb +223 -0
  81. data/spec/serializer_spec.rb +1 -1
  82. data/spec/server_socket_spec.rb +38 -1
  83. data/spec/server_spec.rb +60 -9
  84. data/spec/socket_spec.rb +119 -13
  85. data/spec/socket_spec_shared.rb +73 -9
  86. data/spec/spec_helper.rb +2 -1
  87. data/spec/ssl_server_socket_spec.rb +52 -1
  88. data/spec/ssl_socket_spec.rb +181 -11
  89. data/spec/struct_nested_containers_spec.rb +2 -2
  90. data/spec/struct_spec.rb +114 -9
  91. data/spec/support/header_protocol_helper.rb +55 -0
  92. data/spec/thin_http_server_spec.rb +4 -18
  93. data/spec/types_spec.rb +26 -26
  94. data/spec/union_spec.rb +70 -11
  95. data/spec/unix_socket_spec.rb +17 -2
  96. data/spec/uuid_validation_spec.rb +239 -0
  97. data/test/fuzz/Makefile +779 -0
  98. data/test/fuzz/Makefile.am +173 -0
  99. data/test/fuzz/Makefile.in +775 -0
  100. data/test/fuzz/README.md +149 -0
  101. data/test/fuzz/fuzz_common.rb +96 -0
  102. data/{lib/thrift/core_ext.rb → test/fuzz/fuzz_parse_binary_protocol.rb} +4 -4
  103. data/{lib/thrift/core_ext/fixnum.rb → test/fuzz/fuzz_parse_binary_protocol_accelerated.rb} +7 -13
  104. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +23 -0
  105. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +23 -0
  106. data/test/fuzz/fuzz_parse_compact_protocol.rb +23 -0
  107. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +23 -0
  108. data/test/fuzz/fuzz_parse_json_protocol.rb +23 -0
  109. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +23 -0
  110. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +23 -0
  111. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +23 -0
  112. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +23 -0
  113. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +23 -0
  114. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +23 -0
  115. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +23 -0
  116. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +23 -0
  117. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +23 -0
  118. data/test/fuzz/fuzz_tracer.rb +29 -0
  119. metadata +105 -70
@@ -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
@@ -20,7 +21,6 @@
20
21
  require 'spec_helper'
21
22
 
22
23
  describe 'Serializer' do
23
-
24
24
  describe Thrift::Serializer do
25
25
  it "should serialize structs to binary by default" do
26
26
  serializer = Thrift::Serializer.new(Thrift::BinaryProtocolAcceleratedFactory.new)
@@ -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
@@ -21,7 +22,6 @@ require 'spec_helper'
21
22
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
23
 
23
24
  describe 'Thrift::ServerSocket' do
24
-
25
25
  describe Thrift::ServerSocket do
26
26
  before(:each) do
27
27
  @socket = Thrift::ServerSocket.new(1234)
@@ -44,13 +44,50 @@ describe 'Thrift::ServerSocket' do
44
44
  expect(TCPServer).to receive(:new).with(nil, 1234).and_return(handle)
45
45
  @socket.listen
46
46
  sock = double("sock")
47
+ expect(sock).to receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
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,9 +18,9 @@
17
18
  # under the License.
18
19
  #
19
20
  require 'spec_helper'
21
+ require 'openssl'
20
22
 
21
23
  describe 'Server' do
22
-
23
24
  describe Thrift::BaseServer do
24
25
  before(:each) do
25
26
  @processor = double("Processor")
@@ -36,9 +37,9 @@ describe 'Server' do
36
37
  end
37
38
 
38
39
  it "should not serve" do
39
- expect { @server.serve()}.to raise_error(NotImplementedError)
40
+ expect { @server.serve() }.to raise_error(NotImplementedError)
40
41
  end
41
-
42
+
42
43
  it "should provide a reasonable to_s" do
43
44
  expect(@serverTrans).to receive(:to_s).once.and_return("serverTrans")
44
45
  expect(@trans).to receive(:to_s).once.and_return("trans")
@@ -56,14 +57,14 @@ describe 'Server' do
56
57
  @client = double("Client")
57
58
  @server = described_class.new(@processor, @serverTrans, @trans, @prot)
58
59
  end
59
-
60
+
60
61
  it "should provide a reasonable to_s" do
61
62
  expect(@serverTrans).to receive(:to_s).once.and_return("serverTrans")
62
63
  expect(@trans).to receive(:to_s).once.and_return("trans")
63
64
  expect(@prot).to receive(:to_s).once.and_return("prot")
64
65
  expect(@server.to_s).to eq("simple(server(prot(trans(serverTrans))))")
65
66
  end
66
-
67
+
67
68
  it "should serve in the main thread" do
68
69
  expect(@serverTrans).to receive(:listen).ordered
69
70
  expect(@serverTrans).to receive(:accept).exactly(3).times.and_return(@client)
@@ -81,6 +82,30 @@ describe 'Server' do
81
82
  expect(@serverTrans).to receive(:close).ordered
82
83
  expect { @server.serve }.to throw_symbol(:stop)
83
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
84
109
  end
85
110
 
86
111
  describe Thrift::ThreadedServer do
@@ -99,7 +124,7 @@ describe 'Server' do
99
124
  expect(@prot).to receive(:to_s).once.and_return("prot")
100
125
  expect(@server.to_s).to eq("threaded(server(prot(trans(serverTrans))))")
101
126
  end
102
-
127
+
103
128
  it "should serve using threads" do
104
129
  expect(@serverTrans).to receive(:listen).ordered
105
130
  expect(@serverTrans).to receive(:accept).exactly(3).times.and_return(@client)
@@ -118,6 +143,32 @@ describe 'Server' do
118
143
  expect(@serverTrans).to receive(:close).ordered
119
144
  expect { @server.serve }.to throw_symbol(:stop)
120
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
121
172
  end
122
173
 
123
174
  describe Thrift::ThreadPoolServer do
@@ -137,10 +188,10 @@ describe 'Server' do
137
188
  expect(@prot).to receive(:to_s).once.and_return("prot")
138
189
  expect(@server.to_s).to eq("threadpool(server(prot(trans(server_trans))))")
139
190
  end
140
-
191
+
141
192
  it "should serve inside a thread" do
142
193
  exception_q = @server.instance_variable_get(:@exception_q)
143
- expect_any_instance_of(described_class).to receive(:serve) do
194
+ expect_any_instance_of(described_class).to receive(:serve) do
144
195
  exception_q.push(StandardError.new('ERROR'))
145
196
  end
146
197
  expect { @server.rescuable_serve }.to(raise_error('ERROR'))
@@ -149,7 +200,7 @@ describe 'Server' do
149
200
 
150
201
  it "should avoid running the server twice when retrying rescuable_serve" do
151
202
  exception_q = @server.instance_variable_get(:@exception_q)
152
- expect_any_instance_of(described_class).to receive(:serve) do
203
+ expect_any_instance_of(described_class).to receive(:serve) do
153
204
  exception_q.push(StandardError.new('ERROR1'))
154
205
  exception_q.push(StandardError.new('ERROR2'))
155
206
  end
data/spec/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
@@ -21,48 +22,153 @@ require 'spec_helper'
21
22
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
23
 
23
24
  describe 'Socket' do
24
-
25
25
  describe Thrift::Socket do
26
26
  before(:each) do
27
27
  @socket = Thrift::Socket.new
28
+ @addrinfo = double("Addrinfo")
28
29
  @handle = double("Handle", :closed? => false)
29
30
  allow(@handle).to receive(:close)
30
- allow(@handle).to receive(:connect_nonblock)
31
31
  allow(@handle).to receive(:setsockopt)
32
- allow(::Socket).to receive(:new).and_return(@handle)
32
+ allow(@addrinfo).to receive(:connect).and_return(@handle)
33
+ allow(Addrinfo).to receive(:foreach).and_yield(@addrinfo)
33
34
  end
34
35
 
35
36
  it_should_behave_like "a socket"
36
37
 
37
38
  it "should raise a TransportException when it cannot open a socket" do
38
- expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]])
39
- expect { @socket.open }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) }
39
+ allow(Addrinfo).to receive(:foreach).and_raise(SocketError.new("lookup failed"))
40
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
41
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
42
+ expect(e.message).to eq("Could not connect to localhost:9090")
43
+ expect(e.cause).to be_a(SocketError)
44
+ end
40
45
  end
41
46
 
42
47
  it "should open a ::Socket with default args" do
43
- expect(::Socket).to receive(:new).and_return(double("Handle", :connect_nonblock => true, :setsockopt => nil))
44
- expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]])
45
- expect(::Socket).to receive(:sockaddr_in)
48
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo)
49
+ expect(@addrinfo).to receive(:connect).with(no_args).and_return(@handle)
50
+ expect(@handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
46
51
  @socket.to_s == "socket(localhost:9090)"
47
52
  @socket.open
48
53
  end
49
54
 
50
55
  it "should accept host/port options" do
51
- expect(::Socket).to receive(:new).and_return(double("Handle", :connect_nonblock => true, :setsockopt => nil))
52
- expect(::Socket).to receive(:getaddrinfo).with("my.domain", 1234, nil, ::Socket::SOCK_STREAM).and_return([[]])
53
- expect(::Socket).to receive(:sockaddr_in)
56
+ handle = double("Handle", :closed? => false)
57
+ allow(handle).to receive(:close)
58
+ expect(handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
59
+ expect(Addrinfo).to receive(:foreach).with("my.domain", 1234, nil, :STREAM).and_yield(@addrinfo)
60
+ expect(@addrinfo).to receive(:connect).with(no_args).and_return(handle)
54
61
  @socket = Thrift::Socket.new('my.domain', 1234).open
55
62
  @socket.to_s == "socket(my.domain:1234)"
56
63
  end
57
64
 
58
65
  it "should accept an optional timeout" do
59
- allow(::Socket).to receive(:new)
60
66
  expect(Thrift::Socket.new('localhost', 8080, 5).timeout).to eq(5)
61
67
  end
62
68
 
63
69
  it "should provide a reasonable to_s" do
64
- allow(::Socket).to receive(:new)
65
70
  expect(Thrift::Socket.new('myhost', 8090).to_s).to eq("socket(myhost:8090)")
66
71
  end
72
+
73
+ it "should pass the remaining timeout to each address attempt" do
74
+ @socket.timeout = 5
75
+ second_addrinfo = double("Addrinfo")
76
+
77
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 103.0)
78
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
79
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ECONNREFUSED)
80
+ expect(second_addrinfo).to receive(:connect).with(timeout: 2.0).and_return(@handle)
81
+ expect(@handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
82
+
83
+ expect(@socket.open).to eq(@handle)
84
+ end
85
+
86
+ it "should continue to the next address after a connect timeout while time remains" do
87
+ @socket.timeout = 5
88
+ second_addrinfo = double("Addrinfo")
89
+
90
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 103.0)
91
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
92
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ETIMEDOUT)
93
+ expect(second_addrinfo).to receive(:connect).with(timeout: 2.0).and_return(@handle)
94
+ expect(@handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
95
+
96
+ expect(@socket.open).to eq(@handle)
97
+ end
98
+
99
+ it "should raise TIMED_OUT when an address attempt times out" do
100
+ @socket.timeout = 5
101
+
102
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
103
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ETIMEDOUT)
104
+
105
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
106
+ expect(e.type).to eq(Thrift::TransportException::TIMED_OUT)
107
+ expect(e.message).to eq("Socket: Timed out opening connection to localhost:9090")
108
+ end
109
+ end
110
+
111
+ it "should raise TIMED_OUT when the deadline expires before the next address attempt" do
112
+ @socket.timeout = 5
113
+ second_addrinfo = double("Addrinfo")
114
+
115
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 105.0)
116
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
117
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ECONNREFUSED)
118
+ expect(second_addrinfo).not_to receive(:connect)
119
+
120
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
121
+ expect(e.type).to eq(Thrift::TransportException::TIMED_OUT)
122
+ expect(e.message).to eq("Socket: Timed out opening connection to localhost:9090")
123
+ end
124
+ end
125
+
126
+ it "should treat zero timeout as blocking for open" do
127
+ @socket.timeout = 0
128
+
129
+ expect(@addrinfo).to receive(:connect).with(no_args).and_return(@handle)
130
+ expect(@handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
131
+
132
+ expect(@socket.open).to eq(@handle)
133
+ end
134
+
135
+ it "should report the last connection error when all addresses fail" do
136
+ second_addrinfo = double("Addrinfo")
137
+
138
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
139
+ expect(@addrinfo).to receive(:connect).and_raise(Errno::ECONNREFUSED)
140
+ expect(second_addrinfo).to receive(:connect).and_raise(Errno::EHOSTUNREACH)
141
+
142
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
143
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
144
+ expect(e.message).to eq("Could not connect to localhost:9090")
145
+ expect(e.cause).to be_a(Errno::EHOSTUNREACH)
146
+ end
147
+ end
148
+
149
+ it "should close a connected candidate before falling back when socket setup fails" do
150
+ first_addrinfo = @addrinfo
151
+ second_addrinfo = double("Addrinfo")
152
+ first_handle = double("Handle", :closed? => false)
153
+ allow(first_handle).to receive(:close)
154
+
155
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(first_addrinfo).and_yield(second_addrinfo)
156
+ expect(first_addrinfo).to receive(:connect).with(no_args).and_return(first_handle)
157
+ expect(first_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1).and_raise(StandardError.new("setsockopt failed"))
158
+ expect(first_handle).to receive(:close)
159
+ expect(second_addrinfo).to receive(:connect).with(no_args).and_return(@handle)
160
+ expect(@handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
161
+
162
+ expect(@socket.open).to eq(@handle)
163
+ end
164
+
165
+ it "should avoid a blank error when no addresses are returned" do
166
+ allow(Addrinfo).to receive(:foreach)
167
+
168
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
169
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
170
+ expect(e.message).to eq("Could not connect to localhost:9090")
171
+ end
172
+ end
67
173
  end
68
174
  end