codeclimate-poseidon 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +14 -0
  5. data/.yardopts +8 -0
  6. data/CHANGES.md +31 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +72 -0
  10. data/Rakefile +20 -0
  11. data/TODO.md +27 -0
  12. data/examples/consumer.rb +18 -0
  13. data/examples/producer.rb +9 -0
  14. data/lib/poseidon.rb +120 -0
  15. data/lib/poseidon/broker_pool.rb +86 -0
  16. data/lib/poseidon/cluster_metadata.rb +94 -0
  17. data/lib/poseidon/compressed_value.rb +23 -0
  18. data/lib/poseidon/compression.rb +30 -0
  19. data/lib/poseidon/compression/gzip_codec.rb +23 -0
  20. data/lib/poseidon/compression/snappy_codec.rb +29 -0
  21. data/lib/poseidon/connection.rb +169 -0
  22. data/lib/poseidon/fetched_message.rb +37 -0
  23. data/lib/poseidon/message.rb +151 -0
  24. data/lib/poseidon/message_conductor.rb +86 -0
  25. data/lib/poseidon/message_set.rb +80 -0
  26. data/lib/poseidon/message_to_send.rb +33 -0
  27. data/lib/poseidon/messages_for_broker.rb +56 -0
  28. data/lib/poseidon/messages_to_send.rb +47 -0
  29. data/lib/poseidon/messages_to_send_batch.rb +27 -0
  30. data/lib/poseidon/partition_consumer.rb +225 -0
  31. data/lib/poseidon/producer.rb +199 -0
  32. data/lib/poseidon/producer_compression_config.rb +37 -0
  33. data/lib/poseidon/protocol.rb +122 -0
  34. data/lib/poseidon/protocol/protocol_struct.rb +256 -0
  35. data/lib/poseidon/protocol/request_buffer.rb +77 -0
  36. data/lib/poseidon/protocol/response_buffer.rb +72 -0
  37. data/lib/poseidon/sync_producer.rb +161 -0
  38. data/lib/poseidon/topic_metadata.rb +89 -0
  39. data/lib/poseidon/version.rb +4 -0
  40. data/log/.gitkeep +0 -0
  41. data/poseidon.gemspec +27 -0
  42. data/spec/integration/multiple_brokers/consumer_spec.rb +45 -0
  43. data/spec/integration/multiple_brokers/metadata_failures_spec.rb +144 -0
  44. data/spec/integration/multiple_brokers/rebalance_spec.rb +69 -0
  45. data/spec/integration/multiple_brokers/round_robin_spec.rb +41 -0
  46. data/spec/integration/multiple_brokers/spec_helper.rb +60 -0
  47. data/spec/integration/simple/compression_spec.rb +23 -0
  48. data/spec/integration/simple/connection_spec.rb +35 -0
  49. data/spec/integration/simple/multiple_brokers_spec.rb +10 -0
  50. data/spec/integration/simple/simple_producer_and_consumer_spec.rb +121 -0
  51. data/spec/integration/simple/spec_helper.rb +16 -0
  52. data/spec/integration/simple/truncated_messages_spec.rb +46 -0
  53. data/spec/integration/simple/unavailable_broker_spec.rb +72 -0
  54. data/spec/spec_helper.rb +32 -0
  55. data/spec/test_cluster.rb +211 -0
  56. data/spec/unit/broker_pool_spec.rb +98 -0
  57. data/spec/unit/cluster_metadata_spec.rb +46 -0
  58. data/spec/unit/compression/gzip_codec_spec.rb +34 -0
  59. data/spec/unit/compression/snappy_codec_spec.rb +49 -0
  60. data/spec/unit/compression_spec.rb +17 -0
  61. data/spec/unit/connection_spec.rb +4 -0
  62. data/spec/unit/fetched_message_spec.rb +11 -0
  63. data/spec/unit/message_conductor_spec.rb +164 -0
  64. data/spec/unit/message_set_spec.rb +42 -0
  65. data/spec/unit/message_spec.rb +129 -0
  66. data/spec/unit/message_to_send_spec.rb +10 -0
  67. data/spec/unit/messages_for_broker_spec.rb +54 -0
  68. data/spec/unit/messages_to_send_batch_spec.rb +25 -0
  69. data/spec/unit/messages_to_send_spec.rb +63 -0
  70. data/spec/unit/partition_consumer_spec.rb +142 -0
  71. data/spec/unit/producer_compression_config_spec.rb +42 -0
  72. data/spec/unit/producer_spec.rb +51 -0
  73. data/spec/unit/protocol/request_buffer_spec.rb +16 -0
  74. data/spec/unit/protocol_spec.rb +54 -0
  75. data/spec/unit/sync_producer_spec.rb +156 -0
  76. data/spec/unit/topic_metadata_spec.rb +43 -0
  77. metadata +225 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0d4c27c567e99772b9a024ffe551b68dd81185bf
