mongo 1.4.0 → 1.5.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/docs/HISTORY.md +15 -0
  2. data/docs/REPLICA_SETS.md +19 -7
  3. data/lib/mongo.rb +1 -0
  4. data/lib/mongo/collection.rb +1 -1
  5. data/lib/mongo/connection.rb +29 -351
  6. data/lib/mongo/cursor.rb +88 -6
  7. data/lib/mongo/gridfs/grid.rb +4 -2
  8. data/lib/mongo/gridfs/grid_file_system.rb +4 -2
  9. data/lib/mongo/networking.rb +345 -0
  10. data/lib/mongo/repl_set_connection.rb +236 -191
  11. data/lib/mongo/util/core_ext.rb +45 -0
  12. data/lib/mongo/util/logging.rb +5 -0
  13. data/lib/mongo/util/node.rb +6 -4
  14. data/lib/mongo/util/pool.rb +73 -26
  15. data/lib/mongo/util/pool_manager.rb +100 -30
  16. data/lib/mongo/util/uri_parser.rb +29 -21
  17. data/lib/mongo/version.rb +1 -1
  18. data/test/bson/binary_test.rb +6 -8
  19. data/test/bson/bson_test.rb +1 -0
  20. data/test/bson/ordered_hash_test.rb +2 -0
  21. data/test/bson/test_helper.rb +0 -17
  22. data/test/collection_test.rb +22 -0
  23. data/test/connection_test.rb +1 -1
  24. data/test/cursor_test.rb +3 -3
  25. data/test/load/thin/load.rb +4 -7
  26. data/test/replica_sets/basic_test.rb +46 -0
  27. data/test/replica_sets/connect_test.rb +35 -58
  28. data/test/replica_sets/count_test.rb +15 -6
  29. data/test/replica_sets/insert_test.rb +6 -7
  30. data/test/replica_sets/query_test.rb +4 -6
  31. data/test/replica_sets/read_preference_test.rb +112 -8
  32. data/test/replica_sets/refresh_test.rb +66 -36
  33. data/test/replica_sets/refresh_with_threads_test.rb +55 -0
  34. data/test/replica_sets/replication_ack_test.rb +3 -6
  35. data/test/replica_sets/rs_test_helper.rb +12 -6
  36. data/test/replica_sets/threading_test.rb +111 -0
  37. data/test/test_helper.rb +9 -2
  38. data/test/threading_test.rb +14 -6
  39. data/test/tools/repl_set_manager.rb +55 -40
  40. data/test/unit/collection_test.rb +2 -1
  41. data/test/unit/connection_test.rb +8 -8
  42. data/test/unit/grid_test.rb +4 -2
  43. data/test/unit/pool_manager_test.rb +1 -0
  44. data/test/unit/read_test.rb +17 -5
  45. data/test/uri_test.rb +9 -4
  46. metadata +13 -28
  47. data/test/replica_sets/connection_string_test.rb +0 -29
  48. data/test/replica_sets/pooled_insert_test.rb +0 -58
  49. data/test/replica_sets/query_secondaries.rb +0 -109
@@ -58,3 +58,48 @@ class String
58
58
  end
59
59
 
60
60
  end
61
+
62
+ #:nodoc:
63
+ class Class
64
+ def mongo_thread_local_accessor name, options = {}
65
+ m = Module.new
66
+ m.module_eval do
67
+ class_variable_set :"@@#{name}", Hash.new {|h,k| h[k] = options[:default] }
68
+ end
69
+ m.module_eval %{
70
+
71
+ def #{name}
72
+ @@#{name}[Thread.current.object_id]
73
+ end
74
+
75
+ def #{name}=(val)
76
+ @@#{name}[Thread.current.object_id] = val
77
+ end
78
+ }
79
+
80
+ class_eval do
81
+ include m
82
+ extend m
83
+ end
84
+ end
85
+ end
86
+
87
+ # Fix a bug in the interaction of
88
+ # mutexes and timeouts in Ruby 1.9.
89
+ # See https://jira.mongodb.org/browse/RUBY-364 for details.
90
+ if RUBY_VERSION > '1.9'
91
+ class Mutex
92
+ def lock_with_hack
93
+ lock_without_hack
94
+ rescue ThreadError => e
95
+ if e.message != "deadlock; recursive locking"
96
+ raise
97
+ else
98
+ unlock
99
+ lock_without_hack
100
+ end
101
+ end
102
+ alias_method :lock_without_hack, :lock
103
+ alias_method :lock, :lock_with_hack
104
+ end
105
+ end
@@ -1,6 +1,11 @@
1
1
  module Mongo
2
2
  module Logging
3
3
 
4
+ def write_logging_startup_message
5
+ log(:warn, "Please note that logging negatively impacts client-side performance. " +
6
+ "You should set your logging level no lower than :info in production.")
7
+ end
8
+
4
9
  # Log a message with the given level.
