Add data directory status and (un)lock query (#933)

This commit is contained in:
andrejtonev 2023-05-16 18:36:04 +02:00 committed by GitHub
parent 7ddce539fa
commit 802f8aceda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 172 additions and 56 deletions

View File

@ -2898,7 +2898,7 @@ class LockPathQuery : public memgraph::query::Query {
static const utils::TypeInfo kType; static const utils::TypeInfo kType;
const utils::TypeInfo &GetTypeInfo() const override { return kType; } const utils::TypeInfo &GetTypeInfo() const override { return kType; }
enum class Action { LOCK_PATH, UNLOCK_PATH }; enum class Action { LOCK_PATH, UNLOCK_PATH, STATUS };
LockPathQuery() = default; LockPathQuery() = default;

View File

@ -328,7 +328,9 @@ antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasC
antlrcpp::Any CypherMainVisitor::visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) { antlrcpp::Any CypherMainVisitor::visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) {
auto *lock_query = storage_->Create<LockPathQuery>(); auto *lock_query = storage_->Create<LockPathQuery>();
if (ctx->LOCK()) { if (ctx->STATUS()) {
lock_query->action_ = LockPathQuery::Action::STATUS;
} else if (ctx->LOCK()) {
lock_query->action_ = LockPathQuery::Action::LOCK_PATH; lock_query->action_ = LockPathQuery::Action::LOCK_PATH;
} else if (ctx->UNLOCK()) { } else if (ctx->UNLOCK()) {
lock_query->action_ = LockPathQuery::Action::UNLOCK_PATH; lock_query->action_ = LockPathQuery::Action::UNLOCK_PATH;

View File

@ -91,6 +91,7 @@ memgraphCypherKeyword : cypherKeyword
| SNAPSHOT | SNAPSHOT
| START | START
| STATS | STATS
| STATUS
| STORAGE | STORAGE
| STREAM | STREAM
| STREAMS | STREAMS
@ -335,7 +336,7 @@ dropReplica : DROP REPLICA replicaName ;
showReplicas : SHOW REPLICAS ; showReplicas : SHOW REPLICAS ;
lockPathQuery : ( LOCK | UNLOCK ) DATA DIRECTORY ; lockPathQuery : ( LOCK | UNLOCK ) DATA DIRECTORY | DATA DIRECTORY LOCK STATUS;
freeMemoryQuery : FREE MEMORY ; freeMemoryQuery : FREE MEMORY ;

View File

@ -107,6 +107,7 @@ SNAPSHOT : S N A P S H O T ;
START : S T A R T ; START : S T A R T ;
STATISTICS : S T A T I S T I C S ; STATISTICS : S T A T I S T I C S ;
STATS : S T A T S ; STATS : S T A T S ;
STATUS : S T A T U S ;
STOP : S T O P ; STOP : S T O P ;
STORAGE : S T O R A G E; STORAGE : S T O R A G E;
STORAGE_MODE : S T O R A G E UNDERSCORE MODE; STORAGE_MODE : S T O R A G E UNDERSCORE MODE;

View File

@ -145,6 +145,7 @@ const trie::Trie kKeywords = {"union",
"drop", "drop",
"show", "show",
"stats", "stats",
"status",
"unique", "unique",
"explain", "explain",
"profile", "profile",
@ -212,6 +213,10 @@ const trie::Trie kKeywords = {"union",
"off", "off",
"in_memory_transactional", "in_memory_transactional",
"in_memory_analytical", "in_memory_analytical",
"data",
"directory",
"lock",
"unlock"
"build"}; "build"};
// Unicode codepoints that are allowed at the start of the unescaped name. // Unicode codepoints that are allowed at the start of the unescaped name.

View File

@ -1780,25 +1780,49 @@ PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_tr
auto *lock_path_query = utils::Downcast<LockPathQuery>(parsed_query.query); auto *lock_path_query = utils::Downcast<LockPathQuery>(parsed_query.query);
return PreparedQuery{{}, return PreparedQuery{
std::move(parsed_query.required_privileges), {"STATUS"},
[interpreter_context, action = lock_path_query->action_]( std::move(parsed_query.required_privileges),
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> { [interpreter_context, action = lock_path_query->action_](
switch (action) { AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
case LockPathQuery::Action::LOCK_PATH: std::vector<std::vector<TypedValue>> status;
if (!interpreter_context->db->LockPath()) { std::string res;
throw QueryRuntimeException("Failed to lock the data directory");
} switch (action) {
break; case LockPathQuery::Action::LOCK_PATH: {
case LockPathQuery::Action::UNLOCK_PATH: const auto lock_success = interpreter_context->db->LockPath();
if (!interpreter_context->db->UnlockPath()) { if (lock_success.HasError()) [[unlikely]] {
throw QueryRuntimeException("Failed to unlock the data directory"); throw QueryRuntimeException("Failed to lock the data directory");
} }
break; res = lock_success.GetValue() ? "Data directory is now locked." : "Data directory is already locked.";
} break;
return QueryHandlerResult::COMMIT; }
}, case LockPathQuery::Action::UNLOCK_PATH: {
RWType::NONE}; const auto unlock_success = interpreter_context->db->UnlockPath();
if (unlock_success.HasError()) [[unlikely]] {
throw QueryRuntimeException("Failed to unlock the data directory");
}
res = unlock_success.GetValue() ? "Data directory is now unlocked." : "Data directory is already unlocked.";
break;
}
case LockPathQuery::Action::STATUS: {
const auto locked_status = interpreter_context->db->IsPathLocked();
if (locked_status.HasError()) [[unlikely]] {
throw QueryRuntimeException("Failed to access the data directory");
}
res = locked_status.GetValue() ? "Data directory is locked." : "Data directory is unlocked.";
break;
}
}
status.emplace_back(std::vector<TypedValue>{TypedValue(res)});
auto pull_plan = std::make_shared<PullPlanVector>(std::move(status));
if (pull_plan->Pull(stream, n)) {
return QueryHandlerResult::COMMIT;
}
return std::nullopt;
},
RWType::NONE};
} }
PreparedQuery PrepareFreeMemoryQuery(ParsedQuery parsed_query, bool in_explicit_transaction, PreparedQuery PrepareFreeMemoryQuery(ParsedQuery parsed_query, bool in_explicit_transaction,

View File

@ -399,7 +399,8 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient
// we cannot know if the difference is only in the current WAL or we need // we cannot know if the difference is only in the current WAL or we need
// to send the snapshot. // to send the snapshot.
if (latest_snapshot) { if (latest_snapshot) {
locker_acc.AddPath(latest_snapshot->path); const auto lock_success = locker_acc.AddPath(latest_snapshot->path);
MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path.");
recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path)); recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path));
} }
// if there are no finalized WAL files, snapshot left the current WAL // if there are no finalized WAL files, snapshot left the current WAL
@ -446,7 +447,8 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient
// We need to lock these files and add them to the chain // We need to lock these files and add them to the chain
for (auto result_wal_it = wal_files->begin() + distance_from_first; result_wal_it != wal_files->end(); for (auto result_wal_it = wal_files->begin() + distance_from_first; result_wal_it != wal_files->end();
++result_wal_it) { ++result_wal_it) {
locker_acc.AddPath(result_wal_it->path); const auto lock_success = locker_acc.AddPath(result_wal_it->path);
MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path.");
wal_chain.push_back(std::move(result_wal_it->path)); wal_chain.push_back(std::move(result_wal_it->path));
} }
@ -464,7 +466,8 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient
MG_ASSERT(latest_snapshot, "Invalid durability state, missing snapshot"); MG_ASSERT(latest_snapshot, "Invalid durability state, missing snapshot");
// We didn't manage to find a WAL chain, we need to send the latest snapshot // We didn't manage to find a WAL chain, we need to send the latest snapshot
// with its WALs // with its WALs
locker_acc.AddPath(latest_snapshot->path); const auto lock_success = locker_acc.AddPath(latest_snapshot->path);
MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path.");
recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path)); recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path));
std::vector<std::filesystem::path> recovery_wal_files; std::vector<std::filesystem::path> recovery_wal_files;
@ -483,13 +486,15 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient
} }
for (; wal_it != wal_files->end(); ++wal_it) { for (; wal_it != wal_files->end(); ++wal_it) {
locker_acc.AddPath(wal_it->path); const auto lock_success = locker_acc.AddPath(wal_it->path);
MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path.");
recovery_wal_files.push_back(std::move(wal_it->path)); recovery_wal_files.push_back(std::move(wal_it->path));
} }
// We only have a WAL before the snapshot // We only have a WAL before the snapshot
if (recovery_wal_files.empty()) { if (recovery_wal_files.empty()) {
locker_acc.AddPath(wal_files->back().path); const auto lock_success = locker_acc.AddPath(wal_files->back().path);
MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path.");
recovery_wal_files.push_back(std::move(wal_files->back().path)); recovery_wal_files.push_back(std::move(wal_files->back().path));
} }

