github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/third_party/labix.org/v2/mgo/server.go (about) 1 // mgo - MongoDB driver for Go 2 // 3 // Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> 4 // 5 // All rights reserved. 6 // 7 // Redistribution and use in source and binary forms, with or without 8 // modification, are permitted provided that the following conditions are met: 9 // 10 // 1. Redistributions of source code must retain the above copyright notice, this 11 // list of conditions and the following disclaimer. 12 // 2. Redistributions in binary form must reproduce the above copyright notice, 13 // this list of conditions and the following disclaimer in the documentation 14 // and/or other materials provided with the distribution. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 27 package mgo 28 29 import ( 30 "camlistore.org/third_party/labix.org/v2/mgo/bson" 31 "errors" 32 "net" 33 "sort" 34 "sync" 35 "time" 36 ) 37 38 // --------------------------------------------------------------------------- 39 // Mongo server encapsulation. 40 41 type mongoServer struct { 42 sync.RWMutex 43 Addr string 44 ResolvedAddr string 45 tcpaddr *net.TCPAddr 46 unusedSockets []*mongoSocket 47 liveSockets []*mongoSocket 48 closed bool 49 abended bool 50 sync chan bool 51 dial dialer 52 pingValue time.Duration 53 pingIndex int 54 pingCount uint32 55 pingWindow [6]time.Duration 56 info *mongoServerInfo 57 } 58 59 type dialer struct { 60 old func(addr net.Addr) (net.Conn, error) 61 new func(addr *ServerAddr) (net.Conn, error) 62 } 63 64 func (dial dialer) isSet() bool { 65 return dial.old != nil || dial.new != nil 66 } 67 68 type mongoServerInfo struct { 69 Master bool 70 Mongos bool 71 Tags bson.D 72 } 73 74 var defaultServerInfo mongoServerInfo 75 76 func newServer(addr string, tcpaddr *net.TCPAddr, sync chan bool, dial dialer) *mongoServer { 77 server := &mongoServer{ 78 Addr: addr, 79 ResolvedAddr: tcpaddr.String(), 80 tcpaddr: tcpaddr, 81 sync: sync, 82 dial: dial, 83 info: &defaultServerInfo, 84 } 85 // Once so the server gets a ping value, then loop in background. 86 server.pinger(false) 87 go server.pinger(true) 88 return server 89 } 90 91 var errSocketLimit = errors.New("per-server connection limit reached") 92 var errServerClosed = errors.New("server was closed") 93 94 // AcquireSocket returns a socket for communicating with the server. 95 // This will attempt to reuse an old connection, if one is available. Otherwise, 96 // it will establish a new one. The returned socket is owned by the call site, 97 // and will return to the cache when the socket has its Release method called 98 // the same number of times as AcquireSocket + Acquire were called for it. 99 // If the limit argument is not zero, a socket will only be returned if the 100 // number of sockets in use for this server is under the provided limit. 101 func (server *mongoServer) AcquireSocket(limit int, timeout time.Duration) (socket *mongoSocket, abended bool, err error) { 102 for { 103 server.Lock() 104 abended = server.abended 105 if server.closed { 106 server.Unlock() 107 return nil, abended, errServerClosed 108 } 109 n := len(server.unusedSockets) 110 if limit > 0 && len(server.liveSockets)-n >= limit { 111 server.Unlock() 112 return nil, false, errSocketLimit 113 } 114 if n > 0 { 115 socket = server.unusedSockets[n-1] 116 server.unusedSockets[n-1] = nil // Help GC. 117 server.unusedSockets = server.unusedSockets[:n-1] 118 info := server.info 119 server.Unlock() 120 err = socket.InitialAcquire(info, timeout) 121 if err != nil { 122 continue 123 } 124 } else { 125 server.Unlock() 126 socket, err = server.Connect(timeout) 127 if err == nil { 128 server.Lock() 129 // We've waited for the Connect, see if we got 130 // closed in the meantime 131 if server.closed { 132 server.Unlock() 133 socket.Release() 134 socket.Close() 135 return nil, abended, errServerClosed 136 } 137 server.liveSockets = append(server.liveSockets, socket) 138 server.Unlock() 139 } 140 } 141 return 142 } 143 panic("unreachable") 144 } 145 146 // Connect establishes a new connection to the server. This should 147 // generally be done through server.AcquireSocket(). 148 func (server *mongoServer) Connect(timeout time.Duration) (*mongoSocket, error) { 149 server.RLock() 150 master := server.info.Master 151 dial := server.dial 152 server.RUnlock() 153 154 logf("Establishing new connection to %s (timeout=%s)...", server.Addr, timeout) 155 var conn net.Conn 156 var err error 157 switch { 158 case !dial.isSet(): 159 // Cannot do this because it lacks timeout support. :-( 160 //conn, err = net.DialTCP("tcp", nil, server.tcpaddr) 161 conn, err = net.DialTimeout("tcp", server.ResolvedAddr, timeout) 162 case dial.old != nil: 163 conn, err = dial.old(server.tcpaddr) 164 case dial.new != nil: 165 conn, err = dial.new(&ServerAddr{server.Addr, server.tcpaddr}) 166 default: 167 panic("dialer is set, but both dial.old and dial.new are nil") 168 } 169 if err != nil { 170 logf("Connection to %s failed: %v", server.Addr, err.Error()) 171 return nil, err 172 } 173 logf("Connection to %s established.", server.Addr) 174 175 stats.conn(+1, master) 176 return newSocket(server, conn, timeout), nil 177 } 178 179 // Close forces closing all sockets that are alive, whether 180 // they're currently in use or not. 181 func (server *mongoServer) Close() { 182 server.Lock() 183 server.closed = true 184 liveSockets := server.liveSockets 185 unusedSockets := server.unusedSockets 186 server.liveSockets = nil 187 server.unusedSockets = nil 188 server.Unlock() 189 logf("Connections to %s closing (%d live sockets).", server.Addr, len(liveSockets)) 190 for i, s := range liveSockets { 191 s.Close() 192 liveSockets[i] = nil 193 } 194 for i := range unusedSockets { 195 unusedSockets[i] = nil 196 } 197 } 198 199 // RecycleSocket puts socket back into the unused cache. 200 func (server *mongoServer) RecycleSocket(socket *mongoSocket) { 201 server.Lock() 202 if !server.closed { 203 server.unusedSockets = append(server.unusedSockets, socket) 204 } 205 server.Unlock() 206 } 207 208 func removeSocket(sockets []*mongoSocket, socket *mongoSocket) []*mongoSocket { 209 for i, s := range sockets { 210 if s == socket { 211 copy(sockets[i:], sockets[i+1:]) 212 n := len(sockets) - 1 213 sockets[n] = nil 214 sockets = sockets[:n] 215 break 216 } 217 } 218 return sockets 219 } 220 221 // AbendSocket notifies the server that the given socket has terminated 222 // abnormally, and thus should be discarded rather than cached. 223 func (server *mongoServer) AbendSocket(socket *mongoSocket) { 224 server.Lock() 225 server.abended = true 226 if server.closed { 227 server.Unlock() 228 return 229 } 230 server.liveSockets = removeSocket(server.liveSockets, socket) 231 server.unusedSockets = removeSocket(server.unusedSockets, socket) 232 server.Unlock() 233 // Maybe just a timeout, but suggest a cluster sync up just in case. 234 select { 235 case server.sync <- true: 236 default: 237 } 238 } 239 240 func (server *mongoServer) SetInfo(info *mongoServerInfo) { 241 server.Lock() 242 server.info = info 243 server.Unlock() 244 } 245 246 func (server *mongoServer) Info() *mongoServerInfo { 247 server.Lock() 248 info := server.info 249 server.Unlock() 250 return info 251 } 252 253 func (server *mongoServer) hasTags(serverTags []bson.D) bool { 254 NextTagSet: 255 for _, tags := range serverTags { 256 NextReqTag: 257 for _, req := range tags { 258 for _, has := range server.info.Tags { 259 if req.Name == has.Name { 260 if req.Value == has.Value { 261 continue NextReqTag 262 } 263 continue NextTagSet 264 } 265 } 266 continue NextTagSet 267 } 268 return true 269 } 270 return false 271 } 272 273 var pingDelay = 5 * time.Second 274 275 func (server *mongoServer) pinger(loop bool) { 276 op := queryOp{ 277 collection: "admin.$cmd", 278 query: bson.D{{"ping", 1}}, 279 flags: flagSlaveOk, 280 limit: -1, 281 } 282 for { 283 if loop { 284 time.Sleep(pingDelay) 285 } 286 op := op 287 socket, _, err := server.AcquireSocket(0, 3*pingDelay) 288 if err == nil { 289 start := time.Now() 290 _, _ = socket.SimpleQuery(&op) 291 delay := time.Now().Sub(start) 292 293 server.pingWindow[server.pingIndex] = delay 294 server.pingIndex = (server.pingIndex + 1) % len(server.pingWindow) 295 server.pingCount++ 296 var max time.Duration 297 for i := 0; i < len(server.pingWindow) && uint32(i) < server.pingCount; i++ { 298 if server.pingWindow[i] > max { 299 max = server.pingWindow[i] 300 } 301 } 302 socket.Release() 303 server.Lock() 304 if server.closed { 305 loop = false 306 } 307 server.pingValue = max 308 server.Unlock() 309 logf("Ping for %s is %d ms", server.Addr, max/time.Millisecond) 310 } else if err == errServerClosed { 311 return 312 } 313 if !loop { 314 return 315 } 316 } 317 } 318 319 type mongoServerSlice []*mongoServer 320 321 func (s mongoServerSlice) Len() int { 322 return len(s) 323 } 324 325 func (s mongoServerSlice) Less(i, j int) bool { 326 return s[i].ResolvedAddr < s[j].ResolvedAddr 327 } 328 329 func (s mongoServerSlice) Swap(i, j int) { 330 s[i], s[j] = s[j], s[i] 331 } 332 333 func (s mongoServerSlice) Sort() { 334 sort.Sort(s) 335 } 336 337 func (s mongoServerSlice) Search(resolvedAddr string) (i int, ok bool) { 338 n := len(s) 339 i = sort.Search(n, func(i int) bool { 340 return s[i].ResolvedAddr >= resolvedAddr 341 }) 342 return i, i != n && s[i].ResolvedAddr == resolvedAddr 343 } 344 345 type mongoServers struct { 346 slice mongoServerSlice 347 } 348 349 func (servers *mongoServers) Search(resolvedAddr string) (server *mongoServer) { 350 if i, ok := servers.slice.Search(resolvedAddr); ok { 351 return servers.slice[i] 352 } 353 return nil 354 } 355 356 func (servers *mongoServers) Add(server *mongoServer) { 357 servers.slice = append(servers.slice, server) 358 servers.slice.Sort() 359 } 360 361 func (servers *mongoServers) Remove(other *mongoServer) (server *mongoServer) { 362 if i, found := servers.slice.Search(other.ResolvedAddr); found { 363 server = servers.slice[i] 364 copy(servers.slice[i:], servers.slice[i+1:]) 365 n := len(servers.slice) - 1 366 servers.slice[n] = nil // Help GC. 367 servers.slice = servers.slice[:n] 368 } 369 return 370 } 371 372 func (servers *mongoServers) Slice() []*mongoServer { 373 return ([]*mongoServer)(servers.slice) 374 } 375 376 func (servers *mongoServers) Get(i int) *mongoServer { 377 return servers.slice[i] 378 } 379 380 func (servers *mongoServers) Len() int { 381 return len(servers.slice) 382 } 383 384 func (servers *mongoServers) Empty() bool { 385 return len(servers.slice) == 0 386 } 387 388 // BestFit returns the best guess of what would be the most interesting 389 // server to perform operations on at this point in time. 390 func (servers *mongoServers) BestFit(serverTags []bson.D) *mongoServer { 391 var best *mongoServer 392 for _, next := range servers.slice { 393 if best == nil { 394 best = next 395 best.RLock() 396 if serverTags != nil && !next.info.Mongos && !best.hasTags(serverTags) { 397 best.RUnlock() 398 best = nil 399 } 400 continue 401 } 402 next.RLock() 403 swap := false 404 switch { 405 case serverTags != nil && !next.info.Mongos && !next.hasTags(serverTags): 406 // Must have requested tags. 407 case next.info.Master != best.info.Master: 408 // Prefer slaves. 409 swap = best.info.Master 410 case absDuration(next.pingValue-best.pingValue) > 15*time.Millisecond: 411 // Prefer nearest server. 412 swap = next.pingValue < best.pingValue 413 case len(next.liveSockets)-len(next.unusedSockets) < len(best.liveSockets)-len(best.unusedSockets): 414 // Prefer servers with less connections. 415 swap = true 416 } 417 if swap { 418 best.RUnlock() 419 best = next 420 } else { 421 next.RUnlock() 422 } 423 } 424 if best != nil { 425 best.RUnlock() 426 } 427 return best 428 } 429 430 func absDuration(d time.Duration) time.Duration { 431 if d < 0 { 432 return -d 433 } 434 return d 435 }