ruby-kafka 0.1.0.pre.beta4 → 0.1.0.pre.beta5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71fcf73c1adb43253684cb0ef1406a0631c3ce3a
4
- data.tar.gz: dde9f9123b31f20475c7d280532f0b135f6a84bf
3
+ metadata.gz: 2ed5c1319300907fea512494cd9f4f07c3b15b70
4
+ data.tar.gz: e76e37ec50c63fbd8969478a38e6e804059d5a45
5
5
  SHA512:
6
- metadata.gz: 8c44cc61c8b50ab3854830596bfd965837e03e1af97c3917c3ecf4386bab38534184688ae5dc1f4c284c8f2878143637e9dffdfe35e95c504a8f1381ca4da6e5
7
- data.tar.gz: 06de9ac78b013cf448e03d0ba4ec879059bfbc04c6626c373e30c4c0c95c3b23282b30c3607df8955ab4c617d5d31d67c8ae80033381212baccf107aefda0489
6
+ metadata.gz: 878fc9de44046cee3895212d766f44d5011de1dd3fd05f0f745efd00d284dc2cdae6e5d2aed3cb1ae794210da5f4b76a993cbff03c1554339cb5f5fb088ab071
7
+ data.tar.gz: 243bb41e327d1f9d8a9d06eb0c1b6bc585a63d2607ab0b25fe44af0ff7cb936429949620a9a4e8b12b5f0838db59aebfab7f73a4e100e7f8763f566094592405
@@ -31,4 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "pry"
32
32
  spec.add_development_dependency "dotenv"
33
33
  spec.add_development_dependency "docker-api"
34
+ spec.add_development_dependency "rspec-benchmark"
34
35
  end
@@ -1,5 +1,5 @@
1
- require "socket"
2
1
  require "stringio"
2
+ require "kafka/socket_with_timeout"
3
3
  require "kafka/protocol/request_message"
4
4
  require "kafka/protocol/encoder"
5
5
  require "kafka/protocol/decoder"
@@ -38,7 +38,7 @@ module Kafka
38
38
 
39
39
  @logger.info "Opening connection to #{@host}:#{@port} with client id #{@client_id}..."
40
40
 
41
- @socket = Socket.tcp(host, port, connect_timeout: @connect_timeout)
41
+ @socket = SocketWithTimeout.new(@host, @port, timeout: @connect_timeout)
42
42
 
43
43
  @encoder = Kafka::Protocol::Encoder.new(@socket)
44
44
  @decoder = Kafka::Protocol::Decoder.new(@socket)
@@ -112,14 +112,12 @@ module Kafka
112
112
 
113
113
  data = Kafka::Protocol::Encoder.encode_with(message)
114
114
 
115
- unless IO.select(nil, [@socket], nil, @socket_timeout)
116
- @logger.error "Timed out while writing request #{@correlation_id}"
117
- raise ConnectionError
118
- end
119
-
120
115
  @encoder.write_bytes(data)
121
116
 
122
117
  nil
118
+ rescue Errno::ETIMEDOUT
119
+ @logger.error "Timed out while writing request #{@correlation_id}"
120
+ raise ConnectionError
123
121
  end
124
122
 
125
123
  # Reads a response from the connection.
@@ -131,11 +129,6 @@ module Kafka
131
129
  def read_response(response_class)
132
130
  @logger.debug "Waiting for response #{@correlation_id} from #{to_s}"
133
131
 
134
- unless IO.select([@socket], nil, nil, @socket_timeout)
135
- @logger.error "Timed out while waiting for response #{@correlation_id}"
136
- raise ConnectionError
137
- end
138
-
139
132
  bytes = @decoder.bytes
140
133
 
141
134
  buffer = StringIO.new(bytes)
@@ -147,6 +140,9 @@ module Kafka
147
140
  @logger.debug "Received response #{correlation_id} from #{to_s}"
148
141
 
149
142
  return correlation_id, response
143
+ rescue Errno::ETIMEDOUT
144
+ @logger.error "Timed out while waiting for response #{@correlation_id}"
145
+ raise ConnectionError
150
146
  end
151
147
  end
152
148
  end
@@ -4,15 +4,20 @@ module Kafka
4
4
  class MessageBuffer
