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
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
@@ -24,44 +25,150 @@ describe 'Socket' do
24
25
  describe Thrift::Socket do
25
26
  before(:each) do
26
27
  @socket = Thrift::Socket.new
28
+ @addrinfo = double("Addrinfo")
27
29
  @handle = double("Handle", :closed? => false)
28
30
  allow(@handle).to receive(:close)
29
- allow(@handle).to receive(:connect_nonblock)
30
31
  allow(@handle).to receive(:setsockopt)
31
- 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)
32
34
  end
33
35
 
34
36
  it_should_behave_like "a socket"
35
37
 
36
38
  it "should raise a TransportException when it cannot open a socket" do
37
- expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]])
38
- 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
39
45
  end
40
46
 
41
47
  it "should open a ::Socket with default args" do
42
- expect(::Socket).to receive(:new).and_return(double("Handle", :connect_nonblock => true, :setsockopt => nil))
43
- expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]])
44
- 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)
45
51
  @socket.to_s == "socket(localhost:9090)"
46
52
  @socket.open
47
53
  end
48
54
 
49
55
  it "should accept host/port options" do
50
- expect(::Socket).to receive(:new).and_return(double("Handle", :connect_nonblock => true, :setsockopt => nil))
51
- expect(::Socket).to receive(:getaddrinfo).with("my.domain", 1234, nil, ::Socket::SOCK_STREAM).and_return([[]])
52
- 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)
53
61
  @socket = Thrift::Socket.new('my.domain', 1234).open
54
62
  @socket.to_s == "socket(my.domain:1234)"
55
63
  end
56
64
 
57
65
  it "should accept an optional timeout" do
58
- allow(::Socket).to receive(:new)
59
66
  expect(Thrift::Socket.new('localhost', 8080, 5).timeout).to eq(5)
60
67
  end
61
68
 
62
69
  it "should provide a reasonable to_s" do
63
- allow(::Socket).to receive(:new)
64
70
  expect(Thrift::Socket.new('myhost', 8090).to_s).to eq("socket(myhost:8090)")
65
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
66
173
  end
67
174
  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
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # Licensed to the Apache Software Foundation (ASF) under one
4
5
  # 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
@@ -38,6 +39,45 @@ describe 'SSLServerSocket' do
38
39
  expect(@socket.to_io).to eq(tcp_server)
39
40
  end
40
41
 
42
+ it "should default accepted sockets to a finite client timeout" do
43
+ expect(@socket.client_timeout).to eq(5)
44
+ end
45
+
46
+ it "should preserve the positional ssl context argument" do
47
+ context = double("SSLContext")
48
+ socket = Thrift::SSLServerSocket.new(nil, 1234, context)
49
+
50
+ expect(socket.ssl_context).to eq(context)
51
+ expect(socket.client_timeout).to eq(5)
52
+ end
53
+
54
+ it "should accept a custom client timeout" do
55
+ context = double("SSLContext")
56
+ socket = Thrift::SSLServerSocket.new(nil, 1234, context, client_timeout: 2.5)
57
+
58
+ expect(socket.ssl_context).to eq(context)
59
+ expect(socket.client_timeout).to eq(2.5)
60
+ end
61
+
62
+ it "should apply the client timeout to accepted sockets" do
63
+ tcp_server = double("TCPServer")
64
+ ssl_server = double("SSLServer")
65
+ sock = double("SSLSocket")
66
+
67
+ allow(TCPServer).to receive(:new).with(nil, 1234).and_return(tcp_server)
68
+ allow(OpenSSL::SSL::SSLServer).to receive(:new).with(tcp_server, nil).and_return(ssl_server)
69
+ expect(ssl_server).to receive(:accept).and_return(sock)
70
+ expect(sock).to receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
71
+
72
+ trans = double("Socket")
73
+ expect(Thrift::Socket).to receive(:new).and_return(trans)
74
+ expect(trans).to receive(:timeout=).with(Thrift::BaseServerTransport::DEFAULT_CLIENT_TIMEOUT)
75
+ expect(trans).to receive(:handle=).with(sock)
76
+
77
+ @socket.listen
78
+ expect(@socket.accept).to eq(trans)
79
+ end
80
+
41
81
  it "should provide a reasonable to_s" do
