Using Disk Transactions
Flash disk is a sensitive media. Many things can go wrong — especially if a disk operation is abruptly interrupted due to the power failure or for some other reason. Try appending data to a file in a loop while power-cycling the device at random. It won't be long before the file and even the disk itself become corrupted.
Many systems require reliable disk operation, even during random power cuts. For such systems, disk transactions are the answer!
Transactions help strengthen your application on two levels:
- Disk level: Seemingly simple operations of the disk often involve complex internal changes to the disk data. Losing power while executing such operations may corrupt your files and the disk itself. Transactions make sure it won't happen.
- Logical level: There are often group operations that must be performed together (in synchronicity), or not at all. For example, you make the data table file and its index file. Change the first one, and the other one must be changed too. Transactions allow you to effect such changes in unison and make sure that both files are changed or no files are changed at all.
Transactions only make sense for disk data changing operations. These are fd.create, fd.rename, fd.delete, fd.setattributes, fd.setdata, fd.setfilesize, and fd.cutfromtop. Using transactions with fd. object's methods that only read the data is pointless.
Performing Disk Operations Within Transactions
Transactions are only possible when the flash disk is formatted with the fd.formatj method and the maxjournalsectors argument was > 1. For good disk leveling, choose this parameter to be in the 50-100 range and definitely not less than 17 (see the explanation here).
To start a disk transaction, use the fd.transactionstart method. To finish with the transaction and commit (save) all changes to the disk, invoke fd.transactioncommit. Between these two statements, add any operations that lead to the changing of the disk data.
With transaction in progress, no changes are made to the disk data. Turn the power off before executing fd.transactioncommit, turn the power back on, mount the disk, and it will look as if the disk operations within the unfinished transaction have never happened. This is because all changed sectors are temporarily cached in the journal area of the disk. Every sector that gets changed in the cause of a transaction is saved to the journal, and not back to its original place. Invoking fd.transactioncommit starts the process of copying changed sectors from the journal memory to their original locations. This work can be safely interrupted. If the device loses power while committing changes, the next fd.mount will finish this job!
Here is a simple example:
fd.setdata("write some data to a file")
It may seem that fd.setdata is a trivial operation, so why put it inside the transaction? In reality, performing this simple task may lead to the changing of as many as eight (!) sectors on the disk. If your device experiences a power failure while in the middle of writing to a file, then this file and even the disk itself can become corrupted!
Here is another example, in which we change two files. Encasing this within the transaction makes sure that both files are changed or no files are changed at all:
'we presume that there are two files opened 'on' file numbers 0 and 1
fd.setdata("write some data to the first file")
fd.setdata("write some more to the second file")
The Fd.transactionstarted read-only property exists so you can check if any disk transaction is already in progress. Executing fd.transactioncommit while fd.transactionstarted = 0 — NO generates a non-fatal 13 — PL_FD_STATUS_TRANSACTION_NOT_YET_STARTED error. Executing fd.transactionstart when fd.transactionstarted = 1 — YES generates the 12 — PL_FD_STATUS_TRANSACTION_ALREADY_STARTED non-fatal error code. A 15 — PL_FD_STATUS_TRANSACTIONS_NOT_SUPPORTED non-fatal error is generated if the disk was formatted with fd.format or the maxjournalsectors argument of fd.formatj was set to 0 or 1.
You cannot abort a transaction that has already been started. If you started it, you must follow through with it. There is no need to execute fd.flush when using disk transactions.
Transaction Journal Has Limited Capacity
You can't just pile up a large number of disk write operations within a single transaction. This is because transactions have a limit: the number of sectors that can be changed within a single transaction. Understanding Transaction Capacity provides further details.