Skip to content

Conversation

@prestonvasquez
Copy link
Member

@prestonvasquez prestonvasquez commented Jan 11, 2024

GODRIVER-2348

Summary

The PR attempts to make CSOT feature-gated behavior the default by dropping support for legacy timeout behavior.

Workflow Changes

Remove Contexts From Structs

Putting contexts on structs is an antipattern and makes unifying and creating context timeouts difficult due to the requirement of maintaining them as state (e.g. how do you ensure you never overwrite the context while use in different methods). This PR proposes replacing this practice with the following pattern:

func (s someStruct) do(ctx context.Context) {
	ctx, cancel := context.WithCancel(ctx)

	done := make(chan struct{}) 
	defer close(done) // cancel the context if check conmpletes w/o cancelation signal.

	go func() {
		defer cancel()

		select {
		case <-s.cancel:
		case <-done:
		}
	}()

	...
}

func (s someStruct) close() {
	s.cancelOnce.Do(func() {
		s.cancel <- struct{}{}
	})

	...
}

We still need some ability to cancel contexts passed to blocking operations outside of the go routine they are initiated in. So some state will need to be maintained on the initiating object. In this case, a cancelation channel and a sync.Once object.

Unify Timeout Logic

This PR proposes renaming csot.MakeTimeoutContext to csot.WithTimeout. Additionally, this update removes the need to create blocks like the following in operaiton execution, watching a change stream, and perform CRUD operations in GridFS:

if _, deadlineSet := ctx.Deadline(); !deadlineSet && b.db.Client().Timeout() != nil && !csot.IsTimeoutContext(ctx) {
	newCtx, cancelFunc := csot.MakeTimeoutContext(ctx, *b.db.Client().Timeout())
	// Redefine ctx to be the new timeout-derived context.
	ctx = newCtx
	// Cancel the timeout-derived context at the end of Execute to avoid a context leak.
	defer cancelFunc()
}

Rename maxTimeMS for cursor to maxAwaitTimeMS

There is still a need to maintain the maxTimeMS logic for tailable awaitData cursors. This PR proposes changing the code symbol from MaxTime to MaxAwaitTime to match the operation options (e.g. FindOptions.MaxAwaitTime) and the CSOT specifications. This logic has been deferred to the operation-level via Operation.DefaultWTimeout.

CSOT-Specific Changes

Write Concern

This PR proposes removing the WTimeout field from the WriteConcern object. It is worth noting that there is still a use case for sending wire messages with the wtimeout option set. Specifically, while committing a transaction:

if the modified write concern does not include a wtimeout value, drivers MUST also apply wtimeout: 10000 to the write concern in order to avoid waiting forever (or until a socket timeout) if the majority write concern cannot be satisfied.

The Go Driver organizes this logic by maintaining a copy of the txn write concern on the client session. In this case, given a CSOT, the remainder of that time is used for the value of wtimeout rather than the 10000 ms default.

Server Selection

client-side-operations-timeout/client-side-operations-timeout.md#server-selection

The server selection portion of the specification requires a comparison between timeoutMS and serverSelectionTimeoutMS. It also requires that the remaining timeout be passed to any followup operation establishing or checking out a connection:

ctx, cancel := csot.WithServerSelectionTimeout()
defer cancel()

server := SelectServer(ctx)
conn := server.Connection(ctx)

This workflow proposes building the context in a way that is decoupled from SelectServer and server.Connection.

Because server selection is made through a topology.Deployment interface, there is no obvious way to extrapolate the serverSelectionTimeoutMS for use by WithServerSelectionTimeout. Practically, the only implementation of topology.Deployment that accesses the SSTO data form options.ClientOptions is topology.Topology and so we could assert that implementation before calculating the timeout. However, for extensibility the current proposal is to use an interface:

type ServerSelectionTimeoutGetter struct {
	GetServerSelectionTimeout() time.Duration
}

func WithServerSelectionTimeout(
	parent context.Context, 
	serverSelectionTimeout time.Duration,
) (context.Context, context.CancelFunc) {
	...
}

