blob: 619c5dbf1a3930fe3d735952a4607463fe02308f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
#pragma once
#include "commander.h"
#include "common/lock_manager.h"
#include "event_util.h"
#include "server/redis_connection.h"
#include "server/server.h"
namespace redis {
class BlockingCommander : public Commander,
private EvbufCallbackBase<BlockingCommander, false>,
private EventCallbackBase<BlockingCommander> {
public:
// method to reply when no operation happens
virtual std::string NoopReply(const Connection *conn) = 0;
// method to block keys
virtual void BlockKeys() = 0;
// method to unblock keys
virtual void UnblockKeys() = 0;
// method to access database in write callback
// the return value indicates if the real database operation happens
// in other words, returning true indicates ending the blocking
virtual bool OnBlockingWrite() = 0;
// GetLocks() locks the keys of the BlockingCommander with MultiLockGuard.
// When OnWrite() is triggered, BlockingCommander needs to relock the keys.
virtual MultiLockGuard GetLocks() = 0;
// to start the blocking process
// usually put to the end of the Execute method
Status StartBlocking(int64_t timeout, std::string *output) {
if (conn_->IsInExec() || conn_->IsInScript()) {
*output = NoopReply(conn_);
return Status::OK(); // no blocking in multi-exec
}
BlockKeys();
SetCB(conn_->GetBufferEvent());
if (timeout) {
InitTimer(timeout);
}
return {Status::BlockingCmd};
}
void OnWrite(bufferevent *bev) {
bool done{false};
{
// The blocking command should not be executed when the server is in exclusive state,
// because it might have the data race when the server is in transaction mode and run
// the callback here might cause the current execution also in transaction mode.
//
// For more context, please refer to: https://github.com/apache/kvrocks/issues/2900
auto concurrency = conn_->GetServer()->WorkConcurrencyGuard();
auto guard = GetLocks();
done = OnBlockingWrite();
}
if (!done) {
// The connection may be waked up but can't pop from the datatype.
// For example, connection A is blocked on it and connection B added a new element;
// then connection A was unblocked, but this element may be taken by
// another connection C. So we need to block connection A again
// and wait for the element being added by disabling the WRITE event.
bufferevent_disable(bev, EV_WRITE);
return;
}
if (timer_) {
timer_.reset();
}
UnblockKeys();
conn_->SetCB(bev);
bufferevent_enable(bev, EV_READ);
// We need to manually trigger the read event since we will stop processing commands
// in connection after the blocking command, so there may have some commands to be processed.
// Related issue: https://github.com/apache/kvrocks/issues/831
bufferevent_trigger(bev, EV_READ, BEV_TRIG_IGNORE_WATERMARKS);
}
void OnEvent(bufferevent *bev, int16_t events) {
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
if (timer_ != nullptr) {
timer_.reset();
}
UnblockKeys();
}
conn_->OnEvent(bev, events);
}
// Usually put to the top of the Execute method
void InitConnection(Connection *conn) { conn_ = conn; }
void InitTimer(int64_t timeout) {
auto bev = conn_->GetBufferEvent();
timer_.reset(NewTimer(bufferevent_get_base(bev)));
int64_t timeout_second = timeout / 1000 / 1000;
int64_t timeout_microsecond = timeout % (1000 * 1000);
timeval tm = {timeout_second, static_cast<int>(timeout_microsecond)};
evtimer_add(timer_.get(), &tm);
}
void TimerCB(int, int16_t) {
conn_->Reply(NoopReply(conn_));
timer_.reset();
UnblockKeys();
auto bev = conn_->GetBufferEvent();
conn_->SetCB(bev);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
protected:
Connection *conn_ = nullptr;
UniqueEvent timer_;
};
} // namespace redis