@@ -48,9 +48,14 @@ var securitySensitiveCommands = []string{
4848// recorded. After pool initialization completes, we set eventCutoffSeq to the
4949// current sequence number. Event accessors for CMAP and SDAM types then
5050// filter out any events with sequence <= eventCutoffSeq.
51+ //
52+ // Sequencing is thread-safe to support concurrent operations that may generate
53+ // events (e.g., connection checkouts generating CMAP events).
5154type eventSequencer struct {
5255 counter atomic.Int64
53- cutoff int64
56+ cutoff atomic.Int64
57+
58+ mu sync.RWMutex
5459
5560 // pool events are heterogeneous, so we track their sequence separately
5661 poolSeq []int64
@@ -59,27 +64,42 @@ type eventSequencer struct {
5964
6065// setCutoff marks the current sequence as the filtering cutoff point.
6166func (es * eventSequencer ) setCutoff () {
62- es .cutoff = es .counter .Load ()
67+ es .cutoff . Store ( es .counter .Load () )
6368}
6469
6570// recordEvent stores the sequence number for a given event type.
6671func (es * eventSequencer ) recordEvent (eventType monitoringEventType ) {
6772 next := es .counter .Add (1 )
73+
74+ es .mu .Lock ()
6875 es .seqByEventType [eventType ] = append (es .seqByEventType [eventType ], next )
76+ es .mu .Unlock ()
6977}
7078
7179func (es * eventSequencer ) recordPooledEvent () {
7280 next := es .counter .Add (1 )
81+
82+ es .mu .Lock ()
7383 es .poolSeq = append (es .poolSeq , next )
84+ es .mu .Unlock ()
7485}
7586
7687// shouldFilter returns true if the event at the given index should be filtered.
7788func (es * eventSequencer ) shouldFilter (eventType monitoringEventType , index int ) bool {
78- if es .cutoff == 0 {
89+ cutoff := es .cutoff .Load ()
90+ if cutoff == 0 {
7991 return false
8092 }
8193
82- return es.seqByEventType [eventType ][index ] <= es .cutoff
94+ es .mu .RLock ()
95+ defer es .mu .RUnlock ()
96+
97+ seqs , ok := es .seqByEventType [eventType ]
98+ if ! ok || index < 0 || index >= len (seqs ) {
99+ return false
100+ }
101+
102+ return seqs [index ] <= cutoff
83103}
84104
85105// clientEntity is a wrapper for a mongo.Client object that also holds additional information required during test
@@ -352,17 +372,43 @@ func (c *clientEntity) failedEvents() []*event.CommandFailedEvent {
352372 return events
353373}
354374
355- // filterEventsBySeq filters events by sequence number using the provided
356- // sequence slice. See comments on eventSequencer for more details.
357- func filterEventsBySeq [T any ](c * clientEntity , events []T , seqSlice []int64 ) []T {
358- if c .eventSequencer .cutoff == 0 {
375+ // filterEventsBySeq filters events by sequence number for the given eventType.
376+ // See comments on eventSequencer for more details.
377+ func filterEventsBySeq [T any ](c * clientEntity , events []T , eventType monitoringEventType ) []T {
378+ cutoff := c .eventSequencer .cutoff .Load ()
379+ if cutoff == 0 {
359380 return events
360381 }
361382
362- var filtered []T
363- for i , evt := range events {
364- if seqSlice [i ] > c .eventSequencer .cutoff {
365- filtered = append (filtered , evt )
383+ // Lock order: eventProcessMu -> eventSequencer.mu (matches writers)
384+ c .eventProcessMu .RLock ()
385+ c .eventSequencer .mu .RLock ()
386+
387+ // Snapshot to minimize time under locks and avoid races
388+ localEvents := append ([]T (nil ), events ... )
389+
390+ var seqSlice []int64
391+ if eventType == poolAnyEvent {
392+ seqSlice = c .eventSequencer .poolSeq
393+ } else {
394+ seqSlice = c .eventSequencer .seqByEventType [eventType ]
395+ }
396+
397+ localSeqs := append ([]int64 (nil ), seqSlice ... )
398+
399+ c .eventSequencer .mu .RUnlock ()
400+ c .eventProcessMu .RUnlock ()
401+
402+ // guard against index out of range.
403+ n := len (localEvents )
404+ if len (localSeqs ) < n {
405+ n = len (localSeqs )
406+ }
407+
408+ filtered := make ([]T , 0 , n )
409+ for i := 0 ; i < n ; i ++ {
410+ if localSeqs [i ] > cutoff {
411+ filtered = append (filtered , localEvents [i ])
366412 }
367413 }
368414
@@ -555,8 +601,10 @@ func (c *clientEntity) processPoolEvent(evt *event.PoolEvent) {
555601
556602 eventType := monitoringEventTypeFromPoolEvent (evt )
557603 if _ , ok := c .observedEvents [eventType ]; ok {
604+ c .eventProcessMu .Lock ()
558605 c .pooled = append (c .pooled , evt )
559606 c .eventSequencer .recordPooledEvent ()
607+ c .eventProcessMu .Unlock ()
560608 }
561609
562610 c .addEventsCount (eventType )
@@ -787,9 +835,7 @@ func awaitMinimumPoolSize(ctx context.Context, entity *clientEntity, minPoolSize
787835 case <- awaitCtx .Done ():
788836 return fmt .Errorf ("timed out waiting for client to reach minPoolSize" )
789837 case <- ticker .C :
790- if uint64 (entity .eventsCount [connectionReadyEvent ]) >= minPoolSize {
791- // Clear all CMAP and SDAM events that occurred during pool
792- // initialization.
838+ if uint64 (entity .getEventCount (connectionReadyEvent )) >= minPoolSize {
793839 entity .eventSequencer .setCutoff ()
794840
795841 return nil
0 commit comments