Проблема ext4/fsync неясна в Android (Java)

Тим Брей статья "Сохранение данных безопасно" оставила мне открытые вопросы. Сегодня, это в течение месяца, и я не видел никаких последующих действий, поэтому я решил обратиться к этой теме здесь.

Одна из целей статьи заключается в том, что FileDescriptor.sync() следует вызывать, чтобы быть в безопасности при использовании FileOutputStream. Сначала я был очень раздражен, потому что я никогда не видел, чтобы какой-либо Java-код выполнял синхронизацию в течение 12 лет, когда я занимаюсь Java. Тем более, что справляться с файлами - довольно простая вещь. Кроме того, стандартный JavaDoc FileOutputStream никогда не намекал на синхронизацию (Java 1.0 - 6). После некоторых исследований я решил, что ext4 может быть первой основной файловой системой, требующей синхронизации. (Существуют ли другие файловые системы, в которых рекомендуется явная синхронизация?)

Я ценю некоторые общие мысли по этому вопросу, но у меня также есть некоторые конкретные вопросы:

  • Когда Android выполнит синхронизацию с файловой системой? Это может быть периодическим и дополнительно основано на событиях жизненного цикла (например, процесс приложения переходит в фоновый режим).
  • Помогает ли FileDescriptor.sync() синхронизировать метаданные? Это синхронизация каталога измененного файла. Сравните с FileChannel.force().
  • Обычно, файл напрямую не записывается в FileOutputStream. Здесь мое решение (вы согласны?):
    
    FileOutputStream fileOut = ctx.openFileOutput(file, Context.MODE_PRIVATE);
    BufferedOutputStream out = new BufferedOutputStream(fileOut);
    try {
        out.write(something);
        out.flush();
        fileOut.getFD().sync();
    } finally {
        out.close();
    }
    

Ответы

Ответ 1

Android будет выполнять синхронизацию, когда это необходимо - например, когда экран выключается, выключение устройства и т.д. Если вы просто смотрите на "нормальную" операцию, явная синхронизация приложения никогда не требуется.

Проблема возникает, когда пользователь вытаскивает батарею из своего устройства (или делает жесткие reset ядра), и вы хотите, чтобы вы не потеряли никаких данных.

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

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

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

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

Однако теперь, если вы пишете новый файл, а затем удалите старый, и батарея вытащится, при следующей загрузке вы увидите, что старый файл удален, а новый файл создан, но содержимое нового файла не полный. Выполняя синхронизацию, вы гарантируете, что новый файл будет полностью написан в этой точке, так что вы можете делать дальнейшие изменения (например, удаление старого файла), которые зависят от этого состояния.

Ответ 2

fileOut.getFD().sync(); должен находиться в предложении finally перед close().

sync() является более важным, чем close(), учитывая долговечность.

Итак, каждый раз, когда вы хотите "закончить" работу над файлом, вы должны sync() его перед close() ing.

posix не гарантирует, что ожидающие записи будут записаны на диск при выпуске close().