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
@@ -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
@@ -106,7 +107,7 @@ module Thrift
106
107
  @num_threads = num
107
108
  @logger = logger
108
109
  @connections = []
109
- @buffers = Hash.new { |h, k| h[k] = '' }
110
+ @buffers = Hash.new { |h, k| h[k] = Bytes.empty_byte_buffer }
110
111
  @signal_queue = Queue.new
111
112
  @signal_pipes = IO.pipe
112
113
  @signal_pipes[1].sync = true
@@ -138,7 +139,12 @@ module Thrift
138
139
 
139
140
  def ensure_closed
140
141
  kill_worker_threads if @worker_threads
141
- @iom_thread.kill
142
+ if @iom_thread&.alive?
143
+ @iom_thread.kill
144
+ @iom_thread.join
145
+ end
146
+ close_connections
147
+ close_signal_pipes
142
148
  end
143
149
 
144
150
  private
@@ -246,6 +252,26 @@ module Thrift
246
252
  @worker_threads.clear
247
253
  end
248
254
 
255
+ def close_connections
256
+ @connections.each do |fd|
257
+ begin
258
+ fd.close
259
+ rescue IOError, SystemCallError, TransportException
260
+ end
261
+ end
262
+ @connections.clear
263
+ @buffers.clear
264
+ end
265
+
266
+ def close_signal_pipes
267
+ @signal_pipes.each do |pipe|
268
+ begin
269
+ pipe.close unless pipe.closed?
270
+ rescue IOError
271
+ end
272
+ end
273
+ end
274
+
249
275
  def slice_frame!(buf)
250
276
  if buf.length >= 4
251
277
  size = buf.unpack('N').first
@@ -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
@@ -23,7 +24,14 @@ module Thrift
23
24
  begin
24
25
  @server_transport.listen
25
26
  loop do
26
- client = @server_transport.accept
27
+ begin
28
+ client = @server_transport.accept
29
+ rescue Errno::ECONNRESET, Errno::EPIPE
30
+ next
31
+ rescue => e
32
+ next if defined?(OpenSSL::SSL::SSLError) && e.is_a?(OpenSSL::SSL::SSLError)
33
+ raise
34
+ end
27
35
  trans = @transport_factory.get_transport(client)
28
36
  prot = @protocol_factory.get_protocol(trans)
29
37
  begin
@@ -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
@@ -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,7 +26,14 @@ module Thrift
25
26
  begin
26
27
  @server_transport.listen
27
28
  loop do
28
- client = @server_transport.accept
29
+ begin
30
+ client = @server_transport.accept
31
+ rescue Errno::ECONNRESET, Errno::EPIPE
32
+ next
33
+ rescue => e
34
+ next if defined?(OpenSSL::SSL::SSLError) && e.is_a?(OpenSSL::SSL::SSLError)
35
+ raise
36
+ end
29
37
  trans = @transport_factory.get_transport(client)
30
38
  prot = @protocol_factory.get_protocol(trans)
31
39
  Thread.new(prot, trans) do |p, t|
data/lib/thrift/struct.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
@@ -20,12 +21,6 @@ require 'set'
20
21
 
21
22
  module Thrift
22
23
  module Struct_Union
23
- def validate_container_size(size)
24
- return size unless size < 0
25
-
26
- raise ProtocolException.new(ProtocolException::NEGATIVE_SIZE, 'Negative size')
27
- end
28
-
29
24
  def name_to_id(name)
30
25
  names_to_ids = self.class.instance_variable_get(:@names_to_ids)
31
26
  unless names_to_ids
@@ -61,7 +56,7 @@ module Thrift
61
56
  value.read(iprot)
62
57
  when Types::MAP
63
58
  key_type, val_type, size = iprot.read_map_begin
64
- validate_container_size(size)
59
+ raise ProtocolException.new(ProtocolException::NEGATIVE_SIZE, 'Negative size') unless size >= 0
65
60
  # Skip the map contents if the declared key or value types don't match the expected ones.
66
61
  if (size != 0 && (key_type != field[:key][:type] || val_type != field[:value][:type]))
67
62
  size.times do
