Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cron): add support for "*/n" interval cronjob syntax #2360

Merged
merged 13 commits into from
Jun 14, 2024
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 */int)
# 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 */int)
# 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 */int)
# e.g. dbsize-scan-cron 0 * * * *
# would recalculate the keyspace infos of the db every hour.

Expand Down
50 changes: 38 additions & 12 deletions src/common/cron.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@
#include <utility>

#include "parse_util.h"
#include "string_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 @@ -57,10 +62,21 @@ bool Cron::IsTimeMatch(const tm *tm) {
tm->tm_mon == last_tm_.tm_mon && tm->tm_wday == last_tm_.tm_wday) {
return false;
}

auto match = [](int current, int val, bool interval, int interval_offset) {
if (val == -1) return true;
if (interval) return (current - interval_offset) % val == 0;
return val == current;
};

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 minute_match = match(tm->tm_min, st.minute, st.minute_interval, 0);
bool hour_match = match(tm->tm_hour, st.hour, st.hour_interval, 0);
bool mday_match = match(tm->tm_mday, st.mday, st.mday_interval, 0);
kinoute marked this conversation as resolved.
Show resolved Hide resolved
bool month_match = match(tm->tm_mon + 1, st.month, st.month_interval, 1);
bool wday_match = match(tm->tm_wday, st.wday, st.wday_interval, 0);

if (minute_match && hour_match && mday_match && month_match && wday_match) {
last_tm_ = *tm;
return true;
}
Expand All @@ -84,20 +100,30 @@ 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)
if (util::HasPrefix(param, "*/")) {
auto s = ParseInt<int>(param.substr(2), {lower_bound, upper_bound}, 10);
if (!s || *s == 0) {
return std::move(s).Prefixed(fmt::format("malformed cron token `{}`", param));
}
is_interval = true;
return *s;
}

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);
};
Loading