From 507f7d387abff0e097e87011061fd772071c95e8 Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Thu, 20 Nov 2025 16:58:28 +0100 Subject: [PATCH 1/8] PR-4702: [Filestore] WriteBackCache should flush everything before the session is destoyed --- cloud/filestore/libs/vfs_fuse/fs_ut.cpp | 147 ++++++++++++++++- cloud/filestore/libs/vfs_fuse/loop.cpp | 200 +++++++++++++++--------- 2 files changed, 272 insertions(+), 75 deletions(-) diff --git a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp index d805bd1dd11..88cf8d55e58 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp @@ -61,6 +61,7 @@ namespace { constexpr TDuration WaitTimeout = TDuration::Seconds(5); constexpr TDuration ExceptionWaitTimeout = TDuration::Seconds(1); +constexpr ui64 WriteBackCacheCapacity = 1024 * 1024 + 1024; static const TString FileSystemId = "fs1"; static const TString SessionId = CreateGuidAsString(); @@ -72,6 +73,20 @@ TString CreateBuffer(size_t len, char fill = 0) return TString(len, fill); } +template +bool WaitForCondition(TDuration timeout, F&& predicate) +{ + TSpinWait sw; + auto deadline = TInstant::Now() + timeout; + while (!predicate()) { + if (TInstant::Now() > deadline) { + return false; + } + sw.Sleep(); + } + return true; +} + //////////////////////////////////////////////////////////////////////////////// struct TBootstrap @@ -101,7 +116,7 @@ struct TBootstrap const NProto::TFileStoreFeatures& featuresConfig = {}, ui32 handleOpsQueueSize = 1000, ui32 writeBackCacheAutomaticFlushPeriodMs = 1000, - ui64 writeBackCacheCapacity = 1024 * 1024 + 1024) + ui64 writeBackCacheCapacity = WriteBackCacheCapacity) : Logging(CreateLoggingService("console", { TLOG_RESOURCES })) , Scheduler{std::move(scheduler)} , Timer{std::move(timer)} @@ -2808,6 +2823,136 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) TestShouldSupportZeroCopyWriteByWriteBackCache(false); TestShouldSupportZeroCopyWriteByWriteBackCache(true); } + + Y_UNIT_TEST(ShouldFlushAllRequestsBeforeSessionIsDestroyed) + { + // The idea is to fill WriteBackCache with requests and to stop session. + // It should flush both cached and pending requests before session is + // destroyed. To ensure that there are pending requests, we write more + // data than the cache capacity (~1_MB) and temporarily prevent write + // requests from completion. + + // Note: due to the test framework/client limitations, the number of + // pending requests should not exceed 128 and the size of a message + // should not exceed 8192 bytes + + constexpr ui64 NodeCount = 8; + // ThreadPool limitation + constexpr i64 MaxPendingRequestCount = 4; + constexpr i64 MaxRequestCount = 1000; + // Queue buffer limitation + constexpr ui64 MinByteCount = 7000; + constexpr ui64 MaxByteCount = 7500; + constexpr ui64 MaxOffset = 128_KB; + constexpr TDuration Timeout = TDuration::Seconds(15); + + NProto::TFileStoreFeatures features; + features.SetServerWriteBackCacheEnabled(true); + + // Disable automatic flush + TBootstrap bootstrap( + CreateWallClockTimer(), + CreateScheduler(), + features, + /* handleOpsQueueSize= */ 1000, + /* writeBackCacheAutomaticFlushPeriodMs= */ 1000000000, + WriteBackCacheCapacity); + + auto writeDataPromise = NewPromise(); + std::atomic writeDataCalledCount = 0; + + bootstrap.Service->WriteDataHandler = [&](auto, auto) + { + writeDataCalledCount++; + // The same future cannot be shared between responses + auto promise = NewPromise(); + writeDataPromise.GetFuture().Subscribe( + [promise](const auto&) mutable { promise.SetValue({}); }); + return promise; + }; + + bootstrap.Start(); + Y_DEFER + { + bootstrap.Stop(); + }; + + auto counters = bootstrap.Counters + ->FindSubgroup("component", "fs_ut_fs") + ->FindSubgroup("host", "cluster") + ->FindSubgroup("filesystem", FileSystemId) + ->FindSubgroup("client", "") + ->FindSubgroup("cloud", "") + ->FindSubgroup("folder", "") + ->FindSubgroup("request", "WriteData"); + + auto requestCountSensor = counters->GetCounter("Count"); + auto requestInProgressSensor = counters->GetCounter("InProgress"); + i64 requestCount = 0; + + while (requestInProgressSensor->GetAtomic() < MaxPendingRequestCount) { + UNIT_ASSERT_GT(MaxRequestCount, requestCount); + + ui64 nodeId = RandomNumber(NodeCount) + 123; + ui64 handleId = nodeId + 456; + ui64 offset = RandomNumber(MaxOffset); + ui64 byteCount = + RandomNumber(MaxByteCount - MinByteCount + 1) + MinByteCount; + + auto reqWrite = std::make_shared( + nodeId, + handleId, + offset, + CreateBuffer(byteCount, 'a')); + + reqWrite->In->Body.flags |= O_WRONLY; + bootstrap.Fuse->SendRequest(reqWrite); + + requestCount++; + + UNIT_ASSERT(WaitForCondition( + WaitTimeout, + [&]() + { + const bool requestIsProcessed = + requestCount == requestInProgressSensor->GetAtomic() + + requestCountSensor->GetAtomic(); + + // A pending request will eventually become completed if the + // cache is not full. We don't want to count these requests + // as pending and will wait instead. + + // Cache fullness can be detected by non-zero WriteData + // attempts from Flush that is triggered when there is no + // space to store the request + + const bool nonZeroPendingIsExpected = + requestInProgressSensor->GetAtomic() == 0 || + writeDataCalledCount > 0; + + return requestIsProcessed && nonZeroPendingIsExpected; + })); + } + + auto path = TempDir.Path() / "WriteBackCache" / FileSystemId / + SessionId / "write_back_cache"; + + UNIT_ASSERT(path.Exists()); + + auto stopFuture = bootstrap.StopAsync(); + + UNIT_ASSERT_VALUES_EQUAL( + MaxPendingRequestCount, + requestInProgressSensor->GetAtomic()); + + // Enable progression of WriteData requests - this will enable flushing + writeDataPromise.SetValue(); + UNIT_ASSERT(stopFuture.Wait(Timeout)); + + UNIT_ASSERT_VALUES_EQUAL(0, requestInProgressSensor->GetAtomic()); + UNIT_ASSERT_VALUES_EQUAL(requestCount, requestCountSensor->GetAtomic()); + UNIT_ASSERT(!path.Exists()); + } } } // namespace NCloud::NFileStore::NFuse diff --git a/cloud/filestore/libs/vfs_fuse/loop.cpp b/cloud/filestore/libs/vfs_fuse/loop.cpp index eaee5ed46f2..6d63c465a9d 100644 --- a/cloud/filestore/libs/vfs_fuse/loop.cpp +++ b/cloud/filestore/libs/vfs_fuse/loop.cpp @@ -645,8 +645,9 @@ class TFileSystemLoop final THolder WriteBackCacheFileLock; THolder DirectoryHandlesStorageFileLock; + TWriteBackCache WriteBackCache; + bool HandleOpsQueueInitialized = false; - bool WriteBackCacheInitialized = false; bool DirectoryHandlesStorageInitialized = false; public: @@ -724,75 +725,7 @@ class TFileSystemLoop final return; } - p->FuseLoop->Unmount(); - p->FuseLoop = nullptr; - - auto callContext = MakeIntrusive( - p->Config->GetFileSystemId(), - CreateRequestId()); - callContext->RequestType = EFileStoreRequest::DestroySession; - p->RequestStats->RequestStarted(p->Log, *callContext); - - p->Session->DestroySession() - .Subscribe([ - w = std::move(w), - s = std::move(s), - callContext = std::move(callContext) - ] (const auto& f) mutable { - auto p = w.lock(); - if (!p) { - s.SetValue(); - return; - } - - const auto& response = f.GetValue(); - p->RequestStats->RequestCompleted( - p->Log, - *callContext, - response.GetError()); - - p->StatsRegistry->Unregister( - p->Config->GetFileSystemId(), - p->Config->GetClientId()); - - // We need to cleanup HandleOpsQueue file and directories - if (p->HandleOpsQueueInitialized) { - auto error = UnlockAndDeleteFile( - TFsPath(p->Config->GetHandleOpsQueuePath()) / - p->Config->GetFileSystemId() / p->SessionId, - p->HandleOpsQueueFileLock); - if (HasError(error)) { - ReportHandleOpsQueueCreatingOrDeletingError( - error.GetMessage()); - } - } - - // We need to cleanup WriteBackCache file and directories - if (p->WriteBackCacheInitialized) { - auto error = UnlockAndDeleteFile( - TFsPath(p->Config->GetWriteBackCachePath()) / - p->Config->GetFileSystemId() / p->SessionId, - p->WriteBackCacheFileLock); - if (HasError(error)) { - ReportWriteBackCacheCreatingOrDeletingError( - error.GetMessage()); - } - } - - if (p->DirectoryHandlesStorageInitialized) { - auto error = UnlockAndDeleteFile( - TFsPath( - p->Config->GetDirectoryHandlesStoragePath()) / - p->Config->GetFileSystemId() / p->SessionId, - p->DirectoryHandlesStorageFileLock); - if (HasError(error)) { - ReportDirectoryHandlesStorageError( - error.GetMessage()); - } - } - - s.SetValue(); - }); + p->StopAsyncOnCompletionQueueStopped(std::move(s)); }; CompletionQueue->StopAsync(FUSE_ERROR).Subscribe( @@ -991,7 +924,6 @@ class TFileSystemLoop final } } - TWriteBackCache writeBackCache; if (FileSystemConfig->GetServerWriteBackCacheEnabled()) { if (Config->GetWriteBackCachePath()) { auto path = TFsPath(Config->GetWriteBackCachePath()) / @@ -1013,7 +945,7 @@ class TFileSystemLoop final return error; } - writeBackCache = TWriteBackCache( + WriteBackCache = TWriteBackCache( Session, Scheduler, Timer, @@ -1030,7 +962,6 @@ class TFileSystemLoop final Config->GetWriteBackCacheFlushMaxSumWriteRequestsSize(), FileSystemConfig->GetZeroCopyWriteEnabled() ); - WriteBackCacheInitialized = true; } else { ReportWriteBackCacheCreatingOrDeletingError(Sprintf( "[f:%s][c:%s] Error initializing WriteBackCache: " @@ -1078,7 +1009,7 @@ class TFileSystemLoop final CompletionQueue, std::move(handleOpsQueue), std::move(directoryHandlesStorage), - std::move(writeBackCache)); + WriteBackCache); RequestStats->RegisterIncompleteRequestProvider(CompletionQueue); @@ -1243,6 +1174,127 @@ class TFileSystemLoop final FileSystem->Init(); } + void StopAsyncOnCompletionQueueStopped(TPromise stopCompleted) + { + if (WriteBackCache && !WriteBackCache.IsEmpty()) { + STORAGE_INFO( + "[f:%s][c:%s] WriteBackCache is not empty, starting " + "FlushAllData", + Config->GetFileSystemId().Quote().c_str(), + Config->GetClientId().Quote().c_str()); + + WriteBackCache.FlushAllData().Subscribe( + [w = weak_from_this(), + s = std::move(stopCompleted)](const TFuture& f) mutable + { + f.GetValue(); + if (auto p = w.lock()) { + p->StopAsyncOnWriteBackCacheFlushed(std::move(s)); + } else { + s.SetValue(); + } + }); + } else { + StopAsyncDestroySession(std::move(stopCompleted)); + } + } + + void StopAsyncOnWriteBackCacheFlushed(TPromise stopCompleted) + { + Y_ABORT_UNLESS( + WriteBackCache && WriteBackCache.IsEmpty(), + "WriteBackCache was not emptied after FlushAllData"); + + STORAGE_INFO( + "[f:%s][c:%s] completed FlushAllData", + Config->GetFileSystemId().Quote().c_str(), + Config->GetClientId().Quote().c_str()); + + StopAsyncDestroySession(std::move(stopCompleted)); + } + + void StopAsyncDestroySession(TPromise stopCompleted) + { + FuseLoop->Unmount(); + FuseLoop = nullptr; + + auto callContext = MakeIntrusive( + Config->GetFileSystemId(), + CreateRequestId()); + callContext->RequestType = EFileStoreRequest::DestroySession; + RequestStats->RequestStarted(Log, *callContext); + + Session->DestroySession().Subscribe( + [w = weak_from_this(), + s = std::move(stopCompleted), + callContext = std::move(callContext)](const auto& f) mutable + { + auto p = w.lock(); + if (!p) { + s.SetValue(); + return; + } + p->StopAsyncOnSessionDestroyed( + *callContext, + f.GetValue(), + std::move(s)); + }); + } + + void StopAsyncOnSessionDestroyed( + TCallContext& callContext, + const NProto::TDestroySessionResponse& response, + TPromise stopCompleted) + { + RequestStats->RequestCompleted( + Log, + callContext, + response.GetError()); + + StatsRegistry->Unregister( + Config->GetFileSystemId(), + Config->GetClientId()); + + // We need to cleanup HandleOpsQueue file and directories + if (HandleOpsQueueInitialized) { + auto error = UnlockAndDeleteFile( + TFsPath(Config->GetHandleOpsQueuePath()) / + Config->GetFileSystemId() / SessionId, + HandleOpsQueueFileLock); + if (HasError(error)) { + ReportHandleOpsQueueCreatingOrDeletingError(error.GetMessage()); + } + } + + // We need to cleanup WriteBackCache file and directories + if (WriteBackCache) { + // Deleting file when it contains unflushed requests + // will result in data loss + Y_ABORT_UNLESS(WriteBackCache.IsEmpty()); + WriteBackCache = {}; + + auto error = UnlockAndDeleteFile( + TFsPath(Config->GetWriteBackCachePath()) / + Config->GetFileSystemId() / SessionId, + WriteBackCacheFileLock); + if (HasError(error)) { + ReportWriteBackCacheCreatingOrDeletingError(error.GetMessage()); + } + } + + if (DirectoryHandlesStorageInitialized) { + auto error = UnlockAndDeleteFile( + TFsPath(Config->GetDirectoryHandlesStoragePath()) / + Config->GetFileSystemId() / SessionId, + DirectoryHandlesStorageFileLock); + if (HasError(error)) { + ReportDirectoryHandlesStorageError(error.GetMessage()); + } + } + + stopCompleted.SetValue(); + } + void Destroy() { STORAGE_INFO("[f:%s][c:%s] got destroy request", From 787287792072ab810e105498fa8da42870e827f3 Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Mon, 24 Nov 2025 14:13:42 +0100 Subject: [PATCH 2/8] issue-1751: Restore and drain WriteBackCache at session recreation --- cloud/filestore/libs/vfs_fuse/fs_impl.h | 17 +- .../filestore/libs/vfs_fuse/fs_impl_data.cpp | 181 +++++++++++------- cloud/filestore/libs/vfs_fuse/fs_ut.cpp | 123 ++++++++++++ cloud/filestore/libs/vfs_fuse/loop.cpp | 26 +-- 4 files changed, 268 insertions(+), 79 deletions(-) diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl.h b/cloud/filestore/libs/vfs_fuse/fs_impl.h index 25bd97940bc..94de82c987b 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl.h +++ b/cloud/filestore/libs/vfs_fuse/fs_impl.h @@ -57,6 +57,20 @@ struct TReleaseRequest //////////////////////////////////////////////////////////////////////////////// +enum class EServerWriteBackCacheMode +{ + // The request should go to the session + Disabled, + + // The request should go to the WriteBackCache + Enabled, + + // The request should wait until the cache is empty then go to the session + Draining +}; + +//////////////////////////////////////////////////////////////////////////////// + class TFileSystem final : public IFileSystem , public std::enable_shared_from_this @@ -395,7 +409,8 @@ class TFileSystem final fuse_ino_t ino, uint64_t fh); - bool ShouldUseServerWriteBackCache(const fuse_file_info* fi) const; + EServerWriteBackCacheMode GetServerWriteBackCacheMode( + const fuse_file_info* fi) const; bool UpdateNodeCache( const NProto::TNodeAttr& attrs, diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp index 95d9ce7bb28..7d94425b206 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp @@ -413,45 +413,67 @@ void TFileSystem::Write( request->SetBuffer(alignedBuffer.TakeBuffer()); const auto size = buffer.size(); + const auto wbcMode = GetServerWriteBackCacheMode(fi); - if (ShouldUseServerWriteBackCache(fi)) { + const auto handle = fi->fh; + const auto reqId = callContext->RequestId; + + auto callback = [=, ptr = weak_from_this()](const auto& future) + { + auto self = ptr.lock(); + if (!self) { + return; + } + + const auto& response = future.GetValue(); + const auto& error = response.GetError(); + + if (wbcMode != EServerWriteBackCacheMode::Enabled) { + self->FSyncQueue + ->Dequeue(reqId, error, TNodeId{ino}, THandle{handle}); + } + + if (self->CheckResponse(self, *callContext, req, response)) { + self->ReplyWrite(*callContext, error, req, size); + } + }; + + if (wbcMode == EServerWriteBackCacheMode::Enabled) { WriteBackCache.WriteData(callContext, std::move(request)) - .Subscribe( - [=, - ptr = weak_from_this()] (const auto& future) - { - auto self = ptr.lock(); - if (!self) { - return; - } - - const auto& response = future.GetValue(); - const auto& error = response.GetError(); - - if (CheckResponse(self, *callContext, req, response)) { - self->ReplyWrite(*callContext, error, req, size); - } - }); + .Subscribe(std::move(callback)); return; } - const auto handle = fi->fh; - const auto reqId = callContext->RequestId; FSyncQueue->Enqueue(reqId, TNodeId {ino}, THandle {handle}); - Session->WriteData(callContext, std::move(request)) - .Subscribe([=, ptr = weak_from_this()] (const auto& future) { - auto self = ptr.lock(); - if (!self) { - return; - } + if (wbcMode == EServerWriteBackCacheMode::Disabled) { + Session->WriteData(callContext, std::move(request)) + .Subscribe(std::move(callback)); + return; + } - const auto& response = future.GetValue(); - const auto& error = response.GetError(); - self->FSyncQueue->Dequeue(reqId, error, TNodeId {ino}, THandle {handle}); + Y_ABORT_UNLESS( + wbcMode == EServerWriteBackCacheMode::Draining, + "Invalid EServerWriteBackCacheMode value = %d", + wbcMode); - if (CheckResponse(self, *callContext, req, response)) { - self->ReplyWrite(*callContext, error, req, size); + auto flushFuture = WriteBackCache.FlushNodeData(request->GetNodeId()); + if (flushFuture.HasValue()) { + Session->WriteData(callContext, std::move(request)) + .Subscribe(std::move(callback)); + return; + } + + flushFuture.Subscribe( + [ptr = weak_from_this(), + callback = std::move(callback), + callContext = std::move(callContext), + request = std::move(request)](const auto& f) mutable + { + f.GetValue(); + if (auto self = ptr.lock()) { + self->Session->WriteData(callContext, std::move(request)) + .Subscribe(std::move(callback)); } }); } @@ -577,43 +599,66 @@ void TFileSystem::WriteBuf( request->SetHandle(fi->fh); request->SetOffset(offset); - if (ShouldUseServerWriteBackCache(fi)) { + const auto wbcMode = GetServerWriteBackCacheMode(fi); + const auto handle = fi->fh; + const auto reqId = callContext->RequestId; + + auto callback = [=, ptr = weak_from_this()](const auto& future) + { + auto self = ptr.lock(); + if (!self) { + return; + } + + const auto& response = future.GetValue(); + const auto& error = response.GetError(); + + if (wbcMode != EServerWriteBackCacheMode::Enabled) { + self->FSyncQueue + ->Dequeue(reqId, error, TNodeId{ino}, THandle{handle}); + } + + if (self->CheckResponse(self, *callContext, req, response)) { + self->ReplyWrite(*callContext, error, req, size); + } + }; + + if (wbcMode == EServerWriteBackCacheMode::Enabled) { WriteBackCache.WriteData(callContext, std::move(request)) - .Subscribe( - [=, ptr = weak_from_this()] (const auto& future) - { - auto self = ptr.lock(); - if (!self) { - return; - } - - const auto& response = future.GetValue(); - const auto& error = response.GetError(); - - if (CheckResponse(self, *callContext, req, response)) { - self->ReplyWrite(*callContext, error, req, size); - } - }); + .Subscribe(std::move(callback)); return; } - const auto handle = fi->fh; - const auto reqId = callContext->RequestId; - FSyncQueue->Enqueue(reqId, TNodeId {ino}, THandle {handle}); + FSyncQueue->Enqueue(reqId, TNodeId{ino}, THandle{handle}); - Session->WriteData(callContext, std::move(request)) - .Subscribe([=, ptr = weak_from_this()] (const auto& future) { - auto self = ptr.lock(); - if (!self) { - return; - } + if (wbcMode == EServerWriteBackCacheMode::Disabled) { + Session->WriteData(callContext, std::move(request)) + .Subscribe(std::move(callback)); + return; + } - const auto& response = future.GetValue(); - const auto& error = response.GetError(); - self->FSyncQueue->Dequeue(reqId, error, TNodeId {ino}, THandle {handle}); + Y_ABORT_UNLESS( + wbcMode == EServerWriteBackCacheMode::Draining, + "Invalid EServerWriteBackCacheMode value = %d", + wbcMode); - if (CheckResponse(self, *callContext, req, response)) { - self->ReplyWrite(*callContext, error, req, size); + auto flushFuture = WriteBackCache.FlushNodeData(request->GetNodeId()); + if (flushFuture.HasValue()) { + Session->WriteData(callContext, std::move(request)) + .Subscribe(std::move(callback)); + return; + } + + flushFuture.Subscribe( + [ptr = weak_from_this(), + callback = std::move(callback), + callContext = std::move(callContext), + request = std::move(request)](const auto& f) mutable + { + f.GetValue(); + if (auto self = ptr.lock()) { + self->Session->WriteData(callContext, std::move(request)) + .Subscribe(std::move(callback)); } }); } @@ -960,17 +1005,23 @@ void TFileSystem::FSyncDir( //////////////////////////////////////////////////////////////////////////////// -bool TFileSystem::ShouldUseServerWriteBackCache(const fuse_file_info* fi) const +EServerWriteBackCacheMode TFileSystem::GetServerWriteBackCacheMode( + const fuse_file_info* fi) const { - if (!WriteBackCache || !Config->GetServerWriteBackCacheEnabled()) { - return false; + if (!WriteBackCache) { + return EServerWriteBackCacheMode::Disabled; } if (fi->flags & O_DIRECT) { - return false; + return EServerWriteBackCacheMode::Disabled; } - return true; + if (Config->GetServerWriteBackCacheEnabled()) { + return EServerWriteBackCacheMode::Enabled; + } + + return WriteBackCache.IsEmpty() ? EServerWriteBackCacheMode::Disabled + : EServerWriteBackCacheMode::Draining; } } // namespace NCloud::NFileStore::NFuse diff --git a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp index 88cf8d55e58..a7605b894fe 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp @@ -2953,6 +2953,129 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) UNIT_ASSERT_VALUES_EQUAL(requestCount, requestCountSensor->GetAtomic()); UNIT_ASSERT(!path.Exists()); } + + Y_UNIT_TEST(ShouldRestoreAndDrainCacheAfterSessionRestart) + { + const TString sessionId = CreateGuidAsString(); + + std::atomic writeDataCalled = 0; + std::atomic writeDataCalled2 = 0; + + const ui64 nodeId = 123; + const ui64 handleId = 456; + + { + NProto::TFileStoreFeatures features; + features.SetServerWriteBackCacheEnabled(true); + + TBootstrap bootstrap( + CreateWallClockTimer(), + CreateScheduler(), + features); + + bootstrap.Service->CreateSessionHandler = [&](auto, auto) + { + NProto::TCreateSessionResponse result; + result.MutableSession()->SetSessionId(sessionId); + result.MutableFileStore()->SetBlockSize(4096); + result.MutableFileStore()->MutableFeatures()->CopyFrom( + features); + result.MutableFileStore()->SetFileSystemId(FileSystemId); + return MakeFuture(result); + }; + + bootstrap.Service->WriteDataHandler = [&](auto, const auto&) + { + writeDataCalled++; + NProto::TWriteDataResponse result; + return MakeFuture(result); + }; + + bootstrap.Start(); + Y_DEFER + { + bootstrap.Stop(); + }; + + auto reqWrite = std::make_shared( + nodeId, + handleId, + 0, + CreateBuffer(4096, 'a')); + reqWrite->In->Body.flags |= O_WRONLY; + auto write = bootstrap.Fuse->SendRequest(reqWrite); + UNIT_ASSERT_NO_EXCEPTION(write.GetValue(WaitTimeout)); + + auto suspend = bootstrap.Loop->SuspendAsync(); + UNIT_ASSERT(suspend.Wait(WaitTimeout)); + } + + // Since write-back cache was enabled, the actual write didn't happen + // and the request is stored in the persistent queue + UNIT_ASSERT_VALUES_EQUAL(0, writeDataCalled.load()); + + { + NProto::TFileStoreFeatures features; + features.SetServerWriteBackCacheEnabled(true); + + TBootstrap bootstrap( + CreateWallClockTimer(), + CreateScheduler(), + features); + + bootstrap.Service->CreateSessionHandler = [&](auto, auto) + { + // It is expected that the unwritten requests are restored from + // the persistent queue even if write-back cache is disabled now + NProto::TFileStoreFeatures features; + features.SetServerWriteBackCacheEnabled(false); + + NProto::TCreateSessionResponse result; + result.MutableSession()->SetSessionId(sessionId); + result.MutableFileStore()->SetBlockSize(4096); + result.MutableFileStore()->MutableFeatures()->CopyFrom( + features); + result.MutableFileStore()->SetFileSystemId(FileSystemId); + return MakeFuture(result); + }; + + bootstrap.Service->WriteDataHandler = [&](auto, const auto&) + { + writeDataCalled2++; + NProto::TWriteDataResponse result; + return MakeFuture(result); + }; + + bootstrap.Start(); + Y_DEFER + { + bootstrap.Stop(); + }; + + UNIT_ASSERT_VALUES_EQUAL(0, writeDataCalled2.load()); + + auto flush = + bootstrap.Fuse->SendRequest(nodeId, handleId); + UNIT_ASSERT_NO_EXCEPTION(flush.GetValue(WaitTimeout)); + + // cache should be flushed + UNIT_ASSERT_VALUES_EQUAL(0, writeDataCalled.load()); + UNIT_ASSERT_VALUES_EQUAL(1, writeDataCalled2.load()); + + auto reqWrite = std::make_shared( + nodeId, + handleId, + 0, + CreateBuffer(4096, 'a')); + reqWrite->In->Body.flags |= O_WRONLY; + auto write = bootstrap.Fuse->SendRequest(reqWrite); + UNIT_ASSERT_NO_EXCEPTION(write.GetValue(WaitTimeout)); + + // Cache is drained and disabled - new requests go directly + // to the session + UNIT_ASSERT_VALUES_EQUAL(2, writeDataCalled2.load()); + } + } } } // namespace NCloud::NFileStore::NFuse diff --git a/cloud/filestore/libs/vfs_fuse/loop.cpp b/cloud/filestore/libs/vfs_fuse/loop.cpp index 6d63c465a9d..bb3ed513278 100644 --- a/cloud/filestore/libs/vfs_fuse/loop.cpp +++ b/cloud/filestore/libs/vfs_fuse/loop.cpp @@ -924,12 +924,13 @@ class TFileSystemLoop final } } - if (FileSystemConfig->GetServerWriteBackCacheEnabled()) { - if (Config->GetWriteBackCachePath()) { - auto path = TFsPath(Config->GetWriteBackCachePath()) / - FileSystemConfig->GetFileSystemId() / - SessionId; + if (Config->GetWriteBackCachePath()) { + auto path = TFsPath(Config->GetWriteBackCachePath()) / + FileSystemConfig->GetFileSystemId() / SessionId; + if (path.Exists() || + FileSystemConfig->GetServerWriteBackCacheEnabled()) + { auto error = CreateAndLockFile( path, WriteBackCacheFileName, @@ -960,15 +961,14 @@ class TFileSystemLoop final Config->GetWriteBackCacheFlushMaxWriteRequestSize(), Config->GetWriteBackCacheFlushMaxWriteRequestsCount(), Config->GetWriteBackCacheFlushMaxSumWriteRequestsSize(), - FileSystemConfig->GetZeroCopyWriteEnabled() - ); - } else { - ReportWriteBackCacheCreatingOrDeletingError(Sprintf( - "[f:%s][c:%s] Error initializing WriteBackCache: " - "WriteBackCachePath is not set", - Config->GetFileSystemId().Quote().c_str(), - Config->GetClientId().Quote().c_str())); + FileSystemConfig->GetZeroCopyWriteEnabled()); } + } else if (FileSystemConfig->GetServerWriteBackCacheEnabled()) { + ReportWriteBackCacheCreatingOrDeletingError(Sprintf( + "[f:%s][c:%s] Error initializing WriteBackCache: " + "WriteBackCachePath is not set", + Config->GetFileSystemId().Quote().c_str(), + Config->GetClientId().Quote().c_str())); } TDirectoryHandlesStoragePtr directoryHandlesStorage; From 5dc74cd3b130dffedd7c24c740998b075abf13d8 Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Thu, 4 Dec 2025 13:33:30 +0100 Subject: [PATCH 3/8] Comments fix --- cloud/filestore/libs/vfs_fuse/fs_impl.h | 13 ++-- .../filestore/libs/vfs_fuse/fs_impl_data.cpp | 32 +++++----- cloud/filestore/libs/vfs_fuse/fs_ut.cpp | 63 +++++++------------ 3 files changed, 45 insertions(+), 63 deletions(-) diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl.h b/cloud/filestore/libs/vfs_fuse/fs_impl.h index 94de82c987b..27ac0acc6f6 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl.h +++ b/cloud/filestore/libs/vfs_fuse/fs_impl.h @@ -57,15 +57,18 @@ struct TReleaseRequest //////////////////////////////////////////////////////////////////////////////// -enum class EServerWriteBackCacheMode +enum class EServerWriteBackCacheState { - // The request should go to the session + // Requests should bypass WriteBackCache and go directly to the session + // (even if WriteBackCache is initialized) Disabled, - // The request should go to the WriteBackCache + // Requests should go to the WriteBackCache Enabled, - // The request should wait until the cache is empty then go to the session + // WriteBackCache is in the transition from Enabled to Disabled state. + // Requests should wait until WriteBackCache is flushed and then go + // directly to the session Draining }; @@ -409,7 +412,7 @@ class TFileSystem final fuse_ino_t ino, uint64_t fh); - EServerWriteBackCacheMode GetServerWriteBackCacheMode( + EServerWriteBackCacheState GetServerWriteBackCacheMode( const fuse_file_info* fi) const; bool UpdateNodeCache( diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp index 7d94425b206..a742ffffa73 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp @@ -428,7 +428,7 @@ void TFileSystem::Write( const auto& response = future.GetValue(); const auto& error = response.GetError(); - if (wbcMode != EServerWriteBackCacheMode::Enabled) { + if (wbcMode != EServerWriteBackCacheState::Enabled) { self->FSyncQueue ->Dequeue(reqId, error, TNodeId{ino}, THandle{handle}); } @@ -438,7 +438,7 @@ void TFileSystem::Write( } }; - if (wbcMode == EServerWriteBackCacheMode::Enabled) { + if (wbcMode == EServerWriteBackCacheState::Enabled) { WriteBackCache.WriteData(callContext, std::move(request)) .Subscribe(std::move(callback)); return; @@ -446,15 +446,15 @@ void TFileSystem::Write( FSyncQueue->Enqueue(reqId, TNodeId {ino}, THandle {handle}); - if (wbcMode == EServerWriteBackCacheMode::Disabled) { + if (wbcMode == EServerWriteBackCacheState::Disabled) { Session->WriteData(callContext, std::move(request)) .Subscribe(std::move(callback)); return; } Y_ABORT_UNLESS( - wbcMode == EServerWriteBackCacheMode::Draining, - "Invalid EServerWriteBackCacheMode value = %d", + wbcMode == EServerWriteBackCacheState::Draining, + "Invalid EServerWriteBackCacheState value = %d", wbcMode); auto flushFuture = WriteBackCache.FlushNodeData(request->GetNodeId()); @@ -613,7 +613,7 @@ void TFileSystem::WriteBuf( const auto& response = future.GetValue(); const auto& error = response.GetError(); - if (wbcMode != EServerWriteBackCacheMode::Enabled) { + if (wbcMode != EServerWriteBackCacheState::Enabled) { self->FSyncQueue ->Dequeue(reqId, error, TNodeId{ino}, THandle{handle}); } @@ -623,7 +623,7 @@ void TFileSystem::WriteBuf( } }; - if (wbcMode == EServerWriteBackCacheMode::Enabled) { + if (wbcMode == EServerWriteBackCacheState::Enabled) { WriteBackCache.WriteData(callContext, std::move(request)) .Subscribe(std::move(callback)); return; @@ -631,15 +631,15 @@ void TFileSystem::WriteBuf( FSyncQueue->Enqueue(reqId, TNodeId{ino}, THandle{handle}); - if (wbcMode == EServerWriteBackCacheMode::Disabled) { + if (wbcMode == EServerWriteBackCacheState::Disabled) { Session->WriteData(callContext, std::move(request)) .Subscribe(std::move(callback)); return; } Y_ABORT_UNLESS( - wbcMode == EServerWriteBackCacheMode::Draining, - "Invalid EServerWriteBackCacheMode value = %d", + wbcMode == EServerWriteBackCacheState::Draining, + "Invalid EServerWriteBackCacheState value = %d", wbcMode); auto flushFuture = WriteBackCache.FlushNodeData(request->GetNodeId()); @@ -1005,23 +1005,23 @@ void TFileSystem::FSyncDir( //////////////////////////////////////////////////////////////////////////////// -EServerWriteBackCacheMode TFileSystem::GetServerWriteBackCacheMode( +EServerWriteBackCacheState TFileSystem::GetServerWriteBackCacheMode( const fuse_file_info* fi) const { if (!WriteBackCache) { - return EServerWriteBackCacheMode::Disabled; + return EServerWriteBackCacheState::Disabled; } if (fi->flags & O_DIRECT) { - return EServerWriteBackCacheMode::Disabled; + return EServerWriteBackCacheState::Disabled; } if (Config->GetServerWriteBackCacheEnabled()) { - return EServerWriteBackCacheMode::Enabled; + return EServerWriteBackCacheState::Enabled; } - return WriteBackCache.IsEmpty() ? EServerWriteBackCacheMode::Disabled - : EServerWriteBackCacheMode::Draining; + return WriteBackCache.IsEmpty() ? EServerWriteBackCacheState::Disabled + : EServerWriteBackCacheState::Draining; } } // namespace NCloud::NFileStore::NFuse diff --git a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp index a7605b894fe..571e531d78f 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp @@ -188,13 +188,12 @@ struct TBootstrap DirectoryHandlesStoragePath = proto.GetDirectoryHandlesStoragePath(); } - if (featuresConfig.GetServerWriteBackCacheEnabled()) { - proto.SetWriteBackCachePath(TempDir.Path() / "WriteBackCache"); - // minimum possible capacity - proto.SetWriteBackCacheCapacity(writeBackCacheCapacity); - proto.SetWriteBackCacheAutomaticFlushPeriod( - writeBackCacheAutomaticFlushPeriodMs); - } + // WriteBackCache should be configured even if it is disabled + proto.SetWriteBackCachePath(TempDir.Path() / "WriteBackCache"); + // minimum possible capacity + proto.SetWriteBackCacheCapacity(writeBackCacheCapacity); + proto.SetWriteBackCacheAutomaticFlushPeriod( + writeBackCacheAutomaticFlushPeriodMs); auto config = std::make_shared(std::move(proto)); Loop = NFuse::CreateFuseLoop( @@ -2964,16 +2963,20 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) const ui64 nodeId = 123; const ui64 handleId = 456; + auto createBootstrap = [&](bool serverWriteBackCacheEnabled, + std::atomic& counter) { NProto::TFileStoreFeatures features; - features.SetServerWriteBackCacheEnabled(true); + features.SetServerWriteBackCacheEnabled( + serverWriteBackCacheEnabled); TBootstrap bootstrap( CreateWallClockTimer(), CreateScheduler(), features); - bootstrap.Service->CreateSessionHandler = [&](auto, auto) + bootstrap.Service->CreateSessionHandler = + [features, &sessionId](auto, auto) { NProto::TCreateSessionResponse result; result.MutableSession()->SetSessionId(sessionId); @@ -2984,13 +2987,19 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) return MakeFuture(result); }; - bootstrap.Service->WriteDataHandler = [&](auto, const auto&) + bootstrap.Service->WriteDataHandler = [&counter](auto, const auto&) { - writeDataCalled++; + counter++; NProto::TWriteDataResponse result; return MakeFuture(result); }; + return bootstrap; + }; + + { + auto bootstrap = createBootstrap(true, writeDataCalled); + bootstrap.Start(); Y_DEFER { @@ -3015,36 +3024,7 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) UNIT_ASSERT_VALUES_EQUAL(0, writeDataCalled.load()); { - NProto::TFileStoreFeatures features; - features.SetServerWriteBackCacheEnabled(true); - - TBootstrap bootstrap( - CreateWallClockTimer(), - CreateScheduler(), - features); - - bootstrap.Service->CreateSessionHandler = [&](auto, auto) - { - // It is expected that the unwritten requests are restored from - // the persistent queue even if write-back cache is disabled now - NProto::TFileStoreFeatures features; - features.SetServerWriteBackCacheEnabled(false); - - NProto::TCreateSessionResponse result; - result.MutableSession()->SetSessionId(sessionId); - result.MutableFileStore()->SetBlockSize(4096); - result.MutableFileStore()->MutableFeatures()->CopyFrom( - features); - result.MutableFileStore()->SetFileSystemId(FileSystemId); - return MakeFuture(result); - }; - - bootstrap.Service->WriteDataHandler = [&](auto, const auto&) - { - writeDataCalled2++; - NProto::TWriteDataResponse result; - return MakeFuture(result); - }; + auto bootstrap = createBootstrap(false, writeDataCalled2); bootstrap.Start(); Y_DEFER @@ -3059,7 +3039,6 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) UNIT_ASSERT_NO_EXCEPTION(flush.GetValue(WaitTimeout)); // cache should be flushed - UNIT_ASSERT_VALUES_EQUAL(0, writeDataCalled.load()); UNIT_ASSERT_VALUES_EQUAL(1, writeDataCalled2.load()); auto reqWrite = std::make_shared( From cbc29905d1f47f3278de05a5b6aa40639baa4f4c Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Thu, 4 Dec 2025 14:40:57 +0100 Subject: [PATCH 4/8] Refactoring: code deduplication --- cloud/filestore/libs/vfs_fuse/fs_impl.h | 8 ++ .../filestore/libs/vfs_fuse/fs_impl_data.cpp | 75 +++---------------- 2 files changed, 20 insertions(+), 63 deletions(-) diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl.h b/cloud/filestore/libs/vfs_fuse/fs_impl.h index 27ac0acc6f6..e859a5d8487 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl.h +++ b/cloud/filestore/libs/vfs_fuse/fs_impl.h @@ -468,6 +468,14 @@ class TFileSystem final void ScheduleProcessHandleOpsQueue(); void ProcessHandleOpsQueue(); + void DoWrite( + TCallContextPtr callContext, + fuse_req_t req, + fuse_ino_t ino, + std::shared_ptr request, + ui64 size, + fuse_file_info* fi); + #define FILESYSTEM_REPLY_IMPL(name, ...) \ template \ int Reply##name( \ diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp index a742ffffa73..7570a62885c 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp @@ -412,7 +412,17 @@ void TFileSystem::Write( request->SetBufferOffset(alignedBuffer.AlignedDataOffset()); request->SetBuffer(alignedBuffer.TakeBuffer()); - const auto size = buffer.size(); + DoWrite(callContext, req, ino, std::move(request), buffer.size(), fi); +} + +void TFileSystem::DoWrite( + TCallContextPtr callContext, + fuse_req_t req, + fuse_ino_t ino, + std::shared_ptr request, + ui64 size, + fuse_file_info* fi) +{ const auto wbcMode = GetServerWriteBackCacheMode(fi); const auto handle = fi->fh; @@ -599,68 +609,7 @@ void TFileSystem::WriteBuf( request->SetHandle(fi->fh); request->SetOffset(offset); - const auto wbcMode = GetServerWriteBackCacheMode(fi); - const auto handle = fi->fh; - const auto reqId = callContext->RequestId; - - auto callback = [=, ptr = weak_from_this()](const auto& future) - { - auto self = ptr.lock(); - if (!self) { - return; - } - - const auto& response = future.GetValue(); - const auto& error = response.GetError(); - - if (wbcMode != EServerWriteBackCacheState::Enabled) { - self->FSyncQueue - ->Dequeue(reqId, error, TNodeId{ino}, THandle{handle}); - } - - if (self->CheckResponse(self, *callContext, req, response)) { - self->ReplyWrite(*callContext, error, req, size); - } - }; - - if (wbcMode == EServerWriteBackCacheState::Enabled) { - WriteBackCache.WriteData(callContext, std::move(request)) - .Subscribe(std::move(callback)); - return; - } - - FSyncQueue->Enqueue(reqId, TNodeId{ino}, THandle{handle}); - - if (wbcMode == EServerWriteBackCacheState::Disabled) { - Session->WriteData(callContext, std::move(request)) - .Subscribe(std::move(callback)); - return; - } - - Y_ABORT_UNLESS( - wbcMode == EServerWriteBackCacheState::Draining, - "Invalid EServerWriteBackCacheState value = %d", - wbcMode); - - auto flushFuture = WriteBackCache.FlushNodeData(request->GetNodeId()); - if (flushFuture.HasValue()) { - Session->WriteData(callContext, std::move(request)) - .Subscribe(std::move(callback)); - return; - } - - flushFuture.Subscribe( - [ptr = weak_from_this(), - callback = std::move(callback), - callContext = std::move(callContext), - request = std::move(request)](const auto& f) mutable - { - f.GetValue(); - if (auto self = ptr.lock()) { - self->Session->WriteData(callContext, std::move(request)) - .Subscribe(std::move(callback)); - } - }); + DoWrite(callContext, req, ino, std::move(request), size, fi); } void TFileSystem::FAllocate( From 5455feac55cd80cafa9aa4a8ebab561031b2eca0 Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Thu, 4 Dec 2025 14:52:20 +0100 Subject: [PATCH 5/8] Remove micro-optimization --- .../filestore/libs/vfs_fuse/fs_impl_data.cpp | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp index 7570a62885c..97fd22cb4a5 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp @@ -467,25 +467,19 @@ void TFileSystem::DoWrite( "Invalid EServerWriteBackCacheState value = %d", wbcMode); - auto flushFuture = WriteBackCache.FlushNodeData(request->GetNodeId()); - if (flushFuture.HasValue()) { - Session->WriteData(callContext, std::move(request)) - .Subscribe(std::move(callback)); - return; - } - - flushFuture.Subscribe( - [ptr = weak_from_this(), - callback = std::move(callback), - callContext = std::move(callContext), - request = std::move(request)](const auto& f) mutable - { - f.GetValue(); - if (auto self = ptr.lock()) { - self->Session->WriteData(callContext, std::move(request)) - .Subscribe(std::move(callback)); - } - }); + WriteBackCache.FlushNodeData(request->GetNodeId()) + .Subscribe( + [ptr = weak_from_this(), + callback = std::move(callback), + callContext = std::move(callContext), + request = std::move(request)](const auto& f) mutable + { + f.GetValue(); + if (auto self = ptr.lock()) { + self->Session->WriteData(callContext, std::move(request)) + .Subscribe(std::move(callback)); + } + }); } void TFileSystem::WriteBufLocal( From ac1e02127e61b954cbba7590d51c75a906510ddf Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Thu, 4 Dec 2025 14:55:53 +0100 Subject: [PATCH 6/8] GetServerWriteBackCacheMode -> GetServerWriteBackCacheState --- cloud/filestore/libs/vfs_fuse/fs_impl.h | 2 +- cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl.h b/cloud/filestore/libs/vfs_fuse/fs_impl.h index e859a5d8487..dfb38bca1d3 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl.h +++ b/cloud/filestore/libs/vfs_fuse/fs_impl.h @@ -412,7 +412,7 @@ class TFileSystem final fuse_ino_t ino, uint64_t fh); - EServerWriteBackCacheState GetServerWriteBackCacheMode( + EServerWriteBackCacheState GetServerWriteBackCacheState( const fuse_file_info* fi) const; bool UpdateNodeCache( diff --git a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp index 97fd22cb4a5..5a32abf90fd 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_impl_data.cpp @@ -423,7 +423,7 @@ void TFileSystem::DoWrite( ui64 size, fuse_file_info* fi) { - const auto wbcMode = GetServerWriteBackCacheMode(fi); + const auto wbcMode = GetServerWriteBackCacheState(fi); const auto handle = fi->fh; const auto reqId = callContext->RequestId; @@ -948,7 +948,7 @@ void TFileSystem::FSyncDir( //////////////////////////////////////////////////////////////////////////////// -EServerWriteBackCacheState TFileSystem::GetServerWriteBackCacheMode( +EServerWriteBackCacheState TFileSystem::GetServerWriteBackCacheState( const fuse_file_info* fi) const { if (!WriteBackCache) { From 678cb02cac0f8ee07b6a8539d1546631557c5611 Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Sat, 6 Dec 2025 10:02:31 +0100 Subject: [PATCH 7/8] Add more checks to the test --- cloud/filestore/libs/vfs_fuse/fs_ut.cpp | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp index 571e531d78f..22960735514 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -3023,6 +3024,14 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) // and the request is stored in the persistent queue UNIT_ASSERT_VALUES_EQUAL(0, writeDataCalled.load()); + auto path = TempDir.Path() / "WriteBackCache" / FileSystemId / + sessionId / "write_back_cache"; + + { + TFileRingBuffer ringBuffer(path, WriteBackCacheCapacity); + UNIT_ASSERT(!ringBuffer.Empty()); + } + { auto bootstrap = createBootstrap(false, writeDataCalled2); @@ -3053,7 +3062,27 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) // Cache is drained and disabled - new requests go directly // to the session UNIT_ASSERT_VALUES_EQUAL(2, writeDataCalled2.load()); + + auto suspend = bootstrap.Loop->SuspendAsync(); + UNIT_ASSERT(suspend.Wait(WaitTimeout)); + } + + { + TFileRingBuffer ringBuffer(path, WriteBackCacheCapacity); + UNIT_ASSERT(ringBuffer.Empty()); } + + { + auto bootstrap = createBootstrap(false, writeDataCalled2); + + bootstrap.Start(); + Y_DEFER + { + bootstrap.Stop(); + }; + } + + UNIT_ASSERT(!path.Exists()); } } From 8bc22e330218b0abb176c462e5d39414082db242 Mon Sep 17 00:00:00 2001 From: Andrei Nasonov Date: Sat, 6 Dec 2025 19:30:20 +0100 Subject: [PATCH 8/8] Test adjustment --- cloud/filestore/libs/vfs_fuse/fs_ut.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp index 22960735514..3a474262edd 100644 --- a/cloud/filestore/libs/vfs_fuse/fs_ut.cpp +++ b/cloud/filestore/libs/vfs_fuse/fs_ut.cpp @@ -2855,7 +2855,7 @@ Y_UNIT_TEST_SUITE(TFileSystemTest) CreateScheduler(), features, /* handleOpsQueueSize= */ 1000, - /* writeBackCacheAutomaticFlushPeriodMs= */ 1000000000, + /* writeBackCacheAutomaticFlushPeriodMs= */ 0, WriteBackCacheCapacity); auto writeDataPromise = NewPromise();