@@ -80,7 +75,7 @@ module Thrift
80
75
  iprot.read_map_end
81
76
  when Types::LIST
82
77
  e_type, size = iprot.read_list_begin
83
- validate_container_size(size)
78
+ raise ProtocolException.new(ProtocolException::NEGATIVE_SIZE, 'Negative size') unless size >= 0
84
79
  # Skip the list contents if the declared element type doesn't match the expected one.
85
80
  if (e_type != field[:element][:type])
86
81
  size.times do
@@ -96,7 +91,7 @@ module Thrift
96
91
  iprot.read_list_end
97
92
  when Types::SET
98
93
  e_type, size = iprot.read_set_begin
99
- validate_container_size(size)
94
+ raise ProtocolException.new(ProtocolException::NEGATIVE_SIZE, 'Negative size') unless size >= 0
100
95
  # Skip the set contents if the declared element type doesn't match the expected one.
101
96
  if (e_type != field[:element][:type])
102
97
  size.times do
@@ -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,5 +21,5 @@
20
21
  begin
21
22
  require "thrift_native"
22
23
  rescue LoadError
23
- puts "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
24
+ warn "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
24
25
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -20,6 +21,8 @@
20
21
 
21
22
  module Thrift
22
23
  class BaseServerTransport
24
+ DEFAULT_CLIENT_TIMEOUT = 5
25
+
23
26
  def listen
24
27
  raise NotImplementedError
25
28
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -25,6 +26,8 @@ module Thrift
25
26
  ALREADY_OPEN = 2
26
27
  TIMED_OUT = 3
27
28
  END_OF_FILE = 4
29
+ NEGATIVE_SIZE = 5
30
+ SIZE_LIMIT = 6
28
31
 
29
32
  attr_reader :type
30
33
 
@@ -80,8 +83,9 @@ module Thrift
80
83
  end
81
84
 
82
85
  def read_all(size)
83
- return Bytes.empty_byte_buffer if size <= 0
84
- buf = read(size)
86
+ raise TransportException.new(TransportException::NEGATIVE_SIZE, 'Negative size') unless size >= 0
87
+ return Bytes.empty_byte_buffer if size == 0
88
+ buf = Bytes.force_binary_encoding(read(size))
85
89
  while (buf.length < size)
86
90
  chunk = read(size - buf.length)
87
91
  buf << chunk
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -73,6 +74,12 @@ module Thrift
73
74
  # Maximum frame size (~1GB)
74
75
  MAX_FRAME_SIZE = 0x3FFFFFFF
75
76
 
77
+ # Default decompressed-size cap for ZLIB transform (~15.6 MB, matches other Thrift bindings)
78
+ DEFAULT_MAX_DECOMPRESSED_SIZE = 16_384_000
79
+
80
+ # Chunk size for streaming inflate loop
81
+ ZLIB_INFLATE_CHUNK_SIZE = 16_384
82
+
76
83
  # Binary protocol version mask and version 1
77
84
  BINARY_VERSION_MASK = 0xffff0000
78
85
  BINARY_VERSION_1 = 0x80010000
@@ -112,6 +119,7 @@ module Thrift
112
119
  @sequence_id = 0
113
120
  @flags = 0
114
121
  @max_frame_size = MAX_FRAME_SIZE
122
+ @max_decompressed_size = DEFAULT_MAX_DECOMPRESSED_SIZE
115
123
  end
116
124
 
117
125
  def sequence_id=(sequence_id)
@@ -172,6 +180,14 @@ module Thrift
172
180
  @max_frame_size = size
173
181
  end
174
182
 
183
+ # Sets the maximum allowed decompressed size for ZLIB transforms
184
+ def set_max_decompressed_size(size)
185
+ if size <= 0 || size > MAX_FRAME_SIZE
186
+ raise ArgumentError, "max_decompressed_size must be > 0 and <= #{MAX_FRAME_SIZE}"
187
+ end
188
+ @max_decompressed_size = size
189
+ end
190
+
175
191
  def read(sz)
176
192
  # Try reading from existing buffer
177
193
  data = @read_buffer.read(sz)
@@ -369,13 +385,43 @@ module Thrift
369
385
  payload = buf.read
