Android 9.0 SQLiteCantOpenDatabaseException SQLITE_CANTOPEN (does not support WAL mode) source code analysis and positioning

Recently, I have been busy dealing with reducing the crash rate. A database-related crash was counted in the latest version of Bugly:

# pool-20-thread-1(323)
android.database.sqlite.SQLiteCantOpenDatabaseException
unable to open database file (Sqlite code 14 SQLITE_CANTOPEN), (OS error - 2:No such file or directory)

android.database.sqlite.SQLiteConnection.nativeExecuteForLong(Native Method)
android.database.sqlite.SQLiteConnection.executeForLong(SQLiteConnection.java:657)
android.database.sqlite.SQLiteSession.executeForLong(SQLiteSession.java:667)
android.database.sqlite.SQLiteStatement.simpleQueryForLong(SQLiteStatement.java:107)
android.database.DatabaseUtils.longForQuery(DatabaseUtils.java:842)
android.database.DatabaseUtils.longForQuery(DatabaseUtils.java:830)
android.database.sqlite.SQLiteDatabase.getVersion(SQLiteDatabase.java:1036)
android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:390)
android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:337)
com.meta.android.mpg.mix.sG4DaaDs.a4Dgsas.a4Dgsas(Unknown Source:3)
com.meta.android.mpg.mix.gG4Gas.fa$fa.run(Unknown Source:4)
java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
java.lang.Thread.run(Thread.java:784)

The statistics of the models that occurred are as follows:
insert image description here

1. Source code location analysis

1.1 Use system logs to assist in positioning

View the effective log captured by bugly:

08-23 17:13:28.790 5668 5860 E SQLiteLog: (14) cannot open file at line 36906 of [68b898381a]
4708-23 17:13:28.790 5668 5860 E SQLiteLog: (14) os_unix.c:36906: (2) open(/data/data/com.tools.growth.yhxy/virtual/data/user/0/com.minitech.miniworld.meta/databases/MpgSdk.db-wal) -
4808-23 17:13:28.791 5668 5860 E SQLiteLog: (14) unable to open database file// 有效日志
4908-23 17:13:28.791 5668 5860 E SQLiteDatabase: Failed to open database  '/data/data/com.tools.growth.yhxy/virtual/data/user/0/com.minitech.miniworld.meta/databases/MpgSdk.db'.
5008-23 17:13:28.791 5668 5860 E SQLiteDatabase: android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (Sqlite code 14 SQLITE_CANTOPEN): , while compiling: PRAGMA journal_mode, (OS error - 2:No such file or directory) 
5108-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
5208-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:948)
5308-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:693)
5408-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:378)
5508-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:327)
5608-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:232)
5708-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:210)
5808-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:552)
5908-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:213)
6008-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:202)
6108-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:958)
6208-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:942)
6308-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:816)
6408-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:806)
6508-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:370)
6608-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:337)
6708-23 17:13:28.791 5668 5860 E SQLiteDatabase: at com.meta.android.mpg.mix.sG4DaaDs.a4Dgsas.a4Dgsas(Unknown Source:3)
6808-23 17:13:28.791 5668 5860 E SQLiteDatabase: at com.meta.android.mpg.mix.gG4Gas.fa$fa.run(Unknown Source:4)
6908-23 17:13:28.791 5668 5860 E SQLiteDatabase: at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
7008-23 17:13:28.791 5668 5860 E SQLiteDatabase: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
7108-23 17:13:28.791 5668 5860 E SQLiteDatabase: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)

When there is no log for the current crash record reported in Bugly, you can view a few more records. The log is a very good auxiliary means to speed up the location of the problem.

1.2 Check the source code and lock the error point

The source code of this article is based on android 9.0

/frameworks/base/core/java/android/database/sqlite/SQLiteConnection.java

    private PreparedStatement acquirePreparedStatement(String sql) {
    
    
        PreparedStatement statement = mPreparedStatementCache.get(sql);
        //...
        final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
        //...
        return statement;
    }
	private static native long nativePrepareStatement(long connectionPtr, String sql);

