mongo 1.8.2 → 1.8.3.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data.tar.gz.sig +0 -0
  2. data/LICENSE +1 -1
  3. data/README.md +2 -7
  4. data/VERSION +1 -1
  5. data/lib/mongo.rb +2 -18
  6. data/lib/mongo/collection.rb +20 -24
  7. data/lib/mongo/cursor.rb +30 -35
  8. data/lib/mongo/db.rb +1 -19
  9. data/lib/mongo/exceptions.rb +0 -19
  10. data/lib/mongo/gridfs/grid.rb +18 -29
  11. data/lib/mongo/gridfs/grid_ext.rb +0 -18
  12. data/lib/mongo/gridfs/grid_file_system.rb +17 -26
  13. data/lib/mongo/gridfs/grid_io.rb +0 -18
  14. data/lib/mongo/legacy.rb +0 -18
  15. data/lib/mongo/mongo_client.rb +11 -33
  16. data/lib/mongo/mongo_replica_set_client.rb +29 -56
  17. data/lib/mongo/mongo_sharded_client.rb +38 -50
  18. data/lib/mongo/networking.rb +5 -4
  19. data/lib/mongo/util/conversions.rb +0 -17
  20. data/lib/mongo/util/core_ext.rb +0 -18
  21. data/lib/mongo/util/node.rb +16 -3
  22. data/lib/mongo/util/pool.rb +46 -36
  23. data/lib/mongo/util/pool_manager.rb +102 -71
  24. data/lib/mongo/util/read_preference.rb +4 -2
  25. data/lib/mongo/util/server_version.rb +0 -17
  26. data/lib/mongo/util/sharding_pool_manager.rb +4 -23
  27. data/lib/mongo/util/socket_util.rb +20 -0
  28. data/lib/mongo/util/ssl_socket.rb +10 -19
  29. data/lib/mongo/util/support.rb +0 -18
  30. data/lib/mongo/util/tcp_socket.rb +1 -9
  31. data/lib/mongo/util/thread_local_variable_manager.rb +0 -18
  32. data/lib/mongo/util/uri_parser.rb +87 -82
  33. data/lib/mongo/util/write_concern.rb +0 -18
  34. data/mongo.gemspec +4 -1
  35. data/test/auxillary/pool_reuse_test.rb +65 -0
  36. data/test/functional/collection_test.rb +92 -3
  37. data/test/functional/connection_test.rb +30 -6
  38. data/test/functional/db_api_test.rb +11 -0
  39. data/test/functional/timeout_test.rb +23 -0
  40. data/test/functional/uri_test.rb +69 -0
  41. data/test/replica_set/client_test.rb +0 -22
  42. data/test/replica_set/cursor_test.rb +11 -5
  43. data/test/replica_set/refresh_test.rb +0 -0
  44. data/test/replica_set/z_cluster_shutdown.rb +13 -0
  45. data/test/sharded_cluster/basic_test.rb +46 -32
  46. data/test/test_helper.rb +26 -1
  47. data/test/threading/basic_test.rb +10 -9
  48. data/test/tools/mongo_config.rb +6 -2
  49. data/test/unit/collection_test.rb +1 -1
  50. data/test/unit/grid_test.rb +20 -13
  51. data/test/unit/mongo_sharded_client_test.rb +32 -0
  52. data/test/unit/pool_manager_test.rb +28 -14
  53. metadata +45 -12
  54. metadata.gz.sig +0 -0
@@ -39,7 +39,7 @@ module Mongo
39
39
  raise ex
40
40
  ensure
41
41
  if sock
42
- sock.pool.checkin(sock)
42
+ sock.checkin
43
43
  end
44
44
  end
45
45
  end
@@ -116,6 +116,7 @@ module Mongo
116
116
  send_message_on_socket(packed_message, socket)
117
117
  result = receive(socket, request_id, exhaust)
118
118
  rescue ConnectionFailure => ex
119
+ socket.close
119
120
  checkin(socket)
120
121
  raise ex
121
122
  rescue SystemStackError, NoMemoryError, SystemCallError => ex
@@ -209,13 +210,13 @@ module Mongo
209
210
  end
210
211
 
211
212
  def build_command_message(db_name, query, projection=nil, skip=0, limit=-1)
