Merge "Fix CancellationSignal deadlock." into jb-dev
This commit is contained in:
@@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user