42
82
  expect(@socket.to_s).to eq("ssl(socket(:1234))")
43
83
  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
@@ -25,40 +26,56 @@ describe 'SSLSocket' do
25
26
  before(:each) do
26
27
  @context = OpenSSL::SSL::SSLContext.new
27
28
  @socket = Thrift::SSLSocket.new
29
+ @addrinfo = double("Addrinfo")
28
30
  @simple_socket_handle = double("Handle", :closed? => false)
29
31
  allow(@simple_socket_handle).to receive(:close)
30
- allow(@simple_socket_handle).to receive(:connect_nonblock)
31
32
  allow(@simple_socket_handle).to receive(:setsockopt)
33
+ allow(@simple_socket_handle).to receive(:wait_readable)
34
+ allow(@simple_socket_handle).to receive(:wait_writable)
32
35
 
33
- @handle = double(double("SSLHandle", :connect_nonblock => true, :post_connection_check => true), :closed? => false)
34
- allow(@handle).to receive(:connect_nonblock)
36
+ @handle = double("SSLHandle", :closed? => false)
37
+ allow(@handle).to receive(:connect).and_return(true)
38
+ allow(@handle).to receive(:connect_nonblock).and_return(@handle)
35
39
  allow(@handle).to receive(:close)
36
40
  allow(@handle).to receive(:post_connection_check)
41
+ allow(@handle).to receive(:sync_close=)
37
42
  allow(@handle).to receive(:to_io).and_return(@simple_socket_handle)
38
43
 
39
- allow(::Socket).to receive(:new).and_return(@simple_socket_handle)
44
+ allow(@addrinfo).to receive(:connect).and_return(@simple_socket_handle)
45
+ allow(Addrinfo).to receive(:foreach).and_yield(@addrinfo)
40
46
  allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(@handle)
41
47
  end
42
48
 
43
49
  it_should_behave_like "a socket"
44
50
 
45
51
  it "should raise a TransportException when it cannot open a ssl socket" do
46
- expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]])
47
- expect { @socket.open }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) }
52
+ allow(Addrinfo).to receive(:foreach).and_raise(SocketError.new("lookup failed"))
53
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
54
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
55
+ expect(e.message).to eq("Could not connect to localhost:9090")
56
+ expect(e.cause).to be_a(SocketError)
57
+ end
48
58
  end
49
59
 
50
60
  it "should open a ::Socket with default args" do
61
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
51
62
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(@simple_socket_handle, nil).and_return(@handle)
63
+ expect(@handle).to receive(:sync_close=).with(true)
64
+ expect(@handle).to receive(:connect).and_return(true)
52
65
  expect(@handle).to receive(:post_connection_check).with('localhost')
53
66
  @socket.open
54
67
  end
55
68
 
56
69
  it "should accept host/port options" do
