Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e4aa2b3
DRIVERS-2868 Guard server round trips from timeout
prestonvasquez Jan 14, 2025
b2e5cbb
GODRIVER-3444 Implement validation
prestonvasquez Jan 29, 2025
7397921
Merge branch 'master' into GODRIVER-3444
prestonvasquez Jan 29, 2025
a0fa4b7
GODRIVER-3444 Add test when maxAwaitTimeMS is lt remaining timeout
prestonvasquez Jan 31, 2025
bdf5945
Merge branch 'GODRIVER-3444' of github.com:prestonvasquez/mongo-go-dr…
prestonvasquez Jan 31, 2025
a6fbd2c
GODRIVER-3444 Serverless does not apply to tailable awaitData cursors
prestonvasquez Feb 3, 2025
fdeddb1
GODRIVER-3444 Use prose tests
prestonvasquez Feb 26, 2025
6e37314
Merge branch 'master' into GODRIVER-3444
prestonvasquez Feb 26, 2025
2da2627
GODRIVER-3444 Remove arguments from tests
prestonvasquez Feb 27, 2025
5e7921b
GODRIVER-3444 Update unified spect tests
prestonvasquez Mar 7, 2025
a435efe
GODRIVER-3444 Update tests to reduce event race
prestonvasquez Mar 14, 2025
9d03793
GODRIVER-3444 Resolve merge conflicts
prestonvasquez Apr 2, 2025
8459cd9
GODRIVER-3444 Remove error parameter from CalculateMaxTimeMS
prestonvasquez Apr 2, 2025
e5d5bce
GODRIVER-3444 Run pre-commit
prestonvasquez Apr 2, 2025
0f2ec20
GODRIVER-3444 Check max int
prestonvasquez Apr 2, 2025
210e653
GORIVER-3444 Extend skip to include twd
prestonvasquez Apr 2, 2025
06692da
GORIVER-3444 Extend skip to include twd
prestonvasquez Apr 3, 2025
92b8b5b
GODRIVER-3444 Update error messages
prestonvasquez Apr 3, 2025
db060be
GODRIVER-3444 Resolve merge conflicts
prestonvasquez Apr 16, 2025
62f8bc4
GODRIVER-3444 Resovle merge conflicts
prestonvasquez Apr 16, 2025
34dff0f
Update internal/driverutil/operation.go
prestonvasquez Apr 16, 2025
5ccba5d
GODRIVER-3444 Add tests for CalculateMaxTimeMS
prestonvasquez Apr 17, 2025
90b9171
GODRIVER-3444 Add tests for CalculateMaxTimeMS
prestonvasquez Apr 17, 2025
0ff2ec7
GODRIVER-3444 Add tests for CalculateMaxTimeMS
prestonvasquez Apr 17, 2025
18f7158
GODRIVER-3444 Remove legacy unified spec tests
prestonvasquez Apr 19, 2025
3b62991
GODRIVER-3444 Add license
prestonvasquez Apr 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions internal/driverutil/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

package driverutil

import (
"context"
"fmt"
"math"
"time"
)

// Operation Names should be sourced from the command reference documentation:
// https://www.mongodb.com/docs/manual/reference/command/
const (
Expand All @@ -30,3 +37,35 @@ const (
UpdateOp = "update" // UpdateOp is the name for updating
BulkWriteOp = "bulkWrite" // BulkWriteOp is the name for client-level bulk write
)

func CalculateMaxTimeMS(ctx context.Context, rttMin time.Duration, rttStats string, err error) (int64, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's unconventional to pass the error to be returned in as a parameter. Instead, consider letting CalculateMaxTimeMS return just the int64 value, and let the caller form the error value.

E.g. use in batch_cursor.go:

maxTimeMS = driverutil.CalculateMaxTimeMS(ctx, rttMonitor.Min())
if maxTimeMS <= 0 {
	return nil, fmt.Errorf(
		"calculated server-side timeout (%v ms) is less than or equal to 0 (%v): %w",
		maxTimeMS,
		rttMonitor.Stats(),
		ErrDeadlineWouldBeExceeded)
}

deadline, ok := ctx.Deadline()
if !ok {
return 0, nil
}

remainingTimeout := time.Until(deadline)

// Always round up to the next millisecond value so we never truncate the calculated
// maxTimeMS value (e.g. 400 microseconds evaluates to 1ms, not 0ms).
maxTimeMS := int64((remainingTimeout - rttMin) / time.Millisecond)
if maxTimeMS <= 0 {
return 0, fmt.Errorf(
"remaining time %v until context deadline is less than or equal to min network round-trip time %v (%v): %w",
remainingTimeout,
rttMin,
rttStats,
err)
}

// The server will return a "BadValue" error if maxTimeMS is greater
// than the maximum positive int32 value (about 24.9 days). If the
// user specified a timeout value greater than that, omit maxTimeMS
// and let the client-side timeout handle cancelling the op if the
// timeout is ever reached.
if maxTimeMS > math.MaxInt32 {
return 0, nil
}

return maxTimeMS, nil
}
70 changes: 70 additions & 0 deletions internal/integration/cursor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"go.mongodb.org/mongo-driver/v2/internal/assert"
"go.mongodb.org/mongo-driver/v2/internal/failpoint"
"go.mongodb.org/mongo-driver/v2/internal/integration/mtest"
"go.mongodb.org/mongo-driver/v2/internal/require"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
Expand Down Expand Up @@ -303,6 +304,75 @@ func TestCursor(t *testing.T) {
batchSize = sizeVal.Int32()
assert.Equal(mt, int32(4), batchSize, "expected batchSize 4, got %v", batchSize)
})

