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

Commit 6141e13f authored by Vasu Nori's avatar Vasu Nori
Browse files

when cursorwindow allocation fails, print the number of cursors left open

the reason for bug:3281533, bug:3127159 is probably too many cursors are left
un-closed in the process.
print the info on the number of cursors left open when the exception
"cursorwindow allocation failed" occurs.
This should help us figure out if that indeed is the reason
and which process is leaving the cursors open.

Change-Id: I4b46be63f5dfbe9b102ad7a9cf9dd21e70f71e14
parent 4887804e
Loading
Loading
Loading
Loading
+83 −8
Original line number Diff line number Diff line
@@ -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.
     */
@@ -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;
@@ -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());
        }
    }

    /**
@@ -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;
    }
}
+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);
    }
}
+13 −19
Original line number Diff line number Diff line
@@ -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;
@@ -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)
@@ -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},
+32 −0
Original line number Diff line number Diff line
@@ -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;

@@ -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();
        }
    }
}