mongo 1.10.0-java

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 (116) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +190 -0
  5. data/README.md +149 -0
  6. data/Rakefile +31 -0
  7. data/VERSION +1 -0
  8. data/bin/mongo_console +43 -0
  9. data/ext/jsasl/target/jsasl.jar +0 -0
  10. data/lib/mongo.rb +90 -0
  11. data/lib/mongo/bulk_write_collection_view.rb +380 -0
  12. data/lib/mongo/collection.rb +1164 -0
  13. data/lib/mongo/collection_writer.rb +364 -0
  14. data/lib/mongo/connection.rb +19 -0
  15. data/lib/mongo/connection/node.rb +239 -0
  16. data/lib/mongo/connection/pool.rb +347 -0
  17. data/lib/mongo/connection/pool_manager.rb +325 -0
  18. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  21. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  22. data/lib/mongo/connection/socket/tcp_socket.rb +86 -0
  23. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  24. data/lib/mongo/cursor.rb +719 -0
  25. data/lib/mongo/db.rb +735 -0
  26. data/lib/mongo/exception.rb +88 -0
  27. data/lib/mongo/functional.rb +21 -0
  28. data/lib/mongo/functional/authentication.rb +318 -0
  29. data/lib/mongo/functional/logging.rb +85 -0
  30. data/lib/mongo/functional/read_preference.rb +174 -0
  31. data/lib/mongo/functional/sasl_java.rb +48 -0
  32. data/lib/mongo/functional/uri_parser.rb +374 -0
  33. data/lib/mongo/functional/write_concern.rb +66 -0
  34. data/lib/mongo/gridfs.rb +18 -0
  35. data/lib/mongo/gridfs/grid.rb +112 -0
  36. data/lib/mongo/gridfs/grid_ext.rb +53 -0
  37. data/lib/mongo/gridfs/grid_file_system.rb +163 -0
  38. data/lib/mongo/gridfs/grid_io.rb +484 -0
  39. data/lib/mongo/legacy.rb +140 -0
  40. data/lib/mongo/mongo_client.rb +702 -0
  41. data/lib/mongo/mongo_replica_set_client.rb +523 -0
  42. data/lib/mongo/mongo_sharded_client.rb +159 -0
  43. data/lib/mongo/networking.rb +370 -0
  44. data/lib/mongo/utils.rb +19 -0
  45. data/lib/mongo/utils/conversions.rb +110 -0
  46. data/lib/mongo/utils/core_ext.rb +70 -0
  47. data/lib/mongo/utils/server_version.rb +69 -0
  48. data/lib/mongo/utils/support.rb +80 -0
  49. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  50. data/mongo.gemspec +36 -0
  51. data/test/functional/authentication_test.rb +35 -0
  52. data/test/functional/bulk_api_stress_test.rb +133 -0
  53. data/test/functional/bulk_write_collection_view_test.rb +1129 -0
  54. data/test/functional/client_test.rb +565 -0
  55. data/test/functional/collection_test.rb +2073 -0
  56. data/test/functional/collection_writer_test.rb +83 -0
  57. data/test/functional/conversions_test.rb +163 -0
  58. data/test/functional/cursor_fail_test.rb +63 -0
  59. data/test/functional/cursor_message_test.rb +57 -0
  60. data/test/functional/cursor_test.rb +625 -0
  61. data/test/functional/db_api_test.rb +819 -0
  62. data/test/functional/db_connection_test.rb +27 -0
  63. data/test/functional/db_test.rb +344 -0
  64. data/test/functional/grid_file_system_test.rb +285 -0
  65. data/test/functional/grid_io_test.rb +252 -0
  66. data/test/functional/grid_test.rb +273 -0
  67. data/test/functional/pool_test.rb +62 -0
  68. data/test/functional/safe_test.rb +98 -0
  69. data/test/functional/ssl_test.rb +29 -0
  70. data/test/functional/support_test.rb +62 -0
  71. data/test/functional/timeout_test.rb +58 -0
  72. data/test/functional/uri_test.rb +330 -0
  73. data/test/functional/write_concern_test.rb +118 -0
  74. data/test/helpers/general.rb +50 -0
  75. data/test/helpers/test_unit.rb +317 -0
  76. data/test/replica_set/authentication_test.rb +35 -0
  77. data/test/replica_set/basic_test.rb +174 -0
  78. data/test/replica_set/client_test.rb +341 -0
  79. data/test/replica_set/complex_connect_test.rb +77 -0
  80. data/test/replica_set/connection_test.rb +138 -0
  81. data/test/replica_set/count_test.rb +64 -0
  82. data/test/replica_set/cursor_test.rb +212 -0
  83. data/test/replica_set/insert_test.rb +140 -0
  84. data/test/replica_set/max_values_test.rb +145 -0
  85. data/test/replica_set/pinning_test.rb +55 -0
  86. data/test/replica_set/query_test.rb +73 -0
  87. data/test/replica_set/read_preference_test.rb +214 -0
  88. data/test/replica_set/refresh_test.rb +175 -0
  89. data/test/replica_set/replication_ack_test.rb +94 -0
  90. data/test/replica_set/ssl_test.rb +32 -0
  91. data/test/sharded_cluster/basic_test.rb +197 -0
  92. data/test/shared/authentication/basic_auth_shared.rb +286 -0
  93. data/test/shared/authentication/bulk_api_auth_shared.rb +259 -0
  94. data/test/shared/authentication/gssapi_shared.rb +164 -0
  95. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  96. data/test/shared/ssl_shared.rb +235 -0
  97. data/test/test_helper.rb +56 -0
  98. data/test/threading/basic_test.rb +120 -0
  99. data/test/tools/mongo_config.rb +608 -0
  100. data/test/tools/mongo_config_test.rb +160 -0
  101. data/test/unit/client_test.rb +347 -0
  102. data/test/unit/collection_test.rb +166 -0
  103. data/test/unit/connection_test.rb +325 -0
  104. data/test/unit/cursor_test.rb +299 -0
  105. data/test/unit/db_test.rb +136 -0
  106. data/test/unit/grid_test.rb +76 -0
  107. data/test/unit/mongo_sharded_client_test.rb +48 -0
  108. data/test/unit/node_test.rb +93 -0
  109. data/test/unit/pool_manager_test.rb +142 -0
  110. data/test/unit/read_pref_test.rb +115 -0
  111. data/test/unit/read_test.rb +159 -0
  112. data/test/unit/safe_test.rb +158 -0
  113. data/test/unit/sharding_pool_manager_test.rb +84 -0
  114. data/test/unit/write_concern_test.rb +175 -0
  115. metadata +260 -0
  116. metadata.gz.sig +0 -0
