@logbrew/react-native 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LogBrewReactNative.podspec +25 -3
- package/README.md +106 -3
- package/apple-native-diagnostics.d.ts +52 -0
- package/apple-native-diagnostics.js +286 -0
- package/expo.cjs +46 -0
- package/expo.d.ts +16 -0
- package/expo.js +5 -0
- package/fatal-replay.cjs +12 -1
- package/global-errors.cjs +3 -1
- package/index.cjs +84 -2
- package/index.d.cts +4 -0
- package/index.d.ts +4 -0
- package/index.js +84 -2
- package/index.native.d.ts +4 -0
- package/index.native.js +14 -1
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
- package/package.json +43 -5
- package/promise-rejections.cjs +3 -1
- package/scripts/sync-apple-native-sources.mjs +93 -0
- package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
extension DeliveryEngine {
|
|
6
|
+
func timerFired(generation ownerGeneration: UInt64) {
|
|
7
|
+
let now = DispatchTime.now().uptimeNanoseconds
|
|
8
|
+
stateLock.lock()
|
|
9
|
+
guard generation == ownerGeneration else {
|
|
10
|
+
stateLock.unlock()
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
guard let deadline = nextWakeNanoseconds, now >= deadline else {
|
|
14
|
+
stateLock.unlock()
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
nextWakeNanoseconds = nil
|
|
18
|
+
let active = automaticTransport != nil && (state == .running || state == .retrying)
|
|
19
|
+
stateLock.unlock()
|
|
20
|
+
guard active else {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
runAutomaticDelivery(generation: ownerGeneration)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func runAutomaticDelivery(generation currentGeneration: UInt64) {
|
|
27
|
+
flushLock.lock()
|
|
28
|
+
defer { flushLock.unlock() }
|
|
29
|
+
|
|
30
|
+
guard let transport = transportForAutomaticRun(generation: currentGeneration) else {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
let prefix: FrozenPrefix
|
|
34
|
+
do {
|
|
35
|
+
guard let nextPrefix = try freezePrefix() else {
|
|
36
|
+
finishEmptyAutomaticRun(generation: currentGeneration)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
prefix = nextPrefix
|
|
40
|
+
} catch {
|
|
41
|
+
pauseAfterInternalFailure(generation: currentGeneration)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let result = attempt(transport: transport, prefix: prefix)
|
|
46
|
+
finishAutomaticAttempt(result, prefix: prefix, generation: currentGeneration)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private func transportForAutomaticRun(generation currentGeneration: UInt64) -> (any Transport)? {
|
|
50
|
+
stateLock.lock()
|
|
51
|
+
defer { stateLock.unlock() }
|
|
52
|
+
guard generation == currentGeneration, state == .running || state == .retrying else {
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
return automaticTransport
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private func finishEmptyAutomaticRun(generation currentGeneration: UInt64) {
|
|
59
|
+
stateLock.lock()
|
|
60
|
+
if generation == currentGeneration, state != .paused {
|
|
61
|
+
state = .running
|
|
62
|
+
}
|
|
63
|
+
stateLock.unlock()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private func finishAutomaticAttempt(
|
|
67
|
+
_ result: AttemptResult,
|
|
68
|
+
prefix: FrozenPrefix,
|
|
69
|
+
generation currentGeneration: UInt64,
|
|
70
|
+
) {
|
|
71
|
+
if case .accepted = result {
|
|
72
|
+
finishAcceptedAutomaticAttempt(result, prefix: prefix, generation: currentGeneration)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
stateLock.lock()
|
|
76
|
+
guard generation == currentGeneration || state == .shuttingDown else {
|
|
77
|
+
inFlight = false
|
|
78
|
+
stateLock.unlock()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
inFlight = false
|
|
82
|
+
deliveryAttempts += result.attempts
|
|
83
|
+
let shouldSchedule = applyAutomaticResultLocked(result, prefix: prefix)
|
|
84
|
+
stateLock.unlock()
|
|
85
|
+
if shouldSchedule {
|
|
86
|
+
scheduleTimerUpdate()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private func finishAcceptedAutomaticAttempt(
|
|
91
|
+
_ result: AttemptResult,
|
|
92
|
+
prefix: FrozenPrefix,
|
|
93
|
+
generation currentGeneration: UInt64,
|
|
94
|
+
) {
|
|
95
|
+
storageLock.lock()
|
|
96
|
+
stateLock.lock()
|
|
97
|
+
guard generation == currentGeneration || state == .shuttingDown else {
|
|
98
|
+
inFlight = false
|
|
99
|
+
stateLock.unlock()
|
|
100
|
+
storageLock.unlock()
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
let store = durableStore
|
|
104
|
+
stateLock.unlock()
|
|
105
|
+
|
|
106
|
+
let finalResult: AttemptResult
|
|
107
|
+
do {
|
|
108
|
+
try store?.acknowledge(body: prefix.body, eventRecordNames: prefix.durableRecordNames)
|
|
109
|
+
finalResult = result
|
|
110
|
+
} catch {
|
|
111
|
+
finalResult = .terminal(
|
|
112
|
+
reason: .storage,
|
|
113
|
+
attempts: result.attempts,
|
|
114
|
+
error: storageError(error),
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
stateLock.lock()
|
|
119
|
+
inFlight = false
|
|
120
|
+
deliveryAttempts += finalResult.attempts
|
|
121
|
+
let shouldSchedule = applyAutomaticResultLocked(finalResult, prefix: prefix)
|
|
122
|
+
stateLock.unlock()
|
|
123
|
+
storageLock.unlock()
|
|
124
|
+
if shouldSchedule {
|
|
125
|
+
scheduleTimerUpdate()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private func applyAutomaticResultLocked(_ result: AttemptResult, prefix: FrozenPrefix) -> Bool {
|
|
130
|
+
switch result {
|
|
131
|
+
case .accepted:
|
|
132
|
+
return applyAcceptedResultLocked(prefix)
|
|
133
|
+
case .retryable:
|
|
134
|
+
return applyRetryableResultLocked()
|
|
135
|
+
case let .terminal(reason, _, _):
|
|
136
|
+
applyTerminalResultLocked(reason: reason)
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private func applyAcceptedResultLocked(_ prefix: FrozenPrefix) -> Bool {
|
|
142
|
+
acknowledgeLocked(prefix)
|
|
143
|
+
acceptedEvents += prefix.count
|
|
144
|
+
consecutiveFailures = 0
|
|
145
|
+
retryAttempt = 0
|
|
146
|
+
lastOutcome = .accepted
|
|
147
|
+
guard state != .shuttingDown else {
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
state = .running
|
|
151
|
+
return scheduleLiveQueueLocked()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private func applyRetryableResultLocked() -> Bool {
|
|
155
|
+
consecutiveFailures += 1
|
|
156
|
+
lastOutcome = .retryableFailure
|
|
157
|
+
guard state != .shuttingDown else {
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
guard retryAttempt < maxRetries else {
|
|
161
|
+
state = .paused
|
|
162
|
+
pauseReason = .retryExhausted
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
retryAttempt += 1
|
|
166
|
+
state = .retrying
|
|
167
|
+
return scheduleRetryLocked()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func applyTerminalResultLocked(reason: DeliveryPauseReason) {
|
|
171
|
+
consecutiveFailures += 1
|
|
172
|
+
lastOutcome = .terminalFailure
|
|
173
|
+
if state != .shuttingDown {
|
|
174
|
+
state = .paused
|
|
175
|
+
pauseReason = reason
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
func scheduleCaptureLocked() -> Bool {
|
|
180
|
+
guard automaticTransport != nil, state == .running || state == .retrying else {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
if inFlight || frozenPrefix != nil {
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
return scheduleLiveQueueLocked()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func scheduleLiveQueueLocked() -> Bool {
|
|
190
|
+
guard let options = automaticOptions, !queue.isEmpty, state == .running || state == .retrying else {
|
|
191
|
+
nextWakeNanoseconds = nil
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
let delay: TimeInterval = queue.count >= options.threshold ? 0 : options.interval
|
|
195
|
+
return setWakeLocked(delay: delay, replaceLaterWake: true)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private func scheduleRetryLocked() -> Bool {
|
|
199
|
+
guard let options = automaticOptions else {
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
let multiplier = pow(2, Double(max(0, retryAttempt - 1)))
|
|
203
|
+
let ceiling = min(options.retryBaseDelay * multiplier, options.maxRetryDelay)
|
|
204
|
+
let delay = Double.random(in: (ceiling / 2) ... ceiling)
|
|
205
|
+
return setWakeLocked(delay: delay, replaceLaterWake: true)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private func setWakeLocked(delay: TimeInterval, replaceLaterWake: Bool) -> Bool {
|
|
209
|
+
let delta = UInt64(min(delay * 1_000_000_000, Double(UInt64.max)))
|
|
210
|
+
let now = DispatchTime.now().uptimeNanoseconds
|
|
211
|
+
let deadline = now > UInt64.max - delta ? UInt64.max : now + delta
|
|
212
|
+
if let current = nextWakeNanoseconds, !replaceLaterWake || current <= deadline {
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
nextWakeNanoseconds = deadline
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
func scheduleTimerUpdate() {
|
|
220
|
+
let timerAndDelay: (DispatchSourceTimer, UInt64)? = withStateLock {
|
|
221
|
+
guard let timer = schedulerTimer, let deadline = nextWakeNanoseconds else {
|
|
222
|
+
return nil
|
|
223
|
+
}
|
|
224
|
+
let now = DispatchTime.now().uptimeNanoseconds
|
|
225
|
+
return (timer, deadline > now ? deadline - now : 0)
|
|
226
|
+
}
|
|
227
|
+
guard let (timer, delay) = timerAndDelay else {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
timer.schedule(deadline: .now() + .nanoseconds(Int(min(delay, UInt64(Int.max)))), leeway: .milliseconds(1))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
func prepareManualOperation() throws {
|
|
234
|
+
stateLock.lock()
|
|
235
|
+
if closed || state == .closed || state == .shuttingDown {
|
|
236
|
+
stateLock.unlock()
|
|
237
|
+
throw SdkError(code: "shutdown_error", message: "client is already shut down")
|
|
238
|
+
}
|
|
239
|
+
if state == .paused, pauseReason == .storage {
|
|
240
|
+
stateLock.unlock()
|
|
241
|
+
throw SdkError(code: "storage_corrupt", message: "durable delivery data requires explicit recovery")
|
|
242
|
+
}
|
|
243
|
+
nextWakeNanoseconds = nil
|
|
244
|
+
stateLock.unlock()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
func rescheduleAfterManualOperation() {
|
|
248
|
+
stateLock.lock()
|
|
249
|
+
let shouldSchedule: Bool
|
|
250
|
+
if automaticTransport != nil, state != .paused, state != .shuttingDown, state != .closed {
|
|
251
|
+
state = .running
|
|
252
|
+
retryAttempt = 0
|
|
253
|
+
shouldSchedule = scheduleLiveQueueLocked()
|
|
254
|
+
} else {
|
|
255
|
+
shouldSchedule = false
|
|
256
|
+
}
|
|
257
|
+
stateLock.unlock()
|
|
258
|
+
if shouldSchedule {
|
|
259
|
+
scheduleTimerUpdate()
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
func ownedTransport() throws -> any Transport {
|
|
264
|
+
stateLock.lock()
|
|
265
|
+
defer { stateLock.unlock() }
|
|
266
|
+
guard let transport = automaticTransport else {
|
|
267
|
+
throw SdkError(code: "configuration_error", message: "automatic delivery is not running")
|
|
268
|
+
}
|
|
269
|
+
return transport
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
func beginShutdown() throws -> (timer: DispatchSourceTimer?, scheduler: DispatchQueue?) {
|
|
273
|
+
storageLock.lock()
|
|
274
|
+
defer { storageLock.unlock() }
|
|
275
|
+
stateLock.lock()
|
|
276
|
+
guard !closed, state != .closed, state != .shuttingDown else {
|
|
277
|
+
stateLock.unlock()
|
|
278
|
+
throw SdkError(code: "shutdown_error", message: "client is already shut down")
|
|
279
|
+
}
|
|
280
|
+
if state == .paused, pauseReason == .storage {
|
|
281
|
+
stateLock.unlock()
|
|
282
|
+
throw SdkError(code: "storage_corrupt", message: "durable delivery data requires explicit recovery")
|
|
283
|
+
}
|
|
284
|
+
generation &+= 1
|
|
285
|
+
state = .shuttingDown
|
|
286
|
+
closed = true
|
|
287
|
+
nextWakeNanoseconds = nil
|
|
288
|
+
let result = (schedulerTimer, schedulerQueue)
|
|
289
|
+
schedulerTimer = nil
|
|
290
|
+
schedulerQueue = nil
|
|
291
|
+
automaticTransport = nil
|
|
292
|
+
automaticOptions = nil
|
|
293
|
+
stateLock.unlock()
|
|
294
|
+
return result
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
func finishStopping(timer: DispatchSourceTimer?, scheduler: DispatchQueue?) {
|
|
298
|
+
timer?.setEventHandler {}
|
|
299
|
+
timer?.cancel()
|
|
300
|
+
if DispatchQueue.getSpecific(key: schedulerKey) == nil {
|
|
301
|
+
scheduler?.sync {}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private func pauseAfterInternalFailure(generation currentGeneration: UInt64) {
|
|
306
|
+
stateLock.lock()
|
|
307
|
+
if generation == currentGeneration, state != .shuttingDown {
|
|
308
|
+
state = .paused
|
|
309
|
+
pauseReason = .nonRetryable
|
|
310
|
+
lastOutcome = .terminalFailure
|
|
311
|
+
consecutiveFailures += 1
|
|
312
|
+
inFlight = false
|
|
313
|
+
}
|
|
314
|
+
stateLock.unlock()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
func validate(_ options: AutomaticDeliveryOptions) throws {
|
|
318
|
+
guard options.interval.isFinite, options.interval > 0, options.interval <= Self.maxScheduleDelay else {
|
|
319
|
+
throw SdkError(code: "configuration_error", message: "automatic delivery interval is out of range")
|
|
320
|
+
}
|
|
321
|
+
guard (1 ... Self.maxQueuedEvents).contains(options.threshold) else {
|
|
322
|
+
throw SdkError(code: "configuration_error", message: "automatic delivery threshold is out of range")
|
|
323
|
+
}
|
|
324
|
+
guard options.retryBaseDelay.isFinite, options.retryBaseDelay > 0,
|
|
325
|
+
options.maxRetryDelay.isFinite, options.maxRetryDelay >= options.retryBaseDelay,
|
|
326
|
+
options.maxRetryDelay <= Self.maxScheduleDelay
|
|
327
|
+
else {
|
|
328
|
+
throw SdkError(code: "configuration_error", message: "automatic delivery retry delays are invalid")
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
extension DeliveryEngine {
|
|
6
|
+
func enableDurableDelivery(options: DurableDeliveryOptions) throws {
|
|
7
|
+
storageLock.lock()
|
|
8
|
+
defer { storageLock.unlock() }
|
|
9
|
+
let memoryQueue = try prepareDurableEnable()
|
|
10
|
+
|
|
11
|
+
do {
|
|
12
|
+
let store = try DurableDeliveryStore(parent: options.directory, sdk: sdk)
|
|
13
|
+
let recovered = try recoverDurableState(store: store, memoryQueue: memoryQueue)
|
|
14
|
+
installDurableState(recovered, store: store, parent: options.directory)
|
|
15
|
+
} catch let failure as DurableStoreFailure {
|
|
16
|
+
recordDurableEnableFailure(failure, parent: options.directory)
|
|
17
|
+
throw storageError(failure)
|
|
18
|
+
} catch {
|
|
19
|
+
throw storageError(error)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func purgeDurableDelivery() throws {
|
|
24
|
+
storageLock.lock()
|
|
25
|
+
defer { storageLock.unlock() }
|
|
26
|
+
let parent = try prepareDurablePurge()
|
|
27
|
+
guard let parent else {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
do {
|
|
32
|
+
try DurableDeliveryStore.purge(parent: parent)
|
|
33
|
+
} catch {
|
|
34
|
+
recordStorageFailure()
|
|
35
|
+
throw storageError(error)
|
|
36
|
+
}
|
|
37
|
+
finishDurablePurge()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private func prepareDurableEnable() throws -> [QueuedEvent] {
|
|
41
|
+
stateLock.lock()
|
|
42
|
+
defer { stateLock.unlock() }
|
|
43
|
+
if closed || state == .closed || state == .shuttingDown {
|
|
44
|
+
throw SdkError(code: "shutdown_error", message: "client is already shut down")
|
|
45
|
+
}
|
|
46
|
+
if durableStore != nil {
|
|
47
|
+
throw SdkError(code: "configuration_error", message: "durable delivery is already enabled")
|
|
48
|
+
}
|
|
49
|
+
if automaticTransport != nil || inFlight {
|
|
50
|
+
throw SdkError(code: "configuration_error", message: "stop active delivery before enabling durability")
|
|
51
|
+
}
|
|
52
|
+
return queue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private func recoverDurableState(
|
|
56
|
+
store: DurableDeliveryStore,
|
|
57
|
+
memoryQueue: [QueuedEvent],
|
|
58
|
+
) throws -> (queue: [QueuedEvent], prefix: FrozenPrefix?) {
|
|
59
|
+
let recovery = store.recovery()
|
|
60
|
+
if recovery.events.isEmpty {
|
|
61
|
+
let names = try store.appendExisting(memoryQueue.map { ($0.event, $0.encodedBytes) })
|
|
62
|
+
let queue = zip(memoryQueue, names).map { item, name in
|
|
63
|
+
QueuedEvent(event: item.event, encodedBytes: item.encodedBytes, durableRecordName: name)
|
|
64
|
+
}
|
|
65
|
+
return (queue, nil)
|
|
66
|
+
}
|
|
67
|
+
guard memoryQueue.isEmpty else {
|
|
68
|
+
throw DurableStoreFailure.owned
|
|
69
|
+
}
|
|
70
|
+
let queue = recovery.events.map {
|
|
71
|
+
QueuedEvent(event: $0.event, encodedBytes: $0.encodedBytes, durableRecordName: $0.recordName)
|
|
72
|
+
}
|
|
73
|
+
return try (queue, recoveredFrozenPrefix(recovery.prefix, queue: queue))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private func recoveredFrozenPrefix(
|
|
77
|
+
_ recovered: DurableDeliveryStore.RecoveredPrefix?,
|
|
78
|
+
queue: [QueuedEvent],
|
|
79
|
+
) throws -> FrozenPrefix? {
|
|
80
|
+
guard let recovered else {
|
|
81
|
+
return nil
|
|
82
|
+
}
|
|
83
|
+
let count = recovered.eventRecordNames.count
|
|
84
|
+
let body = try encodeBatch(Array(queue.prefix(count)).map(\.event))
|
|
85
|
+
guard body == recovered.body else {
|
|
86
|
+
throw DurableStoreFailure.corrupt
|
|
87
|
+
}
|
|
88
|
+
return FrozenPrefix(
|
|
89
|
+
count: count,
|
|
90
|
+
bytes: recovered.encodedBytes,
|
|
91
|
+
body: recovered.body,
|
|
92
|
+
eventIDs: Array(queue.prefix(count)).map(\.event.id),
|
|
93
|
+
durableRecordNames: recovered.eventRecordNames,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private func installDurableState(
|
|
98
|
+
_ recovered: (queue: [QueuedEvent], prefix: FrozenPrefix?),
|
|
99
|
+
store: DurableDeliveryStore,
|
|
100
|
+
parent: URL,
|
|
101
|
+
) {
|
|
102
|
+
stateLock.lock()
|
|
103
|
+
queue = recovered.queue
|
|
104
|
+
queuedBytes = recovered.queue.reduce(0) { $0 + $1.encodedBytes }
|
|
105
|
+
frozenPrefix = recovered.prefix
|
|
106
|
+
durableStore = store
|
|
107
|
+
durableParent = parent
|
|
108
|
+
if state == .paused, pauseReason == .storage {
|
|
109
|
+
state = .manual
|
|
110
|
+
pauseReason = .none
|
|
111
|
+
}
|
|
112
|
+
stateLock.unlock()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private func recordDurableEnableFailure(_ failure: DurableStoreFailure, parent: URL) {
|
|
116
|
+
stateLock.lock()
|
|
117
|
+
durableParent = parent
|
|
118
|
+
if failure == .corrupt {
|
|
119
|
+
state = .paused
|
|
120
|
+
pauseReason = .storage
|
|
121
|
+
lastOutcome = .terminalFailure
|
|
122
|
+
consecutiveFailures += 1
|
|
123
|
+
}
|
|
124
|
+
stateLock.unlock()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private func prepareDurablePurge() throws -> URL? {
|
|
128
|
+
stateLock.lock()
|
|
129
|
+
defer { stateLock.unlock() }
|
|
130
|
+
if automaticTransport != nil || inFlight || state == .shuttingDown || state == .closed {
|
|
131
|
+
throw SdkError(code: "configuration_error", message: "stop active delivery before purging durable data")
|
|
132
|
+
}
|
|
133
|
+
durableStore = nil
|
|
134
|
+
return durableParent
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private func finishDurablePurge() {
|
|
138
|
+
stateLock.lock()
|
|
139
|
+
queue.removeAll()
|
|
140
|
+
queuedBytes = 0
|
|
141
|
+
frozenPrefix = nil
|
|
142
|
+
durableParent = nil
|
|
143
|
+
state = .manual
|
|
144
|
+
pauseReason = .none
|
|
145
|
+
inFlight = false
|
|
146
|
+
stateLock.unlock()
|
|
147
|
+
}
|
|
148
|
+
}
|