mongo 1.3.0 → 1.12.5
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +122 -271
- data/Rakefile +25 -209
- data/VERSION +1 -0
- data/bin/mongo_console +31 -9
- data/lib/mongo/bulk_write_collection_view.rb +387 -0
- data/lib/mongo/collection.rb +576 -269
- data/lib/mongo/collection_writer.rb +364 -0
- data/lib/mongo/connection/node.rb +249 -0
- data/lib/mongo/connection/pool.rb +340 -0
- data/lib/mongo/connection/pool_manager.rb +320 -0
- data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
- data/lib/mongo/connection/socket/socket_util.rb +37 -0
- data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
- data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
- data/lib/mongo/connection/socket/unix_socket.rb +39 -0
- data/lib/mongo/connection/socket.rb +18 -0
- data/lib/mongo/connection.rb +7 -875
- data/lib/mongo/cursor.rb +403 -117
- data/lib/mongo/db.rb +444 -243
- data/lib/mongo/exception.rb +145 -0
- data/lib/mongo/functional/authentication.rb +455 -0
- data/lib/mongo/functional/logging.rb +85 -0
- data/lib/mongo/functional/read_preference.rb +183 -0
- data/lib/mongo/functional/scram.rb +556 -0
- data/lib/mongo/functional/uri_parser.rb +409 -0
- data/lib/mongo/functional/write_concern.rb +66 -0
- data/lib/mongo/functional.rb +20 -0
- data/lib/mongo/gridfs/grid.rb +30 -24
- data/lib/mongo/gridfs/grid_ext.rb +6 -10
- data/lib/mongo/gridfs/grid_file_system.rb +38 -20
- data/lib/mongo/gridfs/grid_io.rb +84 -75
- data/lib/mongo/gridfs.rb +18 -0
- data/lib/mongo/legacy.rb +140 -0
- data/lib/mongo/mongo_client.rb +697 -0
- data/lib/mongo/mongo_replica_set_client.rb +535 -0
- data/lib/mongo/mongo_sharded_client.rb +159 -0
- data/lib/mongo/networking.rb +372 -0
- data/lib/mongo/{util → utils}/conversions.rb +29 -8
- data/lib/mongo/{util → utils}/core_ext.rb +28 -18
- data/lib/mongo/{util → utils}/server_version.rb +4 -6
- data/lib/mongo/{util → utils}/support.rb +29 -31
- data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
- data/lib/mongo/utils.rb +19 -0
- data/lib/mongo.rb +51 -50
- data/mongo.gemspec +29 -32
- data/test/functional/authentication_test.rb +39 -0
- data/test/functional/bulk_api_stress_test.rb +133 -0
- data/test/functional/bulk_write_collection_view_test.rb +1198 -0
- data/test/functional/client_test.rb +627 -0
- data/test/functional/collection_test.rb +2175 -0
- data/test/functional/collection_writer_test.rb +83 -0
- data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
- data/test/functional/cursor_fail_test.rb +57 -0
- data/test/functional/cursor_message_test.rb +56 -0
- data/test/functional/cursor_test.rb +683 -0
- data/test/functional/db_api_test.rb +835 -0
- data/test/functional/db_connection_test.rb +25 -0
- data/test/functional/db_test.rb +348 -0
- data/test/functional/grid_file_system_test.rb +285 -0
- data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
- data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
- data/test/functional/pool_test.rb +136 -0
- data/test/functional/safe_test.rb +98 -0
- data/test/functional/ssl_test.rb +29 -0
- data/test/functional/support_test.rb +62 -0
- data/test/functional/timeout_test.rb +60 -0
- data/test/functional/uri_test.rb +446 -0
- data/test/functional/write_concern_test.rb +118 -0
- data/test/helpers/general.rb +50 -0
- data/test/helpers/test_unit.rb +476 -0
- data/test/replica_set/authentication_test.rb +37 -0
- data/test/replica_set/basic_test.rb +189 -0
- data/test/replica_set/client_test.rb +393 -0
- data/test/replica_set/connection_test.rb +138 -0
- data/test/replica_set/count_test.rb +66 -0
- data/test/replica_set/cursor_test.rb +220 -0
- data/test/replica_set/insert_test.rb +157 -0
- data/test/replica_set/max_values_test.rb +151 -0
- data/test/replica_set/pinning_test.rb +105 -0
- data/test/replica_set/query_test.rb +73 -0
- data/test/replica_set/read_preference_test.rb +219 -0
- data/test/replica_set/refresh_test.rb +211 -0
- data/test/replica_set/replication_ack_test.rb +95 -0
- data/test/replica_set/ssl_test.rb +32 -0
- data/test/sharded_cluster/basic_test.rb +203 -0
- data/test/shared/authentication/basic_auth_shared.rb +260 -0
- data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
- data/test/shared/authentication/gssapi_shared.rb +176 -0
- data/test/shared/authentication/sasl_plain_shared.rb +96 -0
- data/test/shared/authentication/scram_shared.rb +92 -0
- data/test/shared/ssl_shared.rb +235 -0
- data/test/test_helper.rb +53 -94
- data/test/threading/basic_test.rb +120 -0
- data/test/tools/mongo_config.rb +708 -0
- data/test/tools/mongo_config_test.rb +160 -0
- data/test/unit/client_test.rb +381 -0
- data/test/unit/collection_test.rb +89 -53
- data/test/unit/connection_test.rb +282 -32
- data/test/unit/cursor_test.rb +206 -8
- data/test/unit/db_test.rb +55 -13
- data/test/unit/grid_test.rb +43 -16
- data/test/unit/mongo_sharded_client_test.rb +48 -0
- data/test/unit/node_test.rb +93 -0
- data/test/unit/pool_manager_test.rb +111 -0
- data/test/unit/read_pref_test.rb +406 -0
- data/test/unit/read_test.rb +159 -0
- data/test/unit/safe_test.rb +69 -36
- data/test/unit/sharding_pool_manager_test.rb +84 -0
- data/test/unit/write_concern_test.rb +175 -0
- data.tar.gz.sig +3 -0
- metadata +227 -216
- metadata.gz.sig +0 -0
- data/docs/CREDITS.md +0 -123
- data/docs/FAQ.md +0 -116
- data/docs/GridFS.md +0 -158
- data/docs/HISTORY.md +0 -244
- data/docs/RELEASES.md +0 -33
- data/docs/REPLICA_SETS.md +0 -72
- data/docs/TUTORIAL.md +0 -247
- data/docs/WRITE_CONCERN.md +0 -28
- data/lib/mongo/exceptions.rb +0 -71
- data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
- data/lib/mongo/repl_set_connection.rb +0 -342
- data/lib/mongo/test.rb +0 -20
- data/lib/mongo/util/pool.rb +0 -177
- data/lib/mongo/util/uri_parser.rb +0 -185
- data/test/async/collection_test.rb +0 -224
- data/test/async/connection_test.rb +0 -24
- data/test/async/cursor_test.rb +0 -162
- data/test/async/worker_pool_test.rb +0 -99
- data/test/auxillary/1.4_features.rb +0 -166
- data/test/auxillary/authentication_test.rb +0 -68
- data/test/auxillary/autoreconnect_test.rb +0 -41
- data/test/auxillary/fork_test.rb +0 -30
- data/test/auxillary/repl_set_auth_test.rb +0 -58
- data/test/auxillary/slave_connection_test.rb +0 -36
- data/test/auxillary/threaded_authentication_test.rb +0 -101
- data/test/bson/binary_test.rb +0 -15
- data/test/bson/bson_test.rb +0 -649
- data/test/bson/byte_buffer_test.rb +0 -208
- data/test/bson/hash_with_indifferent_access_test.rb +0 -38
- data/test/bson/json_test.rb +0 -17
- data/test/bson/object_id_test.rb +0 -154
- data/test/bson/ordered_hash_test.rb +0 -204
- data/test/bson/timestamp_test.rb +0 -24
- data/test/collection_test.rb +0 -910
- data/test/connection_test.rb +0 -309
- data/test/cursor_fail_test.rb +0 -75
- data/test/cursor_message_test.rb +0 -43
- data/test/cursor_test.rb +0 -483
- data/test/db_api_test.rb +0 -726
- data/test/db_connection_test.rb +0 -15
- data/test/db_test.rb +0 -287
- data/test/grid_file_system_test.rb +0 -243
- data/test/load/resque/load.rb +0 -21
- data/test/load/resque/processor.rb +0 -26
- data/test/load/thin/load.rb +0 -24
- data/test/load/unicorn/load.rb +0 -23
- data/test/load/unicorn/unicorn.rb +0 -29
- data/test/replica_sets/connect_test.rb +0 -94
- data/test/replica_sets/connection_string_test.rb +0 -32
- data/test/replica_sets/count_test.rb +0 -35
- data/test/replica_sets/insert_test.rb +0 -53
- data/test/replica_sets/pooled_insert_test.rb +0 -55
- data/test/replica_sets/query_secondaries.rb +0 -96
- data/test/replica_sets/query_test.rb +0 -51
- data/test/replica_sets/replication_ack_test.rb +0 -66
- data/test/replica_sets/rs_test_helper.rb +0 -27
- data/test/safe_test.rb +0 -68
- data/test/support/hash_with_indifferent_access.rb +0 -186
- data/test/support/keys.rb +0 -45
- data/test/support_test.rb +0 -18
- data/test/threading/threading_with_large_pool_test.rb +0 -90
- data/test/threading_test.rb +0 -87
- data/test/tools/auth_repl_set_manager.rb +0 -14
- data/test/tools/load.rb +0 -58
- data/test/tools/repl_set_manager.rb +0 -266
- data/test/tools/sharding_manager.rb +0 -202
- data/test/tools/test.rb +0 -4
- data/test/unit/pool_test.rb +0 -9
- data/test/unit/repl_set_connection_test.rb +0 -59
- data/test/uri_test.rb +0 -91
@@ -0,0 +1,340 @@
|
|
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
|
+
class Pool
|
17
|
+
PING_ATTEMPTS = 6
|
18
|
+
MAX_PING_TIME = 1_000_000
|
19
|
+
PRUNE_INTERVAL = 10_000
|
20
|
+
|
21
|
+
attr_accessor :host,
|
22
|
+
:port,
|
23
|
+
:address,
|
24
|
+
:size,
|
25
|
+
:timeout,
|
26
|
+
:checked_out,
|
27
|
+
:client,
|
28
|
+
:node
|
29
|
+
|
30
|
+
# Create a new pool of connections.
|
31
|
+
def initialize(client, host, port, opts={})
|
32
|
+
@client = client
|
33
|
+
|
34
|
+
@host, @port = host, port
|
35
|
+
|
36
|
+
# A Mongo::Node object.
|
37
|
+
@node = opts[:node]
|
38
|
+
|
39
|
+
# The string address
|
40
|
+
@address = "#{@host}:#{@port}"
|
41
|
+
|
42
|
+
# Pool size and timeout.
|
43
|
+
@size = opts.fetch(:size, 20)
|
44
|
+
@timeout = opts.fetch(:timeout, 30)
|
45
|
+
|
46
|
+
# Mutex for synchronizing pool access
|
47
|
+
@connection_mutex = Mutex.new
|
48
|
+
|
49
|
+
# Mutex for synchronizing pings
|
50
|
+
@ping_mutex = Mutex.new
|
51
|
+
|
52
|
+
# Condition variable for signal and wait
|
53
|
+
@queue = ConditionVariable.new
|
54
|
+
|
55
|
+
@sockets = []
|
56
|
+
@checked_out = []
|
57
|
+
@ping_time = nil
|
58
|
+
@last_ping = nil
|
59
|
+
@closed = false
|
60
|
+
@thread_ids_to_sockets = {}
|
61
|
+
@checkout_counter = 0
|
62
|
+
end
|
63
|
+
|
64
|
+
# Close this pool.
|
65
|
+
#
|
66
|
+
# @option opts [Boolean]:soft (false) If true,
|
67
|
+
# close only those sockets that are not checked out.
|
68
|
+
def close(opts={})
|
69
|
+
@connection_mutex.synchronize do
|
70
|
+
if opts[:soft] && !@checked_out.empty?
|
71
|
+
@closing = true
|
72
|
+
close_sockets(@sockets - @checked_out)
|
73
|
+
else
|
74
|
+
close_sockets(@sockets)
|
75
|
+
@closed = true
|
76
|
+
end
|
77
|
+
@node.close if @node
|
78
|
+
end
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def tags
|
83
|
+
@node.tags
|
84
|
+
end
|
85
|
+
|
86
|
+
def healthy?
|
87
|
+
close if @sockets.all?(&:closed?)
|
88
|
+
!closed? && node.healthy?
|
89
|
+
end
|
90
|
+
|
91
|
+
def closed?
|
92
|
+
@closed
|
93
|
+
end
|
94
|
+
|
95
|
+
def up?
|
96
|
+
!@closed
|
97
|
+
end
|
98
|
+
|
99
|
+
def inspect
|
100
|
+
"#<Mongo::Pool:0x#{self.object_id.to_s(16)} @host=#{@host} @port=#{port} " +
|
101
|
+
"@ping_time=#{@ping_time} #{@checked_out.size}/#{@size} sockets available " +
|
102
|
+
"up=#{!closed?}>"
|
103
|
+
end
|
104
|
+
|
105
|
+
def host_string
|
106
|
+
"#{@host}:#{@port}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def host_port
|
110
|
+
[@host, @port]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Refresh ping time only if we haven't
|
114
|
+
# checked within the last five minutes.
|
115
|
+
def ping_time
|
116
|
+
@ping_mutex.synchronize do
|
117
|
+
if !@last_ping || (Time.now - @last_ping) > 300
|
118
|
+
@ping_time = refresh_ping_time
|
119
|
+
@last_ping = Time.now
|
120
|
+
end
|
121
|
+
end
|
122
|
+
@ping_time
|
123
|
+
end
|
124
|
+
|
125
|
+
# Return the time it takes on average
|
126
|
+
# to do a round-trip against this node.
|
127
|
+
def refresh_ping_time
|
128
|
+
trials = []
|
129
|
+
PING_ATTEMPTS.times do
|
130
|
+
t1 = Time.now
|
131
|
+
if !self.ping
|
132
|
+
return MAX_PING_TIME
|
133
|
+
end
|
134
|
+
trials << (Time.now - t1) * 1000
|
135
|
+
end
|
136
|
+
|
137
|
+
trials.sort!
|
138
|
+
|
139
|
+
# Delete shortest and longest times
|
140
|
+
trials.delete_at(trials.length-1)
|
141
|
+
trials.delete_at(0)
|
142
|
+
|
143
|
+
total = 0.0
|
144
|
+
trials.each { |t| total += t }
|
145
|
+
|
146
|
+
(total / trials.length).ceil
|
147
|
+
end
|
148
|
+
|
149
|
+
def ping
|
150
|
+
begin
|
151
|
+
return self.client['admin'].command({:ping => 1}, :socket => @node.socket,
|
152
|
+
:timeout => client.op_timeout || MongoClient::DEFAULT_OP_TIMEOUT)
|
153
|
+
rescue ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Return a socket to the pool.
|
159
|
+
def checkin(socket)
|
160
|
+
@connection_mutex.synchronize do
|
161
|
+
if @checked_out.delete(socket)
|
162
|
+
@queue.broadcast
|
163
|
+
else
|
164
|
+
return false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
true
|
168
|
+
end
|
169
|
+
|
170
|
+
# Adds a new socket to the pool and checks it out.
|
171
|
+
#
|
172
|
+
# This method is called exclusively from #checkout;
|
173
|
+
# therefore, it runs within a mutex.
|
174
|
+
def checkout_new_socket
|
175
|
+
begin
|
176
|
+
socket = @client.socket_class.new(@host, @port, @client.op_timeout,
|
177
|
+
@client.connect_timeout,
|
178
|
+
@client.socket_opts)
|
179
|
+
socket.pool = self
|
180
|
+
rescue => ex
|
181
|
+
socket.close if socket
|
182
|
+
@node.close if @node
|
183
|
+
raise ConnectionFailure, "Failed to connect to host #{@host} and port #{@port}: #{ex}"
|
184
|
+
end
|
185
|
+
|
186
|
+
@sockets << socket
|
187
|
+
@checked_out << socket
|
188
|
+
@thread_ids_to_sockets[Thread.current.object_id] = socket
|
189
|
+
socket
|
190
|
+
end
|
191
|
+
|
192
|
+
# If a user calls DB#authenticate, and several sockets exist,
|
193
|
+
# then we need a way to apply the authentication on each socket.
|
194
|
+
# So we store the apply_authentication method, and this will be
|
195
|
+
# applied right before the next use of each socket.
|
196
|
+
#
|
197
|
+
# @deprecated This method has been replaced by Pool#check_auths (private)
|
198
|
+
# and it isn't necessary to ever invoke this method directly.
|
199
|
+
# Authentication of sockets is handled upon checkout and checkin.
|
200
|
+
def authenticate_existing
|
201
|
+
end
|
202
|
+
|
203
|
+
# Store the logout op for each existing socket to be applied before
|
204
|
+
# the next use of each socket.
|
205
|
+
#
|
206
|
+
# @deprecated This method has been replaced by Pool#check_auths (private)
|
207
|
+
# and it isn't necessary to ever invoke this method directly.
|
208
|
+
# Authentication of sockets is handled upon checkout and checkin.
|
209
|
+
def logout_existing(database)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Checks out the first available socket from the pool.
|
213
|
+
#
|
214
|
+
# If the pid has changed, remove the socket and check out
|
215
|
+
# new one.
|
216
|
+
#
|
217
|
+
# This method is called exclusively from #checkout;
|
218
|
+
# therefore, it runs within a mutex.
|
219
|
+
def checkout_existing_socket(socket=nil)
|
220
|
+
if !socket
|
221
|
+
available = @sockets - @checked_out
|
222
|
+
socket = available[rand(available.length)]
|
223
|
+
end
|
224
|
+
|
225
|
+
if socket.pid != Process.pid
|
226
|
+
@sockets.delete(socket)
|
227
|
+
if socket
|
228
|
+
socket.close unless socket.closed?
|
229
|
+
end
|
230
|
+
checkout_new_socket
|
231
|
+
else
|
232
|
+
@checked_out << socket
|
233
|
+
@thread_ids_to_sockets[Thread.current.object_id] = socket
|
234
|
+
socket
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def prune_threads
|
239
|
+
live_threads = Thread.list.map(&:object_id)
|
240
|
+
@thread_ids_to_sockets.reject! do |key, value|
|
241
|
+
!live_threads.include?(key)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def check_prune
|
246
|
+
if @checkout_counter > PRUNE_INTERVAL
|
247
|
+
@checkout_counter = 0
|
248
|
+
prune_threads
|
249
|
+
else
|
250
|
+
@checkout_counter += 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Check out an existing socket or create a new socket if the maximum
|
255
|
+
# pool size has not been exceeded. Otherwise, wait for the next
|
256
|
+
# available socket.
|
257
|
+
def checkout
|
258
|
+
@client.connect if !@client.connected?
|
259
|
+
start_time = Time.now
|
260
|
+
loop do
|
261
|
+
if (Time.now - start_time) > @timeout
|
262
|
+
raise ConnectionTimeoutError, "could not obtain connection within " +
|
263
|
+
"#{@timeout} seconds. The max pool size is currently #{@size}; " +
|
264
|
+
"consider increasing the pool size or timeout."
|
265
|
+
end
|
266
|
+
|
267
|
+
@connection_mutex.synchronize do
|
268
|
+
check_prune
|
269
|
+
socket = nil
|
270
|
+
if socket_for_thread = @thread_ids_to_sockets[Thread.current.object_id]
|
271
|
+
if !@checked_out.include?(socket_for_thread)
|
272
|
+
socket = checkout_existing_socket(socket_for_thread)
|
273
|
+
end
|
274
|
+
else
|
275
|
+
if @sockets.size < @size
|
276
|
+
socket = checkout_new_socket
|
277
|
+
elsif @checked_out.size < @sockets.size
|
278
|
+
socket = checkout_existing_socket
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
if socket
|
283
|
+
if !socket.closed?
|
284
|
+
begin
|
285
|
+
check_auths(socket)
|
286
|
+
return socket
|
287
|
+
rescue ConnectionFailure
|
288
|
+
# Socket failed authentication and will be cleaned up below
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Socket was closed from earlier network error, or just now from
|
293
|
+
# a network error when authenticating.
|
294
|
+
@checked_out.delete(socket)
|
295
|
+
@sockets.delete(socket)
|
296
|
+
@thread_ids_to_sockets.delete(Thread.current.object_id)
|
297
|
+
else
|
298
|
+
# Otherwise, wait
|
299
|
+
@queue.wait(@connection_mutex)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
# Helper method to handle keeping track of auths/logouts for sockets.
|
308
|
+
#
|
309
|
+
# @param socket [Socket] The socket instance to be checked.
|
310
|
+
#
|
311
|
+
# @return [Socket] The authenticated socket instance.
|
312
|
+
def check_auths(socket)
|
313
|
+
# find and handle logouts
|
314
|
+
(socket.auths - @client.auths).each do |auth|
|
315
|
+
@client.issue_logout(auth[:source], :socket => socket)
|
316
|
+
socket.auths.delete(auth)
|
317
|
+
end
|
318
|
+
|
319
|
+
# find and handle new auths
|
320
|
+
(@client.auths - socket.auths).each do |auth|
|
321
|
+
@client.issue_authentication(auth, :socket => socket)
|
322
|
+
socket.auths.add(auth)
|
323
|
+
end
|
324
|
+
|
325
|
+
socket
|
326
|
+
end
|
327
|
+
|
328
|
+
def close_sockets(sockets)
|
329
|
+
sockets.each do |socket|
|
330
|
+
@sockets.delete(socket)
|
331
|
+
begin
|
332
|
+
socket.close unless socket.closed?
|
333
|
+
rescue IOError => ex
|
334
|
+
warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,320 @@
|
|
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
|
+
class PoolManager
|
17
|
+
include ThreadLocalVariableManager
|
18
|
+
|
19
|
+
attr_reader :client,
|
20
|
+
:hosts,
|
21
|
+
:pools,
|
22
|
+
:secondaries,
|
23
|
+
:secondary_pools,
|
24
|
+
:arbiters,
|
25
|
+
:primary,
|
26
|
+
:primary_pool,
|
27
|
+
:seeds,
|
28
|
+
:max_bson_size,
|
29
|
+
:max_message_size,
|
30
|
+
:max_wire_version,
|
31
|
+
:min_wire_version
|
32
|
+
|
33
|
+
# Create a new set of connection pools.
|
34
|
+
#
|
35
|
+
# The pool manager will by default use the original seed list passed
|
36
|
+
# to the connection objects, accessible via connection.seeds. In addition,
|
37
|
+
# the user may pass an additional list of seeds nodes discovered in real
|
38
|
+
# time. The union of these lists will be used when attempting to connect,
|
39
|
+
# with the newly-discovered nodes being used first.
|
40
|
+
def initialize(client, seeds=[])
|
41
|
+
@client = client
|
42
|
+
@seeds = seeds
|
43
|
+
|
44
|
+
initialize_immutable_state
|
45
|
+
initialize_mutable_state
|
46
|
+
|
47
|
+
@pools = Set.new
|
48
|
+
@primary = nil
|
49
|
+
@primary_pool = nil
|
50
|
+
@members = Set.new
|
51
|
+
@refresh_required = false
|
52
|
+
@max_bson_size = DEFAULT_MAX_BSON_SIZE
|
53
|
+
@max_message_size = @max_bson_size * MESSAGE_SIZE_FACTOR
|
54
|
+
@max_wire_version = 0
|
55
|
+
@min_wire_version = 0
|
56
|
+
@connect_mutex = Mutex.new
|
57
|
+
thread_local[:locks][:connecting_manager] = false
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"<Mongo::PoolManager:0x#{self.object_id.to_s(16)} @seeds=#{@seeds}>"
|
62
|
+
end
|
63
|
+
|
64
|
+
def connect
|
65
|
+
@connect_mutex.synchronize do
|
66
|
+
begin
|
67
|
+
thread_local[:locks][:connecting_manager] = true
|
68
|
+
@refresh_required = false
|
69
|
+
disconnect_old_members
|
70
|
+
connect_to_members
|
71
|
+
initialize_pools(@members)
|
72
|
+
update_max_sizes
|
73
|
+
@seeds = discovered_seeds
|
74
|
+
ensure
|
75
|
+
thread_local[:locks][:connecting_manager] = false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
clone_state
|
79
|
+
end
|
80
|
+
|
81
|
+
def refresh!(additional_seeds)
|
82
|
+
@seeds |= additional_seeds
|
83
|
+
connect
|
84
|
+
end
|
85
|
+
|
86
|
+
# We're healthy if all members are pingable and if the view
|
87
|
+
# of the replica set returned by isMaster is equivalent
|
88
|
+
# to our view. If any of these isn't the case,
|
89
|
+
# set @refresh_required to true, and return.
|
90
|
+
def check_connection_health
|
91
|
+
return if thread_local[:locks][:connecting_manager]
|
92
|
+
members = copy_members
|
93
|
+
begin
|
94
|
+
seed = get_valid_seed_node
|
95
|
+
rescue ConnectionFailure
|
96
|
+
@refresh_required = true
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
unless current_config = seed.config
|
101
|
+
@refresh_required = true
|
102
|
+
seed.close
|
103
|
+
return
|
104
|
+
end
|
105
|
+
|
106
|
+
if current_config['hosts'].length != members.length
|
107
|
+
@refresh_required = true
|
108
|
+
seed.close
|
109
|
+
return
|
110
|
+
end
|
111
|
+
|
112
|
+
current_config['hosts'].each do |host|
|
113
|
+
member = members.detect do |m|
|
114
|
+
m.address == host
|
115
|
+
end
|
116
|
+
|
117
|
+
if member && validate_existing_member(current_config, member)
|
118
|
+
next
|
119
|
+
else
|
120
|
+
@refresh_required = true
|
121
|
+
seed.close
|
122
|
+
return
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
seed.close
|
127
|
+
end
|
128
|
+
|
129
|
+
# The replica set connection should initiate a full refresh.
|
130
|
+
def refresh_required?
|
131
|
+
@refresh_required
|
132
|
+
end
|
133
|
+
|
134
|
+
def closed?
|
135
|
+
pools.all? { |pool| pool.closed? }
|
136
|
+
end
|
137
|
+
|
138
|
+
def close(opts={})
|
139
|
+
begin
|
140
|
+
pools.each { |pool| pool.close(opts) }
|
141
|
+
rescue ConnectionFailure
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def read
|
146
|
+
read_pool.host_port
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def update_max_sizes
|
152
|
+
unless @members.size == 0
|
153
|
+
@max_bson_size = @members.map(&:max_bson_size).min
|
154
|
+
@max_message_size = @members.map(&:max_message_size).min
|
155
|
+
@max_wire_version = @members.map(&:max_wire_version).min
|
156
|
+
@min_wire_version = @members.map(&:min_wire_version).max
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def validate_existing_member(current_config, member)
|
161
|
+
if current_config['ismaster'] && member.last_state != :primary
|
162
|
+
return false
|
163
|
+
elsif member.last_state != :other
|
164
|
+
return false
|
165
|
+
end
|
166
|
+
return true
|
167
|
+
end
|
168
|
+
|
169
|
+
# For any existing members, close and remove any that are unhealthy or already closed.
|
170
|
+
def disconnect_old_members
|
171
|
+
@pools_mutable.reject! {|pool| !pool.healthy? }
|
172
|
+
@members.reject! {|node| !node.healthy? }
|
173
|
+
end
|
174
|
+
|
175
|
+
# Connect to each member of the replica set
|
176
|
+
# as reported by the given seed node.
|
177
|
+
def connect_to_members
|
178
|
+
seed = get_valid_seed_node
|
179
|
+
seed.node_list.each do |host|
|
180
|
+
if existing = @members.detect {|node| node =~ host }
|
181
|
+
if existing.healthy?
|
182
|
+
# Refresh this node's configuration
|
183
|
+
existing.set_config
|
184
|
+
# If we are unhealthy after refreshing our config, drop from the set.
|
185
|
+
if !existing.healthy?
|
186
|
+
@members.delete(existing)
|
187
|
+
else
|
188
|
+
next
|
189
|
+
end
|
190
|
+
else
|
191
|
+
existing.close
|
192
|
+
@members.delete(existing)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
node = Mongo::Node.new(self.client, host)
|
197
|
+
node.connect
|
198
|
+
@members << node if node.healthy?
|
199
|
+
end
|
200
|
+
seed.close
|
201
|
+
|
202
|
+
if @members.empty?
|
203
|
+
raise ConnectionFailure, "Failed to connect to any given member."
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Initialize the connection pools for the primary and secondary nodes.
|
208
|
+
def initialize_pools(members)
|
209
|
+
@primary_pool = nil
|
210
|
+
@primary = nil
|
211
|
+
@secondaries_mutable.clear
|
212
|
+
@secondary_pools_mutable.clear
|
213
|
+
@hosts_mutable.clear
|
214
|
+
|
215
|
+
members.each do |member|
|
216
|
+
member.last_state = nil
|
217
|
+
@hosts_mutable << member.host_string
|
218
|
+
if member.primary?
|
219
|
+
assign_primary(member)
|
220
|
+
elsif member.secondary?
|
221
|
+
# member could be not primary but secondary still is false
|
222
|
+
assign_secondary(member)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
@arbiters_mutable = members.first.arbiters
|
227
|
+
end
|
228
|
+
|
229
|
+
def assign_primary(member)
|
230
|
+
member.last_state = :primary
|
231
|
+
@primary = member.host_port
|
232
|
+
if existing = @pools_mutable.detect {|pool| pool.node == member }
|
233
|
+
@primary_pool = existing
|
234
|
+
else
|
235
|
+
@primary_pool = Pool.new(self.client, member.host, member.port,
|
236
|
+
:size => self.client.pool_size,
|
237
|
+
:timeout => self.client.pool_timeout,
|
238
|
+
:node => member
|
239
|
+
)
|
240
|
+
@pools_mutable << @primary_pool
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def assign_secondary(member)
|
245
|
+
member.last_state = :secondary
|
246
|
+
@secondaries_mutable << member.host_port
|
247
|
+
if existing = @pools_mutable.detect {|pool| pool.node == member }
|
248
|
+
@secondary_pools_mutable << existing
|
249
|
+
else
|
250
|
+
pool = Pool.new(self.client, member.host, member.port,
|
251
|
+
:size => self.client.pool_size,
|
252
|
+
:timeout => self.client.pool_timeout,
|
253
|
+
:node => member
|
254
|
+
)
|
255
|
+
@secondary_pools_mutable << pool
|
256
|
+
@pools_mutable << pool
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Iterate through the list of provided seed
|
261
|
+
# nodes until we've gotten a response from the
|
262
|
+
# replica set we're trying to connect to.
|
263
|
+
#
|
264
|
+
# If we don't get a response, raise an exception.
|
265
|
+
def get_valid_seed_node
|
266
|
+
@seeds.each do |seed|
|
267
|
+
node = Mongo::Node.new(self.client, seed)
|
268
|
+
node.connect
|
269
|
+
return node if node.healthy?
|
270
|
+
end
|
271
|
+
|
272
|
+
raise ConnectionFailure, "Cannot connect to a replica set using seeds " +
|
273
|
+
"#{@seeds.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
|
274
|
+
end
|
275
|
+
|
276
|
+
def discovered_seeds
|
277
|
+
@members.map(&:host_port)
|
278
|
+
end
|
279
|
+
|
280
|
+
def copy_members
|
281
|
+
members = Set.new
|
282
|
+
@connect_mutex.synchronize do
|
283
|
+
@members.map do |m|
|
284
|
+
members << m.dup
|
285
|
+
end
|
286
|
+
end
|
287
|
+
members
|
288
|
+
end
|
289
|
+
|
290
|
+
def initialize_immutable_state
|
291
|
+
@hosts = Set.new.freeze
|
292
|
+
@pools = Set.new.freeze
|
293
|
+
@secondaries = Set.new.freeze
|
294
|
+
@secondary_pools = [].freeze
|
295
|
+
@arbiters = [].freeze
|
296
|
+
end
|
297
|
+
|
298
|
+
def initialize_mutable_state
|
299
|
+
@hosts_mutable = Set.new
|
300
|
+
@pools_mutable = Set.new
|
301
|
+
@secondaries_mutable = Set.new
|
302
|
+
@secondary_pools_mutable = []
|
303
|
+
@arbiters_mutable = []
|
304
|
+
end
|
305
|
+
|
306
|
+
def clone_state
|
307
|
+
@hosts = @hosts_mutable.clone
|
308
|
+
@pools = @pools_mutable.clone
|
309
|
+
@secondaries = @secondaries_mutable.clone
|
310
|
+
@secondary_pools = @secondary_pools_mutable.clone
|
311
|
+
@arbiters = @arbiters_mutable.clone
|
312
|
+
|
313
|
+
@hosts.freeze
|
314
|
+
@pools.freeze
|
315
|
+
@secondaries.freeze
|
316
|
+
@secondary_pools.freeze
|
317
|
+
@arbiters.freeze
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|