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.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +122 -271
  5. data/Rakefile +25 -209
  6. data/VERSION +1 -0
  7. data/bin/mongo_console +31 -9
  8. data/lib/mongo/bulk_write_collection_view.rb +387 -0
  9. data/lib/mongo/collection.rb +576 -269
  10. data/lib/mongo/collection_writer.rb +364 -0
  11. data/lib/mongo/connection/node.rb +249 -0
  12. data/lib/mongo/connection/pool.rb +340 -0
  13. data/lib/mongo/connection/pool_manager.rb +320 -0
  14. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  15. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  16. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  17. data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
  18. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection.rb +7 -875
  21. data/lib/mongo/cursor.rb +403 -117
  22. data/lib/mongo/db.rb +444 -243
  23. data/lib/mongo/exception.rb +145 -0
  24. data/lib/mongo/functional/authentication.rb +455 -0
  25. data/lib/mongo/functional/logging.rb +85 -0
  26. data/lib/mongo/functional/read_preference.rb +183 -0
  27. data/lib/mongo/functional/scram.rb +556 -0
  28. data/lib/mongo/functional/uri_parser.rb +409 -0
  29. data/lib/mongo/functional/write_concern.rb +66 -0
  30. data/lib/mongo/functional.rb +20 -0
  31. data/lib/mongo/gridfs/grid.rb +30 -24
  32. data/lib/mongo/gridfs/grid_ext.rb +6 -10
  33. data/lib/mongo/gridfs/grid_file_system.rb +38 -20
  34. data/lib/mongo/gridfs/grid_io.rb +84 -75
  35. data/lib/mongo/gridfs.rb +18 -0
  36. data/lib/mongo/legacy.rb +140 -0
  37. data/lib/mongo/mongo_client.rb +697 -0
  38. data/lib/mongo/mongo_replica_set_client.rb +535 -0
  39. data/lib/mongo/mongo_sharded_client.rb +159 -0
  40. data/lib/mongo/networking.rb +372 -0
  41. data/lib/mongo/{util → utils}/conversions.rb +29 -8
  42. data/lib/mongo/{util → utils}/core_ext.rb +28 -18
  43. data/lib/mongo/{util → utils}/server_version.rb +4 -6
  44. data/lib/mongo/{util → utils}/support.rb +29 -31
  45. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  46. data/lib/mongo/utils.rb +19 -0
  47. data/lib/mongo.rb +51 -50
  48. data/mongo.gemspec +29 -32
  49. data/test/functional/authentication_test.rb +39 -0
  50. data/test/functional/bulk_api_stress_test.rb +133 -0
  51. data/test/functional/bulk_write_collection_view_test.rb +1198 -0
  52. data/test/functional/client_test.rb +627 -0
  53. data/test/functional/collection_test.rb +2175 -0
  54. data/test/functional/collection_writer_test.rb +83 -0
  55. data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
  56. data/test/functional/cursor_fail_test.rb +57 -0
  57. data/test/functional/cursor_message_test.rb +56 -0
  58. data/test/functional/cursor_test.rb +683 -0
  59. data/test/functional/db_api_test.rb +835 -0
  60. data/test/functional/db_connection_test.rb +25 -0
  61. data/test/functional/db_test.rb +348 -0
  62. data/test/functional/grid_file_system_test.rb +285 -0
  63. data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
  64. data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
  65. data/test/functional/pool_test.rb +136 -0
  66. data/test/functional/safe_test.rb +98 -0
  67. data/test/functional/ssl_test.rb +29 -0
  68. data/test/functional/support_test.rb +62 -0
  69. data/test/functional/timeout_test.rb +60 -0
  70. data/test/functional/uri_test.rb +446 -0
  71. data/test/functional/write_concern_test.rb +118 -0
  72. data/test/helpers/general.rb +50 -0
  73. data/test/helpers/test_unit.rb +476 -0
  74. data/test/replica_set/authentication_test.rb +37 -0
  75. data/test/replica_set/basic_test.rb +189 -0
  76. data/test/replica_set/client_test.rb +393 -0
  77. data/test/replica_set/connection_test.rb +138 -0
  78. data/test/replica_set/count_test.rb +66 -0
  79. data/test/replica_set/cursor_test.rb +220 -0
  80. data/test/replica_set/insert_test.rb +157 -0
  81. data/test/replica_set/max_values_test.rb +151 -0
  82. data/test/replica_set/pinning_test.rb +105 -0
  83. data/test/replica_set/query_test.rb +73 -0
  84. data/test/replica_set/read_preference_test.rb +219 -0
  85. data/test/replica_set/refresh_test.rb +211 -0
  86. data/test/replica_set/replication_ack_test.rb +95 -0
  87. data/test/replica_set/ssl_test.rb +32 -0
  88. data/test/sharded_cluster/basic_test.rb +203 -0
  89. data/test/shared/authentication/basic_auth_shared.rb +260 -0
  90. data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
  91. data/test/shared/authentication/gssapi_shared.rb +176 -0
  92. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  93. data/test/shared/authentication/scram_shared.rb +92 -0
  94. data/test/shared/ssl_shared.rb +235 -0
  95. data/test/test_helper.rb +53 -94
  96. data/test/threading/basic_test.rb +120 -0
  97. data/test/tools/mongo_config.rb +708 -0
  98. data/test/tools/mongo_config_test.rb +160 -0
  99. data/test/unit/client_test.rb +381 -0
  100. data/test/unit/collection_test.rb +89 -53
  101. data/test/unit/connection_test.rb +282 -32
  102. data/test/unit/cursor_test.rb +206 -8
  103. data/test/unit/db_test.rb +55 -13
  104. data/test/unit/grid_test.rb +43 -16
  105. data/test/unit/mongo_sharded_client_test.rb +48 -0
  106. data/test/unit/node_test.rb +93 -0
  107. data/test/unit/pool_manager_test.rb +111 -0
  108. data/test/unit/read_pref_test.rb +406 -0
  109. data/test/unit/read_test.rb +159 -0
  110. data/test/unit/safe_test.rb +69 -36
  111. data/test/unit/sharding_pool_manager_test.rb +84 -0
  112. data/test/unit/write_concern_test.rb +175 -0
  113. data.tar.gz.sig +3 -0
  114. metadata +227 -216
  115. metadata.gz.sig +0 -0
  116. data/docs/CREDITS.md +0 -123
  117. data/docs/FAQ.md +0 -116
  118. data/docs/GridFS.md +0 -158
  119. data/docs/HISTORY.md +0 -244
  120. data/docs/RELEASES.md +0 -33
  121. data/docs/REPLICA_SETS.md +0 -72
  122. data/docs/TUTORIAL.md +0 -247
  123. data/docs/WRITE_CONCERN.md +0 -28
  124. data/lib/mongo/exceptions.rb +0 -71
  125. data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
  126. data/lib/mongo/repl_set_connection.rb +0 -342
  127. data/lib/mongo/test.rb +0 -20
  128. data/lib/mongo/util/pool.rb +0 -177
  129. data/lib/mongo/util/uri_parser.rb +0 -185
  130. data/test/async/collection_test.rb +0 -224
  131. data/test/async/connection_test.rb +0 -24
  132. data/test/async/cursor_test.rb +0 -162
  133. data/test/async/worker_pool_test.rb +0 -99
  134. data/test/auxillary/1.4_features.rb +0 -166
  135. data/test/auxillary/authentication_test.rb +0 -68
  136. data/test/auxillary/autoreconnect_test.rb +0 -41
  137. data/test/auxillary/fork_test.rb +0 -30
  138. data/test/auxillary/repl_set_auth_test.rb +0 -58
  139. data/test/auxillary/slave_connection_test.rb +0 -36
  140. data/test/auxillary/threaded_authentication_test.rb +0 -101
  141. data/test/bson/binary_test.rb +0 -15
  142. data/test/bson/bson_test.rb +0 -649
  143. data/test/bson/byte_buffer_test.rb +0 -208
  144. data/test/bson/hash_with_indifferent_access_test.rb +0 -38
  145. data/test/bson/json_test.rb +0 -17
  146. data/test/bson/object_id_test.rb +0 -154
  147. data/test/bson/ordered_hash_test.rb +0 -204
  148. data/test/bson/timestamp_test.rb +0 -24
  149. data/test/collection_test.rb +0 -910
  150. data/test/connection_test.rb +0 -309
  151. data/test/cursor_fail_test.rb +0 -75
  152. data/test/cursor_message_test.rb +0 -43
  153. data/test/cursor_test.rb +0 -483
  154. data/test/db_api_test.rb +0 -726
  155. data/test/db_connection_test.rb +0 -15
  156. data/test/db_test.rb +0 -287
  157. data/test/grid_file_system_test.rb +0 -243
  158. data/test/load/resque/load.rb +0 -21
  159. data/test/load/resque/processor.rb +0 -26
  160. data/test/load/thin/load.rb +0 -24
  161. data/test/load/unicorn/load.rb +0 -23
  162. data/test/load/unicorn/unicorn.rb +0 -29
  163. data/test/replica_sets/connect_test.rb +0 -94
  164. data/test/replica_sets/connection_string_test.rb +0 -32
  165. data/test/replica_sets/count_test.rb +0 -35
  166. data/test/replica_sets/insert_test.rb +0 -53
  167. data/test/replica_sets/pooled_insert_test.rb +0 -55
  168. data/test/replica_sets/query_secondaries.rb +0 -96
  169. data/test/replica_sets/query_test.rb +0 -51
  170. data/test/replica_sets/replication_ack_test.rb +0 -66
  171. data/test/replica_sets/rs_test_helper.rb +0 -27
  172. data/test/safe_test.rb +0 -68
  173. data/test/support/hash_with_indifferent_access.rb +0 -186
  174. data/test/support/keys.rb +0 -45
  175. data/test/support_test.rb +0 -18
  176. data/test/threading/threading_with_large_pool_test.rb +0 -90
  177. data/test/threading_test.rb +0 -87
  178. data/test/tools/auth_repl_set_manager.rb +0 -14
  179. data/test/tools/load.rb +0 -58
  180. data/test/tools/repl_set_manager.rb +0 -266
  181. data/test/tools/sharding_manager.rb +0 -202
  182. data/test/tools/test.rb +0 -4
  183. data/test/unit/pool_test.rb +0 -9
  184. data/test/unit/repl_set_connection_test.rb +0 -59
  185. 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