sam
sam

Reputation: 1116

Android ANR: thread waiting on lock that it already holds?

I'm seeing an ANR in my Android application. The stack of my main thread shows this happens while it is running a DB query. As per the Keeping your app responsive guidelines, this is probably wrong and should be moved to a worker thread.

However, I want to check that this operation is just slow and not deadlocked. What I'm confused by, is the stack shows this thread tid=1 is waiting for <0x41788f70>, which is held by ... tid=1! How is this possible? Surely if it is this thread holding the object, there's no need to wait? What am I missing here?

I can't find any other reference to the handle <0x41788f70> anywhere else in the stack dump.

Thanks for your help.

Here's the stack of the main thread:

"main" prio=5 tid=1 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 obj=0x41788ea0 self=0x41677588
  | sysTid=19868 nice=-11 sched=0/0 cgrp=apps handle=1074336084
  | state=S schedstat=( 530094727 210277810 1460 ) utm=36 stm=17 core=1
  at java.lang.Object.wait(Native Method)
  - waiting on <0x41788f70> (a java.lang.VMThread) held by tid=1 (main)
  at java.lang.Thread.parkFor(Thread.java:1205)
  at sun.misc.Unsafe.park(Unsafe.java:325)
  at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:197)
  at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:735)
  at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:397)
  at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:905)
  at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:586)
  at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
  at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31)
  at android.database.sqlite.SQLiteDatabase.compileStatement(SQLiteDatabase.java:1126)
...
  at android.os.Looper.loop(Looper.java:136)

Upvotes: 0

Views: 3531

Answers (1)

Mooing Duck
Mooing Duck

Reputation: 66981

I believe this is what gets displayed when a thread calls wait() on a monitor, which makes it wait for some other thread to call notify() on that monitor, which will let it continue. These "monitor" APIs are a different way of using locks. They're used when multiple threads want to synchronize on an object, but they need to wait for a certain state.

The actual implementation is weird: The first thread locks the monitor, and then, while the current state is "wrong", it calls wait(). Java releases the lock, and blocks the thread. Later, a second thread will lock the same monitor, change the state, and call notify(), which tells Java to unblock a thread waiting on this monitor and make it try to re-lock the object. Immediately after, the second thread releases the lock on the monitor, and the first thread will relock the monitor, and recheck if the current state is still "wrong". I honestly have no idea why Java and other languages require the lock to be held while calling wait/notify.

In your case, the main thread is waiting for "the number of available connections" to be bigger than zero. It can't just hold the lock until the condition is met, because that would prevent other threads from finishing and releasing their connection. So instead, the main thread is waiting on a monitor. Another thread is currently using the database, and when it's done, it'll release the connection and increment "the number of available connections", and then notify the UI thread that it can now continue.


As a note: don't touch the database in your main thread. Don't touch disk at all in the main thread. To the best of your ability, avoid doing any work in the main thread at all.

Upvotes: 0

Related Questions