Next, look at the call of the jni layer.

/frameworks/base/core/jni/android_database_SQLiteConnection.cpp

static jlong nativePrepareStatement(JNIEnv* env, jclass clazz, jlong connectionPtr,
        jstring sqlString) {
    
    
    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);

    jsize sqlLength = env->GetStringLength(sqlString);
    const jchar* sql = env->GetStringCritical(sqlString, NULL);
    sqlite3_stmt* statement;
    int err = sqlite3_prepare16_v2(connection->db,
            sql, sqlLength * sizeof(jchar), &statement, NULL);
    env->ReleaseStringCritical(sqlString, sql);

    if (err != SQLITE_OK) {
    
    
        //构建异常信息
        const char *query = env->GetStringUTFChars(sqlString, NULL);
        char *message = (char*) malloc(strlen(query) + 50);
        if (message) {
    
    
            strcpy(message, ", while compiling: "); // less than 50 chars
            strcat(message, query);
        }
        env->ReleaseStringUTFChars(sqlString, query);
		//抛出异常
        throw_sqlite3_exception(env, connection->db, message);
        free(message);
        return 0;
    }
    return reinterpret_cast<jlong>(statement);
}

Here it just matches the in the log while compiling: PRAGMA journal_mode, indicating that calling sqlite3_prepare16_v2 returns a failure result.

Go ahead and check out sqlite3_prepare16_v2():

/external/sqlite/dist/orig/sqlite3.c

SQLITE_API int sqlite3_prepare16_v2(
  sqlite3 *db,              /* Database handle. */
  const void *zSql,         /* UTF-16 encoded SQL statement. */
  int nBytes,               /* Length of zSql in bytes. */
  sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement */
  const void **pzTail       /* OUT: End of parsed string */
){
    
    
  int rc;
  rc = sqlite3Prepare16(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,ppStmt,pzTail);
  assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 );  /* VERIFY: F13021 */
  return rc;
}

Because the source code of SQLite is too large, we can only consider the reverse method, and first search the key position according to the error log.

According to unable to open database filematch to SQLITE_CANTOPEN field.

SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){
    
    
  static const char* const aMsg[] = {
    
    
   /* SQLITE_CANTOPEN    */ "unable to open database file",
  }
  //... 
}

According to the matching of cannot open file atand , it is used to splice and print the error message when reporting an error:SQLITE_CANTOPENsqlite3CantopenError()

Next look, sqlite3CantopenError():

#define SQLITE_CANTOPEN_BKPT sqlite3CantopenError(__LINE__)

SQLITE_PRIVATE int sqlite3CantopenError(int lineno){
    
    
  testcase( sqlite3GlobalConfig.xLog!=0 );
  return sqlite3ReportError(SQLITE_CANTOPEN, lineno, "cannot open file"); // 对应日志:(14) cannot open file at line 36906 of [68b898381a]
}

Global search SQLITE_CANTOPENkeywords, step-by-step analysis and call chains that consume brain cells: sqlite3Prepare16()->省略部分流程->sqlite3BtreeBeginTrans()->lockBtree()->sqlite3PagerOpenWal().

Next, look at sqlite3BtreeBeginTrans():

SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
    
    
  
  //....
  do {
    
    
  
	//循环检查,直到lockBtree返回相应状态
    while( pBt->pPage1==0 && SQLITE_OK==(rc = lockBtree(pBt)) );
    if( rc==SQLITE_OK && wrflag ){
    
    
      if( (pBt->btsFlags & BTS_READ_ONLY)!=0 ){
    
    
        rc = SQLITE_READONLY;
      }else{
    
    
        rc = sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db));
        if( rc==SQLITE_OK ){
    
    
          rc = newDatabase(pBt);
        }
      }
    }
    if( rc!=SQLITE_OK ){
    
    
      unlockBtreeIfUnused(pBt);
    }
  }while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
          btreeInvokeBusyHandler(pBt) )

  return rc;
}

Then continue to check, lockBtree():

