ClickHouse/dbms/src/Storages/MergeTree/AbandonableLockInZooKeeper.h

178 lines
5.1 KiB
C++
Raw Normal View History

2014-04-02 07:59:43 +00:00
#pragma once
#include <Common/ZooKeeper/ZooKeeper.h>
#include <Common/Exception.h>
#include <IO/ReadHelpers.h>
2014-04-02 07:59:43 +00:00
namespace DB
{
namespace ErrorCodes
{
extern const int LOGICAL_ERROR;
}
2017-04-16 15:00:33 +00:00
/** The synchronization is primitive. Works as follows:
* Creates a non-ephemeral incremental node and marks it as locked (LOCKED).
* `unlock()` unlocks it (UNLOCKED).
* When the destructor is called or the session ends in ZooKeeper, it goes into the ABANDONED state.
* (Including when the program is halted).
2014-04-02 07:59:43 +00:00
*/
class AbandonableLockInZooKeeper : public boost::noncopyable
2014-04-02 07:59:43 +00:00
{
public:
enum State
{
UNLOCKED,
LOCKED,
ABANDONED,
};
AbandonableLockInZooKeeper(
const String & path_prefix_, const String & temp_path, zkutil::ZooKeeper & zookeeper_, zkutil::Ops * precheck_ops = nullptr)
: zookeeper(&zookeeper_), path_prefix(path_prefix_)
{
String abandonable_path = temp_path + "/abandonable_lock-";
2017-04-16 15:00:33 +00:00
/// Let's create an secondary ephemeral node.
if (!precheck_ops || precheck_ops->empty())
{
holder_path = zookeeper->create(abandonable_path, "", zkutil::CreateMode::EphemeralSequential);
}
else
{
precheck_ops->emplace_back(std::make_shared<zkutil::Op::Create>(
abandonable_path, "", zookeeper->getDefaultACL(), zkutil::CreateMode::EphemeralSequential));
zkutil::OpResultsPtr op_results = zookeeper->multi(*precheck_ops);
holder_path = op_results->back().value;
}
2017-04-16 15:00:33 +00:00
/// Write the path to the secondary node in the main node.
path = zookeeper->create(path_prefix, holder_path, zkutil::CreateMode::PersistentSequential);
if (path.size() <= path_prefix.size())
throw Exception("Logical error: name of sequential node is shorter than prefix.", ErrorCodes::LOGICAL_ERROR);
}
AbandonableLockInZooKeeper() = default;
AbandonableLockInZooKeeper(AbandonableLockInZooKeeper && rhs) noexcept
{
*this = std::move(rhs);
}
AbandonableLockInZooKeeper & operator=(AbandonableLockInZooKeeper && rhs) noexcept
{
zookeeper = rhs.zookeeper;
rhs.zookeeper = nullptr;
path_prefix = std::move(rhs.path_prefix);
path = std::move(rhs.path);
holder_path = std::move(rhs.holder_path);
return *this;
}
bool isCreated() const
{
return zookeeper && !holder_path.empty() && !path.empty();
}
String getPath() const
{
checkCreated();
return path;
}
2017-04-16 15:00:33 +00:00
/// Parse the number at the end of the path.
UInt64 getNumber() const
{
checkCreated();
return parse<UInt64>(path.c_str() + path_prefix.size(), path.size() - path_prefix.size());
}
void unlock()
{
checkCreated();
zookeeper->remove(path);
zookeeper->remove(holder_path);
holder_path = "";
}
2017-04-16 15:00:33 +00:00
/// Adds actions equivalent to `unlock()` to the list.
void getUnlockOps(zkutil::Ops & ops)
{
checkCreated();
ops.emplace_back(std::make_shared<zkutil::Op::Remove>(path, -1));
ops.emplace_back(std::make_shared<zkutil::Op::Remove>(holder_path, -1));
}
2018-03-15 19:05:58 +00:00
/// Do not delete nodes in destructor. You may call this method after 'getUnlockOps' and successful execution of these ops,
/// because the nodes will be already deleted.
void assumeUnlocked()
{
2018-03-15 19:06:53 +00:00
holder_path.clear();
}
void checkCreated() const
{
if (!isCreated())
throw Exception("AbandonableLock is not created", ErrorCodes::LOGICAL_ERROR);
}
~AbandonableLockInZooKeeper()
{
if (!zookeeper || holder_path.empty())
return;
try
{
zookeeper->tryRemoveEphemeralNodeWithRetries(holder_path);
zookeeper->trySet(path, ""); /// It's not necessary.
}
catch (...)
{
tryLogCurrentException("~AbandonableLockInZooKeeper");
}
}
static State check(const String & path, zkutil::ZooKeeper & zookeeper)
{
String holder_path;
2017-04-16 15:00:33 +00:00
/// If there is no main node, UNLOCKED.
if (!zookeeper.tryGet(path, holder_path))
return UNLOCKED;
2017-04-16 15:00:33 +00:00
/// If there is no path to the secondary node in the main node, ABANDONED.
if (holder_path.empty())
return ABANDONED;
2017-04-16 15:00:33 +00:00
/// If the secondary node is alive, LOCKED.
if (zookeeper.exists(holder_path))
return LOCKED;
2017-04-16 15:00:33 +00:00
/// If there is no secondary node, you need to test again the existence of the main node,
/// because during this time you might have time to call unlock().
/// At the same time, we will remove the path to the secondary node from there.
if (zookeeper.trySet(path, "") == ZOK)
return ABANDONED;
return UNLOCKED;
}
static void createAbandonedIfNotExists(const String & path, zkutil::ZooKeeper & zookeeper)
{
zookeeper.createIfNotExists(path, "");
}
2014-07-25 16:32:02 +00:00
2014-04-02 07:59:43 +00:00
private:
zkutil::ZooKeeper * zookeeper = nullptr;
String path_prefix;
String path;
String holder_path;
2014-04-02 07:59:43 +00:00
};
}