mongo 1.10.0-java

Sign up to get free protection for your applications and to get access to all the features.
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