@@ -18,6 +18,7 @@ import (
18
18
"go.mongodb.org/mongo-driver/bson"
19
19
"go.mongodb.org/mongo-driver/bson/bsoncodec"
20
20
"go.mongodb.org/mongo-driver/bson/bsonrw"
21
+ "go.mongodb.org/mongo-driver/bson/primitive"
21
22
"go.mongodb.org/mongo-driver/internal"
22
23
"go.mongodb.org/mongo-driver/internal/testutil"
23
24
"go.mongodb.org/mongo-driver/internal/testutil/assert"
@@ -28,6 +29,7 @@ import (
28
29
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
29
30
"go.mongodb.org/mongo-driver/x/mongo/driver"
30
31
"go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage"
32
+ "golang.org/x/sync/errgroup"
31
33
)
32
34
33
35
var noClientOpts = mtest .NewOptions ().CreateClient (false )
@@ -472,3 +474,109 @@ type proxyMessage struct {
472
474
sent wiremessage.WireMessage
473
475
received wiremessage.WireMessage
474
476
}
477
+
478
+ func TestClientStress (t * testing.T ) {
479
+ // TODO: Enable with GODRIVER-2038.
480
+ t .Skip ("TODO: Enable with GODRIVER-2038" )
481
+
482
+ if testing .Short () {
483
+ t .Skip ("skipping integration test in short mode" )
484
+ }
485
+
486
+ mtOpts := mtest .NewOptions ().CreateClient (false )
487
+ mt := mtest .New (t , mtOpts )
488
+
489
+ // Test that a Client can recover from a massive traffic spike after the traffic spike is over.
490
+ mt .Run ("Client recovers from traffic spike" , func (mt * mtest.T ) {
491
+ oid := primitive .NewObjectID ()
492
+ doc := bson.D {{Key : "_id" , Value : oid }, {Key : "key" , Value : "value" }}
493
+ _ , err := mt .Coll .InsertOne (context .Background (), doc )
494
+ assert .Nil (mt , err , "InsertOne error: %v" , err )
495
+
496
+ // findOne calls FindOne("_id": oid) on the given collection and with the given timeout. It
497
+ // returns any errors.
498
+ findOne := func (coll * mongo.Collection , timeout time.Duration ) error {
499
+ ctx , cancel := context .WithTimeout (context .Background (), timeout )
500
+ defer cancel ()
501
+ var res map [string ]interface {}
502
+ return coll .FindOne (ctx , bson.D {{Key : "_id" , Value : oid }}).Decode (& res )
503
+ }
504
+
505
+ // findOneFor calls FindOne on the given collection and with the given timeout in a loop for
506
+ // the given duration and returns any errors returned by FindOne.
507
+ findOneFor := func (coll * mongo.Collection , timeout time.Duration , d time.Duration ) []error {
508
+ errs := make ([]error , 0 )
509
+ start := time .Now ()
510
+ for time .Since (start ) <= d {
511
+ err := findOne (coll , timeout )
512
+ if err != nil {
513
+ errs = append (errs , err )
514
+ }
515
+ }
516
+ return errs
517
+ }
518
+
519
+ // Calculate the maximum observed round-trip time by measuring the duration of some FindOne
520
+ // operations and picking the max.
521
+ var maxRTT time.Duration
522
+ for i := 0 ; i < 50 ; i ++ {
523
+ start := time .Now ()
524
+ err := findOne (mt .Coll , 10 * time .Second )
525
+ assert .Nil (t , err , "FindOne error: %v" , err )
526
+ duration := time .Since (start )
527
+ if duration > maxRTT {
528
+ maxRTT = duration
529
+ }
530
+ }
531
+ assert .True (mt , maxRTT > 0 , "RTT must be greater than 0" )
532
+
533
+ // Run tests with various "maxPoolSize" values, including 1-connection pools and unlimited
534
+ // size pools, to test how the client handles traffic spikes using different connection pool
535
+ // configurations.
536
+ maxPoolSizes := []uint64 {0 , 1 , 10 , 100 }
537
+ for _ , maxPoolSize := range maxPoolSizes {
538
+ maxPoolSizeOpt := mtest .NewOptions ().ClientOptions (options .Client ().SetMaxPoolSize (maxPoolSize ))
539
+ mt .RunOpts (fmt .Sprintf ("maxPoolSize %d" , maxPoolSize ), maxPoolSizeOpt , func (mt * mtest.T ) {
540
+ doc := bson.D {{Key : "_id" , Value : oid }, {Key : "key" , Value : "value" }}
541
+ _ , err := mt .Coll .InsertOne (context .Background (), doc )
542
+ assert .Nil (mt , err , "InsertOne error: %v" , err )
543
+
544
+ // Set the timeout to be 10x the maximum observed RTT. Use a minimum 10ms timeout to
545
+ // prevent spurious test failures due to extremely low timeouts.
546
+ timeout := maxRTT * 10
547
+ if timeout < 10 * time .Millisecond {
548
+ timeout = 10 * time .Millisecond
549
+ }
550
+ t .Logf ("Max RTT %v; using a timeout of %v" , maxRTT , timeout )
551
+
552
+ // Simulate normal traffic by running one FindOne loop for 1 second and assert that there
553
+ // are no errors.
554
+ errs := findOneFor (mt .Coll , timeout , 1 * time .Second )
555
+ assert .True (mt , len (errs ) == 0 , "expected no errors, but got %d (%v)" , len (errs ), errs )
556
+
557
+ // Simulate an extreme traffic spike by running 1,000 FindOne loops in parallel for 10
558
+ // seconds and expect at least some errors to occur.
559
+ g := new (errgroup.Group )
560
+ for i := 0 ; i < 1000 ; i ++ {
561
+ g .Go (func () error {
562
+ errs := findOneFor (mt .Coll , timeout , 10 * time .Second )
563
+ if len (errs ) == 0 {
564
+ return nil
565
+ }
566
+ return errs [len (errs )- 1 ]
567
+ })
568
+ }
569
+ err = g .Wait ()
570
+ assert .NotNil (mt , err , "expected at least one error, got nil" )
571
+
572
+ // Simulate normal traffic again for 1 second. Ignore any errors to allow any outstanding
573
+ // connection errors to stop.
574
+ _ = findOneFor (mt .Coll , timeout , 1 * time .Second )
575
+
576
+ // Simulate normal traffic again for 1 second and assert that there are no errors.
577
+ errs = findOneFor (mt .Coll , timeout , 1 * time .Second )
578
+ assert .True (mt , len (errs ) == 0 , "expected no errors, but got %d (%v)" , len (errs ), errs )
579
+ })
580
+ }
581
+ })
582
+ }
0 commit comments