@@ -48,7 +48,8 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate,
4848 var fsPaths : [ String : String ] = [ : ]
4949 var shutdownTask : UIBackgroundTaskIdentifier = . invalid
5050 var iph : ItemProviderHelper ?
51- var startupLogFileHandle : FileHandle ?
51+ private var startupLogFileHandle : FileHandle ?
52+ private let logQueue = DispatchQueue ( label: " kb.startup.log " , qos: . utility)
5253
5354 private var watchdog : MainThreadWatchdog ?
5455 private var bgEnterWallTime : CFAbsoluteTime = 0
@@ -154,6 +155,7 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate,
154155 /////// KB specific
155156
156157 private static var appStartTime : CFAbsoluteTime = 0
158+ private static let initLog = OSLog ( subsystem: " keybase " , category: " init " )
157159
158160 private static let logDateFormatter : DateFormatter = {
159161 let f = DateFormatter ( )
@@ -167,59 +169,62 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate,
167169 return
168170 }
169171
170- if self . startupLogFileHandle == nil {
171- if !FileManager. default. fileExists ( atPath: logFilePath) {
172- // Match the parent directory's protection class so the file remains accessible
173- // in the background after the device has been unlocked once.
174- FileManager . default. createFile (
175- atPath: logFilePath, contents: nil ,
176- attributes: [ . protectionKey: FileProtectionType . completeUntilFirstUserAuthentication] )
177- }
178- if let fileHandle = FileHandle ( forWritingAtPath: logFilePath) {
179- fileHandle. seekToEndOfFile ( )
180- self . startupLogFileHandle = fileHandle
181- } else {
182- NSLog ( " Error opening startup timing log file: \( logFilePath) " )
183- return
184- }
185- }
186-
187- guard let fileHandle = self . startupLogFileHandle else {
188- return
189- }
190-
172+ // Capture timestamp on the calling thread so it reflects when the event actually occurred.
191173 let now = Date ( )
192174 let timeInterval = now. timeIntervalSince1970
193175 let microseconds = Int ( timeInterval. truncatingRemainder ( dividingBy: 1 ) * 1_000_000 )
194176 let dateString = AppDelegate . logDateFormatter. string ( from: now)
195177 let timestamp = String ( format: " %@.%06dZ " , dateString, microseconds)
196-
197178 let fileName = URL ( fileURLWithPath: file) . lastPathComponent
198179 let logMessage = String (
199180 format: " %@ ▶ [DEBU keybase %@:%d] Delegate startup: %@ \n " , timestamp, fileName, line, message
200181 )
201-
202182 guard let logData = logMessage. data ( using: . utf8) else {
203183 return
204184 }
205185
206- do {
207- try fileHandle. write ( contentsOf: logData)
208- try fileHandle. synchronize ( )
209- } catch {
210- NSLog ( " Error writing startup timing log: \( error) " )
186+ // Dispatch file I/O (including fsync) to a background queue so the calling thread
187+ // (often the main thread) is never blocked by disk latency.
188+ logQueue. async { [ weak self] in
189+ guard let self else { return }
190+ if self . startupLogFileHandle == nil {
191+ if !FileManager. default. fileExists ( atPath: logFilePath) {
192+ // Match the parent directory's protection class so the file remains accessible
193+ // in the background after the device has been unlocked once.
194+ FileManager . default. createFile (
195+ atPath: logFilePath, contents: nil ,
196+ attributes: [ . protectionKey: FileProtectionType . completeUntilFirstUserAuthentication] )
197+ }
198+ if let fileHandle = FileHandle ( forWritingAtPath: logFilePath) {
199+ fileHandle. seekToEndOfFile ( )
200+ self . startupLogFileHandle = fileHandle
201+ } else {
202+ NSLog ( " Error opening startup timing log file: \( logFilePath) " )
203+ return
204+ }
205+ }
206+ guard let fileHandle = self . startupLogFileHandle else { return }
207+ do {
208+ try fileHandle. write ( contentsOf: logData)
209+ try fileHandle. synchronize ( )
210+ } catch {
211+ NSLog ( " Error writing startup timing log: \( error) " )
212+ }
211213 }
212214 }
213215
214216 private func closeStartupLogFile( ) {
215- if let fileHandle = self . startupLogFileHandle {
216- do {
217- try fileHandle. synchronize ( )
218- try fileHandle. close ( )
219- } catch {
220- NSLog ( " Error closing startup timing log: \( error) " )
217+ // Use sync so all pending async log writes are flushed before we close the handle.
218+ logQueue. sync {
219+ if let fileHandle = self . startupLogFileHandle {
220+ do {
221+ try fileHandle. synchronize ( )
222+ try fileHandle. close ( )
223+ } catch {
224+ NSLog ( " Error closing startup timing log: \( error) " )
225+ }
226+ self . startupLogFileHandle = nil
221227 }
222- self . startupLogFileHandle = nil
223228 }
224229 }
225230
@@ -251,7 +256,7 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate,
251256 if let err {
252257 // Log to system log with public annotation so it's not redacted in logarchive.
253258 os_log (
254- . error, log: OSLog ( subsystem : " keybase " , category : " init " ) ,
259+ . error, log: AppDelegate . initLog ,
255260 " KeybaseInit FAILED: %{public}@ (code=%ld domain=%{public}@) " ,
256261 err. localizedDescription, err. code, err. domain)
257262 // Also write to ios.log so it's captured in xcappdata even without a device attached.
0 commit comments