212
- message = BSON::ByteBuffer.new
213
+ message = BSON::ByteBuffer.new("", max_message_size)
213
214
  message.put_int(0)
214
215
  BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
215
216
  message.put_int(skip)
216
217
  message.put_int(limit)
217
- message.put_binary(BSON::BSON_CODER.serialize(query, false).to_s)
218
- message.put_binary(BSON::BSON_CODER.serialize(projection, false).to_s) if projection
218
+ message.put_binary(BSON::BSON_CODER.serialize(query, false, false, max_bson_size).to_s)
219
+ message.put_binary(BSON::BSON_CODER.serialize(projection, false, false, max_bson_size).to_s) if projection
219
220
  message
220
221
  end
221
222
 
@@ -1,20 +1,3 @@
1
- # encoding: UTF-8
2
-
3
- # --
4
- # Copyright (C) 2008-2012 10gen Inc.
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- # ++
18
1
  module Mongo #:nodoc:
19
2
 
20
3
  # Utility module to include when needing to convert certain types of
@@ -1,21 +1,3 @@
1
- # encoding: UTF-8
2
-
3
- # --
4
- # Copyright (C) 2008-2012 10gen Inc.
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- # ++
18
-
19
1
  #:nodoc:
20
2
  class Object
21
3
 
@@ -13,17 +13,26 @@ module Mongo
13
13
  end
14
14
 
15
15
  def eql?(other)
16
- other.is_a?(Node) && @address == other.address
16
+ (other.is_a?(Node) && @address == other.address)
17
17
  end
18
18
  alias :== :eql?
19
19
 
20
+ def =~(other)
21
+ if other.is_a?(String)
22
+ h, p = split_node(other)
23
+ h == @host && p == @port
24
+ else
25
+ false
26
+ end
27
+ end
28
+
20
29
  def host_string
21
30
  address
22
31
  end
23
32
 
24
33
  def config
25
34
  connect unless connected?
26
- set_config unless @config
35
+ set_config unless @config || !connected?
27
36
  @config
28
37
  end
29
38
 
@@ -57,7 +66,7 @@ module Mongo
57
66
  end
58
67
 
59
68
  def connected?
60
- @socket != nil
69
+ @socket != nil && !@socket.closed?
61
70
  end
62
71
 
63
72
  def active?
@@ -75,6 +84,10 @@ module Mongo
75
84
  def set_config
76
85
  @node_mutex.synchronize do
77
86
  begin
87
+ if @config
88
+ @last_state = @config['ismaster'] ? :primary : :other
89
+ end
90
+
78
91
  @config = @client['admin'].command({:ismaster => 1}, :socket => @socket)
79
92
 
80
93
  if @config['msg']
@@ -1,23 +1,8 @@
1
- # --
2
- # Copyright (C) 2008-2012 10gen Inc.
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
-
16
1
  module Mongo
17
2
  class Pool
18
3
  PING_ATTEMPTS = 6
19
4
  MAX_PING_TIME = 1_000_000
20
- include ThreadLocalVariableManager
5
+ PRUNE_INTERVAL = 10_000
21
6
 
22
7
  attr_accessor :host,
23
8
  :port,
@@ -25,7 +10,8 @@ module Mongo
25
10
  :size,
26
11
  :timeout,
27
12
  :checked_out,
28
- :client
13
+ :client,
14
+ :node
29
15
 
30
16
  # Create a new pool of connections.
31
17
  def initialize(client, host, port, opts={})
@@ -55,12 +41,13 @@ module Mongo
55
41
  # Operations to perform on a socket
56
42
  @socket_ops = Hash.new { |h, k| h[k] = [] }
57
43
 
58
- @sockets = []
59
- @checked_out = []
60
- @ping_time = nil
61
- @last_ping = nil
62
- @closed = false
63
- @checkout_counter = 0
44
+ @sockets = []
45
+ @checked_out = []
46
+ @ping_time = nil
47
+ @last_ping = nil
48
+ @closed = false
49
+ @thread_ids_to_sockets = {}
50
+ @checkout_counter = 0
64
51
  end
65
52
 
66
53
  # Close this pool.
@@ -85,6 +72,11 @@ module Mongo
85
72
  @node.tags
86
73
  end
87
74
 