tailableAwaitDataCursorOpts := mtest.NewOptions().MinServerVersion("4.4").
Topologies(mtest.ReplicaSet, mtest.Sharded, mtest.LoadBalanced, mtest.Single)

mt.RunOpts("tailable awaitData cursor", tailableAwaitDataCursorOpts, func(mt *mtest.T) {
mt.Run("apply remaining timeoutMS if less than maxAwaitTimeMS", func(mt *mtest.T) {
initCollection(mt, mt.Coll)
mt.ClearEvents()

// Create a find cursor
opts := options.Find().SetBatchSize(1).SetMaxAwaitTime(100 * time.Millisecond)

cursor, err := mt.Coll.Find(context.Background(), bson.D{}, opts)
require.NoError(mt, err)

_ = mt.GetStartedEvent() // Empty find from started list.

defer cursor.Close(context.Background())

ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()

// Iterate twice to force a getMore
cursor.Next(ctx)
cursor.Next(ctx)

cmd := mt.GetStartedEvent().Command

maxTimeMSRaw, err := cmd.LookupErr("maxTimeMS")
require.NoError(mt, err)

got, ok := maxTimeMSRaw.AsInt64OK()
require.True(mt, ok)

assert.LessOrEqual(mt, got, int64(50))
})

mt.RunOpts("apply maxAwaitTimeMS if less than remaining timeout", tailableAwaitDataCursorOpts, func(mt *mtest.T) {
initCollection(mt, mt.Coll)
mt.ClearEvents()

// Create a find cursor
opts := options.Find().SetBatchSize(1).SetMaxAwaitTime(50 * time.Millisecond)

cursor, err := mt.Coll.Find(context.Background(), bson.D{}, opts)
require.NoError(mt, err)

_ = mt.GetStartedEvent() // Empty find from started list.

defer cursor.Close(context.Background())

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

// Iterate twice to force a getMore
cursor.Next(ctx)
cursor.Next(ctx)

cmd := mt.GetStartedEvent().Command

maxTimeMSRaw, err := cmd.LookupErr("maxTimeMS")
require.NoError(mt, err)

got, ok := maxTimeMSRaw.AsInt64OK()
require.True(mt, ok)

assert.LessOrEqual(mt, got, int64(50))
})
})
}

type tryNextCursor interface {
Expand Down
15 changes: 15 additions & 0 deletions internal/integration/unified/collection_operation_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"

"go.mongodb.org/mongo-driver/v2/bson"
Expand Down Expand Up @@ -1485,6 +1486,20 @@ func createFindCursor(ctx context.Context, operation *operation) (*cursorResult,
opts.SetSkip(int64(val.Int32()))
case "sort":
opts.SetSort(val.Document())
case "timeoutMode":
return nil, newSkipTestError("timeoutMode is not supported")
case "cursorType":
switch strings.ToLower(val.StringValue()) {
case "tailable":
opts.SetCursorType(options.Tailable)
case "tailableawait":
opts.SetCursorType(options.TailableAwait)
case "nontailable":
opts.SetCursorType(options.NonTailable)
}
case "maxAwaitTimeMS":
maxAwaitTimeMS := time.Duration(val.Int32()) * time.Millisecond
opts.SetMaxAwaitTime(maxAwaitTimeMS)
default:
return nil, fmt.Errorf("unrecognized find option %q", key)
}
Expand Down
1 change: 1 addition & 0 deletions internal/integration/unified/cursor_operation_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func executeIterateOnce(ctx context.Context, operation *operation) (*operationRe

return newDocumentResult(res, nil), nil
}

return newErrorResult(cursor.Err()), nil
}

Expand Down
18 changes: 18 additions & 0 deletions internal/integration/unified/unified_spec_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@ var (
"operation is retried multiple times for non-zero timeoutMS - aggregate on collection": "maxTimeMS is disabled on find and aggregate. See DRIVERS-2722.",
"operation is retried multiple times for non-zero timeoutMS - aggregate on database": "maxTimeMS is disabled on find and aggregate. See DRIVERS-2722.",
"timeoutMS applied to find command": "maxTimeMS is disabled on find and aggregate. See DRIVERS-2722.",
"timeoutMS applied to find": "maxTimeMS is disabled on find and aggregate. See DRIVERS-2722.",
"timeoutMS is refreshed for getMore if maxAwaitTimeMS is not set": "maxTimeMS is disabled on find and aggregate. See DRIVERS-2722.",
"timeoutMS is refreshed for getMore if maxAwaitTimeMS is set": "maxTimeMS is disabled on find and aggregate. See DRIVERS-2722.",
"timeoutMS is refreshed for getMore - failure": "maxTimeMS is disabled on find and aggregate. See DRIVERS-2722.",

// GODRIVER-3473: the implementation of DRIVERS-2868 makes it clear that the
// Go Driver does not correctly implement the following validation for
// tailable awaitData cursors:
//
// Drivers MUST error if this option is set, timeoutMS is set to a
// non-zero value, and maxAwaitTimeMS is greater than or equal to
// timeoutMS.
//
// Once GODRIVER-3473 is completed, we can continue running these tests.
"error if maxAwaitTimeMS is equal to timeoutMS": "Go Driver does not implement this behavior. See GODRIVER-3473",
"error if maxAwaitTimeMS is greater than timeoutMS": "Go Driver does not implement this behavior. See GODRIVER-3473",
"apply remaining timeoutMS if less than maxAwaitTimeMS": "Go Driver does not implement this behavior. See GODRIVER-3473",
"apply maxAwaitTimeMS if less than remaining timeout": "Go Driver does not implement this behavior. See GODRIVER-3473",

// DRIVERS-2953: This test requires that the driver sends a "getMore"
// with "maxTimeMS" set. However, "getMore" can only include "maxTimeMS"
Expand Down
Loading
Loading