groue / GRDB.swift

A toolkit for SQLite databases, with a focus on application development

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

App crashes with many (not all) users running iOS 17.4.1

Tybion opened this issue · comments

What did you do?

Upgraded GRDB to the most current version 6 available in Cocoapods

What did you expect to happen?

GRDB code that has been running for at least 2 years. No warnings about code in the IDE.
Worked fine on my own iPhone 14 running iOS 17.5 beta, so expected no problems

What happened instead?

So far, about 30 customers have reported (will be more to come as upgrade is propogated) that they can't run the app because it crashes on start up.

Environment

GRDB flavor(s): (GRDB, SQLCipher, Custom SQLite build?). GRDB.swift (highest version 5) SQLCipher (4.5.6)
GRDB version:
Installation method: (CocoaPods, SPM, manual?) Cocoapods
Xcode version: 15.3
Swift version: 5
Platform(s) running GRDB: (iOS, macOS, watchOS?). iOS
macOS version running Xcode: Sonoma 14.4.1

Demo Project

Can't do this yet

Crashlytics

libsqlite3.dylib
sqlite3_sourceid + 36940
2
Australian Geology Travel Maps
<compiler-generated> - Line 4329370260
specialized Row.subscript.getter + 4329370260
3
Australian Geology Travel Maps
closure #1 in SQLiteTrails.readLocationsToCsv() + 4329396684
4
Australian Geology Travel Maps
<compiler-generated> - Line 4329403204
partial apply for closure #1 in SQLiteTrails.readLocationsToCsv() + 4329403204
30
GRDB
$s4GRDB13DatabaseQueueC4readyxxAA0B0CKXEKlF + 44
31
Australian Geology Travel Maps
SQLiteTrails.swift - Line 758
SQLiteTrails.readLocationsToCsv() + 758
32
Australian Geology Travel Maps
closure #1 in MainView.rescueData() + 4329116344
33
Australian Geology Travel Maps
<compiler-generated> - Line 4329250148
thunk for @escaping @callee_guaranteed (@guaranteed UIAlertAction) -> () + 4329250148
53
UIKitCore
UIApplicationMain + 340
54
Australian Geology Travel Maps
AppDelegate.swift - Line 20
main + 20

Sample code

The trails.sqlite file is unencrypted, so SQLCipher is not used.