75
+ def healthy?
76
+ close if @sockets.all?(&:closed?)
77
+ !closed? && node.healthy?
78
+ end
79
+
88
80
  def closed?
89
81
  @closed
90
82
  end
@@ -174,7 +166,7 @@ module Mongo
174
166
  def checkin(socket)
175
167
  @connection_mutex.synchronize do
176
168
  if @checked_out.delete(socket)
177
- @queue.signal
169
+ @queue.broadcast
178
170
  else
179
171
  return false
180
172
  end
@@ -202,8 +194,7 @@ module Mongo
202
194
 
203
195
  @sockets << socket
204
196
  @checked_out << socket
205
-
206
- thread_local[:sockets][self.object_id] = socket
197
+ @thread_ids_to_sockets[Thread.current.object_id] = socket
207
198
  socket
208
199
  end
209
200
 
@@ -242,7 +233,8 @@ module Mongo
242
233
  # therefore, it runs within a mutex.
243
234
  def checkout_existing_socket(socket=nil)
244
235
  if !socket
245
- socket = (@sockets - @checked_out).first
236
+ available = @sockets - @checked_out
237
+ socket = available[rand(available.length)]
246
238
  end
247
239
 
248
240
  if socket.pid != Process.pid
@@ -253,11 +245,27 @@ module Mongo
253
245
  checkout_new_socket
254
246
  else
255
247
  @checked_out << socket
256
- thread_local[:sockets][self.object_id] = socket
248
+ @thread_ids_to_sockets[Thread.current.object_id] = socket
257
249
  socket
258
250
  end
259
251
  end
260
252
 
253
+ def prune_threads
254
+ live_threads = Thread.list.map(&:object_id)
255
+ @thread_ids_to_sockets.reject! do |key, value|
256
+ !live_threads.include?(key)
257
+ end
258
+ end
259
+
260
+ def check_prune
261
+ if @checkout_counter > PRUNE_INTERVAL
262
+ @checkout_counter = 0
263
+ prune_threads
264
+ else
265
+ @checkout_counter += 1
266
+ end
267
+ end
268
+
261
269
  # Check out an existing socket or create a new socket if the maximum
262
270
  # pool size has not been exceeded. Otherwise, wait for the next
263
271
  # available socket.
@@ -265,8 +273,16 @@ module Mongo
265
273
  @client.connect if !@client.connected?
266
274
  start_time = Time.now
267
275
  loop do
276
+ if (Time.now - start_time) > @timeout
277
+ raise ConnectionTimeoutError, "could not obtain connection within " +
278
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
279
+ "consider increasing the pool size or timeout."
280
+ end
281
+
268
282
  @connection_mutex.synchronize do
269
- if socket_for_thread = thread_local[:sockets][self.object_id]
283
+ check_prune
284
+ socket = nil
285
+ if socket_for_thread = @thread_ids_to_sockets[Thread.current.object_id]
270
286
  if !@checked_out.include?(socket_for_thread)
271
287
  socket = checkout_existing_socket(socket_for_thread)
272
288
  end
@@ -289,7 +305,7 @@ module Mongo
289
305
  if socket.closed?
290
306
  @checked_out.delete(socket)
291
307
  @sockets.delete(socket)
292
- thread_local[:sockets].delete self.object_id
308
+ @thread_ids_to_sockets.delete(Thread.current.object_id)
293
309
  socket = checkout_new_socket
294
310
  end
295
311
 
@@ -299,12 +315,6 @@ module Mongo
299
315
  @queue.wait(@connection_mutex)
300
316
  end
301
317
  end
302
-
303
- if (Time.now - start_time) > @timeout
304
- raise ConnectionTimeoutError, "could not obtain connection within " +
305
- "#{@timeout} seconds. The max pool size is currently #{@size}; " +
306
- "consider increasing the pool size or timeout."
307
- end
308
318
  end
309
319
  end
310
320
 
@@ -3,9 +3,17 @@ module Mongo
3
3
  include Mongo::ReadPreference
4
4
  include ThreadLocalVariableManager
5
5
 
