pw_kvs: Fix AlignedWrite issues; expand tests
- Update AlignedWriter to prevent writing after an error occurs.
- Ensure that the number of written bytes is always returned for
AlignedWriter errors.
- Remove FlashError::Mode, since the number of bytes affected should
always be set.
- Add tests to alignment_test.cc and key_value_store.cc to cover
failures during AlignedWrite.
- Update some comments.
Change-Id: Id62f1564b3641be0aeb7f799f0fa0538a1b5b09e
diff --git a/pw_kvs/alignment.cc b/pw_kvs/alignment.cc
index cba0e40..6f58788 100644
--- a/pw_kvs/alignment.cc
+++ b/pw_kvs/alignment.cc
@@ -31,11 +31,11 @@
// Always use write_size_ for the bytes written. If there was an error
// assume the space was written or at least disturbed.
bytes_written_ += write_size_;
+ bytes_in_buffer_ = 0;
+
if (!result.ok()) {
return StatusWithSize(result.status(), bytes_written_);
}
-
- bytes_in_buffer_ = 0;
}
}
@@ -45,6 +45,8 @@
StatusWithSize AlignedWriter::Flush() {
static constexpr std::byte kPadByte = std::byte{0};
+ Status status;
+
// If data remains in the buffer, pad it to the alignment size and flush the
// remaining data.
if (bytes_in_buffer_ != 0u) {
@@ -52,17 +54,14 @@
std::memset(&buffer_[bytes_in_buffer_],
int(kPadByte),
remaining_bytes - bytes_in_buffer_);
-
- if (auto result = output_.Write(buffer_, remaining_bytes); !result.ok()) {
- return StatusWithSize(result.status(), bytes_written_);
- }
+ status = output_.Write(buffer_, remaining_bytes).status();
bytes_written_ += remaining_bytes; // Include padding in the total.
+ bytes_in_buffer_ = 0;
}
- const StatusWithSize result(bytes_written_);
+ const StatusWithSize result(status, bytes_written_);
bytes_written_ = 0;
- bytes_in_buffer_ = 0;
return result;
}
diff --git a/pw_kvs/alignment_test.cc b/pw_kvs/alignment_test.cc
index 542428d..e82fee9 100644
--- a/pw_kvs/alignment_test.cc
+++ b/pw_kvs/alignment_test.cc
@@ -17,10 +17,12 @@
#include <string_view>
#include "gtest/gtest.h"
+#include "pw_status/status_with_size.h"
namespace pw::kvs {
namespace {
+using namespace std::string_view_literals;
using std::byte;
TEST(AlignUp, Zero) {
@@ -163,7 +165,6 @@
TEST(AlignedWriter, DestructorFlushes) {
static size_t called_with_bytes;
-
called_with_bytes = 0;
OutputToFunction output([](span<const byte> data) {
@@ -180,5 +181,87 @@
EXPECT_EQ(called_with_bytes, AlignUp(sizeof("What is this?"), 3));
}
+TEST(AlignedWriter, Write_NoFurtherWritesOnFailure) {
+ struct BreakableOutput final : public Output {
+ public:
+ enum { kKeepGoing, kBreakOnNext, kBroken } state = kKeepGoing;
+
+ StatusWithSize Write(span<const byte> data) override {
+ switch (state) {
+ case kKeepGoing:
+ return StatusWithSize(data.size());
+ case kBreakOnNext:
+ state = kBroken;
+ break;
+ case kBroken:
+ ADD_FAILURE();
+ break;
+ }
+ return StatusWithSize(Status::UNKNOWN, data.size());
+ }
+ } output;
+
+ {
+ AlignedWriterBuffer<4> writer(3, output);
+ writer.Write(as_bytes(span("Everything is fine.")));
+ output.state = BreakableOutput::kBreakOnNext;
+ EXPECT_EQ(Status::UNKNOWN,
+ writer.Write(as_bytes(span("No more writes, okay?"))).status());
+ writer.Flush();
+ }
+}
+
+TEST(AlignedWriter, Write_ReturnsTotalBytesWritten) {
+ static Status return_status;
+ return_status = Status::OK;
+
+ OutputToFunction output([](span<const byte> data) {
+ return StatusWithSize(return_status, data.size());
+ });
+
+ AlignedWriterBuffer<22> writer(10, output);
+
+ StatusWithSize result = writer.Write(as_bytes(span("12345678901"sv)));
+ EXPECT_EQ(Status::OK, result.status());
+ EXPECT_EQ(0u, result.size()); // No writes; haven't filled buffer.
+
+ result = writer.Write(as_bytes(span("2345678901"sv)));
+ EXPECT_EQ(Status::OK, result.status());
+ EXPECT_EQ(20u, result.size());
+
+ return_status = Status::PERMISSION_DENIED;
+
+ result = writer.Write(as_bytes(span("2345678901234567890"sv)));
+ EXPECT_EQ(Status::PERMISSION_DENIED, result.status());
+ EXPECT_EQ(40u, result.size());
+}
+
+TEST(AlignedWriter, Flush_Ok_ReturnsTotalBytesWritten) {
+ OutputToFunction output(
+ [](span<const byte> data) { return StatusWithSize(data.size()); });
+
+ AlignedWriterBuffer<4> writer(2, output);
+
+ EXPECT_EQ(Status::OK, writer.Write(as_bytes(span("12345678901"sv))).status());
+
+ StatusWithSize result = writer.Flush();
+ EXPECT_EQ(Status::OK, result.status());
+ EXPECT_EQ(12u, result.size());
+}
+
+TEST(AlignedWriter, Flush_Error_ReturnsTotalBytesWritten) {
+ OutputToFunction output([](span<const byte> data) {
+ return StatusWithSize(Status::ABORTED, data.size());
+ });
+
+ AlignedWriterBuffer<20> writer(10, output);
+
+ EXPECT_EQ(0u, writer.Write(as_bytes(span("12345678901"sv))).size());
+
+ StatusWithSize result = writer.Flush();
+ EXPECT_EQ(Status::ABORTED, result.status());
+ EXPECT_EQ(20u, result.size());
+}
+
} // namespace
} // namespace pw::kvs
diff --git a/pw_kvs/in_memory_fake_flash.cc b/pw_kvs/in_memory_fake_flash.cc
index 1c9b7bc..245e329 100644
--- a/pw_kvs/in_memory_fake_flash.cc
+++ b/pw_kvs/in_memory_fake_flash.cc
@@ -18,40 +18,39 @@
namespace pw::kvs {
-FlashError::Result FlashError::Check(span<FlashError> errors,
- FlashMemory::Address address,
- size_t size) {
+Status FlashError::Check(span<FlashError> errors,
+ FlashMemory::Address address,
+ size_t size) {
for (auto& error : errors) {
- if (Result result = error.Check(address, size); !result.status.ok()) {
- return result;
+ if (Status status = error.Check(address, size); !status.ok()) {
+ return status;
}
}
- return {Status::OK, true};
+ return Status::OK;
}
-FlashError::Result FlashError::Check(FlashMemory::Address start_address,
- size_t size) {
+Status FlashError::Check(FlashMemory::Address start_address, size_t size) {
// Check if the event overlaps with this address range.
if (begin_ != kAnyAddress &&
(start_address >= end_ || (start_address + size) < begin_)) {
- return {Status::OK, true};
+ return Status::OK;
}
if (delay_ > 0u) {
delay_ -= 1;
- return {Status::OK, true};
+ return Status::OK;
}
if (remaining_ == 0u) {
- return {Status::OK, true};
+ return Status::OK;
}
if (remaining_ != kAlways) {
remaining_ -= 1;
}
- return {status_, mode_ != kAbort};
+ return status_;
}
Status InMemoryFakeFlash::Erase(Address address, size_t num_sectors) {
@@ -83,11 +82,8 @@
}
// Check for injected read errors
- auto [status, finish_operation] =
- FlashError::Check(read_errors_, address, output.size());
- if (finish_operation) {
- std::memcpy(output.data(), &buffer_[address], output.size());
- }
+ Status status = FlashError::Check(read_errors_, address, output.size());
+ std::memcpy(output.data(), &buffer_[address], output.size());
return StatusWithSize(status, output.size());
}
@@ -128,11 +124,8 @@
}
// Check for any injected write errors
- auto [status, finish_operation] =
- FlashError::Check(write_errors_, address, data.size());
- if (finish_operation) {
- std::memcpy(&buffer_[address], data.data(), data.size());
- }
+ Status status = FlashError::Check(write_errors_, address, data.size());
+ std::memcpy(&buffer_[address], data.data(), data.size());
return StatusWithSize(status, data.size());
}
diff --git a/pw_kvs/key_value_store_error_handling_test.cc b/pw_kvs/key_value_store_error_handling_test.cc
index 69074d3..0fb59c4 100644
--- a/pw_kvs/key_value_store_error_handling_test.cc
+++ b/pw_kvs/key_value_store_error_handling_test.cc
@@ -242,15 +242,28 @@
EXPECT_EQ(32u, kvs_.GetStorageStats().in_use_bytes);
}
-TEST_F(KvsErrorHandling, Put_WriteFailure_EntryNotAdded) {
+TEST_F(KvsErrorHandling, Put_WriteFailure_EntryNotAddedButBytesMarkedWritten) {
ASSERT_EQ(Status::OK, kvs_.Init());
- flash_.InjectWriteError(FlashError::Unconditional(Status::UNAVAILABLE));
+ flash_.InjectWriteError(FlashError::Unconditional(Status::UNAVAILABLE, 1));
EXPECT_EQ(Status::UNAVAILABLE, kvs_.Put("key1", ByteStr("value1")));
- byte buffer[64];
- EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("key1", buffer).status());
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("key1", span<byte>()).status());
ASSERT_TRUE(kvs_.empty());
+
+ auto stats = kvs_.GetStorageStats();
+ EXPECT_EQ(stats.in_use_bytes, 0u);
+ EXPECT_EQ(stats.reclaimable_bytes, 32u);
+ EXPECT_EQ(stats.writable_bytes, 512u * 3 - 32);
+
+ // The bytes were marked used, so a new key should not overlap with the bytes
+ // from the failed Put.
+ EXPECT_EQ(Status::OK, kvs_.Put("key1", ByteStr("value1")));
+
+ stats = kvs_.GetStorageStats();
+ EXPECT_EQ(stats.in_use_bytes, 32u);
+ EXPECT_EQ(stats.reclaimable_bytes, 32u);
+ EXPECT_EQ(stats.writable_bytes, 512u * 3 - 32 * 2);
}
} // namespace
diff --git a/pw_kvs/public/pw_kvs/alignment.h b/pw_kvs/public/pw_kvs/alignment.h
index 61b0b8e..0d734d5 100644
--- a/pw_kvs/public/pw_kvs/alignment.h
+++ b/pw_kvs/public/pw_kvs/alignment.h
@@ -107,8 +107,8 @@
AlignedWriterBuffer<kBufferSize> buffer(alignment_bytes, output);
for (const span<const std::byte>& chunk : data) {
- if (StatusWithSize status = buffer.Write(chunk); !status.ok()) {
- return status;
+ if (StatusWithSize result = buffer.Write(chunk); !result.ok()) {
+ return result;
}
}
diff --git a/pw_kvs/public/pw_kvs/in_memory_fake_flash.h b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
index 2bb385d..5dcb1f9 100644
--- a/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
+++ b/pw_kvs/public/pw_kvs/in_memory_fake_flash.h
@@ -27,42 +27,30 @@
class FlashError {
public:
- enum Mode {
- kAbort,
- kContinueButReportError,
- };
-
static constexpr FlashMemory::Address kAnyAddress = FlashMemory::Address(-1);
static constexpr size_t kAlways = size_t(-1);
// Creates a FlashError that always triggers on the next operation.
static constexpr FlashError Unconditional(Status status,
- Mode mode = kAbort,
size_t times = kAlways,
size_t delay = 0) {
- return FlashError(status, kAnyAddress, 0, mode, times, delay);
+ return FlashError(status, kAnyAddress, 0, times, delay);
}
// Creates a FlashError that triggers for particular addresses.
static constexpr FlashError InRange(Status status,
FlashMemory::Address address,
size_t size = 1,
- Mode mode = kAbort,
size_t times = kAlways,
size_t delay = 0) {
- return FlashError(status, address, size, mode, times, delay);
+ return FlashError(status, address, size, times, delay);
}
- struct Result {
- Status status; // what to return from the operation
- bool finish_operation; // whether to complete the operation
- };
-
// Determines if this FlashError applies to the operation.
- Result Check(FlashMemory::Address start_address, size_t size);
+ Status Check(FlashMemory::Address start_address, size_t size);
// Determines if any of a series of FlashErrors applies to the operation.
- static Result Check(span<FlashError> errors,
+ static Status Check(span<FlashError> errors,
FlashMemory::Address address,
size_t size);
@@ -70,13 +58,11 @@
constexpr FlashError(Status status,
FlashMemory::Address address,
size_t size, // not used if address is kAnyAddress
- Mode mode,
size_t times,
size_t delay)
: status_(status),
begin_(address),
end_(address + size), // not used if address is kAnyAddress
- mode_(mode),
delay_(delay),
remaining_(times) {}
@@ -85,8 +71,6 @@
const FlashMemory::Address begin_;
const FlashMemory::Address end_; // exclusive
- const Mode mode_;
-
size_t delay_;
size_t remaining_;
};
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index 1cf60d7..acd0955 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -33,16 +33,23 @@
namespace pw::kvs {
-// TODO: Select the appropriate defaults, add descriptions.
struct Options {
+ // Perform garbage collection if necessary when writing. If true, garbage
+ // collection is attempted if space for an entry cannot be found. This is a
+ // relatively lengthy operation. If false, Put calls that would require
+ // garbage collection fail with RESOURCE_EXHAUSTED.
bool partial_gc_on_write = true;
+
+ // Verify an entry's checksum after reading it from flash.
bool verify_on_read = true;
+
+ // Verify an in-flash entry's checksum after writing it.
bool verify_on_write = true;
};
class KeyValueStore {
public:
- // TODO: Make this configurable.
+ // TODO: Rework entry relocation to not need a large buffer.
static constexpr size_t kWorkingBufferSizeBytes = (4 * 1024);
// KeyValueStores are declared as instances of
@@ -226,7 +233,7 @@
using KeyDescriptor = internal::KeyDescriptor;
using SectorDescriptor = internal::SectorDescriptor;
- // In the future, will be able to provide additional EntryHeaderFormats for
+ // In the future, will be able to provide additional EntryFormats for
// backwards compatibility.
KeyValueStore(FlashPartition* partition,
Vector<KeyDescriptor>& key_descriptor_list,