Skip to content

Commit ace89a9

Browse files
authored
Merge pull request #19 from ZhaoChaoqun/chore/cloud-rewrite-timeout-5s
chore: increase Cloud Rewrite timeout from 2s to 5s
2 parents 53b088a + 30499c7 commit ace89a9

1 file changed

Lines changed: 47 additions & 3 deletions

File tree

Sources/CloudRewriteService.swift

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ final class CloudRewriteService {
1010

1111
/// Wall-clock timeout for the entire rewrite operation.
1212
/// If the API doesn't respond within this duration, the original text is used.
13-
private static let rewriteTimeout: Duration = .seconds(2)
13+
private static let rewriteTimeout: Duration = .seconds(5)
1414

1515
/// Models to try in priority order. First available model wins.
1616
private static let preferredModels = [
@@ -114,11 +114,17 @@ final class CloudRewriteService {
114114

115115
guard let apiKey = apiKeyProvider(), !apiKey.isEmpty else {
116116
cloudRewriteLogger.debug("Cloud rewrite skipped: no API key")
117+
AnalyticsService.track("CloudRewrite.Completed", parameters: [
118+
"outcome": "noApiKey",
119+
"inputLengthBucket": AnalyticsService.lengthBucket(count: text.count),
120+
])
117121
return text
118122
}
119123

124+
let startTime = ContinuousClock.now
125+
120126
do {
121-
return try await withThrowingTaskGroup(of: String.self) { group in
127+
let result = try await withThrowingTaskGroup(of: String.self) { group in
122128
// Race 1: the actual API call
123129
group.addTask { [self] in
124130
try await self.rewrite(text: text, apiKey: apiKey)
@@ -137,20 +143,58 @@ final class CloudRewriteService {
137143
group.cancelAll()
138144
return result
139145
}
146+
147+
let elapsed = startTime.duration(to: .now)
148+
let elapsedMs = Int(elapsed.components.seconds * 1000) + Int(elapsed.components.attoseconds / 1_000_000_000_000_000)
149+
let resolvedModel = await modelProbeTask.value
150+
AnalyticsService.track("CloudRewrite.Completed", parameters: [
151+
"outcome": "rewritten",
152+
"latencyBucket": AnalyticsService.latencyBucket(ms: elapsedMs),
153+
"model": resolvedModel,
154+
"changed": "\(result != text)",
155+
"inputLengthBucket": AnalyticsService.lengthBucket(count: text.count),
156+
])
157+
return result
140158
} catch is RewriteTimeoutError {
141159
cloudRewriteLogger.warning("Cloud rewrite timed out (\(Self.rewriteTimeout, privacy: .public)), using original text (\(text.count, privacy: .public) chars)")
160+
AnalyticsService.track("CloudRewrite.Completed", parameters: [
161+
"outcome": "timeout",
162+
"latencyBucket": "timeout",
163+
"inputLengthBucket": AnalyticsService.lengthBucket(count: text.count),
164+
])
142165
return text
143166
} catch is CancellationError {
144167
cloudRewriteLogger.debug("Cloud rewrite cancelled")
145168
return text
146169
} catch let error as URLError {
147170
cloudRewriteLogger.warning("Cloud rewrite network error: \(error.code.rawValue, privacy: .public). Fallback to original text")
171+
let elapsedMs = Int(startTime.duration(to: .now).components.seconds * 1000)
172+
AnalyticsService.track("CloudRewrite.Completed", parameters: [
173+
"outcome": "error",
174+
"latencyBucket": AnalyticsService.latencyBucket(ms: elapsedMs),
175+
"errorType": "network_\(error.code.rawValue)",
176+
"inputLengthBucket": AnalyticsService.lengthBucket(count: text.count),
177+
])
148178
return text
149179
} catch let error as CloudRewriteError {
150180
cloudRewriteLogger.warning("Cloud rewrite service error: \(error.logDescription, privacy: .public). Fallback to original text")
181+
let elapsedMs = Int(startTime.duration(to: .now).components.seconds * 1000)
182+
AnalyticsService.track("CloudRewrite.Completed", parameters: [
183+
"outcome": "error",
184+
"latencyBucket": AnalyticsService.latencyBucket(ms: elapsedMs),
185+
"errorType": error.logDescription,
186+
"inputLengthBucket": AnalyticsService.lengthBucket(count: text.count),
187+
])
151188
return text
152189
} catch {
153190
cloudRewriteLogger.warning("Cloud rewrite unknown error: \(error.localizedDescription, privacy: .public). Fallback to original text")
191+
let elapsedMs = Int(startTime.duration(to: .now).components.seconds * 1000)
192+
AnalyticsService.track("CloudRewrite.Completed", parameters: [
193+
"outcome": "error",
194+
"latencyBucket": AnalyticsService.latencyBucket(ms: elapsedMs),
195+
"errorType": "unknown",
196+
"inputLengthBucket": AnalyticsService.lengthBucket(count: text.count),
197+
])
154198
return text
155199
}
156200
}
@@ -160,7 +204,7 @@ final class CloudRewriteService {
160204

161205
var request = URLRequest(url: endpoint)
162206
request.httpMethod = "POST"
163-
request.timeoutInterval = 3 // Safety net; task group timeout (2s) is the primary enforcer
207+
request.timeoutInterval = 8 // Safety net; task group timeout (5s) is the primary enforcer
164208
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
165209
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
166210

0 commit comments

Comments
 (0)