370
386
  transforms.each do |transform_id|
371
387
  if transform_id == HeaderTransformID::ZLIB
372
- payload = Zlib::Inflate.inflate(payload)
388
+ payload = bounded_inflate(payload)
373
389
  end
374
390
  end
375
391
 
376
392
  StringIO.new(payload)
377
393
  end
378
394
 
395
+ # Inflates +compressed+ with a running byte-count check against @max_decompressed_size.
396
+ # Raises TransportException::SIZE_LIMIT if the decompressed output would exceed the limit.
397
+ def bounded_inflate(compressed)
398
+ inflater = Zlib::Inflate.new
399
+ buffer = Bytes.empty_byte_buffer
400
+ offset = 0
401
+ begin
402
+ while offset < compressed.bytesize
403
+ buffer << inflater.inflate(compressed.byteslice(offset, ZLIB_INFLATE_CHUNK_SIZE))
404
+ if buffer.bytesize > @max_decompressed_size
405
+ raise TransportException.new(
406
+ TransportException::SIZE_LIMIT,
407
+ "Decompressed size exceeds limit of #{@max_decompressed_size}"
408
+ )
409
+ end
410
+ offset += ZLIB_INFLATE_CHUNK_SIZE
411
+ end
412
+ buffer << inflater.finish
413
+ if buffer.bytesize > @max_decompressed_size
414
+ raise TransportException.new(
415
+ TransportException::SIZE_LIMIT,
416
+ "Decompressed size exceeds limit of #{@max_decompressed_size}"
417
+ )
418
+ end
419
+ buffer
420
+ ensure
421
+ inflater.close rescue nil
422
+ end
423
+ end
424
+
379
425
  # Flushes data in Header format
380
426
  def flush_header_format(payload)
381
427
  # Apply transforms
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -70,6 +71,12 @@ module Thrift
70
71
  data
71
72
  end
72
73
 
74
+ def read_all(size)
75
+ raise TransportException.new(TransportException::NEGATIVE_SIZE, 'Negative size') unless size >= 0
76
+
77
+ read(size)
78
+ end
79
+
73
80
  def read_byte
74
81
  raise EOFError.new("Not enough bytes remain in buffer") if @index >= @buf.size
75
82
  val = Bytes.get_string_byte(@buf, @index)
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -22,8 +23,8 @@ require 'socket'
22
23
 
23
24
  module Thrift
24
25
  class ServerSocket < BaseServerTransport
25
- # call-seq: initialize(host = nil, port)
26
- def initialize(host_or_port, port = nil)
26
+ # call-seq: initialize(host = nil, port, client_timeout: DEFAULT_CLIENT_TIMEOUT)
27
+ def initialize(host_or_port, port = nil, client_timeout: DEFAULT_CLIENT_TIMEOUT)
27
28
  if port
28
29
  @host = host_or_port
29
30
  @port = port
@@ -31,10 +32,11 @@ module Thrift
31
32
  @host = nil
32
33
  @port = host_or_port
33
34
  end
35
+ @client_timeout = client_timeout
34
36
  @handle = nil
35
37
  end
36
38
 
37
- attr_reader :handle
39
+ attr_reader :handle, :client_timeout
38
40
 
39
41
  def listen
40
42
  @handle = TCPServer.new(@host, @port)
@@ -45,6 +47,7 @@ module Thrift
45
47
  sock = @handle.accept
46
48
  sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
47
49
  trans = Socket.new
50
+ trans.timeout = @client_timeout
48
51
  trans.handle = sock
49
52
  trans
50
53
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -33,39 +34,19 @@ module Thrift
33
34
  attr_accessor :handle, :timeout
34
35
 
35
36
  def open
36
- for addrinfo in ::Socket::getaddrinfo(@host, @port, nil, ::Socket::SOCK_STREAM) do
37
- begin
38
- socket = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0)
39
- socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
40
- sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3])
41
- begin
42
- socket.connect_nonblock(sockaddr)
43
- rescue Errno::EINPROGRESS
44
- unless IO.select(nil, [ socket ], nil, @timeout)
45
- next
46
- end
47
- begin
48
- socket.connect_nonblock(sockaddr)
49
- rescue Errno::EISCONN
50
- end
51
- end
52
- return @handle = socket
53
- rescue StandardError => e
54
- next
55
- end
56
- end
57
- raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
37
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout unless @timeout.nil? || @timeout == 0
38
+ @handle = connect_socket(deadline)
58
39
  end
