Question

From local device testing, I've seen that writing a file to the iOS file system (regardless of how low level the call you use) will often return success before the file is fully committed to the flash. Meaning, if you hard reset the device then reboot, your file could be rolled back (if the write completed or was atomic) or corrupted. What is the source of this delay (documentation appreciated, I haven't been able to find anything), and is there a way to get feedback when the actual filesystem write is completed. For instance, I'd like to acknowledge receipt and storage of a piece of data from a remote server, but I find that acknowledging it after write "reports" success could result in data loss in the event of a hard crash or power failure.

Was it helpful?

Solution

Since this is a 4 years old questions, I'll provide not only the answer, but also the path I took while searching for it.

I was not able to find any clear explanation in the official documentation: File System Programming Guide. There was only a clue in the Performance Tips section. It states that:

Apps can call the BSD fcntl function with the F_NOCACHE flag to enable or disable caching for a file. For more information about this function, see fcntl.

Enabling the F_NOCACHE flag does not solve the problem you're stating, however, the manual for fcntl method states there's an option that you might just find interesting:

F_FULLFSYNC Does the same thing as fsync(2) then asks the drive to flush all buffered data to the permanent storage device

(from man fcntl, see here).

I've checked the manual for fsync for more details. It has given me, eventually, the clearest and most understandable explanation of both the problem and the solution:

Note that while fsync() will flush all data from the host to the drive (i.e. the "permanent storage device"), the drive itself may not physically write the data to the platters for quite some time and it may be written in an out-of-order sequence.

Specifically, if the drive loses power or the OS crashes, the application may find that only some or none of their data was written. The disk drive may also re-order the data so that later writes may be present, while earlier writes are not.

This is not a theoretical edge case. This scenario is easily reproduced with real world workloads and drive power failures.

For applications that require tighter guarantees about the integrity of their data, Mac OS X provides the F_FULLFSYNC fcntl. The F_FULLFSYNC fcntl asks the drive to flush all buffered data to permanent storage. Applications, such as databases, that require a strict ordering of writes should use F_FULLFSYNC to ensure that their data is written in the order they expect.

(from man fsync, see here).

Yeah, it's definitely not a theoretical edge case. Thankfully, once you know the problem, the solution is trivial:

let filePath: String = "your file path"

// you can use other option than read-write
let fd = open(String(path.utf8), O_RDWR)

// if fd is -1, there was an error opening file, handle it as you wish
guard fd != -1 else { return }

// syncResult is -1 if sync operation failed, handle it as you wish
let syncResult = fcntl(fd, F_FULLFSYNC)

// don't forget to close opened file
close(fd)

Once fcntl finishes, your data will be saved.

Notice this operation is slower than a usual writing to file (via NSFileManager or writeToURL methods family). In case of performance issues, it's best to move writing to background thread.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top