57
- handle = double("Handle", :connect_nonblock => true, :setsockopt => nil)
58
- allow(::Socket).to receive(:new).and_return(handle)
59
- expect(::Socket).to receive(:getaddrinfo).with("my.domain", 1234, nil, ::Socket::SOCK_STREAM).and_return([[]])
60
- expect(::Socket).to receive(:sockaddr_in)
70
+ handle = double("Handle", :closed? => false)
71
+ allow(handle).to receive(:close)
72
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 100.0)
73
+ expect(handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
74
+ expect(Addrinfo).to receive(:foreach).with("my.domain", 1234, nil, :STREAM).and_yield(@addrinfo)
75
+ expect(@addrinfo).to receive(:connect).with(timeout: 6000).and_return(handle)
61
76
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(handle, nil).and_return(@handle)
77
+ expect(@handle).to receive(:sync_close=).with(true)
78
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(@handle)
62
79
  expect(@handle).to receive(:post_connection_check).with('my.domain')
63
80
  Thrift::SSLSocket.new('my.domain', 1234, 6000, nil).open
64
81
  end
@@ -71,6 +88,150 @@ describe 'SSLSocket' do
71
88
  expect(Thrift::SSLSocket.new('localhost', 8080, 5, @context).ssl_context).to eq(@context)
72
89
  end
73
90
 
91
+ it "should treat zero timeout as blocking for open and handshake" do
92
+ @socket.timeout = 0
93
+
94
+ expect(@addrinfo).to receive(:connect).with(no_args).and_return(@simple_socket_handle)
95
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
96
+ expect(@handle).to receive(:sync_close=).with(true)
97
+ expect(@handle).to receive(:connect).and_return(true)
98
+ expect(@handle).not_to receive(:connect_nonblock)
99
+ expect(@handle).to receive(:post_connection_check).with('localhost')
100
+
101
+ expect(@socket.open).to eq(@handle)
102
+ end
103
+
104
+ it "should use the remaining timeout across ssl wait states" do
105
+ @socket.timeout = 5
106
+
107
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 102.0, 104.0)
108
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
109
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
110
+ expect(@handle).to receive(:sync_close=).with(true)
111
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(:wait_readable)
112
+ expect(@simple_socket_handle).to receive(:wait_readable).with(3.0).ordered.and_return(true)
113
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(:wait_writable)
114
+ expect(@simple_socket_handle).to receive(:wait_writable).with(1.0).ordered.and_return(true)
115
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(@handle)
116
+ expect(@handle).to receive(:post_connection_check).with('localhost')
117
+
118
+ expect(@socket.open).to eq(@handle)
119
+ end
120
+
121
+ it "should share one timeout budget across tcp fallback and the ssl handshake" do
122
+ @socket.timeout = 5
123
+ second_addrinfo = double("Addrinfo")
124
+
125
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 102.0, 103.0)
126
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
127
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ECONNREFUSED)
128
+ expect(second_addrinfo).to receive(:connect).with(timeout: 3.0).and_return(@simple_socket_handle)
129
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
130
+ expect(@handle).to receive(:sync_close=).with(true)
131
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(:wait_readable)
132
+ expect(@simple_socket_handle).to receive(:wait_readable).with(2.0).ordered.and_return(true)
133
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(@handle)
134
+ expect(@handle).to receive(:post_connection_check).with('localhost')
135
+
136
+ expect(@socket.open).to eq(@handle)
137
+ end
138
+
139
+ it "should continue to the next address after a tcp connect timeout during ssl open" do
140
+ @socket.timeout = 5
141
+ second_addrinfo = double("Addrinfo")
142
+
143
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 103.0)
144
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
145
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ETIMEDOUT)
146
+ expect(second_addrinfo).to receive(:connect).with(timeout: 2.0).and_return(@simple_socket_handle)
147
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
148
+ expect(@handle).to receive(:sync_close=).with(true)
149
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(@handle)
150
+ expect(@handle).to receive(:post_connection_check).with('localhost')
151
+
152
+ expect(@socket.open).to eq(@handle)
153
+ end
154
+
155
+ it "should raise TIMED_OUT when ssl handshake wait times out" do
156
+ @socket.timeout = 5
157
+
158
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 102.0)
159
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
160
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
161
+ expect(@handle).to receive(:sync_close=).with(true)
162
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(:wait_readable)
163
+ expect(@simple_socket_handle).to receive(:wait_readable).with(3.0).and_return(nil)
164
+ expect(@handle).to receive(:close)
165
+
166
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
167
+ expect(e.type).to eq(Thrift::TransportException::TIMED_OUT)
168
+ expect(e.message).to eq("SSL socket: Timed out establishing session with localhost:9090")
169
+ end
170
+ end
171
+
172
+ it "should surface tcp open timeout before starting ssl" do
173
+ @socket.timeout = 5
174
+
175
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
176
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ETIMEDOUT)
177
+ expect(OpenSSL::SSL::SSLSocket).not_to receive(:new)
178
+
179
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
180
+ expect(e.type).to eq(Thrift::TransportException::TIMED_OUT)
181
+ expect(e.message).to eq("Socket: Timed out opening connection to localhost:9090")
182
+ end
183
+ end
184
+
185
+ it "should close the raw socket if ssl wrapper creation fails" do
186
+ @socket.timeout = 5
187
+
188
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
189
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
190
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
191
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(@simple_socket_handle, nil).and_raise(StandardError.new("ssl init failed"))
192
+ expect(@simple_socket_handle).to receive(:close)
193
+
194
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
195
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
196
+ expect(e.message).to eq("Could not connect to localhost:9090")
197
+ expect(e.cause.message).to eq("ssl init failed")
198
+ end
199
+ end
200
+
201
+ it "should close the ssl socket when post connection check fails" do
202
+ @socket.timeout = 5
203
+
204
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
205
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
206
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
207
+ expect(@handle).to receive(:sync_close=).with(true)
208
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(@handle)
209
+ expect(@handle).to receive(:post_connection_check).with('localhost').and_raise(StandardError.new("hostname mismatch"))
210
+ expect(@handle).to receive(:close)
211
+
212
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
213
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
214
+ expect(e.message).to eq("Could not connect to localhost:9090")
215
+ expect(e.cause.message).to eq("hostname mismatch")
216
+ end
217
+ end
218
+
219
+ it "should close the ssl socket on an unexpected nonblocking handshake result" do
220
+ @socket.timeout = 5
221
+
222
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
223
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
224
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
225
+ expect(@handle).to receive(:sync_close=).with(true)
226
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(:unexpected)
227
+ expect(@handle).to receive(:close)
228
+
229
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
230
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
231
+ expect(e.message).to include("unexpected SSL connect result")
232
+ end
233
+ end
234
+
74
235
  it "should delegate to_io to the underlying SSL socket handle" do
