2014-04-02 07:59:43 +00:00
|
|
|
#pragma once
|
|
|
|
|
2017-06-19 20:06:35 +00:00
|
|
|
#include <Common/ZooKeeper/ZooKeeper.h>
|
2017-04-01 09:19:00 +00:00
|
|
|
#include <Common/Exception.h>
|
|
|
|
#include <IO/ReadHelpers.h>
|
2014-04-02 07:59:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
2016-01-24 05:00:24 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
extern const int LOGICAL_ERROR;
|
2016-01-24 05:00:24 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
*/
|
2018-03-13 20:36:22 +00:00
|
|
|
class AbandonableLockInZooKeeper : public boost::noncopyable
|
2014-04-02 07:59:43 +00:00
|
|
|
{
|
|
|
|
public:
|
2017-04-01 07:20:54 +00:00
|
|
|
enum State
|
|
|
|
{
|
|
|
|
UNLOCKED,
|
|
|
|
LOCKED,
|
|
|
|
ABANDONED,
|
|
|
|
};
|
|
|
|
|
|
|
|
AbandonableLockInZooKeeper(
|
2018-01-19 22:37:50 +00:00
|
|
|
const String & path_prefix_, const String & temp_path, zkutil::ZooKeeper & zookeeper_, zkutil::Ops * precheck_ops = nullptr)
|
2018-03-13 20:36:22 +00:00
|
|
|
: zookeeper(&zookeeper_), path_prefix(path_prefix_)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2018-01-19 22:37:50 +00:00
|
|
|
String abandonable_path = temp_path + "/abandonable_lock-";
|
|
|
|
|
2017-04-16 15:00:33 +00:00
|
|
|
/// Let's create an secondary ephemeral node.
|
2018-01-19 22:37:50 +00:00
|
|
|
if (!precheck_ops || precheck_ops->empty())
|
|
|
|
{
|
2018-03-13 20:36:22 +00:00
|
|
|
holder_path = zookeeper->create(abandonable_path, "", zkutil::CreateMode::EphemeralSequential);
|
2018-01-19 22:37:50 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-03-13 20:36:22 +00:00
|
|
|
precheck_ops->emplace_back(std::make_shared<zkutil::Op::Create>(
|
|
|
|
abandonable_path, "", zookeeper->getDefaultACL(), zkutil::CreateMode::EphemeralSequential));
|
2018-01-19 22:37:50 +00:00
|
|
|
|
2018-03-13 20:36:22 +00:00
|
|
|
zkutil::OpResultsPtr op_results = zookeeper->multi(*precheck_ops);
|
2018-01-19 22:37:50 +00:00
|
|
|
|
2018-03-13 20:36:22 +00:00
|
|
|
holder_path = op_results->back().value;
|
2018-01-19 22:37:50 +00:00
|
|
|
}
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-04-16 15:00:33 +00:00
|
|
|
/// Write the path to the secondary node in the main node.
|
2018-03-13 20:36:22 +00:00
|
|
|
path = zookeeper->create(path_prefix, holder_path, zkutil::CreateMode::PersistentSequential);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
|
|
|
if (path.size() <= path_prefix.size())
|
|
|
|
throw Exception("Logical error: name of sequential node is shorter than prefix.", ErrorCodes::LOGICAL_ERROR);
|
|
|
|
}
|
|
|
|
|
2018-03-13 20:36:22 +00:00
|
|
|
AbandonableLockInZooKeeper() = default;
|
|
|
|
|
|
|
|
AbandonableLockInZooKeeper(AbandonableLockInZooKeeper && rhs) noexcept
|
|
|
|
{
|
|
|
|
*this = std::move(rhs);
|
|
|
|
}
|
|
|
|
|
|
|
|
AbandonableLockInZooKeeper & operator=(AbandonableLockInZooKeeper && rhs) noexcept
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2018-03-13 20:36:22 +00:00
|
|
|
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;
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:37:50 +00:00
|
|
|
bool isCreated() const
|
|
|
|
{
|
2018-03-13 20:36:22 +00:00
|
|
|
return zookeeper && !holder_path.empty() && !path.empty();
|
2018-01-19 22:37:50 +00:00
|
|
|
}
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
String getPath() const
|
|
|
|
{
|
2018-01-19 22:37:50 +00:00
|
|
|
checkCreated();
|
2017-04-01 07:20:54 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2017-04-16 15:00:33 +00:00
|
|
|
/// Parse the number at the end of the path.
|
2017-04-01 07:20:54 +00:00
|
|
|
UInt64 getNumber() const
|
|
|
|
{
|
2018-01-19 22:37:50 +00:00
|
|
|
checkCreated();
|
2017-04-01 07:20:54 +00:00
|
|
|
return parse<UInt64>(path.c_str() + path_prefix.size(), path.size() - path_prefix.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
void unlock()
|
|
|
|
{
|
2018-01-19 22:37:50 +00:00
|
|
|
checkCreated();
|
2018-03-13 20:36:22 +00:00
|
|
|
zookeeper->remove(path);
|
|
|
|
zookeeper->remove(holder_path);
|
2017-04-01 07:20:54 +00:00
|
|
|
holder_path = "";
|
|
|
|
}
|
|
|
|
|
2017-04-16 15:00:33 +00:00
|
|
|
/// Adds actions equivalent to `unlock()` to the list.
|
2017-04-01 07:20:54 +00:00
|
|
|
void getUnlockOps(zkutil::Ops & ops)
|
|
|
|
{
|
2018-01-19 22:37:50 +00:00
|
|
|
checkCreated();
|
2018-03-13 20:36:22 +00:00
|
|
|
ops.emplace_back(std::make_shared<zkutil::Op::Remove>(path, -1));
|
|
|
|
ops.emplace_back(std::make_shared<zkutil::Op::Remove>(holder_path, -1));
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
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 17:04:47 +00:00
|
|
|
{
|
2018-03-15 19:06:53 +00:00
|
|
|
holder_path.clear();
|
2018-03-15 17:04:47 +00:00
|
|
|
}
|
|
|
|
|
2018-01-19 22:37:50 +00:00
|
|
|
void checkCreated() const
|
|
|
|
{
|
|
|
|
if (!isCreated())
|
|
|
|
throw Exception("AbandonableLock is not created", ErrorCodes::LOGICAL_ERROR);
|
|
|
|
}
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
~AbandonableLockInZooKeeper()
|
|
|
|
{
|
2018-03-13 20:36:22 +00:00
|
|
|
if (!zookeeper || holder_path.empty())
|
2017-04-01 07:20:54 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2018-03-13 20:36:22 +00:00
|
|
|
zookeeper->tryRemoveEphemeralNodeWithRetries(holder_path);
|
|
|
|
zookeeper->trySet(path, ""); /// It's not necessary.
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
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.
|
2017-04-01 07:20:54 +00:00
|
|
|
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.
|
2017-04-01 07:20:54 +00:00
|
|
|
if (holder_path.empty())
|
|
|
|
return ABANDONED;
|
|
|
|
|
2017-04-16 15:00:33 +00:00
|
|
|
/// If the secondary node is alive, LOCKED.
|
2017-04-01 07:20:54 +00:00
|
|
|
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.
|
2017-04-01 07:20:54 +00:00
|
|
|
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:
|
2018-03-13 20:36:22 +00:00
|
|
|
zkutil::ZooKeeper * zookeeper = nullptr;
|
2017-04-01 07:20:54 +00:00
|
|
|
String path_prefix;
|
|
|
|
String path;
|
|
|
|
String holder_path;
|
2014-04-02 07:59:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|