aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/basicfile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zencore/basicfile.cpp')
-rw-r--r--src/zencore/basicfile.cpp65
1 files changed, 60 insertions, 5 deletions
diff --git a/src/zencore/basicfile.cpp b/src/zencore/basicfile.cpp
index fdf742261..01d550957 100644
--- a/src/zencore/basicfile.cpp
+++ b/src/zencore/basicfile.cpp
@@ -798,11 +798,12 @@ BasicFileWriter::Write(const void* Data, uint64_t Size, uint64_t FileOffset)
{
if (m_Buffer == nullptr || (Size >= m_BufferSize))
{
- if (FileOffset == m_BufferEnd)
- {
- Flush();
- m_BufferStart = m_BufferEnd = FileOffset + Size;
- }
+ // Always flush pending buffered data first. Otherwise a later
+ // Flush() would replay stale bytes at m_BufferStart, clobbering
+ // any range of this direct write that overlaps
+ // [m_BufferStart, m_BufferEnd).
+ Flush();
+ m_BufferStart = m_BufferEnd = FileOffset + Size;
m_Base.Write(Data, Size, FileOffset);
return;
@@ -1200,6 +1201,60 @@ TEST_CASE("BasicFileBuffer")
}
}
+TEST_CASE("BasicFileWriter.LargeDiscontinuousWriteFlushesBuffer")
+{
+ // Regression: BasicFileWriter::Write's large-write branch used to skip
+ // Flush() whenever FileOffset != m_BufferEnd. Any subsequent Flush
+ // (including the one in ~BasicFileWriter) then replayed the stale
+ // buffered bytes at their original offset, clobbering whatever the
+ // caller had just written directly.
+ ScopedCurrentDirectoryChange _;
+
+ constexpr uint64_t BufferSize = 64;
+ constexpr uint64_t SmallSize = 10;
+ constexpr uint64_t LargeSize = 1024; // >= BufferSize, forces the direct-write path
+ constexpr uint64_t LargeOffset = 5; // overlaps the pending small-write region
+ constexpr uint64_t OverlapStart = LargeOffset;
+ constexpr uint64_t OverlapEnd = SmallSize;
+
+ {
+ BasicFile File;
+ File.Open("discontig_write", BasicFile::Mode::kTruncate);
+ BasicFileWriter Writer(File, BufferSize);
+
+ // First: small write buffered at [0, SmallSize) - still pending flush.
+ std::vector<uint8_t> Small(SmallSize, 'A');
+ Writer.Write(Small.data(), Small.size(), 0);
+
+ // Second: large write that overlaps the pending buffered region.
+ // Last-writer-wins means the overlap bytes must end up as 'B'.
+ std::vector<uint8_t> Large(LargeSize, 'B');
+ Writer.Write(Large.data(), Large.size(), LargeOffset);
+ }
+
+ BasicFile Reader;
+ Reader.Open("discontig_write", BasicFile::Mode::kRead);
+ IoBuffer Contents = Reader.ReadAll();
+ REQUIRE_EQ(Contents.Size(), LargeOffset + LargeSize);
+
+ const uint8_t* Bytes = reinterpret_cast<const uint8_t*>(Contents.Data());
+ for (uint64_t I = 0; I < OverlapStart; ++I)
+ {
+ CHECK_EQ(Bytes[I], 'A');
+ }
+ // The bytes in the overlap range [OverlapStart, OverlapEnd) are the
+ // critical check: with the bug, the late Flush() replayed the small
+ // 'A' write and clobbered these with 'A'.
+ for (uint64_t I = OverlapStart; I < OverlapEnd; ++I)
+ {
+ CHECK_EQ(Bytes[I], 'B');
+ }
+ for (uint64_t I = OverlapEnd; I < LargeOffset + LargeSize; ++I)
+ {
+ CHECK_EQ(Bytes[I], 'B');
+ }
+}
+
TEST_SUITE_END();
void