From 144a8cad54ba52d2840946e9f0af3a41f35bf282 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Mon, 27 Jun 2022 13:59:48 -0400 Subject: [PATCH 01/47] EventStream struct + client field members --- Sources/MongoSwift/APM.swift | 39 ++++++++++++++++++++++++++++ Sources/MongoSwift/MongoClient.swift | 21 +++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 2f9f1df88..a944abe52 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,6 +117,45 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } +/// An asynchronous way to monitor events that wraps `AsyncStream` +@available(macOS 10.15, *) //Only available in 10.15+ +public struct EventStream { + + var eventHandler: ((T) -> Void)? + + func startMonitoringPrinter(){ + print("starting") + } + + func stopMonitoring(){ + print("dead") + } + + +} +@available(macOS 10.15, *) //Only available in 10.15+ +extension EventStream { + + public static var startMonitoring : AsyncStream { + AsyncStream { continuation in + var eventStream = EventStream() + eventStream.eventHandler = { + T in continuation.yield(T) + } + continuation.onTermination = {@Sendable _ in + //eventStream.stopMonitoring() + } + eventStream.startMonitoringPrinter() + + } + } +} +@available(macOS 10.15, *) //Only available in 10.15+ +public typealias CommandEventStream = EventStream + +@available(macOS 10.15, *) //Only available in 10.15+ +public typealias SDAMEventStream = EventStream + /// An event published when a command starts. public struct CommandStartedEvent: MongoSwiftEvent, CommandEventProtocol { /// Wrapper around a `mongoc_apm_command_started_t`. diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index e78d7fd0f..97a668671 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -282,6 +282,13 @@ public class MongoClient { /// Handlers for SDAM monitoring events. internal var sdamEventHandlers: [SDAMEventHandler] + + /// Async way to monitor command events. + internal var commandEvents: Any? + + /// Async way to monitor SDAM events. + internal var sdamEvents: Any? + /// Counter for generating client _ids. internal static var clientIDGenerator = NIOAtomic.makeAtomic(value: 0) @@ -365,7 +372,16 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] + if #available(macOS 10.15, *){ + self.commandEvents = CommandEventStream() + self.sdamEvents = SDAMEventStream() + } else { + self.commandEvents = nil + self.sdamEvents = nil + } self.connectionPool.initializeMonitoring(client: self) + + } /** @@ -856,6 +872,11 @@ extension MongoClient: Equatable { } } +//@available(macOS 10.15, *) +//extension MongoClient { +// +//} + /// Event handler constructed from a callback. /// Stores a strong reference to the provided callback. private class CallbackEventHandler { From 80b53224b6bbb44720b0085aa700492d1b00596c Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Mon, 27 Jun 2022 16:58:52 -0400 Subject: [PATCH 02/47] handing api differences using extensions --- Sources/MongoSwift/APM.swift | 62 +++++++++++++++++++--------- Sources/MongoSwift/MongoClient.swift | 18 +++----- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index a944abe52..1317fbb9d 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,10 +117,10 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } -/// An asynchronous way to monitor events that wraps `AsyncStream` -@available(macOS 10.15, *) //Only available in 10.15+ +/// An asynchronous way to monitor events. public struct EventStream { + //AsyncSequence example given in docs... var eventHandler: ((T) -> Void)? func startMonitoringPrinter(){ @@ -130,30 +130,52 @@ public struct EventStream { func stopMonitoring(){ print("dead") } - } -@available(macOS 10.15, *) //Only available in 10.15+ -extension EventStream { + +/// If concurrency can be used, `EventStream` implements `AsyncSequence` to allow for +/// asynchronous event monitoring +#if compiler(>=5.5) && canImport(_Concurrency) +@available(iOS 13.0.0, *) +@available(macOS 10.15, *) +extension EventStream : AsyncSequence, AsyncIteratorProtocol { - public static var startMonitoring : AsyncStream { - AsyncStream { continuation in - var eventStream = EventStream() - eventStream.eventHandler = { - T in continuation.yield(T) - } - continuation.onTermination = {@Sendable _ in - //eventStream.stopMonitoring() - } - eventStream.startMonitoringPrinter() - - } + //Necessary parts of impl the protocols + public typealias Element = T + + mutating public func next() async -> T? { + //what actually goes here? + print("i am a mutant") + return nil } -} -@available(macOS 10.15, *) //Only available in 10.15+ + + public func makeAsyncIterator() -> EventStream { + self + } + +} +#endif +//@available(macOS 10.15, *) //Only available in 10.15+ +//extension EventStream { +// +// public static var startMonitoring : AsyncStream { +// AsyncStream { continuation in +// var eventStream = EventStream() +// eventStream.eventHandler = { +// T in continuation.yield(T) +// } +// continuation.onTermination = {@Sendable _ in +// //eventStream.stopMonitoring() +// } +// eventStream.startMonitoringPrinter() +// +// } +// } +//} + public typealias CommandEventStream = EventStream -@available(macOS 10.15, *) //Only available in 10.15+ + public typealias SDAMEventStream = EventStream /// An event published when a command starts. diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 97a668671..337b35abf 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -283,13 +283,12 @@ public class MongoClient { /// Handlers for SDAM monitoring events. internal var sdamEventHandlers: [SDAMEventHandler] - /// Async way to monitor command events. - internal var commandEvents: Any? + /// Async way to monitor command events. Need macOS above 10.15 to use practically. + public var commandEvents: CommandEventStream - /// Async way to monitor SDAM events. - internal var sdamEvents: Any? + /// Async way to monitor SDAM events. Need macOS above 10.15 to use practically. + public var sdamEvents: SDAMEventStream - /// Counter for generating client _ids. internal static var clientIDGenerator = NIOAtomic.makeAtomic(value: 0) @@ -372,13 +371,8 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - if #available(macOS 10.15, *){ - self.commandEvents = CommandEventStream() - self.sdamEvents = SDAMEventStream() - } else { - self.commandEvents = nil - self.sdamEvents = nil - } + self.commandEvents = CommandEventStream() + self.sdamEvents = SDAMEventStream() self.connectionPool.initializeMonitoring(client: self) From 5eeec51d261aa4b91532ca5fb6f244cfbccdb34f Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 28 Jun 2022 11:33:29 -0400 Subject: [PATCH 03/47] comment debugging --- Sources/MongoSwift/APM.swift | 44 ++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 1317fbb9d..02ef27c66 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -119,6 +119,18 @@ private protocol CommandEventProtocol { /// An asynchronous way to monitor events. public struct EventStream { + //T is command or sdam + + //which event we are at + private var index = 0 + + //events + let events : [T] + + init(events : [T]){ + self.events = events + } + init(){} //AsyncSequence example given in docs... var eventHandler: ((T) -> Void)? @@ -139,20 +151,48 @@ public struct EventStream { @available(iOS 13.0.0, *) @available(macOS 10.15, *) extension EventStream : AsyncSequence, AsyncIteratorProtocol { + - //Necessary parts of impl the protocols + //for both protocols public typealias Element = T - mutating public func next() async -> T? { + + + //for iter protocols + public mutating func next() async -> T? { //what actually goes here? + + //check bounds + guard index < events.count else { + return nil + } + //incr index + let event = events[index] //as MongoSwiftEvent //events is of type MongoSwiftEvent + index += 1 + //api call + //Event adheres to MongoSwiftEvent and has access to its type and pointers + //publishEvent just iterates through eventlisteners tho... + //T is an enum where each event is of type `MongoSwiftClient` + + let output: MongoSwiftEvent = publishEvent(type: event.Type, eventPtr: nil) + //let (data, _) = try await urlSession.data(from: url) + + //return data print("i am a mutant") return nil } + public func makeAsyncIterator() -> EventStream { self } + + //startMonitoring? +} + +extension EventStream : AsyncStream { + } #endif //@available(macOS 10.15, *) //Only available in 10.15+ From a83ba3fd75d519763963006bec535ee2e4ac6660 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 29 Jun 2022 11:17:13 -0400 Subject: [PATCH 04/47] comment debugging --- Sources/MongoSwift/APM.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 02ef27c66..3aef20e45 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -174,7 +174,7 @@ extension EventStream : AsyncSequence, AsyncIteratorProtocol { //publishEvent just iterates through eventlisteners tho... //T is an enum where each event is of type `MongoSwiftClient` - let output: MongoSwiftEvent = publishEvent(type: event.Type, eventPtr: nil) + //let output: MongoSwiftEvent = publishEvent(type: event.Type, eventPtr: nil) //let (data, _) = try await urlSession.data(from: url) //return data @@ -191,9 +191,6 @@ extension EventStream : AsyncSequence, AsyncIteratorProtocol { } -extension EventStream : AsyncStream { - -} #endif //@available(macOS 10.15, *) //Only available in 10.15+ //extension EventStream { From 65b2c1e95600a677020c0556884672de3eea912c Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 29 Jun 2022 15:38:33 -0400 Subject: [PATCH 05/47] setting up iterators and streams with directives --- Sources/MongoSwift/APM.swift | 151 +++++++++++++++------------ Sources/MongoSwift/MongoClient.swift | 29 +++++ 2 files changed, 113 insertions(+), 67 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 3aef20e45..5ac5978ba 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -43,6 +43,12 @@ private protocol MongocEvent { var context: UnsafeMutableRawPointer? { get } } +/// Enum to wrap `CommandEvent` and `SDAMEvent` +public enum StreamEvents { + case CommandEvent + case SDAMEvent +} + /// A command monitoring event. public enum CommandEvent: Publishable { /// An event published when a command starts. @@ -117,103 +123,114 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } +/// If concurrency can be used, `EventStream` implements `AsyncSequence` to allow for +/// asynchronous event monitoring + /// An asynchronous way to monitor events. -public struct EventStream { +#if compiler(>=5.5) && canImport(_Concurrency) // && available(macOS 10.15, *) +@available(macOS 10.15, *) +public struct EventStream { //T is command or sdam //which event we are at private var index = 0 - - //events - let events : [T] - - init(events : [T]){ - self.events = events - } - init(){} - - //AsyncSequence example given in docs... - var eventHandler: ((T) -> Void)? - - func startMonitoringPrinter(){ - print("starting") - } - - func stopMonitoring(){ - print("dead") + var stream : AsyncStream + var iterator: EventStreamIterator + init(){ + } - } -/// If concurrency can be used, `EventStream` implements `AsyncSequence` to allow for -/// asynchronous event monitoring -#if compiler(>=5.5) && canImport(_Concurrency) -@available(iOS 13.0.0, *) @available(macOS 10.15, *) -extension EventStream : AsyncSequence, AsyncIteratorProtocol { - +extension EventStream : AsyncIteratorProtocol { + + //let stream : AsyncStream + + mutating func startMonitoring(client: MongoClient){ + //how to access client? cant init with it bc cant reference a non-fully init client + switch StreamEvents.self { + case is CommandEvent: + stream = AsyncStream { con in + client.addCommandEventHandler{ event in + con.yield(event) + } + } + iterator = EventStreamIterator(asyncStream: stream) + case is SDAMEvent: + stream = AsyncStream { con in + client.addSDAMEventHandler{ event in + con.yield(event) + } + } + iterator = EventStreamIterator(asyncStream: stream) + default: + print("wrong event type") + } + } //for both protocols - public typealias Element = T + public typealias Element = StreamEvents //for iter protocols - public mutating func next() async -> T? { + public mutating func next() async throws -> StreamEvents? { + try await iterator.next() //what actually goes here? - //check bounds - guard index < events.count else { - return nil - } - //incr index - let event = events[index] //as MongoSwiftEvent //events is of type MongoSwiftEvent - index += 1 - //api call - //Event adheres to MongoSwiftEvent and has access to its type and pointers - //publishEvent just iterates through eventlisteners tho... - //T is an enum where each event is of type `MongoSwiftClient` - - //let output: MongoSwiftEvent = publishEvent(type: event.Type, eventPtr: nil) - //let (data, _) = try await urlSession.data(from: url) - - //return data - print("i am a mutant") - return nil + } - public func makeAsyncIterator() -> EventStream { - self + public func makeAsyncIterator() -> EventStreamIterator { + iterator } //startMonitoring? } -#endif -//@available(macOS 10.15, *) //Only available in 10.15+ -//extension EventStream { -// -// public static var startMonitoring : AsyncStream { -// AsyncStream { continuation in -// var eventStream = EventStream() -// eventStream.eventHandler = { -// T in continuation.yield(T) -// } -// continuation.onTermination = {@Sendable _ in -// //eventStream.stopMonitoring() -// } -// eventStream.startMonitoringPrinter() -// -// } -// } -//} - +@available(macOS 10.15, *) +public struct EventStreamIterator : AsyncIteratorProtocol { + var iterator : AsyncStream.AsyncIterator? + + init(asyncStream: AsyncStream ) { + iterator = asyncStream.makeAsyncIterator() + } + public mutating func next() async throws -> StreamEvents? { + await iterator?.next() + } + + public typealias Element = StreamEvents + + +} +@available(macOS 10.15, *) public typealias CommandEventStream = EventStream +@available(macOS 10.15, *) +public typealias SDAMEventStream = EventStream + +#else +/// An asynchronous way to monitor events. +public struct EventStream { + //T is command or sdam + + @available(macOS 10.15, *) + var stream : Int + var iterator: Int + init(){ + stream = 0 + iterator = 0 + } +} + +public typealias CommandEventStream = EventStream public typealias SDAMEventStream = EventStream +#endif + + /// An event published when a command starts. public struct CommandStartedEvent: MongoSwiftEvent, CommandEventProtocol { diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 337b35abf..20dc4117e 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -373,6 +373,14 @@ public class MongoClient { self.commandEventHandlers = [] self.commandEvents = CommandEventStream() self.sdamEvents = SDAMEventStream() + +// if #available(macOS 10.15, *) { +// setUpStream() +// } else { +// self.commandEvents = CommandEventStream( +// ) +// self.sdamEvents = SDAMEventStream() +// } self.connectionPool.initializeMonitoring(client: self) @@ -869,6 +877,27 @@ extension MongoClient: Equatable { //@available(macOS 10.15, *) //extension MongoClient { // +// func setUpStream() { +// self.commandEvents = CommandEventStream(client: self, stream: +// AsyncStream { con in +// addCommandEventHandler{ event in +// con.yield(event) +// } +// } +// +// ) +// self.sdamEvents = SDAMEventStream(client: self, stream: +// AsyncStream { con in +// addSDAMEventHandler{ event in +// con.yield(event) +// } +// } +// ) +// +// } +// +// +// //} /// Event handler constructed from a callback. From e79d817aa7927b80490adf9f47ec6056421adbf7 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 29 Jun 2022 16:42:39 -0400 Subject: [PATCH 06/47] Fixing version issues via computed properties --- Sources/MongoSwift/APM.swift | 109 ++++++++++----------------- Sources/MongoSwift/MongoClient.swift | 39 +++++++++- 2 files changed, 76 insertions(+), 72 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 5ac5978ba..a552160b9 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -45,8 +45,8 @@ private protocol MongocEvent { /// Enum to wrap `CommandEvent` and `SDAMEvent` public enum StreamEvents { - case CommandEvent - case SDAMEvent + case CommandEvent(CommandEvent) + case SDAMEvent(SDAMEvent) } /// A command monitoring event. @@ -130,105 +130,78 @@ private protocol CommandEventProtocol { #if compiler(>=5.5) && canImport(_Concurrency) // && available(macOS 10.15, *) @available(macOS 10.15, *) public struct EventStream { - //T is command or sdam + + //StreamEvents //which event we are at private var index = 0 - var stream : AsyncStream - var iterator: EventStreamIterator - init(){ + private var stream : AsyncStream + //var iterator: EventStreamIterator + init(stream: AsyncStream ){ + self.stream = stream } + } - +#endif +// @available(macOS 10.15, *) -extension EventStream : AsyncIteratorProtocol { - - //let stream : AsyncStream +extension EventStream : AsyncSequence { - mutating func startMonitoring(client: MongoClient){ - //how to access client? cant init with it bc cant reference a non-fully init client - switch StreamEvents.self { - case is CommandEvent: - stream = AsyncStream { con in - client.addCommandEventHandler{ event in - con.yield(event) - } - } - iterator = EventStreamIterator(asyncStream: stream) - case is SDAMEvent: - stream = AsyncStream { con in - client.addSDAMEventHandler{ event in - con.yield(event) - } - } - iterator = EventStreamIterator(asyncStream: stream) - default: - print("wrong event type") - } - } - - //for both protocols public typealias Element = StreamEvents + + public typealias AsyncIterator = EventStreamIterator - - - //for iter protocols - public mutating func next() async throws -> StreamEvents? { - try await iterator.next() - //what actually goes here? - - } - - + //let stream : AsyncStream + +// mutating func startMonitoring(client: MongoClient){ +// //how to access client? cant init with it bc cant reference a non-fully init client +// switch StreamEvents.self { +// case is CommandEvent: +// stream = +// //iterator = EventStreamIterator(asyncStream: stream) +// case is SDAMEvent: +// stream = AsyncStream { con in +// client.addSDAMEventHandler{ event in +// con.yield(event) +// } +// } +// //iterator = EventStreamIterator(asyncStream: stream) +// default: +// print("wrong event type") +// } +// } + public func makeAsyncIterator() -> EventStreamIterator { - iterator + return EventStreamIterator.init(asyncStream: stream) } - + //startMonitoring? } - +// @available(macOS 10.15, *) public struct EventStreamIterator : AsyncIteratorProtocol { var iterator : AsyncStream.AsyncIterator? - + init(asyncStream: AsyncStream ) { iterator = asyncStream.makeAsyncIterator() } public mutating func next() async throws -> StreamEvents? { await iterator?.next() } - + public typealias Element = StreamEvents - - -} -@available(macOS 10.15, *) -public typealias CommandEventStream = EventStream -@available(macOS 10.15, *) -public typealias SDAMEventStream = EventStream -#else -/// An asynchronous way to monitor events. -public struct EventStream { - //T is command or sdam - - @available(macOS 10.15, *) - var stream : Int - var iterator: Int - init(){ - stream = 0 - iterator = 0 - } } +@available (macOS 10.15, *) public typealias CommandEventStream = EventStream +@available (macOS 10.15, *) public typealias SDAMEventStream = EventStream -#endif diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 20dc4117e..e0fa8367f 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -283,11 +283,42 @@ public class MongoClient { /// Handlers for SDAM monitoring events. internal var sdamEventHandlers: [SDAMEventHandler] + private var _commandEvents: Any? = nil + private var _sdamEvents: Any? = nil /// Async way to monitor command events. Need macOS above 10.15 to use practically. - public var commandEvents: CommandEventStream + #if compiler(>=5.5) && canImport(_Concurrency) + @available(macOS 10.15, *) + public var commandEvents: CommandEventStream { + if _commandEvents == nil { + _commandEvents = CommandEventStream( + stream: AsyncStream { con in + self.addCommandEventHandler{ event in + con.yield(event) + } + } + ) + } + return _commandEvents as! CommandEventStream + } + + @available(macOS 10.15, *) /// Async way to monitor SDAM events. Need macOS above 10.15 to use practically. - public var sdamEvents: SDAMEventStream + public var sdamEvents: SDAMEventStream { + if _sdamEvents == nil { + _sdamEvents = SDAMEventStream( + stream : AsyncStream { con in + self.addSDAMEventHandler{ event in + con.yield(event) + } + } + ) + } + return _sdamEvents as! SDAMEventStream + } + #endif + + /// Counter for generating client _ids. internal static var clientIDGenerator = NIOAtomic.makeAtomic(value: 0) @@ -371,8 +402,8 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - self.commandEvents = CommandEventStream() - self.sdamEvents = SDAMEventStream() + //self.commandEvents = CommandEventStream() + //self.sdamEvents = SDAMEventStream() // if #available(macOS 10.15, *) { // setUpStream() From 96b99389ed9d0179efc98c6fba6556c0cf878feb Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 1 Jul 2022 13:52:09 -0400 Subject: [PATCH 07/47] visibility modifiers and docstrings --- Sources/MongoSwift/APM.swift | 90 +++++++++++++--------------- Sources/MongoSwift/MongoClient.swift | 56 +++++++++-------- 2 files changed, 68 insertions(+), 78 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index a552160b9..3ad989a7f 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -44,10 +44,10 @@ private protocol MongocEvent { } /// Enum to wrap `CommandEvent` and `SDAMEvent` -public enum StreamEvents { - case CommandEvent(CommandEvent) - case SDAMEvent(SDAMEvent) -} +// public enum StreamEvents { +// case CommandEvent(CommandEvent) +// case SDAMEvent(SDAMEvent) +// } /// A command monitoring event. public enum CommandEvent: Publishable { @@ -123,87 +123,79 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } -/// If concurrency can be used, `EventStream` implements `AsyncSequence` to allow for -/// asynchronous event monitoring - -/// An asynchronous way to monitor events. #if compiler(>=5.5) && canImport(_Concurrency) // && available(macOS 10.15, *) +/// An asynchronous way to monitor events that uses `AsyncSequence`. +/// Only available for Swift 5.5 and higher. @available(macOS 10.15, *) -public struct EventStream { - - //StreamEvents - - //which event we are at - private var index = 0 - private var stream : AsyncStream - //var iterator: EventStreamIterator - init(stream: AsyncStream ){ +public struct EventStream { + private var stream: AsyncStream + /// Initialize the stream + public init(stream: AsyncStream) { self.stream = stream - } - } -#endif -// + @available(macOS 10.15, *) -extension EventStream : AsyncSequence { - - public typealias Element = StreamEvents - - public typealias AsyncIterator = EventStreamIterator - +extension EventStream: AsyncSequence { + /// The type of element produced by this `EventStream`. + public typealias Element = T - //let stream : AsyncStream + /// The asynchronous iterator of type `EventStreamIterator` + /// that produces elements of this asynchronous sequence. + public typealias AsyncIterator = EventStreamIterator // mutating func startMonitoring(client: MongoClient){ // //how to access client? cant init with it bc cant reference a non-fully init client -// switch StreamEvents.self { +// switch T.self { // case is CommandEvent: // stream = -// //iterator = EventStreamIterator(asyncStream: stream) +// //iterator = EventStreamIterator(asyncStream: stream) // case is SDAMEvent: // stream = AsyncStream { con in // client.addSDAMEventHandler{ event in // con.yield(event) // } // } -// //iterator = EventStreamIterator(asyncStream: stream) +// //iterator = EventStreamIterator(asyncStream: stream) // default: // print("wrong event type") // } // } - public func makeAsyncIterator() -> EventStreamIterator { - return EventStreamIterator.init(asyncStream: stream) + /// Creates the asynchronous iterator that produces elements of this `EventStream`. + public func makeAsyncIterator() -> EventStreamIterator { + EventStreamIterator(asyncStream: self.stream) } - //startMonitoring? - + // startMonitoring? } -// + @available(macOS 10.15, *) -public struct EventStreamIterator : AsyncIteratorProtocol { - var iterator : AsyncStream.AsyncIterator? +public struct EventStreamIterator: AsyncIteratorProtocol { + private var iterator: AsyncStream.AsyncIterator? - init(asyncStream: AsyncStream ) { - iterator = asyncStream.makeAsyncIterator() - } - public mutating func next() async throws -> StreamEvents? { - await iterator?.next() + /// Initialize the iterator + public init(asyncStream: AsyncStream) { + self.iterator = asyncStream.makeAsyncIterator() } - public typealias Element = StreamEvents - + /// Asynchronously advances to the next element and returns it, or ends the sequence if there is no next element. + public mutating func next() async throws -> T? { + await self.iterator?.next() + } + /// The type of element iterated over by this `EventStreamIterator`. + public typealias Element = T } -@available (macOS 10.15, *) +/// An asynchronous way to monitor command events using `EventStream` +@available(macOS 10.15, *) public typealias CommandEventStream = EventStream -@available (macOS 10.15, *) +/// An asynchronous way to monitor SDAM events using `EventStream` +@available(macOS 10.15, *) public typealias SDAMEventStream = EventStream - - +#endif /// An event published when a command starts. public struct CommandStartedEvent: MongoSwiftEvent, CommandEventProtocol { diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index e0fa8367f..3d8fbae69 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -282,44 +282,44 @@ public class MongoClient { /// Handlers for SDAM monitoring events. internal var sdamEventHandlers: [SDAMEventHandler] - - private var _commandEvents: Any? = nil - private var _sdamEvents: Any? = nil + + private var _commandEvents: Any? + private var _sdamEvents: Any? + +#if compiler(>=5.5) && canImport(_Concurrency) /// Async way to monitor command events. Need macOS above 10.15 to use practically. - #if compiler(>=5.5) && canImport(_Concurrency) @available(macOS 10.15, *) public var commandEvents: CommandEventStream { - if _commandEvents == nil { - _commandEvents = CommandEventStream( - stream: AsyncStream { con in - self.addCommandEventHandler{ event in + if self._commandEvents == nil { + self._commandEvents = CommandEventStream(stream: + AsyncStream { con in + self.addCommandEventHandler { event in con.yield(event) } } ) } - return _commandEvents as! CommandEventStream - + // swiftlint:disable:next force_cast + return self._commandEvents as! CommandEventStream } - - @available(macOS 10.15, *) + /// Async way to monitor SDAM events. Need macOS above 10.15 to use practically. + @available(macOS 10.15, *) public var sdamEvents: SDAMEventStream { - if _sdamEvents == nil { - _sdamEvents = SDAMEventStream( - stream : AsyncStream { con in - self.addSDAMEventHandler{ event in + if self._sdamEvents == nil { + self._sdamEvents = SDAMEventStream( + stream: AsyncStream { con in + self.addSDAMEventHandler { event in con.yield(event) } } ) } - return _sdamEvents as! SDAMEventStream + // swiftlint:disable:next force_cast + return self._sdamEvents as! SDAMEventStream } - #endif - - - +#endif + /// Counter for generating client _ids. internal static var clientIDGenerator = NIOAtomic.makeAtomic(value: 0) @@ -402,9 +402,9 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - //self.commandEvents = CommandEventStream() - //self.sdamEvents = SDAMEventStream() - + // self.commandEvents = CommandEventStream() + // self.sdamEvents = SDAMEventStream() + // if #available(macOS 10.15, *) { // setUpStream() // } else { @@ -413,8 +413,6 @@ public class MongoClient { // self.sdamEvents = SDAMEventStream() // } self.connectionPool.initializeMonitoring(client: self) - - } /** @@ -905,8 +903,8 @@ extension MongoClient: Equatable { } } -//@available(macOS 10.15, *) -//extension MongoClient { +// @available(macOS 10.15, *) +// extension MongoClient { // // func setUpStream() { // self.commandEvents = CommandEventStream(client: self, stream: @@ -929,7 +927,7 @@ extension MongoClient: Equatable { // // // -//} +// } /// Event handler constructed from a callback. /// Stores a strong reference to the provided callback. From 81e6ccb1e9b87104c639480ca16ce44c1add44eb Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 1 Jul 2022 14:42:20 -0400 Subject: [PATCH 08/47] commenting force cast and clean ups --- Sources/MongoSwift/MongoClient.swift | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 3d8fbae69..6d13896fe 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -299,6 +299,7 @@ public class MongoClient { } ) } + //Ok to force cast since we are explictly setting up the CommandEventStream // swiftlint:disable:next force_cast return self._commandEvents as! CommandEventStream } @@ -315,6 +316,7 @@ public class MongoClient { } ) } + //Ok to force cast since we are explictly setting up the SDAMEventStream // swiftlint:disable:next force_cast return self._sdamEvents as! SDAMEventStream } @@ -402,16 +404,7 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - // self.commandEvents = CommandEventStream() - // self.sdamEvents = SDAMEventStream() - -// if #available(macOS 10.15, *) { -// setUpStream() -// } else { -// self.commandEvents = CommandEventStream( -// ) -// self.sdamEvents = SDAMEventStream() -// } + //commandEvents/sdamEvents are initialized when called self.connectionPool.initializeMonitoring(client: self) } From ffd4d8fbbe1c6349c8632f81f0399721a60fedbe Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 1 Jul 2022 15:45:45 -0400 Subject: [PATCH 09/47] Prelim tests --- Sources/MongoSwift/MongoClient.swift | 6 +++--- .../ClientSessionTests.swift | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 6d13896fe..5cc059489 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -299,7 +299,7 @@ public class MongoClient { } ) } - //Ok to force cast since we are explictly setting up the CommandEventStream + // Ok to force cast since we are explictly setting up the CommandEventStream // swiftlint:disable:next force_cast return self._commandEvents as! CommandEventStream } @@ -316,7 +316,7 @@ public class MongoClient { } ) } - //Ok to force cast since we are explictly setting up the SDAMEventStream + // Ok to force cast since we are explictly setting up the SDAMEventStream // swiftlint:disable:next force_cast return self._sdamEvents as! SDAMEventStream } @@ -404,7 +404,7 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - //commandEvents/sdamEvents are initialized when called + // commandEvents/sdamEvents are initialized when called self.connectionPool.initializeMonitoring(client: self) } diff --git a/Tests/MongoSwiftSyncTests/ClientSessionTests.swift b/Tests/MongoSwiftSyncTests/ClientSessionTests.swift index a877cabed..5d6e0dcc5 100644 --- a/Tests/MongoSwiftSyncTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftSyncTests/ClientSessionTests.swift @@ -516,6 +516,24 @@ final class SyncClientSessionTests: MongoSwiftTestCase { } } +// func testEventStreamClient() async throws { +// let client = try MongoClient.makeTestClient() +// +// let commandStr : [String] = ["ping", "ping"] +// var i = 0 +// print("here") +// for try await event in client.asyncClient.commandEvents{ +// print("p") +// expect(commandStr[i]).to(equal(event.commandName)) +// i += 1 +// } +// +// try await client.asyncClient.db("admin").runCommand(["ping": 1]) +// print("p") +// //let db = client.db(Self.testDatabase) +// //let collection = db.collection(self.getCollectionName()) +// } + func testSessionsUnified() throws { let tests = try retrieveSpecTestFiles( specName: "sessions", From f71415639568badc24cf11439a9013c7e3f3cd00 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 5 Jul 2022 10:17:53 -0400 Subject: [PATCH 10/47] prelim test --- .../ClientSessionTests.swift | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Tests/MongoSwiftSyncTests/ClientSessionTests.swift b/Tests/MongoSwiftSyncTests/ClientSessionTests.swift index 5d6e0dcc5..252cbfe34 100644 --- a/Tests/MongoSwiftSyncTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftSyncTests/ClientSessionTests.swift @@ -516,23 +516,23 @@ final class SyncClientSessionTests: MongoSwiftTestCase { } } -// func testEventStreamClient() async throws { -// let client = try MongoClient.makeTestClient() -// -// let commandStr : [String] = ["ping", "ping"] -// var i = 0 -// print("here") -// for try await event in client.asyncClient.commandEvents{ -// print("p") -// expect(commandStr[i]).to(equal(event.commandName)) -// i += 1 -// } -// -// try await client.asyncClient.db("admin").runCommand(["ping": 1]) -// print("p") -// //let db = client.db(Self.testDatabase) -// //let collection = db.collection(self.getCollectionName()) -// } + func testEventStreamClient() async throws { + let client = try MongoClient.makeTestClient() + + let commandStr : [String] = ["ping", "ping"] + var i = 0 + print("here") + for try await event in client.asyncClient.commandEvents{ + print("help") + expect(commandStr[i]).to(equal(event.commandName)) + i += 1 + } + + try await client.asyncClient.db("admin").runCommand(["ping": 1]) + print("p")//never prints + //let db = client.db(Self.testDatabase) + //let collection = db.collection(self.getCollectionName()) + } func testSessionsUnified() throws { let tests = try retrieveSpecTestFiles( From c747cb610618e94550c11e81e75dd09047991ac8 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 5 Jul 2022 14:16:46 -0400 Subject: [PATCH 11/47] writing async tests for command/sdam --- .../ClientSessionTests.swift | 18 ---------- .../MongoSwiftTests/ClientSessionTests.swift | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Tests/MongoSwiftSyncTests/ClientSessionTests.swift b/Tests/MongoSwiftSyncTests/ClientSessionTests.swift index 252cbfe34..a877cabed 100644 --- a/Tests/MongoSwiftSyncTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftSyncTests/ClientSessionTests.swift @@ -516,24 +516,6 @@ final class SyncClientSessionTests: MongoSwiftTestCase { } } - func testEventStreamClient() async throws { - let client = try MongoClient.makeTestClient() - - let commandStr : [String] = ["ping", "ping"] - var i = 0 - print("here") - for try await event in client.asyncClient.commandEvents{ - print("help") - expect(commandStr[i]).to(equal(event.commandName)) - i += 1 - } - - try await client.asyncClient.db("admin").runCommand(["ping": 1]) - print("p")//never prints - //let db = client.db(Self.testDatabase) - //let collection = db.collection(self.getCollectionName()) - } - func testSessionsUnified() throws { let tests = try retrieveSpecTestFiles( specName: "sessions", diff --git a/Tests/MongoSwiftTests/ClientSessionTests.swift b/Tests/MongoSwiftTests/ClientSessionTests.swift index 1eca4c65d..0057d7ff3 100644 --- a/Tests/MongoSwiftTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftTests/ClientSessionTests.swift @@ -48,4 +48,40 @@ final class ClientSessionTests: MongoSwiftTestCase { expect(escapedSession?.active).to(beFalse()) } } + + func testCommandEventStreamClient() async throws { + try await self.withTestClient { client in + let commandStr: [String] = ["ping", "ping", "endSessions", "endSessions"] + let eventTypes: [EventType] = [ + .commandStartedEvent, + .commandSucceededEvent, + .commandStartedEvent, + .commandSucceededEvent + ] + Task { + var i = 0 + for try await event in client.commandEvents { + expect(commandStr[i]).to(equal(event.commandName)) + expect(eventTypes[i]).to(equal(event.type)) + + i += 1 + } + } + try await client.db("admin").runCommand(["ping": 1]) + } + } + + func testSDAMEventStreamClient() async throws { + try await self.withTestClient { client in + Task { + var i = 0 + for try await _ in client.sdamEvents { + // pinging should have 30 events (or less) + expect(i).to(beLessThanOrEqualTo(30)) + i += 1 + } + } + try await client.db("admin").runCommand(["ping": 1]) + } + } } From b9887ab906d7539fdf5a7d562c3d5d88c2e17d48 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 5 Jul 2022 15:35:44 -0400 Subject: [PATCH 12/47] Improving SDAM test and creating extension --- Sources/MongoSwift/APM.swift | 18 --- Sources/MongoSwift/MongoClient.swift | 26 ---- Sources/TestsCommon/APMUtils.swift | 115 +++++++++++++++++- Tests/MongoSwiftSyncTests/SDAMTests.swift | 54 -------- .../MongoSwiftTests/ClientSessionTests.swift | 40 +++++- 5 files changed, 151 insertions(+), 102 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 3ad989a7f..9b4ed4ae0 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -144,24 +144,6 @@ extension EventStream: AsyncSequence { /// that produces elements of this asynchronous sequence. public typealias AsyncIterator = EventStreamIterator -// mutating func startMonitoring(client: MongoClient){ -// //how to access client? cant init with it bc cant reference a non-fully init client -// switch T.self { -// case is CommandEvent: -// stream = -// //iterator = EventStreamIterator(asyncStream: stream) -// case is SDAMEvent: -// stream = AsyncStream { con in -// client.addSDAMEventHandler{ event in -// con.yield(event) -// } -// } -// //iterator = EventStreamIterator(asyncStream: stream) -// default: -// print("wrong event type") -// } -// } - /// Creates the asynchronous iterator that produces elements of this `EventStream`. public func makeAsyncIterator() -> EventStreamIterator { EventStreamIterator(asyncStream: self.stream) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 5cc059489..ea5fe316e 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -896,32 +896,6 @@ extension MongoClient: Equatable { } } -// @available(macOS 10.15, *) -// extension MongoClient { -// -// func setUpStream() { -// self.commandEvents = CommandEventStream(client: self, stream: -// AsyncStream { con in -// addCommandEventHandler{ event in -// con.yield(event) -// } -// } -// -// ) -// self.sdamEvents = SDAMEventStream(client: self, stream: -// AsyncStream { con in -// addSDAMEventHandler{ event in -// con.yield(event) -// } -// } -// ) -// -// } -// -// -// -// } - /// Event handler constructed from a callback. /// Stores a strong reference to the provided callback. private class CallbackEventHandler { diff --git a/Sources/TestsCommon/APMUtils.swift b/Sources/TestsCommon/APMUtils.swift index 56ec05fd5..a40aa9374 100644 --- a/Sources/TestsCommon/APMUtils.swift +++ b/Sources/TestsCommon/APMUtils.swift @@ -84,7 +84,10 @@ public enum EventType: String, Decodable { case commandStartedEvent, commandSucceededEvent, commandFailedEvent, connectionCreatedEvent, connectionReadyEvent, connectionClosedEvent, connectionCheckedInEvent, connectionCheckedOutEvent, connectionCheckOutFailedEvent, - poolCreatedEvent, poolReadyEvent, poolClearedEvent, poolClosedEvent + poolCreatedEvent, poolReadyEvent, poolClearedEvent, poolClosedEvent, + topologyDescriptionChanged, topologyOpening, topologyClosed, serverDescriptionChanged, + serverOpening, serverClosed, serverHeartbeatStarted, serverHeartbeatSucceeded, + serverHeartbeatFailed } extension CommandEvent { @@ -123,3 +126,113 @@ extension CommandEvent { return event } } + +extension SDAMEvent { + public var type: EventType { + switch self { + case .topologyDescriptionChanged: + return .topologyDescriptionChanged + case .topologyOpening: + return .topologyOpening + case .topologyClosed: + return .topologyClosed + case .serverDescriptionChanged: + return .serverDescriptionChanged + case .serverOpening: + return .serverOpening + case .serverClosed: + return .serverClosed + case .serverHeartbeatStarted: + return .serverHeartbeatStarted + case .serverHeartbeatSucceeded: + return .serverHeartbeatSucceeded + case .serverHeartbeatFailed: + return .serverHeartbeatFailed + } + } + + // Failable accessors for the different types of topology events. + + /// Returns this event as a `TopologyOpeningEvent` if it is one, nil otherwise. + public var topologyOpeningValue: TopologyOpeningEvent? { + guard case let .topologyOpening(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `TopologyClosedEvent` if it is one, nil otherwise. + public var topologyClosedValue: TopologyClosedEvent? { + guard case let .topologyClosed(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `TopologyDescriptionChangedEvent` if it is one, nil otherwise. + public var topologyDescriptionChangedValue: TopologyDescriptionChangedEvent? { + guard case let .topologyDescriptionChanged(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `ServerOpeningEvent` if it is one, nil otherwise. + public var serverOpeningValue: ServerOpeningEvent? { + guard case let .serverOpening(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `ServerClosedEvent` if it is one, nil otherwise. + public var serverClosedValue: ServerClosedEvent? { + guard case let .serverClosed(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `ServerDescriptionChangedEvent` if it is one, nil otherwise. + public var serverDescriptionChangedValue: ServerDescriptionChangedEvent? { + guard case let .serverDescriptionChanged(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `ServerHeartbeatStartedEvent` if it is one, nil otherwise. + public var serverHeartbeatStartedValue: ServerHeartbeatStartedEvent? { + guard case let .serverHeartbeatStarted(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `ServerHeartbeatSucceededEvent` if it is one, nil otherwise. + public var serverHeartbeatSucceededValue: ServerHeartbeatSucceededEvent? { + guard case let .serverHeartbeatSucceeded(event) = self else { + return nil + } + return event + } + + /// Returns this event as a `ServerHeartbeatFailedEvent` if it is one, nil otherwise. + public var serverHeartbeatFailedValue: ServerHeartbeatFailedEvent? { + guard case let .serverHeartbeatFailed(event) = self else { + return nil + } + return event + } + + /// Checks whether or not this event is a `ServerHeartbeatStartedEvent`, `ServerHeartbeatSucceededEvent`, or + /// a `ServerHeartbeatFailedEvent`. + public var isHeartbeatEvent: Bool { + switch self { + case .serverHeartbeatFailed, .serverHeartbeatStarted, .serverHeartbeatSucceeded: + return true + default: + return false + } + } +} diff --git a/Tests/MongoSwiftSyncTests/SDAMTests.swift b/Tests/MongoSwiftSyncTests/SDAMTests.swift index b6cb83234..78b64806d 100644 --- a/Tests/MongoSwiftSyncTests/SDAMTests.swift +++ b/Tests/MongoSwiftSyncTests/SDAMTests.swift @@ -290,57 +290,3 @@ private class TestSDAMMonitor: SDAMEventHandler { self.topEvents.append(event) } } - -/// Failable accessors for the different types of topology events. -extension SDAMEvent { - fileprivate var topologyOpeningValue: TopologyOpeningEvent? { - guard case let .topologyOpening(event) = self else { - return nil - } - return event - } - - private var topologyClosedValue: TopologyClosedEvent? { - guard case let .topologyClosed(event) = self else { - return nil - } - return event - } - - fileprivate var topologyDescriptionChangedValue: TopologyDescriptionChangedEvent? { - guard case let .topologyDescriptionChanged(event) = self else { - return nil - } - return event - } - - fileprivate var serverOpeningValue: ServerOpeningEvent? { - guard case let .serverOpening(event) = self else { - return nil - } - return event - } - - private var serverClosedValue: ServerClosedEvent? { - guard case let .serverClosed(event) = self else { - return nil - } - return event - } - - fileprivate var serverDescriptionChangedValue: ServerDescriptionChangedEvent? { - guard case let .serverDescriptionChanged(event) = self else { - return nil - } - return event - } - - fileprivate var isHeartbeatEvent: Bool { - switch self { - case .serverHeartbeatFailed, .serverHeartbeatStarted, .serverHeartbeatSucceeded: - return true - default: - return false - } - } -} diff --git a/Tests/MongoSwiftTests/ClientSessionTests.swift b/Tests/MongoSwiftTests/ClientSessionTests.swift index 0057d7ff3..8810b684c 100644 --- a/Tests/MongoSwiftTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftTests/ClientSessionTests.swift @@ -73,15 +73,49 @@ final class ClientSessionTests: MongoSwiftTestCase { func testSDAMEventStreamClient() async throws { try await self.withTestClient { client in + // Standard SDAM pattern + let eventTypes: [EventType] = [ + .topologyOpening, + .topologyDescriptionChanged, + .serverOpening, + .serverHeartbeatStarted, + .serverHeartbeatSucceeded, + .serverDescriptionChanged, + .serverOpening, + .serverOpening, + .serverOpening, + .serverClosed, + .topologyDescriptionChanged, + .serverHeartbeatStarted, + .serverHeartbeatStarted, + .serverHeartbeatStarted, + .serverHeartbeatSucceeded, + .serverHeartbeatSucceeded, + .serverHeartbeatSucceeded, + .serverDescriptionChanged, + .topologyDescriptionChanged, + .serverHeartbeatStarted, + .serverDescriptionChanged, + .topologyDescriptionChanged, + .serverHeartbeatStarted, + .serverDescriptionChanged, + .topologyDescriptionChanged, + .serverHeartbeatStarted, + .serverHeartbeatFailed, + .serverHeartbeatFailed, + .serverHeartbeatFailed, + .topologyClosed + ] Task { var i = 0 - for try await _ in client.sdamEvents { - // pinging should have 30 events (or less) - expect(i).to(beLessThanOrEqualTo(30)) + for try await event in client.sdamEvents { + print(event.type) + expect(eventTypes[i]).to(equal(event.type)) i += 1 } } try await client.db("admin").runCommand(["ping": 1]) + try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) } } } From ea0027fc3868521a53877530be5095cd1325b4d3 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 6 Jul 2022 10:57:01 -0400 Subject: [PATCH 13/47] Fixing SDAM test, cleaning up comments --- Sources/MongoSwift/APM.swift | 8 +-- Sources/MongoSwift/MongoClient.swift | 2 +- .../MongoSwiftTests/ClientSessionTests.swift | 55 +++++++------------ 3 files changed, 21 insertions(+), 44 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 9b4ed4ae0..3fdafc48c 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -43,12 +43,6 @@ private protocol MongocEvent { var context: UnsafeMutableRawPointer? { get } } -/// Enum to wrap `CommandEvent` and `SDAMEvent` -// public enum StreamEvents { -// case CommandEvent(CommandEvent) -// case SDAMEvent(SDAMEvent) -// } - /// A command monitoring event. public enum CommandEvent: Publishable { /// An event published when a command starts. @@ -123,7 +117,7 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } -#if compiler(>=5.5) && canImport(_Concurrency) // && available(macOS 10.15, *) +#if compiler(>=5.5) && canImport(_Concurrency) /// An asynchronous way to monitor events that uses `AsyncSequence`. /// Only available for Swift 5.5 and higher. @available(macOS 10.15, *) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index ea5fe316e..e717f6314 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -404,7 +404,7 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - // commandEvents/sdamEvents are initialized when called + // commandEvents and sdamEvents are initialized when called self.connectionPool.initializeMonitoring(client: self) } diff --git a/Tests/MongoSwiftTests/ClientSessionTests.swift b/Tests/MongoSwiftTests/ClientSessionTests.swift index 8810b684c..ec821e221 100644 --- a/Tests/MongoSwiftTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftTests/ClientSessionTests.swift @@ -73,47 +73,30 @@ final class ClientSessionTests: MongoSwiftTestCase { func testSDAMEventStreamClient() async throws { try await self.withTestClient { client in - // Standard SDAM pattern - let eventTypes: [EventType] = [ - .topologyOpening, - .topologyDescriptionChanged, - .serverOpening, - .serverHeartbeatStarted, - .serverHeartbeatSucceeded, - .serverDescriptionChanged, - .serverOpening, - .serverOpening, - .serverOpening, - .serverClosed, - .topologyDescriptionChanged, - .serverHeartbeatStarted, - .serverHeartbeatStarted, - .serverHeartbeatStarted, - .serverHeartbeatSucceeded, - .serverHeartbeatSucceeded, - .serverHeartbeatSucceeded, - .serverDescriptionChanged, - .topologyDescriptionChanged, - .serverHeartbeatStarted, - .serverDescriptionChanged, - .topologyDescriptionChanged, - .serverHeartbeatStarted, - .serverDescriptionChanged, - .topologyDescriptionChanged, - .serverHeartbeatStarted, - .serverHeartbeatFailed, - .serverHeartbeatFailed, - .serverHeartbeatFailed, - .topologyClosed - ] Task { + // var j = 0 var i = 0 + // var streamEvents: [EventType] = [] + var eventHandler: [EventType] = [] + client.addSDAMEventHandler { event in + if !event.isHeartbeatEvent { + eventHandler.append(event.type) + // print("J is " + String(j)) + // j += 1 + } + } + for try await event in client.sdamEvents { - print(event.type) - expect(eventTypes[i]).to(equal(event.type)) - i += 1 + if !event.isHeartbeatEvent { + expect(event.type).to(equal(eventHandler[i])) +// print(event.type) +// print(eventHandler[i]) +// print("I is " + String(i)) + i += 1 + } } } + try await client.db("admin").runCommand(["ping": 1]) try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) } From e4c4429ee1ca67941beafcf058a763ffbb162016 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 6 Jul 2022 14:39:37 -0400 Subject: [PATCH 14/47] sourcery updates --- Sources/MongoSwiftSync/Exports.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/MongoSwiftSync/Exports.swift b/Sources/MongoSwiftSync/Exports.swift index 091e4c6b4..e68efe14b 100644 --- a/Sources/MongoSwiftSync/Exports.swift +++ b/Sources/MongoSwiftSync/Exports.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.6.0 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.6.1 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT // Re-export the BSON library @@ -36,6 +36,8 @@ @_exported import struct MongoSwift.DropDatabaseOptions @_exported import struct MongoSwift.DropIndexOptions @_exported import struct MongoSwift.EstimatedDocumentCountOptions +@_exported import struct MongoSwift.EventStream +@_exported import struct MongoSwift.EventStreamIterator @_exported import struct MongoSwift.FindOneAndDeleteOptions @_exported import struct MongoSwift.FindOneAndReplaceOptions @_exported import struct MongoSwift.FindOneAndUpdateOptions From 99de8a1c78c358de7ec214eea4ecdb13d62598d5 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 6 Jul 2022 16:36:40 -0400 Subject: [PATCH 15/47] skip swiftlint for sync exports --- Sources/MongoSwift/APM.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 3fdafc48c..cce2aadda 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -118,6 +118,7 @@ private protocol CommandEventProtocol { } #if compiler(>=5.5) && canImport(_Concurrency) +// sourcery: skipSyncExport /// An asynchronous way to monitor events that uses `AsyncSequence`. /// Only available for Swift 5.5 and higher. @available(macOS 10.15, *) @@ -146,6 +147,7 @@ extension EventStream: AsyncSequence { // startMonitoring? } +// sourcery: skipSyncExport @available(macOS 10.15, *) public struct EventStreamIterator: AsyncIteratorProtocol { private var iterator: AsyncStream.AsyncIterator? From f7171f3605e32cfb97f562b09321cdff2a353928 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 7 Jul 2022 10:25:00 -0400 Subject: [PATCH 16/47] sourcery compiler directive issues --- Sources/MongoSwift/APM.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index cce2aadda..00aa4317f 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,8 +117,8 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } -#if compiler(>=5.5) && canImport(_Concurrency) // sourcery: skipSyncExport +#if compiler(>=5.5) && canImport(_Concurrency) /// An asynchronous way to monitor events that uses `AsyncSequence`. /// Only available for Swift 5.5 and higher. @available(macOS 10.15, *) From d30aee661dcded8d57eff406a592640550e6ae61 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 7 Jul 2022 11:22:26 -0400 Subject: [PATCH 17/47] sourcery skip before available --- Sources/MongoSwift/APM.swift | 5 +++-- Sources/MongoSwiftSync/Exports.swift | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 00aa4317f..3e053293b 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,11 +117,12 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } -// sourcery: skipSyncExport + #if compiler(>=5.5) && canImport(_Concurrency) /// An asynchronous way to monitor events that uses `AsyncSequence`. /// Only available for Swift 5.5 and higher. @available(macOS 10.15, *) +// sourcery: skipSyncExport public struct EventStream { private var stream: AsyncStream /// Initialize the stream @@ -147,8 +148,8 @@ extension EventStream: AsyncSequence { // startMonitoring? } -// sourcery: skipSyncExport @available(macOS 10.15, *) +// sourcery: skipSyncExport public struct EventStreamIterator: AsyncIteratorProtocol { private var iterator: AsyncStream.AsyncIterator? diff --git a/Sources/MongoSwiftSync/Exports.swift b/Sources/MongoSwiftSync/Exports.swift index e68efe14b..2ebdd0ed6 100644 --- a/Sources/MongoSwiftSync/Exports.swift +++ b/Sources/MongoSwiftSync/Exports.swift @@ -36,8 +36,6 @@ @_exported import struct MongoSwift.DropDatabaseOptions @_exported import struct MongoSwift.DropIndexOptions @_exported import struct MongoSwift.EstimatedDocumentCountOptions -@_exported import struct MongoSwift.EventStream -@_exported import struct MongoSwift.EventStreamIterator @_exported import struct MongoSwift.FindOneAndDeleteOptions @_exported import struct MongoSwift.FindOneAndReplaceOptions @_exported import struct MongoSwift.FindOneAndUpdateOptions From 6580240553b9936f4287e96dcc0ed78d2ddb3ac6 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 7 Jul 2022 11:23:02 -0400 Subject: [PATCH 18/47] lint --- Sources/MongoSwift/APM.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 3e053293b..6333cb7fe 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,7 +117,6 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } - #if compiler(>=5.5) && canImport(_Concurrency) /// An asynchronous way to monitor events that uses `AsyncSequence`. /// Only available for Swift 5.5 and higher. From b963059f65b5103569ec33201591abb10adeb75b Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 7 Jul 2022 11:54:05 -0400 Subject: [PATCH 19/47] removing comments --- Tests/MongoSwiftTests/ClientSessionTests.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Tests/MongoSwiftTests/ClientSessionTests.swift b/Tests/MongoSwiftTests/ClientSessionTests.swift index ec821e221..ee88f5f1e 100644 --- a/Tests/MongoSwiftTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftTests/ClientSessionTests.swift @@ -74,24 +74,17 @@ final class ClientSessionTests: MongoSwiftTestCase { func testSDAMEventStreamClient() async throws { try await self.withTestClient { client in Task { - // var j = 0 var i = 0 - // var streamEvents: [EventType] = [] var eventHandler: [EventType] = [] client.addSDAMEventHandler { event in if !event.isHeartbeatEvent { eventHandler.append(event.type) - // print("J is " + String(j)) - // j += 1 } } for try await event in client.sdamEvents { if !event.isHeartbeatEvent { expect(event.type).to(equal(eventHandler[i])) -// print(event.type) -// print(eventHandler[i]) -// print("I is " + String(i)) i += 1 } } From 8e81f6eff4e2dfa2970851a6118f836275a3f6d7 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 8 Jul 2022 14:03:33 -0400 Subject: [PATCH 20/47] Fixing comments, tests, locks, annotations --- Sources/MongoSwift/APM.swift | 12 ++-- Sources/MongoSwift/MongoClient.swift | 14 ++-- Tests/MongoSwiftTests/APMTests.swift | 67 +++++++++++++++++++ .../MongoSwiftTests/ClientSessionTests.swift | 46 ------------- 4 files changed, 83 insertions(+), 56 deletions(-) create mode 100644 Tests/MongoSwiftTests/APMTests.swift diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 6333cb7fe..1efd6975d 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,15 +117,15 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } -#if compiler(>=5.5) && canImport(_Concurrency) +#if compiler(>=5.5.2) && canImport(_Concurrency) /// An asynchronous way to monitor events that uses `AsyncSequence`. -/// Only available for Swift 5.5 and higher. +/// Only available for Swift 5.5.2 and higher. @available(macOS 10.15, *) // sourcery: skipSyncExport public struct EventStream { private var stream: AsyncStream /// Initialize the stream - public init(stream: AsyncStream) { + internal init(stream: AsyncStream) { self.stream = stream } } @@ -153,7 +153,7 @@ public struct EventStreamIterator: AsyncIteratorProtocol { private var iterator: AsyncStream.AsyncIterator? /// Initialize the iterator - public init(asyncStream: AsyncStream) { + internal init(asyncStream: AsyncStream) { self.iterator = asyncStream.makeAsyncIterator() } @@ -166,11 +166,11 @@ public struct EventStreamIterator: AsyncIteratorProtocol { public typealias Element = T } -/// An asynchronous way to monitor command events using `EventStream` +/// An asynchronous way to monitor command events using `EventStream`. @available(macOS 10.15, *) public typealias CommandEventStream = EventStream -/// An asynchronous way to monitor SDAM events using `EventStream` +/// An asynchronous way to monitor SDAM events using `EventStream`. @available(macOS 10.15, *) public typealias SDAMEventStream = EventStream #endif diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index e717f6314..1a392846a 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -286,8 +286,11 @@ public class MongoClient { private var _commandEvents: Any? private var _sdamEvents: Any? -#if compiler(>=5.5) && canImport(_Concurrency) - /// Async way to monitor command events. Need macOS above 10.15 to use practically. +#if compiler(>=5.5.2) && canImport(_Concurrency) + /// Provides an `AsyncSequence` API for consuming command monitoring events. + /// Example: printing the command events out would be written as + /// `for await event in client.commandEvents { print(event) }`. + /// Wrapping in a `Task { ... }` may be desired for asynchronicity. @available(macOS 10.15, *) public var commandEvents: CommandEventStream { if self._commandEvents == nil { @@ -304,7 +307,10 @@ public class MongoClient { return self._commandEvents as! CommandEventStream } - /// Async way to monitor SDAM events. Need macOS above 10.15 to use practically. + /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. + /// Example: printing the SDAM events out would be written as + /// `for await event in client.sdamEvents { print(event) }`. + /// Wrapping in a `Task { ... }` may be desired for asynchronicity. @available(macOS 10.15, *) public var sdamEvents: SDAMEventStream { if self._sdamEvents == nil { @@ -404,7 +410,7 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - // commandEvents and sdamEvents are initialized when called + // commandEvents and sdamEvents have initial handlers initialized self.connectionPool.initializeMonitoring(client: self) } diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift new file mode 100644 index 000000000..c67283d60 --- /dev/null +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -0,0 +1,67 @@ +@testable import MongoSwift +import Nimble +import NIOConcurrencyHelpers +import TestsCommon + +final class APMTests: MongoSwiftTestCase { + func testCommandEventStreamClient() async throws { + try await self.withTestClient { client in + let commandStr: [String] = ["ping", "ping", "endSessions", "endSessions"] + let eventTypes: [EventType] = [ + .commandStartedEvent, + .commandSucceededEvent, + .commandStartedEvent, + .commandSucceededEvent + ] + // let cmdEventsTask = Task { () -> Int in + Task { + var i = 0 + for try await event in client.commandEvents { + expect(commandStr[i]).to(equal(event.commandName)) + expect(eventTypes[i]).to(equal(event.type)) + i += 1 + } + // return i + } + + try await client.db("admin").runCommand(["ping": 1]) +// try await client.close() +// let taskResult = await cmdEventsTask.result +// do { +// let output = try taskResult.get() +// expect(output).to(equal(4)) +// } catch { +// print("oopsies") +// } + } + } + + func testSDAMEventStreamClient() async throws { + // Need lock to prevent dataraces + let lock = Lock() + try await self.withTestClient { client in + Task { + var i = 0 + var eventTypes: [EventType] = [] + // Lock the array access while appending + lock.withLock { + client.addSDAMEventHandler { event in + if !event.isHeartbeatEvent { + eventTypes.append(event.type) + } + } + } + // Async so cannot lock + for try await event in client.sdamEvents { + if !event.isHeartbeatEvent { + expect(event.type).to(equal(eventTypes[i])) + i += 1 + } + } + } + + try await client.db("admin").runCommand(["ping": 1]) + try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + } + } +} diff --git a/Tests/MongoSwiftTests/ClientSessionTests.swift b/Tests/MongoSwiftTests/ClientSessionTests.swift index ee88f5f1e..1eca4c65d 100644 --- a/Tests/MongoSwiftTests/ClientSessionTests.swift +++ b/Tests/MongoSwiftTests/ClientSessionTests.swift @@ -48,50 +48,4 @@ final class ClientSessionTests: MongoSwiftTestCase { expect(escapedSession?.active).to(beFalse()) } } - - func testCommandEventStreamClient() async throws { - try await self.withTestClient { client in - let commandStr: [String] = ["ping", "ping", "endSessions", "endSessions"] - let eventTypes: [EventType] = [ - .commandStartedEvent, - .commandSucceededEvent, - .commandStartedEvent, - .commandSucceededEvent - ] - Task { - var i = 0 - for try await event in client.commandEvents { - expect(commandStr[i]).to(equal(event.commandName)) - expect(eventTypes[i]).to(equal(event.type)) - - i += 1 - } - } - try await client.db("admin").runCommand(["ping": 1]) - } - } - - func testSDAMEventStreamClient() async throws { - try await self.withTestClient { client in - Task { - var i = 0 - var eventHandler: [EventType] = [] - client.addSDAMEventHandler { event in - if !event.isHeartbeatEvent { - eventHandler.append(event.type) - } - } - - for try await event in client.sdamEvents { - if !event.isHeartbeatEvent { - expect(event.type).to(equal(eventHandler[i])) - i += 1 - } - } - } - - try await client.db("admin").runCommand(["ping": 1]) - try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) - } - } } From 68b5a4b0bca7dc7d8730c8c40afd9a700482dea5 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 8 Jul 2022 15:39:27 -0400 Subject: [PATCH 21/47] Playing around with caching continuation + buffers --- Sources/MongoSwift/APM.swift | 13 ++++++ Sources/MongoSwift/MongoClient.swift | 62 +++++++++++++++++++--------- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 1efd6975d..494efbf4a 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -124,9 +124,22 @@ private protocol CommandEventProtocol { // sourcery: skipSyncExport public struct EventStream { private var stream: AsyncStream + private var continuation: AsyncStream.Continuation? + /// Initialize the stream internal init(stream: AsyncStream) { self.stream = stream + self.continuation = nil + } + + ///Set the `AsyncStream.Continuation` property of the the stream + internal mutating func setCon(continuation: AsyncStream.Continuation) { + self.continuation = continuation + } + + ///Finish the continuation + internal func finish(){ + continuation?.finish() } } diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 1a392846a..3e49d0fb0 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -291,40 +291,58 @@ public class MongoClient { /// Example: printing the command events out would be written as /// `for await event in client.commandEvents { print(event) }`. /// Wrapping in a `Task { ... }` may be desired for asynchronicity. + /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public var commandEvents: CommandEventStream { - if self._commandEvents == nil { - self._commandEvents = CommandEventStream(stream: - AsyncStream { con in - self.addCommandEventHandler { event in - con.yield(event) + get { + if self._commandEvents == nil { + self._commandEvents = CommandEventStream( + stream: AsyncStream( + CommandEvent.self, + bufferingPolicy: .bufferingNewest(100) + ) { con in + self.addCommandEventHandler { event in + con.yield(event) + self.commandEvents.setCon(continuation: con) + } } - } - ) + ) + } + // Ok to force cast since we are explictly setting up the CommandEventStream + // swiftlint:disable:next force_cast + return self._commandEvents as! CommandEventStream } - // Ok to force cast since we are explictly setting up the CommandEventStream - // swiftlint:disable:next force_cast - return self._commandEvents as! CommandEventStream + // swiftlint:disable unused_setter_value + set (setter){ } } /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. /// Example: printing the SDAM events out would be written as /// `for await event in client.sdamEvents { print(event) }`. /// Wrapping in a `Task { ... }` may be desired for asynchronicity. + /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public var sdamEvents: SDAMEventStream { - if self._sdamEvents == nil { - self._sdamEvents = SDAMEventStream( - stream: AsyncStream { con in - self.addSDAMEventHandler { event in - con.yield(event) + get { + if self._sdamEvents == nil { + self._sdamEvents = SDAMEventStream( + stream: AsyncStream( + SDAMEvent.self, + bufferingPolicy: .bufferingNewest(100) + ) { con in + self.addSDAMEventHandler { event in + con.yield(event) + self.sdamEvents.setCon(continuation: con) + } } - } - ) + ) + } + // Ok to force cast since we are explictly setting up the SDAMEventStream + // swiftlint:disable:next force_cast + return self._sdamEvents as! SDAMEventStream } - // Ok to force cast since we are explictly setting up the SDAMEventStream - // swiftlint:disable:next force_cast - return self._sdamEvents as! SDAMEventStream + // swiftlint:disable unused_setter_value + set(setter) { } } #endif @@ -469,6 +487,10 @@ public class MongoClient { } closeResult.whenComplete { _ in self.wasClosed = true + if #available(macOS 10.15, *){ + self.sdamEvents.finish() + self.commandEvents.finish() + } } return closeResult From 0f17aa6891096dab19d698b74e86dc941103f27b Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 8 Jul 2022 16:07:33 -0400 Subject: [PATCH 22/47] Fixing client.finish --- Sources/MongoSwift/APM.swift | 10 +++++----- Sources/MongoSwift/MongoClient.swift | 9 ++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 494efbf4a..a98d9a101 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -132,14 +132,14 @@ public struct EventStream { self.continuation = nil } - ///Set the `AsyncStream.Continuation` property of the the stream + /// Set the `AsyncStream.Continuation` property of the the stream internal mutating func setCon(continuation: AsyncStream.Continuation) { self.continuation = continuation } - - ///Finish the continuation - internal func finish(){ - continuation?.finish() + + /// Finish the continuation + internal func finish() { + self.continuation?.finish() } } diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 3e49d0fb0..0d7b48730 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -313,7 +313,7 @@ public class MongoClient { return self._commandEvents as! CommandEventStream } // swiftlint:disable unused_setter_value - set (setter){ } + set(setter) {} } /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. @@ -342,7 +342,7 @@ public class MongoClient { return self._sdamEvents as! SDAMEventStream } // swiftlint:disable unused_setter_value - set(setter) { } + set(setter) {} } #endif @@ -487,8 +487,11 @@ public class MongoClient { } closeResult.whenComplete { _ in self.wasClosed = true - if #available(macOS 10.15, *){ + if #available(macOS 10.15, *) { + // Need to use getter to make field exist before finishing + _ = self.sdamEvents self.sdamEvents.finish() + _ = self.commandEvents self.commandEvents.finish() } } From 3453612dadb7b08e3e29668c903c2c457b5f447c Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 8 Jul 2022 16:15:36 -0400 Subject: [PATCH 23/47] Fixing client.close() --- Sources/MongoSwift/MongoClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 0d7b48730..b9bdac8c1 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -489,9 +489,9 @@ public class MongoClient { self.wasClosed = true if #available(macOS 10.15, *) { // Need to use getter to make field exist before finishing - _ = self.sdamEvents + let _ = self.sdamEvents self.sdamEvents.finish() - _ = self.commandEvents + let _ = self.commandEvents self.commandEvents.finish() } } From 3473dac250ad2af5e365db3fb63383e89d952662 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 8 Jul 2022 16:19:46 -0400 Subject: [PATCH 24/47] Skipping client.close() for now --- Sources/MongoSwift/MongoClient.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index b9bdac8c1..c958bfa2d 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -489,10 +489,10 @@ public class MongoClient { self.wasClosed = true if #available(macOS 10.15, *) { // Need to use getter to make field exist before finishing - let _ = self.sdamEvents - self.sdamEvents.finish() - let _ = self.commandEvents - self.commandEvents.finish() +// let _ = self.sdamEvents +// self.sdamEvents.finish() +// let _ = self.commandEvents +// self.commandEvents.finish() } } From 257f3d5e660541727a72ec0c6398ea765f902ec3 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Mon, 11 Jul 2022 14:59:32 -0400 Subject: [PATCH 25/47] On-demand approach, no cache,strong handler --- Sources/MongoSwift/APM.swift | 49 ++++++- Sources/MongoSwift/MongoClient.swift | 197 ++++++++++++++++++++------- Tests/MongoSwiftTests/APMTests.swift | 9 +- 3 files changed, 195 insertions(+), 60 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index a98d9a101..5d4bb0830 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,6 +117,23 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } +// @available(macOS 10.15, *) +// class THandler: CommandEventHandler, SDAMEventHandler { +// let con : AsyncStream.Continuation +// init(con: AsyncStream.Continuation){ +// self.con = con +// } +// func handleCommandEvent(_ event: CommandEvent) { +// con.yield(event as! T) +// +// } +// +// func handleSDAMEvent(_ event: SDAMEvent) { +// con.yield(event as! T) +// +// } +// } + #if compiler(>=5.5.2) && canImport(_Concurrency) /// An asynchronous way to monitor events that uses `AsyncSequence`. /// Only available for Swift 5.5.2 and higher. @@ -125,18 +142,38 @@ private protocol CommandEventProtocol { public struct EventStream { private var stream: AsyncStream private var continuation: AsyncStream.Continuation? - /// Initialize the stream internal init(stream: AsyncStream) { self.stream = stream self.continuation = nil } - /// Set the `AsyncStream.Continuation` property of the the stream - internal mutating func setCon(continuation: AsyncStream.Continuation) { - self.continuation = continuation - } - +// internal init(client: MongoClient) { +// self.continuation = nil +// self.stream = +// AsyncStream ( +// T.self, +// bufferingPolicy: .bufferingNewest(100), +// {con in setUpCon(con: con, client: client)} +// ) +// +// } + +// /// Set the `AsyncStream.Continuation` property of the the stream +// internal mutating func setCon(continuation: AsyncStream.Continuation) { +// self.continuation = continuation +// } +// +// internal mutating func setUpCon(con: AsyncStream.Continuation, client: MongoClient) { +// +// self.continuation = con +// con.onTermination = { @Sendable _ in print("terminadoCMD")} +// if client.wasClosed { +// print("command closed") +// con.finish() +// } +// +// } /// Finish the continuation internal func finish() { self.continuation?.finish() diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index c958bfa2d..f392953d0 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -275,7 +275,7 @@ public class MongoClient { /// Indicates whether this client has been closed. A lock around this variable is not needed because: /// - This value is only modified on success of `ConnectionPool.close()`. That method will succeed exactly once. /// - This value is only read in `deinit`. That occurs exactly once after the above modification is complete. - private var wasClosed = false + internal var wasClosed = false /// Handlers for command monitoring events. internal var commandEventHandlers: [CommandEventHandler] @@ -283,68 +283,64 @@ public class MongoClient { /// Handlers for SDAM monitoring events. internal var sdamEventHandlers: [SDAMEventHandler] - private var _commandEvents: Any? - private var _sdamEvents: Any? +// private var _commandEvents: Any? +// private var _sdamEvents: Any? -#if compiler(>=5.5.2) && canImport(_Concurrency) + // #if compiler(>=5.5.2) && canImport(_Concurrency) /// Provides an `AsyncSequence` API for consuming command monitoring events. /// Example: printing the command events out would be written as /// `for await event in client.commandEvents { print(event) }`. /// Wrapping in a `Task { ... }` may be desired for asynchronicity. /// Note that only the most recent 100 events are stored in the stream. - @available(macOS 10.15, *) - public var commandEvents: CommandEventStream { - get { - if self._commandEvents == nil { - self._commandEvents = CommandEventStream( - stream: AsyncStream( - CommandEvent.self, - bufferingPolicy: .bufferingNewest(100) - ) { con in - self.addCommandEventHandler { event in - con.yield(event) - self.commandEvents.setCon(continuation: con) - } - } - ) - } - // Ok to force cast since we are explictly setting up the CommandEventStream - // swiftlint:disable:next force_cast - return self._commandEvents as! CommandEventStream - } - // swiftlint:disable unused_setter_value - set(setter) {} - } +// @available(macOS 10.15, *) +// public var commandEvents: CommandEventStream { +// get { +// if self._commandEvents == nil { +// self._commandEvents = CommandEventStream( +// stream: AsyncStream( +// CommandEvent.self, +// bufferingPolicy: .bufferingNewest(100) +// ) { con in +// self.addCommandEventHandler { event in +// con.yield(event) +// self.commandEvents.setCon(continuation: con) +// } +// } +// ) +// } +// // Ok to force cast since we are explictly setting up the CommandEventStream +// return self._commandEvents as! CommandEventStream +// } +// set(setter) {} +// } /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. /// Example: printing the SDAM events out would be written as /// `for await event in client.sdamEvents { print(event) }`. /// Wrapping in a `Task { ... }` may be desired for asynchronicity. /// Note that only the most recent 100 events are stored in the stream. - @available(macOS 10.15, *) - public var sdamEvents: SDAMEventStream { - get { - if self._sdamEvents == nil { - self._sdamEvents = SDAMEventStream( - stream: AsyncStream( - SDAMEvent.self, - bufferingPolicy: .bufferingNewest(100) - ) { con in - self.addSDAMEventHandler { event in - con.yield(event) - self.sdamEvents.setCon(continuation: con) - } - } - ) - } - // Ok to force cast since we are explictly setting up the SDAMEventStream - // swiftlint:disable:next force_cast - return self._sdamEvents as! SDAMEventStream - } - // swiftlint:disable unused_setter_value - set(setter) {} - } -#endif +// @available(macOS 10.15, *) +// public var sdamEvents: SDAMEventStream { +// get { +// if self._sdamEvents == nil { +// self._sdamEvents = SDAMEventStream( +// stream: AsyncStream( +// SDAMEvent.self, +// bufferingPolicy: .bufferingNewest(100) +// ) { con in +// self.addSDAMEventHandler { event in +// con.yield(event) +// self.sdamEvents.setCon(continuation: con) +// } +// } +// ) +// } +// // Ok to force cast since we are explictly setting up the SDAMEventStream +// return self._sdamEvents as! SDAMEventStream +// } +// set(setter) {} +// } + // #endif /// Counter for generating client _ids. internal static var clientIDGenerator = NIOAtomic.makeAtomic(value: 0) @@ -428,7 +424,6 @@ public class MongoClient { self.decoder = BSONDecoder(options: options) self.sdamEventHandlers = [] self.commandEventHandlers = [] - // commandEvents and sdamEvents have initial handlers initialized self.connectionPool.initializeMonitoring(client: self) } @@ -466,6 +461,104 @@ public class MongoClient { ) } + @available(macOS 10.15, *) + private class CmdHandler: CommandEventHandler { + var con: AsyncStream.Continuation? + init(con: AsyncStream.Continuation) { + self.con = con + // print("initing") + } + + init() { + self.con = nil + } + + func handleCommandEvent(_ event: CommandEvent) { + // print("handling") + self.con?.yield(event) + } + + func setCon(con: AsyncStream.Continuation) { + self.con = con + } + } + + @available(macOS 10.15, *) + private class SDAMHandler: SDAMEventHandler { + var con: AsyncStream.Continuation? + init(con: AsyncStream.Continuation) { + self.con = con + } + + init() { + self.con = nil + } + + func handleSDAMEvent(_ event: SDAMEvent) { + self.con?.yield(event) + } + + func setCon(con: AsyncStream.Continuation) { + self.con = con + } + } + + /// Provides an `AsyncSequence` API for consuming command monitoring events. + /// Example: printing the command events out would be written as + /// `for await event in client.commandEventStream() { print(event) }`. + /// Wrapping in a `Task { ... }` may be desired for asynchronicity. + /// Note that only the most recent 100 events are stored in the stream. + @available(macOS 10.15, *) + public func commandEventStream() -> CommandEventStream { + // let handler = CmdHandler() + let commandEvents = CommandEventStream( + stream: AsyncStream( + CommandEvent.self, + bufferingPolicy: .bufferingNewest(100) + ) { con in + // adds it strongly + self.addCommandEventHandler { event in con.yield(event) } + // adds it weakly + // handler.setCon(con: con) + // self.addCommandEventHandler(handler) + con.onTermination = { @Sendable _ in print("terminadoCMD") } + if self.wasClosed { + print("command closed") + con.finish() + } + } + ) + return commandEvents + } + + /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. + /// Example: printing the command events out would be written as + /// `for await event in client.sdamEventStream() { print(event) }`. + /// Wrapping in a `Task { ... }` may be desired for asynchronicity. + /// Note that only the most recent 100 events are stored in the stream. + @available(macOS 10.15, *) + public func sdamEventStream() -> SDAMEventStream { + // let handler = SDAMHandler() + let sdamEvents = SDAMEventStream( + stream: AsyncStream( + SDAMEvent.self, + bufferingPolicy: .bufferingNewest(100) + ) { con in + // adds it strongly + self.addSDAMEventHandler { event in con.yield(event) } + // adds it weakly + // handler.setCon(con: con) + // self.addSDAMEventHandler(handler) + con.onTermination = { @Sendable _ in print("terminadoSDAM") } + if self.wasClosed { + print("sdam closed") + con.finish() + } + } + ) + return sdamEvents + } + /** * Closes this `MongoClient`, closing all connections to the server and cleaning up internal state. * diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index c67283d60..2e92ea965 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -16,13 +16,17 @@ final class APMTests: MongoSwiftTestCase { // let cmdEventsTask = Task { () -> Int in Task { var i = 0 - for try await event in client.commandEvents { + let outputter = client.commandEventStream() + for try await event in outputter { + // print("cmd-event") expect(commandStr[i]).to(equal(event.commandName)) expect(eventTypes[i]).to(equal(event.type)) i += 1 } + // print("exiting...") // return i } + defer { _ = client.close() } try await client.db("admin").runCommand(["ping": 1]) // try await client.close() @@ -52,8 +56,9 @@ final class APMTests: MongoSwiftTestCase { } } // Async so cannot lock - for try await event in client.sdamEvents { + for try await event in client.sdamEventStream() { if !event.isHeartbeatEvent { + // print("sdam-event") expect(event.type).to(equal(eventTypes[i])) i += 1 } From e9eb04201feb92432e1a9bea23db9ebfcd350149 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Mon, 11 Jul 2022 16:41:31 -0400 Subject: [PATCH 26/47] Caching continuation, .finish() for cmd test --- Sources/MongoSwift/APM.swift | 4 ++++ Sources/MongoSwift/MongoClient.swift | 15 +++++++++++---- Tests/MongoSwiftTests/APMTests.swift | 26 ++++++++++++-------------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 5d4bb0830..77a8c3d49 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -148,6 +148,10 @@ public struct EventStream { self.continuation = nil } + internal mutating func setCon(con: AsyncStream.Continuation?) { + self.continuation = con + } + // internal init(client: MongoClient) { // self.continuation = nil // self.stream = diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index f392953d0..ee914e024 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -510,8 +510,9 @@ public class MongoClient { /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func commandEventStream() -> CommandEventStream { - // let handler = CmdHandler() - let commandEvents = CommandEventStream( + let handler = CmdHandler() + var outerCon: AsyncStream.Continuation? + var commandEvents = CommandEventStream( stream: AsyncStream( CommandEvent.self, bufferingPolicy: .bufferingNewest(100) @@ -519,8 +520,9 @@ public class MongoClient { // adds it strongly self.addCommandEventHandler { event in con.yield(event) } // adds it weakly - // handler.setCon(con: con) + handler.setCon(con: con) // self.addCommandEventHandler(handler) + outerCon = con con.onTermination = { @Sendable _ in print("terminadoCMD") } if self.wasClosed { print("command closed") @@ -528,6 +530,8 @@ public class MongoClient { } } ) + commandEvents.setCon(con: outerCon) + return commandEvents } @@ -539,7 +543,8 @@ public class MongoClient { @available(macOS 10.15, *) public func sdamEventStream() -> SDAMEventStream { // let handler = SDAMHandler() - let sdamEvents = SDAMEventStream( + var outerCon: AsyncStream.Continuation? + var sdamEvents = SDAMEventStream( stream: AsyncStream( SDAMEvent.self, bufferingPolicy: .bufferingNewest(100) @@ -549,6 +554,7 @@ public class MongoClient { // adds it weakly // handler.setCon(con: con) // self.addSDAMEventHandler(handler) + outerCon = con con.onTermination = { @Sendable _ in print("terminadoSDAM") } if self.wasClosed { print("sdam closed") @@ -556,6 +562,7 @@ public class MongoClient { } } ) + sdamEvents.setCon(con: outerCon) return sdamEvents } diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 2e92ea965..964f9cdc8 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -17,26 +17,21 @@ final class APMTests: MongoSwiftTestCase { Task { var i = 0 let outputter = client.commandEventStream() + // outputter.finish() for try await event in outputter { - // print("cmd-event") + print("cmd-event") expect(commandStr[i]).to(equal(event.commandName)) expect(eventTypes[i]).to(equal(event.type)) i += 1 + if i == 4 { + outputter.finish() + } } - // print("exiting...") - // return i + print("exiting...") + expect(i).to(be(4)) } - defer { _ = client.close() } try await client.db("admin").runCommand(["ping": 1]) -// try await client.close() -// let taskResult = await cmdEventsTask.result -// do { -// let output = try taskResult.get() -// expect(output).to(equal(4)) -// } catch { -// print("oopsies") -// } } } @@ -56,13 +51,16 @@ final class APMTests: MongoSwiftTestCase { } } // Async so cannot lock - for try await event in client.sdamEventStream() { + let outputter = client.sdamEventStream() + for try await event in outputter { if !event.isHeartbeatEvent { - // print("sdam-event") + print("sdam-event") expect(event.type).to(equal(eventTypes[i])) i += 1 } } + // Doesnt print since we dont .finish() + print("goodbye") } try await client.db("admin").runCommand(["ping": 1]) From f501c66aef18282e9be13362fef6d9cb629ae2a4 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 12 Jul 2022 09:53:55 -0400 Subject: [PATCH 27/47] Weak handler, only works with outputter not .cmdstream --- Sources/MongoSwift/APM.swift | 35 +++++++++--------- Sources/MongoSwift/MongoClient.swift | 54 ++++++++++++++++++---------- Tests/MongoSwiftTests/APMTests.swift | 3 ++ 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 77a8c3d49..4ca9917b6 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -117,23 +117,6 @@ private protocol CommandEventProtocol { var serviceID: BSONObjectID? { get } } -// @available(macOS 10.15, *) -// class THandler: CommandEventHandler, SDAMEventHandler { -// let con : AsyncStream.Continuation -// init(con: AsyncStream.Continuation){ -// self.con = con -// } -// func handleCommandEvent(_ event: CommandEvent) { -// con.yield(event as! T) -// -// } -// -// func handleSDAMEvent(_ event: SDAMEvent) { -// con.yield(event as! T) -// -// } -// } - #if compiler(>=5.5.2) && canImport(_Concurrency) /// An asynchronous way to monitor events that uses `AsyncSequence`. /// Only available for Swift 5.5.2 and higher. @@ -142,16 +125,34 @@ private protocol CommandEventProtocol { public struct EventStream { private var stream: AsyncStream private var continuation: AsyncStream.Continuation? + // private var handler: THandler? + private var cmdHandler: CommandEventHandler? + private var sdamHandler: SDAMEventHandler? /// Initialize the stream internal init(stream: AsyncStream) { self.stream = stream self.continuation = nil + // self.handler = nil + self.cmdHandler = nil + self.sdamHandler = nil } internal mutating func setCon(con: AsyncStream.Continuation?) { self.continuation = con } +// internal mutating func setHandler(handler: THandler) { +// self.handler = handler +// } + + internal mutating func setCmdHandler(cmdHandler: CommandEventHandler) { + self.cmdHandler = cmdHandler + } + + internal mutating func setSdamHandler(sdamHandler: SDAMEventHandler) { + self.sdamHandler = sdamHandler + } + // internal init(client: MongoClient) { // self.continuation = nil // self.stream = diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index ee914e024..1a2c05e09 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -462,47 +462,61 @@ public class MongoClient { } @available(macOS 10.15, *) - private class CmdHandler: CommandEventHandler { - var con: AsyncStream.Continuation? - init(con: AsyncStream.Continuation) { + internal class CmdHandler: CommandEventHandler { + private var con: AsyncStream.Continuation? + internal init(con: AsyncStream.Continuation) { self.con = con // print("initing") } - init() { + internal init() { self.con = nil } - func handleCommandEvent(_ event: CommandEvent) { - // print("handling") + internal func handleCommandEvent(_ event: CommandEvent) { + // never prints anything + print("handling") + if self.con == nil { + print("nil land") + } self.con?.yield(event) } - func setCon(con: AsyncStream.Continuation) { + internal func setCon(con: AsyncStream.Continuation) { self.con = con } + + internal func inScope() { + print("I am in scope") + } } @available(macOS 10.15, *) - private class SDAMHandler: SDAMEventHandler { - var con: AsyncStream.Continuation? - init(con: AsyncStream.Continuation) { + internal class SDAMHandler: SDAMEventHandler { + private var con: AsyncStream.Continuation? + internal init(con: AsyncStream.Continuation) { self.con = con } - init() { + internal init() { self.con = nil } - func handleSDAMEvent(_ event: SDAMEvent) { + internal func handleSDAMEvent(_ event: SDAMEvent) { self.con?.yield(event) } - func setCon(con: AsyncStream.Continuation) { + internal func setCon(con: AsyncStream.Continuation) { self.con = con } } + @available(macOS 10.15, *) + internal enum THandler { + case CmdHandler(CmdHandler) + case SdamHandler(SDAMHandler) + } + /// Provides an `AsyncSequence` API for consuming command monitoring events. /// Example: printing the command events out would be written as /// `for await event in client.commandEventStream() { print(event) }`. @@ -510,6 +524,7 @@ public class MongoClient { /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func commandEventStream() -> CommandEventStream { + // Keep handler outside so it has scope after commandEvents is init'd let handler = CmdHandler() var outerCon: AsyncStream.Continuation? var commandEvents = CommandEventStream( @@ -518,10 +533,10 @@ public class MongoClient { bufferingPolicy: .bufferingNewest(100) ) { con in // adds it strongly - self.addCommandEventHandler { event in con.yield(event) } + // self.addCommandEventHandler { event in con.yield(event) } // adds it weakly handler.setCon(con: con) - // self.addCommandEventHandler(handler) + self.addCommandEventHandler(handler) outerCon = con con.onTermination = { @Sendable _ in print("terminadoCMD") } if self.wasClosed { @@ -531,8 +546,10 @@ public class MongoClient { } ) commandEvents.setCon(con: outerCon) + commandEvents.setCmdHandler(cmdHandler: handler) + handler.inScope() - return commandEvents + return commandEvents // , handler) } /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. @@ -542,7 +559,7 @@ public class MongoClient { /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func sdamEventStream() -> SDAMEventStream { - // let handler = SDAMHandler() + let handler = SDAMHandler() var outerCon: AsyncStream.Continuation? var sdamEvents = SDAMEventStream( stream: AsyncStream( @@ -552,7 +569,7 @@ public class MongoClient { // adds it strongly self.addSDAMEventHandler { event in con.yield(event) } // adds it weakly - // handler.setCon(con: con) + handler.setCon(con: con) // self.addSDAMEventHandler(handler) outerCon = con con.onTermination = { @Sendable _ in print("terminadoSDAM") } @@ -563,6 +580,7 @@ public class MongoClient { } ) sdamEvents.setCon(con: outerCon) + sdamEvents.setSdamHandler(sdamHandler: handler) return sdamEvents } diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 964f9cdc8..b66c2747b 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -17,6 +17,9 @@ final class APMTests: MongoSwiftTestCase { Task { var i = 0 let outputter = client.commandEventStream() +// let outputter = result.0 +// let handler = result.1 + // outputter.finish() for try await event in outputter { print("cmd-event") From 455cc3b799a8924595aa7fdd8f85a6922a2010ac Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 12 Jul 2022 10:09:37 -0400 Subject: [PATCH 28/47] Modified compiler directives+documentation --- Sources/MongoSwift/MongoClient.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 1a2c05e09..c1e9f751a 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -461,6 +461,7 @@ public class MongoClient { ) } +#if compiler(>=5.5.2) && canImport(_Concurrency) @available(macOS 10.15, *) internal class CmdHandler: CommandEventHandler { private var con: AsyncStream.Continuation? @@ -519,7 +520,7 @@ public class MongoClient { /// Provides an `AsyncSequence` API for consuming command monitoring events. /// Example: printing the command events out would be written as - /// `for await event in client.commandEventStream() { print(event) }`. + /// c /// Wrapping in a `Task { ... }` may be desired for asynchronicity. /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) @@ -553,8 +554,9 @@ public class MongoClient { } /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. - /// Example: printing the command events out would be written as - /// `for await event in client.sdamEventStream() { print(event) }`. + /// Example: printing the SDAM events out would be written as + /// `let stream = client.sdamEventStream() + /// `for await event in stream { print(event) }`. /// Wrapping in a `Task { ... }` may be desired for asynchronicity. /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) @@ -583,7 +585,7 @@ public class MongoClient { sdamEvents.setSdamHandler(sdamHandler: handler) return sdamEvents } - +#endif /** * Closes this `MongoClient`, closing all connections to the server and cleaning up internal state. * From db7c6a79aef8057608c687b196d7567192d96268 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 12 Jul 2022 10:43:33 -0400 Subject: [PATCH 29/47] Annotations on test, good but lot of extra comments --- Sources/MongoSwift/APM.swift | 2 +- Sources/MongoSwift/MongoClient.swift | 16 ++++++++-------- Tests/MongoSwiftTests/APMTests.swift | 13 ++++++++----- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 4ca9917b6..2c7de6fb4 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -180,7 +180,7 @@ public struct EventStream { // // } /// Finish the continuation - internal func finish() { + public func finish() { self.continuation?.finish() } } diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index c1e9f751a..a7b26c294 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -476,10 +476,10 @@ public class MongoClient { internal func handleCommandEvent(_ event: CommandEvent) { // never prints anything - print("handling") - if self.con == nil { - print("nil land") - } + // print("handling") +// if self.con == nil { +// //print("nil land") +// } self.con?.yield(event) } @@ -487,9 +487,9 @@ public class MongoClient { self.con = con } - internal func inScope() { - print("I am in scope") - } +// internal func inScope() { +// print("I am in scope") +// } } @available(macOS 10.15, *) @@ -548,7 +548,7 @@ public class MongoClient { ) commandEvents.setCon(con: outerCon) commandEvents.setCmdHandler(cmdHandler: handler) - handler.inScope() + // handler.inScope() return commandEvents // , handler) } diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index b66c2747b..23a7656ef 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -1,8 +1,10 @@ -@testable import MongoSwift +#if compiler(>=5.5.2) && canImport(_Concurrency) +import MongoSwift import Nimble import NIOConcurrencyHelpers import TestsCommon +@available(macOS 10.15, *) final class APMTests: MongoSwiftTestCase { func testCommandEventStreamClient() async throws { try await self.withTestClient { client in @@ -22,7 +24,7 @@ final class APMTests: MongoSwiftTestCase { // outputter.finish() for try await event in outputter { - print("cmd-event") + // print("cmd-event") expect(commandStr[i]).to(equal(event.commandName)) expect(eventTypes[i]).to(equal(event.type)) i += 1 @@ -30,7 +32,7 @@ final class APMTests: MongoSwiftTestCase { outputter.finish() } } - print("exiting...") + // print("exiting...") expect(i).to(be(4)) } @@ -57,13 +59,13 @@ final class APMTests: MongoSwiftTestCase { let outputter = client.sdamEventStream() for try await event in outputter { if !event.isHeartbeatEvent { - print("sdam-event") + // print("sdam-event") expect(event.type).to(equal(eventTypes[i])) i += 1 } } // Doesnt print since we dont .finish() - print("goodbye") + // print("goodbye") } try await client.db("admin").runCommand(["ping": 1]) @@ -71,3 +73,4 @@ final class APMTests: MongoSwiftTestCase { } } } +#endif From a47a4ed51d3664480f0a4d6e0c85c3be0a1d0edf Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 12 Jul 2022 11:43:29 -0400 Subject: [PATCH 30/47] Removing commented code/prints, beefing up docstring --- Sources/MongoSwift/APM.swift | 41 ++++--------------------- Sources/MongoSwift/MongoClient.swift | 45 ++++------------------------ Tests/MongoSwiftTests/APMTests.swift | 21 +++++-------- 3 files changed, 19 insertions(+), 88 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 2c7de6fb4..0d3f4afcd 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -125,26 +125,22 @@ private protocol CommandEventProtocol { public struct EventStream { private var stream: AsyncStream private var continuation: AsyncStream.Continuation? - // private var handler: THandler? private var cmdHandler: CommandEventHandler? private var sdamHandler: SDAMEventHandler? + /// Initialize the stream internal init(stream: AsyncStream) { self.stream = stream self.continuation = nil - // self.handler = nil self.cmdHandler = nil self.sdamHandler = nil } + // Setters that are called after init() internal mutating func setCon(con: AsyncStream.Continuation?) { self.continuation = con } -// internal mutating func setHandler(handler: THandler) { -// self.handler = handler -// } - internal mutating func setCmdHandler(cmdHandler: CommandEventHandler) { self.cmdHandler = cmdHandler } @@ -153,33 +149,10 @@ public struct EventStream { self.sdamHandler = sdamHandler } -// internal init(client: MongoClient) { -// self.continuation = nil -// self.stream = -// AsyncStream ( -// T.self, -// bufferingPolicy: .bufferingNewest(100), -// {con in setUpCon(con: con, client: client)} -// ) -// -// } - -// /// Set the `AsyncStream.Continuation` property of the the stream -// internal mutating func setCon(continuation: AsyncStream.Continuation) { -// self.continuation = continuation -// } -// -// internal mutating func setUpCon(con: AsyncStream.Continuation, client: MongoClient) { -// -// self.continuation = con -// con.onTermination = { @Sendable _ in print("terminadoCMD")} -// if client.wasClosed { -// print("command closed") -// con.finish() -// } -// -// } - /// Finish the continuation + /// Finishes the stream by having the task return nil, + /// which signifies the end of the iteration. Calling this function more than once has no effect. + /// After calling finish, the stream enters a terminal state and doesn't produces any additional + /// elements. public func finish() { self.continuation?.finish() } @@ -198,8 +171,6 @@ extension EventStream: AsyncSequence { public func makeAsyncIterator() -> EventStreamIterator { EventStreamIterator(asyncStream: self.stream) } - - // startMonitoring? } @available(macOS 10.15, *) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index a7b26c294..aa363d1cb 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -463,37 +463,28 @@ public class MongoClient { #if compiler(>=5.5.2) && canImport(_Concurrency) @available(macOS 10.15, *) - internal class CmdHandler: CommandEventHandler { + private class CmdHandler: CommandEventHandler { private var con: AsyncStream.Continuation? internal init(con: AsyncStream.Continuation) { self.con = con - // print("initing") } internal init() { self.con = nil } + // Satisfies the protocol internal func handleCommandEvent(_ event: CommandEvent) { - // never prints anything - // print("handling") -// if self.con == nil { -// //print("nil land") -// } self.con?.yield(event) } internal func setCon(con: AsyncStream.Continuation) { self.con = con } - -// internal func inScope() { -// print("I am in scope") -// } } @available(macOS 10.15, *) - internal class SDAMHandler: SDAMEventHandler { + private class SDAMHandler: SDAMEventHandler { private var con: AsyncStream.Continuation? internal init(con: AsyncStream.Continuation) { self.con = con @@ -503,6 +494,7 @@ public class MongoClient { self.con = nil } + // Satisfies the protocol internal func handleSDAMEvent(_ event: SDAMEvent) { self.con?.yield(event) } @@ -512,12 +504,6 @@ public class MongoClient { } } - @available(macOS 10.15, *) - internal enum THandler { - case CmdHandler(CmdHandler) - case SdamHandler(SDAMHandler) - } - /// Provides an `AsyncSequence` API for consuming command monitoring events. /// Example: printing the command events out would be written as /// c @@ -525,7 +511,6 @@ public class MongoClient { /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func commandEventStream() -> CommandEventStream { - // Keep handler outside so it has scope after commandEvents is init'd let handler = CmdHandler() var outerCon: AsyncStream.Continuation? var commandEvents = CommandEventStream( @@ -533,24 +518,18 @@ public class MongoClient { CommandEvent.self, bufferingPolicy: .bufferingNewest(100) ) { con in - // adds it strongly - // self.addCommandEventHandler { event in con.yield(event) } - // adds it weakly handler.setCon(con: con) self.addCommandEventHandler(handler) outerCon = con - con.onTermination = { @Sendable _ in print("terminadoCMD") } if self.wasClosed { - print("command closed") con.finish() } } ) commandEvents.setCon(con: outerCon) commandEvents.setCmdHandler(cmdHandler: handler) - // handler.inScope() - return commandEvents // , handler) + return commandEvents } /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. @@ -568,15 +547,10 @@ public class MongoClient { SDAMEvent.self, bufferingPolicy: .bufferingNewest(100) ) { con in - // adds it strongly - self.addSDAMEventHandler { event in con.yield(event) } - // adds it weakly handler.setCon(con: con) - // self.addSDAMEventHandler(handler) + self.addSDAMEventHandler(handler) outerCon = con - con.onTermination = { @Sendable _ in print("terminadoSDAM") } if self.wasClosed { - print("sdam closed") con.finish() } } @@ -607,13 +581,6 @@ public class MongoClient { } closeResult.whenComplete { _ in self.wasClosed = true - if #available(macOS 10.15, *) { - // Need to use getter to make field exist before finishing -// let _ = self.sdamEvents -// self.sdamEvents.finish() -// let _ = self.commandEvents -// self.commandEvents.finish() - } } return closeResult diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 23a7656ef..372caac54 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -18,21 +18,16 @@ final class APMTests: MongoSwiftTestCase { // let cmdEventsTask = Task { () -> Int in Task { var i = 0 - let outputter = client.commandEventStream() -// let outputter = result.0 -// let handler = result.1 - - // outputter.finish() - for try await event in outputter { - // print("cmd-event") + let cmdStream = client.commandEventStream() + for try await event in cmdStream { + print("cmd-event") expect(commandStr[i]).to(equal(event.commandName)) expect(eventTypes[i]).to(equal(event.type)) i += 1 if i == 4 { - outputter.finish() + cmdStream.finish() } } - // print("exiting...") expect(i).to(be(4)) } @@ -56,16 +51,14 @@ final class APMTests: MongoSwiftTestCase { } } // Async so cannot lock - let outputter = client.sdamEventStream() - for try await event in outputter { + let sdamStream = client.sdamEventStream() + for try await event in sdamStream { if !event.isHeartbeatEvent { - // print("sdam-event") expect(event.type).to(equal(eventTypes[i])) i += 1 } } - // Doesnt print since we dont .finish() - // print("goodbye") + // Doesnt exit since we dont .finish() } try await client.db("admin").runCommand(["ping": 1]) From 1f5a971424db389a3077811adfde5eb33ad9c05f Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 12 Jul 2022 14:08:17 -0400 Subject: [PATCH 31/47] Removing old task code --- Tests/MongoSwiftTests/APMTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 372caac54..53b8ca722 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -15,7 +15,6 @@ final class APMTests: MongoSwiftTestCase { .commandStartedEvent, .commandSucceededEvent ] - // let cmdEventsTask = Task { () -> Int in Task { var i = 0 let cmdStream = client.commandEventStream() From 850ac0c301b9e13850532a99afaa1b9b4e8680ca Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 13 Jul 2022 17:05:29 -0400 Subject: [PATCH 32/47] Removing generic types, new tests, peer programming --- Sources/MongoSwift/APM.swift | 121 +++++---- Sources/MongoSwift/MongoClient.swift | 186 ++++++------- Tests/MongoSwiftTests/APMTests.swift | 248 +++++++++++++++--- .../MongoSwiftTests/AsyncAwaitTestUtils.swift | 19 ++ 4 files changed, 384 insertions(+), 190 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 0d3f4afcd..3e618292c 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -13,6 +13,10 @@ public protocol SDAMEventHandler: AnyObject { func handleSDAMEvent(_ event: SDAMEvent) } +internal protocol EventHandler { + associatedtype HandlerEventType +} + /// A protocol for events that are directly consumable by users to implement. private protocol Publishable { func publish(to client: MongoClient) @@ -118,87 +122,108 @@ private protocol CommandEventProtocol { } #if compiler(>=5.5.2) && canImport(_Concurrency) -/// An asynchronous way to monitor events that uses `AsyncSequence`. +/// An asynchronous way to monitor command events that uses `AsyncSequence`. /// Only available for Swift 5.5.2 and higher. @available(macOS 10.15, *) // sourcery: skipSyncExport -public struct EventStream { - private var stream: AsyncStream - private var continuation: AsyncStream.Continuation? - private var cmdHandler: CommandEventHandler? - private var sdamHandler: SDAMEventHandler? - - /// Initialize the stream - internal init(stream: AsyncStream) { +public struct CommandEventStream { + fileprivate let stream: AsyncStream + private let cmdHandler: CommandEventHandler + /// Initialize the stream with a `CommandEventHandler` + internal init(cmdHandler: CommandEventHandler, stream: AsyncStream) { + self.cmdHandler = cmdHandler self.stream = stream - self.continuation = nil - self.cmdHandler = nil - self.sdamHandler = nil } +} - // Setters that are called after init() - internal mutating func setCon(con: AsyncStream.Continuation?) { - self.continuation = con +/// An asynchronous way to monitor SDAM events that uses `AsyncSequence`. +/// Only available for Swift 5.5.2 and higher. +@available(macOS 10.15, *) +// sourcery: skipSyncExport +public struct SDAMEventStream { + fileprivate let stream: AsyncStream + private let sdamHandler: SDAMEventHandler + /// Initialize the stream with a `CommandEventHandler` + internal init(sdamHandler: SDAMEventHandler, stream: AsyncStream) { + self.sdamHandler = sdamHandler + self.stream = stream } +} - internal mutating func setCmdHandler(cmdHandler: CommandEventHandler) { - self.cmdHandler = cmdHandler - } +@available(macOS 10.15, *) +extension CommandEventStream: AsyncSequence { + /// The type of element produced by this `CommandEventStream`. + public typealias Element = CommandEvent - internal mutating func setSdamHandler(sdamHandler: SDAMEventHandler) { - self.sdamHandler = sdamHandler - } + /// The asynchronous iterator of type `CommandEventStreamIterator` + /// that produces elements of this asynchronous sequence. + public typealias AsyncIterator = CommandEventStreamIterator - /// Finishes the stream by having the task return nil, - /// which signifies the end of the iteration. Calling this function more than once has no effect. - /// After calling finish, the stream enters a terminal state and doesn't produces any additional - /// elements. - public func finish() { - self.continuation?.finish() + /// Creates the asynchronous iterator that produces elements of this `CommandEventStream`. + public func makeAsyncIterator() -> CommandEventStreamIterator { + CommandEventStreamIterator(cmdEventStream: self) } } @available(macOS 10.15, *) -extension EventStream: AsyncSequence { - /// The type of element produced by this `EventStream`. - public typealias Element = T +extension SDAMEventStream: AsyncSequence { + /// The type of element produced by this `SDAMEventStream`. + public typealias Element = SDAMEvent - /// The asynchronous iterator of type `EventStreamIterator` + /// The asynchronous iterator of type `SDAMEventStreamIterator` /// that produces elements of this asynchronous sequence. - public typealias AsyncIterator = EventStreamIterator + public typealias AsyncIterator = SDAMEventStreamIterator - /// Creates the asynchronous iterator that produces elements of this `EventStream`. - public func makeAsyncIterator() -> EventStreamIterator { - EventStreamIterator(asyncStream: self.stream) + /// Creates the asynchronous iterator that produces elements of this `SDAMEventStream`. + public func makeAsyncIterator() -> SDAMEventStreamIterator { + SDAMEventStreamIterator(sdamEventStream: self) } } +/// The associated iterator for the `CommandEventStream`. @available(macOS 10.15, *) // sourcery: skipSyncExport -public struct EventStreamIterator: AsyncIteratorProtocol { - private var iterator: AsyncStream.AsyncIterator? +public struct CommandEventStreamIterator: AsyncIteratorProtocol { + private var iterator: AsyncStream.AsyncIterator + private let cmdEventStream: CommandEventStream /// Initialize the iterator - internal init(asyncStream: AsyncStream) { - self.iterator = asyncStream.makeAsyncIterator() + internal init(cmdEventStream: CommandEventStream) { + self.iterator = cmdEventStream.stream.makeAsyncIterator() + self.cmdEventStream = cmdEventStream } /// Asynchronously advances to the next element and returns it, or ends the sequence if there is no next element. - public mutating func next() async throws -> T? { - await self.iterator?.next() + public mutating func next() async throws -> CommandEvent? { + await self.iterator.next() } - /// The type of element iterated over by this `EventStreamIterator`. - public typealias Element = T + /// The type of element iterated over by this `CommandEventStreamIterator`. + public typealias Element = CommandEvent } -/// An asynchronous way to monitor command events using `EventStream`. +/// The associated iterator for the `SDAMEventStream`. @available(macOS 10.15, *) -public typealias CommandEventStream = EventStream +// sourcery: skipSyncExport +public struct SDAMEventStreamIterator: AsyncIteratorProtocol { + private var iterator: AsyncStream.AsyncIterator + private let sdamEventStream: SDAMEventStream + + /// Initialize the iterator + internal init(sdamEventStream: SDAMEventStream) { + self.iterator = sdamEventStream.stream.makeAsyncIterator() + self.sdamEventStream = sdamEventStream + } + + /// Asynchronously advances to the next element and returns it, or ends the sequence if there is no next element. + public mutating func next() async throws -> SDAMEvent? { + await self.iterator.next() + } + + /// The type of element iterated over by this `SDAMEventStreamIterator`. + public typealias Element = SDAMEvent +} -/// An asynchronous way to monitor SDAM events using `EventStream`. -@available(macOS 10.15, *) -public typealias SDAMEventStream = EventStream #endif /// An event published when a command starts. diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index aa363d1cb..7d7bc3f83 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -283,65 +283,6 @@ public class MongoClient { /// Handlers for SDAM monitoring events. internal var sdamEventHandlers: [SDAMEventHandler] -// private var _commandEvents: Any? -// private var _sdamEvents: Any? - - // #if compiler(>=5.5.2) && canImport(_Concurrency) - /// Provides an `AsyncSequence` API for consuming command monitoring events. - /// Example: printing the command events out would be written as - /// `for await event in client.commandEvents { print(event) }`. - /// Wrapping in a `Task { ... }` may be desired for asynchronicity. - /// Note that only the most recent 100 events are stored in the stream. -// @available(macOS 10.15, *) -// public var commandEvents: CommandEventStream { -// get { -// if self._commandEvents == nil { -// self._commandEvents = CommandEventStream( -// stream: AsyncStream( -// CommandEvent.self, -// bufferingPolicy: .bufferingNewest(100) -// ) { con in -// self.addCommandEventHandler { event in -// con.yield(event) -// self.commandEvents.setCon(continuation: con) -// } -// } -// ) -// } -// // Ok to force cast since we are explictly setting up the CommandEventStream -// return self._commandEvents as! CommandEventStream -// } -// set(setter) {} -// } - - /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. - /// Example: printing the SDAM events out would be written as - /// `for await event in client.sdamEvents { print(event) }`. - /// Wrapping in a `Task { ... }` may be desired for asynchronicity. - /// Note that only the most recent 100 events are stored in the stream. -// @available(macOS 10.15, *) -// public var sdamEvents: SDAMEventStream { -// get { -// if self._sdamEvents == nil { -// self._sdamEvents = SDAMEventStream( -// stream: AsyncStream( -// SDAMEvent.self, -// bufferingPolicy: .bufferingNewest(100) -// ) { con in -// self.addSDAMEventHandler { event in -// con.yield(event) -// self.sdamEvents.setCon(continuation: con) -// } -// } -// ) -// } -// // Ok to force cast since we are explictly setting up the SDAMEventStream -// return self._sdamEvents as! SDAMEventStream -// } -// set(setter) {} -// } - // #endif - /// Counter for generating client _ids. internal static var clientIDGenerator = NIOAtomic.makeAtomic(value: 0) @@ -463,103 +404,126 @@ public class MongoClient { #if compiler(>=5.5.2) && canImport(_Concurrency) @available(macOS 10.15, *) - private class CmdHandler: CommandEventHandler { - private var con: AsyncStream.Continuation? - internal init(con: AsyncStream.Continuation) { - self.con = con + internal class CmdHandler: CommandEventHandler { + private var continuation: AsyncStream.Continuation? + internal init(continuation: AsyncStream.Continuation) { + self.continuation = continuation } internal init() { - self.con = nil + self.continuation = nil } // Satisfies the protocol internal func handleCommandEvent(_ event: CommandEvent) { - self.con?.yield(event) + self.continuation?.yield(event) + } + + // Sets the continuation after init'ing + internal func setCon(continuation: AsyncStream.Continuation) { + self.continuation = continuation } - internal func setCon(con: AsyncStream.Continuation) { - self.con = con + internal func finish() { + // Ok to force unwrap since continuations are always set + // swiftlint:disable force_unwrapping + self.continuation!.finish() + print("continuation finishes") } } @available(macOS 10.15, *) - private class SDAMHandler: SDAMEventHandler { - private var con: AsyncStream.Continuation? - internal init(con: AsyncStream.Continuation) { - self.con = con + internal class SDAMHandler: SDAMEventHandler { + private var continuation: AsyncStream.Continuation? + internal init(continuation: AsyncStream.Continuation) { + self.continuation = continuation } internal init() { - self.con = nil + self.continuation = nil } // Satisfies the protocol internal func handleSDAMEvent(_ event: SDAMEvent) { - self.con?.yield(event) + self.continuation?.yield(event) } - internal func setCon(con: AsyncStream.Continuation) { - self.con = con + // Sets the continuation after init'ing + internal func setCon(continuation: AsyncStream.Continuation) { + self.continuation = continuation + } + + internal func finish() { + // Ok to force unwrap since continuations are always set + // swiftlint:disable force_unwrapping + self.continuation!.finish() } } /// Provides an `AsyncSequence` API for consuming command monitoring events. /// Example: printing the command events out would be written as - /// c + /// `for await event in client.commandEventStream() { print(event) }`. /// Wrapping in a `Task { ... }` may be desired for asynchronicity. /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func commandEventStream() -> CommandEventStream { let handler = CmdHandler() - var outerCon: AsyncStream.Continuation? - var commandEvents = CommandEventStream( - stream: AsyncStream( - CommandEvent.self, - bufferingPolicy: .bufferingNewest(100) - ) { con in - handler.setCon(con: con) - self.addCommandEventHandler(handler) - outerCon = con - if self.wasClosed { - con.finish() - } - } + let stream = AsyncStream( + CommandEvent.self, + bufferingPolicy: .bufferingNewest(100) + ) { con in + handler.setCon(continuation: con) + self.addCommandEventHandler(handler) + } + + let commandEvents = CommandEventStream( + cmdHandler: handler, stream: stream ) - commandEvents.setCon(con: outerCon) - commandEvents.setCmdHandler(cmdHandler: handler) return commandEvents } /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. /// Example: printing the SDAM events out would be written as - /// `let stream = client.sdamEventStream() - /// `for await event in stream { print(event) }`. + /// `for await event in client.sdamEventStream() { print(event) }`. /// Wrapping in a `Task { ... }` may be desired for asynchronicity. /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func sdamEventStream() -> SDAMEventStream { let handler = SDAMHandler() - var outerCon: AsyncStream.Continuation? - var sdamEvents = SDAMEventStream( - stream: AsyncStream( - SDAMEvent.self, - bufferingPolicy: .bufferingNewest(100) - ) { con in - handler.setCon(con: con) - self.addSDAMEventHandler(handler) - outerCon = con - if self.wasClosed { - con.finish() - } - } - ) - sdamEvents.setCon(con: outerCon) - sdamEvents.setSdamHandler(sdamHandler: handler) + let stream = AsyncStream( + SDAMEvent.self, + bufferingPolicy: .bufferingNewest(100) + ) { con in + handler.setCon(continuation: con) + self.addSDAMEventHandler(handler) + } + + let sdamEvents = SDAMEventStream(sdamHandler: handler, stream: stream) return sdamEvents } #endif + + // Check which handlers are assoc. with streams and finish them + private func closeHandlers() { +#if compiler(>=5.5.2) && canImport(_Concurrency) + if #available(macOS 10.15, *) { + for handler in commandEventHandlers { + // print(handler) + if let cmdHandler = handler as? WeakEventHandler { + cmdHandler.handler?.finish() + print("closing") + } + } + for handler in sdamEventHandlers { + if let sdamHandler = handler as? WeakEventHandler { + sdamHandler.handler?.finish() + } + } + } +#endif + } + /** * Closes this `MongoClient`, closing all connections to the server and cleaning up internal state. * @@ -580,6 +544,7 @@ public class MongoClient { self.operationExecutor.shutdown() } closeResult.whenComplete { _ in + self.closeHandlers() self.wasClosed = true } @@ -599,6 +564,7 @@ public class MongoClient { public func syncClose() throws { try self.connectionPool.close() try self.operationExecutor.syncShutdown() + self.closeHandlers() self.wasClosed = true } @@ -1040,7 +1006,7 @@ extension CallbackEventHandler: CommandEventHandler where EventType == CommandEv /// Event handler that stores a weak reference to the underlying handler. private class WeakEventHandler { - private weak var handler: T? + internal weak var handler: T? fileprivate init(referencing handler: T) { self.handler = handler diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 53b8ca722..9d4d82e6a 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -1,68 +1,252 @@ #if compiler(>=5.5.2) && canImport(_Concurrency) import MongoSwift import Nimble +import NIO import NIOConcurrencyHelpers import TestsCommon @available(macOS 10.15, *) final class APMTests: MongoSwiftTestCase { - func testCommandEventStreamClient() async throws { + func testClientFinishesCommandStreams() async throws { + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { elg.syncShutdownOrFail() } + let client = try MongoClient.makeTestClient(eventLoopGroup: elg) + + // Task to create a commandEventStream and should return when the client is closed + let eventTask = Task { () -> Bool in + // let cmdStream = client.commandEventStream() + for try await _ in client.commandEventStream() { + print("here") + } + print("out of loop") + return true + } + try await client.db("admin").runCommand(["ping": 1]) + try await client.close() + + // Ensure the task returned eventually + _ = try await eventTask.value + } + + func testCommandStreamHasCorrectEvents() async throws { + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { elg.syncShutdownOrFail() } + let client = try MongoClient.makeTestClient(eventLoopGroup: elg) + let commandStr: [String] = ["ping", "ping", "endSessions", "endSessions"] + let eventTypes: [EventType] = [ + .commandStartedEvent, + .commandSucceededEvent, + .commandStartedEvent, + .commandSucceededEvent + ] + + // Task to create a commandEventStream and should return when the client is closed + let eventTask = Task { () -> Int in + var i = 0 + for try await event in client.commandEventStream() { + print(event.commandName) + expect(event.commandName).to(equal(commandStr[i])) + expect(event.type).to(equal(eventTypes[i])) + i += 1 + } + return i + } + try await client.db("admin").runCommand(["ping": 1]) + try await client.close() + + // Ensure the task returned with proper number of events + let numEvents = try await eventTask.value + expect(numEvents).to(equal(4)) + } + + func testCommandStreamConcurrentStreams() async throws { + actor CmdEventArray { + var eventTypes: [[EventType]] = [] + var eventCommands: [[String]] = [] + + func appendType(eventType: EventType, index: Int) { + self.eventTypes[index].append(eventType) + } + + func newTask() { + self.eventTypes.append([]) + self.eventCommands.append([]) + } + + func appendCommands(commandEvent: String, index: Int) { + self.eventCommands[index].append(commandEvent) + } + } + let cmdEventArray = CmdEventArray() try await self.withTestClient { client in - let commandStr: [String] = ["ping", "ping", "endSessions", "endSessions"] - let eventTypes: [EventType] = [ - .commandStartedEvent, - .commandSucceededEvent, - .commandStartedEvent, - .commandSucceededEvent - ] - Task { - var i = 0 - let cmdStream = client.commandEventStream() - for try await event in cmdStream { - print("cmd-event") - expect(commandStr[i]).to(equal(event.commandName)) - expect(eventTypes[i]).to(equal(event.type)) - i += 1 - if i == 4 { - cmdStream.finish() + // Task to create a commandEventStream and should return when the client is closed + + for i in 0...4 { + _ = Task { () -> Bool in + await cmdEventArray.newTask() + for try await event in client.commandEventStream() { + // print(event.commandName) + await cmdEventArray.appendType(eventType: event.type, index: i) + await cmdEventArray.appendCommands(commandEvent: event.commandName, index: i) + } + return true + } + } + try await assertIsEventuallyTrue(description: "event stream should be started") { + await cmdEventArray.eventTypes.count == 5 + } + + try await client.db("admin").runCommand(["ping": 1]) + try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + } + // Expect all tasks received the same number (>0) of events + for i in 0...4 { + let eventTypes = await cmdEventArray.eventTypes[i] + let eventCommands = await cmdEventArray.eventCommands[i] + expect(eventTypes.count).to(beGreaterThan(0)) + expect(eventCommands.count).to(beGreaterThan(0)) + } + for i in 1...4 { + let eventTypesOld = await cmdEventArray.eventTypes[i - 1] + let eventCommandsOld = await cmdEventArray.eventCommands[i - 1] + let eventTypesCurr = await cmdEventArray.eventTypes[i] + let eventCommandsCurr = await cmdEventArray.eventCommands[i] + expect(eventTypesOld).to(equal(eventTypesCurr)) + expect(eventCommandsOld).to(equal(eventCommandsCurr)) + } + } + + func testCommandStreamBufferingPolicy() async throws { + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { elg.syncShutdownOrFail() } + let client = try MongoClient.makeTestClient(eventLoopGroup: elg) + let insertsDone = NIOAtomic.makeAtomic(value: false) + + let taskBuffer = Task { () -> Int in + var i = 0 + let stream = client.commandEventStream() + try await assertIsEventuallyTrue(description: "inserts done") { + insertsDone.load() + } + for try await _ in stream { + // print(i) + i += 1 + } + return i + } + for i in 1...60 { // 120 events + try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + if i == 60 { insertsDone.store(true) } + } + try await client.close() + let eventTypes1 = try await taskBuffer.value + expect(eventTypes1).to(equal(100)) + } + + // Need SDAM impl + func testSDAMStreamConcurrentStreams() async throws { + actor SdamEventArray { + var eventTypes: [[EventType]] = [] + + func appendType(eventType: EventType, index: Int) { + self.eventTypes[index].append(eventType) + } + + func newTask() { + self.eventTypes.append([]) + } + } + let sdamEventArray = SdamEventArray() + try await self.withTestClient { client in + // Task to create a commandEventStream and should return when the client is closed + + for i in 0...4 { + _ = Task { () -> Bool in + await sdamEventArray.newTask() + for try await event in client.sdamEventStream() { + // print(event.commandName) + await sdamEventArray.appendType(eventType: event.type, index: i) } + return true } - expect(i).to(be(4)) + } + try await assertIsEventuallyTrue(description: "event stream should be started") { + await sdamEventArray.eventTypes.count == 5 } try await client.db("admin").runCommand(["ping": 1]) + try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) } + // Expect all tasks received the same number (>0) of events + for i in 0...4 { + let eventTypes = await sdamEventArray.eventTypes[i] + + expect(eventTypes.count).to(beGreaterThan(0)) + } + for i in 1...4 { + let eventTypesOld = await sdamEventArray.eventTypes[i - 1] + let eventTypesCurr = await sdamEventArray.eventTypes[i] + expect(eventTypesOld).to(equal(eventTypesCurr)) + } + } + + func testClientFinishesSDAMStreams() async throws { + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { elg.syncShutdownOrFail() } + let client = try MongoClient.makeTestClient(eventLoopGroup: elg) + + // Task to create a commandEventStream and should return when the client is closed + let eventTask = Task { () -> Bool in + for try await _ in client.sdamEventStream() {} + return true + } + try await client.db("admin").runCommand(["ping": 1]) + try await client.close() + + // Ensure the task returned eventually + _ = try await eventTask.value } - func testSDAMEventStreamClient() async throws { + func testSDAMEventStreamHasCorrectEvents() async throws { + actor EventArray { + var streamEvents: [EventType] = [] + + func append(event: EventType) { + self.streamEvents.append(event) + } + } // Need lock to prevent dataraces let lock = Lock() + let taskStarted = NIOAtomic.makeAtomic(value: false) + var handlerEvents: [EventType] = [] + let eventArray = EventArray() try await self.withTestClient { client in - Task { - var i = 0 - var eventTypes: [EventType] = [] - // Lock the array access while appending + client.addSDAMEventHandler { event in lock.withLock { - client.addSDAMEventHandler { event in - if !event.isHeartbeatEvent { - eventTypes.append(event.type) - } + if !event.isHeartbeatEvent { + handlerEvents.append(event.type) } } - // Async so cannot lock + } + Task { let sdamStream = client.sdamEventStream() + taskStarted.store(true) for try await event in sdamStream { if !event.isHeartbeatEvent { - expect(event.type).to(equal(eventTypes[i])) - i += 1 + await eventArray.append(event: event.type) } } - // Doesnt exit since we dont .finish() + } + // Wait until the event stream has been opened before we start inserting data. + try await assertIsEventuallyTrue(description: "event stream should be started") { + taskStarted.load() } try await client.db("admin").runCommand(["ping": 1]) try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) } + let streamEvents = await eventArray.streamEvents + expect(streamEvents).to(equal(handlerEvents)) } } #endif diff --git a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift index 0c3296b99..d222683e8 100644 --- a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift +++ b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift @@ -51,6 +51,25 @@ func assertIsEventuallyTrue( XCTFail("Expected condition \"\(description)\" to be true within \(timeout) seconds, but was not") } +/// Asserts that the provided block returns true within the specified timeout. Nimble's `toEventually` can only be used +/// rom the main testing thread which is too restrictive for our purposes testing the async/await APIs. +@available(macOS 10.15.0, *) +func assertIsEventuallyTrue( + description: String, + timeout: TimeInterval = 5, + sleepInterval: TimeInterval = 0.1, + _ block: () async throws -> Bool +) async throws { + let start = DispatchTime.now() + while DispatchTime.now() < start + timeout { + if try await block() { + return + } + try await Task.sleep(seconds: sleepInterval) + } + XCTFail("Expected condition \"\(description)\" to be true within \(timeout) seconds, but was not") +} + @available(macOS 10.15.0, *) extension MongoSwiftTestCase { internal func withTestClient( From 6010451e189dd5658f8ff15eb3a42f6d0b226ef7 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 14 Jul 2022 13:20:47 -0400 Subject: [PATCH 33/47] Jagged array check --- Tests/MongoSwiftTests/APMTests.swift | 32 ++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 9d4d82e6a..b99a5e17b 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -68,13 +68,23 @@ final class APMTests: MongoSwiftTestCase { self.eventTypes[index].append(eventType) } + func appendCommands(commandEvent: String, index: Int) { + self.eventCommands[index].append(commandEvent) + } + func newTask() { + // let currSize = self.eventTypes.count self.eventTypes.append([]) self.eventCommands.append([]) + print("here") } - func appendCommands(commandEvent: String, index: Int) { - self.eventCommands[index].append(commandEvent) + func isRectangular() -> Bool { + let size = self.eventTypes[0].count + for entry in self.eventTypes where entry.count != size { + return false + } + return true } } let cmdEventArray = CmdEventArray() @@ -84,20 +94,29 @@ final class APMTests: MongoSwiftTestCase { for i in 0...4 { _ = Task { () -> Bool in await cmdEventArray.newTask() + let p = await cmdEventArray.eventTypes.count + print(String(p) + " " + String(i)) +// try await assertIsEventuallyTrue(description: "event stream should be started") { +// await cmdEventArray.eventTypes.count == i + 1 +// } for try await event in client.commandEventStream() { // print(event.commandName) await cmdEventArray.appendType(eventType: event.type, index: i) await cmdEventArray.appendCommands(commandEvent: event.commandName, index: i) } + return true } } - try await assertIsEventuallyTrue(description: "event stream should be started") { - await cmdEventArray.eventTypes.count == 5 - } try await client.db("admin").runCommand(["ping": 1]) - try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + //try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + try await assertIsEventuallyTrue(description: "5 tasks started") { + await cmdEventArray.eventTypes.count == 5 + } + try await assertIsEventuallyTrue(description: "each task gets same events") { + await cmdEventArray.isRectangular() + } } // Expect all tasks received the same number (>0) of events for i in 0...4 { @@ -180,7 +199,6 @@ final class APMTests: MongoSwiftTestCase { // Expect all tasks received the same number (>0) of events for i in 0...4 { let eventTypes = await sdamEventArray.eventTypes[i] - expect(eventTypes.count).to(beGreaterThan(0)) } for i in 1...4 { From efe51e336086f2607d5041ea7301a27d3c1d05e9 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 14 Jul 2022 14:15:17 -0400 Subject: [PATCH 34/47] Locking handler access when appending + fixing test --- Sources/MongoSwift/MongoClient.swift | 21 +++++++-- Tests/MongoSwiftTests/APMTests.swift | 68 ++++++++++++++++------------ 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 7d7bc3f83..0f6b08435 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -304,6 +304,9 @@ public class MongoClient { /// The write concern set on this client, or nil if one is not set. public let writeConcern: WriteConcern? + /// Lock used to prevent `malloc_double_free` errors when accessing handlers due to data races + private let eventHandlerLock: Lock = .init() + /** * Create a new client for a MongoDB deployment. For options that included in both the `MongoConnectionString` * and the `MongoClientOptions` struct, the final value is set in descending order of priority: the value specified @@ -511,7 +514,7 @@ public class MongoClient { for handler in commandEventHandlers { // print(handler) if let cmdHandler = handler as? WeakEventHandler { - cmdHandler.handler?.finish() + cmdHandler.handler!.finish() print("closing") } } @@ -910,7 +913,9 @@ public class MongoClient { * to continue to receive events. */ public func addCommandEventHandler(_ handler: T) { - self.commandEventHandlers.append(WeakEventHandler(referencing: handler)) + self.eventHandlerLock.withLock { + self.commandEventHandlers.append(WeakEventHandler(referencing: handler)) + } } /** @@ -920,7 +925,9 @@ public class MongoClient { * strong reference cycle and potentially result in memory leaks. */ public func addCommandEventHandler(_ handlerFunc: @escaping (CommandEvent) -> Void) { - self.commandEventHandlers.append(CallbackEventHandler(handlerFunc)) + self.eventHandlerLock.withLock { + self.commandEventHandlers.append(CallbackEventHandler(handlerFunc)) + } } /** @@ -930,7 +937,9 @@ public class MongoClient { * to continue to receive events. */ public func addSDAMEventHandler(_ handler: T) { - self.sdamEventHandlers.append(WeakEventHandler(referencing: handler)) + self.eventHandlerLock.withLock { + self.sdamEventHandlers.append(WeakEventHandler(referencing: handler)) + } } /** @@ -940,7 +949,9 @@ public class MongoClient { * strong reference cycle and potentially result in memory leaks. */ public func addSDAMEventHandler(_ handlerFunc: @escaping (SDAMEvent) -> Void) { - self.sdamEventHandlers.append(CallbackEventHandler(handlerFunc)) + self.eventHandlerLock.withLock { + self.sdamEventHandlers.append(CallbackEventHandler(handlerFunc)) + } } /// Internal method to check the `ReadConcern` that was ultimately set on this client. **This method may block diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index b99a5e17b..4df756012 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -61,8 +61,10 @@ final class APMTests: MongoSwiftTestCase { func testCommandStreamConcurrentStreams() async throws { actor CmdEventArray { + // Keep track of types, commands, and tasks themselves var eventTypes: [[EventType]] = [] var eventCommands: [[String]] = [] + var tasks: [Task] = [] func appendType(eventType: EventType, index: Int) { self.eventTypes[index].append(eventType) @@ -72,6 +74,10 @@ final class APMTests: MongoSwiftTestCase { self.eventCommands[index].append(commandEvent) } + func appendTasks(task: Task) { + self.tasks.append(task) + } + func newTask() { // let currSize = self.eventTypes.count self.eventTypes.append([]) @@ -79,44 +85,46 @@ final class APMTests: MongoSwiftTestCase { print("here") } - func isRectangular() -> Bool { - let size = self.eventTypes[0].count - for entry in self.eventTypes where entry.count != size { - return false + func getResults() async throws -> [Bool] { + var output: [Bool] = [] + for elt in self.tasks { + print("attempting") + try await output.append(elt.value) + print("success") } - return true + return output } } let cmdEventArray = CmdEventArray() - try await self.withTestClient { client in - // Task to create a commandEventStream and should return when the client is closed - - for i in 0...4 { - _ = Task { () -> Bool in - await cmdEventArray.newTask() - let p = await cmdEventArray.eventTypes.count - print(String(p) + " " + String(i)) -// try await assertIsEventuallyTrue(description: "event stream should be started") { -// await cmdEventArray.eventTypes.count == i + 1 -// } - for try await event in client.commandEventStream() { - // print(event.commandName) - await cmdEventArray.appendType(eventType: event.type, index: i) - await cmdEventArray.appendCommands(commandEvent: event.commandName, index: i) - } + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { elg.syncShutdownOrFail() } + let client = try MongoClient.makeTestClient(eventLoopGroup: elg) - return true + // Kick off 5 streams + for i in 0...4 { + let task = Task { () -> Bool in + await cmdEventArray.newTask() + for try await event in client.commandEventStream() { + await cmdEventArray.appendType(eventType: event.type, index: i) + await cmdEventArray.appendCommands(commandEvent: event.commandName, index: i) } - } - try await client.db("admin").runCommand(["ping": 1]) - //try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) - try await assertIsEventuallyTrue(description: "5 tasks started") { - await cmdEventArray.eventTypes.count == 5 - } - try await assertIsEventuallyTrue(description: "each task gets same events") { - await cmdEventArray.isRectangular() + return true } + await cmdEventArray.appendTasks(task: task) + } + + try await client.db("admin").runCommand(["ping": 1]) + try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + + // Tasks start, close client, close all tasks + try await assertIsEventuallyTrue(description: "each task is started") { + await cmdEventArray.eventTypes.count == 5 + } + + try await client.close() + try await assertIsEventuallyTrue(description: "each task is closed") { + try await cmdEventArray.getResults().count == 5 } // Expect all tasks received the same number (>0) of events for i in 0...4 { From 057aee070020645340b84a15980cbd443e69c5c5 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 14 Jul 2022 15:41:43 -0400 Subject: [PATCH 35/47] Using concurrent streams helper for command+SDAM tests --- Sources/MongoSwift/MongoClient.swift | 5 +- Tests/MongoSwiftTests/APMTests.swift | 188 +++++++++++---------------- 2 files changed, 74 insertions(+), 119 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 0f6b08435..be8920329 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -431,7 +431,6 @@ public class MongoClient { // Ok to force unwrap since continuations are always set // swiftlint:disable force_unwrapping self.continuation!.finish() - print("continuation finishes") } } @@ -512,10 +511,8 @@ public class MongoClient { #if compiler(>=5.5.2) && canImport(_Concurrency) if #available(macOS 10.15, *) { for handler in commandEventHandlers { - // print(handler) if let cmdHandler = handler as? WeakEventHandler { - cmdHandler.handler!.finish() - print("closing") + cmdHandler.handler?.finish() } } for handler in sdamEventHandlers { diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 4df756012..4612f6c0c 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -6,6 +6,15 @@ import NIOConcurrencyHelpers import TestsCommon @available(macOS 10.15, *) + +private protocol StreamableEvent { + var type: EventType { get } +} + +extension CommandEvent: StreamableEvent {} + +extension SDAMEvent: StreamableEvent {} + final class APMTests: MongoSwiftTestCase { func testClientFinishesCommandStreams() async throws { let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) @@ -15,10 +24,7 @@ final class APMTests: MongoSwiftTestCase { // Task to create a commandEventStream and should return when the client is closed let eventTask = Task { () -> Bool in // let cmdStream = client.commandEventStream() - for try await _ in client.commandEventStream() { - print("here") - } - print("out of loop") + for try await _ in client.commandEventStream() {} return true } try await client.db("admin").runCommand(["ping": 1]) @@ -44,7 +50,6 @@ final class APMTests: MongoSwiftTestCase { let eventTask = Task { () -> Int in var i = 0 for try await event in client.commandEventStream() { - print(event.commandName) expect(event.commandName).to(equal(commandStr[i])) expect(event.type).to(equal(eventTypes[i])) i += 1 @@ -59,43 +64,63 @@ final class APMTests: MongoSwiftTestCase { expect(numEvents).to(equal(4)) } - func testCommandStreamConcurrentStreams() async throws { - actor CmdEventArray { - // Keep track of types, commands, and tasks themselves - var eventTypes: [[EventType]] = [] - var eventCommands: [[String]] = [] - var tasks: [Task] = [] + func testCommandStreamBufferingPolicy() async throws { + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { elg.syncShutdownOrFail() } + let client = try MongoClient.makeTestClient(eventLoopGroup: elg) + let insertsDone = NIOAtomic.makeAtomic(value: false) - func appendType(eventType: EventType, index: Int) { - self.eventTypes[index].append(eventType) + let taskBuffer = Task { () -> Int in + var i = 0 + let stream = client.commandEventStream() + try await assertIsEventuallyTrue(description: "inserts done") { + insertsDone.load() } - - func appendCommands(commandEvent: String, index: Int) { - self.eventCommands[index].append(commandEvent) + for try await _ in stream { + i += 1 } + return i + } + for i in 1...60 { // 120 events + try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + if i == 60 { insertsDone.store(true) } + } + try await client.close() + let eventTypes1 = try await taskBuffer.value + expect(eventTypes1).to(equal(100)) + } - func appendTasks(task: Task) { - self.tasks.append(task) - } + // Actor to handle array access/modification in an async way + actor EventArray { + var eventTypes: [[EventType]] = [] + var tasks: [Task] = [] - func newTask() { - // let currSize = self.eventTypes.count - self.eventTypes.append([]) - self.eventCommands.append([]) - print("here") - } + func appendType(eventType: EventType, index: Int) { + self.eventTypes[index].append(eventType) + } - func getResults() async throws -> [Bool] { - var output: [Bool] = [] - for elt in self.tasks { - print("attempting") - try await output.append(elt.value) - print("success") - } - return output + func appendTasks(task: Task) { + self.tasks.append(task) + } + + func newTask() { + self.eventTypes.append([]) + } + + func getResults() async throws -> [Bool] { + var output: [Bool] = [] + for elt in self.tasks { + try await output.append(elt.value) } + return output } - let cmdEventArray = CmdEventArray() + } + + /// Helper that tests kicking off multiple streams concurrently + fileprivate func concurrencyHelper(f: @escaping (MongoClient) -> T) async throws + where T: AsyncSequence, T.Element: StreamableEvent + { + let eventArray = EventArray() let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { elg.syncShutdownOrFail() } let client = try MongoClient.makeTestClient(eventLoopGroup: elg) @@ -103,15 +128,14 @@ final class APMTests: MongoSwiftTestCase { // Kick off 5 streams for i in 0...4 { let task = Task { () -> Bool in - await cmdEventArray.newTask() - for try await event in client.commandEventStream() { - await cmdEventArray.appendType(eventType: event.type, index: i) - await cmdEventArray.appendCommands(commandEvent: event.commandName, index: i) + await eventArray.newTask() + for try await event in f(client) { + await eventArray.appendType(eventType: event.type, index: i) } return true } - await cmdEventArray.appendTasks(task: task) + await eventArray.appendTasks(task: task) } try await client.db("admin").runCommand(["ping": 1]) @@ -119,100 +143,34 @@ final class APMTests: MongoSwiftTestCase { // Tasks start, close client, close all tasks try await assertIsEventuallyTrue(description: "each task is started") { - await cmdEventArray.eventTypes.count == 5 + await eventArray.eventTypes.count == 5 } try await client.close() try await assertIsEventuallyTrue(description: "each task is closed") { - try await cmdEventArray.getResults().count == 5 + try await eventArray.getResults().count == 5 } // Expect all tasks received the same number (>0) of events for i in 0...4 { - let eventTypes = await cmdEventArray.eventTypes[i] - let eventCommands = await cmdEventArray.eventCommands[i] + let eventTypes = await eventArray.eventTypes[i] expect(eventTypes.count).to(beGreaterThan(0)) - expect(eventCommands.count).to(beGreaterThan(0)) } for i in 1...4 { - let eventTypesOld = await cmdEventArray.eventTypes[i - 1] - let eventCommandsOld = await cmdEventArray.eventCommands[i - 1] - let eventTypesCurr = await cmdEventArray.eventTypes[i] - let eventCommandsCurr = await cmdEventArray.eventCommands[i] + let eventTypesOld = await eventArray.eventTypes[i - 1] + let eventTypesCurr = await eventArray.eventTypes[i] expect(eventTypesOld).to(equal(eventTypesCurr)) - expect(eventCommandsOld).to(equal(eventCommandsCurr)) } } - func testCommandStreamBufferingPolicy() async throws { - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { elg.syncShutdownOrFail() } - let client = try MongoClient.makeTestClient(eventLoopGroup: elg) - let insertsDone = NIOAtomic.makeAtomic(value: false) - - let taskBuffer = Task { () -> Int in - var i = 0 - let stream = client.commandEventStream() - try await assertIsEventuallyTrue(description: "inserts done") { - insertsDone.load() - } - for try await _ in stream { - // print(i) - i += 1 - } - return i - } - for i in 1...60 { // 120 events - try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) - if i == 60 { insertsDone.store(true) } + func testCommandStreamConcurrentStreams() async throws { + try await self.concurrencyHelper { client in + client.commandEventStream() } - try await client.close() - let eventTypes1 = try await taskBuffer.value - expect(eventTypes1).to(equal(100)) } - // Need SDAM impl func testSDAMStreamConcurrentStreams() async throws { - actor SdamEventArray { - var eventTypes: [[EventType]] = [] - - func appendType(eventType: EventType, index: Int) { - self.eventTypes[index].append(eventType) - } - - func newTask() { - self.eventTypes.append([]) - } - } - let sdamEventArray = SdamEventArray() - try await self.withTestClient { client in - // Task to create a commandEventStream and should return when the client is closed - - for i in 0...4 { - _ = Task { () -> Bool in - await sdamEventArray.newTask() - for try await event in client.sdamEventStream() { - // print(event.commandName) - await sdamEventArray.appendType(eventType: event.type, index: i) - } - return true - } - } - try await assertIsEventuallyTrue(description: "event stream should be started") { - await sdamEventArray.eventTypes.count == 5 - } - - try await client.db("admin").runCommand(["ping": 1]) - try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) - } - // Expect all tasks received the same number (>0) of events - for i in 0...4 { - let eventTypes = await sdamEventArray.eventTypes[i] - expect(eventTypes.count).to(beGreaterThan(0)) - } - for i in 1...4 { - let eventTypesOld = await sdamEventArray.eventTypes[i - 1] - let eventTypesCurr = await sdamEventArray.eventTypes[i] - expect(eventTypesOld).to(equal(eventTypesCurr)) + try await self.concurrencyHelper { client in + client.sdamEventStream() } } From 403b286758e7094e9eccff48f741bdf59097b04e Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 14 Jul 2022 16:23:56 -0400 Subject: [PATCH 36/47] Accounting for load balancer events --- Tests/MongoSwiftTests/APMTests.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 4612f6c0c..f7b6bc17d 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -71,23 +71,28 @@ final class APMTests: MongoSwiftTestCase { let insertsDone = NIOAtomic.makeAtomic(value: false) let taskBuffer = Task { () -> Int in - var i = 0 let stream = client.commandEventStream() try await assertIsEventuallyTrue(description: "inserts done") { insertsDone.load() } + var i = 0 + for try await _ in stream { i += 1 + print(i) } return i } - for i in 1...60 { // 120 events + for _ in 1...60 { // 120 events try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) - if i == 60 { insertsDone.store(true) } } + insertsDone.store(true) try await client.close() + let eventTypes1 = try await taskBuffer.value - expect(eventTypes1).to(equal(100)) + + // Cant check for explictly 100 because of potential load balancer events getting added in during for loop + expect(eventTypes1).to(beLessThan(120)) } // Actor to handle array access/modification in an async way From 42655155e44a67323ecb30bfaf82c025bfcc0d94 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 14 Jul 2022 16:40:26 -0400 Subject: [PATCH 37/47] Removing a forgotten print statement --- Tests/MongoSwiftTests/APMTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index f7b6bc17d..b56235753 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -79,7 +79,6 @@ final class APMTests: MongoSwiftTestCase { for try await _ in stream { i += 1 - print(i) } return i } From ddfea22e30829f0995e3a3078262cb52590f67a6 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Thu, 14 Jul 2022 16:44:32 -0400 Subject: [PATCH 38/47] Removing no-longer-necessary struct wrappers --- Sources/MongoSwift/APM.swift | 4 ---- Sources/MongoSwift/MongoClient.swift | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 3e618292c..2e81486a2 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -13,10 +13,6 @@ public protocol SDAMEventHandler: AnyObject { func handleSDAMEvent(_ event: SDAMEvent) } -internal protocol EventHandler { - associatedtype HandlerEventType -} - /// A protocol for events that are directly consumable by users to implement. private protocol Publishable { func publish(to client: MongoClient) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index be8920329..627b330d1 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -275,7 +275,7 @@ public class MongoClient { /// Indicates whether this client has been closed. A lock around this variable is not needed because: /// - This value is only modified on success of `ConnectionPool.close()`. That method will succeed exactly once. /// - This value is only read in `deinit`. That occurs exactly once after the above modification is complete. - internal var wasClosed = false + private var wasClosed = false /// Handlers for command monitoring events. internal var commandEventHandlers: [CommandEventHandler] From 14e41a27d564c130bc424171d71011c216ebb361 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Fri, 15 Jul 2022 15:11:08 -0400 Subject: [PATCH 39/47] Minor test changes, change unwrapping procedures, docs, comments --- Sources/MongoSwift/MongoClient.swift | 63 ++++------ Tests/MongoSwiftTests/APMTests.swift | 111 ++++++++---------- .../MongoSwiftTests/AsyncAwaitTestUtils.swift | 29 +++-- 3 files changed, 91 insertions(+), 112 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 627b330d1..bb130d610 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -304,7 +304,7 @@ public class MongoClient { /// The write concern set on this client, or nil if one is not set. public let writeConcern: WriteConcern? - /// Lock used to prevent `malloc_double_free` errors when accessing handlers due to data races + /// Lock used to synchronize access to the event handler arrays to prevent data races private let eventHandlerLock: Lock = .init() /** @@ -408,57 +408,35 @@ public class MongoClient { #if compiler(>=5.5.2) && canImport(_Concurrency) @available(macOS 10.15, *) internal class CmdHandler: CommandEventHandler { - private var continuation: AsyncStream.Continuation? + private var continuation: AsyncStream.Continuation internal init(continuation: AsyncStream.Continuation) { self.continuation = continuation } - internal init() { - self.continuation = nil - } - // Satisfies the protocol internal func handleCommandEvent(_ event: CommandEvent) { - self.continuation?.yield(event) - } - - // Sets the continuation after init'ing - internal func setCon(continuation: AsyncStream.Continuation) { - self.continuation = continuation + self.continuation.yield(event) } internal func finish() { - // Ok to force unwrap since continuations are always set - // swiftlint:disable force_unwrapping - self.continuation!.finish() + self.continuation.finish() } } @available(macOS 10.15, *) internal class SDAMHandler: SDAMEventHandler { - private var continuation: AsyncStream.Continuation? + private var continuation: AsyncStream.Continuation internal init(continuation: AsyncStream.Continuation) { self.continuation = continuation } - internal init() { - self.continuation = nil - } - // Satisfies the protocol internal func handleSDAMEvent(_ event: SDAMEvent) { - self.continuation?.yield(event) - } - - // Sets the continuation after init'ing - internal func setCon(continuation: AsyncStream.Continuation) { - self.continuation = continuation + self.continuation.yield(event) } internal func finish() { - // Ok to force unwrap since continuations are always set - // swiftlint:disable force_unwrapping - self.continuation!.finish() + self.continuation.finish() } } @@ -469,18 +447,20 @@ public class MongoClient { /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func commandEventStream() -> CommandEventStream { - let handler = CmdHandler() + var handler: CmdHandler? let stream = AsyncStream( CommandEvent.self, bufferingPolicy: .bufferingNewest(100) ) { con in - handler.setCon(continuation: con) - self.addCommandEventHandler(handler) + handler = CmdHandler(continuation: con) + // Ok to force unwrap since handler is set just above + // swiftlint:disable force_unwrapping + self.addCommandEventHandler(handler!) } - let commandEvents = CommandEventStream( - cmdHandler: handler, stream: stream - ) + // Ok to force unwrap since handler is set in the closure + // swiftlint:disable force_unwrapping + let commandEvents = CommandEventStream(cmdHandler: handler!, stream: stream) return commandEvents } @@ -492,16 +472,19 @@ public class MongoClient { /// Note that only the most recent 100 events are stored in the stream. @available(macOS 10.15, *) public func sdamEventStream() -> SDAMEventStream { - let handler = SDAMHandler() + var handler: SDAMHandler? let stream = AsyncStream( SDAMEvent.self, bufferingPolicy: .bufferingNewest(100) ) { con in - handler.setCon(continuation: con) - self.addSDAMEventHandler(handler) + handler = SDAMHandler(continuation: con) + // Ok to force unwrap since handler is set just above + // swiftlint:disable force_unwrapping + self.addSDAMEventHandler(handler!) } - - let sdamEvents = SDAMEventStream(sdamHandler: handler, stream: stream) + // Ok to force unwrap since handler is set just above + // swiftlint:disable force_unwrapping + let sdamEvents = SDAMEventStream(sdamHandler: handler!, stream: stream) return sdamEvents } #endif diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index b56235753..485dab2bf 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -23,7 +23,6 @@ final class APMTests: MongoSwiftTestCase { // Task to create a commandEventStream and should return when the client is closed let eventTask = Task { () -> Bool in - // let cmdStream = client.commandEventStream() for try await _ in client.commandEventStream() {} return true } @@ -35,9 +34,6 @@ final class APMTests: MongoSwiftTestCase { } func testCommandStreamHasCorrectEvents() async throws { - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { elg.syncShutdownOrFail() } - let client = try MongoClient.makeTestClient(eventLoopGroup: elg) let commandStr: [String] = ["ping", "ping", "endSessions", "endSessions"] let eventTypes: [EventType] = [ .commandStartedEvent, @@ -45,53 +41,42 @@ final class APMTests: MongoSwiftTestCase { .commandStartedEvent, .commandSucceededEvent ] - - // Task to create a commandEventStream and should return when the client is closed - let eventTask = Task { () -> Int in - var i = 0 - for try await event in client.commandEventStream() { - expect(event.commandName).to(equal(commandStr[i])) - expect(event.type).to(equal(eventTypes[i])) - i += 1 + try await self.withTestClient { client in + Task { + var i = 0 + for try await event in client.commandEventStream() { + expect(event.commandName).to(equal(commandStr[i])) + expect(event.type).to(equal(eventTypes[i])) + i += 1 + } + expect(i).to(equal(4)) } - return i - } - try await client.db("admin").runCommand(["ping": 1]) - try await client.close() - // Ensure the task returned with proper number of events - let numEvents = try await eventTask.value - expect(numEvents).to(equal(4)) + try await client.db("admin").runCommand(["ping": 1]) + } } func testCommandStreamBufferingPolicy() async throws { - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { elg.syncShutdownOrFail() } - let client = try MongoClient.makeTestClient(eventLoopGroup: elg) let insertsDone = NIOAtomic.makeAtomic(value: false) + try await self.withTestNamespace { client, _, coll in + Task { + let stream = client.commandEventStream() + try await assertIsEventuallyTrue(description: "inserts done") { + insertsDone.load() + } + var i = 0 + for try await _ in stream { + i += 1 + } - let taskBuffer = Task { () -> Int in - let stream = client.commandEventStream() - try await assertIsEventuallyTrue(description: "inserts done") { - insertsDone.load() + // Cant check for exactly 100 because of potential load balancer events getting added in during loop + expect(i).to(beLessThan(120)) } - var i = 0 - - for try await _ in stream { - i += 1 + for _ in 1...60 { // 120 events + try await coll.insertOne(["hello": "world"]) } - return i - } - for _ in 1...60 { // 120 events - try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + insertsDone.store(true) } - insertsDone.store(true) - try await client.close() - - let eventTypes1 = try await taskBuffer.value - - // Cant check for explictly 100 because of potential load balancer events getting added in during for loop - expect(eventTypes1).to(beLessThan(120)) } // Actor to handle array access/modification in an async way @@ -121,36 +106,31 @@ final class APMTests: MongoSwiftTestCase { } /// Helper that tests kicking off multiple streams concurrently - fileprivate func concurrencyHelper(f: @escaping (MongoClient) -> T) async throws + fileprivate func concurrentStreamTestHelper(f: @escaping (MongoClient) -> T) async throws where T: AsyncSequence, T.Element: StreamableEvent { let eventArray = EventArray() - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { elg.syncShutdownOrFail() } - let client = try MongoClient.makeTestClient(eventLoopGroup: elg) + try await self.withTestNamespace { client, _, coll in + // Kick off 5 streams + for i in 0...4 { + let task = Task { () -> Bool in + await eventArray.newTask() + for try await event in f(client) { + await eventArray.appendType(eventType: event.type, index: i) + } - // Kick off 5 streams - for i in 0...4 { - let task = Task { () -> Bool in - await eventArray.newTask() - for try await event in f(client) { - await eventArray.appendType(eventType: event.type, index: i) + return true } - - return true + await eventArray.appendTasks(task: task) } - await eventArray.appendTasks(task: task) - } - try await client.db("admin").runCommand(["ping": 1]) - try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) - - // Tasks start, close client, close all tasks + try await client.db("admin").runCommand(["ping": 1]) + try await coll.insertOne(["hello": "world"]) + } + // Tasks start and finish try await assertIsEventuallyTrue(description: "each task is started") { await eventArray.eventTypes.count == 5 } - - try await client.close() try await assertIsEventuallyTrue(description: "each task is closed") { try await eventArray.getResults().count == 5 } @@ -167,13 +147,13 @@ final class APMTests: MongoSwiftTestCase { } func testCommandStreamConcurrentStreams() async throws { - try await self.concurrencyHelper { client in + try await self.concurrentStreamTestHelper { client in client.commandEventStream() } } func testSDAMStreamConcurrentStreams() async throws { - try await self.concurrencyHelper { client in + try await self.concurrentStreamTestHelper { client in client.sdamEventStream() } } @@ -208,7 +188,7 @@ final class APMTests: MongoSwiftTestCase { let taskStarted = NIOAtomic.makeAtomic(value: false) var handlerEvents: [EventType] = [] let eventArray = EventArray() - try await self.withTestClient { client in + try await self.withTestNamespace { client, _, coll in client.addSDAMEventHandler { event in lock.withLock { if !event.isHeartbeatEvent { @@ -231,9 +211,10 @@ final class APMTests: MongoSwiftTestCase { } try await client.db("admin").runCommand(["ping": 1]) - try await client.db("trialDB").collection("trialColl").insertOne(["hello": "world"]) + try await coll.insertOne(["hello": "world"]) } let streamEvents = await eventArray.streamEvents + expect(streamEvents.count).to(beGreaterThan(0)) expect(streamEvents).to(equal(handlerEvents)) } } diff --git a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift index d222683e8..03374f8a1 100644 --- a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift +++ b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift @@ -58,16 +58,31 @@ func assertIsEventuallyTrue( description: String, timeout: TimeInterval = 5, sleepInterval: TimeInterval = 0.1, - _ block: () async throws -> Bool + _ block: @escaping () async throws -> Bool ) async throws { - let start = DispatchTime.now() - while DispatchTime.now() < start + timeout { - if try await block() { - return + // Task that does the work as long as its not cancelled + let workTask = Task { () -> Bool in + while !Task.isCancelled { + guard try await block() else { + try await Task.sleep(seconds: sleepInterval) + continue + } + // task succeeded to we return true + return true } - try await Task.sleep(seconds: sleepInterval) + // Ran out of time before we succeeded, so return false + return false + } + + // Sleep until the timeout time is reached and then cancel the work + Task { + try await Task.sleep(seconds: timeout) + workTask.cancel() + } + guard try await workTask.value else { + XCTFail("Expected condition \"\(description)\" to be true within \(timeout) seconds, but was not") + return } - XCTFail("Expected condition \"\(description)\" to be true within \(timeout) seconds, but was not") } @available(macOS 10.15.0, *) From 4206a6d8b7e64e52c37bf99016380f70a321f5df Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Mon, 18 Jul 2022 11:03:13 -0400 Subject: [PATCH 40/47] Returning task from clients, removing need for actors, comments --- Sources/MongoSwift/MongoClient.swift | 16 ++-- Tests/MongoSwiftTests/APMTests.swift | 112 ++++++++++----------------- 2 files changed, 48 insertions(+), 80 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index bb130d610..430c4c72d 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -304,7 +304,7 @@ public class MongoClient { /// The write concern set on this client, or nil if one is not set. public let writeConcern: WriteConcern? - /// Lock used to synchronize access to the event handler arrays to prevent data races + /// Lock used to synchronize access to the event handler arrays to prevent data races. private let eventHandlerLock: Lock = .init() /** @@ -452,10 +452,9 @@ public class MongoClient { CommandEvent.self, bufferingPolicy: .bufferingNewest(100) ) { con in - handler = CmdHandler(continuation: con) - // Ok to force unwrap since handler is set just above - // swiftlint:disable force_unwrapping - self.addCommandEventHandler(handler!) + let cmdHandler = CmdHandler(continuation: con) + handler = cmdHandler + self.addCommandEventHandler(cmdHandler) } // Ok to force unwrap since handler is set in the closure @@ -477,10 +476,9 @@ public class MongoClient { SDAMEvent.self, bufferingPolicy: .bufferingNewest(100) ) { con in - handler = SDAMHandler(continuation: con) - // Ok to force unwrap since handler is set just above - // swiftlint:disable force_unwrapping - self.addSDAMEventHandler(handler!) + let sdamHandler = SDAMHandler(continuation: con) + handler = sdamHandler + self.addSDAMEventHandler(sdamHandler) } // Ok to force unwrap since handler is set just above // swiftlint:disable force_unwrapping diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 485dab2bf..c02909ee7 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -41,87 +41,67 @@ final class APMTests: MongoSwiftTestCase { .commandStartedEvent, .commandSucceededEvent ] - try await self.withTestClient { client in - Task { + let clientTask = try await self.withTestClient { client -> Task in + let cmdTask = Task { () -> Int in var i = 0 for try await event in client.commandEventStream() { expect(event.commandName).to(equal(commandStr[i])) expect(event.type).to(equal(eventTypes[i])) i += 1 } - expect(i).to(equal(4)) + return i } try await client.db("admin").runCommand(["ping": 1]) + return cmdTask } + let numEvents = try await clientTask.value + expect(numEvents).to(equal(4)) } func testCommandStreamBufferingPolicy() async throws { let insertsDone = NIOAtomic.makeAtomic(value: false) - try await self.withTestNamespace { client, _, coll in - Task { + let clientTask = try await self.withTestNamespace { client, _, coll -> Task in + let cmdBufferTask = Task { () -> Int in + var i = 0 let stream = client.commandEventStream() try await assertIsEventuallyTrue(description: "inserts done") { insertsDone.load() } - var i = 0 + for try await _ in stream { i += 1 } - - // Cant check for exactly 100 because of potential load balancer events getting added in during loop - expect(i).to(beLessThan(120)) + return i } for _ in 1...60 { // 120 events try await coll.insertOne(["hello": "world"]) } insertsDone.store(true) + return cmdBufferTask } - } - - // Actor to handle array access/modification in an async way - actor EventArray { - var eventTypes: [[EventType]] = [] - var tasks: [Task] = [] - - func appendType(eventType: EventType, index: Int) { - self.eventTypes[index].append(eventType) - } - - func appendTasks(task: Task) { - self.tasks.append(task) - } - - func newTask() { - self.eventTypes.append([]) - } - - func getResults() async throws -> [Bool] { - var output: [Bool] = [] - for elt in self.tasks { - try await output.append(elt.value) - } - return output - } + let numEventsBuffer = try await clientTask.value + // Cant check for exactly 100 because of potential load balancer events getting added in during loop + expect(numEventsBuffer).to(beLessThan(120)) } /// Helper that tests kicking off multiple streams concurrently fileprivate func concurrentStreamTestHelper(f: @escaping (MongoClient) -> T) async throws where T: AsyncSequence, T.Element: StreamableEvent { - let eventArray = EventArray() + var taskArr: [Task<[EventType], Error>] = [] try await self.withTestNamespace { client, _, coll in // Kick off 5 streams - for i in 0...4 { - let task = Task { () -> Bool in - await eventArray.newTask() + for _ in 0...4 { + let task = Task { () -> [EventType] in + var eventArr: [EventType] = [] for try await event in f(client) { - await eventArray.appendType(eventType: event.type, index: i) + eventArr.append(event.type) } - return true + return eventArr } - await eventArray.appendTasks(task: task) + taskArr.append(task) } try await client.db("admin").runCommand(["ping": 1]) @@ -129,20 +109,18 @@ final class APMTests: MongoSwiftTestCase { } // Tasks start and finish try await assertIsEventuallyTrue(description: "each task is started") { - await eventArray.eventTypes.count == 5 - } - try await assertIsEventuallyTrue(description: "each task is closed") { - try await eventArray.getResults().count == 5 + taskArr.count == 5 } + // Expect all tasks received the same number (>0) of events for i in 0...4 { - let eventTypes = await eventArray.eventTypes[i] - expect(eventTypes.count).to(beGreaterThan(0)) + let eventArrOutput = try await taskArr[i].value + expect(eventArrOutput.count).to(beGreaterThan(0)) } for i in 1...4 { - let eventTypesOld = await eventArray.eventTypes[i - 1] - let eventTypesCurr = await eventArray.eventTypes[i] - expect(eventTypesOld).to(equal(eventTypesCurr)) + let eventArrOld = try await taskArr[i - 1].value + let eventArrCurr = try await taskArr[i].value + expect(eventArrOld).to(equal(eventArrCurr)) } } @@ -176,34 +154,25 @@ final class APMTests: MongoSwiftTestCase { } func testSDAMEventStreamHasCorrectEvents() async throws { - actor EventArray { - var streamEvents: [EventType] = [] - - func append(event: EventType) { - self.streamEvents.append(event) - } - } - // Need lock to prevent dataraces - let lock = Lock() let taskStarted = NIOAtomic.makeAtomic(value: false) + // Events seen by the regular SDAM handler var handlerEvents: [EventType] = [] - let eventArray = EventArray() - try await self.withTestNamespace { client, _, coll in + let clientTask = try await self.withTestNamespace { client, _, coll -> Task<[EventType], Error> in client.addSDAMEventHandler { event in - lock.withLock { - if !event.isHeartbeatEvent { - handlerEvents.append(event.type) - } + if !event.isHeartbeatEvent { + handlerEvents.append(event.type) } } - Task { - let sdamStream = client.sdamEventStream() + + let sdamTask = Task { () -> [EventType] in + var eventTypeSdam: [EventType] = [] taskStarted.store(true) - for try await event in sdamStream { + for try await event in client.sdamEventStream() { if !event.isHeartbeatEvent { - await eventArray.append(event: event.type) + eventTypeSdam.append(event.type) } } + return eventTypeSdam } // Wait until the event stream has been opened before we start inserting data. try await assertIsEventuallyTrue(description: "event stream should be started") { @@ -212,8 +181,9 @@ final class APMTests: MongoSwiftTestCase { try await client.db("admin").runCommand(["ping": 1]) try await coll.insertOne(["hello": "world"]) + return sdamTask } - let streamEvents = await eventArray.streamEvents + let streamEvents = try await clientTask.value expect(streamEvents.count).to(beGreaterThan(0)) expect(streamEvents).to(equal(handlerEvents)) } From f7bbd55e0203c07fd0cb9d3ba82b34e2b518eb5d Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Mon, 25 Jul 2022 14:06:17 -0400 Subject: [PATCH 41/47] Fixing tests + docstrings --- Sources/MongoSwift/MongoClient.swift | 36 +++++++++++++------ Tests/MongoSwiftTests/APMTests.swift | 34 +++++++++++------- .../MongoSwiftTests/AsyncAwaitTestUtils.swift | 2 +- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 430c4c72d..ca61d6659 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -440,11 +440,19 @@ public class MongoClient { } } - /// Provides an `AsyncSequence` API for consuming command monitoring events. - /// Example: printing the command events out would be written as - /// `for await event in client.commandEventStream() { print(event) }`. - /// Wrapping in a `Task { ... }` may be desired for asynchronicity. - /// Note that only the most recent 100 events are stored in the stream. + /** + * Provides an `AsyncSequence` API for consuming command monitoring events. + * + * Example: printing the command events out would be written as + + for try await event in client.commandEventStream() { + print(event) + } + + * Wrapping in a `Task { ... }` may be desired for asynchronicity. + * - Returns: A `CommandEventStream` that implements `AsyncSequence`. + * - Note: Only the most recent 100 events are stored in the stream. + */ @available(macOS 10.15, *) public func commandEventStream() -> CommandEventStream { var handler: CmdHandler? @@ -464,11 +472,19 @@ public class MongoClient { return commandEvents } - /// Provides an `AsyncSequence` API for consuming SDAM monitoring events. - /// Example: printing the SDAM events out would be written as - /// `for await event in client.sdamEventStream() { print(event) }`. - /// Wrapping in a `Task { ... }` may be desired for asynchronicity. - /// Note that only the most recent 100 events are stored in the stream. + /** + * Provides an `AsyncSequence` API for consuming SDAM monitoring events. + * + * Example: printing the SDAM events out would be written as + + for try await event in client.sdamEventStream() { + print(event) + } + + * Wrapping in a `Task { ... }` may be desired for asynchronicity. + * - Returns: An `SDAMEventStream` that implements `AsyncSequence`. + * - Note: Only the most recent 100 events are stored in the stream. + */ @available(macOS 10.15, *) public func sdamEventStream() -> SDAMEventStream { var handler: SDAMHandler? diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index c02909ee7..854cb380a 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -5,8 +5,6 @@ import NIO import NIOConcurrencyHelpers import TestsCommon -@available(macOS 10.15, *) - private protocol StreamableEvent { var type: EventType { get } } @@ -15,6 +13,7 @@ extension CommandEvent: StreamableEvent {} extension SDAMEvent: StreamableEvent {} +@available(macOS 10.15, *) final class APMTests: MongoSwiftTestCase { func testClientFinishesCommandStreams() async throws { let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) @@ -41,7 +40,7 @@ final class APMTests: MongoSwiftTestCase { .commandStartedEvent, .commandSucceededEvent ] - let clientTask = try await self.withTestClient { client -> Task in + let clientTask = try await self.withTestNamespace { client, db, _ -> Task in let cmdTask = Task { () -> Int in var i = 0 for try await event in client.commandEventStream() { @@ -52,7 +51,7 @@ final class APMTests: MongoSwiftTestCase { return i } - try await client.db("admin").runCommand(["ping": 1]) + try await db.runCommand(["ping": 1]) return cmdTask } let numEvents = try await clientTask.value @@ -85,15 +84,25 @@ final class APMTests: MongoSwiftTestCase { expect(numEventsBuffer).to(beLessThan(120)) } + actor TaskCounter { + var taskCounter = 0 + + func incr() { + self.taskCounter += 1 + } + } + /// Helper that tests kicking off multiple streams concurrently fileprivate func concurrentStreamTestHelper(f: @escaping (MongoClient) -> T) async throws where T: AsyncSequence, T.Element: StreamableEvent { var taskArr: [Task<[EventType], Error>] = [] - try await self.withTestNamespace { client, _, coll in + let taskActor = TaskCounter() + try await self.withTestNamespace { client, db, coll in // Kick off 5 streams for _ in 0...4 { let task = Task { () -> [EventType] in + await taskActor.incr() var eventArr: [EventType] = [] for try await event in f(client) { eventArr.append(event.type) @@ -104,13 +113,14 @@ final class APMTests: MongoSwiftTestCase { taskArr.append(task) } - try await client.db("admin").runCommand(["ping": 1]) + // Tasks start + try await assertIsEventuallyTrue(description: "each task is started") { + await taskActor.taskCounter == 5 + } + + try await db.runCommand(["ping": 1]) try await coll.insertOne(["hello": "world"]) } - // Tasks start and finish - try await assertIsEventuallyTrue(description: "each task is started") { - taskArr.count == 5 - } // Expect all tasks received the same number (>0) of events for i in 0...4 { @@ -157,7 +167,7 @@ final class APMTests: MongoSwiftTestCase { let taskStarted = NIOAtomic.makeAtomic(value: false) // Events seen by the regular SDAM handler var handlerEvents: [EventType] = [] - let clientTask = try await self.withTestNamespace { client, _, coll -> Task<[EventType], Error> in + let clientTask = try await self.withTestNamespace { client, db, coll -> Task<[EventType], Error> in client.addSDAMEventHandler { event in if !event.isHeartbeatEvent { handlerEvents.append(event.type) @@ -179,7 +189,7 @@ final class APMTests: MongoSwiftTestCase { taskStarted.load() } - try await client.db("admin").runCommand(["ping": 1]) + try await db.runCommand(["ping": 1]) try await coll.insertOne(["hello": "world"]) return sdamTask } diff --git a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift index 03374f8a1..4ed20211e 100644 --- a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift +++ b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift @@ -64,7 +64,7 @@ func assertIsEventuallyTrue( let workTask = Task { () -> Bool in while !Task.isCancelled { guard try await block() else { - try await Task.sleep(seconds: sleepInterval) + try? await Task.sleep(seconds: sleepInterval) continue } // task succeeded to we return true From 974611867bba12711d5b834fb134a8d031f247d9 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Mon, 25 Jul 2022 14:38:03 -0400 Subject: [PATCH 42/47] Adding drop expectation bc of namespace in cmd tests --- Tests/MongoSwiftTests/APMTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index 854cb380a..a82ce47e4 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -33,8 +33,10 @@ final class APMTests: MongoSwiftTestCase { } func testCommandStreamHasCorrectEvents() async throws { - let commandStr: [String] = ["ping", "ping", "endSessions", "endSessions"] + let commandStr: [String] = ["ping", "ping", "drop", "drop", "endSessions", "endSessions"] let eventTypes: [EventType] = [ + .commandStartedEvent, + .commandSucceededEvent, .commandStartedEvent, .commandSucceededEvent, .commandStartedEvent, @@ -55,7 +57,7 @@ final class APMTests: MongoSwiftTestCase { return cmdTask } let numEvents = try await clientTask.value - expect(numEvents).to(equal(4)) + expect(numEvents).to(equal(6)) } func testCommandStreamBufferingPolicy() async throws { From 2617a1458a2ed3fe343a9fc8a6fadf2b3afc9f53 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Tue, 26 Jul 2022 18:21:13 -0400 Subject: [PATCH 43/47] Adding backticks to docstring --- Sources/MongoSwift/MongoClient.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index ca61d6659..5a578f9e1 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -444,11 +444,11 @@ public class MongoClient { * Provides an `AsyncSequence` API for consuming command monitoring events. * * Example: printing the command events out would be written as - - for try await event in client.commandEventStream() { - print(event) - } - + * ``` + * for try await event in client.commandEventStream() { + * print(event) + * } + * ``` * Wrapping in a `Task { ... }` may be desired for asynchronicity. * - Returns: A `CommandEventStream` that implements `AsyncSequence`. * - Note: Only the most recent 100 events are stored in the stream. @@ -476,11 +476,11 @@ public class MongoClient { * Provides an `AsyncSequence` API for consuming SDAM monitoring events. * * Example: printing the SDAM events out would be written as - - for try await event in client.sdamEventStream() { - print(event) - } - + * ``` + * for try await event in client.sdamEventStream() { + * print(event) + * } + * ``` * Wrapping in a `Task { ... }` may be desired for asynchronicity. * - Returns: An `SDAMEventStream` that implements `AsyncSequence`. * - Note: Only the most recent 100 events are stored in the stream. From c567588ee5ee443eb78f210a55a19726e5d694af Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 27 Jul 2022 14:07:39 -0400 Subject: [PATCH 44/47] Minor variable/comment/doc changes + taskGroup for concurrent stream --- Sources/MongoSwift/APM.swift | 8 ++-- Sources/MongoSwift/MongoClient.swift | 20 +++++----- Tests/MongoSwiftTests/APMTests.swift | 60 ++++++++++++++-------------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Sources/MongoSwift/APM.swift b/Sources/MongoSwift/APM.swift index 2e81486a2..83bfc9bec 100644 --- a/Sources/MongoSwift/APM.swift +++ b/Sources/MongoSwift/APM.swift @@ -125,7 +125,7 @@ private protocol CommandEventProtocol { public struct CommandEventStream { fileprivate let stream: AsyncStream private let cmdHandler: CommandEventHandler - /// Initialize the stream with a `CommandEventHandler` + /// Initialize the stream with a `CommandEventHandler` internal init(cmdHandler: CommandEventHandler, stream: AsyncStream) { self.cmdHandler = cmdHandler self.stream = stream @@ -139,7 +139,7 @@ public struct CommandEventStream { public struct SDAMEventStream { fileprivate let stream: AsyncStream private let sdamHandler: SDAMEventHandler - /// Initialize the stream with a `CommandEventHandler` + /// Initialize the stream with an `SDAMEventHandler` internal init(sdamHandler: SDAMEventHandler, stream: AsyncStream) { self.sdamHandler = sdamHandler self.stream = stream @@ -190,7 +190,7 @@ public struct CommandEventStreamIterator: AsyncIteratorProtocol { } /// Asynchronously advances to the next element and returns it, or ends the sequence if there is no next element. - public mutating func next() async throws -> CommandEvent? { + public mutating func next() async -> CommandEvent? { await self.iterator.next() } @@ -212,7 +212,7 @@ public struct SDAMEventStreamIterator: AsyncIteratorProtocol { } /// Asynchronously advances to the next element and returns it, or ends the sequence if there is no next element. - public mutating func next() async throws -> SDAMEvent? { + public mutating func next() async -> SDAMEvent? { await self.iterator.next() } diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index 5a578f9e1..fb6d48b93 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -277,12 +277,15 @@ public class MongoClient { /// - This value is only read in `deinit`. That occurs exactly once after the above modification is complete. private var wasClosed = false - /// Handlers for command monitoring events. + /// Handlers for command monitoring events. Should only be accessed when holding `eventHandlerLock`. internal var commandEventHandlers: [CommandEventHandler] - /// Handlers for SDAM monitoring events. + /// Handlers for SDAM monitoring events. Should only be accessed when holding `eventHandlerLock`. internal var sdamEventHandlers: [SDAMEventHandler] + /// Lock used to synchronize access to the event handler arrays to prevent data races. + private let eventHandlerLock: Lock = .init() + /// Counter for generating client _ids. internal static var clientIDGenerator = NIOAtomic.makeAtomic(value: 0) @@ -304,9 +307,6 @@ public class MongoClient { /// The write concern set on this client, or nil if one is not set. public let writeConcern: WriteConcern? - /// Lock used to synchronize access to the event handler arrays to prevent data races. - private let eventHandlerLock: Lock = .init() - /** * Create a new client for a MongoDB deployment. For options that included in both the `MongoConnectionString` * and the `MongoClientOptions` struct, the final value is set in descending order of priority: the value specified @@ -408,7 +408,7 @@ public class MongoClient { #if compiler(>=5.5.2) && canImport(_Concurrency) @available(macOS 10.15, *) internal class CmdHandler: CommandEventHandler { - private var continuation: AsyncStream.Continuation + private let continuation: AsyncStream.Continuation internal init(continuation: AsyncStream.Continuation) { self.continuation = continuation } @@ -425,7 +425,7 @@ public class MongoClient { @available(macOS 10.15, *) internal class SDAMHandler: SDAMEventHandler { - private var continuation: AsyncStream.Continuation + private let continuation: AsyncStream.Continuation internal init(continuation: AsyncStream.Continuation) { self.continuation = continuation } @@ -449,7 +449,8 @@ public class MongoClient { * print(event) * } * ``` - * Wrapping in a `Task { ... }` may be desired for asynchronicity. + * If you are looping over the events in the stream, you may wish to do so in a dedicated `Task`. + * The stream will be ended automatically if the `Task` it is running in is cancelled. * - Returns: A `CommandEventStream` that implements `AsyncSequence`. * - Note: Only the most recent 100 events are stored in the stream. */ @@ -481,7 +482,8 @@ public class MongoClient { * print(event) * } * ``` - * Wrapping in a `Task { ... }` may be desired for asynchronicity. + * If you are looping over the events in the stream, you may wish to do so in a dedicated `Task`. + * The stream will be ended automatically if the `Task` it is running in is cancelled. * - Returns: An `SDAMEventStream` that implements `AsyncSequence`. * - Note: Only the most recent 100 events are stored in the stream. */ diff --git a/Tests/MongoSwiftTests/APMTests.swift b/Tests/MongoSwiftTests/APMTests.swift index a82ce47e4..e481a5eac 100644 --- a/Tests/MongoSwiftTests/APMTests.swift +++ b/Tests/MongoSwiftTests/APMTests.swift @@ -86,52 +86,50 @@ final class APMTests: MongoSwiftTestCase { expect(numEventsBuffer).to(beLessThan(120)) } - actor TaskCounter { - var taskCounter = 0 - - func incr() { - self.taskCounter += 1 - } - } - /// Helper that tests kicking off multiple streams concurrently fileprivate func concurrentStreamTestHelper(f: @escaping (MongoClient) -> T) async throws where T: AsyncSequence, T.Element: StreamableEvent { - var taskArr: [Task<[EventType], Error>] = [] - let taskActor = TaskCounter() - try await self.withTestNamespace { client, db, coll in - // Kick off 5 streams - for _ in 0...4 { - let task = Task { () -> [EventType] in - await taskActor.incr() - var eventArr: [EventType] = [] - for try await event in f(client) { - eventArr.append(event.type) + let taskCounter = NIOAtomic.makeAtomic(value: 0) + let concurrentTaskGroupCorrect = try await withThrowingTaskGroup( + of: [EventType].self, + returning: [[EventType]].self + ) { taskGroup in + // Client used for stream and then closed with taskGroup in scope to ensure all tasks return + try await self.withTestNamespace { client, db, coll in + for _ in 0...4 { + // Add 5 tasks + taskGroup.addTask { + taskCounter.add(1) + var eventArr: [EventType] = [] + for try await event in f(client) { + eventArr.append(event.type) + } + return eventArr } - - return eventArr } - taskArr.append(task) + // Ensure all tasks start, then run commands + try await assertIsEventuallyTrue(description: "each task is started") { + taskCounter.load() == 5 + } + try await db.runCommand(["ping": 1]) + try await coll.insertOne(["hello": "world"]) } - - // Tasks start - try await assertIsEventuallyTrue(description: "each task is started") { - await taskActor.taskCounter == 5 + var taskArr: [[EventType]] = [] + for try await result in taskGroup { + taskArr.append(result) } - - try await db.runCommand(["ping": 1]) - try await coll.insertOne(["hello": "world"]) + return taskArr } // Expect all tasks received the same number (>0) of events for i in 0...4 { - let eventArrOutput = try await taskArr[i].value + let eventArrOutput = concurrentTaskGroupCorrect[i] expect(eventArrOutput.count).to(beGreaterThan(0)) } for i in 1...4 { - let eventArrOld = try await taskArr[i - 1].value - let eventArrCurr = try await taskArr[i].value + let eventArrOld = concurrentTaskGroupCorrect[i] + let eventArrCurr = concurrentTaskGroupCorrect[i] expect(eventArrOld).to(equal(eventArrCurr)) } } From b25aeb94e985b6e10e833ed5bc95886b243049f4 Mon Sep 17 00:00:00 2001 From: Rohan Chhaya <67851478+rchhaya@users.noreply.github.com> Date: Wed, 27 Jul 2022 15:14:18 -0400 Subject: [PATCH 45/47] Apply suggestions from code review Co-authored-by: Kaitlin Mahar --- Sources/MongoSwift/MongoClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MongoSwift/MongoClient.swift b/Sources/MongoSwift/MongoClient.swift index fb6d48b93..5a7f1a0e7 100644 --- a/Sources/MongoSwift/MongoClient.swift +++ b/Sources/MongoSwift/MongoClient.swift @@ -450,7 +450,7 @@ public class MongoClient { * } * ``` * If you are looping over the events in the stream, you may wish to do so in a dedicated `Task`. - * The stream will be ended automatically if the `Task` it is running in is cancelled. + * The stream will be ended automatically if the `Task` it is running in is cancelled. * - Returns: A `CommandEventStream` that implements `AsyncSequence`. * - Note: Only the most recent 100 events are stored in the stream. */ @@ -483,7 +483,7 @@ public class MongoClient { * } * ``` * If you are looping over the events in the stream, you may wish to do so in a dedicated `Task`. - * The stream will be ended automatically if the `Task` it is running in is cancelled. + * The stream will be ended automatically if the `Task` it is running in is cancelled. * - Returns: An `SDAMEventStream` that implements `AsyncSequence`. * - Note: Only the most recent 100 events are stored in the stream. */ From f6c6badd11d043d82b8beed393123b0e7cb61a46 Mon Sep 17 00:00:00 2001 From: Rohan Harshal Chhaya Date: Wed, 27 Jul 2022 15:25:57 -0400 Subject: [PATCH 46/47] Comment explaining optional try for cancellationerror --- Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift index 4ed20211e..d4f2d89b1 100644 --- a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift +++ b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift @@ -64,6 +64,7 @@ func assertIsEventuallyTrue( let workTask = Task { () -> Bool in while !Task.isCancelled { guard try await block() else { + // Optional bc if task is cancelled, we want to return false and not encounter a `CancellationError` try? await Task.sleep(seconds: sleepInterval) continue } From 8d5b7dd90624f8e0f693aeb0961a71e9cb82b45c Mon Sep 17 00:00:00 2001 From: Rohan Chhaya <67851478+rchhaya@users.noreply.github.com> Date: Thu, 28 Jul 2022 15:36:01 -0400 Subject: [PATCH 47/47] Update Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift Co-authored-by: Isabel Atkinson --- Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift index d4f2d89b1..6eb7fbe81 100644 --- a/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift +++ b/Tests/MongoSwiftTests/AsyncAwaitTestUtils.swift @@ -68,7 +68,7 @@ func assertIsEventuallyTrue( try? await Task.sleep(seconds: sleepInterval) continue } - // task succeeded to we return true + // task succeeded so we return true return true } // Ran out of time before we succeeded, so return false