Skip to content

Commit

Permalink
Add support for "*/n" interval cronjob syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
kinoute committed Jun 9, 2024
1 parent c7a1272 commit da0669f
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 16 deletions.
6 changes: 3 additions & 3 deletions kvrocks.conf
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ profiling-sample-record-threshold-ms 100
################################## CRON ###################################

# Compact Scheduler, auto compact at schedule time
# time expression format is the same as crontab(currently only support * and int)
# Time expression format is the same as crontab (currently only support *, int and */n)
# e.g. compact-cron 0 3 * * * 0 4 * * *
# would compact the db at 3am and 4am everyday
# compact-cron 0 3 * * *
Expand All @@ -515,14 +515,14 @@ compaction-checker-range 0-7
# force-compact-file-min-deleted-percentage 10

# Bgsave scheduler, auto bgsave at scheduled time
# time expression format is the same as crontab(currently only support * and int)
# Time expression format is the same as crontab (currently only support *, int and */n)
# e.g. bgsave-cron 0 3 * * * 0 4 * * *
# would bgsave the db at 3am and 4am every day

# Kvrocks doesn't store the key number directly. It needs to scan the DB and
# then retrieve the key number by using the dbsize scan command.
# The Dbsize scan scheduler auto-recalculates the estimated keys at scheduled time.
# Time expression format is the same as crontab (currently only support * and int)
# Time expression format is the same as crontab (currently only support *, int and */n)
# e.g. dbsize-scan-cron 0 * * * *
# would recalculate the keyspace infos of the db every hour.

Expand Down
46 changes: 34 additions & 12 deletions src/common/cron.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,20 @@

#include "cron.h"

#include <regex>
#include <stdexcept>
#include <utility>

#include "parse_util.h"

std::string Scheduler::ToString() const {
auto param2string = [](int n) -> std::string { return n == -1 ? "*" : std::to_string(n); };
return param2string(minute) + " " + param2string(hour) + " " + param2string(mday) + " " + param2string(month) + " " +
param2string(wday);
auto param2string = [](int n, bool is_interval) -> std::string {
if (n == -1) return "*";
return is_interval ? "*/" + std::to_string(n) : std::to_string(n);
};
return param2string(minute, minute_interval) + " " + param2string(hour, hour_interval) + " " +
param2string(mday, mday_interval) + " " + param2string(month, month_interval) + " " +
param2string(wday, wday_interval);
}

