ClickHouse/src/IO/ResourceGuard.h

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

140 lines
3.8 KiB
C++
Raw Normal View History

2022-09-27 13:26:41 +00:00
#pragma once
#include <base/types.h>
#include <IO/ResourceRequest.h>
2023-01-27 18:47:22 +00:00
#include <IO/ResourceLink.h>
2022-09-27 13:26:41 +00:00
#include <IO/ISchedulerConstraint.h>
2023-01-27 18:47:22 +00:00
#include <condition_variable>
#include <mutex>
2022-09-27 13:26:41 +00:00
namespace DB
{
/*
* Scoped resource guard.
* Waits for resource to be available in constructor and releases resource in destructor
2023-01-27 22:48:02 +00:00
* IMPORTANT: multiple resources should not be locked concurrently by a single thread
2022-09-27 13:26:41 +00:00
*/
class ResourceGuard
{
public:
enum ResourceGuardCtor
{
2023-01-27 18:47:22 +00:00
LockStraightAway, /// Locks inside constructor (default)
// WARNING: Only for tests. It is not exception-safe because `lock()` must be called after construction.
PostponeLocking /// Don't lock in constructor, but send request
2022-09-27 13:26:41 +00:00
};
2023-01-27 18:47:22 +00:00
enum RequestState
2022-09-27 13:26:41 +00:00
{
2023-01-27 18:47:22 +00:00
Finished, // Last request has already finished; no concurrent access is possible
Enqueued, // Enqueued into the scheduler; thread-safe access is required
Dequeued // Dequeued from the scheduler and is in consumption state; no concurrent access is possible
};
2022-09-27 13:26:41 +00:00
2023-01-27 18:47:22 +00:00
class Request : public ResourceRequest
{
public:
void enqueue(ResourceCost cost_, ResourceLink link_)
{
// lock(mutex) is not required because `Finished` request cannot be used by the scheduler thread
chassert(state == Finished);
state = Enqueued;
ResourceRequest::reset(cost_);
link_.queue->enqueueRequestUsingBudget(this);
}
2022-09-27 13:26:41 +00:00
2023-01-27 18:47:22 +00:00
// This function is executed inside scheduler thread and wakes thread issued this `request`.
// That thread will continue execution and do real consumption of requested resource synchronously.
2022-09-27 13:26:41 +00:00
void execute() override
{
2023-01-27 18:47:22 +00:00
{
std::unique_lock lock(mutex);
chassert(state == Enqueued);
state = Dequeued;
}
dequeued_cv.notify_one();
}
void wait()
{
std::unique_lock lock(mutex);
dequeued_cv.wait(lock, [this] { return state == Dequeued; });
2022-09-27 13:26:41 +00:00
}
2023-01-27 18:47:22 +00:00
void finish()
{
// lock(mutex) is not required because `Dequeued` request cannot be used by the scheduler thread
chassert(state == Dequeued);
state = Finished;
if (constraint)
constraint->finishRequest(this);
}
static Request & local()
{
// Since single thread cannot use more than one resource request simultaneously,
// we can reuse thread-local request to avoid allocations
static thread_local Request instance;
return instance;
}
private:
std::mutex mutex;
std::condition_variable dequeued_cv;
RequestState state = Finished;
2022-09-27 13:26:41 +00:00
};
/// Creates pending request for resource; blocks while resource is not available (unless `PostponeLocking`)
explicit ResourceGuard(ResourceLink link_, ResourceCost cost = 1, ResourceGuardCtor ctor = LockStraightAway)
: link(link_)
2023-01-27 18:47:22 +00:00
, request(Request::local())
2022-09-27 13:26:41 +00:00
{
2023-01-27 18:47:22 +00:00
if (cost == 0)
link.queue = nullptr; // Ignore zero-cost requests
else if (link.queue)
2022-09-27 13:26:41 +00:00
{
2023-01-27 18:47:22 +00:00
request.enqueue(cost, link);
2022-09-27 13:26:41 +00:00
if (ctor == LockStraightAway)
2023-01-27 18:47:22 +00:00
request.wait();
2022-09-27 13:26:41 +00:00
}
}
~ResourceGuard()
{
unlock();
}
/// Blocks until resource is available
void lock()
{
if (link.queue)
2023-01-27 18:47:22 +00:00
request.wait();
2022-09-27 13:26:41 +00:00
}
2023-01-27 18:47:22 +00:00
/// Report resource consumption has finished
2022-09-27 13:26:41 +00:00
void unlock()
{
if (link.queue)
{
2023-01-27 18:47:22 +00:00
request.finish();
link.queue = nullptr;
2022-09-27 13:26:41 +00:00
}
}
/// Mark request as unsuccessful; by default request is considered to be successful
void setFailure()
{
request.successful = false;
}
ResourceLink link;
2023-01-27 18:47:22 +00:00
Request & request;
2022-09-27 13:26:41 +00:00
};
}