5
5
  include Enumerable
6
6
 
7
+ attr_reader :size
8
+
7
9
  def initialize
8
10
  @buffer = {}
11
+ @size = 0
9
12
  end
10
13
 
11
14
  def write(message, topic:, partition:)
15
+ @size += 1
12
16
  buffer_for(topic, partition) << message
13
17
  end
14
18
 
15
19
  def concat(messages, topic:, partition:)
20
+ @size += messages.count
16
21
  buffer_for(topic, partition).concat(messages)
17
22
  end
18
23
 
@@ -20,10 +25,6 @@ module Kafka
20
25
  @buffer
21
26
  end
22
27
 
23
- def size
24
- @buffer.values.inject(0) {|sum, messages| messages.values.flatten.size + sum }
25
- end
26
-
27
28
  def empty?
28
29
  @buffer.empty?
29
30
  end
@@ -43,6 +44,8 @@ module Kafka
43
44
  #
44
45
  # @return [nil]
45
46
  def clear_messages(topic:, partition:)
47
+ @size -= @buffer[topic][partition].count
48
+
46
49
  @buffer[topic].delete(partition)
47
50
  @buffer.delete(topic) if @buffer[topic].empty?
48
51
  end
@@ -52,6 +55,7 @@ module Kafka
52
55
  # @return [nil]
53
56
  def clear
54
57
  @buffer = {}
58
+ @size = 0
55
59
  end
56
60
 
57
61
  private
@@ -1,6 +1,7 @@
1
1
  require "kafka/partitioner"
2
2
  require "kafka/message_buffer"
3
3
  require "kafka/protocol/message"
4
+ require "kafka/transmission"
4
5
 
5
6
  module Kafka
6
7
 
@@ -161,11 +162,19 @@ module Kafka
161
162
  def send_messages
162
163
  attempt = 0
163
164
 
165
+ transmission = Transmission.new(
166
+ broker_pool: @broker_pool,
167
+ buffer: @buffer,
168
+ required_acks: @required_acks,
169
+ ack_timeout: @ack_timeout,
170
+ logger: @logger,
171
+ )
172
+
164
173
  loop do
165
174
  @logger.info "Sending #{@buffer.size} messages"
166
175
 
167
176
  attempt += 1
168
- transmit_messages
177
+ transmission.send_messages
169
178
 
170
179
  if @buffer.empty?
171
180
  @logger.info "Successfully transmitted all messages"
@@ -208,72 +217,5 @@ module Kafka
208
217
  def shutdown
209
218
  @broker_pool.shutdown
210
219
  end
211
-
212
- private
213
-
214
- def transmit_messages
215
- messages_for_broker = {}
216
-
217
- @buffer.each do |topic, partition, messages|
218
- broker_id = @broker_pool.get_leader_id(topic, partition)
219
-
220
- @logger.debug "Current leader for #{topic}/#{partition} is node #{broker_id}"
221
-
222
- messages_for_broker[broker_id] ||= MessageBuffer.new
223
- messages_for_broker[broker_id].concat(messages, topic: topic, partition: partition)
224
- end
225
-
226
- messages_for_broker.each do |broker_id, message_set|
227
- begin
228
- broker = @broker_pool.get_broker(broker_id)
229
-
230
- response = broker.produce(
231
- messages_for_topics: message_set.to_h,
232
- required_acks: @required_acks,
233
- timeout: @ack_timeout * 1000, # Kafka expects the timeout in milliseconds.
234
- )
235
-
236
- handle_response(response) if response
237
- rescue ConnectionError => e
238
- @logger.error "Could not connect to broker #{broker_id}: #{e}"
239
-
240
- # Mark the broker pool as stale in order to force a cluster metadata refresh.
241
- @broker_pool.mark_as_stale!
242
- end
243
- end
244
- end
245
-
246
- def handle_response(response)
247
- response.each_partition do |topic_info, partition_info|
248
- topic = topic_info.topic
249
- partition = partition_info.partition
250
-
251
- begin
252
- Protocol.handle_error(partition_info.error_code)
253
- rescue Kafka::CorruptMessage
254
- @logger.error "Corrupt message when writing to #{topic}/#{partition}"
255
- rescue Kafka::UnknownTopicOrPartition
256
- @logger.error "Unknown topic or partition #{topic}/#{partition}"
257
- rescue Kafka::LeaderNotAvailable
258
- @logger.error "Leader currently not available for #{topic}/#{partition}"
259
- @broker_pool.mark_as_stale!
260
- rescue Kafka::NotLeaderForPartition
261
- @logger.error "Broker not currently leader for #{topic}/#{partition}"
262
- @broker_pool.mark_as_stale!
263
- rescue Kafka::RequestTimedOut
264
- @logger.error "Timed out while writing to #{topic}/#{partition}"
265
- rescue Kafka::NotEnoughReplicas
266
- @logger.error "Not enough in-sync replicas for #{topic}/#{partition}"
267
- rescue Kafka::NotEnoughReplicasAfterAppend
268
- @logger.error "Messages written, but to fewer in-sync replicas than required for #{topic}/#{partition}"
269
- else
270
- offset = partition_info.offset
271
- @logger.info "Successfully sent messages for #{topic}/#{partition}; new offset is #{offset}"
272
-
273
- # The messages were successfully written; clear them from the buffer.
274
- @buffer.clear_messages(topic: topic, partition: partition)
275
- end
276
- end
277
- end
278
220
  end
