From a69627f13556f5b498791c030b95e67f53747b58 Mon Sep 17 00:00:00 2001 From: Andy Chong Date: Mon, 2 Feb 2026 00:39:00 +0800 Subject: [PATCH] fix: fixed race condition crashes in Chart and Stack widgets (#2944) Ensure data mutations from background readers are dispatched to the main thread to prevent conflicts with drawing logic (which runs on main thread). This resolves SIGTRAP and SIGABRT crashes observed during extended runtime. --- Kit/Widgets/BarChart.swift | 34 ++++++++++++++++++++-------------- Kit/Widgets/LineChart.swift | 6 +++--- Kit/Widgets/NetworkChart.swift | 6 +++--- Kit/Widgets/Stack.swift | 34 +++++++++++++++++----------------- 4 files changed, 43 insertions(+), 37 deletions(-) diff --git a/Kit/Widgets/BarChart.swift b/Kit/Widgets/BarChart.swift index 07ef38b7..49f1c5c7 100644 --- a/Kit/Widgets/BarChart.swift +++ b/Kit/Widgets/BarChart.swift @@ -223,27 +223,33 @@ public class BarChart: WidgetWrapper { } public func setValue(_ newValue: [[ColorValue]]) { - let tolerance: CGFloat = 0.01 - let isDifferent = self._value.count != newValue.count || zip(self._value, newValue).contains { row1, row2 in - row1.count != row2.count || zip(row1, row2).contains { val1, val2 in - abs(val1.value - val2.value) > tolerance || val1.color != val2.color + DispatchQueue.main.async(execute: { + let tolerance: CGFloat = 0.01 + let isDifferent = self._value.count != newValue.count || zip(self._value, newValue).contains { row1, row2 in + row1.count != row2.count || zip(row1, row2).contains { val1, val2 in + abs(val1.value - val2.value) > tolerance || val1.color != val2.color + } } - } - guard isDifferent else { return } - self._value = newValue - self.redraw() + guard isDifferent else { return } + self._value = newValue + self.redraw() + }) } public func setPressure(_ newPressureLevel: RAMPressure) { - guard self._pressureLevel != newPressureLevel else { return } - self._pressureLevel = newPressureLevel - self.redraw() + DispatchQueue.main.async(execute: { + guard self._pressureLevel != newPressureLevel else { return } + self._pressureLevel = newPressureLevel + self.redraw() + }) } public func setColorZones(_ newColorZones: colorZones) { - guard self._colorZones != newColorZones else { return } - self._colorZones = newColorZones - self.redraw() + DispatchQueue.main.async(execute: { + guard self._colorZones != newColorZones else { return } + self._colorZones = newColorZones + self.redraw() + }) } // MARK: - Settings diff --git a/Kit/Widgets/LineChart.swift b/Kit/Widgets/LineChart.swift index dae43713..7c41cf77 100644 --- a/Kit/Widgets/LineChart.swift +++ b/Kit/Widgets/LineChart.swift @@ -249,17 +249,17 @@ public class LineChart: WidgetWrapper { } public func setValue(_ newValue: Double) { - self._value = newValue DispatchQueue.main.async(execute: { + self._value = newValue self.chart.addValue(newValue) self.display() }) } public func setPressure(_ newPressureLevel: RAMPressure) { - guard self._pressureLevel != newPressureLevel else { return } - self._pressureLevel = newPressureLevel DispatchQueue.main.async(execute: { + guard self._pressureLevel != newPressureLevel else { return } + self._pressureLevel = newPressureLevel self.display() }) } diff --git a/Kit/Widgets/NetworkChart.swift b/Kit/Widgets/NetworkChart.swift index 1ff0a073..8f708364 100644 --- a/Kit/Widgets/NetworkChart.swift +++ b/Kit/Widgets/NetworkChart.swift @@ -263,10 +263,10 @@ public class NetworkChart: WidgetWrapper { } public func setValue(upload: Double, download: Double) { - self.points.remove(at: 0) - self.points.append((upload, download)) - DispatchQueue.main.async(execute: { + self.points.remove(at: 0) + self.points.append((upload, download)) + if self.window?.isVisible ?? false { self.display() } diff --git a/Kit/Widgets/Stack.swift b/Kit/Widgets/Stack.swift index 6d70bbfb..7c908e7b 100644 --- a/Kit/Widgets/Stack.swift +++ b/Kit/Widgets/Stack.swift @@ -226,24 +226,24 @@ public class StackWidget: WidgetWrapper { } public func setValues(_ values: [Stack_t]) { - var tableNeedsToBeUpdated: Bool = false - - values.forEach { (p: Stack_t) in - if let idx = self.values.firstIndex(where: { $0.key == p.key }) { - self.values[idx].value = p.value - return - } - tableNeedsToBeUpdated = true - self.values.append(p) - } - - let diff = self.values.filter({ v in values.contains(where: { $0.key == v.key }) }) - if diff.count != self.values.count { - tableNeedsToBeUpdated = true - } - self.values = diff.sorted(by: { $0.index < $1.index }) - DispatchQueue.main.async(execute: { + var tableNeedsToBeUpdated: Bool = false + + values.forEach { (p: Stack_t) in + if let idx = self.values.firstIndex(where: { $0.key == p.key }) { + self.values[idx].value = p.value + return + } + tableNeedsToBeUpdated = true + self.values.append(p) + } + + let diff = self.values.filter({ v in values.contains(where: { $0.key == v.key }) }) + if diff.count != self.values.count { + tableNeedsToBeUpdated = true + } + self.values = diff.sorted(by: { $0.index < $1.index }) + if tableNeedsToBeUpdated { self.orderTableView.update() }