diff --git a/cloud/filestore/libs/diagnostics/critical_events.h b/cloud/filestore/libs/diagnostics/critical_events.h index f502542c3f..0c4d4b3219 100644 --- a/cloud/filestore/libs/diagnostics/critical_events.h +++ b/cloud/filestore/libs/diagnostics/critical_events.h @@ -53,6 +53,7 @@ namespace NCloud::NFileStore{ xxx(UnexpectedLocalNode) \ xxx(NoRenameNodeInDestinationRequest) \ xxx(BadChildRefUponCommitRenameNodeInSource) \ + xxx(FailedToLockNodeRef) \ // FILESTORE_IMPOSSIBLE_EVENTS //////////////////////////////////////////////////////////////////////////////// diff --git a/cloud/filestore/libs/storage/service/service_ut_sharding.cpp b/cloud/filestore/libs/storage/service/service_ut_sharding.cpp index 7cb8324415..ecb03bad3f 100644 --- a/cloud/filestore/libs/storage/service/service_ut_sharding.cpp +++ b/cloud/filestore/libs/storage/service/service_ut_sharding.cpp @@ -4964,6 +4964,331 @@ Y_UNIT_TEST_SUITE(TStorageServiceShardingTest) listNodesResponse.GetNodes(0).GetId()); } } + + SERVICE_TEST_SID_SELECT_IN_LEADER_ONLY( + ShouldLockSourceNodeRefsUponRenameNodeWithDirectoriesInShards) + { + config.SetMultiTabletForwardingEnabled(true); + config.SetDirectoryCreationInShardsEnabled(true); + TTestEnv env({}, config); + env.CreateSubDomain("nfs"); + + ui32 nodeIdx = env.CreateNode("nfs"); + + TServiceClient service(env.GetRuntime(), nodeIdx); + TFileSystemConfig fsConfig; + fsConfig.DirectoryCreationInShardsEnabled = true; + CreateFileSystem(service, fsConfig); + + auto headers = service.InitSession(fsConfig.FsId, "client"); + + // creating 2 dirs - we'll move a file from one dir to another + + ui64 dir1Id = service.CreateNode( + headers, + TCreateNodeArgs::Directory(RootNodeId, "dir1") + )->Record.GetNode().GetId(); + + ui64 dir2Id = service.CreateNode( + headers, + TCreateNodeArgs::Directory(RootNodeId, "dir2") + )->Record.GetNode().GetId(); + + UNIT_ASSERT_VALUES_UNEQUAL(0, ExtractShardNo(dir1Id)); + UNIT_ASSERT_VALUES_UNEQUAL(0, ExtractShardNo(dir2Id)); + UNIT_ASSERT_VALUES_UNEQUAL( + ExtractShardNo(dir1Id), + ExtractShardNo(dir2Id)); + + // creating a file which we will move + + ui64 node1Id = service.CreateNode( + headers, + TCreateNodeArgs::File(dir1Id, "file1") + )->Record.GetNode().GetId(); + + // configuring an interceptor to make RenameNode get stuck in the shard + // in charge of dir1 + + TEventHelper evHelper(env); + evHelper.CatchEvent(TEvIndexTablet::EvRenameNodeInDestinationRequest); + + service.SendRenameNodeRequest( + headers, + dir1Id, + "file1", + dir2Id, + "file2", + 0); + + ui32 iterations = 0; + while (!evHelper.Event && iterations++ < 100) { + env.GetRuntime().DispatchEvents({}, TDuration::MilliSeconds(50)); + } + + UNIT_ASSERT(evHelper.Event); + evHelper.ShouldIntercept = false; + + // listing dir1 - at this point our file should still be seen there + + { + auto listNodesResponse = service.ListNodes( + headers, + fsConfig.FsId, + dir1Id)->Record; + + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NamesSize()); + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NodesSize()); + UNIT_ASSERT_VALUES_EQUAL("file1", listNodesResponse.GetNames(0)); + } + + // unlinking of that file should be unallowed since we have an inflight + // RenameNode operation + + service.SendUnlinkNodeRequest(headers, dir1Id, "file1"); + { + auto unlinkResponse = service.RecvUnlinkNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + E_REJECTED, + unlinkResponse->GetError().GetCode(), + unlinkResponse->GetError().GetMessage()); + } + + // renaming should not be allowed as well + + service.SendRenameNodeRequest( + headers, + dir1Id, + "file1", + dir1Id, + "file1_moved", + 0); + + { + auto renameResponse = service.RecvRenameNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + E_REJECTED, + renameResponse->GetError().GetCode(), + renameResponse->GetError().GetMessage()); + } + + // listing dir1 - our file should still be there + + { + auto listNodesResponse = service.ListNodes( + headers, + fsConfig.FsId, + dir1Id)->Record; + + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NamesSize()); + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NodesSize()); + UNIT_ASSERT_VALUES_EQUAL("file1", listNodesResponse.GetNames(0)); + } + + // unblocking the inflight RenameNode op + + env.GetRuntime().Send(evHelper.Event.Release()); + + { + auto renameResponse = service.RecvRenameNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + S_OK, + renameResponse->GetError().GetCode(), + renameResponse->GetError().GetMessage()); + } + + // now the file should finally be moved + + { + auto listNodesResponse = service.ListNodes( + headers, + fsConfig.FsId, + dir1Id)->Record; + + UNIT_ASSERT_VALUES_EQUAL(0, listNodesResponse.NamesSize()); + UNIT_ASSERT_VALUES_EQUAL(0, listNodesResponse.NodesSize()); + } + + { + auto listNodesResponse = service.ListNodes( + headers, + fsConfig.FsId, + dir2Id)->Record; + + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NamesSize()); + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NodesSize()); + UNIT_ASSERT_VALUES_EQUAL("file2", listNodesResponse.GetNames(0)); + UNIT_ASSERT_VALUES_EQUAL( + node1Id, + listNodesResponse.GetNodes(0).GetId()); + } + + // RenameNode and UnlinkNode ops should work again + + ui64 node2Id = service.CreateNode( + headers, + TCreateNodeArgs::File(dir1Id, "file1") + )->Record.GetNode().GetId(); + UNIT_ASSERT_VALUES_UNEQUAL(node1Id, node2Id); + + service.SendRenameNodeRequest( + headers, + dir1Id, + "file1", + dir1Id, + "file1_moved", + 0); + + { + auto renameResponse = service.RecvRenameNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + S_OK, + renameResponse->GetError().GetCode(), + renameResponse->GetError().GetMessage()); + } + + ui64 node3Id = service.CreateNode( + headers, + TCreateNodeArgs::File(dir1Id, "file1") + )->Record.GetNode().GetId(); + UNIT_ASSERT_VALUES_UNEQUAL(node1Id, node3Id); + UNIT_ASSERT_VALUES_UNEQUAL(node2Id, node3Id); + + service.SendUnlinkNodeRequest(headers, dir1Id, "file1"); + { + auto unlinkResponse = service.RecvUnlinkNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + S_OK, + unlinkResponse->GetError().GetCode(), + unlinkResponse->GetError().GetMessage()); + } + + { + auto listNodesResponse = service.ListNodes( + headers, + fsConfig.FsId, + dir1Id)->Record; + + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NamesSize()); + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NodesSize()); + UNIT_ASSERT_VALUES_EQUAL("file1_moved", listNodesResponse.GetNames(0)); + UNIT_ASSERT_VALUES_EQUAL( + node2Id, + listNodesResponse.GetNodes(0).GetId()); + } + } + + SERVICE_TEST_SID_SELECT_IN_LEADER_ONLY( + ShouldRestoreNodeRefLocksUponLeaderRestartWithDirectoriesInShards) + { + config.SetMultiTabletForwardingEnabled(true); + config.SetDirectoryCreationInShardsEnabled(true); + TTestEnv env({}, config); + env.CreateSubDomain("nfs"); + + ui32 nodeIdx = env.CreateNode("nfs"); + + TServiceClient service(env.GetRuntime(), nodeIdx); + TFileSystemConfig fsConfig; + fsConfig.DirectoryCreationInShardsEnabled = true; + const auto fsInfo = CreateFileSystem(service, fsConfig); + + auto headers = service.InitSession(fsConfig.FsId, "client"); + + // creating 2 dirs - we'll move a file from one dir to another + + ui64 dir1Id = service.CreateNode( + headers, + TCreateNodeArgs::Directory(RootNodeId, "dir1") + )->Record.GetNode().GetId(); + + ui64 dir2Id = service.CreateNode( + headers, + TCreateNodeArgs::Directory(RootNodeId, "dir2") + )->Record.GetNode().GetId(); + + UNIT_ASSERT_VALUES_UNEQUAL(0, ExtractShardNo(dir1Id)); + UNIT_ASSERT_VALUES_UNEQUAL(0, ExtractShardNo(dir2Id)); + UNIT_ASSERT_VALUES_UNEQUAL( + ExtractShardNo(dir1Id), + ExtractShardNo(dir2Id)); + + // creating a file which we will move + + service.CreateNode(headers, TCreateNodeArgs::File(dir1Id, "file1")); + + // configuring an interceptor to make RenameNode get stuck in the shard + // in charge of dir1 + + TEventHelper evHelper(env); + evHelper.CatchEvent(TEvIndexTablet::EvRenameNodeInDestinationRequest); + + service.SendRenameNodeRequest( + headers, + dir1Id, + "file1", + dir2Id, + "file2", + 0); + + ui32 iterations = 0; + while (!evHelper.Event && iterations++ < 100) { + env.GetRuntime().DispatchEvents({}, TDuration::MilliSeconds(50)); + } + + UNIT_ASSERT(evHelper.Event); + + // rebooting the shard in charge of dir1 to trigger OpLog replay which + // should lock the affected NodeRef + + const ui64 tabletId = ExtractShardNo(dir1Id) == 1 + ? fsInfo.Shard1TabletId : fsInfo.Shard2TabletId; + TIndexTabletClient tablet(env.GetRuntime(), nodeIdx, tabletId); + tablet.RebootTablet(); + + { + auto renameResponse = service.RecvRenameNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + E_REJECTED, + renameResponse->GetError().GetCode(), + renameResponse->GetError().GetMessage()); + } + + // remaking session since CreateSessionActor doesn't do it by itself + // because EvWakeup never arrives because Scheduling doesn't work by + // default and RegistrationObservers get reset after RebootTablet + headers = service.InitSession(fsConfig.FsId, "client"); + + // unlinking of that file should be unallowed since we have an inflight + // RenameNode operation + + service.SendUnlinkNodeRequest(headers, dir1Id, "file1"); + { + auto unlinkResponse = service.RecvUnlinkNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + E_REJECTED, + unlinkResponse->GetError().GetCode(), + unlinkResponse->GetError().GetMessage()); + } + + // renaming should not be allowed as well + + service.SendRenameNodeRequest( + headers, + dir1Id, + "file1", + dir1Id, + "file1_moved", + 0); + + { + auto renameResponse = service.RecvRenameNodeResponse(); + UNIT_ASSERT_VALUES_EQUAL_C( + E_REJECTED, + renameResponse->GetError().GetCode(), + renameResponse->GetError().GetMessage()); + } + } } } // namespace NCloud::NFileStore::NStorage diff --git a/cloud/filestore/libs/storage/tablet/model/node_index_cache.cpp b/cloud/filestore/libs/storage/tablet/model/node_index_cache.cpp index 78d9572594..9e560d9e88 100644 --- a/cloud/filestore/libs/storage/tablet/model/node_index_cache.cpp +++ b/cloud/filestore/libs/storage/tablet/model/node_index_cache.cpp @@ -25,7 +25,7 @@ void TNodeIndexCache::Reset(ui32 maxNodes) void TNodeIndexCache::InvalidateCache(ui64 parentNodeId, const TString& name) { - auto it = AttrByParentNodeId.find(TNodeIndexCacheKey(parentNodeId, name)); + auto it = AttrByParentNodeId.find(TNodeRefKey(parentNodeId, name)); if (it != AttrByParentNodeId.end()) { KeyByNodeId.erase(it->second.GetId()); AttrByParentNodeId.erase(it); @@ -54,7 +54,7 @@ void TNodeIndexCache::RegisterGetNodeAttrResult( AttrByParentNodeId.clear(); } - auto key = TNodeIndexCacheKey(parentNodeId, name); + auto key = TNodeRefKey(parentNodeId, name); AttrByParentNodeId[key] = response; KeyByNodeId.emplace(response.GetId(), key); } @@ -64,7 +64,7 @@ bool TNodeIndexCache::TryFillGetNodeAttrResult( const TString& name, NProto::TNodeAttr* response) { - auto it = AttrByParentNodeId.find(TNodeIndexCacheKey(parentNodeId, name)); + auto it = AttrByParentNodeId.find(TNodeRefKey(parentNodeId, name)); if (it == AttrByParentNodeId.end()) { return false; } diff --git a/cloud/filestore/libs/storage/tablet/model/node_index_cache.h b/cloud/filestore/libs/storage/tablet/model/node_index_cache.h index bee69b784b..5d1ea169d9 100644 --- a/cloud/filestore/libs/storage/tablet/model/node_index_cache.h +++ b/cloud/filestore/libs/storage/tablet/model/node_index_cache.h @@ -2,9 +2,10 @@ #include "public.h" +#include "node_ref.h" + #include -#include #include #include @@ -22,39 +23,8 @@ struct TNodeIndexCacheStats class TNodeIndexCache { private: - struct TNodeIndexCacheKey - { - ui64 ParentNodeId; - TString Name; - - TNodeIndexCacheKey(ui64 parentNodeId, const TString& name) - : ParentNodeId(parentNodeId) - , Name(name) - {} - - size_t GetHash() const - { - return MultiHash(ParentNodeId, Name); - } - - TNodeIndexCacheKey(const TNodeIndexCacheKey&) = default; - TNodeIndexCacheKey& operator=(const TNodeIndexCacheKey&) = default; - TNodeIndexCacheKey(TNodeIndexCacheKey&&) = default; - TNodeIndexCacheKey& operator=(TNodeIndexCacheKey&&) = default; - - bool operator==(const TNodeIndexCacheKey& rhs) const = default; - }; - - struct TNodeIndexCacheKeyHash - { - size_t operator()(const TNodeIndexCacheKey& key) const noexcept - { - return key.GetHash(); - } - }; - - THashMap KeyByNodeId; - THashMap + THashMap KeyByNodeId; + THashMap AttrByParentNodeId; ui32 MaxNodes = 0; diff --git a/cloud/filestore/libs/storage/tablet/model/node_ref.cpp b/cloud/filestore/libs/storage/tablet/model/node_ref.cpp new file mode 100644 index 0000000000..45c1dbeb01 --- /dev/null +++ b/cloud/filestore/libs/storage/tablet/model/node_ref.cpp @@ -0,0 +1 @@ +#include "node_ref.h" diff --git a/cloud/filestore/libs/storage/tablet/model/node_ref.h b/cloud/filestore/libs/storage/tablet/model/node_ref.h new file mode 100644 index 0000000000..a023448f3f --- /dev/null +++ b/cloud/filestore/libs/storage/tablet/model/node_ref.h @@ -0,0 +1,42 @@ +#pragma once + +#include "public.h" + +#include + +namespace NCloud::NFileStore::NStorage { + +//////////////////////////////////////////////////////////////////////////////// + +struct TNodeRefKey +{ + ui64 ParentNodeId; + TString Name; + + TNodeRefKey(ui64 parentNodeId, const TString& name) + : ParentNodeId(parentNodeId) + , Name(name) + {} + + size_t GetHash() const + { + return MultiHash(ParentNodeId, Name); + } + + TNodeRefKey(const TNodeRefKey&) = default; + TNodeRefKey& operator=(const TNodeRefKey&) = default; + TNodeRefKey(TNodeRefKey&&) = default; + TNodeRefKey& operator=(TNodeRefKey&&) = default; + + bool operator==(const TNodeRefKey& rhs) const = default; +}; + +struct TNodeRefKeyHash +{ + size_t operator()(const TNodeRefKey& key) const noexcept + { + return key.GetHash(); + } +}; + +} // namespace NCloud::NFileStore::NStorage diff --git a/cloud/filestore/libs/storage/tablet/model/ya.make b/cloud/filestore/libs/storage/tablet/model/ya.make index 1f5dda5c16..e49dd868ef 100644 --- a/cloud/filestore/libs/storage/tablet/model/ya.make +++ b/cloud/filestore/libs/storage/tablet/model/ya.make @@ -26,6 +26,7 @@ SRCS( large_blocks.cpp mixed_blocks.cpp node_index_cache.cpp + node_ref.cpp node_session_stat.cpp operation.cpp profile_log_events.cpp diff --git a/cloud/filestore/libs/storage/tablet/tablet_actor_oplog.cpp b/cloud/filestore/libs/storage/tablet/tablet_actor_oplog.cpp index 8a90c0cdc0..70463ebd7b 100644 --- a/cloud/filestore/libs/storage/tablet/tablet_actor_oplog.cpp +++ b/cloud/filestore/libs/storage/tablet/tablet_actor_oplog.cpp @@ -1,5 +1,7 @@ #include "tablet_actor.h" +#include + namespace NCloud::NFileStore::NStorage { using namespace NActors; @@ -40,11 +42,18 @@ void TIndexTabletActor::ReplayOpLog( {} // result ); } else if (op.HasRenameNodeInDestinationRequest()) { - // TODO(#2674): lock SourceNodeRef + const auto& request = op.GetRenameNodeInDestinationRequest(); + const bool locked = TryLockNodeRef({ + request.GetOriginalRequest().GetNodeId(), + request.GetOriginalRequest().GetName()}); + if (!locked) { + ReportFailedToLockNodeRef(TStringBuilder() << "Request: " + << request.GetOriginalRequest().ShortUtf8DebugString()); + } RegisterRenameNodeInDestinationActor( ctx, nullptr, // requestInfo - op.GetRenameNodeInDestinationRequest(), + request, 0, // requestId (TODO(#2674): either idempotency or use real // requestId) op.GetEntryId()); diff --git a/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode.cpp b/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode.cpp index fb8be853e0..3e6f39b009 100644 --- a/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode.cpp +++ b/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode.cpp @@ -64,6 +64,15 @@ void TIndexTabletActor::HandleRenameNode( session->DropDupEntry(requestId); } + if (!TryLockNodeRef({msg->Record.GetNodeId(), msg->Record.GetName()})) { + auto response = std::make_unique( + MakeError(E_REJECTED, TStringBuilder() << "node ref " + << msg->Record.GetNodeId() << " " << msg->Record.GetName() + << " is locked for RenameNode")); + NCloud::Reply(ctx, *ev, std::move(response)); + return; + } + auto requestInfo = CreateRequestInfo( ev->Sender, ev->Cookie, @@ -87,6 +96,8 @@ void TIndexTabletActor::HandleRenameNode( auto response = std::make_unique( MakeError(E_ARGUMENT, std::move(message))); NCloud::Reply(ctx, *requestInfo, std::move(response)); + + UnlockNodeRef({msg->Record.GetNodeId(), msg->Record.GetName()}); return; } @@ -125,6 +136,7 @@ void TIndexTabletActor::HandleRenameNode( : GetMainFileSystemId()); } + UnlockNodeRef({msg->Record.GetNodeId(), msg->Record.GetName()}); return; } } @@ -473,6 +485,8 @@ void TIndexTabletActor::CompleteTx_RenameNode( const TActorContext& ctx, TTxIndexTablet::TRenameNode& args) { + UnlockNodeRef({args.ParentNodeId, args.Name}); + InvalidateNodeCaches(args.ParentNodeId); InvalidateNodeCaches(args.NewParentNodeId); if (args.ChildRef) { diff --git a/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode_source.cpp b/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode_source.cpp index 11cd51d36e..b992b36527 100644 --- a/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode_source.cpp +++ b/cloud/filestore/libs/storage/tablet/tablet_actor_renamenode_source.cpp @@ -268,8 +268,6 @@ void TIndexTabletActor::ExecuteTx_PrepareRenameNodeInSource( return RebootTabletOnCommitOverflow(ctx, "PrepareRenameNodeInSource"); } - // TODO(#2674): lock NodeRef for any modifications - // OpLogEntryId doesn't have to be a CommitId - it's just convenient // to use CommitId here in order not to generate some other unique // ui64 @@ -361,6 +359,8 @@ void TIndexTabletActor::CompleteTx_PrepareRenameNodeInSource( } } + UnlockNodeRef({args.ParentNodeId, args.Name}); + Metrics.RenameNode.Update( 1, 0, @@ -462,9 +462,9 @@ void TIndexTabletActor::ExecuteTx_CommitRenameNodeInSource( return RebootTabletOnCommitOverflow(ctx, "CommitRenameNodeInSource"); } - if (HasError(args.Response)) { - // TODO(#2674): unlock NodeRef (or mb do it in CompleteTx?) + UnlockNodeRef({args.Request.GetNodeId(), args.Request.GetName()}); + if (HasError(args.Response)) { NProto::TRenameNodeResponse response; *response.MutableError() = args.Response.GetError(); PatchDupCacheEntry( diff --git a/cloud/filestore/libs/storage/tablet/tablet_actor_unlinknode.cpp b/cloud/filestore/libs/storage/tablet/tablet_actor_unlinknode.cpp index 637f37e06f..e6feed3109 100644 --- a/cloud/filestore/libs/storage/tablet/tablet_actor_unlinknode.cpp +++ b/cloud/filestore/libs/storage/tablet/tablet_actor_unlinknode.cpp @@ -270,6 +270,15 @@ void TIndexTabletActor::HandleUnlinkNode( } } + if (!TryLockNodeRef({msg->Record.GetNodeId(), msg->Record.GetName()})) { + auto response = std::make_unique( + MakeError(E_REJECTED, TStringBuilder() << "node ref " + << msg->Record.GetNodeId() << " " << msg->Record.GetName() + << " is locked for UnlinkNode")); + NCloud::Reply(ctx, *ev, std::move(response)); + return; + } + auto requestInfo = CreateRequestInfo( ev->Sender, ev->Cookie, @@ -445,6 +454,8 @@ void TIndexTabletActor::CompleteTx_UnlinkNode( args.SessionId.c_str(), FormatError(args.Error).c_str()); + UnlockNodeRef({args.ParentNodeId, args.Name}); + if (args.ParentNodeId != InvalidNodeId) { InvalidateNodeCaches(args.ParentNodeId); } diff --git a/cloud/filestore/libs/storage/tablet/tablet_state.h b/cloud/filestore/libs/storage/tablet/tablet_state.h index 7d708abf43..c9bb44c68a 100644 --- a/cloud/filestore/libs/storage/tablet/tablet_state.h +++ b/cloud/filestore/libs/storage/tablet/tablet_state.h @@ -573,6 +573,9 @@ FILESTORE_FILESYSTEM_STATS(FILESTORE_DECLARE_COUNTER) const TString& shardId, const TString& shardNodeName); + bool TryLockNodeRef(TNodeRefKey key); + void UnlockNodeRef(TNodeRefKey key); + // // Sessions // diff --git a/cloud/filestore/libs/storage/tablet/tablet_state_impl.h b/cloud/filestore/libs/storage/tablet/tablet_state_impl.h index 504a79c31b..e8486f6caf 100644 --- a/cloud/filestore/libs/storage/tablet/tablet_state_impl.h +++ b/cloud/filestore/libs/storage/tablet/tablet_state_impl.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ struct TIndexTabletState::TImpl TInMemoryIndexState InMemoryIndexState; TSet OrphanNodeIds; TSet PendingNodeCreateInShardNames; + THashSet LockedNodeRefs; TCheckpointStore Checkpoints; TChannels Channels; diff --git a/cloud/filestore/libs/storage/tablet/tablet_state_nodes.cpp b/cloud/filestore/libs/storage/tablet/tablet_state_nodes.cpp index 7b6d2524fe..5de5389f8b 100644 --- a/cloud/filestore/libs/storage/tablet/tablet_state_nodes.cpp +++ b/cloud/filestore/libs/storage/tablet/tablet_state_nodes.cpp @@ -579,6 +579,16 @@ void TIndexTabletState::RewriteNodeRef( InvalidateNodeIndexCache(nodeId, childName); } +bool TIndexTabletState::TryLockNodeRef(TNodeRefKey key) +{ + return Impl->LockedNodeRefs.insert(std::move(key)).second; +} + +void TIndexTabletState::UnlockNodeRef(TNodeRefKey key) +{ + Impl->LockedNodeRefs.erase(std::move(key)); +} + //////////////////////////////////////////////////////////////////////////////// bool TIndexTabletState::TryFillGetNodeAttrResult(