Android-job: Crash when scheduling jobs at Application's onCreate method

Created on 9 Jan 2018  Â·  14Comments  Â·  Source: evernote/android-job

I have 2 types of jobs which runs at application's onCreate in the following sequence:

EventJobUploader.schedulePeriodicJob();
EventJobUploader.runJobImmediately();
FavoriteJob.schedulePeriodicJob();
FavoriteJob.runJobImmediately();

Implementation of jobs methods a similar:

    public static void runJobImmediately(){
        new JobRequest.Builder(EventJobUploader.TAG)
                .setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.SECONDS.toMillis(20))
                .build()
                .schedule();
    }

    public static void schedulePeriodicJob(){
        new JobRequest.Builder(EventJobUploader.TAG)
                .setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
                .setUpdateCurrent(true)
                .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
                .build()
                .schedule();
    }

And on several devices (unknown, sentry did not provide any info about them, sorry) I've got crash with the following stacktrace:

android.database.sqlite.SQLiteException: table jobs already exists (code 1)
    at dalvik.system.NativeStart.main
    at com.android.internal.os.ZygoteInit.main
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run
    at java.lang.reflect.Method.invoke
    at java.lang.reflect.Method.invokeNative
    at android.app.ActivityThread.main
    at android.os.Looper.loop
    at android.os.Handler.dispatchMessage
    at android.app.ActivityThread$H.handleMessage
    at android.app.ActivityThread.access$1300
    at android.app.ActivityThread.handleBindApplication
    at android.app.Instrumentation.callApplicationOnCreate
    at xxx.Application.onCreate
    at xxx.EventJobUploader.schedulePeriodicJob
    at com.evernote.android.job.JobRequest.schedule
    at com.evernote.android.job.JobManager.schedule
    at com.evernote.android.job.JobStorage.put
    at com.evernote.android.job.JobStorage.store
    at com.evernote.android.job.JobStorage.getDatabase
    at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase
    at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked
    at com.evernote.android.job.JobStorage$JobOpenHelper.onCreate
    at com.evernote.android.job.JobStorage$JobOpenHelper.createJobTable
    at android.database.sqlite.SQLiteDatabase.execSQL
    at android.database.sqlite.SQLiteDatabase.executeSql
    at android.database.sqlite.SQLiteStatement.executeUpdateDelete
    at android.database.sqlite.SQLiteSession.executeForChangedRowCount
    at android.database.sqlite.SQLiteConnection.executeForChangedRowCount
    at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount
enhancement

Most helpful comment

In android's training its said we should leave our database connection open for as long as we possibly need to access it

Since getWritableDatabase() and getReadableDatabase() are expensive to call when the database is closed, you should leave your database connection open for as long as you possibly need to access it. Typically, it is optimal to close the database in the onDestroy() of the calling Activity.

Ref: https://developer.android.com/training/data-storage/sqlite.html

Also post post by a Google engineer (Dianne Hackborn)

Android made a deliberate design decision that is can seem surprising, to just give up on the whole idea of applications cleanly exiting and instead let the kernel clean up their resources. After all, the kernel needs to be able to do this anyway. Given that design, keeping anything open for the entire duration of a process's life and never closing it is simply not a leak. It will be cleaned up when the process is cleaned up.

Ref: https://groups.google.com/forum/#!msg/android-developers/nopkaw4UZ9U/cPfPL3uW7nQJ

All 14 comments

Also sometimes app crashed with another kind of sqlite related error:

