Использование дисковых транзакций
В этом разделе рассматриваются: fd.transactionstart, fd.transactioncommit, fd.transactionstarted, fd.transactioncapacityremaining.
Флеш-диск - капризный носитель. Многое может пойти не так, особенно если дисковая операция внезапно прервана из-за пропадания питания или по другой причине. Попробуйте в цикле дописывать данные в файл, отключая питание устройства в случайные моменты. Очень скоро файл, а то и сам диск окажется повреждённым.
Многим системам нужна надёжная работа с диском даже при случайных отключениях питания. Для таких систем есть дисковые транзакции!
Транзакции повышают надёжность приложения на двух уровнях:
- Уровень диска: внешне простые операции с диском часто влекут сложные внутренние изменения данных на диске. Потеря питания во время такой операции может повредить файлы и сам диск. Транзакции этого не допускают.
- Логический уровень: часто бывают групповые операции, которые нужно выполнить вместе (синхронно) или не выполнять вовсе. Например, вы ведёте файл с таблицей данных и файл с её индексом. Изменили первый - должен измениться и второй. Транзакции позволяют внести такие изменения одновременно и гарантируют, что изменены либо оба файла, либо ни один.
Транзакции имеет смысл применять только к операциям, изменяющим данные на диске. Это fd.create, fd.rename, fd.delete, fd.setattributes, fd.setdata, fd.setfilesize и fd.cutfromtop. Использовать транзакции с методами объекта fd, которые только читают данные, бессмысленно.
Выполнение дисковых операций в транзакциях
Транзакции возможны, только если флеш-диск отформатирован методом fd.formatj с аргументом maxjournalsectors > 1. Для хорошего выравнивания износа диска выбирайте этот параметр в диапазоне 50-100 и ни в коем случае не меньше 17 (объяснение здесь).
Чтобы начать дисковую транзакцию, используйте метод fd.transactionstart. Чтобы завершить транзакцию и зафиксировать (сохранить) все изменения на диске, вызовите fd.transactioncommit. Между этими двумя операторами размещайте любые операции, изменяющие данные на диске.
Пока транзакция выполняется, данные на диске не меняются. Выключите питание до выполнения fd.transactioncommit, снова включите его, смонтируйте диск - и всё будет выглядеть так, будто дисковые операции незавершённой транзакции никогда не выполнялись. Дело в том, что все изменённые секторы временно сохраняются в области журнала диска. Каждый сектор, изменённый в ходе транзакции, записывается в журнал, а не на своё прежнее место. Вызов fd.transactioncommit запускает копирование изменённых секторов из памяти журнала в их исходные места. Эту работу можно безопасно прервать. Если при фиксации изменений устройство теряет питание, следующий вызов fd.mount доведёт дело до конца!
Простой пример:
fd.transactionstart
fd.setdata("write some data to a file")
fd.transactioncommit
Может показаться, что fd.setdata - тривиальная операция, зачем же помещать её в транзакцию? На деле эта простая задача может изменить на диске до восьми (!) секторов. Если питание устройства пропадёт посреди записи в файл, этот файл и даже сам диск могут оказаться повреждёнными!
Вот другой пример, где меняются два файла. Заключив их в транзакцию, вы гарантируете, что изменены либо оба файла, либо ни один:
'we presume that there are two files opened 'on' file numbers 0 and 1
fd.transactionstart
fd.filenum=0
fd.setdata("write some data to the first file")
fd.filenum=1
fd.setdata("write some more to the second file")
fd.transactioncommit
Свойство fd.transactionstarted (только чтение) позволяет проверить, выполняется ли уже какая-либо дисковая транзакция. Выполнение fd.transactioncommit при fd.transactionstarted = 0 - NO вызывает нефатальную ошибку 13 - PL_FD_STATUS_TRANSACTION_NOT_YET_STARTED. Выполнение fd.transactionstart при fd.transactionstarted = 1 - YES вызывает нефатальную ошибку с кодом 12 - PL_FD_STATUS_TRANSACTION_ALREADY_STARTED. Нефатальная ошибка 15 - PL_FD_STATUS_TRANSACTIONS_NOT_SUPPORTED возникает, если диск был отформатирован методом fd.format или аргумент maxjournalsectors метода fd.formatj был равен 0 или 1.
Отменить уже начатую транзакцию нельзя. Начав её, нужно довести её до конца. При использовании дисковых транзакций выполнять fd.flush не нужно.
Ёмкость журнала транзакций ограничена
Нельзя накапливать в одной транзакции сколько угодно операций записи на диск. У транзакций есть предел: число секторов, которые можно изменить в одной транзакции. Подробности - в разделе Ёмкость транзакции.
Транзакции не влияют на прямой доступ к секторам
Методы fd.setsector и fd.getsector всегда обращаются к указанному целевому сектору, а не к его кэшированной копии, даже если дисковая транзакция выполняется и целевой сектор уже закэширован.