59
40
 
60
41
  def open?
61
- !@handle.nil? and !@handle.closed?
42
+ !@handle.nil? && !@handle.closed?
62
43
  end
63
44
 
64
45
  def write(str)
65
46
  raise TransportException.new(TransportException::NOT_OPEN, "closed stream") unless open?
66
47
  str = Bytes.force_binary_encoding(str)
67
48
  begin
68
- if @timeout.nil? or @timeout == 0
49
+ if @timeout.nil? || @timeout == 0
69
50
  @handle.write(str)
70
51
  else
71
52
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
@@ -87,7 +68,7 @@ module Thrift
87
68
  # pass this on
88
69
  raise e
89
70
  rescue StandardError => e
90
- @handle.close
71
+ close_socket(@handle)
91
72
  @handle = nil
92
73
  raise TransportException.new(TransportException::NOT_OPEN, e.message)
93
74
  end
@@ -97,7 +78,7 @@ module Thrift
97
78
  raise TransportException.new(TransportException::NOT_OPEN, "closed stream") unless open?
98
79
 
99
80
  begin
100
- if @timeout.nil? or @timeout == 0
81
+ if @timeout.nil? || @timeout == 0
101
82
  data = @handle.readpartial(sz)
102
83
  else
103
84
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
@@ -116,18 +97,18 @@ module Thrift
116
97
  # don't let this get caught by the StandardError handler
117
98
  raise e
118
99
  rescue StandardError => e
119
- @handle.close unless @handle.closed?
100
+ close_socket(@handle)
120
101
  @handle = nil
121
102
  raise TransportException.new(TransportException::NOT_OPEN, e.message)
122
103
  end
123
- if (data.nil? or data.length == 0)
104
+ if (data.nil? || data.length == 0)
124
105
  raise TransportException.new(TransportException::UNKNOWN, "Socket: Could not read #{sz} bytes from #{@desc}")
125
106
  end
126
107
  data
127
108
  end
128
109
 
129
110
  def close
130
- @handle.close unless @handle.nil? or @handle.closed?
111
+ close_socket(@handle)
131
112
  @handle = nil
132
113
  end
133
114
 
@@ -141,6 +122,64 @@ module Thrift
141
122
 
142
123
  private
143
124
 
125
+ def connect_socket(deadline)
126
+ last_error = nil
127
+ connected_socket = nil
128
+
129
+ Addrinfo.foreach(@host, @port, nil, :STREAM) do |addrinfo|
130
+ socket = nil
131
+
132
+ begin
133
+ socket = if deadline
134
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
135
+ raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out opening connection to #{@desc}") if remaining <= 0
136
+
137
+ addrinfo.connect(timeout: remaining)
138
+ else
139
+ addrinfo.connect
140
+ end
141
+
142
+ socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
143
+ connected_socket = socket
144
+ break
145
+ rescue Errno::ETIMEDOUT => e
146
+ close_socket(socket)
147
+ last_error = e
148
+ rescue TransportException
149
+ close_socket(socket)
150
+ raise
151
+ rescue StandardError => e
152
+ close_socket(socket)
153
+ last_error = e
154
+ end
155
+ end
156
+
157
+ return connected_socket if connected_socket
158
+
159
+ if last_error.is_a?(Errno::ETIMEDOUT)
160
+ raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out opening connection to #{@desc}")
161
+ end
162
+
163
+ if deadline && deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC) <= 0
164
+ raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out opening connection to #{@desc}")
165
+ end
166
+
167
+ raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}"), cause: last_error
168
+ rescue TransportException
169
+ raise
170
+ rescue StandardError
171
+ raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}")
172
+ end
173
+
174
+ def close_socket(socket)
175
+ return if socket.nil?
176
+ return if socket.respond_to?(:closed?) && socket.closed?
177
+
178
+ socket.close
179
+ rescue StandardError
180
+ nil
181
+ end
182
+
144
183
  def wait_for(operation, deadline, sz)
