newline battles continue
This commit is contained in:
391
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/client_session.go
generated
vendored
Executable file
391
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/client_session.go
generated
vendored
Executable file
@@ -0,0 +1,391 @@
|
||||
// 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 "go.mongodb.org/mongo-driver/x/mongo/driver/session"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/readconcern"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
"go.mongodb.org/mongo-driver/mongo/writeconcern"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/description"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/uuid"
|
||||
)
|
||||
|
||||
// ErrSessionEnded is returned when a client session is used after a call to endSession().
|
||||
var ErrSessionEnded = errors.New("ended session was used")
|
||||
|
||||
// ErrNoTransactStarted is returned if a transaction operation is called when no transaction has started.
|
||||
var ErrNoTransactStarted = errors.New("no transaction started")
|
||||
|
||||
// ErrTransactInProgress is returned if startTransaction() is called when a transaction is in progress.
|
||||
var ErrTransactInProgress = errors.New("transaction already in progress")
|
||||
|
||||
// ErrAbortAfterCommit is returned when abort is called after a commit.
|
||||
var ErrAbortAfterCommit = errors.New("cannot call abortTransaction after calling commitTransaction")
|
||||
|
||||
// ErrAbortTwice is returned if abort is called after transaction is already aborted.
|
||||
var ErrAbortTwice = errors.New("cannot call abortTransaction twice")
|
||||
|
||||
// ErrCommitAfterAbort is returned if commit is called after an abort.
|
||||
var ErrCommitAfterAbort = errors.New("cannot call commitTransaction after calling abortTransaction")
|
||||
|
||||
// ErrUnackWCUnsupported is returned if an unacknowledged write concern is supported for a transaciton.
|
||||
var ErrUnackWCUnsupported = errors.New("transactions do not support unacknowledged write concerns")
|
||||
|
||||
// Type describes the type of the session
|
||||
type Type uint8
|
||||
|
||||
// These constants are the valid types for a client session.
|
||||
const (
|
||||
Explicit Type = iota
|
||||
Implicit
|
||||
)
|
||||
|
||||
// State indicates the state of the FSM.
|
||||
type state uint8
|
||||
|
||||
// Client Session states
|
||||
const (
|
||||
None state = iota
|
||||
Starting
|
||||
InProgress
|
||||
Committed
|
||||
Aborted
|
||||
)
|
||||
|
||||
// Client is a session for clients to run commands.
|
||||
type Client struct {
|
||||
*Server
|
||||
ClientID uuid.UUID
|
||||
ClusterTime bson.Raw
|
||||
Consistent bool // causal consistency
|
||||
OperationTime *primitive.Timestamp
|
||||
SessionType Type
|
||||
Terminated bool
|
||||
RetryingCommit bool
|
||||
Committing bool
|
||||
Aborting bool
|
||||
RetryWrite bool
|
||||
|
||||
// options for the current transaction
|
||||
// most recently set by transactionopt
|
||||
CurrentRc *readconcern.ReadConcern
|
||||
CurrentRp *readpref.ReadPref
|
||||
CurrentWc *writeconcern.WriteConcern
|
||||
|
||||
// default transaction options
|
||||
transactionRc *readconcern.ReadConcern
|
||||
transactionRp *readpref.ReadPref
|
||||
transactionWc *writeconcern.WriteConcern
|
||||
|
||||
pool *Pool
|
||||
state state
|
||||
PinnedServer *description.Server
|
||||
RecoveryToken bson.Raw
|
||||
}
|
||||
|
||||
func getClusterTime(clusterTime bson.Raw) (uint32, uint32) {
|
||||
if clusterTime == nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
clusterTimeVal, err := clusterTime.LookupErr("$clusterTime")
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
timestampVal, err := bson.Raw(clusterTimeVal.Value).LookupErr("clusterTime")
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
return timestampVal.Timestamp()
|
||||
}
|
||||
|
||||
// MaxClusterTime compares 2 clusterTime documents and returns the document representing the highest cluster time.
|
||||
func MaxClusterTime(ct1, ct2 bson.Raw) bson.Raw {
|
||||
epoch1, ord1 := getClusterTime(ct1)
|
||||
epoch2, ord2 := getClusterTime(ct2)
|
||||
|
||||
if epoch1 > epoch2 {
|
||||
return ct1
|
||||
} else if epoch1 < epoch2 {
|
||||
return ct2
|
||||
} else if ord1 > ord2 {
|
||||
return ct1
|
||||
} else if ord1 < ord2 {
|
||||
return ct2
|
||||
}
|
||||
|
||||
return ct1
|
||||
}
|
||||
|
||||
// NewClientSession creates a Client.
|
||||
func NewClientSession(pool *Pool, clientID uuid.UUID, sessionType Type, opts ...*ClientOptions) (*Client, error) {
|
||||
c := &Client{
|
||||
Consistent: true, // set default
|
||||
ClientID: clientID,
|
||||
SessionType: sessionType,
|
||||
pool: pool,
|
||||
}
|
||||
|
||||
mergedOpts := mergeClientOptions(opts...)
|
||||
if mergedOpts.CausalConsistency != nil {
|
||||
c.Consistent = *mergedOpts.CausalConsistency
|
||||
}
|
||||
if mergedOpts.DefaultReadPreference != nil {
|
||||
c.transactionRp = mergedOpts.DefaultReadPreference
|
||||
}
|
||||
if mergedOpts.DefaultReadConcern != nil {
|
||||
c.transactionRc = mergedOpts.DefaultReadConcern
|
||||
}
|
||||
if mergedOpts.DefaultWriteConcern != nil {
|
||||
c.transactionWc = mergedOpts.DefaultWriteConcern
|
||||
}
|
||||
|
||||
servSess, err := pool.GetSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Server = servSess
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// AdvanceClusterTime updates the session's cluster time.
|
||||
func (c *Client) AdvanceClusterTime(clusterTime bson.Raw) error {
|
||||
if c.Terminated {
|
||||
return ErrSessionEnded
|
||||
}
|
||||
c.ClusterTime = MaxClusterTime(c.ClusterTime, clusterTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdvanceOperationTime updates the session's operation time.
|
||||
func (c *Client) AdvanceOperationTime(opTime *primitive.Timestamp) error {
|
||||
if c.Terminated {
|
||||
return ErrSessionEnded
|
||||
}
|
||||
|
||||
if c.OperationTime == nil {
|
||||
c.OperationTime = opTime
|
||||
return nil
|
||||
}
|
||||
|
||||
if opTime.T > c.OperationTime.T {
|
||||
c.OperationTime = opTime
|
||||
} else if (opTime.T == c.OperationTime.T) && (opTime.I > c.OperationTime.I) {
|
||||
c.OperationTime = opTime
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUseTime updates the session's last used time.
|
||||
// Must be called whenver this session is used to send a command to the server.
|
||||
func (c *Client) UpdateUseTime() error {
|
||||
if c.Terminated {
|
||||
return ErrSessionEnded
|
||||
}
|
||||
c.updateUseTime()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRecoveryToken updates the session's recovery token from the server response.
|
||||
func (c *Client) UpdateRecoveryToken(response bson.Raw) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := response.LookupErr("recoveryToken")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RecoveryToken = token.Document()
|
||||
}
|
||||
|
||||
// ClearPinnedServer sets the PinnedServer to nil.
|
||||
func (c *Client) ClearPinnedServer() {
|
||||
if c != nil {
|
||||
c.PinnedServer = nil
|
||||
}
|
||||
}
|
||||
|
||||
// EndSession ends the session.
|
||||
func (c *Client) EndSession() {
|
||||
if c.Terminated {
|
||||
return
|
||||
}
|
||||
|
||||
c.Terminated = true
|
||||
c.pool.ReturnSession(c.Server)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TransactionInProgress returns true if the client session is in an active transaction.
|
||||
func (c *Client) TransactionInProgress() bool {
|
||||
return c.state == InProgress
|
||||
}
|
||||
|
||||
// TransactionStarting returns true if the client session is starting a transaction.
|
||||
func (c *Client) TransactionStarting() bool {
|
||||
return c.state == Starting
|
||||
}
|
||||
|
||||
// TransactionRunning returns true if the client session has started the transaction
|
||||
// and it hasn't been committed or aborted
|
||||
func (c *Client) TransactionRunning() bool {
|
||||
return c != nil && (c.state == Starting || c.state == InProgress)
|
||||
}
|
||||
|
||||
// TransactionCommitted returns true of the client session just committed a transaciton.
|
||||
func (c *Client) TransactionCommitted() bool {
|
||||
return c.state == Committed
|
||||
}
|
||||
|
||||
// CheckStartTransaction checks to see if allowed to start transaction and returns
|
||||
// an error if not allowed
|
||||
func (c *Client) CheckStartTransaction() error {
|
||||
if c.state == InProgress || c.state == Starting {
|
||||
return ErrTransactInProgress
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartTransaction initializes the transaction options and advances the state machine.
|
||||
// It does not contact the server to start the transaction.
|
||||
func (c *Client) StartTransaction(opts *TransactionOptions) error {
|
||||
err := c.CheckStartTransaction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.IncrementTxnNumber()
|
||||
c.RetryingCommit = false
|
||||
|
||||
if opts != nil {
|
||||
c.CurrentRc = opts.ReadConcern
|
||||
c.CurrentRp = opts.ReadPreference
|
||||
c.CurrentWc = opts.WriteConcern
|
||||
}
|
||||
|
||||
if c.CurrentRc == nil {
|
||||
c.CurrentRc = c.transactionRc
|
||||
}
|
||||
|
||||
if c.CurrentRp == nil {
|
||||
c.CurrentRp = c.transactionRp
|
||||
}
|
||||
|
||||
if c.CurrentWc == nil {
|
||||
c.CurrentWc = c.transactionWc
|
||||
}
|
||||
|
||||
if !writeconcern.AckWrite(c.CurrentWc) {
|
||||
c.clearTransactionOpts()
|
||||
return ErrUnackWCUnsupported
|
||||
}
|
||||
|
||||
c.state = Starting
|
||||
c.PinnedServer = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckCommitTransaction checks to see if allowed to commit transaction and returns
|
||||
// an error if not allowed.
|
||||
func (c *Client) CheckCommitTransaction() error {
|
||||
if c.state == None {
|
||||
return ErrNoTransactStarted
|
||||
} else if c.state == Aborted {
|
||||
return ErrCommitAfterAbort
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitTransaction updates the state for a successfully committed transaction and returns
|
||||
// an error if not permissible. It does not actually perform the commit.
|
||||
func (c *Client) CommitTransaction() error {
|
||||
err := c.CheckCommitTransaction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.state = Committed
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCommitTransactionWriteConcern will set the write concern to majority and potentially set a
|
||||
// w timeout of 10 seconds. This should be called after a commit transaction operation fails with a
|
||||
// retryable error or after a successful commit transaction operation.
|
||||
func (c *Client) UpdateCommitTransactionWriteConcern() {
|
||||
wc := c.CurrentWc
|
||||
timeout := 10 * time.Second
|
||||
if wc != nil && wc.GetWTimeout() != 0 {
|
||||
timeout = wc.GetWTimeout()
|
||||
}
|
||||
c.CurrentWc = wc.WithOptions(writeconcern.WMajority(), writeconcern.WTimeout(timeout))
|
||||
}
|
||||
|
||||
// CheckAbortTransaction checks to see if allowed to abort transaction and returns
|
||||
// an error if not allowed.
|
||||
func (c *Client) CheckAbortTransaction() error {
|
||||
if c.state == None {
|
||||
return ErrNoTransactStarted
|
||||
} else if c.state == Committed {
|
||||
return ErrAbortAfterCommit
|
||||
} else if c.state == Aborted {
|
||||
return ErrAbortTwice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AbortTransaction updates the state for a successfully aborted transaction and returns
|
||||
// an error if not permissible. It does not actually perform the abort.
|
||||
func (c *Client) AbortTransaction() error {
|
||||
err := c.CheckAbortTransaction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.state = Aborted
|
||||
c.clearTransactionOpts()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyCommand advances the state machine upon command execution.
|
||||
func (c *Client) ApplyCommand(desc description.Server) {
|
||||
if c.Committing {
|
||||
// Do not change state if committing after already committed
|
||||
return
|
||||
}
|
||||
if c.state == Starting {
|
||||
c.state = InProgress
|
||||
// If this is in a transaction and the server is a mongos, pin it
|
||||
if desc.Kind == description.Mongos {
|
||||
c.PinnedServer = &desc
|
||||
}
|
||||
} else if c.state == Committed || c.state == Aborted {
|
||||
c.clearTransactionOpts()
|
||||
c.state = None
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) clearTransactionOpts() {
|
||||
c.RetryingCommit = false
|
||||
c.Aborting = false
|
||||
c.Committing = false
|
||||
c.CurrentWc = nil
|
||||
c.CurrentRp = nil
|
||||
c.CurrentRc = nil
|
||||
c.PinnedServer = nil
|
||||
c.RecoveryToken = nil
|
||||
}
|
||||
36
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/cluster_clock.go
generated
vendored
Executable file
36
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/cluster_clock.go
generated
vendored
Executable file
@@ -0,0 +1,36 @@
|
||||
// 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"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// ClusterClock represents a logical clock for keeping track of cluster time.
|
||||
type ClusterClock struct {
|
||||
clusterTime bson.Raw
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// GetClusterTime returns the cluster's current time.
|
||||
func (cc *ClusterClock) GetClusterTime() bson.Raw {
|
||||
var ct bson.Raw
|
||||
cc.lock.Lock()
|
||||
ct = cc.clusterTime
|
||||
cc.lock.Unlock()
|
||||
|
||||
return ct
|
||||
}
|
||||
|
||||
// AdvanceClusterTime updates the cluster's current time.
|
||||
func (cc *ClusterClock) AdvanceClusterTime(clusterTime bson.Raw) {
|
||||
cc.lock.Lock()
|
||||
cc.clusterTime = MaxClusterTime(cc.clusterTime, clusterTime)
|
||||
cc.lock.Unlock()
|
||||
}
|
||||
51
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/options.go
generated
vendored
Executable file
51
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/options.go
generated
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
// 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 (
|
||||
"go.mongodb.org/mongo-driver/mongo/readconcern"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
"go.mongodb.org/mongo-driver/mongo/writeconcern"
|
||||
)
|
||||
|
||||
// ClientOptions represents all possible options for creating a client session.
|
||||
type ClientOptions struct {
|
||||
CausalConsistency *bool
|
||||
DefaultReadConcern *readconcern.ReadConcern
|
||||
DefaultWriteConcern *writeconcern.WriteConcern
|
||||
DefaultReadPreference *readpref.ReadPref
|
||||
}
|
||||
|
||||
// TransactionOptions represents all possible options for starting a transaction in a session.
|
||||
type TransactionOptions struct {
|
||||
ReadConcern *readconcern.ReadConcern
|
||||
WriteConcern *writeconcern.WriteConcern
|
||||
ReadPreference *readpref.ReadPref
|
||||
}
|
||||
|
||||
func mergeClientOptions(opts ...*ClientOptions) *ClientOptions {
|
||||
c := &ClientOptions{}
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
if opt.CausalConsistency != nil {
|
||||
c.CausalConsistency = opt.CausalConsistency
|
||||
}
|
||||
if opt.DefaultReadConcern != nil {
|
||||
c.DefaultReadConcern = opt.DefaultReadConcern
|
||||
}
|
||||
if opt.DefaultReadPreference != nil {
|
||||
c.DefaultReadPreference = opt.DefaultReadPreference
|
||||
}
|
||||
if opt.DefaultWriteConcern != nil {
|
||||
c.DefaultWriteConcern = opt.DefaultWriteConcern
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
63
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/server_session.go
generated
vendored
Executable file
63
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/server_session.go
generated
vendored
Executable file
@@ -0,0 +1,63 @@
|
||||
// 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 (
|
||||
"time"
|
||||
|
||||
"crypto/rand"
|
||||
|
||||
"go.mongodb.org/mongo-driver/x/bsonx"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/uuid"
|
||||
)
|
||||
|
||||
var rander = rand.Reader
|
||||
|
||||
// Server is an open session with the server.
|
||||
type Server struct {
|
||||
SessionID bsonx.Doc
|
||||
TxnNumber int64
|
||||
LastUsed time.Time
|
||||
}
|
||||
|
||||
// returns whether or not a session has expired given a timeout in minutes
|
||||
// a session is considered expired if it has less than 1 minute left before becoming stale
|
||||
func (ss *Server) expired(timeoutMinutes uint32) bool {
|
||||
if timeoutMinutes <= 0 {
|
||||
return true
|
||||
}
|
||||
timeUnused := time.Since(ss.LastUsed).Minutes()
|
||||
return timeUnused > float64(timeoutMinutes-1)
|
||||
}
|
||||
|
||||
// update the last used time for this session.
|
||||
// must be called whenever this server session is used to send a command to the server.
|
||||
func (ss *Server) updateUseTime() {
|
||||
ss.LastUsed = time.Now()
|
||||
}
|
||||
|
||||
func newServerSession() (*Server, error) {
|
||||
id, err := uuid.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idDoc := bsonx.Doc{{"id", bsonx.Binary(UUIDSubtype, id[:])}}
|
||||
|
||||
return &Server{
|
||||
SessionID: idDoc,
|
||||
LastUsed: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IncrementTxnNumber increments the transaction number.
|
||||
func (ss *Server) IncrementTxnNumber() {
|
||||
ss.TxnNumber++
|
||||
}
|
||||
|
||||
// UUIDSubtype is the BSON binary subtype that a UUID should be encoded as
|
||||
const UUIDSubtype byte = 4
|
||||
175
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/session_pool.go
generated
vendored
Executable file
175
vendor/go.mongodb.org/mongo-driver/x/mongo/driver/session/session_pool.go
generated
vendored
Executable file
@@ -0,0 +1,175 @@
|
||||
// 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"
|
||||
|
||||
"go.mongodb.org/mongo-driver/x/bsonx"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/description"
|
||||
)
|
||||
|
||||
// Node represents a server session in a linked list
|
||||
type Node struct {
|
||||
*Server
|
||||
next *Node
|
||||
prev *Node
|
||||
}
|
||||
|
||||
// Pool is a pool of server sessions that can be reused.
|
||||
type Pool struct {
|
||||
descChan <-chan description.Topology
|
||||
head *Node
|
||||
tail *Node
|
||||
timeout uint32
|
||||
mutex sync.Mutex // mutex to protect list and sessionTimeout
|
||||
|
||||
checkedOut int // number of sessions checked out of pool
|
||||
}
|
||||
|
||||
func (p *Pool) createServerSession() (*Server, error) {
|
||||
s, err := newServerSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.checkedOut++
|
||||
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.timeout = 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.timeout) {
|
||||
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
|
||||
}
|
||||
|
||||
p.checkedOut++
|
||||
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()
|
||||
|
||||
p.checkedOut--
|
||||
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.timeout) {
|
||||
if p.tail.prev != nil {
|
||||
p.tail.prev.next = nil
|
||||
}
|
||||
p.tail = p.tail.prev
|
||||
}
|
||||
|
||||
// session expired
|
||||
if ss.expired(p.timeout) {
|
||||
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() []bsonx.Doc {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
ids := []bsonx.Doc{}
|
||||
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() int {
|
||||
return p.checkedOut
|
||||
}
|
||||
Reference in New Issue
Block a user