You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
4.1 KiB
193 lines
4.1 KiB
// Copyright (C) MongoDB, Inc. 2017-present.
|
|
//
|
|
// 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
|
|
|
|
package session
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/description"
|
|
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
|
)
|
|
|
|
// Node represents a server session in a linked list
|
|
type Node struct {
|
|
*Server
|
|
next *Node
|
|
prev *Node
|
|
}
|
|
|
|
// topologyDescription is used to track a subset of the fields present in a description.Topology instance that are
|
|
// relevant for determining session expiration.
|
|
type topologyDescription struct {
|
|
kind description.TopologyKind
|
|
timeoutMinutes uint32
|
|
}
|
|
|
|
// Pool is a pool of server sessions that can be reused.
|
|
type Pool struct {
|
|
// number of sessions checked out of pool (accessed atomically)
|
|
checkedOut int64
|
|
|
|
descChan <-chan description.Topology
|
|
head *Node
|
|
tail *Node
|
|
latestTopology topologyDescription
|
|
mutex sync.Mutex // mutex to protect list and sessionTimeout
|
|
}
|
|
|
|
func (p *Pool) createServerSession() (*Server, error) {
|
|
s, err := newServerSession()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
atomic.AddInt64(&p.checkedOut, 1)
|
|
return s, nil
|
|
}
|
|
|
|
// NewPool creates a new server session pool
|
|
func NewPool(descChan <-chan description.Topology) *Pool {
|
|
p := &Pool{
|
|
descChan: descChan,
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// assumes caller has mutex to protect the pool
|
|
func (p *Pool) updateTimeout() {
|
|
select {
|
|
case newDesc := <-p.descChan:
|
|
p.latestTopology = topologyDescription{
|
|
kind: newDesc.Kind,
|
|
timeoutMinutes: newDesc.SessionTimeoutMinutes,
|
|
}
|
|
default:
|
|
// no new description waiting
|
|
}
|
|
}
|
|
|
|
// GetSession retrieves an unexpired session from the pool.
|
|
func (p *Pool) GetSession() (*Server, error) {
|
|
p.mutex.Lock() // prevent changing the linked list while seeing if sessions have expired
|
|
defer p.mutex.Unlock()
|
|
|
|
// empty pool
|
|
if p.head == nil && p.tail == nil {
|
|
return p.createServerSession()
|
|
}
|
|
|
|
p.updateTimeout()
|
|
for p.head != nil {
|
|
// pull session from head of queue and return if it is valid for at least 1 more minute
|
|
if p.head.expired(p.latestTopology) {
|
|
p.head = p.head.next
|
|
continue
|
|
}
|
|
|
|
// found unexpired session
|
|
session := p.head.Server
|
|
if p.head.next != nil {
|
|
p.head.next.prev = nil
|
|
}
|
|
if p.tail == p.head {
|
|
p.tail = nil
|
|
p.head = nil
|
|
} else {
|
|
p.head = p.head.next
|
|
}
|
|
|
|
atomic.AddInt64(&p.checkedOut, 1)
|
|
return session, nil
|
|
}
|
|
|
|
// no valid session found
|
|
p.tail = nil // empty list
|
|
return p.createServerSession()
|
|
}
|
|
|
|
// ReturnSession returns a session to the pool if it has not expired.
|
|
func (p *Pool) ReturnSession(ss *Server) {
|
|
if ss == nil {
|
|
return
|
|
}
|
|
|
|
p.mutex.Lock()
|
|
defer p.mutex.Unlock()
|
|
|
|
atomic.AddInt64(&p.checkedOut, -1)
|
|
p.updateTimeout()
|
|
// check sessions at end of queue for expired
|
|
// stop checking after hitting the first valid session
|
|
for p.tail != nil && p.tail.expired(p.latestTopology) {
|
|
if p.tail.prev != nil {
|
|
p.tail.prev.next = nil
|
|
}
|
|
p.tail = p.tail.prev
|
|
}
|
|
|
|
// session expired
|
|
if ss.expired(p.latestTopology) {
|
|
return
|
|
}
|
|
|
|
// session is dirty
|
|
if ss.Dirty {
|
|
return
|
|
}
|
|
|
|
newNode := &Node{
|
|
Server: ss,
|
|
next: nil,
|
|
prev: nil,
|
|
}
|
|
|
|
// empty list
|
|
if p.tail == nil {
|
|
p.head = newNode
|
|
p.tail = newNode
|
|
return
|
|
}
|
|
|
|
// at least 1 valid session in list
|
|
newNode.next = p.head
|
|
p.head.prev = newNode
|
|
p.head = newNode
|
|
}
|
|
|
|
// IDSlice returns a slice of session IDs for each session in the pool
|
|
func (p *Pool) IDSlice() []bsoncore.Document {
|
|
p.mutex.Lock()
|
|
defer p.mutex.Unlock()
|
|
|
|
var ids []bsoncore.Document
|
|
for node := p.head; node != nil; node = node.next {
|
|
ids = append(ids, node.SessionID)
|
|
}
|
|
|
|
return ids
|
|
}
|
|
|
|
// String implements the Stringer interface
|
|
func (p *Pool) String() string {
|
|
p.mutex.Lock()
|
|
defer p.mutex.Unlock()
|
|
|
|
s := ""
|
|
for head := p.head; head != nil; head = head.next {
|
|
s += head.SessionID.String() + "\n"
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// CheckedOut returns number of sessions checked out from pool.
|
|
func (p *Pool) CheckedOut() int64 {
|
|
return atomic.LoadInt64(&p.checkedOut)
|
|
}
|