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 }