static int lockBtree(BtShared *pBt){
    
    
  int rc;              /* Result code from subfunctions */
  MemPage *pPage1;     /* Page 1 of the database file */
  int nPage;           /* Number of pages in the database */
  int nPageFile = 0;   /* Number of pages in the database file */
  int nPageHeader;     /* Number of pages in the database according to hdr */

  //先获取一个share lock
  rc = sqlite3PagerSharedLock(pBt->pPager);
  if( rc!=SQLITE_OK ) return rc;
  
  //...
  
  //当write version 是2,数据库应该使用wal 模式。若是检查到log文件没有被打开,则进行打开。
    if( page1[19]==2 && (pBt->btsFlags & BTS_NO_WAL)==0 ){
    
    
      int isOpen = 0;
      rc = sqlite3PagerOpenWal(pBt->pPager, &isOpen);
      if( rc!=SQLITE_OK ){
    
    
	    //若是打开失败,则进行释放资源操作
        goto page1_init_failed;
      }else{
    
    
        setDefaultSyncFlag(pBt, SQLITE_DEFAULT_WAL_SYNCHRONOUS+1);
        if( isOpen==0 ){
    
    
          releasePageOne(pPage1);
          return SQLITE_OK;
        }
      }
	  // 若是打开正常,则返回 SQLITE_NOTADB
      rc = SQLITE_NOTADB;
    }else{
    
    
      setDefaultSyncFlag(pBt, SQLITE_DEFAULT_SYNCHRONOUS+1);
    }
   //....
   
  page1_init_failed:
  releasePageOne(pPage1);
  pBt->pPage1 = 0;
  return rc;
}

Let's take a look first sqlite3PagerSharedLock():

SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
    
    
  int rc = SQLITE_OK;                /* Return code */


  if( !pagerUseWal(pPager) && pPager->eState==PAGER_OPEN ){
    
    

    // 获取到shared lock 
    rc = pager_wait_on_lock(pPager, SHARED_LOCK);
    //...

  }
 
  return rc;
}

Next look, sqlite3PagerOpenWal():

SQLITE_PRIVATE int sqlite3PagerOpenWal(
  Pager *pPager,                  /* Pager object */
  int *pbOpen                     /* OUT: Set to true if call is a no-op */
){
    
    
  int rc = SQLITE_OK;             /* Return code */

  assert( assert_pager_state(pPager) );
  assert( pPager->eState==PAGER_OPEN   || pbOpen );
  assert( pPager->eState==PAGER_READER || !pbOpen );
  assert( pbOpen==0 || *pbOpen==0 );
  assert( pbOpen!=0 || (!pPager->tempFile && !pPager->pWal) );

  if( !pPager->tempFile && !pPager->pWal ){
    
    
  
    // 关键代码: 若是不支持Write-Ahead-Logging(即)模式, 则返回SQLITE_CANTOPEN。
    if( !sqlite3PagerWalSupported(pPager) ) return SQLITE_CANTOPEN;

    /* Close any rollback journal previously open */
    sqlite3OsClose(pPager->jfd);

    rc = pagerOpenWal(pPager);
    if( rc==SQLITE_OK ){
    
     // 若是支持Write-Ahead-Logging 模式,则标记打开模式和状态
      pPager->journalMode = PAGER_JOURNALMODE_WAL;
      pPager->eState = PAGER_OPEN;
    }
  }else{
    
    
    *pbOpen = 1;
  }

  return rc;
}

Through the analysis of some columns, it is inferred that the database does not support the Write-Ahead-Logging mode, so consider disabling the use of this mode to deal with this problem .

2. Solutions

2.1 WAL mode of Android 9 and above database :

Android 9 introduces a special mode of SQLiteDatabase called "compatibility WAL (write-ahead logging)", which allows database usage journal_mode=WALwhile preserving the behavior of creating at most one connection per database.

For more information, read Application Compatibility WAL (Write Ahead Logging) .

2.2 Solutions to disable wal mode :

https://stackoverflow.com/questions/53659206/disabling-sqlite-write-ahead-logging-in-android-pie

Guess you like

Origin blog.csdn.net/hexingen/article/details/126538093