@@ -0,0 +1,159 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ # Instantiates and manages connections to a MongoDB sharded cluster for high availability.
18
+ class MongoShardedClient < MongoReplicaSetClient
19
+ include ThreadLocalVariableManager
20
+
21
+ SHARDED_CLUSTER_OPTS = [:refresh_mode, :refresh_interval, :tag_sets, :read]
22
+
23
+ attr_reader :seeds, :refresh_interval, :refresh_mode,
24
+ :refresh_version, :manager
25
+
26
+ def initialize(*args)
27
+ opts = args.last.is_a?(Hash) ? args.pop : {}
28
+
29
+ nodes = args.flatten
30
+
31
+ if nodes.empty? and ENV.has_key?('MONGODB_URI')
32
+ parser = URIParser.new ENV['MONGODB_URI']
33
+ opts = parser.connection_options.merge! opts
34
+ nodes = parser.node_strings
35
+ end
36
+
37
+ unless nodes.length > 0
38
+ raise MongoArgumentError, "A MongoShardedClient requires at least one seed node."
39
+ end
40
+
41
+ @seeds = nodes.map do |host_port|
42
+ Support.normalize_seeds(host_port)
43
+ end
44
+
45
+ # TODO: add a method for replacing this list of node.
46
+ @seeds.freeze
47
+
48
+ # Refresh
49
+ @last_refresh = Time.now
50
+ @refresh_version = 0
51
+
52
+ # No connection manager by default.
53
+ @manager = nil
54
+
55
+ # Lock for request ids.
56
+ @id_lock = Mutex.new
57
+
58
+ @connected = false
59
+
60
+ @connect_mutex = Mutex.new
61
+
62
+ @mongos = true
63
+
64
+ check_opts(opts)
65
+ setup(opts)
66
+ end
67
+
68
+ def valid_opts
69
+ super + SHARDED_CLUSTER_OPTS
70
+ end
71
+
72
+ def inspect
73
+ "<Mongo::MongoShardedClient:0x#{self.object_id.to_s(16)} @seeds=#{@seeds.inspect} " +
74
+ "@connected=#{@connected}>"
75
+ end
76
+
77
+ # Initiate a connection to the sharded cluster.
78
+ def connect(force = !connected?)
79
+ return unless force
80
+ log(:info, "Connecting...")
81
+
82
+ # Prevent recursive connection attempts from the same thread.
83
+ # This is done rather than using a Monitor to prevent potentially recursing
84
+ # infinitely while attempting to connect and continually failing. Instead, fail fast.
85
+ raise ConnectionFailure, "Failed to get node data." if thread_local[:locks][:connecting]
86
+
87
+ @connect_mutex.synchronize do
88
+ begin
89
+ thread_local[:locks][:connecting] = true
90
+ if @manager
91
+ thread_local[:managers][self] = @manager
92
+ @manager.refresh! @seeds
93
+ else
94
+ @manager = ShardingPoolManager.new(self, @seeds)
95
+ ensure_manager
96
+ @manager.connect
97
+ check_wire_version_in_range
98
+ end
99
+ ensure
100
+ thread_local[:locks][:connecting] = false
101
+ end
102
+
103
+ @refresh_version += 1
104
+ @last_refresh = Time.now
105
+ @connected = true
106
+ end
107
+ end
108
+
109
+ # Force a hard refresh of this connection's view
110
+ # of the sharded cluster.
111
+ #
112
+ # @return [Boolean] +true+ if hard refresh
113
+ # occurred. +false+ is returned when unable
114
+ # to get the refresh lock.
115
+ def hard_refresh!
116
+ log(:info, "Initiating hard refresh...")
117
+ connect(true)
118
+ return true
119
+ end
120
+
121
+ def connected?
122
+ !!(@connected && @manager.primary_pool)
123
+ end
124
+
125
+ # Returns +true+ if it's okay to read from a secondary node.
126
+ # Since this is a sharded cluster, this must always be false.
127
+ #
128
+ # This method exist primarily so that Cursor objects will
129
+ # generate query messages with a slaveOkay value of +true+.
130
+ #
131
+ # @return [Boolean] +true+
132
+ def slave_ok?
133
+ false
134
+ end
135
+
136
+ def checkout(&block)
137
+ tries = 0
138
+ begin
139
+ super(&block)
140
+ rescue ConnectionFailure
141
+ tries +=1
142
+ tries < 2 ? retry : raise
143
+ end
144
+ end
145
+
146
+ # Initialize a connection to MongoDB using the MongoDB URI spec.
147
+ #
148
+ # @param uri [ String ] string of the format:
149
+ # mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]
150
+ #
151
+ # @param options [ Hash ] Any of the options available for MongoShardedClient.new
152
+ #
153
+ # @return [ Mongo::MongoShardedClient ] The sharded client.
154
+ def self.from_uri(uri, options={})
155
+ uri ||= ENV['MONGODB_URI']
156
+ URIParser.new(uri).connection(options, false, true)
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,370 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ module Networking
17
+
18
+ STANDARD_HEADER_SIZE = 16
19
+ RESPONSE_HEADER_SIZE = 20
20
+
21
+ # Counter for generating unique request ids.
22
+ @@current_request_id = 0
23
+
24
+ # Send a message to MongoDB, adding the necessary headers.
25
+ #
26
+ # @param [Integer] operation a MongoDB opcode.
27
+ # @param [BSON::ByteBuffer] message a message to send to the database.
28
+ #
29
+ # @option opts [Symbol] :connection (:writer) The connection to which
30
+ # this message should be sent. Valid options are :writer and :reader.
31
+ #
32
+ # @return [Integer] number of bytes sent
33
+ def send_message(operation, message, opts={})
34
+ if opts.is_a?(String)
35
+ warn "MongoClient#send_message no longer takes a string log message. " +
36
+ "Logging is now handled within the Collection and Cursor classes."
37
+ opts = {}
38
+ end
39
+
40
+ add_message_headers(message, operation)
41
+ packed_message = message.to_s
42
+
43
+ sock = nil
44
+ pool = opts.fetch(:pool, nil)
45
+ begin
46
+ if pool
47
+ #puts "send_message pool.port:#{pool.port}"
48
+ sock = pool.checkout
49
+ else
50
+ sock ||= checkout_writer
51
+ end
52
+ send_message_on_socket(packed_message, sock)
53
+ rescue SystemStackError, NoMemoryError, SystemCallError => ex
54
+ close
55
+ raise ex
56
+ ensure
57
+ if sock
58
+ sock.checkin
59
+ end
60
+ end
61
+ true
62
+ end
63
+
64
+ # Sends a message to the database, waits for a response, and raises
65
+ # an exception if the operation has failed.
66
+ #
67
+ # @param [Integer] operation a MongoDB opcode.
68
+ # @param [BSON::ByteBuffer] message a message to send to the database.
69
+ # @param [String] db_name the name of the database. used on call to get_last_error.
70
+ # @param [String] log_message this is currently a no-op and will be removed.
71
+ # @param [Hash] write_concern write concern.
72
+ #
73
+ # @see DB#get_last_error for valid last error params.
74
+ #
75
+ # @return [Hash] The document returned by the call to getlasterror.
76
+ def send_message_with_gle(operation, message, db_name, log_message=nil, write_concern=false)
77
+ docs = num_received = cursor_id = ''
78
+ add_message_headers(message, operation)
79
+
80
+ last_error_message = build_get_last_error_message(db_name, write_concern)
81
+ last_error_id = add_message_headers(last_error_message, Mongo::Constants::OP_QUERY)
82
+
83
+ packed_message = message.append!(last_error_message).to_s
84
+ sock = nil
85
+ begin
86
+ sock = checkout_writer
87
+ send_message_on_socket(packed_message, sock)
88
+ docs, num_received, cursor_id = receive(sock, last_error_id)
89
+ checkin(sock)
90
+ rescue ConnectionFailure, OperationFailure, OperationTimeout => ex
91
+ checkin(sock)
92
+ raise ex
93
+ rescue SystemStackError, NoMemoryError, SystemCallError => ex
94
+ close
95
+ raise ex
96
+ end
97
+
98
+ if num_received == 1
99
+ error = docs[0]['err'] || docs[0]['errmsg']
100
+ if error && error.include?("not master")
101
+ close
102
+ raise ConnectionFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
103
+ elsif (note = docs[0]['jnote'] || docs[0]['wnote']) # assignment
104
+ code = docs[0]['code'] || Mongo::ErrorCode::BAD_VALUE # as of server version 2.5.5
105
+ raise WriteConcernError.new(code.to_s + ': ' + note, code, docs[0])
106
+ elsif error
107
+ code = docs[0]['code'] || Mongo::ErrorCode::UNKNOWN_ERROR
108
+ error = "wtimeout" if error == "timeout"
109
+ raise WriteConcernError.new(code.to_s + ': ' + error, code, docs[0]) if error == "wtimeout"
110
+ raise OperationFailure.new(code.to_s + ': ' + error, code, docs[0])
111
+ end
112
+ end
113
+
114
+ docs[0]
115
+ end
116
+
117
+ # Sends a message to the database and waits for the response.
118
+ #
119
+ # @param [Integer] operation a MongoDB opcode.
120
+ # @param [BSON::ByteBuffer] message a message to send to the database.
121
+ # @param [String] log_message this is currently a no-op and will be removed.
122
+ # @param [Socket] socket a socket to use in lieu of checking out a new one.
123
+ # @param [Boolean] command (false) indicate whether this is a command. If this is a command,
124
+ # the message will be sent to the primary node.
125
+ # @param [Symbol] read the read preference.
126
+ # @param [Boolean] exhaust (false) indicate whether the cursor should be exhausted. Set
127
+ # this to true only when the OP_QUERY_EXHAUST flag is set.
128
+ # @param [Boolean] compile_regex whether BSON regex objects should be compiled into Ruby regexes.
129
+ #
130
+ # @return [Array]
131
+ # An array whose indexes include [0] documents returned, [1] number of document received,
132
+ # and [3] a cursor_id.
133
+ def receive_message(operation, message, log_message=nil, socket=nil, command=false,
134
+ read=:primary, exhaust=false, compile_regex=true)
135
+ request_id = add_message_headers(message, operation)
136
+ packed_message = message.to_s
137
+ opts = { :exhaust => exhaust,
138
+ :compile_regex => compile_regex }
139
+
140
+ result = ''
141
+
142
+ begin
143
+ send_message_on_socket(packed_message, socket)
144
+ result = receive(socket, request_id, opts)
145
+ rescue ConnectionFailure => ex
146
+ socket.close
147
+ checkin(socket)
148
+ raise ex
149
+ rescue SystemStackError, NoMemoryError, SystemCallError => ex
150
+ close
151
+ raise ex
152
+ rescue Exception => ex
153
+ if defined?(IRB)
154
+ close if ex.class == IRB::Abort
155
+ end
156
+ raise ex
157
+ end
158
+ result
159
+ end
160
+
161
+ private
162
+
163
+ def receive(sock, cursor_id, opts={})
164
+ exhaust = !!opts.delete(:exhaust)
165
+
166
+ if exhaust
167
+ docs = []
168
+ num_received = 0
169
+
170
+ while(cursor_id != 0) do
171
+ receive_header(sock, cursor_id, exhaust)
172
+ number_received, cursor_id = receive_response_header(sock)
173
+ new_docs, n = read_documents(number_received, sock, opts)
174
+ docs += new_docs
175
+ num_received += n
176
+ end
177
+
178
+ return [docs, num_received, cursor_id]
179
+ else
180
+ receive_header(sock, cursor_id, exhaust)
181
+ number_received, cursor_id = receive_response_header(sock)
182
+ docs, num_received = read_documents(number_received, sock, opts)
183
+
184
+ return [docs, num_received, cursor_id]
185
+ end
186
+ end
187
+
188
+ def receive_header(sock, expected_response, exhaust=false)
189
+ header = receive_message_on_socket(16, sock)
190
+
191
+ # unpacks to size, request_id, response_to
192
+ response_to = header.unpack('VVV')[2]
193
+ if !exhaust && expected_response != response_to
194
+ raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
195
+ end
196
+
197
+ unless header.size == STANDARD_HEADER_SIZE
198
+ raise "Short read for DB response header: " +
199
+ "expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
200
+ end
201
+ nil
202
+ end
203
+
204
+ def receive_response_header(sock)
205
+ header_buf = receive_message_on_socket(RESPONSE_HEADER_SIZE, sock)
206
+ if header_buf.length != RESPONSE_HEADER_SIZE
207
+ raise "Short read for DB response header; " +
208
+ "expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
209
+ end
210
+
211
+ # unpacks to flags, cursor_id_a, cursor_id_b, starting_from, number_remaining
212
+ flags, cursor_id_a, cursor_id_b, _, number_remaining = header_buf.unpack('VVVVV')
213
+
214
+ check_response_flags(flags)
215
+ cursor_id = (cursor_id_b << 32) + cursor_id_a
216
+ [number_remaining, cursor_id]
217
+ end
218
+
219
+ def check_response_flags(flags)
220
+ if flags & Mongo::Constants::REPLY_CURSOR_NOT_FOUND != 0
221
+ raise Mongo::OperationFailure, "Query response returned CURSOR_NOT_FOUND. " +
222
+ "Either an invalid cursor was specified, or the cursor may have timed out on the server."
223
+ elsif flags & Mongo::Constants::REPLY_QUERY_FAILURE != 0
224
+ # Mongo query reply failures are handled in Cursor#next.
225
+ end
226
+ end
227
+
228
+ def read_documents(number_received, sock, opts)
229
+ docs = []
230
+ number_remaining = number_received
231
+ while number_remaining > 0 do
232
+ buf = receive_message_on_socket(4, sock)
233
+ size = buf.unpack('V')[0]
234
+ buf << receive_message_on_socket(size - 4, sock)
235
+ number_remaining -= 1
236
+ docs << BSON::BSON_CODER.deserialize(buf, opts)
237
+ end
238
+ [docs, number_received]
239
+ end
240
+
241
+ def build_command_message(db_name, query, projection=nil, skip=0, limit=-1)
242
+ message = BSON::ByteBuffer.new("", max_message_size)
243
+ message.put_int(0)
244
+ BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
245
+ message.put_int(skip)
246
+ message.put_int(limit)
247
+ message.put_binary(BSON::BSON_CODER.serialize(query, false, false, max_bson_size).to_s)
248
+ message.put_binary(BSON::BSON_CODER.serialize(projection, false, false, max_bson_size).to_s) if projection
249
+ message
250
+ end
251
+
252
+ # Constructs a getlasterror message. This method is used exclusively by
253
+ # MongoClient#send_message_with_gle.
254
+ def build_get_last_error_message(db_name, write_concern)
255
+ gle = BSON::OrderedHash.new
256
+ gle[:getlasterror] = 1
257
+ if write_concern.is_a?(Hash)
258
+ write_concern.assert_valid_keys(:w, :wtimeout, :fsync, :j)
259
+ gle.merge!(write_concern)
260
+ gle.delete(:w) if gle[:w] == 1
261
+ end
262
+ gle[:w] = gle[:w].to_s if gle[:w].is_a?(Symbol)
263
+ build_command_message(db_name, gle)
264
+ end
265
+
266
+ # Prepares a message for transmission to MongoDB by
267
+ # constructing a valid message header.
268
+ #
269
+ # Note: this method modifies message by reference.
270
+ #
271
+ # @return [Integer] the request id used in the header
272
+ def add_message_headers(message, operation)
273
+ headers = [
274
+ # Message size.
275
+ 16 + message.size,
276
+
277
+ # Unique request id.
278
+ request_id = get_request_id,
279
+
280
+ # Response id.
281
+ 0,
282
+
283
+ # Opcode.
284
+ operation
285
+ ].pack('VVVV')
286
+
287
+ message.prepend!(headers)
288
+
289
+ request_id
290
+ end
291
+
292
+ # Increment and return the next available request id.
293
+ #
294
+ # return [Integer]
295
+ def get_request_id
296
+ request_id = ''
297
+ @id_lock.synchronize do
298
+ request_id = @@current_request_id += 1
299
+ end
300
+ request_id
301
+ end
302
+
303
+ # Low-level method for sending a message on a socket.
304
+ # Requires a packed message and an available socket,
305
+ #
306
+ # @return [Integer] number of bytes sent
307
+ def send_message_on_socket(packed_message, socket)
308
+ begin
309
+ total_bytes_sent = socket.send(packed_message)
310
+ if total_bytes_sent != packed_message.size
311
+ packed_message.slice!(0, total_bytes_sent)
312
+ while packed_message.size > 0
313
+ byte_sent = socket.send(packed_message)
314
+ total_bytes_sent += byte_sent
315
+ packed_message.slice!(0, byte_sent)
316
+ end
317
+ end
318
+ total_bytes_sent
319
+ rescue => ex
320
+ socket.close
321
+ raise ConnectionFailure, "Operation failed with the following exception: #{ex}:#{ex.message}"
322
+ end
323
+ end
324
+
325
+ # Low-level method for receiving data from socket.
326
+ # Requires length and an available socket.
327
+ def receive_message_on_socket(length, socket)
328
+ begin
329
+ message = receive_data(length, socket)
330
+ rescue OperationTimeout, ConnectionFailure => ex
331
+ socket.close
332
+
333
+ if ex.class == OperationTimeout
334
+ raise OperationTimeout, "Timed out waiting on socket read."
335
+ else
336
+ raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
337
+ end
338
+ end
339
+ message
340
+ end
341
+
342
+ def receive_data(length, socket)
343
+ message = new_binary_string
344
+ socket.read(length, message)
345
+
346
+ raise ConnectionFailure, "connection closed" unless message && message.length > 0
347
+ if message.length < length
348
+ chunk = new_binary_string
349
+ while message.length < length
350
+ socket.read(length - message.length, chunk)
351
+ raise ConnectionFailure, "connection closed" unless chunk.length > 0
352
+ message << chunk
353
+ end
354
+ end
355
+ message
356
+ end
357
+
358
+ if defined?(Encoding)
359
+ BINARY_ENCODING = Encoding.find("binary")
360
+
361
+ def new_binary_string
362
+ "".force_encoding(BINARY_ENCODING)
363
+ end
364
+ else
365
+ def new_binary_string
366
+ ""
367
+ end
368
+ end
369
+ end
370
+ end