@@ -277,12 +277,15 @@ public class MongoClient {
277
277
/// - This value is only read in `deinit`. That occurs exactly once after the above modification is complete.
278
278
private var wasClosed = false
279
279
280
- /// Handlers for command monitoring events.
280
+ /// Handlers for command monitoring events. Should only be accessed when holding `eventHandlerLock`.
281
281
internal var commandEventHandlers : [ CommandEventHandler ]
282
282
283
- /// Handlers for SDAM monitoring events.
283
+ /// Handlers for SDAM monitoring events. Should only be accessed when holding `eventHandlerLock`.
284
284
internal var sdamEventHandlers : [ SDAMEventHandler ]
285
285
286
+ /// Lock used to synchronize access to the event handler arrays to prevent data races.
287
+ private let eventHandlerLock : Lock = . init( )
288
+
286
289
/// Counter for generating client _ids.
287
290
internal static var clientIDGenerator = NIOAtomic< Int> . makeAtomic( value: 0 )
288
291
@@ -402,6 +405,124 @@ public class MongoClient {
402
405
)
403
406
}
404
407
408
+ #if compiler(>=5.5.2) && canImport(_Concurrency)
409
+ @available ( macOS 10 . 15 , * )
410
+ internal class CmdHandler : CommandEventHandler {
411
+ private let continuation : AsyncStream < CommandEvent > . Continuation
412
+ internal init ( continuation: AsyncStream < CommandEvent > . Continuation ) {
413
+ self . continuation = continuation
414
+ }
415
+
416
+ // Satisfies the protocol
417
+ internal func handleCommandEvent( _ event: CommandEvent ) {
418
+ self . continuation. yield ( event)
419
+ }
420
+
421
+ internal func finish( ) {
422
+ self . continuation. finish ( )
423
+ }
424
+ }
425
+
426
+ @available ( macOS 10 . 15 , * )
427
+ internal class SDAMHandler : SDAMEventHandler {
428
+ private let continuation : AsyncStream < SDAMEvent > . Continuation
429
+ internal init ( continuation: AsyncStream < SDAMEvent > . Continuation ) {
430
+ self . continuation = continuation
431
+ }
432
+
433
+ // Satisfies the protocol
434
+ internal func handleSDAMEvent( _ event: SDAMEvent ) {
435
+ self . continuation. yield ( event)
436
+ }
437
+
438
+ internal func finish( ) {
439
+ self . continuation. finish ( )
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Provides an `AsyncSequence` API for consuming command monitoring events.
445
+ *
446
+ * Example: printing the command events out would be written as
447
+ * ```
448
+ * for try await event in client.commandEventStream() {
449
+ * print(event)
450
+ * }
451
+ * ```
452
+ * If you are looping over the events in the stream, you may wish to do so in a dedicated `Task`.
453
+ * The stream will be ended automatically if the `Task` it is running in is cancelled.
454
+ * - Returns: A `CommandEventStream` that implements `AsyncSequence`.
455
+ * - Note: Only the most recent 100 events are stored in the stream.
456
+ */
457
+ @available ( macOS 10 . 15 , * )
458
+ public func commandEventStream( ) -> CommandEventStream {
459
+ var handler : CmdHandler ?
460
+ let stream = AsyncStream (
461
+ CommandEvent . self,
462
+ bufferingPolicy: . bufferingNewest( 100 )
463
+ ) { con in
464
+ let cmdHandler = CmdHandler ( continuation: con)
465
+ handler = cmdHandler
466
+ self . addCommandEventHandler ( cmdHandler)
467
+ }
468
+
469
+ // Ok to force unwrap since handler is set in the closure
470
+ // swiftlint:disable force_unwrapping
471
+ let commandEvents = CommandEventStream ( cmdHandler: handler!, stream: stream)
472
+
473
+ return commandEvents
474
+ }
475
+
476
+ /**
477
+ * Provides an `AsyncSequence` API for consuming SDAM monitoring events.
478
+ *
479
+ * Example: printing the SDAM events out would be written as
480
+ * ```
481
+ * for try await event in client.sdamEventStream() {
482
+ * print(event)
483
+ * }
484
+ * ```
485
+ * If you are looping over the events in the stream, you may wish to do so in a dedicated `Task`.
486
+ * The stream will be ended automatically if the `Task` it is running in is cancelled.
487
+ * - Returns: An `SDAMEventStream` that implements `AsyncSequence`.
488
+ * - Note: Only the most recent 100 events are stored in the stream.
489
+ */
490
+ @available ( macOS 10 . 15 , * )
491
+ public func sdamEventStream( ) -> SDAMEventStream {
492
+ var handler : SDAMHandler ?
493
+ let stream = AsyncStream (
494
+ SDAMEvent . self,
495
+ bufferingPolicy: . bufferingNewest( 100 )
496
+ ) { con in
497
+ let sdamHandler = SDAMHandler ( continuation: con)
498
+ handler = sdamHandler
499
+ self . addSDAMEventHandler ( sdamHandler)
500
+ }
501
+ // Ok to force unwrap since handler is set just above
502
+ // swiftlint:disable force_unwrapping
503
+ let sdamEvents = SDAMEventStream ( sdamHandler: handler!, stream: stream)
504
+ return sdamEvents
505
+ }
506
+ #endif
507
+
508
+ // Check which handlers are assoc. with streams and finish them
509
+ private func closeHandlers( ) {
510
+ #if compiler(>=5.5.2) && canImport(_Concurrency)
511
+ if #available( macOS 10 . 15 , * ) {
512
+ for handler in commandEventHandlers {
513
+ if let cmdHandler = handler as? WeakEventHandler < CmdHandler > {
514
+ cmdHandler. handler? . finish ( )
515
+ }
516
+ }
517
+ for handler in sdamEventHandlers {
518
+ if let sdamHandler = handler as? WeakEventHandler < SDAMHandler > {
519
+ sdamHandler. handler? . finish ( )
520
+ }
521
+ }
522
+ }
523
+ #endif
524
+ }
525
+
405
526
/**
406
527
* Closes this `MongoClient`, closing all connections to the server and cleaning up internal state.
407
528
*
@@ -422,6 +543,7 @@ public class MongoClient {
422
543
self . operationExecutor. shutdown ( )
423
544
}
424
545
closeResult. whenComplete { _ in
546
+ self . closeHandlers ( )
425
547
self . wasClosed = true
426
548
}
427
549
@@ -441,6 +563,7 @@ public class MongoClient {
441
563
public func syncClose( ) throws {
442
564
try self . connectionPool. close ( )
443
565
try self . operationExecutor. syncShutdown ( )
566
+ self . closeHandlers ( )
444
567
self . wasClosed = true
445
568
}
446
569
@@ -786,7 +909,9 @@ public class MongoClient {
786
909
* to continue to receive events.
787
910
*/
788
911
public func addCommandEventHandler< T: CommandEventHandler > ( _ handler: T ) {
789
- self . commandEventHandlers. append ( WeakEventHandler < T > ( referencing: handler) )
912
+ self . eventHandlerLock. withLock {
913
+ self . commandEventHandlers. append ( WeakEventHandler < T > ( referencing: handler) )
914
+ }
790
915
}
791
916
792
917
/**
@@ -796,7 +921,9 @@ public class MongoClient {
796
921
* strong reference cycle and potentially result in memory leaks.
797
922
*/
798
923
public func addCommandEventHandler( _ handlerFunc: @escaping ( CommandEvent ) -> Void ) {
799
- self . commandEventHandlers. append ( CallbackEventHandler ( handlerFunc) )
924
+ self . eventHandlerLock. withLock {
925
+ self . commandEventHandlers. append ( CallbackEventHandler ( handlerFunc) )
926
+ }
800
927
}
801
928
802
929
/**
@@ -806,7 +933,9 @@ public class MongoClient {
806
933
* to continue to receive events.
807
934
*/
808
935
public func addSDAMEventHandler< T: SDAMEventHandler > ( _ handler: T ) {
809
- self . sdamEventHandlers. append ( WeakEventHandler ( referencing: handler) )
936
+ self . eventHandlerLock. withLock {
937
+ self . sdamEventHandlers. append ( WeakEventHandler ( referencing: handler) )
938
+ }
810
939
}
811
940
812
941
/**
@@ -816,7 +945,9 @@ public class MongoClient {
816
945
* strong reference cycle and potentially result in memory leaks.
817
946
*/
818
947
public func addSDAMEventHandler( _ handlerFunc: @escaping ( SDAMEvent ) -> Void ) {
819
- self . sdamEventHandlers. append ( CallbackEventHandler ( handlerFunc) )
948
+ self . eventHandlerLock. withLock {
949
+ self . sdamEventHandlers. append ( CallbackEventHandler ( handlerFunc) )
950
+ }
820
951
}
821
952
822
953
/// Internal method to check the `ReadConcern` that was ultimately set on this client. **This method may block
@@ -882,7 +1013,7 @@ extension CallbackEventHandler: CommandEventHandler where EventType == CommandEv
882
1013
883
1014
/// Event handler that stores a weak reference to the underlying handler.
884
1015
private class WeakEventHandler < T: AnyObject > {
885
- private weak var handler : T ?
1016
+ internal weak var handler : T ?
886
1017
887
1018
fileprivate init ( referencing handler: T ) {
888
1019
self . handler = handler
0 commit comments