Skip to content

Commit 820fdb2

Browse files
author
Divjot Arora
authored
GODRIVER-1380 Document Session and provide GoDoc examples (#227)
1 parent 439cf68 commit 820fdb2

File tree

3 files changed

+177
-47
lines changed

3 files changed

+177
-47
lines changed

mongo/client.go

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ func (c *Client) Ping(ctx context.Context, rp *readpref.ReadPref) error {
237237
}
238238

239239
// StartSession starts a new session configured with the given options.
240+
//
241+
// If the DefaultReadConcern, DefaultWriteConcern, or DefaultReadPreference options are not set, the client's read
242+
// concern, write concern, or read preference will be used, respectively.
240243
func (c *Client) StartSession(opts ...*options.SessionOptions) (Session, error) {
241244
if c.sessionPool == nil {
242245
return nil, ErrClientDisconnected
@@ -734,38 +737,30 @@ func (c *Client) ListDatabaseNames(ctx context.Context, filter interface{}, opts
734737
return names, nil
735738
}
736739

737-
// WithSession allows a user to start a session themselves and manage
738-
// its lifetime. The only way to provide a session to a CRUD method is
739-
// to invoke that CRUD method with the mongo.SessionContext within the
740-
// closure. The mongo.SessionContext can be used as a regular context,
741-
// so methods like context.WithDeadline and context.WithTimeout are
742-
// supported.
740+
// WithSession creates a new SessionContext from the ctx and sess parameters and uses it to call the fn callback. The
741+
// SessionContext must be used as the Context parameter for any operations in the fn callback that should be executed
742+
// under the session.
743743
//
744-
// If the context.Context already has a mongo.Session attached, that
745-
// mongo.Session will be replaced with the one provided.
744+
// If the ctx parameter already contains a Session, that Session will be replaced with the one provided.
746745
//
747-
// Errors returned from the closure are transparently returned from
748-
// this function.
746+
// Any error returned by the fn callback will be returned without any modifications.
749747
func WithSession(ctx context.Context, sess Session, fn func(SessionContext) error) error {
750748
return fn(contextWithSession(ctx, sess))
751749
}
752750

753-
// UseSession creates a new session that is only valid for the
754-
// lifetime of the fn callback. No cleanup outside of closing the session
755-
// is done upon exiting the closure. This means that an outstanding
756-
// transaction will be aborted, even if the closure returns an error.
751+
// UseSession creates a new Session and uses it to create a new SessionContext, which is used to call the fn callback.
752+
// The SessionContext parameter must be used as the Context parameter for any operations in the fn callback that should
753+
// be executed under a session. After the callback returns, the created Session is ended, meaning that any in-progress
754+
// transactions started by fn will be aborted even if fn returns an error.
757755
//
758-
// If ctx already contains a mongo.Session, that mongo.Session will be
759-
// replaced with the newly created one.
756+
// If the ctx parameter already contains a Session, that Session will be replaced with the newly created one.
760757
//
761-
// Errors returned from the closure are transparently returned from
762-
// this method.
758+
// Any error returned by the fn callback will be returned without any modifications.
763759
func (c *Client) UseSession(ctx context.Context, fn func(SessionContext) error) error {
764760
return c.UseSessionWithOptions(ctx, options.Session(), fn)
765761
}
766762

767-
// UseSessionWithOptions works like UseSession but allows the caller
768-
// to specify the options used to create the session.
763+
// UseSessionWithOptions operates like UseSession but uses the given SessionOptions to create the Session.
769764
func (c *Client) UseSessionWithOptions(ctx context.Context, opts *options.SessionOptions, fn func(SessionContext) error) error {
770765
defaultSess, err := c.StartSession(opts)
771766
if err != nil {

mongo/crud_examples_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"go.mongodb.org/mongo-driver/bson/primitive"
1717
"go.mongodb.org/mongo-driver/mongo"
1818
"go.mongodb.org/mongo-driver/mongo/options"
19+
"go.mongodb.org/mongo-driver/mongo/readconcern"
1920
"go.mongodb.org/mongo-driver/mongo/readpref"
2021
)
2122

@@ -451,3 +452,107 @@ func ExampleCollection_Watch() {
451452
fmt.Println(changeStream.Current)
452453
}
453454
}
455+
456+
// Session examples
457+
458+
func ExampleWithSession() {
459+
var client *mongo.Client // assume client is configured with write concern majority and read preference primary
460+
461+
// Specify the DefaultReadConcern option so any transactions started through the session will have read concern
462+
// majority.
463+
// The DefaultReadPreference and DefaultWriteConcern options aren't specified so they will be inheritied from client
464+
// and be set to primary and majority, respectively.
465+
opts := options.Session().SetDefaultReadConcern(readconcern.Majority())
466+
sess, err := client.StartSession(opts)
467+
if err != nil {
468+
log.Fatal(err)
469+
}
470+
defer sess.EndSession(context.TODO())
471+
472+
// Call WithSession to use the new Session to insert a document and find it.
473+
err = mongo.WithSession(context.TODO(), sess, func(sessCtx mongo.SessionContext) error {
474+
// Use sessCtx as the Context parameter for InsertOne and FindOne so both operations are run under the new
475+
// Session.
476+
477+
coll := client.Database("db").Collection("coll")
478+
res, err := coll.InsertOne(sessCtx, bson.D{{"x", 1}})
479+
if err != nil {
480+
return err
481+
}
482+
483+
var result bson.M
484+
if err = coll.FindOne(sessCtx, bson.D{{"_id", res.InsertedID}}).Decode(result); err != nil {
485+
return err
486+
}
487+
fmt.Println(result)
488+
return nil
489+
})
490+
}
491+
492+
func ExampleClient_UseSessionWithOptions() {
493+
var client *mongo.Client
494+
495+
// Specify the DefaultReadConcern option so any transactions started through the session will have read concern
496+
// majority.
497+
// The DefaultReadPreference and DefaultWriteConcern options aren't specified so they will be inheritied from client
498+
// and be set to primary and majority, respectively.
499+
opts := options.Session().SetDefaultReadConcern(readconcern.Majority())
500+
err := client.UseSessionWithOptions(context.TODO(), opts, func(sessCtx mongo.SessionContext) error {
501+
// Use sessCtx as the Context parameter for InsertOne and FindOne so both operations are run under the new
502+
// Session.
503+
504+
coll := client.Database("db").Collection("coll")
505+
res, err := coll.InsertOne(sessCtx, bson.D{{"x", 1}})
506+
if err != nil {
507+
return err
508+
}
509+
510+
var result bson.M
511+
if err = coll.FindOne(sessCtx, bson.D{{"_id", res.InsertedID}}).Decode(result); err != nil {
512+
return err
513+
}
514+
fmt.Println(result)
515+
return nil
516+
})
517+
if err != nil {
518+
log.Fatal(err)
519+
}
520+
}
521+
522+
func ExampleClient_StartSession_withTransaction() {
523+
var client *mongo.Client // assume client is configured with write concern majority and read preference primary
524+
525+
// Specify the DefaultReadConcern option so any transactions started through the session will have read concern
526+
// majority.
527+
// The DefaultReadPreference and DefaultWriteConcern options aren't specified so they will be inheritied from client
528+
// and be set to primary and majority, respectively.
529+
opts := options.Session().SetDefaultReadConcern(readconcern.Majority())
530+
sess, err := client.StartSession(opts)
531+
if err != nil {
532+
log.Fatal(err)
533+
}
534+
defer sess.EndSession(context.TODO())
535+
536+
// Specify the ReadPreference option to set the read preference to primary preferred for this transaction.
537+
txnOpts := options.Transaction().SetReadPreference(readpref.PrimaryPreferred())
538+
result, err := sess.WithTransaction(context.TODO(), func(sessCtx mongo.SessionContext) (interface{}, error) {
539+
// Use sessCtx as the Context parameter for InsertOne and FindOne so both operations are run in a
540+
// transaction.
541+
542+
coll := client.Database("db").Collection("coll")
543+
res, err := coll.InsertOne(sessCtx, bson.D{{"x", 1}})
544+
if err != nil {
545+
return nil, err
546+
}
547+
548+
var result bson.M
549+
if err = coll.FindOne(sessCtx, bson.D{{"_id", res.InsertedID}}).Decode(result); err != nil {
550+
return nil, err
551+
}
552+
return result, err
553+
}, txnOpts)
554+
if err != nil {
555+
log.Fatal(err)
556+
}
557+
fmt.Printf("result: %v\n", result)
558+
}

mongo/session.go

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ var ErrWrongClient = errors.New("session was not created by this client")
2828

2929
var withTransactionTimeout = 120 * time.Second
3030

31-
// SessionContext is a hybrid interface. It combines a context.Context with
32-
// a mongo.Session. This type can be used as a regular context.Context or
33-
// Session type. It is not goroutine safe and should not be used in multiple goroutines concurrently.
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. This type is not goroutine safe and must not be used concurrently
33+
// by multiple goroutines.
3434
type SessionContext interface {
3535
context.Context
3636
Session
@@ -44,20 +44,51 @@ type sessionContext struct {
4444
type sessionKey struct {
4545
}
4646

47-
// Session is the interface that represents a sequential set of operations executed.
48-
// Instances of this interface can be used to use transactions against the server
49-
// and to enable causally consistent behavior for applications.
47+
// Session is an interface that represents a MongoDB logical session. Sessions can be used to enable causal consistency
48+
// for a group of operations or to execute operations in an ACID transaction. A new Session can be created from a Client
49+
// instance. A Session created from a Client must only be used to execute operations using that Client or a Database or
50+
// Collection created from that Client. Custom implementations of this interface should not be used in production. For
51+
// more information about sessions, and their use cases, see https://docs.mongodb.com/manual/reference/server-sessions/,
52+
// https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#causal-consistency, and
53+
// https://docs.mongodb.com/manual/core/transactions/.
54+
//
55+
// StartTransaction starts a new transaction, configured with the given options, on this session. This method will
56+
// return an error if there is already a transaction in-progress for this session.
57+
//
58+
// CommitTransaction commits the active transaction for this session. This method will return an error if there is no
59+
// active transaction for this session or the transaction has been aborted.
60+
//
61+
// AbortTransaction aborts the active transaction for this session. This method will return an error if there is no
62+
// active transaction for this session or the transaction has been committed or aborted.
63+
//
64+
// WithTransaction starts a transaction on this session and runs the fn callback. Errors with the
65+
// TransientTransactionError and UnknownTransactionCommitResult labels are retried for up to 120 seconds. Inside the
66+
// callback, sessCtx must be used as the Context parameter for any operations that should be part of the transaction. If
67+
// the ctx parameter already has a Session attached to it, it will be replaced by this session. The fn callback may be
68+
// run multiple times during WithTransaction due to retry attempts, so it must be idempotent. Non-retryable operation
69+
// errors or any operation errors that occur after the timeout expires will be returned without retrying. For a usage
70+
// example, see the Client.StartSession method documentation.
71+
//
72+
// ClusterTime, OperationTime, and Client return the session's current operation time, the session's current cluster
73+
// time, and the Client associated with the session, respectively.
74+
//
75+
// EndSession method should abort any existing transactions and close the session.
76+
//
77+
// AdvanceClusterTime and AdvanceOperationTime are for internal use only and must not be called.
5078
type Session interface {
51-
EndSession(context.Context)
52-
WithTransaction(ctx context.Context, fn func(sessCtx SessionContext) (interface{}, error), opts ...*options.TransactionOptions) (interface{}, error)
5379
StartTransaction(...*options.TransactionOptions) error
5480
AbortTransaction(context.Context) error
5581
CommitTransaction(context.Context) error
82+
WithTransaction(ctx context.Context, fn func(sessCtx SessionContext) (interface{}, error),
83+
opts ...*options.TransactionOptions) (interface{}, error)
5684
ClusterTime() bson.Raw
57-
AdvanceClusterTime(bson.Raw) error
5885
OperationTime() *primitive.Timestamp
59-
AdvanceOperationTime(*primitive.Timestamp) error
6086
Client() *Client
87+
EndSession(context.Context)
88+
89+
AdvanceClusterTime(bson.Raw) error
90+
AdvanceOperationTime(*primitive.Timestamp) error
91+
6192
session()
6293
}
6394

@@ -89,7 +120,7 @@ func (s *sessionImpl) ID() bsonx.Doc {
89120
return s.clientSession.SessionID
90121
}
91122

92-
// EndSession ends the session.
123+
// EndSession implements the Session interface.
93124
func (s *sessionImpl) EndSession(ctx context.Context) {
94125
if s.clientSession.TransactionInProgress() {
95126
// ignore all errors aborting during an end session
@@ -98,18 +129,9 @@ func (s *sessionImpl) EndSession(ctx context.Context) {
98129
s.clientSession.EndSession()
99130
}
100131

101-
// WithTransaction creates a transaction on this session and runs the given callback, retrying for
102-
// TransientTransactionError and UnknownTransactionCommitResult errors. The only way to provide a
103-
// session to a CRUD method is to invoke that CRUD method with the mongo.SessionContext within the
104-
// callback. The mongo.SessionContext can be used as a regular context, so methods like
105-
// context.WithDeadline and context.WithTimeout are supported.
106-
//
107-
// If the context.Context already has a mongo.Session attached, that mongo.Session will be replaced
108-
// with the one provided.
109-
//
110-
// The callback may be run multiple times due to retry attempts. Non-retryable and timed out errors
111-
// are returned from this function.
112-
func (s *sessionImpl) WithTransaction(ctx context.Context, fn func(sessCtx SessionContext) (interface{}, error), opts ...*options.TransactionOptions) (interface{}, error) {
132+
// WithTransaction implements the Session interface.
133+
func (s *sessionImpl) WithTransaction(ctx context.Context, fn func(sessCtx SessionContext) (interface{}, error),
134+
opts ...*options.TransactionOptions) (interface{}, error) {
113135
timeout := time.NewTimer(withTransactionTimeout)
114136
defer timeout.Stop()
115137
var err error
@@ -170,7 +192,7 @@ func (s *sessionImpl) WithTransaction(ctx context.Context, fn func(sessCtx Sessi
170192
}
171193
}
172194

173-
// StartTransaction starts a transaction for this session.
195+
// StartTransaction implements the Session interface.
174196
func (s *sessionImpl) StartTransaction(opts ...*options.TransactionOptions) error {
175197
err := s.clientSession.CheckStartTransaction()
176198
if err != nil {
@@ -190,7 +212,7 @@ func (s *sessionImpl) StartTransaction(opts ...*options.TransactionOptions) erro
190212
return s.clientSession.StartTransaction(coreOpts)
191213
}
192214

193-
// AbortTransaction aborts the session's transaction, returning any errors and error codes
215+
// AbortTransaction implements the Session interface.
194216
func (s *sessionImpl) AbortTransaction(ctx context.Context) error {
195217
err := s.clientSession.CheckAbortTransaction()
196218
if err != nil {
@@ -207,15 +229,16 @@ func (s *sessionImpl) AbortTransaction(ctx context.Context) error {
207229
s.clientSession.Aborting = true
208230
_ = operation.NewAbortTransaction().Session(s.clientSession).ClusterClock(s.client.clock).Database("admin").
209231
Deployment(s.deployment).WriteConcern(s.clientSession.CurrentWc).ServerSelector(selector).
210-
Retry(driver.RetryOncePerCommand).CommandMonitor(s.client.monitor).RecoveryToken(bsoncore.Document(s.clientSession.RecoveryToken)).Execute(ctx)
232+
Retry(driver.RetryOncePerCommand).CommandMonitor(s.client.monitor).
233+
RecoveryToken(bsoncore.Document(s.clientSession.RecoveryToken)).Execute(ctx)
211234

212235
s.clientSession.Aborting = false
213236
_ = s.clientSession.AbortTransaction()
214237

215238
return nil
216239
}
217240

218-
// CommitTransaction commits the sesson's transaction.
241+
// CommitTransaction implements the Session interface.
219242
func (s *sessionImpl) CommitTransaction(ctx context.Context) error {
220243
err := s.clientSession.CheckCommitTransaction()
221244
if err != nil {
@@ -256,26 +279,32 @@ func (s *sessionImpl) CommitTransaction(ctx context.Context) error {
256279
return commitErr
257280
}
258281

282+
// ClusterTime implements the Session interface.
259283
func (s *sessionImpl) ClusterTime() bson.Raw {
260284
return s.clientSession.ClusterTime
261285
}
262286

287+
// AdvanceClusterTime implements the Session interface.
263288
func (s *sessionImpl) AdvanceClusterTime(d bson.Raw) error {
264289
return s.clientSession.AdvanceClusterTime(d)
265290
}
266291

292+
// OperationTime implements the Session interface.
267293
func (s *sessionImpl) OperationTime() *primitive.Timestamp {
268294
return s.clientSession.OperationTime
269295
}
270296

297+
// AdvanceOperationTime implements the Session interface.
271298
func (s *sessionImpl) AdvanceOperationTime(ts *primitive.Timestamp) error {
272299
return s.clientSession.AdvanceOperationTime(ts)
273300
}
274301

302+
// Client implements the Session interface.
275303
func (s *sessionImpl) Client() *Client {
276304
return s.client
277305
}
278306

307+
// session implements the Session interface.
279308
func (*sessionImpl) session() {
280309
}
281310

@@ -290,6 +319,7 @@ func sessionFromContext(ctx context.Context) *session.Client {
290319
return nil
291320
}
292321

322+
// contextWithSession creates a new SessionContext associated with the given Context and Session parameters.
293323
func contextWithSession(ctx context.Context, sess Session) SessionContext {
294324
return &sessionContext{
295325
Context: context.WithValue(ctx, sessionKey{}, sess),

0 commit comments

Comments
 (0)