5
10
  def log(level, msg)
6
11
  return unless @logger
@@ -1,7 +1,8 @@
1
1
  module Mongo
2
2
  class Node
3
3
 
4
- attr_accessor :host, :port, :address, :config, :connection, :socket
4
+ attr_accessor :host, :port, :address, :config, :connection, :socket,
5
+ :last_state
5
6
 
6
7
  def initialize(connection, data)
7
8
  @connection = connection
@@ -13,6 +14,7 @@ module Mongo
13
14
  end
14
15
  @address = "#{host}:#{port}"
15
16
  @config = nil
17
+ @socket = nil
16
18
  end
17
19
 
18
20
  def eql?(other)
@@ -57,11 +59,11 @@ module Mongo
57
59
  end
58
60
 
59
61
  def close
60
- if @socket
62
+ if @socket && !@socket.closed?
61
63
  @socket.close
62
- @socket = nil
63
- @config = nil
64
64
  end
65
+ @socket = nil
66
+ @config = nil
65
67
  end
66
68
 
67
69
  def connected?
@@ -17,12 +17,13 @@
17
17
 
18
18
  module Mongo
19
19
  class Pool
20
- PING_ATTEMPTS = 6
20
+ PING_ATTEMPTS = 6
21
+ MAX_PING_TIME = 1_000_000
21
22
 
22
- attr_accessor :host, :port, :size, :timeout, :safe, :checked_out, :connection
23
+ attr_accessor :host, :port, :address,
24
+ :size, :timeout, :safe, :checked_out, :connection
23
25
 
24
26
  # Create a new pool of connections.
25
- #
26
27
  def initialize(connection, host, port, opts={})
27
28
  @connection = connection
28
29
 
@@ -31,8 +32,11 @@ module Mongo
31
32
  # A Mongo::Node object.
32
33
  @node = opts[:node]
33
34
 
35
+ # The string address
36
+ @address = "#{@host}:#{@port}"
37
+
34
38
  # Pool size and timeout.
35
- @size = opts[:size] || 1
39
+ @size = opts[:size] || 10000
36
40
  @timeout = opts[:timeout] || 5.0
37
41
 
38
42
  # Mutex for synchronizing pool access
@@ -47,26 +51,42 @@ module Mongo
47
51
  @sockets = []
48
52
  @pids = {}
49
53
  @checked_out = []
54
+ @threads = {}
50
55
  @ping_time = nil
51
56
  @last_ping = nil
57
+ @closed = false
58
+ @last_pruning = Time.now
52
59
  end
53
60
 
54
- def close
61
+ # Close this pool.
62
+ #
63
+ # @option opts [Boolean] :soft (false) If true,
64
+ # close only those sockets that are not checked out.
65
+ def close(opts={})
55
66
  @connection_mutex.synchronize do
56
- @sockets.each do |sock|
67
+ if opts[:soft]
68
+ sockets_to_close = @sockets - @checked_out
69
+ else
70
+ sockets_to_close = @sockets
71
+ end
72
+ sockets_to_close.each do |sock|
57
73
  begin
58
- sock.close
74
+ sock.close unless sock.closed?
59
75
  rescue IOError => ex
60
76
  warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
61
77
  end
62
78
  end
63
- @host = @port = nil
64
79
  @sockets.clear
65
80
  @pids.clear
66
81
  @checked_out.clear
82
+ @closed = true
67
83
  end
68
84
  end
69
85
 
86
+ def closed?
87
+ @closed
88
+ end
89
+
70
90
  def inspect
71
91
  "#<Mongo::Pool:0x#{self.object_id.to_s(16)} @host=#{@host} @port=#{port} " +
72
92
  "@ping_time=#{@ping_time} #{@checked_out.size}/#{@size} sockets available.>"
@@ -98,14 +118,12 @@ module Mongo
98
118
  # to do a round-trip against this node.
99
119
  def refresh_ping_time
100
120
  trials = []
101
- begin
102
- PING_ATTEMPTS.times do
103
- t1 = Time.now
104
- self.connection['admin'].command({:ping => 1}, :socket => @node.socket)
105
- trials << (Time.now - t1) * 1000
106
- end
107
- rescue OperationFailure, SocketError, SystemCallError, IOError => ex
108
- return nil
121
+ PING_ATTEMPTS.times do
122
+ t1 = Time.now
123
+ if !self.ping
124
+ return MAX_PING_TIME
125
+ end
126
+ trials << (Time.now - t1) * 1000
109
127
  end
110
128
 
111
129
  trials.sort!
@@ -120,11 +138,22 @@ module Mongo
120
138
  (total / trials.length).ceil
121
139
  end
122
140
 