6
- attr_reader :client, :arbiters, :primary, :secondaries, :primary_pool,
7
- :secondary_pool, :secondary_pools, :hosts, :nodes, :members, :seeds,
8
- :max_bson_size
6
+ attr_reader :client,
7
+ :arbiters,
8
+ :primary,
9
+ :secondaries,
10
+ :primary_pool,
11
+ :secondary_pools,
12
+ :hosts,
13
+ :nodes,
14
+ :members,
15
+ :seeds,
16
+ :pools
9
17
 
10
18
  # Create a new set of connection pools.
11
19
  #
@@ -17,7 +25,15 @@ module Mongo
17
25
  def initialize(client, seeds=[])
18
26
  @client = client
19
27
  @seeds = seeds
20
- @previously_connected = false
28
+
29
+ @pools = Set.new
30
+ @primary = nil
31
+ @primary_pool = nil
32
+ @secondaries = Set.new
33
+ @secondary_pools = []
34
+ @hosts = Set.new
35
+ @members = Set.new
36
+ @refresh_required = false
21
37
  end
22
38
 
23
39
  def inspect
@@ -25,15 +41,16 @@ module Mongo
25
41
  end
26
42
 
27
43
  def connect
28
- close if @previously_connected
29
-
30
- initialize_data
31
- members = connect_to_members
32
- initialize_pools(members)
33
- cache_discovered_seeds(members)
44
+ @refresh_required = false
45
+ disconnect_old_members
46
+ connect_to_members
47
+ initialize_pools(@members)
48
+ cache_discovered_seeds
49
+ end
34
50
 
35
- @members = members
36
- @previously_connected = true
51
+ def refresh!(additional_seeds)
52
+ @seeds |= additional_seeds
53
+ connect
37
54
  end
38
55
 
39
56
  # We're healthy if all members are pingable and if the view
@@ -48,25 +65,24 @@ module Mongo
48
65
  return
49
66
  end
50
67
 
51
- config = seed.config
52
- if config
68
+ unless current_config = seed.config
53
69
  @refresh_required = true
54
70
  seed.close
55
71
  return
56
72
  end
57
73
 
58
- if config['hosts'].length != @members.length
74
+ if current_config['hosts'].length != @members.length
59
75
  @refresh_required = true
60
76
  seed.close
61
77
  return
62
78
  end
63
79
 
64
- config['hosts'].each do |host|
80
+ current_config['hosts'].each do |host|
65
81
  member = @members.detect do |m|
66
82
  m.address == host
67
83
  end
68
84
 
69
- if member && validate_existing_member(member)
85
+ if member && validate_existing_member(current_config, member)
70
86
  next
71
87
  else
72
88
  @refresh_required = true
@@ -118,71 +134,73 @@ module Mongo
118
134
  pool
119
135
  end
120
136
 
121
- def pools
122
- [@primary_pool, *@secondary_pools].compact
137
+ def max_bson_size
138
+ @max_bson_size ||= config_min('maxBsonObjectSize', DEFAULT_MAX_BSON_SIZE)
139
+ end
140
+
141
+ def max_message_size
142
+ @max_message_size ||= config_min('maxMessageSizeBytes', DEFAULT_MAX_MESSAGE_SIZE)
123
143
  end
124
144
 
125
145
  private
126
146
 
127
- def validate_existing_member(member)
128
- config = member.config
129
- if config
147
+ def validate_existing_member(current_config, member)
148
+ if current_config['ismaster'] && member.last_state != :primary
149
+ return false
150
+ elsif member.last_state != :other
130
151
  return false
131
- else
132
- if member.primary?
133
- if member.last_state == :primary
134
- return true
135
- else # This node is now primary, but didn't used to be.
136
- return false
137
- end
138
- elsif member.last_state == :secondary &&
139
- member.secondary?
140
- return true
141
- else # This node isn't what it used to be.
142
- return false
143
- end
144
152
  end
153
+ return true
145
154
  end
146
155
 
147
- def initialize_data
148
- @primary = nil
149
- @primary_pool = nil
150
- @read = nil
151
- @read_pool = nil
152
- @arbiters = []
153
- @secondaries = Set.new
154
- @secondary_pool = nil
155
- @secondary_pools = []
156
- @hosts = Set.new
157
- @members = Set.new
158
- @refresh_required = false
156
+ # For any existing members, close and remove any that are unhealthy or already closed.
157
+ def disconnect_old_members
158
+ @pools.reject! {|pool| !pool.healthy? }
159
+ @members.reject! {|node| !node.healthy? }
159
160
  end