145
184
  rd_ary, wr_ary = case operation
146
185
  when :read
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -22,8 +23,8 @@ require 'socket'
22
23
 
23
24
  module Thrift
24
25
  class SSLServerSocket < ServerSocket
25
- def initialize(host_or_port, port = nil, ssl_context = nil)
26
- super(host_or_port, port)
26
+ def initialize(host_or_port, port = nil, ssl_context = nil, client_timeout: DEFAULT_CLIENT_TIMEOUT)
27
+ super(host_or_port, port, client_timeout: client_timeout)
27
28
  @ssl_context = ssl_context
28
29
  end
29
30
 
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -17,6 +18,8 @@
17
18
  # specific language governing permissions and limitations
18
19
  # under the License.
19
20
 
21
+ require 'io/wait'
22
+
20
23
  module Thrift
21
24
  class SSLSocket < Socket
22
25
  def initialize(host = 'localhost', port = 9090, timeout = nil, ssl_context = nil)
@@ -27,20 +30,49 @@ module Thrift
27
30
  attr_accessor :ssl_context
28
31
 
29
32
  def open
30
- socket = super
31
- @handle = OpenSSL::SSL::SSLSocket.new(socket, @ssl_context)
33
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout unless @timeout.nil? || @timeout == 0
34
+ socket = connect_socket(deadline)
35
+
32
36
  begin
33
- @handle.connect_nonblock
37
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, @ssl_context)
38
+ ssl_socket.sync_close = true
39
+ @handle = ssl_socket
40
+
41
+ if deadline
42
+ loop do
43
+ result = @handle.connect_nonblock(exception: false)
44
+
45
+ case result
46
+ when @handle
47
+ break
48
+ when :wait_readable
49
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
50
+ if remaining <= 0 || !@handle.to_io.wait_readable(remaining)
51
+ raise TransportException.new(TransportException::TIMED_OUT, "SSL socket: Timed out establishing session with #{@desc}")
52
+ end
53
+ when :wait_writable
54
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
55
+ if remaining <= 0 || !@handle.to_io.wait_writable(remaining)
56
+ raise TransportException.new(TransportException::TIMED_OUT, "SSL socket: Timed out establishing session with #{@desc}")
57
+ end
58
+ else
59
+ raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: unexpected SSL connect result #{result.inspect}")
60
+ end
61
+ end
62
+ else
63
+ @handle.connect
64
+ end
65
+
34
66
  @handle.post_connection_check(@host)
35
67
  @handle
36
- rescue IO::WaitReadable
37
- IO.select([ @handle ], nil, nil, @timeout)
38
- retry
39
- rescue IO::WaitWritable
40
- IO.select(nil, [ @handle ], nil, @timeout)
41
- retry
42
- rescue StandardError => e
43
- raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
68
+ rescue TransportException
69
+ close_socket(@handle)
70
+ @handle = nil
71
+ raise
72
+ rescue StandardError
73
+ close_socket(@handle || socket)
74
+ @handle = nil
75
+ raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}")
44
76
  end
45
77
  end
46
78
 
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -22,12 +23,14 @@ require 'socket'
22
23
 
23
24
  module Thrift
24
25
  class UNIXServerSocket < BaseServerTransport
25
- def initialize(path)
26
+ def initialize(path, client_timeout: DEFAULT_CLIENT_TIMEOUT)
26
27
  @path = path
28
+ @client_timeout = client_timeout
27
29
  @handle = nil
28
30
  end
29
31
 
30
32
  attr_accessor :handle
33
+ attr_reader :client_timeout
31
34
 
32
35
  def listen
33
36
  @handle = ::UNIXServer.new(@path)
@@ -37,6 +40,7 @@ module Thrift
37
40
  unless @handle.nil?
38
41
  sock = @handle.accept
39
42
  trans = UNIXSocket.new(nil)
43
+ trans.timeout = @client_timeout
40
44
  trans.handle = sock
41
45
  trans
42
46
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
data/lib/thrift/types.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