Status Cron::SetScheduleTime(const std::vector<std::string> &args) {
Expand Down Expand Up @@ -58,9 +63,13 @@ bool Cron::IsTimeMatch(const tm *tm) {
return false;
}
for (const auto &st : schedulers_) {
if ((st.minute == -1 || tm->tm_min == st.minute) && (st.hour == -1 || tm->tm_hour == st.hour) &&
(st.mday == -1 || tm->tm_mday == st.mday) && (st.month == -1 || (tm->tm_mon + 1) == st.month) &&
(st.wday == -1 || tm->tm_wday == st.wday)) {
bool minuteMatch = (st.minute == -1 || tm->tm_min == st.minute || (st.minute > 0 && tm->tm_min % st.minute == 0));
bool hourMatch = (st.hour == -1 || tm->tm_hour == st.hour || (st.hour > 0 && tm->tm_hour % st.hour == 0));
bool mdayMatch = (st.mday == -1 || tm->tm_mday == st.mday);
bool monthMatch = (st.month == -1 || (tm->tm_mon + 1) == st.month);
bool wdayMatch = (st.wday == -1 || tm->tm_wday == st.wday);

if (minuteMatch && hourMatch && mdayMatch && monthMatch && wdayMatch) {
last_tm_ = *tm;
return true;
}
Expand All @@ -84,20 +93,33 @@ StatusOr<Scheduler> Cron::convertToScheduleTime(const std::string &minute, const
const std::string &wday) {
Scheduler st;

st.minute = GET_OR_RET(convertParam(minute, 0, 59));
st.hour = GET_OR_RET(convertParam(hour, 0, 23));
st.mday = GET_OR_RET(convertParam(mday, 1, 31));
st.month = GET_OR_RET(convertParam(month, 1, 12));
st.wday = GET_OR_RET(convertParam(wday, 0, 6));
st.minute = GET_OR_RET(convertParam(minute, 0, 59, st.minute_interval));
st.hour = GET_OR_RET(convertParam(hour, 0, 23, st.hour_interval));
st.mday = GET_OR_RET(convertParam(mday, 1, 31, st.mday_interval));
st.month = GET_OR_RET(convertParam(month, 1, 12, st.month_interval));
st.wday = GET_OR_RET(convertParam(wday, 0, 6, st.wday_interval));

return st;
}

StatusOr<int> Cron::convertParam(const std::string &param, int lower_bound, int upper_bound) {
StatusOr<int> Cron::convertParam(const std::string &param, int lower_bound, int upper_bound, bool &is_interval) {
if (param == "*") {
return -1;
}

// Check for interval syntax (*/n)
std::regex interval_regex(R"(\*/(\d+))");
std::smatch match;
if (std::regex_match(param, match, interval_regex)) {
int interval = std::stoi(match[1].str());
if (interval >= lower_bound && interval <= upper_bound) {
is_interval = true;
return interval;
} else {
return {Status::NotOK, "interval value out of bounds"};
}
}

auto s = ParseInt<int>(param, {lower_bound, upper_bound}, 10);
if (!s) {
return std::move(s).Prefixed(fmt::format("malformed cron token `{}`", param));
Expand Down
9 changes: 8 additions & 1 deletion src/common/cron.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ struct Scheduler {
int month;
int wday;

// Whether we use */n interval syntax
bool minute_interval = false;
bool hour_interval = false;
bool mday_interval = false;
bool month_interval = false;
bool wday_interval = false;

std::string ToString() const;
};

Expand All @@ -54,5 +61,5 @@ class Cron {
static StatusOr<Scheduler> convertToScheduleTime(const std::string &minute, const std::string &hour,
const std::string &mday, const std::string &month,
const std::string &wday);
static StatusOr<int> convertParam(const std::string &param, int lower_bound, int upper_bound);
static StatusOr<int> convertParam(const std::string &param, int lower_bound, int upper_bound, bool &is_interval);
};
34 changes: 34 additions & 0 deletions tests/cppunit/cron_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,37 @@ TEST_F(CronTest, ToString) {
std::string got = cron_->ToString();
ASSERT_EQ("* 3 * * *", got);
}

class CronTestInterval : public testing::Test {
protected:
explicit CronTestInterval() {
cron_ = std::make_unique<Cron>();
std::vector<std::string> schedule{"0", "*/4", "*", "*", "*"};
auto s = cron_->SetScheduleTime(schedule);
EXPECT_TRUE(s.IsOK());
}
~CronTestInterval() override = default;

std::unique_ptr<Cron> cron_;
};

TEST_F(CronTestInterval, IsTimeMatch) {
std::time_t t = std::time(nullptr);
std::tm *now = std::localtime(&t);
now->tm_hour = 0;
now->tm_min = 0;
ASSERT_TRUE(cron_->IsTimeMatch(now));
now->tm_hour = 4;
ASSERT_TRUE(cron_->IsTimeMatch(now));
now->tm_hour = 8;
ASSERT_TRUE(cron_->IsTimeMatch(now));
now->tm_hour = 12;
ASSERT_TRUE(cron_->IsTimeMatch(now));
now->tm_hour = 3;
ASSERT_FALSE(cron_->IsTimeMatch(now));
}

TEST_F(CronTestInterval, ToString) {
std::string got = cron_->ToString();
ASSERT_EQ("0 */4 * * *", got);
}

0 comments on commit da0669f

Please sign in to comment.