diff --git a/Kit/module/module.swift b/Kit/module/module.swift index 684c4da4cc9..0aefce5e6ab 100644 --- a/Kit/module/module.swift +++ b/Kit/module/module.swift @@ -238,14 +238,22 @@ open class Module { // call when popup appear/disappear private func visibilityCallback(_ state: Bool) { - self.readers.filter{ $0.popup }.forEach { (reader: Reader_p) in - if state { + let popupReaders = self.readers.filter { $0.popup } + + if state { + popupReaders.forEach { (reader: Reader_p) in reader.unlock() reader.start() - } else { - reader.pause() - reader.lock() } + self.readers.filter { !$0.popup }.forEach { (reader: Reader_p) in + reader.replay() + } + return + } + + popupReaders.forEach { (reader: Reader_p) in + reader.pause() + reader.lock() } } diff --git a/Kit/module/reader.swift b/Kit/module/reader.swift index 41286390461..219abbdec49 100644 --- a/Kit/module/reader.swift +++ b/Kit/module/reader.swift @@ -22,6 +22,7 @@ public protocol Reader_p { func start() func pause() func stop() + func replay() func lock() func unlock() @@ -138,14 +139,16 @@ open class Reader: NSObject, ReaderInternal_p { } } - if !self.initlizalized { + let shouldPrimeRead = !self.initlizalized || (self.popup && !self.active) + self.active = true + + if shouldPrimeRead { DispatchQueue.global(qos: .background).async { self.read() } self.initlizalized = true } self.repeatTask?.start() - self.active = true } open func pause() { @@ -165,6 +168,11 @@ open class Reader: NSObject, ReaderInternal_p { self.interval = Double(value) self.repeatTask?.reset(seconds: value, restart: true) } + + public func replay() { + guard self.active, let value = self.value else { return } + self.callbackHandler(value) + } public func save(_ value: T) { DB.shared.insert(key: "\(self.module.stringValue)@\(self.name)", value: value, ts: self.history, force: true) diff --git a/Tests/RAM.swift b/Tests/RAM.swift index 2a9b1b83942..e76e0a570bc 100644 --- a/Tests/RAM.swift +++ b/Tests/RAM.swift @@ -11,6 +11,7 @@ import XCTest import RAM +import Kit class RAM: XCTestCase { func testProcessReader_parseProcess() throws { @@ -79,3 +80,82 @@ class RAM: XCTestCase { XCTAssertEqual(process.usage, 658 * Double(1000 * 1000)) } } + +final class ReaderLifecycleTests: XCTestCase { + private final class ProbeReader: Reader { + private let queue = DispatchQueue(label: "io.serhiy.Stats.Tests.ProbeReader") + private var _readCount: Int = 0 + var onRead: ((Int) -> Void)? + + var readCount: Int { + self.queue.sync { self._readCount } + } + + override func read() { + self.queue.sync { + self._readCount += 1 + let current = self._readCount + self.onRead?(current) + } + self.callback(self.readCount) + } + } + + func testPopupReaderForcesReadWhenReopened() { + let reader = ProbeReader(.CPU, popup: true) + reader.unlock() + + let firstRead = expectation(description: "first read") + reader.onRead = { count in + if count == 1 { + firstRead.fulfill() + } + } + + reader.start() + wait(for: [firstRead], timeout: 5) + + reader.pause() + + let secondRead = expectation(description: "second read after reopen") + reader.onRead = { count in + if count == 2 { + secondRead.fulfill() + } + } + + reader.start() + wait(for: [secondRead], timeout: 5) + + reader.stop() + } + + func testReplayEmitsCachedValueWithoutNewRead() { + var callbackValues: [Int] = [] + let reader = ProbeReader(.CPU) { value in + if let value { + callbackValues.append(value) + } + } + let firstRead = expectation(description: "initial read") + reader.onRead = { count in + if count == 1 { + firstRead.fulfill() + } + } + + reader.start() + wait(for: [firstRead], timeout: 5) + + XCTAssertEqual(reader.readCount, 1) + let callbacksBeforeReplay = callbackValues.count + + reader.replay() + + XCTAssertEqual(reader.readCount, 1) + XCTAssertEqual(callbackValues.count, callbacksBeforeReplay + 1) + XCTAssertEqual(callbackValues.last, 1) + + reader.stop() + } +}