141
+ def ping
142
+ begin
143
+ return self.connection['admin'].command({:ping => 1}, :socket => @node.socket)
144
+ rescue OperationFailure, SocketError, SystemCallError, IOError => ex
145
+ return false
146
+ end
147
+ end
148
+
123
149
  # Return a socket to the pool.
124
150
  def checkin(socket)
125
151
  @connection_mutex.synchronize do
126
- @checked_out.delete(socket)
127
- @queue.signal
152
+ if @checked_out.delete(socket)
153
+ @queue.signal
154
+ else
155
+ return false
156
+ end
128
157
  end
129
158
  true
130
159
  end
@@ -150,6 +179,7 @@ module Mongo
150
179
  @sockets << socket
151
180
  @pids[socket] = Process.pid
152
181
  @checked_out << socket
182
+ @threads[socket] = Thread.current.object_id
153
183
  socket
154
184
  end
155
185
 
@@ -195,10 +225,26 @@ module Mongo
195
225
  checkout_new_socket
196
226
  else
197
227
  @checked_out << socket
228
+ @threads[socket] = Thread.current.object_id
198
229
  socket
199
230
  end
200
231
  end
201
232
 
233
+ # If we have more sockets than the soft limit specified
234
+ # by the max pool size, then we should prune those
235
+ # extraneous sockets.
236
+ #
237
+ # Note: this must be called from within a mutex.
238
+ def prune
239
+ surplus = @size - @sockets.size
240
+ return if surplus <= 0
241
+ idle_sockets = @sockets - @checked_out
242
+ [surplus, idle_sockets.length].min.times do |n|
243
+ idle_sockets[n].close
244
+ @sockets.delete(idle_sockets[n])
245
+ end
246
+ end
247
+
202
248
  # Check out an existing socket or create a new socket if the maximum
203
249
  # pool size has not been exceeded. Otherwise, wait for the next
204
250
  # available socket.
@@ -213,20 +259,21 @@ module Mongo
213
259
  end
214
260
 
215
261
  @connection_mutex.synchronize do
262
+ #prune
263
+
216
264
  socket = if @checked_out.size < @sockets.size
217
265
  checkout_existing_socket
218
- elsif @sockets.size < @size
266
+ else
219
267
  checkout_new_socket
220
268
  end
221
269
 
222
270
  if socket
223
-
224
- # This calls all procs, in order, scoped to existing sockets.
225
- # At the moment, we use this to lazily authenticate and
226
- # logout existing socket connections.
227
- @socket_ops[socket].reject! do |op|
228
- op.call
229
- end
271
+ # This calls all procs, in order, scoped to existing sockets.
272
+ # At the moment, we use this to lazily authenticate and
273
+ # logout existing socket connections.
274
+ @socket_ops[socket].reject! do |op|
275
+ op.call
276
+ end
230
277
 
231
278
  return socket
232
279
  else
@@ -3,13 +3,13 @@ module Mongo
3
3
 
4
4
  attr_reader :connection, :seeds, :arbiters, :primary, :secondaries,
5
5
  :primary_pool, :read_pool, :secondary_pools, :hosts, :nodes, :max_bson_size,
6
- :tags_to_pools, :members
6
+ :tags_to_pools, :tag_map, :members
7
7
 
8
8
  def initialize(connection, seeds)
9
9
  @connection = connection
10
10
  @seeds = seeds
11
- @refresh_node = nil
12
11
  @previously_connected = false
12
+ @refresh_required = false
13
13
  end
14
14
 
15
15
  def inspect
@@ -25,28 +25,69 @@ module Mongo
25
25
  members = connect_to_members
26
26
  initialize_pools(members)
27
27
  update_seed_list(members)
28
+ set_read_pool
29
+ set_tag_mappings
28
30
 
29
31
  @members = members
30
32
  @previously_connected = true
31
33
  end
32
34
 
33
- def healthy?
34
- if !@refresh_node || !refresh_node.set_config
35
- return false
35
+ # We're healthy if all members are pingable and if the view
36
+ # of the replica set returned by isMaster is equivalent
37
+ # to our view. If any of these isn't the case,
38
+ # set @refresh_require to true, and return.
39
+ def check_connection_health
40
+ begin
41
+ seed = get_valid_seed_node
42
+ rescue ConnectionFailure
43
+ @refresh_required = true
44
+ return
45
+ end
46
+
47
+ config = seed.set_config
48
+ if !config
49
+ @refresh_required = true
50
+ seed.close
51
+ return
52
+ end
53
+
54
+ if config['hosts'].length != @members.length
55
+ @refresh_required = true
56
+ seed.close
57
+ return
36
58
  end
37
59
 
