github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/mongo/session.go (about)

     1  // Copyright (C) MongoDB, Inc. 2017-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package mongo
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"time"
    13  
    14  	"go.mongodb.org/mongo-driver/bson"
    15  	"go.mongodb.org/mongo-driver/bson/primitive"
    16  	"go.mongodb.org/mongo-driver/internal"
    17  	"go.mongodb.org/mongo-driver/mongo/description"
    18  	"go.mongodb.org/mongo-driver/mongo/options"
    19  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    20  	"go.mongodb.org/mongo-driver/x/mongo/driver"
    21  	"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
    22  	"go.mongodb.org/mongo-driver/x/mongo/driver/session"
    23  )
    24  
    25  // ErrWrongClient is returned when a user attempts to pass in a session created by a different client than
    26  // the method call is using.
    27  var ErrWrongClient = errors.New("session was not created by this client")
    28  
    29  var withTransactionTimeout = 120 * time.Second
    30  
    31  // SessionContext combines the context.Context and mongo.Session interfaces. It should be used as the Context arguments
    32  // to operations that should be executed in a session.
    33  //
    34  // Implementations of SessionContext are not safe for concurrent use by multiple goroutines.
    35  //
    36  // There are two ways to create a SessionContext and use it in a session/transaction. The first is to use one of the
    37  // callback-based functions such as WithSession and UseSession. These functions create a SessionContext and pass it to
    38  // the provided callback. The other is to use NewSessionContext to explicitly create a SessionContext.
    39  type SessionContext interface {
    40  	context.Context
    41  	Session
    42  }
    43  
    44  type sessionContext struct {
    45  	context.Context
    46  	Session
    47  }
    48  
    49  type sessionKey struct {
    50  }
    51  
    52  // NewSessionContext creates a new SessionContext associated with the given Context and Session parameters.
    53  func NewSessionContext(ctx context.Context, sess Session) SessionContext {
    54  	return &sessionContext{
    55  		Context: context.WithValue(ctx, sessionKey{}, sess),
    56  		Session: sess,
    57  	}
    58  }
    59  
    60  // SessionFromContext extracts the mongo.Session object stored in a Context. This can be used on a SessionContext that
    61  // was created implicitly through one of the callback-based session APIs or explicitly by calling NewSessionContext. If
    62  // there is no Session stored in the provided Context, nil is returned.
    63  func SessionFromContext(ctx context.Context) Session {
    64  	val := ctx.Value(sessionKey{})
    65  	if val == nil {
    66  		return nil
    67  	}
    68  
    69  	sess, ok := val.(Session)
    70  	if !ok {
    71  		return nil
    72  	}
    73  
    74  	return sess
    75  }
    76  
    77  // Session is an interface that represents a MongoDB logical session. Sessions can be used to enable causal consistency
    78  // for a group of operations or to execute operations in an ACID transaction. A new Session can be created from a Client
    79  // instance. A Session created from a Client must only be used to execute operations using that Client or a Database or
    80  // Collection created from that Client. Custom implementations of this interface should not be used in production. For
    81  // more information about sessions, and their use cases, see
    82  // https://www.mongodb.com/docs/manual/reference/server-sessions/,
    83  // https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/#causal-consistency, and
    84  // https://www.mongodb.com/docs/manual/core/transactions/.
    85  //
    86  // Implementations of Session are not safe for concurrent use by multiple goroutines.
    87  type Session interface {
    88  	// StartTransaction starts a new transaction, configured with the given options, on this
    89  	// session. This method returns an error if there is already a transaction in-progress for this
    90  	// session.
    91  	StartTransaction(...*options.TransactionOptions) error
    92  
    93  	// AbortTransaction aborts the active transaction for this session. This method returns an error
    94  	// if there is no active transaction for this session or if the transaction has been committed
    95  	// or aborted.
    96  	AbortTransaction(context.Context) error
    97  
    98  	// CommitTransaction commits the active transaction for this session. This method returns an
    99  	// error if there is no active transaction for this session or if the transaction has been
   100  	// aborted.
   101  	CommitTransaction(context.Context) error
   102  
   103  	// WithTransaction starts a transaction on this session and runs the fn callback. Errors with
   104  	// the TransientTransactionError and UnknownTransactionCommitResult labels are retried for up to
   105  	// 120 seconds. Inside the callback, the SessionContext must be used as the Context parameter
   106  	// for any operations that should be part of the transaction. If the ctx parameter already has a
   107  	// Session attached to it, it will be replaced by this session. The fn callback may be run
   108  	// multiple times during WithTransaction due to retry attempts, so it must be idempotent.
   109  	// Non-retryable operation errors or any operation errors that occur after the timeout expires
   110  	// will be returned without retrying. If the callback fails, the driver will call
   111  	// AbortTransaction. Because this method must succeed to ensure that server-side resources are
   112  	// properly cleaned up, context deadlines and cancellations will not be respected during this
   113  	// call. For a usage example, see the Client.StartSession method documentation.
   114  	WithTransaction(ctx context.Context, fn func(ctx SessionContext) (interface{}, error),
   115  		opts ...*options.TransactionOptions) (interface{}, error)
   116  
   117  	// EndSession aborts any existing transactions and close the session.
   118  	EndSession(context.Context)
   119  
   120  	// ClusterTime returns the current cluster time document associated with the session.
   121  	ClusterTime() bson.Raw
   122  
   123  	// OperationTime returns the current operation time document associated with the session.
   124  	OperationTime() *primitive.Timestamp
   125  
   126  	// Client the Client associated with the session.
   127  	Client() *Client
   128  
   129  	// ID returns the current ID document associated with the session. The ID document is in the
   130  	// form {"id": <BSON binary value>}.
   131  	ID() bson.Raw
   132  
   133  	// AdvanceClusterTime advances the cluster time for a session. This method returns an error if
   134  	// the session has ended.
   135  	AdvanceClusterTime(bson.Raw) error
   136  
   137  	// AdvanceOperationTime advances the operation time for a session. This method returns an error
   138  	// if the session has ended.
   139  	AdvanceOperationTime(*primitive.Timestamp) error
   140  
   141  	session()
   142  }
   143  
   144  // XSession is an unstable interface for internal use only.
   145  //
   146  // Deprecated: This interface is unstable because it provides access to a session.Client object, which exists in the
   147  // "x" package. It should not be used by applications and may be changed or removed in any release.
   148  type XSession interface {
   149  	ClientSession() *session.Client
   150  }
   151  
   152  // sessionImpl represents a set of sequential operations executed by an application that are related in some way.
   153  type sessionImpl struct {
   154  	clientSession       *session.Client
   155  	client              *Client
   156  	deployment          driver.Deployment
   157  	didCommitAfterStart bool // true if commit was called after start with no other operations
   158  }
   159  
   160  var _ Session = &sessionImpl{}
   161  var _ XSession = &sessionImpl{}
   162  
   163  // ClientSession implements the XSession interface.
   164  func (s *sessionImpl) ClientSession() *session.Client {
   165  	return s.clientSession
   166  }
   167  
   168  // ID implements the Session interface.
   169  func (s *sessionImpl) ID() bson.Raw {
   170  	return bson.Raw(s.clientSession.SessionID)
   171  }
   172  
   173  // EndSession implements the Session interface.
   174  func (s *sessionImpl) EndSession(ctx context.Context) {
   175  	if s.clientSession.TransactionInProgress() {
   176  		// ignore all errors aborting during an end session
   177  		_ = s.AbortTransaction(ctx)
   178  	}
   179  	s.clientSession.EndSession()
   180  }
   181  
   182  // WithTransaction implements the Session interface.
   183  func (s *sessionImpl) WithTransaction(ctx context.Context, fn func(ctx SessionContext) (interface{}, error),
   184  	opts ...*options.TransactionOptions) (interface{}, error) {
   185  	timeout := time.NewTimer(withTransactionTimeout)
   186  	defer timeout.Stop()
   187  	var err error
   188  	for {
   189  		err = s.StartTransaction(opts...)
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  
   194  		res, err := fn(NewSessionContext(ctx, s))
   195  		if err != nil {
   196  			if s.clientSession.TransactionRunning() {
   197  				// Wrap the user-provided Context in a new one that behaves like context.Background() for deadlines and
   198  				// cancellations, but forwards Value requests to the original one.
   199  				_ = s.AbortTransaction(internal.NewBackgroundContext(ctx))
   200  			}
   201  
   202  			select {
   203  			case <-timeout.C:
   204  				return nil, err
   205  			default:
   206  			}
   207  
   208  			if errorHasLabel(err, driver.TransientTransactionError) {
   209  				continue
   210  			}
   211  			return res, err
   212  		}
   213  
   214  		// Check if callback intentionally aborted and, if so, return immediately
   215  		// with no error.
   216  		err = s.clientSession.CheckAbortTransaction()
   217  		if err != nil {
   218  			return res, nil
   219  		}
   220  
   221  		// If context has errored, run AbortTransaction and return, as the CommitLoop
   222  		// has no chance of succeeding.
   223  		//
   224  		// Aborting after a failed CommitTransaction is dangerous. Failed transaction
   225  		// commits may unpin the session server-side, and subsequent transaction aborts
   226  		// may run on a new mongos which could end up with commit and abort being executed
   227  		// simultaneously.
   228  		if ctx.Err() != nil {
   229  			// Wrap the user-provided Context in a new one that behaves like context.Background() for deadlines and
   230  			// cancellations, but forwards Value requests to the original one.
   231  			_ = s.AbortTransaction(internal.NewBackgroundContext(ctx))
   232  			return nil, ctx.Err()
   233  		}
   234  
   235  	CommitLoop:
   236  		for {
   237  			err = s.CommitTransaction(ctx)
   238  			// End when error is nil, as transaction has been committed.
   239  			if err == nil {
   240  				return res, nil
   241  			}
   242  
   243  			select {
   244  			case <-timeout.C:
   245  				return res, err
   246  			default:
   247  			}
   248  
   249  			if cerr, ok := err.(CommandError); ok {
   250  				if cerr.HasErrorLabel(driver.UnknownTransactionCommitResult) && !cerr.IsMaxTimeMSExpiredError() {
   251  					continue
   252  				}
   253  				if cerr.HasErrorLabel(driver.TransientTransactionError) {
   254  					break CommitLoop
   255  				}
   256  			}
   257  			return res, err
   258  		}
   259  	}
   260  }
   261  
   262  // StartTransaction implements the Session interface.
   263  func (s *sessionImpl) StartTransaction(opts ...*options.TransactionOptions) error {
   264  	err := s.clientSession.CheckStartTransaction()
   265  	if err != nil {
   266  		return err
   267  	}
   268  
   269  	s.didCommitAfterStart = false
   270  
   271  	topts := options.MergeTransactionOptions(opts...)
   272  	coreOpts := &session.TransactionOptions{
   273  		ReadConcern:    topts.ReadConcern,
   274  		ReadPreference: topts.ReadPreference,
   275  		WriteConcern:   topts.WriteConcern,
   276  		MaxCommitTime:  topts.MaxCommitTime,
   277  	}
   278  
   279  	return s.clientSession.StartTransaction(coreOpts)
   280  }
   281  
   282  // AbortTransaction implements the Session interface.
   283  func (s *sessionImpl) AbortTransaction(ctx context.Context) error {
   284  	err := s.clientSession.CheckAbortTransaction()
   285  	if err != nil {
   286  		return err
   287  	}
   288  
   289  	// Do not run the abort command if the transaction is in starting state
   290  	if s.clientSession.TransactionStarting() || s.didCommitAfterStart {
   291  		return s.clientSession.AbortTransaction()
   292  	}
   293  
   294  	selector := makePinnedSelector(s.clientSession, description.WriteSelector())
   295  
   296  	s.clientSession.Aborting = true
   297  	_ = operation.NewAbortTransaction().Session(s.clientSession).ClusterClock(s.client.clock).Database("admin").
   298  		Deployment(s.deployment).WriteConcern(s.clientSession.CurrentWc).ServerSelector(selector).
   299  		Retry(driver.RetryOncePerCommand).CommandMonitor(s.client.monitor).
   300  		RecoveryToken(bsoncore.Document(s.clientSession.RecoveryToken)).ServerAPI(s.client.serverAPI).Execute(ctx)
   301  
   302  	s.clientSession.Aborting = false
   303  	_ = s.clientSession.AbortTransaction()
   304  
   305  	return nil
   306  }
   307  
   308  // CommitTransaction implements the Session interface.
   309  func (s *sessionImpl) CommitTransaction(ctx context.Context) error {
   310  	err := s.clientSession.CheckCommitTransaction()
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	// Do not run the commit command if the transaction is in started state
   316  	if s.clientSession.TransactionStarting() || s.didCommitAfterStart {
   317  		s.didCommitAfterStart = true
   318  		return s.clientSession.CommitTransaction()
   319  	}
   320  
   321  	if s.clientSession.TransactionCommitted() {
   322  		s.clientSession.RetryingCommit = true
   323  	}
   324  
   325  	selector := makePinnedSelector(s.clientSession, description.WriteSelector())
   326  
   327  	s.clientSession.Committing = true
   328  	op := operation.NewCommitTransaction().
   329  		Session(s.clientSession).ClusterClock(s.client.clock).Database("admin").Deployment(s.deployment).
   330  		WriteConcern(s.clientSession.CurrentWc).ServerSelector(selector).Retry(driver.RetryOncePerCommand).
   331  		CommandMonitor(s.client.monitor).RecoveryToken(bsoncore.Document(s.clientSession.RecoveryToken)).
   332  		ServerAPI(s.client.serverAPI).MaxTime(s.clientSession.CurrentMct)
   333  
   334  	err = op.Execute(ctx)
   335  	// Return error without updating transaction state if it is a timeout, as the transaction has not
   336  	// actually been committed.
   337  	if IsTimeout(err) {
   338  		return replaceErrors(err)
   339  	}
   340  	s.clientSession.Committing = false
   341  	commitErr := s.clientSession.CommitTransaction()
   342  
   343  	// We set the write concern to majority for subsequent calls to CommitTransaction.
   344  	s.clientSession.UpdateCommitTransactionWriteConcern()
   345  
   346  	if err != nil {
   347  		return replaceErrors(err)
   348  	}
   349  	return commitErr
   350  }
   351  
   352  // ClusterTime implements the Session interface.
   353  func (s *sessionImpl) ClusterTime() bson.Raw {
   354  	return s.clientSession.ClusterTime
   355  }
   356  
   357  // AdvanceClusterTime implements the Session interface.
   358  func (s *sessionImpl) AdvanceClusterTime(d bson.Raw) error {
   359  	return s.clientSession.AdvanceClusterTime(d)
   360  }
   361  
   362  // OperationTime implements the Session interface.
   363  func (s *sessionImpl) OperationTime() *primitive.Timestamp {
   364  	return s.clientSession.OperationTime
   365  }
   366  
   367  // AdvanceOperationTime implements the Session interface.
   368  func (s *sessionImpl) AdvanceOperationTime(ts *primitive.Timestamp) error {
   369  	return s.clientSession.AdvanceOperationTime(ts)
   370  }
   371  
   372  // Client implements the Session interface.
   373  func (s *sessionImpl) Client() *Client {
   374  	return s.client
   375  }
   376  
   377  // session implements the Session interface.
   378  func (*sessionImpl) session() {
   379  }
   380  
   381  // sessionFromContext checks for a sessionImpl in the argued context and returns the session if it
   382  // exists
   383  func sessionFromContext(ctx context.Context) *session.Client {
   384  	s := ctx.Value(sessionKey{})
   385  	if ses, ok := s.(*sessionImpl); ses != nil && ok {
   386  		return ses.clientSession
   387  	}
   388  
   389  	return nil
   390  }