Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -310,26 +310,40 @@ final class DeviceDataManager {

func instantiateDeviceManagers() {
if let pumpManagerRawValue = rawPumpManager ?? UserDefaults.appGroup?.legacyPumpManagerRawValue {
pumpManager = pumpManagerFromRawValue(pumpManagerRawValue)
// Update lastPumpEventsReconciliation on DoseStore
if let lastSync = pumpManager?.lastSync {
Task {
try? await doseStore.addPumpEvents([], lastReconciliation: lastSync)
if let restoredPumpManager = pumpManagerFromRawValue(pumpManagerRawValue) {
pumpManager = restoredPumpManager
// Update lastPumpEventsReconciliation on DoseStore
if let lastSync = pumpManager?.lastSync {
Task {
try? await doseStore.addPumpEvents([], lastReconciliation: lastSync)
}
}
}
if let status = pumpManager?.status {
updatePumpIsAllowingAutomation(status: status)
if let status = pumpManager?.status {
updatePumpIsAllowingAutomation(status: status)
}
} else {
// We have saved pump state, but its plugin couldn't be instantiated in this build (e.g.
// the app was built without that pump manager plugin). Do NOT assign pumpManager = nil —
// that fires the didSet, which writes rawPumpManager = nil and DELETES the persisted
// PumpManagerState, permanently losing the pod pairing/session keys. Leave the saved
// state on disk so a build that includes the plugin can restore it on a later launch.
log.error("Saved pump manager plugin unavailable; preserving persisted PumpManagerState for a future launch")
}
} else {
pumpManager = nil
}

if let cgmManagerRawValue = rawCGMManager ?? UserDefaults.appGroup?.legacyCGMManagerRawValue {
cgmManager = cgmManagerFromRawValue(cgmManagerRawValue)

// Handle case of PumpManager providing CGM
if cgmManager == nil && pumpManagerTypeFromRawValue(cgmManagerRawValue) != nil {
if let restoredCGMManager = cgmManagerFromRawValue(cgmManagerRawValue) {
cgmManager = restoredCGMManager
} else if pumpManagerTypeFromRawValue(cgmManagerRawValue) != nil {
// Handle case of PumpManager providing CGM
cgmManager = pumpManager as? CGMManager
} else {
// Saved CGM state exists but its plugin couldn't be instantiated in this build. Don't
// assign cgmManager = nil — that would delete the persisted CGMManagerState (same
// footgun as the pump path above). Preserve it for a future launch with the plugin.
log.error("Saved CGM manager plugin unavailable; preserving persisted CGMManagerState for a future launch")
}
}
}
Expand Down Expand Up @@ -837,7 +851,17 @@ extension DeviceDataManager {
}

func updatePumpManagerBLEHeartbeatPreference() {
pumpManager?.setMustProvideBLEHeartbeat(pumpManagerMustProvideBLEHeartbeat)
guard pumpManagerMustProvideBLEHeartbeat else {
pumpManager?.setBLEHeartbeatRequest(nil)
return
}
// Tell the pump when the last CGM reading landed and how often readings are expected, so it can
// schedule its next heartbeat to arrive just after the next reading is due (the pump adds its own
// buffer for the remote CGM value to be fetched and stored).
let request = PumpHeartbeatRequest(
lastCGMReadingDate: glucoseStore.latestGlucose?.startDate,
expectedCGMReadingInterval: cgmManager?.expectedGlucoseSampleInterval ?? .minutes(5))
pumpManager?.setBLEHeartbeatRequest(request)
}
}

Expand Down
27 changes: 11 additions & 16 deletions Loop/Managers/Live Activity/LiveActivityManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class LiveActivityManager : LiveActivityManagerProxy {
let statusContext = UserDefaults.appGroup?.statusExtensionContext
let glucoseFormatter = NumberFormatter.glucoseFormatter(for: unit)

let glucoseSamples = self.getGlucoseSample(unit: unit)
let glucoseSamples = await self.getGlucoseSample(unit: unit)
guard let currentGlucose = glucoseSamples.last else {
print("ERROR: No glucose sample found...")
return
Expand Down Expand Up @@ -327,24 +327,19 @@ class LiveActivityManager : LiveActivityManagerProxy {
return iobFormatter.string(from: iob) ?? "??"
}

private func getGlucoseSample(unit: LoopUnit) -> [StoredGlucoseSample] {
let updateGroup = DispatchGroup()
var samples: [StoredGlucoseSample] = []

private func getGlucoseSample(unit: LoopUnit) async -> [StoredGlucoseSample] {
// When in spacious mode, we want to show the predictive line
// In compact mode, we only want to show the history
let timeInterval: TimeInterval = self.settings.addPredictiveLine ? .hours(-2) : .hours(-6)
updateGroup.enter()
Task {
samples = (try? await self.glucoseStore.getGlucoseSamples(
start: adjustedChartStart(Date.now.addingTimeInterval(timeInterval)),
end: Date.now
)) ?? []
updateGroup.leave()
}

_ = updateGroup.wait(timeout: .distantFuture)
return samples

// NOTE: Previously this bridged async→sync via DispatchGroup.wait(.distantFuture),
// which blocked a Swift Concurrency cooperative-pool thread. Because update() itself
// runs on that pool, repeated calls starved every cooperative thread and deadlocked
// the whole concurrency runtime (Loop stopped looping). Await directly instead.
return (try? await self.glucoseStore.getGlucoseSamples(
start: adjustedChartStart(Date.now.addingTimeInterval(timeInterval)),
end: Date.now
)) ?? []
}

// If the chart start falls past the half-hour mark (HH:31–HH:59), pull it back to HH:30
Expand Down