279
221
  end
@@ -0,0 +1,87 @@
1
+ require "socket"
2
+
3
+ module Kafka
4
+
5
+ # Opens sockets in a non-blocking fashion, ensuring that we're not stalling
6
+ # for long periods of time.
7
+ #
8
+ # It's possible to set timeouts for connecting to the server, for reading data,
9
+ # and for writing data. Whenever a timeout is exceeded, Errno::ETIMEDOUT is
10
+ # raised.
11
+ #
12
+ class SocketWithTimeout
13
+
14
+ # Opens a socket.
15
+ #
16
+ # @param host [String]
17
+ # @param port [Integer]
18
+ # @param timeout [Integer] the connection timeout, in seconds.
19
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
20
+ def initialize(host, port, timeout: nil)
21
+ addr = Socket.getaddrinfo(host, nil)
22
+ sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
23
+
24
+ @socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
25
+ @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
26
+
27
+ begin
28
+ # Initiate the socket connection in the background. If it doesn't fail
29
+ # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
30
+ # indicating the connection is in progress.
31
+ @socket.connect_nonblock(sockaddr)
32
+ rescue IO::WaitWritable
33
+ # IO.select will block until the socket is writable or the timeout
34
+ # is exceeded, whichever comes first.
35
+ unless IO.select(nil, [@socket], nil, timeout)
36
+ # IO.select returns nil when the socket is not ready before timeout
37
+ # seconds have elapsed
38
+ @socket.close
39
+ raise Errno::ETIMEDOUT
40
+ end
41
+
42
+ begin
43
+ # Verify there is now a good connection.
44
+ @socket.connect_nonblock(sockaddr)
45
+ rescue Errno::EISCONN
46
+ # The socket is connected, we're good!
47
+ end
48
+ end
49
+ end
50
+
51
+ # Reads bytes from the socket, possible with a timeout.
52
+ #
53
+ # @param num_bytes [Integer] the number of bytes to read.
54
+ # @param timeout [Integer] the number of seconds to wait before timing out.
55
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
56
+ # @return [String] the data that was read from the socket.
57
+ def read(num_bytes, timeout: nil)
58
+ unless IO.select([@socket], nil, nil, timeout)
59
+ raise Errno::ETIMEDOUT
60
+ end
61
+
62
+ @socket.read(num_bytes)
63
+ end
64
+
65
+ # Writes bytes to the socket, possible with a timeout.
66
+ #
67
+ # @param bytes [String] the data that should be written to the socket.
68
+ # @param timeout [Integer] the number of seconds to wait before timing out.
69
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
70
+ # @return [Integer] the number of bytes written.
71
+ def write(bytes, timeout: nil)
72
+ unless IO.select(nil, [@socket], nil, timeout)
73
+ raise Errno::ETIMEDOUT
74
+ end
75
+
76
+ @socket.write(bytes)
77
+ end
78
+
79
+ def close
80
+ @socket.close
81
+ end
82
+
83
+ def set_encoding(encoding)
84
+ @socket.set_encoding(encoding)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,78 @@
1
+ module Kafka
2
+ class Transmission
3
+ def initialize(broker_pool:, buffer:, required_acks:, ack_timeout:, logger:)
4
+ @broker_pool = broker_pool
5
+ @buffer = buffer
6
+ @required_acks = required_acks
7
+ @ack_timeout = ack_timeout
8
+ @logger = logger
9
+ end
10
+
11
+ def send_messages
12
+ messages_for_broker = {}
13
+
14
+ @buffer.each do |topic, partition, messages|
15
+ broker_id = @broker_pool.get_leader_id(topic, partition)
16
+
17
+ @logger.debug "Current leader for #{topic}/#{partition} is node #{broker_id}"
18
+
19
+ messages_for_broker[broker_id] ||= MessageBuffer.new
20
+ messages_for_broker[broker_id].concat(messages, topic: topic, partition: partition)
21
+ end
22
+
23
+ messages_for_broker.each do |broker_id, message_set|
24
+ begin
25
+ broker = @broker_pool.get_broker(broker_id)
26
+
27
+ response = broker.produce(
28
+ messages_for_topics: message_set.to_h,
29
+ required_acks: @required_acks,
30
+ timeout: @ack_timeout * 1000, # Kafka expects the timeout in milliseconds.
31
+ )
32
+
33
+ handle_response(response) if response
34
+ rescue ConnectionError => e
35
+ @logger.error "Could not connect to broker #{broker_id}: #{e}"
36
+
37
+ # Mark the broker pool as stale in order to force a cluster metadata refresh.
38
+ @broker_pool.mark_as_stale!
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def handle_response(response)
46
+ response.each_partition do |topic_info, partition_info|
47
+ topic = topic_info.topic
48
+ partition = partition_info.partition
49
+
50
+ begin
51
+ Protocol.handle_error(partition_info.error_code)
52
+ rescue Kafka::CorruptMessage
53
+ @logger.error "Corrupt message when writing to #{topic}/#{partition}"
54
+ rescue Kafka::UnknownTopicOrPartition
55
+ @logger.error "Unknown topic or partition #{topic}/#{partition}"
56
+ rescue Kafka::LeaderNotAvailable
57
+ @logger.error "Leader currently not available for #{topic}/#{partition}"
58
+ @broker_pool.mark_as_stale!
59
+ rescue Kafka::NotLeaderForPartition
60
+ @logger.error "Broker not currently leader for #{topic}/#{partition}"
61
+ @broker_pool.mark_as_stale!
62
+ rescue Kafka::RequestTimedOut
63
+ @logger.error "Timed out while writing to #{topic}/#{partition}"
64
+ rescue Kafka::NotEnoughReplicas
65
+ @logger.error "Not enough in-sync replicas for #{topic}/#{partition}"
66
+ rescue Kafka::NotEnoughReplicasAfterAppend
67
+ @logger.error "Messages written, but to fewer in-sync replicas than required for #{topic}/#{partition}"
68
+ else
69
+ offset = partition_info.offset
70
+ @logger.info "Successfully sent messages for #{topic}/#{partition}; new offset is #{offset}"
71
+
72
+ # The messages were successfully written; clear them from the buffer.
73
+ @buffer.clear_messages(topic: topic, partition: partition)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,3 @@
1
1
  module Kafka
2
- VERSION = "0.1.0-beta4"
2
+ VERSION = "0.1.0-beta5"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-kafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.beta4
4
+ version: 0.1.0.pre.beta5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-01 00:00:00.000000000 Z
11
+ date: 2016-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-benchmark
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: |-
98
112
  A client library for the Kafka distributed commit log.
99
113
 
@@ -133,6 +147,8 @@ files:
133
147
  - lib/kafka/protocol/produce_response.rb
134
148
  - lib/kafka/protocol/request_message.rb
135
149
  - lib/kafka/protocol/topic_metadata_request.rb
150
+ - lib/kafka/socket_with_timeout.rb
151
+ - lib/kafka/transmission.rb
136
152
  - lib/kafka/version.rb
137
153
  - lib/ruby-kafka.rb
138
154
  homepage: https://github.com/zendesk/ruby-kafka