import Foundation
import GRDB
import MapKit
import CSV
 :  :
    // Table = 'trail'
    struct Trail: Codable, FetchableRecord, PersistableRecord {
        var id: Int
        var title: String
        var descr: String
        var colour: String
    }

    // Table = 'coord'
    struct Coord: Codable, FetchableRecord, PersistableRecord {
        //var seqno: Int - auto-incremented primary key
        var trailid: Int
        var latitude: Double
        var longitude: Double
        var speed: Double
    }
    
    // Table = 'location'
    struct Location: Codable, FetchableRecord, PersistableRecord {
        var id: Int
        var title: String
        var descr: String
        var colour: String
        var longitude: Double
        var latitude: Double
    }
    
    // If these 4 values are the same in 2 trails or 2 locations, then assume they are duplicates
    // Although, theoretically the 2 trails could be different, it is exteremely unlikely
    struct UniqFeature {
        var title : String
        var descr : String
        var longitude : Double  // first longitude in a trail
        var latitude : Double   // first latitude in a trail
    }

    // ***************************
    init() throws {

        let folder = MyUtils.getAppFolder("trails")
        let filemgr = FileManager.default
        var isDir = ObjCBool(true)
        
        let exists = filemgr.fileExists(atPath: folder, isDirectory: &isDir)
        if !exists {
            // create folder and SQLite file
            do {
                try filemgr.createDirectory(atPath: folder, withIntermediateDirectories: true, attributes: nil)
            } catch let error as NSError {
                let str = "Unable to create trails directory: \(error.debugDescription)"
                throw myError(str)
            }
        }
        
        // Create SQLite file if it doesn't exist, else just connect ..
        do {
            let path = folder + "trails.sqlite"
            //trailsDB = try Connection(folder + "trails.sqlite")
            dbQueue = try DatabaseQueue(path: path)
            MyUtils.Log(TAG, "Opened trails.sqlite (un-encrypted)")
            
        } catch let error as NSError {
            let str = "Unable to create trails SQLite file: \(error.debugDescription)"
            throw myError(str)
        }

        // Create 'location' table if it doesn't exist
        /*
        let location = Table("location")
        let id = Expression<Int>("id")
        let title = Expression<String>("title")
        let descr = Expression<String>("descr")
        let colour = Expression<String>("colour")
        let longitude = Expression<Double>("longitude")
        let latitude = Expression<Double>("latitude")
        */

        do {
            try dbQueue!.write { db in
                try db.create(table: "location", ifNotExists: true) { t in
                    t.column("id", .integer)
                    t.column("title", .text)
                    t.column("descr", .text)
                    t.column("longitude", .double)
                    t.column("latitude", .double)
                    t.primaryKey(["id"])
                }
                try db.execute(sql: "CREATE INDEX IF NOT EXISTS index_location_on_longitude_latitude ON location(longitude, latitude)")
            }
          
        } catch let error as NSError {
            let str = "Unable to create 'location' table: \(error.debugDescription)"
            throw myError(str)
        }

        // Add 'colour' table to hold RGB value
        // Been tested on iPhone 7 - colour(TEXT) field is created.
        do {
            try dbQueue!.write { db in
                try db.execute(sql: "ALTER TABLE location ADD COLUMN colour text")
            }
        } catch let error as NSError {
            let str = "Unable to add 'colour' column to 'location' table: \(error.debugDescription)"
            MyUtils.Log(TAG, str)
        }

        // Create 'trail' and 'coord' tables if they don't exist
        /*
        let trail = Table("trail")
        let id = Expression<Int>("id")
        let title = Expression<String>("title")
        let descr = Expression<String>("descr")
        */
 
        do {
            try dbQueue!.write { db in
                try db.create(table: "trail", ifNotExists: true) { t in
                    t.column("id", .integer)
                    t.column("title", .text)
                    t.column("descr", .text)
                    t.primaryKey(["id"])
                }
            }
            /*
            try trailsDB.run(trail.create(ifNotExists: true) { t in
                t.column(id, primaryKey: true)        // "id" INTEGER PRIMARY KEY NOT NULL,
                t.column(title)
                t.column(descr)
            }) */
        } catch let error as NSError {
            let str = "Unable to create 'trail' table: \(error.debugDescription)"
            throw myError(str)
            //return
        }
        
        // Add 'colour' to trail
        do {
            try dbQueue!.write { db in
                try db.execute(sql: "ALTER TABLE trail ADD COLUMN colour text")
            }
        } catch let error as NSError {
            let str = "Unable to add 'colour' column to 'trail' table: \(error.debugDescription)"
            MyUtils.Log(TAG, str)
        }

        /*
        let coord = Table("coord")
        let seqno = Expression<Int>("seqno")
        let trailid = Expression<Int>("trailid")
        let speed = Expression<Double>("speed")
        */

        do {
            try dbQueue!.write { db in
                try db.create(table: "coord", ifNotExists: true) { t in
                    t.autoIncrementedPrimaryKey("seqno")
                    t.column("trailid", .integer)
                    t.column("latitude", .double)
                    t.column("longitude", .double)
                    t.column("speed", .double)
                }
                try db.execute(sql: "CREATE INDEX IF NOT EXISTS index_coord_on_trailid  ON coord(trailid)")
            }
            /*
            try trailsDB.run(coord.create(ifNotExists: true) { t in
                t.column(seqno, primaryKey: .autoincrement) // "seqno" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
                t.column(trailid)
                t.column(latitude)
                t.column(longitude)
                t.column(speed)
            })
            try trailsDB.run(coord.createIndex(trailid, ifNotExists: true))
            */
        } catch let error as NSError {
            let str = "Unable to create 'coord' table: \(error.debugDescription)"
            throw myError(str)
        }
    }
    // ***************************
    // ***************************
    func readTrails(trails: inout [trailGPS], idStart: Int, idFinish: Int) throws -> Int {
        
        // Read IDs between idStart and idFinish inclusive
        
        if (dbQueue == nil) {return -1}
            
        /*
        let trail = Table("trail")
        let id = Expression<Int>("id")
        let coord = Table("coord")
        let trailid = Expression<Int>("trailid")
        let seqno = Expression<Int>("seqno")
        let title = Expression<String>("title")
        let descr = Expression<String>("descr")
        let speed = Expression<Double>("speed")
        let latitude = Expression<Double>("latitude")
        let longitude = Expression<Double>("longitude")
        */
        
        // Read data - one SELECT statement
        var oldid: Int = 0
        var coords: Int = 0
        var trailGps: trailGPS!
        do {
            try dbQueue!.read { db in
                // Fetch database rows - always build SQL in DB Browser for Sqlite
                let rows = try Row.fetchCursor(db, sql: """
                    SELECT * FROM trail
                    INNER JOIN coord on trail.id = coord.trailid
                    WHERE id>=? AND id<=?
                    ORDER BY id, seqno
                """, arguments: [idStart, idFinish])
                
                while let row = try rows.next() {
                    
                    // If CSV has 'colour' column, then get colour as TrailColour
                    var color = TrailColour.MAGENTA
                    if let colr = row["colour"] {
                        color = TrailColour(rawValue: colr as! String) ?? .MAGENTA
                    }
                    
                    //print("id: \(user[id]), email: \(user[email]), name: \(user[name])")
                    if oldid == 0 || oldid != row["id"] {
                        if oldid != 0 {
                            // append complete trail to array
                            trails.append(trailGps)
                        }
                        // next trail ..
                        trailGps = trailGPS(trailid: row["id"])
                        trailGps.title = row["title"]
                        trailGps.descr = row["descr"]
                        trailGps.colour = color
                    }
                    
                    coords += 1
                    trailGps.add(coord: CLLocationCoordinate2D(
                        latitude: row["latitude"], longitude: row["longitude"]),
                        speed: row["speed"])
                    oldid = row["id"]
                }
            }
            
            // LAST trail read (unless database is empty) ..
            if oldid != 0 {trails.append(trailGps)}
            
        } catch let error as NSError {
            let str = "Unable to read trails from SQLite: \(error.debugDescription)"
            throw myError(str)
            //return
        }

        MyUtils.getSQLiteTableCount(dbQueue: dbQueue!, tablename: "trail")
        MyUtils.getSQLiteTableCount(dbQueue: dbQueue!, tablename: "coord")

        return coords
    }
    
    // ***************************

   