160
161
 
161
162
  # Connect to each member of the replica set
162
- # as reported by the given seed node, and return
163
- # as a list of Mongo::Node objects.
163
+ # as reported by the given seed node.
164
164
  def connect_to_members
165
- members = []
166
-
167
165
  seed = get_valid_seed_node
168
-
169
166
  seed.node_list.each do |host|
167
+ if existing = @members.detect {|node| node =~ host }
168
+ if existing.healthy?
169
+ # Refresh this node's configuration
170
+ existing.set_config
171
+ # If we are unhealthy after refreshing our config, drop from the set.
172
+ if !existing.healthy?
173
+ @members.delete existing
174
+ else
175
+ next
176
+ end
177
+ else
178
+ existing.close
179
+ @members.delete existing
180
+ end
181
+ end
182
+
170
183
  node = Mongo::Node.new(self.client, host)
171
184
  node.connect
172
- members << node if node.healthy?
185
+ @members << node if node.healthy?
173
186
  end
174
187
  seed.close
175
188
 
176
- if members.empty?
189
+ if @members.empty?
177
190
  raise ConnectionFailure, "Failed to connect to any given member."
178
191
  end
179
-
180
- members
181
192
  end
182
193
 
183
194
  # Initialize the connection pools for the primary and secondary nodes.
184
195
  def initialize_pools(members)
196
+ @primary_pool = nil
197
+ @primary = nil
198
+ @secondaries.clear
199
+ @secondary_pools.clear
200
+ @hosts.clear
201
+
185
202
  members.each do |member|
203
+ member.last_state = nil
186
204
  @hosts << member.host_string
187
205
  if member.primary?
188
206
  assign_primary(member)
@@ -192,30 +210,43 @@ module Mongo
192
210
  end
193
211
  end
194
212
 
195
- @max_bson_size = members.first.config['maxBsonObjectSize'] ||
196
- Mongo::DEFAULT_MAX_BSON_SIZE
197
213
  @arbiters = members.first.arbiters
198
214
  end
199
215
 
216
+ def config_min(attribute, default)
217
+ @members.reject {|m| !m.config[attribute]}
218
+ @members.map {|m| m.config[attribute]}.min || default
219
+ end
220
+
200
221
  def assign_primary(member)
201
222
  member.last_state = :primary
202
223
  @primary = member.host_port
203
- @primary_pool = Pool.new(self.client, member.host, member.port,
204
- :size => self.client.pool_size,
205
- :timeout => self.client.pool_timeout,
206
- :node => member
207
- )
224
+ if existing = @pools.detect {|pool| pool.node == member }
225
+ @primary_pool = existing
226
+ else
227
+ @primary_pool = Pool.new(self.client, member.host, member.port,
228
+ :size => self.client.pool_size,
229
+ :timeout => self.client.pool_timeout,
230
+ :node => member
231
+ )
232
+ @pools << @primary_pool
233
+ end
208
234
  end
209
235
 
210
236
  def assign_secondary(member)
211
237
  member.last_state = :secondary
212
238
  @secondaries << member.host_port
213
- pool = Pool.new(self.client, member.host, member.port,
214
- :size => self.client.pool_size,
215
- :timeout => self.client.pool_timeout,
216
- :node => member
217
- )
218
- @secondary_pools << pool
239
+ if existing = @pools.detect {|pool| pool.node == member }
240
+ @secondary_pools << existing
241
+ else
242
+ pool = Pool.new(self.client, member.host, member.port,
243
+ :size => self.client.pool_size,
244
+ :timeout => self.client.pool_timeout,
245
+ :node => member
246
+ )
247
+ @secondary_pools << pool
248
+ @pools << pool
249
+ end
219
250
  end
220
251
 
221
252
  # Iterate through the list of provided seed
@@ -236,8 +267,8 @@ module Mongo
236
267
 
237
268
  private
238
269
 
239
- def cache_discovered_seeds(members)
240
- @seeds = members.map { |n| n.host_port }
270
+ def cache_discovered_seeds
271
+ @seeds = @members.map &:host_port
241
272
  end
242
273
 
243
274
  end