Merge "Fix cursor window leak when query execution fails."

This commit is contained in:
Jeff Brown
2013-01-08 15:57:21 -08:00
committed by Android (Google) Code Review
6 changed files with 56 additions and 23 deletions

View File

@@ -78,7 +78,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
// So we treat this case as an unhandled exception. // So we treat this case as an unhandled exception.
throw ex; throw ex;
} }
if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)"); if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)", ex);
return null; return null;
} }
} }

View File

@@ -113,13 +113,21 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder, Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder,
cancellationSignal); cancellationSignal);
if (cursor != null) { if (cursor != null) {
CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( try {
cursor, observer, getProviderName()); CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor(); cursor, observer, getProviderName());
BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
cursor = null;
reply.writeNoException(); reply.writeNoException();
reply.writeInt(1); reply.writeInt(1);
d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} finally {
// Close cursor if an exception was thrown while constructing the adaptor.
if (cursor != null) {
cursor.close();
}
}
} else { } else {
reply.writeNoException(); reply.writeNoException();
reply.writeInt(0); reply.writeInt(0);

View File

@@ -381,6 +381,7 @@ public abstract class ContentResolver {
return null; return null;
} }
IContentProvider stableProvider = null; IContentProvider stableProvider = null;
Cursor qCursor = null;
try { try {
long startTime = SystemClock.uptimeMillis(); long startTime = SystemClock.uptimeMillis();
@@ -390,7 +391,6 @@ public abstract class ContentResolver {
remoteCancellationSignal = unstableProvider.createCancellationSignal(); remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal); cancellationSignal.setRemote(remoteCancellationSignal);
} }
Cursor qCursor;
try { try {
qCursor = unstableProvider.query(uri, projection, qCursor = unstableProvider.query(uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal); selection, selectionArgs, sortOrder, remoteCancellationSignal);
@@ -409,20 +409,26 @@ public abstract class ContentResolver {
if (qCursor == null) { if (qCursor == null) {
return null; return null;
} }
// force query execution
// Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount(); qCursor.getCount();
long durationMillis = SystemClock.uptimeMillis() - startTime; long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder); maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
// Wrap the cursor object into CursorWrapperInner object
// Wrap the cursor object into CursorWrapperInner object.
CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
stableProvider != null ? stableProvider : acquireProvider(uri)); stableProvider != null ? stableProvider : acquireProvider(uri));
stableProvider = null; stableProvider = null;
qCursor = null;
return wrapper; return wrapper;
} catch (RemoteException e) { } catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity // Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway. // Manager will kill this process shortly anyway.
return null; return null;
} finally { } finally {
if (qCursor != null) {
qCursor.close();
}
if (unstableProvider != null) { if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider); releaseUnstableProvider(unstableProvider);
} }

View File

@@ -65,9 +65,14 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection, Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal); mSelectionArgs, mSortOrder, mCancellationSignal);
if (cursor != null) { if (cursor != null) {
// Ensure the cursor window is filled try {
cursor.getCount(); // Ensure the cursor window is filled.
registerContentObserver(cursor, mObserver); cursor.getCount();
registerContentObserver(cursor, mObserver);
} catch (RuntimeException ex) {
cursor.close();
throw ex;
}
} }
return cursor; return cursor;
} finally { } finally {

View File

@@ -132,6 +132,11 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
} }
} }
/**
* Returns an object that contains sufficient metadata to reconstruct
* the cursor remotely. May throw if an error occurs when executing the query
* and obtaining the row count.
*/
public BulkCursorDescriptor getBulkCursorDescriptor() { public BulkCursorDescriptor getBulkCursorDescriptor() {
synchronized (mLock) { synchronized (mLock) {
throwIfCursorIsClosed(); throwIfCursorIsClosed();

View File

@@ -138,17 +138,26 @@ public class SQLiteCursor extends AbstractWindowedCursor {
private void fillWindow(int requiredPos) { private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath()); clearOrCreateWindow(getDatabase().getPath());
if (mCount == NO_COUNT) { try {
int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0); if (mCount == NO_COUNT) {
mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true); int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
mCursorWindowCapacity = mWindow.getNumRows(); mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
if (Log.isLoggable(TAG, Log.DEBUG)) { mCursorWindowCapacity = mWindow.getNumRows();
Log.d(TAG, "received count(*) from native_fill_window: " + mCount); if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
mCursorWindowCapacity);
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
} }
} else { } catch (RuntimeException ex) {
int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, // Close the cursor window if the query failed and therefore will
mCursorWindowCapacity); // not produce any results. This helps to avoid accidentally leaking
mQuery.fillWindow(mWindow, startPos, requiredPos, false); // the cursor window if the client does not correctly handle exceptions
// and fails to close the cursor.
closeWindow();
throw ex;
} }
} }