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.
- data/docs/HISTORY.md +15 -0
- data/docs/REPLICA_SETS.md +19 -7
- data/lib/mongo.rb +1 -0
- data/lib/mongo/collection.rb +1 -1
- data/lib/mongo/connection.rb +29 -351
- data/lib/mongo/cursor.rb +88 -6
- data/lib/mongo/gridfs/grid.rb +4 -2
- data/lib/mongo/gridfs/grid_file_system.rb +4 -2
- data/lib/mongo/networking.rb +345 -0
- data/lib/mongo/repl_set_connection.rb +236 -191
- data/lib/mongo/util/core_ext.rb +45 -0
- data/lib/mongo/util/logging.rb +5 -0
- data/lib/mongo/util/node.rb +6 -4
- data/lib/mongo/util/pool.rb +73 -26
- data/lib/mongo/util/pool_manager.rb +100 -30
- data/lib/mongo/util/uri_parser.rb +29 -21
- data/lib/mongo/version.rb +1 -1
- data/test/bson/binary_test.rb +6 -8
- data/test/bson/bson_test.rb +1 -0
- data/test/bson/ordered_hash_test.rb +2 -0
- data/test/bson/test_helper.rb +0 -17
- data/test/collection_test.rb +22 -0
- data/test/connection_test.rb +1 -1
- data/test/cursor_test.rb +3 -3
- data/test/load/thin/load.rb +4 -7
- data/test/replica_sets/basic_test.rb +46 -0
- data/test/replica_sets/connect_test.rb +35 -58
- data/test/replica_sets/count_test.rb +15 -6
- data/test/replica_sets/insert_test.rb +6 -7
- data/test/replica_sets/query_test.rb +4 -6
- data/test/replica_sets/read_preference_test.rb +112 -8
- data/test/replica_sets/refresh_test.rb +66 -36
- data/test/replica_sets/refresh_with_threads_test.rb +55 -0
- data/test/replica_sets/replication_ack_test.rb +3 -6
- data/test/replica_sets/rs_test_helper.rb +12 -6
- data/test/replica_sets/threading_test.rb +111 -0
- data/test/test_helper.rb +9 -2
- data/test/threading_test.rb +14 -6
- data/test/tools/repl_set_manager.rb +55 -40
- data/test/unit/collection_test.rb +2 -1
- data/test/unit/connection_test.rb +8 -8
- data/test/unit/grid_test.rb +4 -2
- data/test/unit/pool_manager_test.rb +1 -0
- data/test/unit/read_test.rb +17 -5
- data/test/uri_test.rb +9 -4
- metadata +13 -28
- data/test/replica_sets/connection_string_test.rb +0 -29
- data/test/replica_sets/pooled_insert_test.rb +0 -58
- data/test/replica_sets/query_secondaries.rb +0 -109
data/lib/mongo/util/core_ext.rb
CHANGED
@@ -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
|
data/lib/mongo/util/logging.rb
CHANGED
@@ -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
|
data/lib/mongo/util/node.rb
CHANGED
@@ -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?
|
data/lib/mongo/util/pool.rb
CHANGED
@@ -17,12 +17,13 @@
|
|
17
17
|
|
18
18
|
module Mongo
|
19
19
|
class Pool
|
20
|
-
PING_ATTEMPTS
|
20
|
+
PING_ATTEMPTS = 6
|
21
|
+
MAX_PING_TIME = 1_000_000
|
21
22
|
|
22
|
-
attr_accessor :host, :port, :
|
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] ||
|
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
|
-
|
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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
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
|
-
|
266
|
+
else
|
219
267
|
checkout_new_socket
|
220
268
|
end
|
221
269
|
|
222
270
|
if socket
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
136
|
-
|
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
|
142
|
-
@tags_to_pools.each do |
|
209
|
+
def set_tag_mappings
|
210
|
+
@tags_to_pools.each do |key, pool_list|
|
143
211
|
if pool_list.length == 1
|
144
|
-
@
|
212
|
+
@tag_map[key] = pool_list.first
|
145
213
|
else
|
146
|
-
@
|
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
|
260
|
+
if !node.connect
|
261
|
+
next
|
262
|
+
elsif node.set_config
|
193
263
|
return node
|
194
264
|
else
|
195
265
|
node.close
|