mongo 1.4.0 → 1.5.0.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.
- 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
|