View File

@ -1980,16 +1980,23 @@ utils::BasicResult<Storage::CreateSnapshotError> Storage::CreateSnapshot(std::op
return CreateSnapshotError::ReachedMaxNumTries; return CreateSnapshotError::ReachedMaxNumTries;
} }
bool Storage::LockPath() { utils::FileRetainer::FileLockerAccessor::ret_type Storage::IsPathLocked() {
auto locker_accessor = global_locker_.Access();
return locker_accessor.IsPathLocked(config_.durability.storage_directory);
}
utils::FileRetainer::FileLockerAccessor::ret_type Storage::LockPath() {
auto locker_accessor = global_locker_.Access(); auto locker_accessor = global_locker_.Access();
return locker_accessor.AddPath(config_.durability.storage_directory); return locker_accessor.AddPath(config_.durability.storage_directory);
} }
bool Storage::UnlockPath() { utils::FileRetainer::FileLockerAccessor::ret_type Storage::UnlockPath() {
{ {
auto locker_accessor = global_locker_.Access(); auto locker_accessor = global_locker_.Access();
if (!locker_accessor.RemovePath(config_.durability.storage_directory)) { const auto ret = locker_accessor.RemovePath(config_.durability.storage_directory);
return false; if (ret.HasError() || !ret.GetValue()) {
// Exit without cleaning the queue
return ret;
} }
} }

View File

@ -39,6 +39,7 @@
#include "storage/v2/vertex_accessor.hpp" #include "storage/v2/vertex_accessor.hpp"
#include "utils/file_locker.hpp" #include "utils/file_locker.hpp"
#include "utils/on_scope_exit.hpp" #include "utils/on_scope_exit.hpp"
#include "utils/result.hpp"
#include "utils/rw_lock.hpp" #include "utils/rw_lock.hpp"
#include "utils/scheduler.hpp" #include "utils/scheduler.hpp"
#include "utils/skip_list.hpp" #include "utils/skip_list.hpp"
@ -467,8 +468,9 @@ class Storage final {
StorageInfo GetInfo() const; StorageInfo GetInfo() const;
bool LockPath(); utils::FileRetainer::FileLockerAccessor::ret_type IsPathLocked();
bool UnlockPath(); utils::FileRetainer::FileLockerAccessor::ret_type LockPath();
utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath();
bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {}); bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {});

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -80,13 +80,14 @@ void FileRetainer::CleanQueue() {
} }
////// LockerEntry ////// ////// LockerEntry //////
void FileRetainer::LockerEntry::LockPath(const std::filesystem::path &path) { bool FileRetainer::LockerEntry::LockPath(const std::filesystem::path &path) {
auto absolute_path = std::filesystem::absolute(path); auto absolute_path = std::filesystem::absolute(path);
if (std::filesystem::is_directory(absolute_path)) { if (std::filesystem::is_directory(absolute_path)) {
directories_.emplace(std::move(absolute_path)); const auto [itr, success] = directories_.emplace(std::move(absolute_path));
return; return success;
} }
files_.emplace(std::move(absolute_path)); const auto [itr, success] = files_.emplace(std::move(absolute_path));
return success;
} }
bool FileRetainer::LockerEntry::RemovePath(const std::filesystem::path &path) { bool FileRetainer::LockerEntry::RemovePath(const std::filesystem::path &path) {
@ -140,13 +141,27 @@ FileRetainer::FileLockerAccessor::FileLockerAccessor(FileRetainer *retainer, siz
file_retainer_->active_accessors_.fetch_add(1); file_retainer_->active_accessors_.fetch_add(1);
} }
bool FileRetainer::FileLockerAccessor::AddPath(const std::filesystem::path &path) { FileRetainer::FileLockerAccessor::ret_type FileRetainer::FileLockerAccessor::IsPathLocked(
if (!std::filesystem::exists(path)) return false; const std::filesystem::path &path) {
file_retainer_->lockers_.WithLock([&](auto &lockers) { lockers[locker_id_].LockPath(path); }); if (!std::filesystem::exists(path)) {
return true; return Error::NonexistentPath;
}
return file_retainer_->FileLocked(std::filesystem::absolute(path));
} }
bool FileRetainer::FileLockerAccessor::RemovePath(const std::filesystem::path &path) { FileRetainer::FileLockerAccessor::ret_type FileRetainer::FileLockerAccessor::AddPath(
const std::filesystem::path &path) {
if (!std::filesystem::exists(path)) {
return Error::NonexistentPath;
}
return file_retainer_->lockers_.WithLock([&](auto &lockers) { return lockers[locker_id_].LockPath(path); });
}
FileRetainer::FileLockerAccessor::ret_type FileRetainer::FileLockerAccessor::RemovePath(
const std::filesystem::path &path) {
if (!std::filesystem::exists(path)) {
return Error::NonexistentPath;
}
return file_retainer_->lockers_.WithLock([&](auto &lockers) { return lockers[locker_id_].RemovePath(path); }); return file_retainer_->lockers_.WithLock([&](auto &lockers) { return lockers[locker_id_].RemovePath(path); });
} }

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -18,6 +18,7 @@
#include <unordered_map> #include <unordered_map>
#include "utils/file.hpp" #include "utils/file.hpp"
#include "utils/result.hpp"
#include "utils/rw_lock.hpp" #include "utils/rw_lock.hpp"
#include "utils/spin_lock.hpp" #include "utils/spin_lock.hpp"
#include "utils/synchronized.hpp" #include "utils/synchronized.hpp"
@ -114,15 +115,26 @@ class FileRetainer {
struct FileLockerAccessor { struct FileLockerAccessor {
friend FileLocker; friend FileLocker;
enum class Error : uint8_t {
NonexistentPath = 0,
};
using ret_type = utils::BasicResult<FileRetainer::FileLockerAccessor::Error, bool>;
/**
* Checks if a single path is in the current locker.
*/
ret_type IsPathLocked(const std::filesystem::path &path);
/** /**
* Add a single path to the current locker. * Add a single path to the current locker.
*/ */
bool AddPath(const std::filesystem::path &path); ret_type AddPath(const std::filesystem::path &path);
/** /**
* Remove a single path form the current locker. * Remove a single path form the current locker.
*/ */
bool RemovePath(const std::filesystem::path &path); ret_type RemovePath(const std::filesystem::path &path);
FileLockerAccessor(const FileLockerAccessor &) = delete; FileLockerAccessor(const FileLockerAccessor &) = delete;
FileLockerAccessor(FileLockerAccessor &&) = default; FileLockerAccessor(FileLockerAccessor &&) = default;
@ -182,7 +194,7 @@ class FileRetainer {
class LockerEntry { class LockerEntry {
public: public:
void LockPath(const std::filesystem::path &path); bool LockPath(const std::filesystem::path &path);
bool RemovePath(const std::filesystem::path &path); bool RemovePath(const std::filesystem::path &path);
[[nodiscard]] bool LocksFile(const std::filesystem::path &path) const; [[nodiscard]] bool LocksFile(const std::filesystem::path &path) const;

View File

@ -3368,10 +3368,43 @@ TEST_P(CypherMainVisitorTest, TestLockPathQuery) {
ASSERT_TRUE(parsed_query); ASSERT_TRUE(parsed_query);
EXPECT_EQ(parsed_query->action_, action); EXPECT_EQ(parsed_query->action_, action);
} }
{
const std::string query = fmt::format("{} DATA DIRECTORY LOCK STATUS", command);
ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException);
}
{
const std::string query = fmt::format("{} DATA DIRECTORY STATUS", command);
ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException);
}
}; };
test_lock_path_query("LOCK", LockPathQuery::Action::LOCK_PATH); test_lock_path_query("LOCK", LockPathQuery::Action::LOCK_PATH);
test_lock_path_query("UNLOCK", LockPathQuery::Action::UNLOCK_PATH); test_lock_path_query("UNLOCK", LockPathQuery::Action::UNLOCK_PATH);
// Status test
{
const std::string query = "DATA DIRECTORY LOCK";
ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException);
}
{
const std::string query = "DATA LOCK STATUS";
ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException);
}
{
const std::string query = "DIRECTORY LOCK STATUS";
ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException);
}
{
const std::string query = "DATA DIRECTORY LOCK STATUS";
auto *parsed_query = dynamic_cast<LockPathQuery *>(ast_generator.ParseQuery(query));
ASSERT_TRUE(parsed_query);
EXPECT_EQ(parsed_query->action_, LockPathQuery::Action::STATUS);
}
} }
TEST_P(CypherMainVisitorTest, TestLoadCsvClause) { TEST_P(CypherMainVisitorTest, TestLoadCsvClause) {

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -89,7 +89,8 @@ TEST_P(FileLockerParameterizedTest, DeleteWhileInLocker) {
auto locker = file_retainer.AddLocker(); auto locker = file_retainer.AddLocker();
{ {
auto acc = locker.Access(); auto acc = locker.Access();
acc.AddPath(lock_absolute ? file_absolute : file); const auto lock_success = acc.AddPath(lock_absolute ? file_absolute : file);
ASSERT_FALSE(lock_success.HasError());
} }
file_retainer.DeleteFile(delete_absolute ? file_absolute : file); file_retainer.DeleteFile(delete_absolute ? file_absolute : file);
@ -133,7 +134,9 @@ TEST_P(FileLockerParameterizedTest, DirectoryLock) {
auto locker = file_retainer.AddLocker(); auto locker = file_retainer.AddLocker();
{ {
auto acc = locker.Access(); auto acc = locker.Access();
acc.AddPath(lock_absolute ? std::filesystem::absolute(directory_to_lock) : directory_to_lock); const auto lock_success =
acc.AddPath(lock_absolute ? std::filesystem::absolute(directory_to_lock) : directory_to_lock);
ASSERT_FALSE(lock_success.HasError());
} }
file_retainer.DeleteFile(delete_absolute ? file_absolute : file); file_retainer.DeleteFile(delete_absolute ? file_absolute : file);
@ -165,7 +168,8 @@ TEST_P(FileLockerParameterizedTest, RemovePath) {
auto locker = file_retainer.AddLocker(); auto locker = file_retainer.AddLocker();
{ {
auto acc = locker.Access(); auto acc = locker.Access();
acc.AddPath(lock_absolute ? file_absolute : file); const auto lock_success = acc.AddPath(lock_absolute ? file_absolute : file);
ASSERT_FALSE(lock_success.HasError());
} }
file_retainer.DeleteFile(delete_absolute ? file_absolute : file); file_retainer.DeleteFile(delete_absolute ? file_absolute : file);
@ -205,8 +209,10 @@ TEST_F(FileLockerTest, MultipleLockers) {
auto locker = file_retainer.AddLocker(); auto locker = file_retainer.AddLocker();
{ {
auto acc = locker.Access(); auto acc = locker.Access();
acc.AddPath(file1); const auto lock_success1 = acc.AddPath(file1);
acc.AddPath(common_file); ASSERT_FALSE(lock_success1.HasError());
const auto lock_success2 = acc.AddPath(common_file);
ASSERT_FALSE(lock_success2.HasError());
} }
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
}); });
@ -215,8 +221,10 @@ TEST_F(FileLockerTest, MultipleLockers) {
auto locker = file_retainer.AddLocker(); auto locker = file_retainer.AddLocker();
{ {
auto acc = locker.Access(); auto acc = locker.Access();
acc.AddPath(file2); const auto lock_success1 = acc.AddPath(file2);
acc.AddPath(common_file); ASSERT_FALSE(lock_success1.HasError());
const auto lock_success2 = acc.AddPath(common_file);
ASSERT_FALSE(lock_success2.HasError());
} }
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
}); });
@ -275,7 +283,8 @@ TEST_F(FileLockerTest, MultipleLockersAndDeleters) {
auto acc = locker.Access(); auto acc = locker.Access();
for (auto i = 0; i < file_access_num; ++i) { for (auto i = 0; i < file_access_num; ++i) {
auto file = random_file(); auto file = random_file();
if (acc.AddPath(file)) { const auto res = acc.AddPath(file);
if (!res.HasError()) {
ASSERT_TRUE(std::filesystem::exists(file)); ASSERT_TRUE(std::filesystem::exists(file));
locked_files.emplace_back(std::move(file)); locked_files.emplace_back(std::move(file));
} else { } else {