Skip to content

Commit c89163b

Browse files
author
Andrei Nasonov
committed
issue-1751: [Filestore] WriteBackCache should not crash on corruption
1 parent 7fa4981 commit c89163b

12 files changed

Lines changed: 239 additions & 104 deletions

cloud/filestore/libs/diagnostics/critical_events.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ namespace NCloud::NFileStore{
4141
xxx(WriteBackCacheCorruptionError) \
4242
xxx(WriteBackCacheDataLossError) \
4343
xxx(WriteBackCacheImpossibleState) \
44+
xxx(WriteBackCacheInvalidConfiguration) \
4445
xxx(WriteBackCacheWritingNotAllowedInDrainingMode) \
4546
xxx(ErrorWasSentToTheGuest) \
4647
xxx(DirectoryHandlesStorageError) \

cloud/filestore/libs/vfs_fuse/write_back_cache/persistent_storage.cpp

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,10 @@ class TFileRingBufferStorage: public IPersistentStorage
4242
, LogTag(std::move(logTag))
4343
{
4444
SetCounters();
45-
}
4645

47-
NProto::TError Init()
48-
{
4946
if (Storage.IsCorrupted()) {
50-
return MakeError(E_FAIL, "Data structure is corrupted");
47+
// Reporting corrupted state is handled by TFileRingBuffer
48+
return;
5149
}
5250

5351
NJsonWriter::TBuf json;
@@ -65,19 +63,23 @@ class TFileRingBufferStorage: public IPersistentStorage
6563
.EndObject();
6664

6765
STORAGE_INFO(
68-
LogTag << " WriteBackCache has been initialized " << json.Str());
69-
70-
return {};
66+
LogTag << " WriteBackCache storage has been initialized "
67+
<< json.Str());
7168
}
7269

7370
bool Empty() const override
7471
{
7572
return Storage.Empty();
7673
}
7774