android.database.sqlite.SQLiteDatabaseLockedException: database is locked(Sqlite code 5): , while compiling: PRAGMA journal_mode,(OS error - 11:Try again)
    at com.android.internal.os.ZygoteInit.main
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run
    at java.lang.reflect.Method.invoke
    at java.lang.reflect.Method.invoke
    at android.app.ActivityThread.main
    at android.os.Looper.loop
    at android.os.Handler.dispatchMessage
    at android.app.ActivityThread$H.handleMessage
    at android.app.ActivityThread.access$1900
    at android.app.ActivityThread.handleBindApplication
    at android.app.Instrumentation.callApplicationOnCreate
    at xxx.Application.onCreate
    at xxx.FavoriteJob.runJobDelay
    at com.evernote.android.job.JobRequest.schedule
    at com.evernote.android.job.JobManager.schedule
    at com.evernote.android.job.JobStorage.put
    at com.evernote.android.job.JobStorage.store
    at com.evernote.android.job.JobStorage.getDatabase
    at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase
    at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked
    at android.content.ContextWrapper.openOrCreateDatabase
    at android.app.ContextImpl.openOrCreateDatabase
    at android.database.sqlite.SQLiteDatabase.openDatabase
    at android.database.sqlite.SQLiteDatabase.open
    at android.database.sqlite.SQLiteDatabase.openInner
    at android.database.sqlite.SQLiteConnectionPool.open
    at android.database.sqlite.SQLiteConnectionPool.open
    at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked
    at android.database.sqlite.SQLiteConnection.open
    at android.database.sqlite.SQLiteConnection.open
    at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration
    at android.database.sqlite.SQLiteConnection.setJournalMode
    at android.database.sqlite.SQLiteConnection.executeForString
    at android.database.sqlite.SQLiteConnection.acquirePreparedStatement
    at android.database.sqlite.SQLiteConnection.nativePrepareStatement

Source code shows that db access is not synchronized at all - maybe any ReadWriteLock can be added to resolve concurrent access to database between database access methods inside JobStorage class?

All the methods inside of JobStorage are synchronized. Honestly, I don't know what I should do here. Any recommendation?

Well, I was talking about synchronized access to database object, i.e. use readLock to access for queries and writeLock for database modify operations.

But that's what the synchronized keyword is also achieving. Or am I missing something?

synchronized keyword for the method just guarantee that just a method is
thread-safe. But calling several methods from several threads do not
guarantee thread-safety for database object.

On Thu, Jan 11, 2018 at 4:15 PM, Ralf Wondratschek <[email protected]

wrote:

But that's what the synchronized keyword is also achieving. Or am I
missing something?

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/evernote/android-job/issues/344#issuecomment-356873158,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABGgmlX3PXJCPtXk0TDhTX63yNowLPHmks5tJdFIgaJpZM4RYBWw
.

Dang, I thought I also synchronized the whole object when accessing it. Good catch! Thanks

Sorry for the confusion, but what you said is wrong. Synchronized methods prevent that calls on ALL synchronized methods of the same object don't interleave, see here. But I agree that a ReadWriteLock provides a better performance. Nonetheless, it won't fix your problem.

Sorry for the confusion, but what you said is wrong.

Yep, sorry, my fault.

Version 1.2.2 has been released. It now uses a ReadWriteLock.

A much simpler fix would be to just open the database connection once, and reuse it everywhere. You don't need to open and close the database connection every time you read or write. SQLite itself takes care of thread safety.

I'll make this feature optional through the JobConfig.

In android's training its said we should leave our database connection open for as long as we possibly need to access it

Since getWritableDatabase() and getReadableDatabase() are expensive to call when the database is closed, you should leave your database connection open for as long as you possibly need to access it. Typically, it is optimal to close the database in the onDestroy() of the calling Activity.

Ref: https://developer.android.com/training/data-storage/sqlite.html

Also post post by a Google engineer (Dianne Hackborn)

Android made a deliberate design decision that is can seem surprising, to just give up on the whole idea of applications cleanly exiting and instead let the kernel clean up their resources. After all, the kernel needs to be able to do this anyway. Given that design, keeping anything open for the entire duration of a process's life and never closing it is simply not a leak. It will be cleaned up when the process is cleaned up.

Ref: https://groups.google.com/forum/#!msg/android-developers/nopkaw4UZ9U/cPfPL3uW7nQJ

In version 1.2.3 I don't close the database anymore. Please let me know if that works for you all better now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

karntrehan picture karntrehan  Â·  5Comments

jsu4650 picture jsu4650  Â·  5Comments

SwiftyWang picture SwiftyWang  Â·  7Comments

Haoxiqiang picture Haoxiqiang  Â·  3Comments

ExploiTR picture ExploiTR  Â·  6Comments