Merge "Fix CancellationSignal deadlock." into jb-dev

This commit is contained in:
Jeff Brown
2012-04-27 16:44:23 -07:00
committed by Android (Google) Code Review
2 changed files with 121 additions and 79 deletions

View File

@@ -25,6 +25,7 @@ public final class CancellationSignal {
private boolean mIsCanceled; private boolean mIsCanceled;
private OnCancelListener mOnCancelListener; private OnCancelListener mOnCancelListener;
private ICancellationSignal mRemote; private ICancellationSignal mRemote;
private boolean mCancelInProgress;
/** /**
* Creates a cancellation signal, initially not canceled. * Creates a cancellation signal, initially not canceled.
@@ -59,19 +60,33 @@ public final class CancellationSignal {
* If the operation has not yet started, then it will be canceled as soon as it does. * If the operation has not yet started, then it will be canceled as soon as it does.
*/ */
public void cancel() { public void cancel() {
final OnCancelListener listener;
final ICancellationSignal remote;
synchronized (this) { synchronized (this) {
if (!mIsCanceled) { if (mIsCanceled) {
mIsCanceled = true; return;
if (mOnCancelListener != null) { }
mOnCancelListener.onCancel(); mIsCanceled = true;
} mCancelInProgress = true;
if (mRemote != null) { listener = mOnCancelListener;
try { remote = mRemote;
mRemote.cancel(); }
} catch (RemoteException ex) {
} try {
if (listener != null) {
listener.onCancel();
}
if (remote != null) {
try {
remote.cancel();
} catch (RemoteException ex) {
} }
} }
} finally {
synchronized (this) {
mCancelInProgress = false;
notifyAll();
}
} }
} }
@@ -86,38 +101,62 @@ public final class CancellationSignal {
* If {@link CancellationSignal#cancel} has already been called, then the provided * If {@link CancellationSignal#cancel} has already been called, then the provided
* listener is invoked immediately. * listener is invoked immediately.
* *
* The listener is called while holding the cancellation signal's lock which is * This method is guaranteed that the listener will not be called after it
* also held while registering or unregistering the listener. Because of the lock, * has been removed.
* it is not possible for the listener to run after it has been unregistered.
* This design choice makes it easier for clients of {@link CancellationSignal} to
* prevent race conditions related to listener registration and unregistration.
* *
* @param listener The cancellation listener, or null to remove the current listener. * @param listener The cancellation listener, or null to remove the current listener.
*/ */
public void setOnCancelListener(OnCancelListener listener) { public void setOnCancelListener(OnCancelListener listener) {
synchronized (this) { synchronized (this) {
waitForCancelFinishedLocked();
if (mOnCancelListener == listener) {
return;
}
mOnCancelListener = listener; mOnCancelListener = listener;
if (mIsCanceled && listener != null) { if (!mIsCanceled || listener == null) {
listener.onCancel(); return;
} }
} }
listener.onCancel();
} }
/** /**
* Sets the remote transport. * Sets the remote transport.
* *
* If {@link CancellationSignal#cancel} has already been called, then the provided
* remote transport is canceled immediately.
*
* This method is guaranteed that the remote transport will not be called after it
* has been removed.
*
* @param remote The remote transport, or null to remove. * @param remote The remote transport, or null to remove.
* *
* @hide * @hide
*/ */
public void setRemote(ICancellationSignal remote) { public void setRemote(ICancellationSignal remote) {
synchronized (this) { synchronized (this) {
waitForCancelFinishedLocked();
if (mRemote == remote) {
return;
}
mRemote = remote; mRemote = remote;
if (mIsCanceled && remote != null) { if (!mIsCanceled || remote == null) {
try { return;
remote.cancel(); }
} catch (RemoteException ex) { }
} try {
remote.cancel();
} catch (RemoteException ex) {
}
}
private void waitForCancelFinishedLocked() {
while (mCancelInProgress) {
try {
wait();
} catch (InterruptedException ex) {
} }
} }
} }

View File

@@ -594,6 +594,7 @@ public final class SQLiteConnectionPool implements Closeable {
(connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0; (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
final ConnectionWaiter waiter; final ConnectionWaiter waiter;
final int nonce;
synchronized (mLock) { synchronized (mLock) {
throwIfClosedLocked(); throwIfClosedLocked();
@@ -636,73 +637,75 @@ public final class SQLiteConnectionPool implements Closeable {
mConnectionWaiterQueue = waiter; mConnectionWaiterQueue = waiter;
} }
if (cancellationSignal != null) { nonce = waiter.mNonce;
final int nonce = waiter.mNonce;
cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
@Override
public void onCancel() {
synchronized (mLock) {
cancelConnectionWaiterLocked(waiter, nonce);
}
}
});
}
} }
// Park the thread until a connection is assigned or the pool is closed. // Set up the cancellation listener.
// Rethrow an exception from the wait, if we got one. if (cancellationSignal != null) {
long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS; cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis; @Override
for (;;) { public void onCancel() {
// Detect and recover from connection leaks. synchronized (mLock) {
if (mConnectionLeaked.compareAndSet(true, false)) { if (waiter.mNonce == nonce) {
cancelConnectionWaiterLocked(waiter);
}
}
}
});
}
try {
// Park the thread until a connection is assigned or the pool is closed.
// Rethrow an exception from the wait, if we got one.
long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
for (;;) {
// Detect and recover from connection leaks.
if (mConnectionLeaked.compareAndSet(true, false)) {
synchronized (mLock) {
wakeConnectionWaitersLocked();
}
}
// Wait to be unparked (may already have happened), a timeout, or interruption.
LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
// Clear the interrupted flag, just in case.
Thread.interrupted();
// Check whether we are done waiting yet.
synchronized (mLock) { synchronized (mLock) {
wakeConnectionWaitersLocked(); throwIfClosedLocked();
final SQLiteConnection connection = waiter.mAssignedConnection;
final RuntimeException ex = waiter.mException;
if (connection != null || ex != null) {
recycleConnectionWaiterLocked(waiter);
if (connection != null) {
return connection;
}
throw ex; // rethrow!
}
final long now = SystemClock.uptimeMillis();
if (now < nextBusyTimeoutTime) {
busyTimeoutMillis = now - nextBusyTimeoutTime;
} else {
logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
nextBusyTimeoutTime = now + busyTimeoutMillis;
}
} }
} }
} finally {
// Wait to be unparked (may already have happened), a timeout, or interruption. // Remove the cancellation listener.
LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L); if (cancellationSignal != null) {
cancellationSignal.setOnCancelListener(null);
// Clear the interrupted flag, just in case.
Thread.interrupted();
// Check whether we are done waiting yet.
synchronized (mLock) {
throwIfClosedLocked();
final SQLiteConnection connection = waiter.mAssignedConnection;
final RuntimeException ex = waiter.mException;
if (connection != null || ex != null) {
if (cancellationSignal != null) {
cancellationSignal.setOnCancelListener(null);
}
recycleConnectionWaiterLocked(waiter);
if (connection != null) {
return connection;
}
throw ex; // rethrow!
}
final long now = SystemClock.uptimeMillis();
if (now < nextBusyTimeoutTime) {
busyTimeoutMillis = now - nextBusyTimeoutTime;
} else {
logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
nextBusyTimeoutTime = now + busyTimeoutMillis;
}
} }
} }
} }
// Can't throw. // Can't throw.
private void cancelConnectionWaiterLocked(ConnectionWaiter waiter, int nonce) { private void cancelConnectionWaiterLocked(ConnectionWaiter waiter) {
if (waiter.mNonce != nonce) {
// Waiter already removed and recycled.
return;
}
if (waiter.mAssignedConnection != null || waiter.mException != null) { if (waiter.mAssignedConnection != null || waiter.mException != null) {
// Waiter is done waiting but has not woken up yet. // Waiter is done waiting but has not woken up yet.
return; return;