78-
void Visit(const TVisitor& visitor) override
75+
bool IsCorrupted() const override
7976
{
80-
Storage.Visit(
77+
return Storage.IsCorrupted();
78+
}
79+
80+
NProto::TError Visit(const TVisitor& visitor) override
81+
{
82+
return Storage.Visit(
8183
[&visitor](ui32 checksum, ui32 tag, TStringBuf entry)
8284
{
8385
Y_UNUSED(checksum);
@@ -97,27 +99,23 @@ class TFileRingBufferStorage: public IPersistentStorage
9799
return Storage.Alloc(size);
98100
}
99101

100-
void Commit() override
102+
NProto::TError Commit() override
101103
{
102104
auto res = Storage.Commit();
103-
Y_ENSURE(
104-
!HasError(res),
105-
"Failed to commit allocation: " << FormatError(res));
106105
SetCounters();
106+
return res;
107107
}
108108

109-
void Free(const void* ptr) override
109+
NProto::TError Free(const void* ptr) override
110110
{
111111
auto res = Storage.Free(ptr);
112-
Y_ENSURE(
113-
!HasError(res),
114-
"Failed to free pointer " << ptr << ": " << FormatError(res));
115112
SetCounters();
113+
return res;
116114
}
117115

118-
void SetTag(const void* ptr, ui32 tag) override
116+
NProto::TError SetTag(const void* ptr, ui32 tag) override
119117
{
120-
Storage.SetTag(ptr, tag);
118+
return Storage.SetTag(ptr, tag);
121119
}
122120

123121
void UpdateStats() const override
@@ -144,24 +142,17 @@ class TFileRingBufferStorage: public IPersistentStorage
144142

145143
////////////////////////////////////////////////////////////////////////////////
146144

147-
TResultOrError<IPersistentStoragePtr> CreateFileRingBufferPersistentStorage(
145+
IPersistentStoragePtr CreateFileRingBufferPersistentStorage(
148146
IPersistentStorageStatsPtr stats,
149147
TPersistentStorageConfig config,
150148
TLog log,
151149
TString logTag)
152150
{
153-
auto storage = std::make_shared<TFileRingBufferStorage>(
151+
return std::make_shared<TFileRingBufferStorage>(
154152
std::move(stats),
155153
std::move(config),
156154
std::move(log),
157155
std::move(logTag));
158-
159-
auto error = storage->Init();
160-
if (HasError(error)) {
161-
return error;
162-
}
163-
164-
return static_cast<IPersistentStoragePtr>(storage);
165156
}
166157

167158
} // namespace NCloud::NFileStore::NFuse::NWriteBackCache

cloud/filestore/libs/vfs_fuse/write_back_cache/persistent_storage.h

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,24 @@ struct IPersistentStorage
2222
virtual ~IPersistentStorage() = default;
2323

2424
virtual bool Empty() const = 0;
25+
virtual bool IsCorrupted() const = 0;
2526

26-
// Enumerates the contents of the persistent storage in the allocation order
27-
virtual void Visit(const TVisitor& visitor) = 0;
27+
/**
28+
* Enumerates the contents of the persistent storage in the allocation order
29+
*
30+
* Does not visit anything and returns an error if the buffer is corrupted.
31+
*/
32+
virtual NProto::TError Visit(const TVisitor& visitor) = 0;
2833

2934
/**
30-
* Gets the maximum possible buffer size that can be allocated.
31-
* Alloc is guaranteed to succeed for any size <=
32-
* MaxSupportedAllocationByteCount when the storage is empty.
35+
* Returns the number of bytes that can be successfully allocated by
36+
* PushBack and Alloc for an empty buffer without exceeding the capacity.
37+
*
38+
* Returns zero if the buffer is corrupted.
39+
*
40+
* Note: the purpose of this method is to provide a guarantee that an
41+
* allocation of this size will eventually succeed. Allocations of higher
42+
* sizes will fail with an error.
3343
*/
3444
virtual ui64 GetMaxSupportedAllocationByteCount() const = 0;
3545

@@ -39,29 +49,43 @@ struct IPersistentStorage
3949
* On successful allocation, returns a pointer to the buffer in persistent
4050
* storage. The caller should fill the buffer and call Commit.
4151
*
42-
* Returns nullptr if there is not enough free space in the storage.
52+
* On failure, returns nullptr if the buffer is full or an error if
53+
* allocation is not possible due to corruption or invalid argument.
4354
*
44-
* Returns an error if allocation is not possible due to other reasons.
55+
* Note: only one allocation is possible at a time. Repeated Alloc will
56+
* return an error.
4557
*/
4658
[[nodiscard]] virtual TResultOrError<char*> Alloc(size_t size) = 0;
4759

4860
/**
49-
* Commits previous memory allocation
61+
* Commits previously allocated memory buffer.
62+
*
63+
* Once committed, it is not allowed to modify the contents of the allocated
64+
* entry. If there is a need to augment the allocation with additional data,
65+
* SetTag can be used.
5066
*
5167
* Memory that was allocated but not committed will be lost at buffer
5268
* recreation.
5369
*
54-
* Returns true if the commit was successful.
55-
* Returns false if Alloc was not called.
70+
* An error is returned if there is no incomplete allocation or the buffer
71+
* is corrupted.
5672
*/
57-
virtual void Commit() = 0;
73+
virtual NProto::TError Commit() = 0;
5874

59-
// Frees a previously allocated buffer.
60-
virtual void Free(const void* ptr) = 0;
75+
/**
76+
* Frees a previously allocated and committed buffer.
77+
*
78+
* An error is returned if the pointer is invalid or the buffer is corrupted
79+
*/
80+
virtual NProto::TError Free(const void* ptr) = 0;
6181

62-
// Once committed, entries should remain immutable.
63-
// But it is possible to assign a small mutable tag to the entry.
64-
virtual void SetTag(const void* ptr, ui32 tag) = 0;
82+
/**
83+
* Sets the tag value associated with the allocation.
84+
*
85+
* Returns an error if the pointer is invalid, the tag value exceeds the
86+
* maximal supported value or if the buffer is corrupted.
87+
*/
88+
virtual NProto::TError SetTag(const void* ptr, ui32 tag) = 0;
6589

6690
virtual void UpdateStats() const = 0;
6791
};
@@ -79,7 +103,7 @@ struct TPersistentStorageConfig
79103

80104
////////////////////////////////////////////////////////////////////////////////
81105

82-
TResultOrError<IPersistentStoragePtr> CreateFileRingBufferPersistentStorage(
106+
IPersistentStoragePtr CreateFileRingBufferPersistentStorage(
83107
IPersistentStorageStatsPtr stats,
84108
TPersistentStorageConfig config,
85109
TLog log,

cloud/filestore/libs/vfs_fuse/write_back_cache/persistent_storage_ut.cpp

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,15 @@ struct TBootstrap
4141

4242
NProto::TError Initialize()
4343
{
44-
auto res = CreateFileRingBufferPersistentStorage(
44+
Storage = CreateFileRingBufferPersistentStorage(
4545
Stats,
4646
{.FilePath = TempFile.GetName(),
4747
.DataCapacity = DefaultCapacity,
4848
.MetadataCapacity = 0},
4949
Log,
5050
"[tag]");
5151

52-
if (HasError(res)) {
53-
return res.GetError();
54-
}
55-
56-
Storage = res.ExtractResult();
57-
return {};
52+
return MakeError(Storage->IsCorrupted() ? E_FAIL : S_OK);
5853
}
5954

6055
void Deinitialize()
@@ -89,9 +84,9 @@ struct TBootstrap
8984
return HasError(allocationResult);
9085
}
9186

92-
void Free(const void* ptr) const
87+
NProto::TError Free(const void* ptr) const
9388
{
94-
Storage->Free(ptr);
89+
return Storage->Free(ptr);
9590
}
9691

9792
TString Dump() const
@@ -157,7 +152,7 @@ Y_UNIT_TEST_SUITE(TPersistentStorageTest)
157152
UNIT_ASSERT_VALUES_EQUAL(0, stats.EntryCount->Get());
158153
}
159154

160-
Y_UNIT_TEST(ShouldThrowOnDoubleFree)
155+
Y_UNIT_TEST(ShouldReturnErrorOnDoubleFree)
161156
{
162157
TBootstrap b;
163158

@@ -170,11 +165,11 @@ Y_UNIT_TEST_SUITE(TPersistentStorageTest)
170165
const auto* ptr2 = b.Alloc("567890");
171166
UNIT_ASSERT(ptr2);
172167

173-
b.Free(ptr2);
174-
UNIT_ASSERT_EXCEPTION(b.Free(ptr2), yexception);
168+
UNIT_ASSERT(!HasError(b.Free(ptr2)));
169+
UNIT_ASSERT(HasError(b.Free(ptr2)));
175170

176-
b.Free(ptr1);
177-
UNIT_ASSERT_EXCEPTION(b.Free(ptr1), yexception);
171+
UNIT_ASSERT(!HasError(b.Free(ptr1)));
172+
UNIT_ASSERT(HasError(b.Free(ptr1)));
178173
}
179174

180175
Y_UNIT_TEST(ShouldValidateAllocationSize)

cloud/filestore/libs/vfs_fuse/write_back_cache/read_response_builder_ut.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ class TBootstrap: private IQueuedOperationsProcessor
3737
: Stats(CreateWriteBackCacheStats())
3838
, State(
3939
*this,
40+
CreateTestStorage(Stats),
4041
std::make_shared<TTestTimer>(),
4142
Stats->GetWriteBackCacheStateStats(),
4243
Stats->GetWriteDataRequestManagerStats(),
4344
Stats->GetNodeStateHolderStats(),
4445
TFlushBatchLimits{},
4546
"[tag]")
4647
{
47-
State.Init(CreateTestStorage(Stats));
48+
State.Init();
4849

4950
Write(2, "ABCD");
5051
Write(10, "IJKL");

cloud/filestore/libs/vfs_fuse/write_back_cache/test/test_persistent_storage.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ bool TTestStorage::Empty() const
1313
return Data.empty();
1414
}
1515

16-
void TTestStorage::Visit(const TVisitor& visitor)
16+
bool TTestStorage::IsCorrupted() const
17+
{
18+
return false;
19+
}
20+
21+
NProto::TError TTestStorage::Visit(const TVisitor& visitor)
1722
{
1823
for (const auto& it: List) {
1924
visitor(it.Tag, it.Data);
2025
}
26+
return {};
2127
}
2228

2329
ui64 TTestStorage::GetMaxSupportedAllocationByteCount() const
@@ -43,25 +49,29 @@ TResultOrError<char*> TTestStorage::Alloc(size_t size)
4349
return res;
4450
}
4551

46-
void TTestStorage::Commit()
47-
{}
52+
NProto::TError TTestStorage::Commit()
53+
{
54+
return {};
55+
}
4856

49-
void TTestStorage::Free(const void* ptr)
57+
NProto::TError TTestStorage::Free(const void* ptr)
5058
{
5159
auto it = Data.find(ptr);
5260
Y_ENSURE(it != Data.end(), "Double free detected");
5361

5462
Data.erase(it);
5563

5664
SetStats();
65+
return {};
5766
}
5867

59-
void TTestStorage::SetTag(const void* ptr, ui32 tag)
68+
NProto::TError TTestStorage::SetTag(const void* ptr, ui32 tag)
6069
{
6170
auto it = Data.find(ptr);
6271
Y_ENSURE(it != Data.end(), "Entry not found");
6372

6473
it->second->Tag = tag;
74+
return {};
6575
}
6676

6777
void TTestStorage::UpdateStats() const

cloud/filestore/libs/vfs_fuse/write_back_cache/test/test_persistent_storage.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ class TTestStorage: public IPersistentStorage
2929
explicit TTestStorage(IPersistentStorageStatsPtr stats);
3030

3131
bool Empty() const override;
32-
void Visit(const TVisitor& visitor) override;
32+
bool IsCorrupted() const override;
33+
NProto::TError Visit(const TVisitor& visitor) override;
3334
ui64 GetMaxSupportedAllocationByteCount() const override;
3435
TResultOrError<char*> Alloc(size_t size) override;
35-
void Commit() override;
36-
void Free(const void* ptr) override;
37-
void SetTag(const void* ptr, ui32 tag) override;
36+
NProto::TError Commit() override;
37+
NProto::TError Free(const void* ptr) override;
38+
NProto::TError SetTag(const void* ptr, ui32 tag) override;
3839
void UpdateStats() const override;
3940

4041
void SetCapacity(size_t capacity);

0 commit comments

Comments
 (0)