Dropped file-level idempotent errors

This is a big compromise in robustness in the face of errors, vs code
size and stack size.

To be clear, errors here refer to runtime errors, such as ENOSPC, EIO,
etc, not on-disk errors, though disk errors could result in a runtime
error if unrecoverable. The question is what happens to the filesystem
after it reports one of these errors to the user. Operations are
necessarily interrupted, so some in-device state may be lost.

The way I see it, there are three options, increasing in robustness, but
also increasing in code/stack cost:

1. Leave the filesystem in an inconsistent state, require an
   unmount+mount cycle to continue using the filesystem.

2. Use on-stack copies to prevent corrupted state until disk commits
   complete. This does not protect against intermediary states during
   file operations.

3. Use on-stack copies and file snapshots to fully revert any failed
   filesystem operation.

My initial thought, since this is supposed to be a robust filesystem,
was that we should try 3., fully revert any failed filesystem operation.

With some stack tradeoff, this isn't too much of a problem, until we get
to files. Files present some real problems:

1. File bshrubs need to be tracked in order to be compacted correctly.
   This means our on-stack copies need to be tracked, which complicates
   things a bit.

2. Bshrub estimates need to conservatively include all snapshots to
   avoid compaction issues. This means if we are tracking on-stack
   copies, we are effectively multiplying bshrub cost by ~3x vs ~2x.

3. We only have one file buffer. Being able to revert buffer updates
   would require either unecessary disk flushes and some weird mechanism
   to handle small files, or ~2x the RAM cost.

   See the previous commit for more info on this.

These issues can _probably_ be worked around, with some tradeoffs, but I
think the writing is on the wall. Full reverts on errors just isn't
worth the cost for littlefs's use case.

With the snapshotting features of littlefs, it shouldn't be too hard to
still handle errors gracefully in littlefs, either by keeping two file
handles around, or reopening the file after an error.

This adds cost at the user-level, but consider that the alternative is
that all users pay roughly this cost at the filesystem-level.

Maybe in the future we should additional LFS_GLASS/LFS_TEMPERED modes to
provide all three of the above options? Let the user chose their
robustness vs code/RAM tradeoff?

---

To be clear, this change makes it so all filesystem operations are
error-idempotent, with the exception of the _contents_ of files after an
error. If an error occurs during a file operation, the contents of that
file is undefined (but also desynced, so disk is unaffected).

Code changes:

           code          stack
  before: 33964           3080
  after:  33286 (-2.0%)   2968 (-3.6%)

With these changes, O_SYNC/O_FLUSH are also much cheaper to implement.
We can see their specific costs, which, to be honest, is a bit more than
I expected since these are now just a flag check and function call:

                   code          stack
  default:        33174           2944
  O_SYNC:         33224 (+0.2%)   2968 (+0.8%)
  O_SYNC+O_FLUSH: 33286 (+0.3%)   2968 (+0.8%)
This commit is contained in:
Christopher Haster
2024-01-07 00:24:58 -06:00
parent b76ff63e53
commit ebddafd66f
+341 -468
View File
File diff suppressed because it is too large Load Diff