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.
- checksums.yaml +4 -4
- data/README.md +235 -17
- data/benchmark/benchmark.rb +23 -8
- data/benchmark/client.rb +50 -6
- data/benchmark/server.rb +46 -7
- data/benchmark/thin_server.rb +2 -0
- data/ext/binary_protocol_accelerated.c +154 -42
- data/ext/bytes.c +14 -0
- data/ext/compact_protocol.c +138 -45
- data/ext/constants.h +12 -0
- data/ext/extconf.rb +17 -8
- data/ext/memory_buffer.c +44 -7
- data/ext/protocol.c +29 -0
- data/ext/protocol.h +35 -0
- data/ext/struct.c +48 -17
- data/ext/thrift_native.c +28 -3
- data/lib/thrift/bytes.rb +70 -100
- data/lib/thrift/client.rb +54 -13
- data/lib/thrift/exceptions.rb +6 -5
- data/lib/thrift/multiplexed_processor.rb +20 -10
- data/lib/thrift/processor.rb +7 -6
- data/lib/thrift/protocol/base_protocol.rb +52 -21
- data/lib/thrift/protocol/binary_protocol.rb +65 -20
- data/lib/thrift/protocol/binary_protocol_accelerated.rb +6 -5
- data/lib/thrift/protocol/compact_protocol.rb +76 -41
- data/lib/thrift/protocol/header_protocol.rb +321 -0
- data/lib/thrift/protocol/json_protocol.rb +44 -27
- data/lib/thrift/protocol/multiplexed_protocol.rb +6 -5
- data/lib/thrift/protocol/protocol_decorator.rb +13 -4
- data/lib/thrift/serializer/deserializer.rb +6 -5
- data/lib/thrift/serializer/serializer.rb +5 -5
- data/lib/thrift/server/base_server.rb +5 -4
- data/lib/thrift/server/mongrel_http_server.rb +7 -6
- data/lib/thrift/server/nonblocking_server.rb +35 -9
- data/lib/thrift/server/simple_server.rb +13 -5
- data/lib/thrift/server/thin_http_server.rb +4 -3
- data/lib/thrift/server/thread_pool_server.rb +7 -6
- data/lib/thrift/server/threaded_server.rb +13 -5
- data/lib/thrift/struct.rb +12 -11
- data/lib/thrift/struct_union.rb +14 -9
- data/lib/thrift/thrift_native.rb +3 -2
- data/lib/thrift/transport/base_server_transport.rb +8 -5
- data/lib/thrift/transport/base_transport.rb +18 -14
- data/lib/thrift/transport/buffered_transport.rb +7 -6
- data/lib/thrift/transport/framed_transport.rb +8 -7
- data/lib/thrift/transport/header_transport.rb +562 -0
- data/lib/thrift/transport/http_client_transport.rb +2 -1
- data/lib/thrift/transport/io_stream_transport.rb +4 -3
- data/lib/thrift/transport/memory_buffer_transport.rb +13 -6
- data/lib/thrift/transport/server_socket.rb +14 -8
- data/lib/thrift/transport/socket.rb +126 -60
- data/lib/thrift/transport/ssl_server_socket.rb +4 -3
- data/lib/thrift/transport/ssl_socket.rb +45 -13
- data/lib/thrift/transport/unix_server_socket.rb +9 -5
- data/lib/thrift/transport/unix_socket.rb +7 -6
- data/lib/thrift/types.rb +10 -6
- data/lib/thrift/union.rb +15 -8
- data/lib/thrift/uuid.rb +50 -0
- data/lib/thrift.rb +4 -1
- data/spec/ThriftSpec.thrift +21 -1
- data/spec/base_protocol_spec.rb +21 -2
- data/spec/base_transport_spec.rb +48 -8
- data/spec/binary_protocol_accelerated_spec.rb +1 -0
- data/spec/binary_protocol_spec.rb +1 -2
- data/spec/binary_protocol_spec_shared.rb +206 -155
- data/spec/bytes_spec.rb +71 -114
- data/spec/client_spec.rb +86 -19
- data/spec/compact_protocol_spec.rb +153 -16
- data/spec/constants_demo_spec.rb +102 -0
- data/spec/exception_spec.rb +1 -1
- data/spec/flat_spec.rb +1 -0
- data/spec/header_protocol_spec.rb +476 -0
- data/spec/header_transport_spec.rb +431 -0
- data/spec/http_client_spec.rb +5 -6
- data/spec/json_protocol_spec.rb +69 -47
- data/spec/multiplexed_processor_spec.rb +75 -0
- data/spec/namespaced_spec.rb +1 -1
- data/spec/nonblocking_server_spec.rb +174 -8
- data/spec/processor_spec.rb +1 -1
- data/spec/recursion_depth_spec.rb +223 -0
- data/spec/serializer_spec.rb +1 -1
- data/spec/server_socket_spec.rb +38 -1
- data/spec/server_spec.rb +60 -9
- data/spec/socket_spec.rb +119 -13
- data/spec/socket_spec_shared.rb +73 -9
- data/spec/spec_helper.rb +2 -1
- data/spec/ssl_server_socket_spec.rb +52 -1
- data/spec/ssl_socket_spec.rb +181 -11
- data/spec/struct_nested_containers_spec.rb +2 -2
- data/spec/struct_spec.rb +114 -9
- data/spec/support/header_protocol_helper.rb +55 -0
- data/spec/thin_http_server_spec.rb +4 -18
- data/spec/types_spec.rb +26 -26
- data/spec/union_spec.rb +70 -11
- data/spec/unix_socket_spec.rb +17 -2
- data/spec/uuid_validation_spec.rb +239 -0
- data/test/fuzz/Makefile +779 -0
- data/test/fuzz/Makefile.am +173 -0
- data/test/fuzz/Makefile.in +775 -0
- data/test/fuzz/README.md +149 -0
- data/test/fuzz/fuzz_common.rb +96 -0
- data/{lib/thrift/core_ext.rb → test/fuzz/fuzz_parse_binary_protocol.rb} +4 -4
- data/{lib/thrift/core_ext/fixnum.rb → test/fuzz/fuzz_parse_binary_protocol_accelerated.rb} +7 -13
- data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +23 -0
- data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +23 -0
- data/test/fuzz/fuzz_parse_compact_protocol.rb +23 -0
- data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +23 -0
- data/test/fuzz/fuzz_parse_json_protocol.rb +23 -0
- data/test/fuzz/fuzz_parse_json_protocol_harness.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_json_protocol.rb +23 -0
- data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +23 -0
- data/test/fuzz/fuzz_tracer.rb +29 -0
- 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
|
data/spec/serializer_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
|
|
@@ -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)
|
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
|
|
@@ -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(
|
|
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
|
-
|
|
39
|
-
expect { @socket.open }.to raise_error(Thrift::TransportException)
|
|
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(
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
expect(
|
|
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
|