Loading core/java/android/database/CursorWindow.java +83 −8 Original line number Diff line number Diff line Loading @@ -18,14 +18,20 @@ package android.database; import android.content.res.Resources; import android.database.sqlite.SQLiteClosable; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.util.Log; import android.util.SparseIntArray; /** * A buffer containing multiple cursor rows. */ public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; /** The cursor window size. resource xml file specifies the value in kB. * convert it to bytes here by multiplying with 1024. */ Loading @@ -33,8 +39,9 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { Resources.getSystem().getInteger( com.android.internal.R.integer.config_cursorWindowSize) * 1024; /** The pointer to the native window class */ @SuppressWarnings("unused") /** The pointer to the native window class. set by the native methods in * android_database_CursorWindow.cpp */ private int nWindow; private int mStartPos; Loading @@ -46,7 +53,18 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { */ public CursorWindow(boolean localWindow) { mStartPos = 0; native_init(sCursorWindowSize, localWindow); int rslt = native_init(sCursorWindowSize, localWindow); printDebugMsgIfError(rslt); recordNewWindow(Binder.getCallingPid(), nWindow); } private void printDebugMsgIfError(int rslt) { if (rslt > 0) { // cursor window allocation failed. either low memory or too many cursors being open. // print info to help in debugging this. throw new CursorWindowAllocationException("Cursor Window allocation of " + sCursorWindowSize/1024 + " kb failed. " + printStats()); } } /** Loading Loading @@ -574,21 +592,78 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private CursorWindow(Parcel source) { IBinder nativeBinder = source.readStrongBinder(); mStartPos = source.readInt(); native_init(nativeBinder); int rslt = native_init(nativeBinder); printDebugMsgIfError(rslt); } /** Get the binder for the native side of the window */ private native IBinder native_getBinder(); /** Does the native side initialization for an empty window */ private native void native_init(int cursorWindowSize, boolean localOnly); private native int native_init(int cursorWindowSize, boolean localOnly); /** Does the native side initialization with an existing binder from another process */ private native void native_init(IBinder nativeBinder); private native int native_init(IBinder nativeBinder); @Override protected void onAllReferencesReleased() { int windowId = nWindow; recordClosingOfWindow(Binder.getCallingPid(), nWindow); close_native(); } private static final SparseIntArray sWindowToPidMap = new SparseIntArray(); private void recordNewWindow(int pid, int window) { synchronized (sWindowToPidMap) { sWindowToPidMap.put(window, pid); if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) { Log.i(STATS_TAG, "Created a new Cursor. " + printStats()); } } } private void recordClosingOfWindow(int pid, int window) { synchronized (sWindowToPidMap) { if (sWindowToPidMap.size() == 0) { // this means we are not in the ContentProvider. return; } sWindowToPidMap.delete(window); } } private String printStats() { StringBuilder buff = new StringBuilder(); int myPid = Process.myPid(); int total = 0; SparseIntArray pidCounts = new SparseIntArray(); synchronized (sWindowToPidMap) { int size = sWindowToPidMap.size(); if (size == 0) { // this means we are not in the ContentProvider. return ""; } for (int indx = 0; indx < size; indx++) { int pid = sWindowToPidMap.valueAt(indx); int value = pidCounts.get(pid); pidCounts.put(pid, ++value); } } int numPids = pidCounts.size(); for (int i = 0; i < numPids;i++) { buff.append(" (# cursors opened by "); int pid = pidCounts.keyAt(i); if (pid == myPid) { buff.append("this proc="); } else { buff.append("pid " + pid + "="); } int num = pidCounts.get(pid); buff.append(num + ")"); total += num; } // limit the returned string size to 1000 String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString(); return "# Open Cursors=" + total + s; } } core/java/android/database/CursorWindowAllocationException.java 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database; /** * This exception is thrown when a CursorWindow couldn't be allocated, * most probably due to memory not being available */ class CursorWindowAllocationException extends java.lang.RuntimeException { public CursorWindowAllocationException() { super(); } public CursorWindowAllocationException(String description) { super(description); } } core/jni/android_database_CursorWindow.cpp +13 −19 Original line number Diff line number Diff line Loading @@ -50,7 +50,7 @@ CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow) return GET_WINDOW(env, javaWindow); } static void native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize, static jint native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize, jboolean localOnly) { uint8_t * data; Loading @@ -59,44 +59,38 @@ static void native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSiz window = new CursorWindow(cursorWindowSize); if (!window) { jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object"); return; return 1; } if (!window->initBuffer(localOnly)) { jniThrowException(env, "java/lang/RuntimeException", "Memory couldn't be allocated for 1MB CursorWindow object."); delete window; return; return 1; } LOG_WINDOW("native_init_empty: window = %p", window); SET_WINDOW(env, object, window); return 0; } static void native_init_memory(JNIEnv * env, jobject object, jobject memObj) static jint native_init_memory(JNIEnv * env, jobject object, jobject memObj) { sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj)); if (memory == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder"); return; return 1; } CursorWindow * window = new CursorWindow(); if (!window) { jniThrowException(env, "java/lang/RuntimeException", "CursorWindow of size 1MB couldn't be created. No memory?"); return; return 1; } if (!window->setMemory(memory)) { jniThrowException(env, "java/lang/RuntimeException", "Memory couldn't be initialized for 1MB CursorWindow object."); delete window; return; return 1; } LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window); SET_WINDOW(env, object, window); return 0; } static jobject native_getBinder(JNIEnv * env, jobject object) Loading Loading @@ -615,8 +609,8 @@ static jint getType_native(JNIEnv* env, jobject object, jint row, jint column) static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"native_init", "(IZ)V", (void *)native_init_empty}, {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory}, {"native_init", "(IZ)I", (void *)native_init_empty}, {"native_init", "(Landroid/os/IBinder;)I", (void *)native_init_memory}, {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder}, {"native_clear", "()V", (void *)native_clear}, {"close_native", "()V", (void *)native_close}, Loading core/tests/coretests/src/android/database/DatabaseCursorTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.test.suitebuilder.annotation.Suppress; import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; Loading Loading @@ -581,4 +582,35 @@ public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTe c.deactivate(); c.requery(); } /** * sometimes CursorWindow creation fails due to non-availability of memory create * another CursorWindow object. One of the scenarios of its occurrence is when * there are too many CursorWindow objects already opened by the process. * This test is for that scenario. */ @LargeTest public void testCursorWindowFailureWhenTooManyCursorWindowsLeftOpen() { mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); mDatabase.execSQL("INSERT INTO test values(1, 'test');"); int N = 1024; ArrayList<Cursor> cursorList = new ArrayList<Cursor>(); // open many cursors until a failure occurs for (int i = 0; i < N; i++) { try { Cursor cursor = mDatabase.rawQuery("select * from test", null); cursor.getCount(); cursorList.add(cursor); } catch (CursorWindowAllocationException e) { // got the exception we wanted break; } catch (Exception e) { fail("unexpected exception: " + e.getMessage()); e.printStackTrace(); break; } } for (Cursor c : cursorList) { c.close(); } } } Loading
core/java/android/database/CursorWindow.java +83 −8 Original line number Diff line number Diff line Loading @@ -18,14 +18,20 @@ package android.database; import android.content.res.Resources; import android.database.sqlite.SQLiteClosable; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.util.Log; import android.util.SparseIntArray; /** * A buffer containing multiple cursor rows. */ public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; /** The cursor window size. resource xml file specifies the value in kB. * convert it to bytes here by multiplying with 1024. */ Loading @@ -33,8 +39,9 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { Resources.getSystem().getInteger( com.android.internal.R.integer.config_cursorWindowSize) * 1024; /** The pointer to the native window class */ @SuppressWarnings("unused") /** The pointer to the native window class. set by the native methods in * android_database_CursorWindow.cpp */ private int nWindow; private int mStartPos; Loading @@ -46,7 +53,18 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { */ public CursorWindow(boolean localWindow) { mStartPos = 0; native_init(sCursorWindowSize, localWindow); int rslt = native_init(sCursorWindowSize, localWindow); printDebugMsgIfError(rslt); recordNewWindow(Binder.getCallingPid(), nWindow); } private void printDebugMsgIfError(int rslt) { if (rslt > 0) { // cursor window allocation failed. either low memory or too many cursors being open. // print info to help in debugging this. throw new CursorWindowAllocationException("Cursor Window allocation of " + sCursorWindowSize/1024 + " kb failed. " + printStats()); } } /** Loading Loading @@ -574,21 +592,78 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private CursorWindow(Parcel source) { IBinder nativeBinder = source.readStrongBinder(); mStartPos = source.readInt(); native_init(nativeBinder); int rslt = native_init(nativeBinder); printDebugMsgIfError(rslt); } /** Get the binder for the native side of the window */ private native IBinder native_getBinder(); /** Does the native side initialization for an empty window */ private native void native_init(int cursorWindowSize, boolean localOnly); private native int native_init(int cursorWindowSize, boolean localOnly); /** Does the native side initialization with an existing binder from another process */ private native void native_init(IBinder nativeBinder); private native int native_init(IBinder nativeBinder); @Override protected void onAllReferencesReleased() { int windowId = nWindow; recordClosingOfWindow(Binder.getCallingPid(), nWindow); close_native(); } private static final SparseIntArray sWindowToPidMap = new SparseIntArray(); private void recordNewWindow(int pid, int window) { synchronized (sWindowToPidMap) { sWindowToPidMap.put(window, pid); if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) { Log.i(STATS_TAG, "Created a new Cursor. " + printStats()); } } } private void recordClosingOfWindow(int pid, int window) { synchronized (sWindowToPidMap) { if (sWindowToPidMap.size() == 0) { // this means we are not in the ContentProvider. return; } sWindowToPidMap.delete(window); } } private String printStats() { StringBuilder buff = new StringBuilder(); int myPid = Process.myPid(); int total = 0; SparseIntArray pidCounts = new SparseIntArray(); synchronized (sWindowToPidMap) { int size = sWindowToPidMap.size(); if (size == 0) { // this means we are not in the ContentProvider. return ""; } for (int indx = 0; indx < size; indx++) { int pid = sWindowToPidMap.valueAt(indx); int value = pidCounts.get(pid); pidCounts.put(pid, ++value); } } int numPids = pidCounts.size(); for (int i = 0; i < numPids;i++) { buff.append(" (# cursors opened by "); int pid = pidCounts.keyAt(i); if (pid == myPid) { buff.append("this proc="); } else { buff.append("pid " + pid + "="); } int num = pidCounts.get(pid); buff.append(num + ")"); total += num; } // limit the returned string size to 1000 String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString(); return "# Open Cursors=" + total + s; } }
core/java/android/database/CursorWindowAllocationException.java 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database; /** * This exception is thrown when a CursorWindow couldn't be allocated, * most probably due to memory not being available */ class CursorWindowAllocationException extends java.lang.RuntimeException { public CursorWindowAllocationException() { super(); } public CursorWindowAllocationException(String description) { super(description); } }
core/jni/android_database_CursorWindow.cpp +13 −19 Original line number Diff line number Diff line Loading @@ -50,7 +50,7 @@ CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow) return GET_WINDOW(env, javaWindow); } static void native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize, static jint native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize, jboolean localOnly) { uint8_t * data; Loading @@ -59,44 +59,38 @@ static void native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSiz window = new CursorWindow(cursorWindowSize); if (!window) { jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object"); return; return 1; } if (!window->initBuffer(localOnly)) { jniThrowException(env, "java/lang/RuntimeException", "Memory couldn't be allocated for 1MB CursorWindow object."); delete window; return; return 1; } LOG_WINDOW("native_init_empty: window = %p", window); SET_WINDOW(env, object, window); return 0; } static void native_init_memory(JNIEnv * env, jobject object, jobject memObj) static jint native_init_memory(JNIEnv * env, jobject object, jobject memObj) { sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj)); if (memory == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder"); return; return 1; } CursorWindow * window = new CursorWindow(); if (!window) { jniThrowException(env, "java/lang/RuntimeException", "CursorWindow of size 1MB couldn't be created. No memory?"); return; return 1; } if (!window->setMemory(memory)) { jniThrowException(env, "java/lang/RuntimeException", "Memory couldn't be initialized for 1MB CursorWindow object."); delete window; return; return 1; } LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window); SET_WINDOW(env, object, window); return 0; } static jobject native_getBinder(JNIEnv * env, jobject object) Loading Loading @@ -615,8 +609,8 @@ static jint getType_native(JNIEnv* env, jobject object, jint row, jint column) static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"native_init", "(IZ)V", (void *)native_init_empty}, {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory}, {"native_init", "(IZ)I", (void *)native_init_empty}, {"native_init", "(Landroid/os/IBinder;)I", (void *)native_init_memory}, {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder}, {"native_clear", "()V", (void *)native_clear}, {"close_native", "()V", (void *)native_close}, Loading
core/tests/coretests/src/android/database/DatabaseCursorTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.test.suitebuilder.annotation.Suppress; import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; Loading Loading @@ -581,4 +582,35 @@ public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTe c.deactivate(); c.requery(); } /** * sometimes CursorWindow creation fails due to non-availability of memory create * another CursorWindow object. One of the scenarios of its occurrence is when * there are too many CursorWindow objects already opened by the process. * This test is for that scenario. */ @LargeTest public void testCursorWindowFailureWhenTooManyCursorWindowsLeftOpen() { mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);"); mDatabase.execSQL("INSERT INTO test values(1, 'test');"); int N = 1024; ArrayList<Cursor> cursorList = new ArrayList<Cursor>(); // open many cursors until a failure occurs for (int i = 0; i < N; i++) { try { Cursor cursor = mDatabase.rawQuery("select * from test", null); cursor.getCount(); cursorList.add(cursor); } catch (CursorWindowAllocationException e) { // got the exception we wanted break; } catch (Exception e) { fail("unexpected exception: " + e.getMessage()); e.printStackTrace(); break; } } for (Cursor c : cursorList) { c.close(); } } }