Backport the alleged sqlite deadlock workaround

This commit is contained in:
Ali 2021-04-06 02:57:11 +04:00
parent 0dca1b727d
commit ec8870816d

View File

@ -65,7 +65,7 @@ struct SqlitePreparedStatement {
} }
return res == SQLITE_ROW return res == SQLITE_ROW
} }
struct SqlError: Error { struct SqlError: Error {
var code: Int32 var code: Int32
} }
@ -234,10 +234,8 @@ public final class SqliteValueBox: ValueBox {
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil) let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
let path = basePath + "/db_sqlite" let path = basePath + "/db_sqlite"
#if DEBUG postboxLog("Instance \(self) opening sqlite at \(path)")
print("Instance \(self) opening sqlite at \(path)")
#endif
#if DEBUG #if DEBUG
let exists = FileManager.default.fileExists(atPath: path) let exists = FileManager.default.fileExists(atPath: path)
@ -297,8 +295,10 @@ public final class SqliteValueBox: ValueBox {
let _ = try? FileManager.default.removeItem(atPath: path) let _ = try? FileManager.default.removeItem(atPath: path)
preconditionFailure("Couldn't open database") preconditionFailure("Couldn't open database")
} }
sqlite3_busy_timeout(database.handle, 1000 * 10000) postboxLog("Did open DB at \(path)")
sqlite3_busy_timeout(database.handle, 5 * 1000)
var resultCode: Bool = true var resultCode: Bool = true
@ -306,8 +306,12 @@ public final class SqliteValueBox: ValueBox {
assert(resultCode) assert(resultCode)
resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32") resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32")
assert(resultCode) assert(resultCode)
postboxLog("Did set up cipher")
if self.isEncrypted(database) { if self.isEncrypted(database) {
postboxLog("Database is encrypted")
if let encryptionParameters = encryptionParameters { if let encryptionParameters = encryptionParameters {
precondition(encryptionParameters.salt.data.count == 16) precondition(encryptionParameters.salt.data.count == 16)
precondition(encryptionParameters.key.data.count == 32) precondition(encryptionParameters.key.data.count == 32)
@ -316,12 +320,15 @@ public final class SqliteValueBox: ValueBox {
resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"") resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"")
assert(resultCode) assert(resultCode)
postboxLog("Setting encryption key")
if self.isEncrypted(database) { if self.isEncrypted(database) {
postboxLog("Encryption key is invalid")
if isTemporary || isReadOnly { if isTemporary || isReadOnly {
return nil return nil
} }
postboxLog("Encryption key is invalid")
for fileName in dabaseFileNames { for fileName in dabaseFileNames {
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)") let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
@ -354,6 +361,8 @@ public final class SqliteValueBox: ValueBox {
assert(resultCode) assert(resultCode)
} }
} else if let encryptionParameters = encryptionParameters, encryptionParameters.forceEncryptionIfNoSet { } else if let encryptionParameters = encryptionParameters, encryptionParameters.forceEncryptionIfNoSet {
postboxLog("Not encrypted")
let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data) let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data)
if FileManager.default.fileExists(atPath: path) { if FileManager.default.fileExists(atPath: path) {
@ -409,6 +418,8 @@ public final class SqliteValueBox: ValueBox {
} }
} }
} }
postboxLog("Did set up encryption")
//database.execute("PRAGMA cache_size=-2097152") //database.execute("PRAGMA cache_size=-2097152")
resultCode = database.execute("PRAGMA mmap_size=0") resultCode = database.execute("PRAGMA mmap_size=0")
@ -421,6 +432,9 @@ public final class SqliteValueBox: ValueBox {
assert(resultCode) assert(resultCode)
resultCode = database.execute("PRAGMA cipher_memory_security = OFF") resultCode = database.execute("PRAGMA cipher_memory_security = OFF")
assert(resultCode) assert(resultCode)
postboxLog("Did set up pragmas")
//resultCode = database.execute("PRAGMA wal_autocheckpoint=500") //resultCode = database.execute("PRAGMA wal_autocheckpoint=500")
//database.execute("PRAGMA journal_size_limit=1536") //database.execute("PRAGMA journal_size_limit=1536")
@ -441,8 +455,12 @@ public final class SqliteValueBox: ValueBox {
let _ = self.runPragma(database, "checkpoint_fullfsync = 1") let _ = self.runPragma(database, "checkpoint_fullfsync = 1")
assert(self.runPragma(database, "checkpoint_fullfsync") == "1") assert(self.runPragma(database, "checkpoint_fullfsync") == "1")
postboxLog("Did set up checkpoint_fullfsync")
self.beginInternal(database: database) self.beginInternal(database: database)
postboxLog("Did begin transaction")
let result = self.getUserVersion(database) let result = self.getUserVersion(database)
@ -462,8 +480,12 @@ public final class SqliteValueBox: ValueBox {
for table in self.listFullTextTables(database) { for table in self.listFullTextTables(database) {
self.fullTextTables[table.id] = table self.fullTextTables[table.id] = table
} }
postboxLog("Did load tables")
self.commitInternal(database: database) self.commitInternal(database: database)
postboxLog("Did commit final")
lock.unlock() lock.unlock()
@ -518,7 +540,21 @@ public final class SqliteValueBox: ValueBox {
private func isEncrypted(_ database: Database) -> Bool { private func isEncrypted(_ database: Database) -> Bool {
var statement: OpaquePointer? = nil var statement: OpaquePointer? = nil
postboxLog("isEncrypted prepare...")
let allIsOk = Atomic<Bool>(value: false)
let databasePath = self.databasePath
DispatchQueue.global().asyncAfter(deadline: .now() + 5.0, execute: {
if allIsOk.with({ $0 }) == false {
postboxLog("Timeout reached, discarding database")
try? FileManager.default.removeItem(atPath: databasePath)
preconditionFailure()
}
})
let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil) let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil)
let _ = allIsOk.swap(true)
postboxLog("isEncrypted prepare done")
if statement == nil { if statement == nil {
postboxLog("isEncrypted: sqlite3_prepare_v2 status = \(status) [\(self.databasePath)]") postboxLog("isEncrypted: sqlite3_prepare_v2 status = \(status) [\(self.databasePath)]")
return true return true
@ -536,6 +572,7 @@ public final class SqliteValueBox: ValueBox {
preparedStatement.destroy() preparedStatement.destroy()
return true return true
} }
postboxLog("isEncrypted step done")
preparedStatement.destroy() preparedStatement.destroy()
return status == SQLITE_NOTADB return status == SQLITE_NOTADB
} }