func main() {
	dep := newDeployment()

	ctx, cancel := WithServerSelectionTimeout(context.Background(), dep.GetServerSelectionTimeout())
	defer cancel()

	server := SelectServer(ctx)
	conn := server.Connection(ctx)
	
	...
}

This would extend the topology.Deployment interface with the addition function GetServerSelectionTimeout().

Command Execution

client-side-operations-timeout/client-side-operations-timeout.md#command-execution

The original implementation of CSOT did not apply a context deadline to the maxTimeMS value. See here for the original logic. This PR proposes the following workflow to be spec compliant:

Screenshot 2024-02-20 at 8 42 22 PM

Change Streams

client-side-operations-timeout/client-side-operations-timeout.md#change-streams

GridFS API

client-side-operations-timeout/client-side-operations-timeout.md#gridfs-api

The specifications note that all methods in the GridFS bucket API must support the timeoutMS option. It's worth pointing out that all blocking operations within the scope of the database used to construct a GridFS bucket will inherit the client-level timeout value.

Additionally, the specifications require that the timeoutMS option cap the lifetime of the entire stream. The Go Driver currently only ensures this is true if a deadline is set on a context. If relying on the client-level timeout, this value is "refreshed" with every new blocking operation.

Sessions

client-side-operations-timeout/client-side-operations-timeout.md#convenient-transaction-api

Propose implementing the convenient transaction api requirements in GODRIVER-2467 , since the context behavior for convenience transactions are still being debated.

Background & Motivation

v1 required deprecating a set of legacy timeout options, gating their use behind a CSOT to avoid making a backwards breaking change. For v2, we can remove these legacy timeouts altogether.

@prestonvasquez prestonvasquez changed the base branch from v1 to master January 11, 2024 02:24
@mongodb-drivers-pr-bot
Copy link
Contributor

mongodb-drivers-pr-bot bot commented Jan 16, 2024

API Change Report

./mongo

incompatible changes

(*Cursor).SetMaxTime: removed

compatible changes

(*Cursor).SetMaxAwaitTime: added

./mongo/options

incompatible changes

(*AggregateOptions).SetMaxTime: removed
(*ClientOptions).SetSocketTimeout: removed
(*CountOptions).SetMaxTime: removed
(*CreateIndexesOptions).SetMaxTime: removed
(*DistinctOptions).SetMaxTime: removed
(*DropIndexesOptions).SetMaxTime: removed
(*EstimatedDocumentCountOptions).SetMaxTime: removed
(*FindOneAndDeleteOptions).SetMaxTime: removed
(*FindOneAndReplaceOptions).SetMaxTime: removed
(*FindOneAndUpdateOptions).SetMaxTime: removed
(*FindOneOptions).SetMaxTime: removed
(*FindOptions).SetMaxTime: removed
(*GridFSFindOptions).SetMaxTime: removed
(*ListIndexesOptions).SetMaxTime: removed
(*SessionOptions).SetDefaultMaxCommitTime: removed
(*TransactionOptions).SetMaxCommitTime: removed
AggregateOptions.MaxTime: removed
ClientOptions.SocketTimeout: removed
CountOptions.MaxTime: removed
CreateIndexesOptions.MaxTime: removed
DistinctOptions.MaxTime: removed
DropIndexesOptions.MaxTime: removed
EstimatedDocumentCountOptions.MaxTime: removed
FindOneAndDeleteOptions.MaxTime: removed
FindOneAndReplaceOptions.MaxTime: removed
FindOneAndUpdateOptions.MaxTime: removed
FindOneOptions.MaxTime: removed
FindOptions.MaxTime: removed
GridFSFindOptions.MaxTime: removed
ListIndexesOptions.MaxTime: removed
SessionOptions.DefaultMaxCommitTime: removed
TransactionOptions.MaxCommitTime: removed

./mongo/writeconcern

incompatible changes

WriteConcern.WTimeout: removed

./x/mongo/driver

incompatible changes