75
236
  @socket.open
76
237
  expect(@socket.to_io).to eq(@simple_socket_handle)
@@ -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
data/spec/struct_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
@@ -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
@@ -33,7 +34,7 @@ module HeaderProtocolHelper
33
34
  end
34
35
 
35
36
  def build_header_frame(header_data, payload = Thrift::Bytes.empty_byte_buffer, header_words: nil)
36
- header_data = Thrift::Bytes.force_binary_encoding(header_data)
37
+ header_data = header_data.b
37
38
  if header_words.nil?
38
39
  padding = (4 - (header_data.bytesize % 4)) % 4
39
40
  header_data += "\x00" * padding
@@ -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
data/spec/types_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
data/spec/union_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
@@ -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
@@ -67,10 +68,25 @@ describe 'UNIXSocket' do
67
68
  expect(handle).to receive(:accept).and_return(sock)
68
69
  trans = double("UNIXSocket")
69
70
  expect(Thrift::UNIXSocket).to receive(:new).and_return(trans)
71
+ expect(trans).to receive(:timeout=).with(Thrift::BaseServerTransport::DEFAULT_CLIENT_TIMEOUT)
70
72
  expect(trans).to receive(:handle=).with(sock)
71
73
  expect(@socket.accept).to eq(trans)
72
74
  end
73
75
 
76
+ it "should default accepted sockets to a finite client timeout" do
77
+ expect(@socket.client_timeout).to eq(5)
78
+ end
79
+
80
+ it "should accept a custom client timeout" do
81
+ @socket = Thrift::UNIXServerSocket.new(@path, client_timeout: 2.5)
82
+ expect(@socket.client_timeout).to eq(2.5)
83
+ end
84
+
85
+ it "should allow blocking accepted sockets with nil or zero client timeout" do
86
+ expect(Thrift::UNIXServerSocket.new(@path, client_timeout: nil).client_timeout).to be_nil
87
+ expect(Thrift::UNIXServerSocket.new(@path, client_timeout: 0).client_timeout).to eq(0)
88
+ end
89
+
74
90
  it "should close the handle when closed" do
75
91
  handle = double("UNIXServer", :closed? => false)
76
92
  expect(UNIXServer).to receive(:new).with(@path).and_return(handle)
@@ -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