mongo 1.8.2 → 1.8.3.rc0

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 (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