@ -53,6 +53,9 @@ open class Storage {
private static let writeTransactionStartTimeout : TimeInterval = 5
// / I f a t r a n s a c t i o n t a k e s l o n g e r t h a n t h i s d u r a t i o n t h e n w e s h o u l d f a i l t h e t r a n s a c t i o n r a t h e r t h a n k e e p h a n g i n g
// /
// / * * N o t e : * * T h i s t i m e o u t o n l y a p p l i e s t o s y n c h r o n o u s o p e r a t i o n s ( t h e a s s u m p t i o n b e i n g t h a t i f w e k n o w a n o p e r a t i o n i s g o i n g t o
// / t a k e a l o n g t i m e t h e n w e s h o u l d p r o b a b l y b e h a n d l i n g i t a s y n c h r o n o u s l y r a t h e r t h a n a s y n c h r o n o u s w a y )
private static let transactionDeadlockTimeoutSeconds : Int = 5
private static var sharedDatabaseDirectoryPath : String { " \( SessionFileManager . nonInjectedAppSharedDataDirectoryPath ) /database " }
@ -142,11 +145,6 @@ open class Storage {
var config = Configuration ( )
config . label = Storage . queuePrefix
config . maximumReaderCount = 10 // / I n c r e a s e t h e m a x r e a d c o n n e c t i o n l i m i t - D e f a u l t i s 5
// / I t s e e m s w e s h o u l d d o t h i s p e r h t t p s : / / g i t h u b . c o m / g r o u e / G R D B . s w i f t / p u l l / 1 4 8 5 b u t w i t h t h i s c h a n g e
// / w e t h e n n e e d t o d e f i n e h o w l o n g a w r i t e t r a n s a c t i o n s h o u l d w a i t f o r b e f o r e t i m i n g o u t ( r e a d t r a n s a c t i o n s a l w a y s r u n
// / i n ` D E F E R R E D ` m o d e s o w o n ' t b e a f f e c t e d b y t h e s e s e t t i n g s )
config . defaultTransactionKind = . immediate
config . busyMode = . timeout ( Storage . writeTransactionStartTimeout )
// / L o a d i n t h e S Q L C i p h e r k e y s
@ -551,6 +549,13 @@ open class Storage {
case valid ( DatabaseWriter )
case invalid ( Error )
var forcedError : Error {
switch self {
case . valid : return StorageError . validStorageIncorrectlyHandledAsError
case . invalid ( let error ) : return error
}
}
init ( _ storage : Storage ? ) {
switch ( storage ? . isSuspended , storage ? . isValid , storage ? . dbWriter ) {
case ( true , _ , _ ) : self = . invalid ( StorageError . databaseSuspended )
@ -559,38 +564,46 @@ open class Storage {
}
}
static func logIfNeeded ( _ error : Error , isWrite : Bool ) {
fileprivate static func logIfNeeded ( _ error : Error , info : Storage . CallInfo ) {
let action : String = ( info . isWrite ? " write " : " read " )
switch error {
case DatabaseError . SQLITE_ABORT , DatabaseError . SQLITE_INTERRUPT , DatabaseError . SQLITE_ERROR :
let message : String = ( ( error as ? DatabaseError ) ? . message ? ? " Unknown " )
Log . error ( . storage , " Database \( isWrite ? " write " : " read " ) failed due to error: \( message ) " )
Log . error ( . storage , " Database \( action ) failed due to error: \( message ) - [ \( info . callInfo ) ] " )
case StorageError . databaseInvalid :
Log . error ( . storage , " Database \( isWrite ? " write " : " read " ) failed as the database is invalid ." )
Log . error ( . storage , " Database \( action ) failed as the database is invalid - [ \( info .callInfo ) ] " )
case StorageError . databaseSuspended :
Log . error ( . storage , " Database \( isWrite ? " write " : " read " ) failed as the database is suspended ." )
Log . error ( . storage , " Database \( action ) failed as the database is suspended - [ \( info .callInfo ) ] " )
case StorageError . transactionDeadlockTimeout :
Log . critical ( " [Storage] Database \( isWrite ? " write " : " read " ) failed due to a potential synchronous query deadlock timeout ." )
Log . critical ( . storage , " Database \( action ) failed due to a potential synchronous query deadlock timeout - [ \( info .callInfo ) ] " )
default : break
}
}
static func logIfNeeded < T > ( _ error : Error , i sWrite: Bool ) -> T ? {
logIfNeeded ( error , i sWrite: isWrite )
fileprivate static func logIfNeeded < T > ( _ error : Error , i nfo: Storage . CallInfo ) -> T ? {
logIfNeeded ( error , i nfo: info )
return nil
}
static func logIfNeeded < T > ( _ error : Error , i sWrite: Bool ) -> AnyPublisher < T , Error > {
logIfNeeded ( error , i sWrite: isWrite )
fileprivate static func logIfNeeded < T > ( _ error : Error , i nfo: Storage . CallInfo ) -> AnyPublisher < T , Error > {
logIfNeeded ( error , i nfo: info )
return Fail < T , Error > ( error : error ) . eraseToAnyPublisher ( )
}
}
// MARK: - O p e r a t i o n s
// / I n t e r n a l t y p e t o w r a p t h e d a t a b a s e o p e r a t i o n ` T a s k ` s o i t c a n b e c a n c e l l e d w h e n u s e d w i t h ` C o m b i n e ` ( s i n c e G R D B d o e s n ' t
// / a c t u a l l y h a n d l e p u b l i s h e r s p u b l i s h e r s )
final class TaskHolder {
var task : Task < ( ) , Never > ?
}
private static func track < T > (
_ db : Database ,
_ info : CallInfo ,
@ -634,122 +647,108 @@ open class Storage {
_ dependencies : Dependencies ,
_ operation : @ escaping ( Database ) throws -> T ,
_ asyncCompletion : ( ( Result < T , Error > ) -> Void ) ? = nil
) -> Result < T , Error > {
// A s e r i a l q u e u e f o r s y n c h r o n i z i n g c o m p l e t i o n u p d a t e s .
let syncQueue = DispatchQueue ( label : " com.session.performOperation.syncQueue " )
) -> ( result : Result < T , Error > , task : Task < ( ) , Never > ? ) {
// / E n s u r e w e a r e i n a v a l i d s t a t e
let storageState : StorageState = StorageState ( info . storage )
guard case . valid ( let dbWriter ) = storageState else {
if info . isAsync { asyncCompletion ? ( . failure ( storageState . forcedError ) ) }
return ( . failure ( storageState . forcedError ) , nil )
}
weak var queryDb : Database ?
var didTimeout : Bool = false
// / S e t u p r e q u i r e d v a r i a b l e s
let syncQueue = DispatchQueue ( label : " com.session.performOperation.syncQueue " )
let semaphore : DispatchSemaphore = DispatchSemaphore ( value : 0 )
var operationResult : Result < T , Error > ?
let semaphore : DispatchSemaphore ? = ( info . isAsync ? nil : DispatchSemaphore ( value : 0 ) )
let logErrorIfNeeded : ( Result < T , Error > ) -> Result < T , Error > = { result in
switch result {
case . success : break
case . failure ( let error ) : StorageState . logIfNeeded ( error , i sWrite: info . isWrite )
case . failure ( let error ) : StorageState . logIfNeeded ( error , i nfo: info )
}
return result
}
// / C o n v e n i e n c e f u n c t i o n t o r e m o v e d u p l i c a t i o n
func completeOperation ( with result : Result < T , Error > ) {
syncQueue . sync {
guard ! didTimeout && operationResult = = nil else { return }
guard operationResult = = nil else { return }
operationResult = result
semaphore ? .signal ( )
semaphore .signal ( )
// F o r a s y n c o p e r a t i o n s , l o g a n d i n v o k e t h e c o m p l e t i o n c l o s u r e .
// / F o r a s y n c o p e r a t i o n s , l o g a n d i n v o k e t h e c o m p l e t i o n c l o s u r e .
if info . isAsync {
asyncCompletion ? ( logErrorIfNeeded ( result ) )
}
}
}
// / P e r f o r m t h e a c t u a l o p e r a t i o n
switch ( StorageState ( info . storage ) , info . isWrite ) {
case ( . invalid ( let error ) , _ ) : completeOperation ( with : . failure ( error ) )
case ( . valid ( let dbWriter ) , true ) :
dbWriter . asyncWrite (
{ db in
syncQueue . sync { queryDb = db }
defer { syncQueue . sync { queryDb = nil } }
let task : Task < ( ) , Never > = Task {
return await withThrowingTaskGroup ( of : T . self ) { group in
// / A d d t h e t a s k t o p e r f o r m t h e a c t u a l d a t a b a s e o p e r a t i o n
group . addTask {
let trackedOperation : @ Sendable ( Database ) throws -> T = { db in
if dependencies [ feature : . forceSlowDatabaseQueries ] {
Thread . sleep ( forTimeInterval : 1 )
}
return try Storage . track ( db , info , operation )
} ,
completion : { _ , dbResult in completeOperation ( with : dbResult ) }
)
}
return ( info . isWrite ?
try await dbWriter . write ( trackedOperation ) :
try await dbWriter . read ( trackedOperation )
)
}
case ( . valid ( let dbWriter ) , false ) :
dbWriter . asyncRead { dbResult in
do {
switch dbResult {
case . failure ( let error ) : throw error
case . success ( let db ) :
syncQueue . sync { queryDb = db }
defer { syncQueue . sync { queryDb = nil } }
if dependencies [ feature : . forceSlowDatabaseQueries ] {
Thread . sleep ( forTimeInterval : 1 )
}
completeOperation ( with : . success ( try Storage . track ( db , info , operation ) ) )
// / I f t h i s i s a s y n c r o n o u s t a s k t h e n w e w a n t t o t h e o p e r a t i o n t o t i m e o u t t o e n s u r e w e d o n ' t u n i n t e n t i o n a l l y
// / c r e a t e a d e a d l o c k
if ! info . isAsync {
group . addTask {
let timeoutNanoseconds : UInt64 = UInt64 ( Storage . transactionDeadlockTimeoutSeconds * 1_000_000_000 )
// / I f t h e d e b u g g e r i s a t t a c h e d t h e n w e w a n t t o h a v e a l o t o f s h o r t e r s l e e p i t e r a t i o n s a s t h e c l o c k d o e s n ' t g e t
// / p a u s e d w h e n s t o p p e d o n a b r e a k p o i n t ( a n d w e d o n ' t w a n t t o e n d u p h a v i n g a b u n c h o f f a l s e p o s i t i v e
// / d a t a b a s e t i m e o u t s w h i l e d e b u g g i n g c o d e )
// /
// / * * N o t e : * * ` i s D e b u g g e r A t t a c h e d ` w i l l a l w a y s r e t u r n ` f a l s e ` i n p r o d u c t i o n b u i l d s
if isDebuggerAttached ( ) {
let numIterations : UInt64 = 50
for _ in ( 0. . < numIterations ) {
try await Task . sleep ( nanoseconds : ( timeoutNanoseconds / numIterations ) )
}
}
} catch {
completeOperation ( with : . failure ( error ) )
else if info . isWrite {
// / T h i s i f s t a t e m e n t i s r e d u n d a n t * * b u t * * i t m e a n s w h e n w e g e t s y m b o l i c a t e d c r a s h l o g s w e c a n d i s t i n g u i s h
// / b e t w e e n t h e d a t a b a s e t h r e a d s w h i c h a r e r e a d i n g a n d w r i t i n g
try await Task . sleep ( nanoseconds : timeoutNanoseconds )
}
else {
try await Task . sleep ( nanoseconds : timeoutNanoseconds )
}
throw StorageError . transactionDeadlockTimeout
}
}
}
// / I f t h i s i s a s y n c h r o n o u s o p e r a t i o n t h e n ` s e m a p h o r e ` w i l l e x i s t a n d w i l l b l o c k h e r e w a i t i n g o n t h e s i g n a l f r o m o n e o f t h e
// / a b o v e c l o s u r e s t o b e s e n t
// /
// / * * N o t e : * * U n f o r t u n a t e l y t h i s t i m e o u t c a n b e r e a l l y a n n o y i n g w h e n d e b u g g i n g b e c a u s e t h e s e m a p h o r e t i m e o u t i s b a s e d o n
// / s y s t e m t i m e w h i c h d o e s n ' t g e t p a u s e d w h e n s t o p p i n g o n a b r e a k p o i n t ( w h i c h m e a n s i f y o u b r e a k i n t h e m i d d l e o f a d a t a b a s e
// / q u e r y i t ' s p r e t t y m u c h g u a r a n t e e d t o t i m e o u t )
// /
// / T o t r y t o a v o i d t h i s w e h a v e t h e b e l o w c o d e t o t r y t o r e p l i c a t e t h e b e h a v i o u r o f t h e p r o p e r s e m a p h o r e t i m e o u t w h i l e t h e d e b u g g e r
// / i s a t t a c h e d a s t h i s a p p r o a c h d o e s s e e m t o g e t p a u s e d ( o r a t l e a s t o n l y p e r f o r m a s i n g l e i t e r a t i o n p e r d e b u g g e r s t e p )
if let semaphore : DispatchSemaphore = semaphore {
var semaphoreResult : DispatchTimeoutResult
#if DEBUG
if isDebuggerAttached ( ) {
semaphoreResult = debugWait ( semaphore : semaphore , info : info )
}
else {
semaphoreResult = semaphore . wait ( timeout : . now ( ) + . seconds ( Storage . transactionDeadlockTimeoutSeconds ) )
}
#else
// / T h i s i f s t a t e m e n t i s r e d u n d a n t * * b u t * * i t m e a n s w h e n w e g e t s y m b o l i c a t e d c r a s h l o g s w e c a n d i s t i n g u i s h
// / b e t w e e n t h e d a t a b a s e t h r e a d s w h i c h a r e r e a d i n g a n d w r i t i n g
if info . isWrite {
semaphoreResult = semaphore . wait ( timeout : . now ( ) + . seconds ( Storage . transactionDeadlockTimeoutSeconds ) )
}
else {
semaphoreResult = semaphore . wait ( timeout : . now ( ) + . seconds ( Storage . transactionDeadlockTimeoutSeconds ) )
}
#endif
// / C h e c k i f t h e q u e r y t i m e d o u t i n t h e ` s y n c Q u e u e ` t o e n s u r e t h a t w e d o n ' t r u n i n t o a r a c e c o n d i t i o n b e t w e e n h a n d l i n g
// / t h e t i m e o u t a n d h a n d l i n g t h e q u e r y c o m p l e t i o n
// /
// / I f i t d i d t i m e o u t t h e n w e s h o u l d i n t e r r u p t t h e q u e r y ( d o n ' t w a n t t h e q u e r y t h r e a d t o r e m a i n b l o c k e d w h e n w e ' v e
// / a l r e a d y h a n d l e d i t a s a f a i l u r e )
syncQueue . sync {
guard semaphoreResult = = . timedOut && operationResult = = nil else { return }
didTimeout = true
queryDb ? . interrupt ( )
// / W a i t f o r t h e f i r s t t a s k t o f i n i s h
// /
// / * * N o t e : * * T H e c a s e w h e r e ` n e x t R e s u l t ` r e t u r n s ` n i l ` i s o n l y m e a n t t o h a p p e n w h e n t h e g r o u p h a s n o
// / t a s k s , s o s h o u l d n ' t b e c o n s i d e r e d a v a l i d c a s e ( h e n c e t h e ` i n v a l i d Q u e r y R e s u l t ` f a l l b a c k )
let result : Result < T , Error > ? = await group . nextResult ( )
group . cancelAll ( )
completeOperation ( with : result ? ? . failure ( StorageError . invalidQueryResult ) )
}
return logErrorIfNeeded ( operationResult ? ? . failure ( StorageError . transactionDeadlockTimeout ) )
}
// / F o r t h e ` a s y n c ` o p e r a t i o n t h e r e t u r n e d v a l u e s h o u l d b e i g n o r e d s o j u s t r e t u r n t h e ` i n v a l i d Q u e r y R e s u l t ` e r r o r
return . failure ( StorageError . invalidQueryResult )
guard ! info . isAsync else {
return ( . failure ( StorageError . invalidQueryResult ) , task )
}
// / B l o c k u n t i l w e h a v e a r e s u l t
semaphore . wait ( )
return ( logErrorIfNeeded ( operationResult ? ? . failure ( StorageError . transactionDeadlockTimeout ) ) , task )
}
private func performPublisherOperation < T > (
@ -759,59 +758,33 @@ open class Storage {
isWrite : Bool ,
_ operation : @ escaping ( Database ) throws -> T
) -> AnyPublisher < T , Error > {
let info : CallInfo = CallInfo ( self , fileName , functionName , lineNumber , ( isWrite ? . asyncWrite : . asyncRead ) )
switch StorageState ( self ) {
case . invalid ( let error ) : return StorageState . logIfNeeded ( error , isWrite : false )
case . invalid ( let error ) : return StorageState . logIfNeeded ( error , i nfo: info )
case . valid :
// / * * N o t e : * * G R D B d o e s h a v e ` r e a d P u b l i s h e r ` / ` w r i t e P u b l i s h e r ` f u n c t i o n s b u t i t a p p e a r s t o a s y n c h r o n o u s l y
// / t r i g g e r b o t h t h e ` o u t p u t ` a n d ` c o m p l e t e ` c l o s u r e s a t t h e s a m e t i m e w h i c h c a u s e s a l o t o f u n e x p e c t e d
// / b e h a v i o u r s ( t h i s b e h a v i o u r i s a p p a r e n t l y e x p e c t e d b u t s t i l l c a u s e s a n u m b e r o f o d d b e h a v i o u r s i n o u r c o d e
// / f o r m o r e i n f o r m a t i o n s e e h t t p s : / / g i t h u b . c o m / g r o u e / G R D B . s w i f t / i s s u e s / 1 3 3 4 )
// /
// / I n s t e a d o f t h i s w e a r e j u s t u s i n g ` D e f e r r e d { F u t u r e { } } ` w h i c h i s e x e c u t e d o n t h e s p e c i f i e d s c h e d u l e d
// / w h i c h b e h a v e s i n a m u c h m o r e e x p e c t e d w a y t h a n t h e G R D B ` r e a d P u b l i s h e r ` / ` w r i t e P u b l i s h e r ` d o e s
let info : CallInfo = CallInfo ( self , fileName , functionName , lineNumber , . syncWrite )
// / I n s t e a d o f t h i s w e a r e j u s t u s i n g ` D e f e r r e d { F u t u r e { } } ` w h i c h i s e x e c u t e d o n t h e s p e c i f i e d s c h e d u l e r
// / ( w h i c h b e h a v e s i n a m u c h m o r e e x p e c t e d w a y t h a n t h e G R D B ` r e a d P u b l i s h e r ` / ` w r i t e P u b l i s h e r ` d o e s )
// / a n d h o o k i n g t h a t i n t o o u r ` p e r f o r m O p e r a t i o n ` f u n c t i o n w h i c h u s e s t h e G R D B a s y n c / a w a i t f u n c t i o n s t h a t s u p p o r t
// / c a n c e l l a t i o n ( a s w e w a n t t o s u p p o r t c a n c e l l a t i o n a s w e l l )
let holder : TaskHolder = TaskHolder ( )
return Deferred { [ dependencies ] in
Future { resolver in
resolver ( Storage . performOperation ( info , dependencies , operation ) )
let ( _ , task ) = Storage . performOperation ( info , dependencies , operation ) { result in
resolver ( result )
}
holder . task = task
}
} . eraseToAnyPublisher ( )
}
}
private static func debugWait ( semaphore : DispatchSemaphore , info : CallInfo ) -> DispatchTimeoutResult {
let pollQueue : DispatchQueue = DispatchQueue ( label : " com.session.debugWaitTimer. \( UUID ( ) . uuidString ) " )
let standardPollInterval : DispatchTimeInterval = . milliseconds ( 100 )
var iterations : Int = 0
let maxIterations : Int = ( ( Storage . transactionDeadlockTimeoutSeconds * 1000 ) / standardPollInterval . milliseconds )
let pollCompletionSemaphore : DispatchSemaphore = DispatchSemaphore ( value : 0 )
// / S t a g g e r t h e s i z e o f t h e ` p o l l I n t e r v a l s ` t o a v o i d h o l d i n g u p t h e t h r e a d i n c a s e t h e q u e r y r e s o l v e s v e r y q u i c k l y ( t h i s
// / m e a n s t h e t i m e o u t w i l l o c c u r ~ 5 0 0 m s e a r l y b u t h e l p s p r e v e n t f a l s e m a i n t h r e a d l a g a p p e a r i n g w h e n d e b u g g i n g t h a t w o u l d n ' t
// / a f f e c t p r o d u c t i o n )
let pollIntervals : [ DispatchTimeInterval ] = [
. milliseconds ( 5 ) , . milliseconds ( 5 ) , . milliseconds ( 10 ) , . milliseconds ( 10 ) , . milliseconds ( 10 ) ,
standardPollInterval
]
func pollSemaphore ( ) {
iterations += 1
guard iterations < maxIterations && semaphore . wait ( timeout : . now ( ) ) != . success else {
pollCompletionSemaphore . signal ( )
return
}
let nextInterval : DispatchTimeInterval = pollIntervals [ min ( iterations , pollIntervals . count - 1 ) ]
pollQueue . asyncAfter ( deadline : . now ( ) + nextInterval ) {
pollSemaphore ( )
}
}
. handleEvents ( receiveCancel : { holder . task ? . cancel ( ) } )
. eraseToAnyPublisher ( )
}
// / P o l l t h e s e m a p h o r e i n a b a c k g r o u n d q u e u e
pollQueue . asyncAfter ( deadline : . now ( ) + pollIntervals [ 0 ] ) { pollSemaphore ( ) }
pollCompletionSemaphore . wait ( ) // W a i t i n d e f i n i t e l y f o r t h e t i m e r s e m a p h o r e
return ( iterations >= 50 ? . timedOut : . success )
}
// MARK: - F u n c t i o n s
@ -823,8 +796,8 @@ open class Storage {
updates : @ escaping ( Database ) throws -> T ?
) -> T ? {
switch Storage . performOperation ( CallInfo ( self , file , funcN , line , . syncWrite ) , dependencies , updates ) {
case .failure : return nil
case .success ( let result ) : return result
case ( .failure , _ ) : return nil
case ( .success ( let result ) , _ ) : return result
}
}
@ -854,8 +827,8 @@ open class Storage {
_ value : @ escaping ( Database ) throws -> T ?
) -> T ? {
switch Storage . performOperation ( CallInfo ( self , file , funcN , line , . syncRead ) , dependencies , value ) {
case .failure : return nil
case .success ( let result ) : return result
case ( .failure , _ ) : return nil
case ( .success ( let result ) , _ ) : return result
}
}
@ -896,30 +869,32 @@ open class Storage {
)
}
// / A d d a d a t a b a s e o b s e r v a t i o n
// /
// / * * N o t e : * * T h i s f u n c t i o n * * M U S T N O T * * b e c a l l e d f r o m t h e m a i n t h r e a d
public func addObserver ( _ observer : TransactionObserver ? ) {
guard isValid , let dbWriter : DatabaseWriter = dbWriter else { return }
guard let observer : TransactionObserver = observer else { return }
// N o t e : T h i s a c t u a l l y t r i g g e r s a w r i t e t o t h e d a t a b a s e s o c a n b e b l o c k e d b y o t h e r
// w r i t e s , s i n c e i t ' s u s u a l l y c a l l e d o n t h e m a i n t h r e a d w h e n c r e a t i n g a v i e w c o n t r o l l e r
// t h i s c a n r e s u l t i n t h e U I h a n g i n g - t o a v o i d t h i s w e d i s p a t c h ( a n d h o p e t h e r e i s n ' t
// n e g a t i v e i m p a c t )
DispatchQueue . global ( qos : . default ) . async {
dbWriter . add ( transactionObserver : observer )
}
// / T h i s a c t u a l l y t r i g g e r s a w r i t e t o t h e d a t a b a s e s o c a n b e b l o c k e d b y o t h e r w r i t e s s o s h o u l d n ' t b e c a l l e d o n t h e m a i n t h r e a d ,
// / w e d o n ' t d i s p a t c h t o a n a s y n c t h r e a d i n h e r e b e c a u s e ` T r a n s a c t i o n O b s e r v e r ` i s n ' t ` S e n d a b l e ` s o i n s t e a d j u s t r e q u i r e
// / t h a t i t i s n ' t c a l l e d o n t h e m a i n t h r e a d
Log . assertNotOnMainThread ( )
dbWriter . add ( transactionObserver : observer )
}
// / R e m o v e a d a t a b a s e o b s e r v a t i o n
// /
// / * * N o t e : * * T h i s f u n c t i o n * * M U S T N O T * * b e c a l l e d f r o m t h e m a i n t h r e a d
public func removeObserver ( _ observer : TransactionObserver ? ) {
guard isValid , let dbWriter : DatabaseWriter = dbWriter else { return }
guard let observer : TransactionObserver = observer else { return }
// N o t e : T h i s a c t u a l l y t r i g g e r s a w r i t e t o t h e d a t a b a s e s o c a n b e b l o c k e d b y o t h e r
// w r i t e s , s i n c e i t ' s u s u a l l y c a l l e d o n t h e m a i n t h r e a d w h e n c r e a t i n g a v i e w c o n t r o l l e r
// t h i s c a n r e s u l t i n t h e U I h a n g i n g - t o a v o i d t h i s w e d i s p a t c h ( a n d h o p e t h e r e i s n ' t
// n e g a t i v e i m p a c t )
DispatchQueue . global ( qos : . default ) . async {
dbWriter . remove ( transactionObserver : observer )
}
// / T h i s a c t u a l l y t r i g g e r s a w r i t e t o t h e d a t a b a s e s o c a n b e b l o c k e d b y o t h e r w r i t e s s o s h o u l d n ' t b e c a l l e d o n t h e m a i n t h r e a d ,
// / w e d o n ' t d i s p a t c h t o a n a s y n c t h r e a d i n h e r e b e c a u s e ` T r a n s a c t i o n O b s e r v e r ` i s n ' t ` S e n d a b l e ` s o i n s t e a d j u s t r e q u i r e
// / t h a t i t i s n ' t c a l l e d o n t h e m a i n t h r e a d
Log . assertNotOnMainThread ( )
dbWriter . remove ( transactionObserver : observer )
}
}
@ -1028,7 +1003,7 @@ private extension Storage {
result ? . timer = nil
let action : String = ( info . isWrite ? " write " : " read " )
Log . warn ( " [Storage] Slow \( action ) taking longer than \( Storage . slowTransactionThreshold , format : " .2 " , omitZeroDecimal : true ) s - [ \( info . callInfo ) ] " )
Log . warn ( . storage , " Slow \( action ) taking longer than \( Storage . slowTransactionThreshold , format : " .2 " , omitZeroDecimal : true ) s - [ \( info . callInfo ) ] " )
result ? . wasSlowTransaction = true
}
result . timer ? . resume ( )
@ -1044,7 +1019,7 @@ private extension Storage {
let end : CFTimeInterval = CACurrentMediaTime ( )
let action : String = ( info . isWrite ? " write " : " read " )
Log . warn ( " [Storage] Slow \( action ) completed after \( end - start , format : " .2 " , omitZeroDecimal : true ) s - [ \( info . callInfo ) ] " )
Log . warn ( . storage , " Slow \( action ) completed after \( end - start , format : " .2 " , omitZeroDecimal : true ) s - [ \( info . callInfo ) ] " )
}
}
}
@ -1159,13 +1134,18 @@ public extension Storage {
}
}
#if DEBUG
// / F u n c t i o n t o d e t e r m i n e i f t h e d e b u g g e r i s a t t a c h e d
// /
// / * * N o t e : * * O n l y c o n t a i n s l o g i c w h e n ` D E B U G ` i s d e f i n e d , o t h e r w i s e i t a l w a y s r e t u r n s f a l s e
func isDebuggerAttached ( ) -> Bool {
#if DEBUG
var info = kinfo_proc ( )
var size = MemoryLayout < kinfo_proc > . stride
var mib : [ Int32 ] = [ CTL_KERN , KERN_PROC , KERN_PROC_PID , getpid ( ) ]
let sysctlResult = sysctl ( & mib , UInt32 ( mib . count ) , & info , & size , nil , 0 )
guard sysctlResult = = 0 else { return false }
return ( info . kp_proc . p_flag & P_TRACED ) != 0
}
#else
return false
#endif
}