Skip to content

Commit 263d044

Browse files
Jeff BrownAndroid (Google) Code Review
authored andcommitted
Merge "Fix CancellationSignal deadlock." into jb-dev
2 parents 5c55c40 + aeee2f5 commit 263d044

File tree

2 files changed

+116
-74
lines changed

2 files changed

+116
-74
lines changed

core/java/android/content/CancellationSignal.java

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public final class CancellationSignal {
2525
private boolean mIsCanceled;
2626
private OnCancelListener mOnCancelListener;
2727
private ICancellationSignal mRemote;
28+
private boolean mCancelInProgress;
2829

2930
/**
3031
* Creates a cancellation signal, initially not canceled.
@@ -59,19 +60,33 @@ public void throwIfCanceled() {
5960
* If the operation has not yet started, then it will be canceled as soon as it does.
6061
*/
6162
public void cancel() {
63+
final OnCancelListener listener;
64+
final ICancellationSignal remote;
6265
synchronized (this) {
63-
if (!mIsCanceled) {
64-
mIsCanceled = true;
65-
if (mOnCancelListener != null) {
66-
mOnCancelListener.onCancel();
67-
}
68-
if (mRemote != null) {
69-
try {
70-
mRemote.cancel();
71-
} catch (RemoteException ex) {
72-
}
66+
if (mIsCanceled) {
67+
return;
68+
}
69+
mIsCanceled = true;
70+
mCancelInProgress = true;
71+
listener = mOnCancelListener;
72+
remote = mRemote;
73+
}
74+
75+
try {
76+
if (listener != null) {
77+
listener.onCancel();
78+
}
79+
if (remote != null) {
80+
try {
81+
remote.cancel();
82+
} catch (RemoteException ex) {
7383
}
7484
}
85+
} finally {
86+
synchronized (this) {
87+
mCancelInProgress = false;
88+
notifyAll();
89+
}
7590
}
7691
}
7792

@@ -86,38 +101,62 @@ public void cancel() {
86101
* If {@link CancellationSignal#cancel} has already been called, then the provided
87102
* listener is invoked immediately.
88103
*
89-
* The listener is called while holding the cancellation signal's lock which is
90-
* also held while registering or unregistering the listener. Because of the lock,
91-
* it is not possible for the listener to run after it has been unregistered.
92-
* This design choice makes it easier for clients of {@link CancellationSignal} to
93-
* prevent race conditions related to listener registration and unregistration.
104+
* This method is guaranteed that the listener will not be called after it
105+
* has been removed.
94106
*
95107
* @param listener The cancellation listener, or null to remove the current listener.
96108
*/
97109
public void setOnCancelListener(OnCancelListener listener) {
98110
synchronized (this) {
111+
waitForCancelFinishedLocked();
112+
113+
if (mOnCancelListener == listener) {
114+
return;
115+
}
99116
mOnCancelListener = listener;
100-
if (mIsCanceled && listener != null) {
101-
listener.onCancel();
117+
if (!mIsCanceled || listener == null) {
118+
return;
102119
}
103120
}
121+
listener.onCancel();
104122
}
105123

106124
/**
107125
* Sets the remote transport.
108126
*
127+
* If {@link CancellationSignal#cancel} has already been called, then the provided
128+
* remote transport is canceled immediately.
129+
*
130+
* This method is guaranteed that the remote transport will not be called after it
131+
* has been removed.
132+
*
109133
* @param remote The remote transport, or null to remove.
110134
*
111135
* @hide
112136
*/
113137
public void setRemote(ICancellationSignal remote) {
114138
synchronized (this) {
139+
waitForCancelFinishedLocked();
140+
141+
if (mRemote == remote) {
142+
return;
143+
}
115144
mRemote = remote;
116-
if (mIsCanceled && remote != null) {
117-
try {
118-
remote.cancel();
119-
} catch (RemoteException ex) {
120-
}
145+
if (!mIsCanceled || remote == null) {
146+
return;
147+
}
148+
}
149+
try {
150+
remote.cancel();
151+
} catch (RemoteException ex) {
152+
}
153+
}
154+
155+
private void waitForCancelFinishedLocked() {
156+
while (mCancelInProgress) {
157+
try {
158+
wait();
159+
} catch (InterruptedException ex) {
121160
}
122161
}
123162
}

core/java/android/database/sqlite/SQLiteConnectionPool.java

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ private SQLiteConnection waitForConnection(String sql, int connectionFlags,
594594
(connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
595595

596596
final ConnectionWaiter waiter;
597+
final int nonce;
597598
synchronized (mLock) {
598599
throwIfClosedLocked();
599600

@@ -636,73 +637,75 @@ private SQLiteConnection waitForConnection(String sql, int connectionFlags,
636637
mConnectionWaiterQueue = waiter;
637638
}
638639

639-
if (cancellationSignal != null) {
640-
final int nonce = waiter.mNonce;
641-
cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
642-
@Override
643-
public void onCancel() {
644-
synchronized (mLock) {
645-
cancelConnectionWaiterLocked(waiter, nonce);
640+
nonce = waiter.mNonce;
641+
}
642+
643+
// Set up the cancellation listener.
644+
if (cancellationSignal != null) {
645+
cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
646+
@Override
647+
public void onCancel() {
648+
synchronized (mLock) {
649+
if (waiter.mNonce == nonce) {
650+
cancelConnectionWaiterLocked(waiter);
646651
}
647652
}
648-
});
649-
}
653+
}
654+
});
650655
}
651-
652-
// Park the thread until a connection is assigned or the pool is closed.
653-
// Rethrow an exception from the wait, if we got one.
654-
long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
655-
long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
656-
for (;;) {
657-
// Detect and recover from connection leaks.
658-
if (mConnectionLeaked.compareAndSet(true, false)) {
659-
synchronized (mLock) {
660-
wakeConnectionWaitersLocked();
656+
try {
657+
// Park the thread until a connection is assigned or the pool is closed.
658+
// Rethrow an exception from the wait, if we got one.
659+
long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
660+
long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
661+
for (;;) {
662+
// Detect and recover from connection leaks.
663+
if (mConnectionLeaked.compareAndSet(true, false)) {
664+
synchronized (mLock) {
665+
wakeConnectionWaitersLocked();
666+
}
661667
}
662-
}
663668

664-
// Wait to be unparked (may already have happened), a timeout, or interruption.
665-
LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
669+
// Wait to be unparked (may already have happened), a timeout, or interruption.
670+
LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
666671

667-
// Clear the interrupted flag, just in case.
668-
Thread.interrupted();
672+
// Clear the interrupted flag, just in case.
673+
Thread.interrupted();
669674

670-
// Check whether we are done waiting yet.
671-
synchronized (mLock) {
672-
throwIfClosedLocked();
673-
674-
final SQLiteConnection connection = waiter.mAssignedConnection;
675-
final RuntimeException ex = waiter.mException;
676-
if (connection != null || ex != null) {
677-
if (cancellationSignal != null) {
678-
cancellationSignal.setOnCancelListener(null);
679-
}
680-
recycleConnectionWaiterLocked(waiter);
681-
if (connection != null) {
682-
return connection;
675+
// Check whether we are done waiting yet.
676+
synchronized (mLock) {
677+
throwIfClosedLocked();
678+
679+
final SQLiteConnection connection = waiter.mAssignedConnection;
680+
final RuntimeException ex = waiter.mException;
681+
if (connection != null || ex != null) {
682+
recycleConnectionWaiterLocked(waiter);
683+
if (connection != null) {
684+
return connection;
685+
}
686+
throw ex; // rethrow!
683687
}
684-
throw ex; // rethrow!
685-
}
686688

687-
final long now = SystemClock.uptimeMillis();
688-
if (now < nextBusyTimeoutTime) {
689-
busyTimeoutMillis = now - nextBusyTimeoutTime;
690-
} else {
691-
logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
692-
busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
693-
nextBusyTimeoutTime = now + busyTimeoutMillis;
689+
final long now = SystemClock.uptimeMillis();
690+
if (now < nextBusyTimeoutTime) {
691+
busyTimeoutMillis = now - nextBusyTimeoutTime;
692+
} else {
693+
logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
694+
busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
695+
nextBusyTimeoutTime = now + busyTimeoutMillis;
696+
}
694697
}
695698
}
699+
} finally {
700+
// Remove the cancellation listener.
701+
if (cancellationSignal != null) {
702+
cancellationSignal.setOnCancelListener(null);
703+
}
696704
}
697705
}
698706

699707
// Can't throw.
700-
private void cancelConnectionWaiterLocked(ConnectionWaiter waiter, int nonce) {
701-
if (waiter.mNonce != nonce) {
702-
// Waiter already removed and recycled.
703-
return;
704-
}
705-
708+
private void cancelConnectionWaiterLocked(ConnectionWaiter waiter) {
706709
if (waiter.mAssignedConnection != null || waiter.mException != null) {
707710
// Waiter is done waiting but has not woken up yet.
708711
return;

0 commit comments

Comments
 (0)