From 5158575bac24fdc6ce359a335bbd91574549b3ff Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Fri, 25 Apr 2025 16:22:05 +0100 Subject: [PATCH 1/2] Set tolerance to zero when using `Task.sleep` --- .../ClientRPCExecutor+HedgingExecutor.swift | 4 ++-- .../ClientRPCExecutor+OneShotExecutor.swift | 2 +- .../Internal/ClientRPCExecutor+RetryExecutor.swift | 5 +++-- .../Call/Server/Internal/ServerRPCExecutor.swift | 2 +- ...ientRPCExecutorTestHarness+ServerBehavior.swift | 2 +- Tests/GRPCCoreTests/GRPCClientTests.swift | 8 ++++---- .../Internal/Result+CatchingTests.swift | 2 +- .../InProcessClientTransportTests.swift | 14 +++++++------- 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+HedgingExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+HedgingExecutor.swift index 480b23817..e0743bee3 100644 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+HedgingExecutor.swift +++ b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+HedgingExecutor.swift @@ -83,7 +83,7 @@ extension ClientRPCExecutor.HedgingExecutor { if let deadline = self.deadline { group.addTask { let result = await Result { - try await Task.sleep(until: deadline, clock: .continuous) + try await Task.sleep(until: deadline, tolerance: .zero, clock: .continuous) } return .timedOut(result) } @@ -533,7 +533,7 @@ extension ClientRPCExecutor.HedgingExecutor { self._isPushback = pushback self._handle = group.addCancellableTask { do { - try await Task.sleep(for: delay, clock: .continuous) + try await Task.sleep(for: delay, tolerance: .zero, clock: .continuous) return .scheduledAttemptFired(.ran) } catch { return .scheduledAttemptFired(.cancelled) diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+OneShotExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+OneShotExecutor.swift index cc21ad4fc..a4de402af 100644 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+OneShotExecutor.swift +++ b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+OneShotExecutor.swift @@ -137,7 +137,7 @@ func withDeadline( return await withTaskGroup(of: _DeadlineChildTaskResult.self) { group in group.addTask { do { - try await Task.sleep(until: deadline) + try await Task.sleep(until: deadline, tolerance: .zero) return .deadlinePassed } catch { return .timeoutCancelled diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift index bfc62203f..b985342dd 100644 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift +++ b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift @@ -92,7 +92,7 @@ extension ClientRPCExecutor.RetryExecutor { if let deadline = self.deadline { group.addTask { let result = await Result { - try await Task.sleep(until: deadline, clock: .continuous) + try await Task.sleep(until: deadline, tolerance: .zero, clock: .continuous) } return .timedOut(result) } @@ -155,11 +155,12 @@ extension ClientRPCExecutor.RetryExecutor { // If the delay is overridden with server pushback then reset the iterator for the // next retry. delayIterator = delaySequence.makeIterator() - try? await Task.sleep(until: .now.advanced(by: delayOverride), clock: .continuous) + try? await Task.sleep(until: .now.advanced(by: delayOverride), tolerance: .zero, clock: .continuous) } else { // The delay iterator never terminates. try? await Task.sleep( until: .now.advanced(by: delayIterator.next()!), + tolerance: .zero, clock: .continuous ) } diff --git a/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift b/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift index dee3303d5..7d5f42f05 100644 --- a/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift +++ b/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift @@ -123,7 +123,7 @@ struct ServerRPCExecutor { await withTaskGroup(of: Void.self) { group in group.addTask { do { - try await Task.sleep(for: timeout, clock: .continuous) + try await Task.sleep(for: timeout, tolerance: .zero, clock: .continuous) context.cancellation.cancel() } catch { () // Only cancel the RPC if the timeout completes. diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift index 2b0f2b90f..cf4a4ccc1 100644 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift +++ b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift @@ -112,7 +112,7 @@ extension ClientRPCExecutorTestHarness.ServerStreamHandler { static func sleepFor(duration: Duration, then handler: Self) -> Self { return Self { stream in - try await Task.sleep(until: .now.advanced(by: duration), clock: .continuous) + try await Task.sleep(until: .now.advanced(by: duration), tolerance: .zero, clock: .continuous) try await handler.handle(stream: stream) } } diff --git a/Tests/GRPCCoreTests/GRPCClientTests.swift b/Tests/GRPCCoreTests/GRPCClientTests.swift index 477a43588..61fb3b6c6 100644 --- a/Tests/GRPCCoreTests/GRPCClientTests.swift +++ b/Tests/GRPCCoreTests/GRPCClientTests.swift @@ -40,7 +40,7 @@ final class GRPCClientTests: XCTestCase { transport: inProcess.client, interceptorPipeline: interceptorPipeline ) { client in - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) try await body(client, server) } } @@ -341,7 +341,7 @@ final class GRPCClientTests: XCTestCase { let task = Task { try await client.clientStreaming( request: StreamingClientRequest { writer in - try await Task.sleep(for: .seconds(5)) + try await Task.sleep(for: .seconds(5), tolerance: .zero) }, descriptor: BinaryEcho.Methods.collect, serializer: IdentitySerializer(), @@ -382,7 +382,7 @@ final class GRPCClientTests: XCTestCase { // Run the client. let task = Task { try await client.runConnections() } // Make sure the client is run for the first time here. - try await Task.sleep(for: .milliseconds(10)) + try await Task.sleep(for: .milliseconds(10), tolerance: .zero) // Client is already running, should throw an error. await XCTAssertThrowsErrorAsync(ofType: RuntimeError.self) { @@ -545,7 +545,7 @@ struct ClientTests { } // Make sure both server and client are running - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) try await body(client, server) client.beginGracefulShutdown() server.beginGracefulShutdown() diff --git a/Tests/GRPCCoreTests/Internal/Result+CatchingTests.swift b/Tests/GRPCCoreTests/Internal/Result+CatchingTests.swift index cbe5ac742..d5bc65cd1 100644 --- a/Tests/GRPCCoreTests/Internal/Result+CatchingTests.swift +++ b/Tests/GRPCCoreTests/Internal/Result+CatchingTests.swift @@ -21,7 +21,7 @@ import XCTest final class ResultCatchingTests: XCTestCase { func testResultCatching() async { let result = await Result { - try? await Task.sleep(nanoseconds: 1) + try? await Task.sleep(for: .nanoseconds(1), tolerance: .zero) throw RPCError(code: .unknown, message: "foo") } diff --git a/Tests/GRPCInProcessTransportTests/InProcessClientTransportTests.swift b/Tests/GRPCInProcessTransportTests/InProcessClientTransportTests.swift index c1e5dfc9b..c64f97646 100644 --- a/Tests/GRPCInProcessTransportTests/InProcessClientTransportTests.swift +++ b/Tests/GRPCInProcessTransportTests/InProcessClientTransportTests.swift @@ -62,7 +62,7 @@ final class InProcessClientTransportTests: XCTestCase { try await client.connect() } group.addTask { - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) } try await group.next() @@ -97,7 +97,7 @@ final class InProcessClientTransportTests: XCTestCase { try await client.connect() } group.addTask { - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) } try await group.next() @@ -121,7 +121,7 @@ final class InProcessClientTransportTests: XCTestCase { group.addTask { // Add a sleep to make sure connection happens after `withStream` has been called, // to test pending streams are handled correctly. - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) try await client.connect() } @@ -171,7 +171,7 @@ final class InProcessClientTransportTests: XCTestCase { } group.addTask { - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) client.beginGracefulShutdown() } @@ -252,18 +252,18 @@ final class InProcessClientTransportTests: XCTestCase { group.addTask { try await client.withStream(descriptor: .testTest, options: .defaults) { stream, _ in - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) } } group.addTask { try await client.withStream(descriptor: .testTest, options: .defaults) { stream, _ in - try await Task.sleep(for: .milliseconds(100)) + try await Task.sleep(for: .milliseconds(100), tolerance: .zero) } } group.addTask { - try await Task.sleep(for: .milliseconds(50)) + try await Task.sleep(for: .milliseconds(50), tolerance: .zero) client.beginGracefulShutdown() } From 2dfc1ed145ec41c65eefff9f656b3a0d43271cd7 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Fri, 25 Apr 2025 16:34:35 +0100 Subject: [PATCH 2/2] Format --- .../Client/Internal/ClientRPCExecutor+RetryExecutor.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift index b985342dd..e5964baee 100644 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift +++ b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift @@ -155,7 +155,11 @@ extension ClientRPCExecutor.RetryExecutor { // If the delay is overridden with server pushback then reset the iterator for the // next retry. delayIterator = delaySequence.makeIterator() - try? await Task.sleep(until: .now.advanced(by: delayOverride), tolerance: .zero, clock: .continuous) + try? await Task.sleep( + until: .now.advanced(by: delayOverride), + tolerance: .zero, + clock: .continuous + ) } else { // The delay iterator never terminates. try? await Task.sleep(