-
Notifications
You must be signed in to change notification settings - Fork 532
/
mongo_sharded_client.rb
221 lines (185 loc) · 7.49 KB
/
mongo_sharded_client.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# encoding: UTF-8
# --
# Copyright (C) 2008-2012 10gen Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ++
module Mongo
# Instantiates and manages connections to a MongoDB sharded cluster for high availability.
class MongoShardedClient < MongoReplicaSetClient
SHARDED_CLUSTER_OPTS = [:refresh_mode, :refresh_interval]
attr_reader :seeds, :refresh_interval, :refresh_mode,
:refresh_version, :manager
# Create a connection to a MongoDB sharded cluster.
#
# If no args are provided, it will check <code>ENV["MONGODB_URI"]</code>.
#
# @param [Array] seeds "host:port" strings
#
# @option opts [String] :name (nil) The name of the sharded cluster to connect to. You
# can use this option to verify that you're connecting to the right sharded cluster.
# @option opts [Hash] ::w (1), :j (false), :wtimeout (false), :fsync (false) Set the default write concern
# propagated to DB objects instantiated off of this MongoClient. This
# default can be overridden upon instantiation of any DB by explicitly setting a write concern values
# on initialization.
# @option opts [Logger] :logger (nil) Logger instance to receive driver operation log.
# @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
# connection pool. Note: this setting is relevant only for multi-threaded applications.
# @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
# this is the number of seconds to wait for a new connection to be released before throwing an exception.
# Note: this setting is relevant only for multi-threaded applications.
# @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
# @option opts [Float] :connect_timeout (30) The number of seconds to wait before timing out a
# connection attempt.
# @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
# @option opts [Boolean] :refresh_mode (false) Set this to :sync to periodically update the
# state of the connection every :refresh_interval seconds. Sharded cluster connection failures
# will always trigger a complete refresh. This option is useful when you want to add new nodes
# or remove sharded cluster nodes not currently in use by the driver.
# @option opts [Integer] :refresh_interval (90) If :refresh_mode is enabled, this is the number of seconds
# between calls to check the sharded cluster's state.
# Note: that the number of seed nodes does not have to be equal to the number of sharded cluster members.
# The purpose of seed nodes is to permit the driver to find at least one sharded cluster member even if a member is down.
#
# @example Connect to a sharded cluster and provide two seed nodes.
# MongoShardedClient.new(['localhost:30000', 'localhost:30001'])
#
# @raise [MongoArgumentError] This is raised for usage errors.
#
# @raise [ConnectionFailure] This is raised for the various connection failures.
def initialize(*args)
opts = args.last.is_a?(Hash) ? args.pop : {}
nodes = args.flatten
if nodes.empty? and ENV.has_key?('MONGODB_URI')
parser = URIParser.new ENV['MONGODB_URI']
if parser.direct?
raise MongoArgumentError, "Mongo::MongoShardedClient.new called with no arguments, but ENV['MONGODB_URI'] implies a direct connection."
end
opts = parser.connection_options.merge! opts
nodes = [parser.nodes]
end
unless nodes.length > 0
raise MongoArgumentError, "A MongoShardedClient requires at least one seed node."
end
@seeds = nodes.map do |host_port|
host, port = host_port.split(":")
[ host, port.to_i ]
end
# TODO: add a method for replacing this list of node.
@seeds.freeze
# Refresh
@last_refresh = Time.now
@refresh_version = 0
# No connection manager by default.
@manager = nil
@old_managers = []
# Lock for request ids.
@id_lock = Mutex.new
@pool_mutex = Mutex.new
@connected = false
@safe_mutex_lock = Mutex.new
@safe_mutexes = Hash.new {|hash, key| hash[key] = Mutex.new}
@connect_mutex = Mutex.new
@refresh_mutex = Mutex.new
check_opts(opts)
setup(opts)
end
def valid_opts
GENERIC_OPTS + SHARDED_CLUSTER_OPTS
end
def inspect
"<Mongo::MongoShardedClient:0x#{self.object_id.to_s(16)} @seeds=#{@seeds.inspect} " +
"@connected=#{@connected}>"
end
# Initiate a connection to the sharded cluster.
def connect(force = !@connected)
return unless force
log(:info, "Connecting...")
@connect_mutex.synchronize do
discovered_seeds = @manager ? @manager.seeds : []
@old_managers << @manager if @manager
@manager = ShardingPoolManager.new(self, discovered_seeds | @seeds)
Thread.current[:managers] ||= Hash.new
Thread.current[:managers][self] = @manager
@manager.connect
@refresh_version += 1
@last_refresh = Time.now
@connected = true
end
end
# Force a hard refresh of this connection's view
# of the sharded cluster.
#
# @return [Boolean] +true+ if hard refresh
# occurred. +false+ is returned when unable
# to get the refresh lock.
def hard_refresh!
log(:info, "Initiating hard refresh...")
connect(true)
return true
end
def connected?
@connected && @manager.primary_pool
end
# Returns +true+ if it's okay to read from a secondary node.
# Since this is a sharded cluster, this must always be false.
#
# This method exist primarily so that Cursor objects will
# generate query messages with a slaveOkay value of +true+.
#
# @return [Boolean] +true+
def slave_ok?
false
end
def checkout(&block)
2.times do
if connected?
sync_refresh
else
connect
end
begin
socket = block.call
rescue => ex
checkin(socket) if socket
raise ex
end
if socket
return socket
else
@connected = false
#raise ConnectionFailure.new("Could not checkout a socket.")
end
end
end
private
# Parse option hash
def setup(opts)
# Refresh
@refresh_mode = opts.fetch(:refresh_mode, false)
@refresh_interval = opts.fetch(:refresh_interval, 90)
if @refresh_mode && @refresh_interval < 60
@refresh_interval = 60 unless ENV['TEST_MODE'] = 'TRUE'
end
if @refresh_mode == :async
warn ":async refresh mode has been deprecated. Refresh
mode will be disabled."
elsif ![:sync, false].include?(@refresh_mode)
raise MongoArgumentError,
"Refresh mode must be either :sync or false."
end
opts[:connect_timeout] = opts[:connect_timeout] || 30
super opts
end
end
end