38
- #if refresh_node.node_list
60
+ config['hosts'].each do |host|
61
+ member = @members.detect do |m|
62
+ m.address == host
63
+ end
64
+
65
+ if member && validate_existing_member(member)
66
+ next
67
+ else
68
+ @refresh_required = true
69
+ seed.close
70
+ return false
71
+ end
72
+ end
73
+
74
+ seed.close
75
+ end
76
+
77
+ # The replica set connection should initiate a full refresh.
78
+ def refresh_required?
79
+ @refresh_required
39
80
  end
40
81
 
41
- def close
82
+ def close(opts={})
42
83
  begin
43
84
  if @primary_pool
44
- @primary_pool.close
85
+ @primary_pool.close(opts)
45
86
  end
46
87
 
47
88
  if @secondary_pools
48
89
  @secondary_pools.each do |pool|
49
- pool.close
90
+ pool.close(opts)
50
91
  end
51
92
  end
52
93
 
@@ -62,6 +103,26 @@ module Mongo
62
103
 
63
104
  private
64
105
 
106
+ def validate_existing_member(member)
107
+ config = member.set_config
108
+ if !config
109
+ return false
110
+ else
111
+ if member.primary?
112
+ if member.last_state == :primary
113
+ return true
114
+ else # This node is now primary, but didn't used to be.
115
+ return false
116
+ end
117
+ elsif member.last_state == :secondary &&
118
+ member.secondary?
119
+ return true
120
+ else # This node isn't what it used to be.
121
+ return false
122
+ end
123
+ end
124
+ end
125
+
65
126
  def initialize_data
66
127
  @primary = nil
67
128
  @primary_pool = nil
@@ -72,6 +133,7 @@ module Mongo
72
133
  @hosts = Set.new
73
134
  @members = Set.new
74
135
  @tags_to_pools = {}
136
+ @tag_map = {}
75
137
  end
76
138
 
77
139
  # Connect to each member of the replica set
@@ -110,40 +172,46 @@ module Mongo
110
172
  @hosts << member.host_string
111
173
 
112
174
  if member.primary?
113
- @primary = member.host_port
114
- @primary_pool = Pool.new(self.connection, member.host, member.port,
115
- :size => self.connection.pool_size,
116
- :timeout => self.connection.connect_timeout,
117
- :node => member)
118
- associate_tags_with_pool(member.tags, @primary_pool)
175
+ assign_primary(member)
119
176
  elsif member.secondary? && !@secondaries.include?(member.host_port)
120
- @secondaries << member.host_port
121
- pool = Pool.new(self.connection, member.host, member.port,
122
- :size => self.connection.pool_size,
123
- :timeout => self.connection.connect_timeout,
124
- :node => member)
125
- @secondary_pools << pool
126
- associate_tags_with_pool(member.tags, pool)
177
+ assign_secondary(member)
127
178
  end
128
179
  end
129
180
 
130
-
131
181
  @max_bson_size = members.first.config['maxBsonObjectSize'] ||
132
182
  Mongo::DEFAULT_MAX_BSON_SIZE
133
183
  @arbiters = members.first.arbiters
184
+ end
134
185
 
135
- set_read_pool
136
- set_primary_tag_pools
186
+ def assign_primary(member)
187
+ member.last_state = :primary
188
+ @primary = member.host_port
189
+ @primary_pool = Pool.new(self.connection, member.host, member.port,
190
+ :size => self.connection.pool_size,
191
+ :timeout => self.connection.pool_timeout,
192
+ :node => member)
193
+ associate_tags_with_pool(member.tags, @primary_pool)
194
+ end
195
+
196
+ def assign_secondary(member)
197
+ member.last_state = :secondary
198
+ @secondaries << member.host_port
199
+ pool = Pool.new(self.connection, member.host, member.port,
200
+ :size => self.connection.pool_size,
201
+ :timeout => self.connection.pool_timeout,
202
+ :node => member)
203
+ @secondary_pools << pool
204
+ associate_tags_with_pool(member.tags, pool)
137
205
  end
138
206
 
139
207
  # If there's more than one pool associated with
140
208
  # a given tag, choose a close one using the bucket method.
141
- def set_primary_tag_pools
142
- @tags_to_pools.each do |k, pool_list|
209
+ def set_tag_mappings
210
+ @tags_to_pools.each do |key, pool_list|
143
211
  if pool_list.length == 1
144
- @tags_to_pools[k] = pool_list.first
212
+ @tag_map[key] = pool_list.first
145
213
  else
146
- @tags_to_pools[k] = nearby_pool_from_set(pool_list)
214
+ @tag_map[key] = nearby_pool_from_set(pool_list)
147
215
  end
148
216
  end
149
217
  end
@@ -189,7 +257,9 @@ module Mongo
189
257
  def get_valid_seed_node
190
258
  @seeds.each do |seed|
191
259
  node = Mongo::Node.new(self.connection, seed)
192
- if node.connect && node.set_config
260
+ if !node.connect
261
+ next
262
+ elsif node.set_config
193
263
  return node
194
264
  else
195
265
  node.close