From 3905dd55125a40b29bccc69b259d7bc7921b8b9f Mon Sep 17 00:00:00 2001 From: Andrew Hershberger Date: Mon, 4 Aug 2025 13:19:07 -0400 Subject: [PATCH 1/2] Fix crash in DebounceAsyncSequence The expression `UInt64(self.dueTime - Date().timeIntervalSince(lastEmission))` crashes if `self.dueTime - Date().timeIntervalSince(lastEmission)` is less than `UInt64.min`. Since `Date().timeIntervalSince(lastEmission)` seems like it normally evaluates to something near 0, the suspicion is that the crash must occur when the process is suspended in between `lastEmission = Date()` and `Date().timeIntervalSince(lastEmission)`. This commit removes `lastEmission` entirely and ensures that the value passed to `UInt64.min` is non-negative. Fixes #60 --- .../Sequences/DebounceAsyncSequence.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift b/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift index 0c7f23d..17e8b2e 100644 --- a/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift +++ b/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift @@ -92,8 +92,7 @@ extension DebounceAsyncSequence { public mutating func next() async rethrows -> Element? { var lastResult: Result? - var lastEmission: Date = .init() - + while true { let resultTask = self.resultTask ?? Task { [base] in var iterator = base @@ -105,14 +104,12 @@ extension DebounceAsyncSequence { } } self.resultTask = nil - - lastEmission = Date() - let delay = UInt64(self.dueTime - Date().timeIntervalSince(lastEmission)) * 1_000_000_000 - let sleep = Task { - try? await Task.sleep(nanoseconds: delay) + + let sleep = Task { [dueTime] in + try? await Task.sleep(nanoseconds: UInt64(Swift.max(dueTime, 0)) * 1_000_000_000) return .sleep } - + let tasks = [resultTask, sleep] let firstTask = await { () async -> Task in let raceCoordinator = TaskRaceCoodinator() @@ -140,7 +137,6 @@ extension DebounceAsyncSequence { switch await firstTask.value { case .winner(let result, let iterator): lastResult = result - lastEmission = Date() self.base = iterator switch result { From 721606cf8b9bb6722dbfad2e73fccb222d99960d Mon Sep 17 00:00:00 2001 From: Andrew Hershberger Date: Mon, 4 Aug 2025 13:21:11 -0400 Subject: [PATCH 2/2] Use NSEC_PER_SEC instead of 1_000_000_000 literal --- Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift b/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift index 17e8b2e..2e772ea 100644 --- a/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift +++ b/Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift @@ -106,7 +106,7 @@ extension DebounceAsyncSequence { self.resultTask = nil let sleep = Task { [dueTime] in - try? await Task.sleep(nanoseconds: UInt64(Swift.max(dueTime, 0)) * 1_000_000_000) + try? await Task.sleep(nanoseconds: UInt64(Swift.max(dueTime, 0)) * NSEC_PER_SEC) return .sleep }