I'm facing the same problem.
My app crashes when I try to write data into db.

3e4f225f39679739ce76

Hello @Tybion,

I'm sorry for those crashes.

There's insufficient information in your report:

  • The crashlytics report looks mutilated. If there is a crash, there must be some error code or message visible somewhere. It is your task to read the crash report and its context and share the information that is relevant to the issue. Pasting without reading is not enough.
  • The pasted code in unrelated to the crashlytics excerpt. Again, it is your task to provide information that is relevant to the issue.
  • You did not provide the results of your own inquiry.

Thanks, Gwendal.

Sorry, yes, I accidentally pasted the wrong crashlytics report. I have attached this one for the readTrails() crash - this is the function where I have pasted the code above.

I picked this because 13 users at this stage have experienced this, so is quite representative. Crashlytics says 92% of these are running iOS 17.4.1. (5% 17.2.1, 2% 17.3.0)

Line 576 is ..

try dbQueue!.read { db in

.. so the that is not very useful - just means somewhere in the closure.

ios17_4_1crash.txt

I am really under pressure at the moment and have to travel over the next 24 hours, which is really bad timing. Sorry, I do not have huge skills in this low level stuff, and I am in a team of one. When I created this ticket, I thought maybe I had done something dumb that you could point out, but sounds more serious than that.

By the way, for a TestFlight app, I have upgraded to GRDB 6.24.1, but am still waiting for Apple to approve the build.

Something is strange in the stack trace:

Crashed: GRDB.DatabaseQueue
0  libsqlite3.dylib                  0x9a10c sqlite3_sourceid + 36940
1  libsqlite3.dylib                  0x52a7c sqlite3_column_type + 104

sqlite3_column_type does not call sqlite3_sourceid. And sqlite3_sourceid returns a static C string: it can't crash. Either Crashlytics symbolication is wrong, either we're witnessing a big linker mess.

Could it be that your application is linked against two different and competing SQLite libraries? For example, did you introduce a new framework in your app? The problem would not be with GRDB, but with the way Xcode deals with the interaction of GRDB with this framework. I unfortunately don't have a straight answer to this kind of linker problem.

Thanks, I will look into that.