fix(startup): survive store open failures instead of crashing before splash
일기 기능의 스키마 변경(모델 4종 추가) 후 기존 데이터가 있는 기기에서 첫 실행 시 마이그레이션이 필요한데, 이때 위젯 프로세스와의 스토어 경합이나 마이그레이션 실패가 발생하면 makeContainer()가 곧장 fatalError로 죽어 스플래시도 못 띄우는 문제 방어: - 일시적 경합 대비 최대 3회 재시도(점증 대기) - 그래도 실패하면 스토어를 타임스탬프 백업으로 보존 후 새로 생성해 앱은 반드시 켜지게 - 검증: 스토어 파일 고의 손상 → 백업 생성 + 정상 부팅 확인 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
parent
8c803d4d6e
commit
1f77264852
@ -64,12 +64,44 @@ nonisolated enum DataStore {
|
||||
|
||||
/// 앱·위젯·인텐트가 공유하는 컨테이너 생성.
|
||||
/// CloudKit 연동은 메인 앱 프로세스에서만 — 확장은 같은 파일을 로컬 전용으로 연다.
|
||||
/// 앱 본체(DataStore.shared)는 스토어 없이는 동작할 수 없어, 생성 실패 시 크래시한다.
|
||||
///
|
||||
/// 실패해도 곧장 크래시하지 않는다:
|
||||
/// 1) 위젯 등 다른 프로세스와의 일시적 스토어 경합(특히 스키마 마이그레이션이 필요한
|
||||
/// 업데이트 직후 첫 실행)일 수 있어 짧게 재시도하고,
|
||||
/// 2) 그래도 실패하면(영구적 마이그레이션/파일 손상) 기존 스토어를 백업 이름으로 보존한 뒤
|
||||
/// 새 스토어로 시작한다 — 예전에는 여기서 바로 fatalError라 스플래시도 못 띄우고
|
||||
/// 앱이 무한히 죽는 상태가 될 수 있었다. 백업 파일(HaruDanim.store.backup-*)은 남는다.
|
||||
static func makeContainer() -> ModelContainer {
|
||||
do {
|
||||
return try makeContainerThrowing()
|
||||
} catch {
|
||||
fatalError("[DataStore] 컨테이너 생성 실패: \(error)")
|
||||
print("[DataStore] 컨테이너 생성 실패, 재시도: \(error)")
|
||||
for attempt in 1...3 {
|
||||
Thread.sleep(forTimeInterval: 0.4 * Double(attempt))
|
||||
if let container = try? makeContainerThrowing() {
|
||||
return container
|
||||
}
|
||||
}
|
||||
print("[DataStore] 재시도 실패 — 스토어를 백업하고 새로 시작: \(error)")
|
||||
backupBrokenStore()
|
||||
do {
|
||||
return try makeContainerThrowing()
|
||||
} catch {
|
||||
fatalError("[DataStore] 컨테이너 생성 실패(백업 후 재생성도 실패): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 열 수 없게 된 스토어 파일(-wal/-shm 포함)을 타임스탬프 백업 이름으로 옮긴다.
|
||||
/// 삭제하지 않으므로 필요 시 파일을 되돌려 데이터를 복구할 수 있다.
|
||||
private static func backupBrokenStore() {
|
||||
let fileManager = FileManager.default
|
||||
let stamp = Int(Date.now.timeIntervalSince1970)
|
||||
for suffix in ["", "-wal", "-shm"] {
|
||||
let source = URL(fileURLWithPath: storeURL.path + suffix)
|
||||
guard fileManager.fileExists(atPath: source.path) else { continue }
|
||||
let destination = URL(fileURLWithPath: storeURL.path + ".backup-\(stamp)" + suffix)
|
||||
try? fileManager.moveItem(at: source, to: destination)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user