(*BatchCursor).SetMaxTime: removed
CursorOptions.MaxTimeMS: removed
Deployment.GetServerSelectionTimeout: added
ErrNegativeMaxTime: removed
Operation.MaxTime: removed

compatible changes

(*BatchCursor).SetMaxAwaitTime: added
(*CursorOptions).SetMaxAwaitTime: added
CursorOptions.MaxAwaitTime: added
Operation.OmitMaxTimeMS: added
SingleConnectionDeployment.GetServerSelectionTimeout: added
SingleServerDeployment.GetServerSelectionTimeout: added

./x/mongo/driver/connstring

incompatible changes

ConnString.WTimeout: removed
ConnString.WTimeoutSet: removed
ConnString.WTimeoutSetFromOption: removed

./x/mongo/driver/operation

incompatible changes

(*Aggregate).MaxTime: removed
(*CommitTransaction).MaxTime: removed
(*Count).MaxTime: removed
(*CreateIndexes).MaxTime: removed
(*Distinct).MaxTime: removed
(*DropIndexes).MaxTime: removed
(*Find).MaxTime: removed
(*FindAndModify).MaxTime: removed
(*ListIndexes).MaxTime: removed

compatible changes

(*Hello).OmitMaxTimeMS: added

./x/mongo/driver/session

incompatible changes

Client.CurrentMct: removed
ClientOptions.DefaultMaxCommitTime: removed
TransactionOptions.MaxCommitTime: removed

compatible changes

Client.CurrentWTimeout: added

./x/mongo/driver/topology

incompatible changes

##ConnectServer: changed from func(./mongo/address.Address, updateTopologyCallback, ./bson.ObjectID, ...ServerOption) (*Server, error) to func(./mongo/address.Address, updateTopologyCallback, ./bson.ObjectID, time.Duration, ...ServerOption) (*Server, error)
ErrServerSelectionTimeout: removed
##NewServer: changed from func(./mongo/address.Address, ./bson.ObjectID, ...ServerOption) *Server to func(./mongo/address.Address, ./bson.ObjectID, time.Duration, ...ServerOption) *Server
##ServerAPIFromServerOptions: changed from func([]ServerOption) *./x/mongo/driver.ServerAPIOptions to func(time.Duration, []ServerOption) *./x/mongo/driver.ServerAPIOptions
WithConnectTimeout: removed
WithHeartbeatTimeout: removed
WithReadTimeout: removed
WithWriteTimeout: removed

compatible changes

(*Topology).GetServerSelectionTimeout: added
Config.ConnectTimeout: added
Config.Timeout: added

@prestonvasquez prestonvasquez dismissed stale reviews from qingyang-hu and matthewdale via 8725fca June 26, 2024 16:11
matthewdale
matthewdale previously approved these changes Jun 26, 2024
Copy link
Collaborator

@matthewdale matthewdale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍

qingyang-hu
qingyang-hu previously approved these changes Jun 27, 2024
Copy link
Collaborator

@qingyang-hu qingyang-hu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@prestonvasquez prestonvasquez dismissed stale reviews from qingyang-hu and matthewdale via 91c94d6 June 27, 2024 15:11
@prestonvasquez prestonvasquez requested review from matthewdale and qingyang-hu and removed request for blink1073 June 27, 2024 16:20
// connections directly before closing the connection context. This ensures
// that closing a connection will manifest as an io.EOF error, avoiding
// non-deterministic connection closure errors.
defer c.closeConnectContext()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matthewdale We must defer closing the connect context to avoid non-deterministic closure errors. For example running TestErrors/errors_are_wrapped/network_error_during_auth, not defering can result in 1 of 2 errors when closing a connection:

  • io.EOF
  • connection() error occurred during connection handshake: auth error: sasl conversation error: unable to authenticate using mechanism "SCRAM-SHA-256": connection(localhost:27018[-1761]) socket was unexpectedly closed: context canceled: connection(localhost:27018[-1761]) socket was unexpectedly closed: context canceled

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-priority-normal Medium Priority PR for Review: within 1 business day

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants