1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-28 04:02:19 -05:00

MessageProvider: making sure exposed cursor gets dereferenced on close (and closed on finalization) to prevent memory leak

This commit is contained in:
Fiouz 2010-10-11 20:41:59 +00:00
parent e8fd9683e6
commit 9db44bf4a9

View File

@ -84,7 +84,7 @@ public class MessageProvider extends ContentProvider
String INCREMENT = "id"; String INCREMENT = "id";
} }
protected interface QueryHandler protected static interface QueryHandler
{ {
/** /**
* The path this instance is able to respond to. * The path this instance is able to respond to.
@ -451,272 +451,359 @@ public class MessageProvider extends ContentProvider
} }
} }
protected class ThrottlingQueryHandler implements QueryHandler /**
{ * Cursor wrapper that release a semaphore on close. Close is also triggered
* on {@link #finalize()}.
protected final class MonitoredCursor implements CrossProcessCursor */
protected static class MonitoredCursor implements CrossProcessCursor
{ {
/**
* The underlying cursor implementation that handles regular
* requests
*/
private CrossProcessCursor mCursor; private CrossProcessCursor mCursor;
/**
* Whether {@link #close()} was invoked
*/
private AtomicBoolean mClosed = new AtomicBoolean(false);
private Semaphore mSemaphore;
/**
* @param cursor
* Never <code>null</code>.
* @param semaphore
* The semaphore to release on close. Never
* <code>null</code>.
*/
protected MonitoredCursor(final CrossProcessCursor cursor, final Semaphore semaphore)
{
this.mCursor = cursor;
this.mSemaphore = semaphore;
}
/* (non-Javadoc)
*
* Close the underlying cursor and dereference it.
*
* @see android.database.Cursor#close()
*/
@Override @Override
public void close() public void close()
{ {
mCursor.close();
if (mClosed.compareAndSet(false, true)) if (mClosed.compareAndSet(false, true))
{ {
Log.d(K9.LOG_TAG, "Cursor closed, releasing semaphore"); mCursor.close();
Log.d(K9.LOG_TAG, "Cursor closed, null'ing & releasing semaphore");
mCursor = null;
mSemaphore.release(); mSemaphore.release();
} }
} }
@Override
public boolean isClosed()
{
return mClosed.get() || mCursor.isClosed();
}
/* (non-Javadoc)
*
* Making sure cursor gets closed on garbage collection
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable
{
close();
super.finalize();
}
protected void checkClosed() throws IllegalStateException
{
if (mClosed.get())
{
throw new IllegalStateException("Cursor was closed");
}
}
@Override @Override
public void fillWindow(int pos, CursorWindow winow) public void fillWindow(int pos, CursorWindow winow)
{ {
checkClosed();
mCursor.fillWindow(pos, winow); mCursor.fillWindow(pos, winow);
} }
@Override @Override
public CursorWindow getWindow() public CursorWindow getWindow()
{ {
checkClosed();
return mCursor.getWindow(); return mCursor.getWindow();
} }
@Override @Override
public boolean onMove(int oldPosition, int newPosition) public boolean onMove(int oldPosition, int newPosition)
{ {
checkClosed();
return mCursor.onMove(oldPosition, newPosition); return mCursor.onMove(oldPosition, newPosition);
} }
private AtomicBoolean mClosed = new AtomicBoolean(false);
protected MonitoredCursor(final CrossProcessCursor cursor)
{
this.mCursor = cursor;
}
@Override @Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)
{ {
checkClosed();
mCursor.copyStringToBuffer(columnIndex, buffer); mCursor.copyStringToBuffer(columnIndex, buffer);
} }
@Override @Override
public void deactivate() public void deactivate()
{ {
checkClosed();
mCursor.deactivate(); mCursor.deactivate();
} }
@Override @Override
public byte[] getBlob(int columnIndex) public byte[] getBlob(int columnIndex)
{ {
checkClosed();
return mCursor.getBlob(columnIndex); return mCursor.getBlob(columnIndex);
} }
@Override @Override
public int getColumnCount() public int getColumnCount()
{ {
checkClosed();
return mCursor.getColumnCount(); return mCursor.getColumnCount();
} }
@Override @Override
public int getColumnIndex(String columnName) public int getColumnIndex(String columnName)
{ {
checkClosed();
return mCursor.getColumnIndex(columnName); return mCursor.getColumnIndex(columnName);
} }
@Override @Override
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException
{ {
checkClosed();
return mCursor.getColumnIndexOrThrow(columnName); return mCursor.getColumnIndexOrThrow(columnName);
} }
@Override @Override
public String getColumnName(int columnIndex) public String getColumnName(int columnIndex)
{ {
checkClosed();
return mCursor.getColumnName(columnIndex); return mCursor.getColumnName(columnIndex);
} }
@Override @Override
public String[] getColumnNames() public String[] getColumnNames()
{ {
checkClosed();
return mCursor.getColumnNames(); return mCursor.getColumnNames();
} }
@Override @Override
public int getCount() public int getCount()
{ {
checkClosed();
return mCursor.getCount(); return mCursor.getCount();
} }
@Override @Override
public double getDouble(int columnIndex) public double getDouble(int columnIndex)
{ {
checkClosed();
return mCursor.getDouble(columnIndex); return mCursor.getDouble(columnIndex);
} }
@Override @Override
public Bundle getExtras() public Bundle getExtras()
{ {
checkClosed();
return mCursor.getExtras(); return mCursor.getExtras();
} }
@Override @Override
public float getFloat(int columnIndex) public float getFloat(int columnIndex)
{ {
checkClosed();
return mCursor.getFloat(columnIndex); return mCursor.getFloat(columnIndex);
} }
@Override @Override
public int getInt(int columnIndex) public int getInt(int columnIndex)
{ {
checkClosed();
return mCursor.getInt(columnIndex); return mCursor.getInt(columnIndex);
} }
@Override @Override
public long getLong(int columnIndex) public long getLong(int columnIndex)
{ {
checkClosed();
return mCursor.getLong(columnIndex); return mCursor.getLong(columnIndex);
} }
@Override @Override
public int getPosition() public int getPosition()
{ {
checkClosed();
return mCursor.getPosition(); return mCursor.getPosition();
} }
@Override @Override
public short getShort(int columnIndex) public short getShort(int columnIndex)
{ {
checkClosed();
return mCursor.getShort(columnIndex); return mCursor.getShort(columnIndex);
} }
@Override @Override
public String getString(int columnIndex) public String getString(int columnIndex)
{ {
checkClosed();
return mCursor.getString(columnIndex); return mCursor.getString(columnIndex);
} }
@Override @Override
public boolean getWantsAllOnMoveCalls() public boolean getWantsAllOnMoveCalls()
{ {
checkClosed();
return mCursor.getWantsAllOnMoveCalls(); return mCursor.getWantsAllOnMoveCalls();
} }
@Override @Override
public boolean isAfterLast() public boolean isAfterLast()
{ {
checkClosed();
return mCursor.isAfterLast(); return mCursor.isAfterLast();
} }
@Override @Override
public boolean isBeforeFirst() public boolean isBeforeFirst()
{ {
checkClosed();
return mCursor.isBeforeFirst(); return mCursor.isBeforeFirst();
} }
@Override
public boolean isClosed()
{
return mCursor.isClosed();
}
@Override @Override
public boolean isFirst() public boolean isFirst()
{ {
checkClosed();
return mCursor.isFirst(); return mCursor.isFirst();
} }
public boolean isLast() public boolean isLast()
{ {
checkClosed();
return mCursor.isLast(); return mCursor.isLast();
} }
@Override @Override
public boolean isNull(int columnIndex) public boolean isNull(int columnIndex)
{ {
checkClosed();
return mCursor.isNull(columnIndex); return mCursor.isNull(columnIndex);
} }
@Override @Override
public boolean move(int offset) public boolean move(int offset)
{ {
checkClosed();
return mCursor.move(offset); return mCursor.move(offset);
} }
@Override @Override
public boolean moveToFirst() public boolean moveToFirst()
{ {
checkClosed();
return mCursor.moveToFirst(); return mCursor.moveToFirst();
} }
@Override @Override
public boolean moveToLast() public boolean moveToLast()
{ {
checkClosed();
return mCursor.moveToLast(); return mCursor.moveToLast();
} }
@Override @Override
public boolean moveToNext() public boolean moveToNext()
{ {
checkClosed();
return mCursor.moveToNext(); return mCursor.moveToNext();
} }
@Override @Override
public boolean moveToPosition(int position) public boolean moveToPosition(int position)
{ {
checkClosed();
return mCursor.moveToPosition(position); return mCursor.moveToPosition(position);
} }
@Override @Override
public boolean moveToPrevious() public boolean moveToPrevious()
{ {
checkClosed();
return mCursor.moveToPrevious(); return mCursor.moveToPrevious();
} }
@Override @Override
public void registerContentObserver(ContentObserver observer) public void registerContentObserver(ContentObserver observer)
{ {
checkClosed();
mCursor.registerContentObserver(observer); mCursor.registerContentObserver(observer);
} }
@Override @Override
public void registerDataSetObserver(DataSetObserver observer) public void registerDataSetObserver(DataSetObserver observer)
{ {
checkClosed();
mCursor.registerDataSetObserver(observer); mCursor.registerDataSetObserver(observer);
} }
@Override @Override
public boolean requery() public boolean requery()
{ {
checkClosed();
return mCursor.requery(); return mCursor.requery();
} }
@Override @Override
public Bundle respond(Bundle extras) public Bundle respond(Bundle extras)
{ {
checkClosed();
return mCursor.respond(extras); return mCursor.respond(extras);
} }
@Override @Override
public void setNotificationUri(ContentResolver cr, Uri uri) public void setNotificationUri(ContentResolver cr, Uri uri)
{ {
checkClosed();
mCursor.setNotificationUri(cr, uri); mCursor.setNotificationUri(cr, uri);
} }
@Override @Override
public void unregisterContentObserver(ContentObserver observer) public void unregisterContentObserver(ContentObserver observer)
{ {
checkClosed();
mCursor.unregisterContentObserver(observer); mCursor.unregisterContentObserver(observer);
} }
@Override @Override
public void unregisterDataSetObserver(DataSetObserver observer) public void unregisterDataSetObserver(DataSetObserver observer)
{ {
checkClosed();
mCursor.unregisterDataSetObserver(observer); mCursor.unregisterDataSetObserver(observer);
} }
} }
protected class ThrottlingQueryHandler implements QueryHandler
{
private QueryHandler mDelegate; private QueryHandler mDelegate;
public ThrottlingQueryHandler(final QueryHandler delegate) public ThrottlingQueryHandler(final QueryHandler delegate)
@ -740,12 +827,13 @@ public class MessageProvider extends ContentProvider
cursor = mDelegate.query(uri, projection, selection, selectionArgs, sortOrder); cursor = mDelegate.query(uri, projection, selection, selectionArgs, sortOrder);
/* Android content resolvers can only process CrossProcessCursor instances */ /* Android content resolvers can only process CrossProcessCursor instances */
if (cursor == null || !(cursor instanceof CrossProcessCursor)) if (!(cursor instanceof CrossProcessCursor))
{ {
Log.w(K9.LOG_TAG, "Unsupported cursor, returning null: " + cursor);
return null; return null;
} }
final MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor); final MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor, mSemaphore);
/* use a weak reference not to actively prevent garbage collection */ /* use a weak reference not to actively prevent garbage collection */
final WeakReference<MonitoredCursor> weakReference = new WeakReference<MonitoredCursor>(wrapped); final WeakReference<MonitoredCursor> weakReference = new WeakReference<MonitoredCursor>(wrapped);
@ -758,7 +846,7 @@ public class MessageProvider extends ContentProvider
public void run() public void run()
{ {
final MonitoredCursor monitored = weakReference.get(); final MonitoredCursor monitored = weakReference.get();
if (monitored != null && monitored.mClosed.compareAndSet(false, true)) if (monitored != null && !monitored.isClosed())
{ {
Log.w(K9.LOG_TAG, "Forcibly closing remotely exposed cursor"); Log.w(K9.LOG_TAG, "Forcibly closing remotely exposed cursor");
try try
@ -865,6 +953,9 @@ public class MessageProvider extends ContentProvider
private MessageHelper mMessageHelper; private MessageHelper mMessageHelper;
/**
* How many simultaneous cursors we can affort to expose at once
*/
/* package */ /* package */
Semaphore mSemaphore = new Semaphore(1); Semaphore mSemaphore = new Semaphore(1);