4
+ data.tar.gz: b384e7627ec29faadbc7582a69312050ffe693bf
5
+ SHA512:
6
+ metadata.gz: aab82245bd26818cad1a361bfefa20619c39e4f882af4204c5f0f4e0f2b17535e86829595cbb9ae1ed63d9fa1bc8fb63b9e604124e336d74dcbc5579f55d0fe3
7
+ data.tar.gz: 1ab14df98fc583eb6d48df2986421dc9ec33fbf39a17bfbc64ccc0172dffa2a7a07cb6a4eac3e0880a6298634f365f62a33c5ddfaea696d09f4684d8d77025ae
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.log
19
+ *.log.*
20
+ tags
21
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ laguage: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1
6
+ - ruby-head
7
+ - jruby-19mode
8
+ - jruby-head
9
+ - rbx-2
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: ruby-head
13
+ - rvm: jruby-head
14
+ - rvm: rbx-2
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
3
+ --no-private
4
+ --files LICENSE.txt
5
+ --exclude '~$'
6
+ --title 'Poseidon (Kafka library for Ruby)'
7
+ --api public
8
+ --readme README.md
data/CHANGES.md ADDED
@@ -0,0 +1,31 @@
1
+ # 0.0.5
2
+
3
+ * Add support for negative offsets. [GH-24]
4
+ * Fix serious bug where we would send messages to the wrong partition. [GH-36] (Thanks @sclasen and @jorgeortiz85 for tracking this down.)
5
+ * Better error message when we can't connect to a broker. [GH-42]
6
+ * Handle broker rebalances. [GH-43]
7
+ * PartitionConsumer: Block for messages by default. [GH-48]
8
+ * Add a logger to help debug issues. [GH-51]
9
+ * Add snappy support. [GH-57]
10
+ * Allow `:none` value for `:compression_codec` option. [GH-72]
11
+ * Allow request buffer to accept mixed encodings. [GH-74]
12
+
13
+ # 0.0.4
14
+
15
+ * Don't truncate UTF8 Messages [GH-18]
16
+ * Gracefully handle truncated fetch reponses [GH-19]
17
+
18
+ # 0.0.3
19
+
20
+ * Better distribute messages across partitions.
21
+ * Handle broken connections better.
22
+ * Gracefully handle attempts to send an empty set of messages.
23
+
24
+ # 0.0.2
25
+
26
+ * Added ability to create a partitioner consumer for a topic+partition using topic metadata.
27
+ * Added PartitionConsumer#offset to return offset of the last fetch
28
+
29
+ # 0.0.1
30
+
31
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake'
4
+
5
+ # Specify your gem's dependencies in poseidon.gemspec
6
+ gemspec
7
+
8
+ gem 'coveralls', require: false
9
+
10
+ group :development do
11
+ gem 'github-markup', :platform => :ruby
12
+ gem 'redcarpet', :platform => :ruby
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Bob Potter
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Poseidon [![Build Status](https://travis-ci.org/bpot/poseidon.png?branch=master)](https://travis-ci.org/bpot/poseidon) [![Code Climate](https://codeclimate.com/github/bpot/poseidon.png)](https://codeclimate.com/github/bpot/poseidon)
2
+
3
+ Poseidon is a Kafka client. Poseidon only supports the 0.8 API and above.
4
+
5
+ **Until 1.0.0 this should be considered ALPHA software and not neccessarily production ready.**
6
+
7
+ ## Usage
8
+
9
+ ### API Documentation
10
+
11
+ * [Latest release](http://rubydoc.info/gems/poseidon)
12
+ * [Github master](http://rubydoc.info/github/bpot/poseidon)
13
+
14
+ ### Installing a Kafka broker locally
15
+
16
+ Follow the [instructions](http://kafka.apache.org/documentation.html#quickstart) on the Kafka wiki to build Kafka 0.8 and get a test broker up and running.
17
+
18
+ ### Sending messages to Kafka
19
+
20
+ ```ruby
21
+ require 'poseidon'
22
+
23
+ producer = Poseidon::Producer.new(["localhost:9092"], "my_test_producer")
24
+
25
+ messages = []
26
+ messages << Poseidon::MessageToSend.new("topic1", "value1")
27
+ messages << Poseidon::MessageToSend.new("topic2", "value2")
28
+ producer.send_messages(messages)
29
+ ```
30
+
31
+ More detailed [Poseidon::Producer](http://rubydoc.info/github/bpot/poseidon/Poseidon/Producer) documentation.
32
+
33
+ ### Fetching messages from Kafka
34
+
35
+ ```ruby
36
+ require 'poseidon'
37
+
38
+ consumer = Poseidon::PartitionConsumer.new("my_test_consumer", "localhost", 9092,
39
+ "topic1", 0, :earliest_offset)
40
+
41
+ loop do
42
+ messages = consumer.fetch
43
+ messages.each do |m|
44
+ puts m.value
45
+ end
46
+ end
47
+ ```
48
+
49
+ More detailed [Poseidon::PartitionConsumer](http://rubydoc.info/github/bpot/poseidon/Poseidon/PartitionConsumer) documentation.
50
+
51
+ ### Using snappy compression
52
+
53
+ To use snappy compression in your producers or consumers, install the [snappy](http://rubygems.org/gems/snappy) gem or simply add `gem 'snappy'` to your project's Gemfile.
54
+
55
+ ## Semantic Versioning
56
+
57
+ This gem follows [SemVer](http://semver.org). In particular, the public API should not be considered stable and anything may change without warning until Version 1.0.0. Additionally, for the purposes of the versioning the public API is everything documented in the [public API docs](http://rubydoc.info/github/bpot/poseidon).
58
+
59
+ ## Requirements
60
+
61
+ * Ruby 1.9.3 or higher (1.9.2 and below not supported!!!)
62
+ * Kafka 0.8 or higher
63
+
64
+ ## Integration Tests
65
+
66
+ In order to run integration tests you must specify a `KAFKA_PATH` environment variable which points to a built Kafka installation. To build Kafka locally follow the [instructions](http://kafka.apache.org/documentation.html#quickstart) provided by the project.
67
+
68
+ # cd ~/src/poseidon/
69
+ # bundle
70
+ # KAFKA_PATH=~/src/kafka bundle exec rake spec:all # run all unit and integration specs
71
+
72
+ The poseidon test suite will take care of spinning up and down the broker(s) needed for the integration tests.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new("spec:unit") do |t|
5
+ t.pattern = 'spec/unit/**/*_spec.rb'
6
+ end
7
+
8
+ RSpec::Core::RakeTask.new('spec:integration:simple') do |t|
9
+ t.pattern = 'spec/integration/simple/*_spec.rb'
10
+ t.rspec_opts = ["--fail-fast", "-f d"]
11
+ end
12
+
13
+ RSpec::Core::RakeTask.new('spec:integration:multiple_brokers') do |t|
14
+ t.pattern = 'spec/integration/multiple_brokers/*_spec.rb'
15
+ t.rspec_opts = ["--fail-fast", "-f d"]
16
+ end
17
+
18
+ task :spec => 'spec:unit'
19
+ task 'spec:all' => ['spec:unit', 'spec:integration:simple', 'spec:integration:multiple_brokers']
20
+ task :default => 'spec:unit'
data/TODO.md ADDED
@@ -0,0 +1,27 @@
1
+ ### 0.0.1
2
+ * Ensure that protocol errors are being handled correctly and not bubbling up
3
+ * More integration tests, replication, leader changes, etc. Investigate interesting cases in kafka's tests
4
+ * End-to-end integration specs
5
+ - In specs that test broker failure, verify that messages were actually sent/not sent with a consumer.
6
+
7
+ * AsyncProducer
8
+ - Implement a bounded queue, sending thread, etc
9
+ * Cleanup: extract protocol struct delegation to a module.
10
+ * When failing to send messages in sync producer, return messages that failed to send?
11
+
12
+ ### 0.0.2
13
+
14
+ * New Consumer/Consumer Enhancements
15
+ - Automatically partition work among consumers (zookeeper, redis, pluggable?)
16
+ - Handle case where the offset we're trying to read from no longer exists
17
+
18
+ * Snappy Compression
19
+ - snappy: c-ext, would like to avoid
20
+ - snappy_ffi: ffi interface, but needs to be updated (pre c-api)
21
+ and has no specs, docs. Also linked to a c-ext version, two gems, etc..
22
+ - new snappy ffi library with specs, docs, etc. Shave that Yak!
23
+
24
+ * Benchmark/Profiling. KGIO?
25
+
26
+ ### 0.0.3 -- Targets Kafka 0.8.1
27
+ - Offset API
@@ -0,0 +1,18 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+ require 'poseidon'
3
+
4
+ producer = Poseidon::PartitionConsumer.new("example_consumer", "localhost", 9092,
5
+ "example", 0, :earliest_offset)
6
+
7
+ loop do
8
+ begin
9
+ messages = producer.fetch
10
+ messages.each do |m|
11
+ puts "Received message: #{m.value}"
12
+ end
13
+ rescue Poseidon::Errors::UnknownTopicOrPartition
14
+ puts "Topic does not exist yet"
15
+ end
16
+
17
+ sleep 1
18
+ end
@@ -0,0 +1,9 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+ require 'poseidon'
3
+
4
+ producer = Poseidon::Producer.new(["localhost:9092"], "example_producer")
5
+
6
+ loop do
7
+ producer.send_messages([Poseidon::MessageToSend.new("example", Time.now.to_s)])
8
+ sleep 1
9
+ end
data/lib/poseidon.rb ADDED
@@ -0,0 +1,120 @@
1
+ # Stdlib requires
2
+ require 'socket'
3
+ require 'zlib'
4
+ require 'thread'
5
+ require 'set'
6
+ require 'logger'
7
+ require 'stringio'
8
+
9
+ # Top level Poseidon namespace
10
+ #
11
+ # @api public
12
+ module Poseidon
13
+ # Posiedon exception namespace
14
+ module Errors
15
+ # @api private
16
+ class ProtocolError < StandardError; end
17
+
18
+ # Protocol Exceptions
19
+ #
20
+ # These are defined by the Poseidon wire format,
21
+ # they should be caught before being raised to users.
22
+ #
23
+ # @api private
24
+ class UnknownError < ProtocolError; end
25
+ # @api private
26
+ class OffsetOutOfRange < ProtocolError; end
27
+ # @api private
28
+ class InvalidMessage < ProtocolError; end
29
+ # @api private
30
+ class UnknownTopicOrPartition < ProtocolError; end
31
+ # @api private
32
+ class InvalidMessageSize < ProtocolError; end
33
+ # @api private
34
+ class LeaderNotAvailable < ProtocolError; end
35
+ # @api private
36
+ class NotLeaderForPartition < ProtocolError; end
37
+ # @api private
38
+ class RequestTimedOut < ProtocolError; end
39
+ # @api private
40
+ class BrokerNotAvailable < ProtocolError; end
41
+ # @api private
42
+ class ReplicaNotAvailable < ProtocolError; end
43
+ # @api private
44
+ class MessageSizeTooLarge < ProtocolError; end
45
+ # @api private
46
+ class UnrecognizedProtocolError < ProtocolError; end
47
+
48
+ # @api private
49
+ NO_ERROR_CODE = 0
50
+ # @api private
51
+ ERROR_CODES = {
52
+ -1 => UnknownError,
53
+ 1 => OffsetOutOfRange,
54
+ 2 => InvalidMessage,
55
+ 3 => UnknownTopicOrPartition,
56
+ 4 => InvalidMessageSize,
57
+ 5 => LeaderNotAvailable,
58
+ 6 => NotLeaderForPartition,
59
+ 7 => RequestTimedOut,
60
+ 8 => BrokerNotAvailable,
61
+ 9 => ReplicaNotAvailable,
62
+ 10 => MessageSizeTooLarge
63
+ }
64
+
65
+ # Raised when a custom partitioner tries to send
66
+ # a message to a partition that doesn't exist.
67
+ class InvalidPartitionError < StandardError; end
68
+
69
+ # Raised when we are unable to fetch metadata from
70
+ # any of the brokers.
71
+ class UnableToFetchMetadata < StandardError; end
72
+
73
+ # Raised when a messages checksum doesn't match
74
+ class ChecksumError < StandardError; end
75
+
76
+ # Raised when you try to send messages to a producer
77
+ # object that has been #shutdown
78
+ class ProducerShutdownError < StandardError; end
79
+ end
80
+
81
+ def self.logger
82
+ @logger ||= null_logger
83
+ end
84
+
85
+ def self.logger=(logger)
86
+ @logger = logger
87
+ end
88
+
89
+ private
90
+ def self.null_logger
91
+ devnull = RUBY_PLATFORM =~ /w32/ ? 'nul' : '/dev/null'
92
+ l = Logger.new(devnull)
93
+ l.level = Logger::INFO
94
+ l
95
+ end
96
+ end
97
+
98
+ # Public API
99
+ require "poseidon/message_to_send"
100
+ require "poseidon/producer"
101
+ require "poseidon/fetched_message"
102
+ require "poseidon/partition_consumer"
103
+
104
+ # Poseidon!
105
+ require "poseidon/message"
106
+ require "poseidon/message_set"
107
+ require "poseidon/topic_metadata"
108
+ require "poseidon/protocol"
109
+
110
+ require "poseidon/broker_pool"
111
+ require "poseidon/cluster_metadata"
112
+ require "poseidon/compression"
113
+ require "poseidon/connection"
114
+ require "poseidon/message_conductor"
115
+ require "poseidon/messages_for_broker"
116
+ require "poseidon/messages_to_send"
117
+ require "poseidon/messages_to_send_batch"
118
+ require "poseidon/producer_compression_config"
119
+ require "poseidon/sync_producer"
120
+ require "poseidon/version"
@@ -0,0 +1,86 @@
1
+ module Poseidon
2
+ # BrokerPool allows you to send api calls to the a brokers Connection.
3
+ #
4
+ # @api private
5
+ class BrokerPool
6
+ class UnknownBroker < StandardError; end
7
+
8
+ # @yieldparam [BrokerPool]
9
+ def self.open(client_id, seed_brokers, socket_timeout_ms, &block)
10
+ broker_pool = new(client_id, seed_brokers, socket_timeout_ms)
11
+
12
+ yield broker_pool
13
+ ensure
14
+ broker_pool.close
15
+ end
16
+
17
+ # @param [String] client_id
18
+ def initialize(client_id, seed_brokers, socket_timeout_ms)
19
+ @connections = {}
20
+ @brokers = {}
21
+ @client_id = client_id
22
+ @seed_brokers = seed_brokers
23
+ @socket_timeout_ms = socket_timeout_ms
24
+ end
25
+
26
+ def fetch_metadata(topics)
27
+ @seed_brokers.each do |broker|
28
+ if metadata = fetch_metadata_from_broker(broker, topics)
29
+ Poseidon.logger.debug { "Fetched metadata from #{broker}:\n" + metadata.to_s }
30
+ return metadata
31
+ end
32
+ end
33
+ raise Errors::UnableToFetchMetadata
34
+ end
35
+
36
+ # Update the brokers we know about
37
+ #
38
+ # TODO break connection when a brokers info changes?
39
+ #
40
+ # @param [Hash<Integer,Hash>] brokers
41
+ # Hash of broker_id => { :host => host, :port => port }
42
+ def update_known_brokers(brokers)
43
+ @brokers.update(brokers)
44
+ nil
45
+ end
46
+
47
+ # Executes an api call on the connection
48
+ #
49
+ # @param [Integer] broker_id id of the broker we want to execute it on
50
+ # @param [Symbol] api_call
51
+ # the api call we want to execute (:produce,:fetch,etc)
52
+ def execute_api_call(broker_id, api_call, *args)
53
+ connection(broker_id).send(api_call, *args)
54
+ end
55
+
56
+ # Closes all open connections to brokers
57
+ def close
58
+ @brokers.values(&:close)
59
+ @brokers = {}
60
+ end
61
+
62
+ alias_method :shutdown, :close
63
+
64
+ private
65
+ def fetch_metadata_from_broker(broker, topics)
66
+ host, port = broker.split(":")
67
+ Connection.open(host, port, @client_id, @socket_timeout_ms) do |connection|
68
+ connection.topic_metadata(topics)
69
+ end
70
+ rescue Connection::ConnectionFailedError
71
+ return nil
72
+ end
73
+
74
+ def connection(broker_id)
75
+ @connections[broker_id] ||= new_connection(broker_id)
76
+ end
77
+
78
+ def new_connection(broker_id)
79
+ info = @brokers[broker_id]
80
+ if info.nil?
81
+ raise UnknownBroker
82
+ end
83
+ Connection.new(info[:host], info[:port], @client_id, @socket_timeout_ms)
84
+ end
85
+ end
86
+ end