ClickHouse/src/Common/PoolBase.h

195 lines
5.3 KiB
C++
Raw Normal View History

2015-10-05 01:26:43 +00:00
#pragma once
#include <mutex>
#include <condition_variable>
2015-10-05 01:26:43 +00:00
#include <Poco/Timespan.h>
#include <boost/noncopyable.hpp>
2022-04-27 15:05:45 +00:00
#include <Common/logger_useful.h>
#include <Common/Exception.h>
2015-10-05 01:26:43 +00:00
namespace DB
{
namespace ErrorCodes
{
extern const int LOGICAL_ERROR;
}
}
2017-05-07 20:25:26 +00:00
/** A class from which you can inherit and get a pool of something. Used for database connection pools.
* Descendant class must provide a method for creating a new object to place in the pool.
2015-10-05 01:26:43 +00:00
*/
template <typename TObject>
class PoolBase : private boost::noncopyable
{
public:
using Object = TObject;
using ObjectPtr = std::shared_ptr<Object>;
using Ptr = std::shared_ptr<PoolBase<TObject>>;
2015-10-05 01:26:43 +00:00
private:
2017-05-07 20:25:26 +00:00
/** The object with the flag, whether it is currently used. */
struct PooledObject
{
PooledObject(ObjectPtr object_, PoolBase & pool_)
: object(object_), pool(pool_)
{
}
ObjectPtr object;
bool in_use = false;
2022-01-29 01:46:00 +00:00
std::atomic<bool> is_expired = false;
PoolBase & pool;
};
using Objects = std::vector<std::shared_ptr<PooledObject>>;
2017-05-07 20:25:26 +00:00
/** The helper, which sets the flag for using the object, and in the destructor - removes,
* and also notifies the event using condvar.
*/
struct PoolEntryHelper
{
2021-03-19 21:49:18 +00:00
explicit PoolEntryHelper(PooledObject & data_) : data(data_) { data.in_use = true; }
~PoolEntryHelper()
{
2019-01-02 06:44:36 +00:00
std::unique_lock lock(data.pool.mutex);
data.in_use = false;
data.pool.available.notify_one();
}
PooledObject & data;
};
2015-10-05 01:26:43 +00:00
public:
2017-05-07 20:25:26 +00:00
/** What is given to the user. */
class Entry
{
public:
friend class PoolBase<Object>;
2021-03-19 21:49:18 +00:00
Entry() = default; /// For deferred initialization.
2017-05-07 20:25:26 +00:00
/** The `Entry` object protects the resource from being used by another thread.
* The following methods are forbidden for `rvalue`, so you can not write a similar to
*
* auto q = pool.get()->query("SELECT .."); // Oops, after this line Entry was destroyed
* q.execute (); // Someone else can use this Connection
*/
Object * operator->() && = delete;
const Object * operator->() const && = delete;
Object & operator*() && = delete;
const Object & operator*() const && = delete;
Object * operator->() & { return &*data->data.object; }
const Object * operator->() const & { return &*data->data.object; }
Object & operator*() & { return *data->data.object; }
const Object & operator*() const & { return *data->data.object; }
2022-01-28 06:03:34 +00:00
/**
2022-01-28 08:55:17 +00:00
* Expire an object to make it reallocated later.
2022-01-28 06:03:34 +00:00
*/
void expire()
{
data->data.is_expired = true;
}
bool isNull() const { return data == nullptr; }
PoolBase * getPool() const
{
if (!data)
throw DB::Exception("Attempt to get pool from uninitialized entry", DB::ErrorCodes::LOGICAL_ERROR);
return &data->data.pool;
}
private:
std::shared_ptr<PoolEntryHelper> data;
2021-03-19 21:49:18 +00:00
explicit Entry(PooledObject & object) : data(std::make_shared<PoolEntryHelper>(object)) {}
};
2021-03-19 21:49:18 +00:00
virtual ~PoolBase() = default;
/** Allocates the object. Wait for free object in pool for 'timeout'. With 'timeout' < 0, the timeout is infinite. */
Entry get(Poco::Timespan::TimeDiff timeout)
{
2019-01-02 06:44:36 +00:00
std::unique_lock lock(mutex);
while (true)
{
for (auto & item : items)
2022-01-29 01:46:00 +00:00
{
if (!item->in_use)
2022-01-28 10:34:54 +00:00
{
2022-01-29 01:46:00 +00:00
if (likely(!item->is_expired))
{
2022-01-28 06:03:34 +00:00
return Entry(*item);
2022-01-29 01:46:00 +00:00
}
2022-01-28 06:03:34 +00:00
else
{
expireObject(item->object);
item->object = allocObject();
item->is_expired = false;
return Entry(*item);
}
}
2022-01-29 01:46:00 +00:00
}
if (items.size() < max_items)
{
ObjectPtr object = allocObject();
items.emplace_back(std::make_shared<PooledObject>(object, *this));
return Entry(*items.back());
}
LOG_INFO(log, "No free connections in pool. Waiting.");
if (timeout < 0)
available.wait(lock);
else
available.wait_for(lock, std::chrono::microseconds(timeout));
}
}
void reserve(size_t count)
{
2019-01-02 06:44:36 +00:00
std::lock_guard lock(mutex);
while (items.size() < count)
items.emplace_back(std::make_shared<PooledObject>(allocObject(), *this));
}
2015-10-05 01:26:43 +00:00
2022-01-29 01:46:00 +00:00
inline size_t size()
2022-01-28 06:03:34 +00:00
{
std::unique_lock lock(mutex);
return items.size();
}
2015-10-05 01:26:43 +00:00
private:
2017-05-07 20:25:26 +00:00
/** The maximum size of the pool. */
unsigned max_items;
2015-10-05 01:26:43 +00:00
2017-05-07 20:25:26 +00:00
/** Pool. */
Objects items;
2015-10-05 01:26:43 +00:00
/** Lock to access the pool. */
std::mutex mutex;
std::condition_variable available;
2015-10-05 01:26:43 +00:00
protected:
2020-05-30 21:57:37 +00:00
Poco::Logger * log;
2015-10-05 01:26:43 +00:00
2020-05-30 21:57:37 +00:00
PoolBase(unsigned max_items_, Poco::Logger * log_)
: max_items(max_items_), log(log_)
{
items.reserve(max_items);
}
2015-10-05 01:26:43 +00:00
/** Creates a new object to put into the pool. */
virtual ObjectPtr allocObject() = 0;
2022-01-28 06:03:34 +00:00
virtual void expireObject(ObjectPtr) {}
2015-10-05 01:26:43 +00:00
};