Просмотр исходного кода

Changes for Concurrency (#575)

* Changes for Concurrency

* this should clean up CoreData to run thread-safe

* Make sure to wrap the fetch calls as well

* make sure variables defined in right scope

* this seems to get me through ~20 minutes running without any issues ...
I have statistics set for 15 minutes, so I've been through at least
one statistics() run as well as everything else ...
Marc G. Fournier 3 лет назад
Родитель
Сommit
7fea5a67ec

+ 57 - 56
FreeAPS/Sources/APS/APSManager.swift

@@ -79,7 +79,7 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     private var openAPS: OpenAPS!
 
@@ -671,9 +671,9 @@ final class BaseAPSManager: APSManager, Injectable {
             // Update the TDD value
             tdd(enacted_: enacted)
             // Update statistics. Only run if enabled in preferences
-            if settingsManager.settings.displayStatistics {
-                statistics()
-            }
+            // if settingsManager.settings.displayStatistics {
+            statistics()
+            // }
         }
     }
 
@@ -689,45 +689,30 @@ final class BaseAPSManager: APSManager, Injectable {
             let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
 
             var uniqEvents = [TDD]()
-            let requestTDD = TDD.fetchRequest() as NSFetchRequest<TDD>
-            requestTDD.predicate = NSPredicate(format: "timestamp > %@ AND tdd > 0", tenDaysAgo as NSDate)
-            let sortTDD = NSSortDescriptor(key: "timestamp", ascending: true)
-            requestTDD.sortDescriptors = [sortTDD]
+            var total: Decimal = 0
+            var totalAmount: Decimal = 0
+            var indeces: Int = 0
+            var nrOfIndeces: Int = 0
 
-            try? uniqEvents = coredataContext.fetch(requestTDD)
+            coredataContext.performAndWait {
+                let requestTDD = TDD.fetchRequest() as NSFetchRequest<TDD>
 
-            var total: Decimal = 0
-            total = uniqEvents.compactMap({ each in each.tdd as? Decimal ?? 0 }).reduce(0, +)
-            var indeces = uniqEvents.count
+                requestTDD.predicate = NSPredicate(format: "timestamp > %@ AND tdd > 0", tenDaysAgo as NSDate)
 
-            /*
-             if uniqEvents.first != nil {
-             for uniqEvent in uniqEvents {
-             total += uniqEvent != Empty ? (uniqEvent.tdd ?? 0) as Decimal : 0
-             indeces += 1
-             }
-             }
-             */
+                let sortTDD = NSSortDescriptor(key: "timestamp", ascending: true)
+                requestTDD.sortDescriptors = [sortTDD]
 
-            // Only fetch once. Use same (previous) fetch
-            let twoHoursArray = uniqEvents.filter({ ($0.timestamp ?? Date()) >= twoHoursAgo })
+                try? uniqEvents = coredataContext.fetch(requestTDD)
 
-            var totalAmount: Decimal = 0
+                total = uniqEvents.compactMap({ each in each.tdd as? Decimal ?? 0 }).reduce(0, +)
+                indeces = uniqEvents.count
 
-            totalAmount = twoHoursArray.compactMap({ each in each.tdd as? Decimal ?? 0 }).reduce(0, +)
-            var nrOfIndeces = twoHoursArray.count
-
-            /*
-             var nrOfIndeces: Decimal = 0
-             if twoHoursArray.first != nil {
-             for entry in twoHoursArray {
-             if (entry.tdd?.decimalValue ?? 0) > 0 {
-             totalAmount += entry.tdd?.decimalValue ?? 0
-             nrOfIndeces += 1
-             }
-             }
-             }
-             */
+                // Only fetch once. Use same (previous) fetch
+                let twoHoursArray = uniqEvents.filter({ ($0.timestamp ?? Date()) >= twoHoursAgo })
+                nrOfIndeces = twoHoursArray.count
+
+                totalAmount = twoHoursArray.compactMap({ each in each.tdd as? Decimal ?? 0 }).reduce(0, +)
+            }
 
             if indeces == 0 {
                 indeces = 1
@@ -800,14 +785,21 @@ final class BaseAPSManager: APSManager, Injectable {
         // MARK: Fetch Carbs from CoreData
 
         var carbs = [Carbohydrates]()
-        let requestCarbs = Carbohydrates.fetchRequest() as NSFetchRequest<Carbohydrates>
-        let daysAgo = Date().addingTimeInterval(-1.days.timeInterval)
-        requestCarbs.predicate = NSPredicate(format: "carbs > 0 AND date > %@", daysAgo as NSDate)
-        let sortCarbs = NSSortDescriptor(key: "date", ascending: true)
-        requestCarbs.sortDescriptors = [sortCarbs]
-        try? carbs = coredataContext.fetch(requestCarbs)
+        var carbTotal: Decimal = 0
+
+        coredataContext.performAndWait {
+            let requestCarbs = Carbohydrates.fetchRequest() as NSFetchRequest<Carbohydrates>
+
+            let daysAgo = Date().addingTimeInterval(-1.days.timeInterval)
+            requestCarbs.predicate = NSPredicate(format: "carbs > 0 AND date > %@", daysAgo as NSDate)
 
-        let carbTotal = carbs.map({ carbs in carbs.carbs as? Decimal ?? 0 }).reduce(0, +)
+            let sortCarbs = NSSortDescriptor(key: "date", ascending: true)
+            requestCarbs.sortDescriptors = [sortCarbs]
+
+            try? carbs = coredataContext.fetch(requestCarbs)
+
+            carbTotal = carbs.map({ carbs in carbs.carbs as? Decimal ?? 0 }).reduce(0, +)
+        }
 
         // MARK: Fetch TDD from CoreData
 
@@ -1103,12 +1095,17 @@ final class BaseAPSManager: APSManager, Injectable {
 
         // MARK: Save to Median to CoreData
 
-        let saveMedianToCoreData = BGmedian(context: coredataContext)
-        saveMedianToCoreData.date = Date()
-        saveMedianToCoreData.median = median.total as NSDecimalNumber
-        saveMedianToCoreData.median_1 = median.day as NSDecimalNumber
-        saveMedianToCoreData.median_7 = median.week as NSDecimalNumber
-        saveMedianToCoreData.median_30 = median.month as NSDecimalNumber
+        coredataContext.perform {
+            let saveMedianToCoreData = BGmedian(context: self.coredataContext)
+
+            saveMedianToCoreData.date = Date()
+            saveMedianToCoreData.median = median.total as NSDecimalNumber
+            saveMedianToCoreData.median_1 = median.day as NSDecimalNumber
+            saveMedianToCoreData.median_7 = median.week as NSDecimalNumber
+            saveMedianToCoreData.median_30 = median.month as NSDecimalNumber
+
+            try? self.coredataContext.save()
+        }
 
         var hbs = Durations(
             day: roundDecimal(NGSPa1CStatisticValue, 1),
@@ -1331,12 +1328,16 @@ final class BaseAPSManager: APSManager, Injectable {
     private func loopStats(loopStatRecord: LoopStats) {
         let LoopStatsStartedAt = Date()
 
-        let nLS = LoopStatRecord(context: coredataContext)
-        nLS.start = loopStatRecord.start
-        nLS.end = loopStatRecord.end ?? Date()
-        nLS.loopStatus = loopStatRecord.loopStatus
-        nLS.duration = loopStatRecord.duration ?? 0.0
-        try? coredataContext.save()
+        coredataContext.perform {
+            let nLS = LoopStatRecord(context: self.coredataContext)
+
+            nLS.start = loopStatRecord.start
+            nLS.end = loopStatRecord.end ?? Date()
+            nLS.loopStatus = loopStatRecord.loopStatus
+            nLS.duration = loopStatRecord.duration ?? 0.0
+
+            try? self.coredataContext.save()
+        }
 
         print("Test time of LoopStats computation: \(-1 * LoopStatsStartedAt.timeIntervalSinceNow) s")
     }

+ 10 - 5
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -9,7 +9,7 @@ final class OpenAPS {
 
     private let storage: FileStorage
 
-    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     init(storage: FileStorage) {
         self.storage = storage
@@ -84,23 +84,28 @@ final class OpenAPS {
 
                     // MARK: Save to CoreData also. To do: Remove JSON saving
 
-                    self.coredataContext.performAndWait {
-                        var saveToTDD = TDD(context: self.coredataContext)
+                    if suggestion.tdd ?? 0 > 0 {
+                        self.coredataContext.perform {
+                            let saveToTDD = TDD(context: self.coredataContext)
 
-                        if suggestion.tdd ?? 0 > 0 {
-                            // let saveToTDD = TDD(context: self.coredataContext)
                             saveToTDD.timestamp = suggestion.timestamp ?? Date()
                             saveToTDD.tdd = (suggestion.tdd ?? 0) as NSDecimalNumber?
+
                             try? self.coredataContext.save()
+                        }
 
+                        self.coredataContext.perform {
                             let saveToInsulin = InsulinDistribution(context: self.coredataContext)
+
                             saveToInsulin.bolus = (suggestion.insulin?.bolus ?? 0) as NSDecimalNumber?
                             saveToInsulin.scheduledBasal = (suggestion.insulin?.scheduled_basal ?? 0) as NSDecimalNumber?
                             saveToInsulin.tempBasal = (suggestion.insulin?.temp_basal ?? 0) as NSDecimalNumber?
                             saveToInsulin.date = Date()
+
                             try? self.coredataContext.save()
                         }
                     }
+
                     promise(.success(suggestion))
                 } else {
                     promise(.success(nil))

+ 9 - 5
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -20,7 +20,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     @Injected() private var storage: FileStorage!
     @Injected() private var broadcaster: Broadcaster!
 
-    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     init(resolver: Resolver) {
         injectServices(resolver)
@@ -47,10 +47,14 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 carbDate = carbs[0].createdAt
             }
             if cbs != 0 {
-                let carbDataForStats = Carbohydrates(context: coredataContext)
-                carbDataForStats.date = carbDate
-                carbDataForStats.carbs = cbs as NSDecimalNumber
-                try? coredataContext.save()
+                self.coredataContext.perform {
+                    let carbDataForStats = Carbohydrates(context: self.coredataContext)
+
+                    carbDataForStats.date = carbDate
+                    carbDataForStats.carbs = cbs as NSDecimalNumber
+
+                    try? self.coredataContext.save()
+                }
             }
 
             broadcaster.notify(CarbsObserver.self, on: processQueue) {

+ 9 - 9
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -25,7 +25,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var settingsManager: SettingsManager!
 
-    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     private enum Config {
         static let filterTime: TimeInterval = 4.5 * 60
@@ -65,16 +65,16 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                     bgDate = glucose[0].dateString
                 }
 
-                // coredataContext.performAndWait {
-                var dataForForStats = Readings(context: coredataContext)
-
                 if bg_ != 0 {
-                    // let dataForStats = Readings(context: coredataContext)
-                    dataForForStats.date = bgDate
-                    dataForForStats.glucose = Int16(bg_)
-                    try? coredataContext.save()
+                    self.coredataContext.perform {
+                        let dataForForStats = Readings(context: self.coredataContext)
+
+                        dataForForStats.date = bgDate
+                        dataForForStats.glucose = Int16(bg_)
+
+                        try? self.coredataContext.save()
+                    }
                 }
-                // }
             }
 
             self.storage.transaction { storage in

+ 4 - 0
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -1,3 +1,4 @@
+import Firebase
 import SwiftUI
 import Swinject
 
@@ -43,6 +44,9 @@ import Swinject
     }
 
     init() {
+        // Use the Firebase library to configure APIs.
+        FirebaseApp.configure()
+
         debug(
             .default,
             "FreeAPS X Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(Bundle.main.buildDate)]"

+ 11 - 8
FreeAPS/Sources/Helpers/CoreDataStack.swift

@@ -19,14 +19,17 @@ class CoreDataStack {
 
     func saveContext() {
         let context = persistentContainer.viewContext
-        if context.hasChanges {
-            do {
-                try context.save()
-            } catch {
-                // Replace this implementation with code to handle the error appropriately.
-                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
-                let nserror = error as NSError
-                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
+
+        context.perform {
+            if context.hasChanges {
+                do {
+                    try context.save()
+                } catch {
+                    // Replace this implementation with code to handle the error appropriately.
+                    // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping    application, although it may be useful during development.
+                    let nserror = error as NSError
+                    fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
+                }
             }
         }
     }