Утечка дескрипторов файлов трассировки Android в производстве - PullRequest
1 голос
/ 18 октября 2019

Иногда у наших пользователей происходит сбой приложения из-за внезапной недоступности базы данных:

1) android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (code 14) 

или

2) java.lang.RuntimeException: Could not read input channel file descriptors from parcel.

или

3) android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=1 (# cursors opened by this proc=1)

И я заметил, чтов этот момент я даже не могу извлечь logcat:

07-17 16:57:20.722 E/APP ( 2301): 2019-07-17_16:57:20.598+0300 [FileUtils] logcat extraction error
07-17 16:57:20.722 E/APP ( 2301): java.io.IOException: Error running exec(). Command: [logcat, -d, -v, time] Working Directory: null Environment: null
07-17 16:57:20.722 E/APP ( 2301):   at java.lang.ProcessManager.exec(ProcessManager.java:211)
07-17 16:57:20.722 E/APP ( 2301):   at java.lang.Runtime.exec(Runtime.java:174)
07-17 16:57:20.722 E/APP ( 2301):   at java.lang.Runtime.exec(Runtime.java:247)
07-17 16:57:20.722 E/APP ( 2301):   at java.lang.Runtime.exec(Runtime.java:190)
....
07-17 16:57:20.722 E/APP ( 2301): Caused by: java.io.IOException: Too many open files
07-17 16:57:20.722 E/APP ( 2301):   at java.lang.ProcessManager.exec(Native Method)
07-17 16:57:20.722 E/APP ( 2301):   at java.lang.ProcessManager.exec(ProcessManager.java:209)
07-17 16:57:20.722 E/APP ( 2301):   ... 8 more

Итак, у меня возникает вопрос - можно ли получить во время выполнения (на устройстве пользователя) список всех открытых файловых дескрипторов в моем процессе(и вытяните его, например, в logcat)?

PS Мне не удалось запустить процесс записи logcat в файл. Но работает дамп файла app.hprof ... Хотя я не нашел здесь ничего особенного.

Ответы [ 3 ]

0 голосов
/ 19 октября 2019

Вы можете читать и выводить (я предпочитаю Crashlytics для такого рода отчетов) список дескрипторов открытых файлов из /proc/self/fd.

0 голосов
/ 06 ноября 2019

Следуя совету @Alex Cohn, я написал код, который хранит

/proc/self/fd

информацию в кеше

import android.support.v4.util.LruCache;
import android.text.format.DateUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.Map;

public class CrashlyticsUtils {
    private static final String TAG = "Crashlytics";
    private static final LruCache<java.util.Date, String> crashlytics = new LruCache<>(20);
    private static final long INTERVAL = 10 * DateUtils.MINUTE_IN_MILLIS;
    private static long lastTime = 0;

    private static String extractShellCommand(String command) {
        Process process = null;
        BufferedReader bufferedReader = null;
        try {
            process = Runtime.getRuntime().exec(command);
            bufferedReader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));

            StringBuilder log=new StringBuilder();
            String line;
            log.append(String.format("Executing a command '%s'", command)).append("\n");
            while ((line = bufferedReader.readLine()) != null) {
                log.append(line).append("\n");
            }

            return log.toString();

        }

        catch (IOException e) {
            Log.e(TAG, "logcat extraction error", e);
            return "";
        }
        finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    Log.e(TAG, "logcat reading error", e);
                }
            }
            if (process != null) {
                process.destroy();
            }
        }
    }

    private static void addLogNow(String log) {
        Date now = now();
        if (now.getTime() > lastTime + INTERVAL) {
            crashlytics.put(now, log);
        }
    }

    public static void collectSomeLogs() {
        addLogNow( extractShellCommand("ls -l /proc/self/fd") );
        addLogNow( extractShellCommand("lsof") );
        waitSomeTimeout();
    }

    public static void waitSomeTimeout() {
        lastTime = now().getTime();
    }

    public static void printCrashlyticsInfo() {
        Map<Date, String> snapshot = crashlytics.snapshot();
        for (Map.Entry<Date, String> entry : snapshot.entrySet()) {
            Log.d(TAG, String.format("record from %s", entry.getKey()));
            String[] strings = entry.getValue().split("\n");
            for (String line : strings) {
                Log.d(TAG, line);
            }
        }
    }

    private static Date now() {
        return new java.util.Date();
    }
}

Так что я пишу звонки среди моего проекта

CrashlyticsUtils.collectSomeLogs ()

, а затем (при возникновении необработанного исключения) записывать журналы в logcat

CrashlyticsUtils.printCrashlyticsInfo ()

(вы можете написать в методе печати все, что пожелаете)

0 голосов
/ 18 октября 2019

Не ответ на точный заданный вопрос, но тот, который может помочь вам решить проблему: убедитесь, что все ваши файловые дескрипторы обернуты в типы RAII, чтобы вы не могли забыть закрыть их. В Android мы используем unique_fd для этого: https://android.googlesource.com/platform/system/core/+/master/base/include/android-base/unique_fd.h.

Это, конечно, помогает только с подмножеством утечек. Это не поможет, если проблема в том, что вы используете слишком много файловых дескрипторов одновременно, но это поможет вам в более распространенном случае - просто забыть закрыть файловый дескриптор.

...