Fixed writing of unaligned fragments to new files

This was only noticed when forcing btrees for other unrelated tests
(INLINED_SIZE=0, CRYSTAL_THRESH=-1), where even simple file writes would
end up with some unaligned fragments the size of our file buffer.

It was hard to notice without forcing btrees, since our crystallization
algorithm has a tendency to fix alignment issues.

The problem was that we weren't bypassing the file buffer correctly when
buffer.size == 0. We relied on the LFS_F_UNFLUSH flag to know if we
could do a bypassing write, but inlined files set the LFS_F_UNFLUSH flag
even for empty files. This led to blocked bypassing writes, attempts
to merge with empty buffers, and unaligned fragments.

To avoid this, lfsr_file_write now checks for buffer.size == 0
explicitly. There may be a better solution, but for now this gets the
job done.

---

To make sure we don't end up with unaligned fragments again in the
future, I've extend the fwrite litmus tests to check for well-aligned
fragments in addition to blocks:

- test_fwrite_simple_litmus_fragments
- test_fwrite_incr_litmus_fragments

These fixes end up adding a bit of code, as checking for both the
unflushed flag and buffer.size == 0 has a cost:

           code          stack
  before: 36424           2680
  after:  36452 (+0.1%)   2680 (+0.0%)

But hey, file aren't stuck with unaligned fragments anymore.
This commit is contained in:
Christopher Haster
2024-08-12 01:58:00 -05:00
parent 9d4b4d2557
commit dffd8fa0fa
2 changed files with 315 additions and 17 deletions
+7 -3
View File
@@ -11846,7 +11846,8 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
// strictly necessary, but enforces a more intuitive write order
// and avoids weird cases with low-level write heuristics
//
if (!lfsr_f_isunflush(file->o.o.flags)
if ((!lfsr_f_isunflush(file->o.o.flags)
|| file->buffer.size == 0)
&& size >= lfsr_file_buffersize(lfs, file)) {
err = lfsr_file_flush_(lfs, file,
pos, buffer_, size);
@@ -11864,6 +11865,7 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
lfsr_file_buffersize(lfs, file));
file->buffer.size = lfsr_file_buffersize(lfs, file);
file->o.o.flags &= ~LFS_F_UNFLUSH;
written += size;
pos += size;
buffer_ += size;
@@ -11881,14 +11883,16 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
// 2. Bypassing the buffer above means we only write to the
// buffer once, and flush at most twice.
//
if (!lfsr_f_isunflush(file->o.o.flags)
if ((!lfsr_f_isunflush(file->o.o.flags)
|| file->buffer.size == 0)
|| (pos >= file->buffer.pos
&& pos <= file->buffer.pos + file->buffer.size
&& pos
< file->buffer.pos
+ lfsr_file_buffersize(lfs, file))) {
// unused buffer? we can move it where we need it
if (!lfsr_f_isunflush(file->o.o.flags)) {
if ((!lfsr_f_isunflush(file->o.o.flags)
|| file->buffer.size == 0)) {
file->buffer.pos = pos;
file->buffer.size = 0;
}