Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aeee2f5d authored by Jeff Brown's avatar Jeff Brown
Browse files

Fix CancellationSignal deadlock.

Bug: 6398945
Change-Id: I5a3d4dce74009d6f1d284a8cc54c9f68daa6a9c2
parent 20c4f87b
Loading
Loading
Loading
Loading
+61 −22
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ public final class CancellationSignal {
    private boolean mIsCanceled;
    private OnCancelListener mOnCancelListener;
    private ICancellationSignal mRemote;
    private boolean mCancelInProgress;

    /**
     * Creates a cancellation signal, initially not canceled.
@@ -59,18 +60,32 @@ public final class CancellationSignal {
     * If the operation has not yet started, then it will be canceled as soon as it does.
     */
    public void cancel() {
        final OnCancelListener listener;
        final ICancellationSignal remote;
        synchronized (this) {
            if (!mIsCanceled) {
            if (mIsCanceled) {
                return;
            }
            mIsCanceled = true;
                if (mOnCancelListener != null) {
                    mOnCancelListener.onCancel();
            mCancelInProgress = true;
            listener = mOnCancelListener;
            remote = mRemote;
        }

        try {
            if (listener != null) {
                listener.onCancel();
            }
                if (mRemote != null) {
            if (remote != null) {
                try {
                        mRemote.cancel();
                    remote.cancel();
                } catch (RemoteException ex) {
                }
            }
        } finally {
            synchronized (this) {
                mCancelInProgress = false;
                notifyAll();
            }
        }
    }
@@ -86,39 +101,63 @@ public final class CancellationSignal {
     * If {@link CancellationSignal#cancel} has already been called, then the provided
     * listener is invoked immediately.
     *
     * The listener is called while holding the cancellation signal's lock which is
     * also held while registering or unregistering the listener.  Because of the lock,
     * 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.
     * This method is guaranteed that the listener will not be called after it
     * has been removed.
     *
     * @param listener The cancellation listener, or null to remove the current listener.
     */
    public void setOnCancelListener(OnCancelListener listener) {
        synchronized (this) {
            waitForCancelFinishedLocked();

            if (mOnCancelListener == listener) {
                return;
            }
            mOnCancelListener = listener;
            if (mIsCanceled && listener != null) {
                listener.onCancel();
            if (!mIsCanceled || listener == null) {
                return;
            }
        }
        listener.onCancel();
    }

    /**
     * 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.
     *
     * @hide
     */
    public void setRemote(ICancellationSignal remote) {
        synchronized (this) {
            waitForCancelFinishedLocked();

            if (mRemote == remote) {
                return;
            }
            mRemote = remote;
            if (mIsCanceled && remote != null) {
            if (!mIsCanceled || remote == null) {
                return;
            }
        }
        try {
            remote.cancel();
        } catch (RemoteException ex) {
        }
    }

    private void waitForCancelFinishedLocked() {
        while (mCancelInProgress) {
            try {
                wait();
            } catch (InterruptedException ex) {
            }
        }
    }

+55 −52
Original line number Diff line number Diff line
@@ -594,6 +594,7 @@ public final class SQLiteConnectionPool implements Closeable {
                (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;

        final ConnectionWaiter waiter;
        final int nonce;
        synchronized (mLock) {
            throwIfClosedLocked();

@@ -636,19 +637,23 @@ public final class SQLiteConnectionPool implements Closeable {
                mConnectionWaiterQueue = waiter;
            }

            nonce = waiter.mNonce;
        }

        // Set up the cancellation listener.
        if (cancellationSignal != null) {
                final int nonce = waiter.mNonce;
            cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
                @Override
                public void onCancel() {
                    synchronized (mLock) {
                            cancelConnectionWaiterLocked(waiter, nonce);
                        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;
@@ -674,9 +679,6 @@ public final class SQLiteConnectionPool implements Closeable {
                    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;
@@ -694,15 +696,16 @@ public final class SQLiteConnectionPool implements Closeable {
                    }
                }
            }
        } finally {
            // Remove the cancellation listener.
            if (cancellationSignal != null) {
                cancellationSignal.setOnCancelListener(null);
            }
        }

    // Can't throw.
    private void cancelConnectionWaiterLocked(ConnectionWaiter waiter, int nonce) {
        if (waiter.mNonce != nonce) {
            // Waiter already removed and recycled.
            return;
    }

    // Can't throw.
    private void cancelConnectionWaiterLocked(ConnectionWaiter waiter) {
        if (waiter.mAssignedConnection != null || waiter.mException != null) {
            // Waiter is done waiting but has not woken up yet.
            return;