This is an automated email from the ASF dual-hosted git repository.
twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new 8547cfb4 refactor(cron): add CronPattern and implement more cron
syntax (#2377)
8547cfb4 is described below
commit 8547cfb48cb30dc3ad26214980e2c6a61c0e609a
Author: Twice <[email protected]>
AuthorDate: Sat Jun 29 21:00:07 2024 +0900
refactor(cron): add CronPattern and implement more cron syntax (#2377)
Co-authored-by: hulk <[email protected]>
---
.github/workflows/kvrocks.yaml | 31 ++++-----
src/common/cron.cc | 94 ++++++++++-----------------
src/common/cron.h | 141 +++++++++++++++++++++++++++++++++++------
3 files changed, 171 insertions(+), 95 deletions(-)
diff --git a/.github/workflows/kvrocks.yaml b/.github/workflows/kvrocks.yaml
index 270d4e70..1d8c24ea 100644
--- a/.github/workflows/kvrocks.yaml
+++ b/.github/workflows/kvrocks.yaml
@@ -110,24 +110,25 @@ jobs:
fail-fast: false
matrix:
include:
- - name: Darwin Clang
- os: macos-11
- compiler: auto
+ # FIXME: update macos-11 to macos-12/13
+ # - name: Darwin Clang
+ # os: macos-11
+ # compiler: auto
- name: Darwin Clang arm64
os: macos-14
compiler: auto
- - name: Darwin Clang without Jemalloc
- os: macos-11
- compiler: auto
- without_jemalloc: -DDISABLE_JEMALLOC=ON
- - name: Darwin Clang with OpenSSL
- os: macos-11
- compiler: auto
- with_openssl: -DENABLE_OPENSSL=ON
- - name: Darwin Clang without luaJIT
- os: macos-11
- compiler: auto
- without_luajit: -DENABLE_LUAJIT=OFF
+ # - name: Darwin Clang without Jemalloc
+ # os: macos-11
+ # compiler: auto
+ # without_jemalloc: -DDISABLE_JEMALLOC=ON
+ # - name: Darwin Clang with OpenSSL
+ # os: macos-11
+ # compiler: auto
+ # with_openssl: -DENABLE_OPENSSL=ON
+ # - name: Darwin Clang without luaJIT
+ # os: macos-11
+ # compiler: auto
+ # without_luajit: -DENABLE_LUAJIT=OFF
- name: Ubuntu GCC
os: ubuntu-20.04
compiler: gcc
diff --git a/src/common/cron.cc b/src/common/cron.cc
index 9bac0ca5..26db65f3 100644
--- a/src/common/cron.cc
+++ b/src/common/cron.cc
@@ -23,17 +23,36 @@
#include <stdexcept>
#include <utility>
+#include "fmt/core.h"
#include "parse_util.h"
#include "string_util.h"
-std::string Scheduler::ToString() const {
- 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);
+std::string CronScheduler::ToString() const {
+ return fmt::format("{} {} {} {} {}", minute.ToString(), hour.ToString(),
mday.ToString(), month.ToString(),
+ wday.ToString());
+}
+
+bool CronScheduler::IsMatch(const tm *tm) const {
+ bool minute_match = minute.IsMatch(tm->tm_min);
+ bool hour_match = hour.IsMatch(tm->tm_hour);
+ bool mday_match = mday.IsMatch(tm->tm_mday, 1);
+ bool month_match = month.IsMatch(tm->tm_mon + 1, 1);
+ bool wday_match = wday.IsMatch(tm->tm_wday);
+
+ return minute_match && hour_match && mday_match && month_match && wday_match;
+}
+
+StatusOr<CronScheduler> CronScheduler::Parse(std::string_view minute,
std::string_view hour, std::string_view mday,
+ std::string_view month,
std::string_view wday) {
+ CronScheduler st;
+
+ st.minute = GET_OR_RET(CronPattern::Parse(minute, {0, 59}));
+ st.hour = GET_OR_RET(CronPattern::Parse(hour, {0, 23}));
+ st.mday = GET_OR_RET(CronPattern::Parse(mday, {1, 31}));
+ st.month = GET_OR_RET(CronPattern::Parse(month, {1, 12}));
+ st.wday = GET_OR_RET(CronPattern::Parse(wday, {0, 6}));
+
+ return st;
}
Status Cron::SetScheduleTime(const std::vector<std::string> &args) {
@@ -42,14 +61,14 @@ Status Cron::SetScheduleTime(const std::vector<std::string>
&args) {
return Status::OK();
}
if (args.size() % 5 != 0) {
- return {Status::NotOK, "time expression format error,should only contain
5x fields"};
+ return {Status::NotOK, "cron expression format error, should only contain
5x fields"};
}
- std::vector<Scheduler> new_schedulers;
+ std::vector<CronScheduler> new_schedulers;
for (size_t i = 0; i < args.size(); i += 5) {
- auto s = convertToScheduleTime(args[i], args[i + 1], args[i + 2], args[i +
3], args[i + 4]);
+ auto s = CronScheduler::Parse(args[i], args[i + 1], args[i + 2], args[i +
3], args[i + 4]);
if (!s.IsOK()) {
- return std::move(s).Prefixed("time expression format error");
+ return std::move(s).Prefixed("cron expression format error");
}
new_schedulers.push_back(*s);
}
@@ -63,20 +82,8 @@ bool Cron::IsTimeMatch(const tm *tm) {
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_) {
- 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, 1);
- 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)
{
+ if (st.IsMatch(tm)) {
last_tm_ = *tm;
return true;
}
@@ -94,40 +101,3 @@ std::string Cron::ToString() const {
}
return ret;
}
-
-StatusOr<Scheduler> Cron::convertToScheduleTime(const std::string &minute,
const std::string &hour,
- const std::string &mday, const
std::string &month,
- const std::string &wday) {
- Scheduler st;
-
- 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 ¶m, 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));
- }
-
- return *s;
-}
diff --git a/src/common/cron.h b/src/common/cron.h
index 325745e9..b228a69e 100644
--- a/src/common/cron.h
+++ b/src/common/cron.h
@@ -23,25 +23,135 @@
#include <ctime>
#include <iostream>
#include <string>
+#include <variant>
#include <vector>
+#include "parse_util.h"
#include "status.h"
+#include "string_util.h"
-struct Scheduler {
- int minute;
- int hour;
- int mday;
- int month;
- int wday;
+struct CronPattern {
+ using Number = int;
+ using Range = std::pair<int, int>;
- // 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;
+ struct Interval {
+ int interval;
+ }; // */n
+ struct Any {}; // *
+ using Numbers = std::vector<std::variant<Number, Range>>; // 1,2,3-6,7
+
+ std::variant<Numbers, Interval, Any> val;
+
+ static StatusOr<CronPattern> Parse(std::string_view str, std::tuple<int,
int> minmax) {
+ if (str == "*") {
+ return CronPattern{Any{}};
+ } else if (str.rfind("*/", 0) == 0) {
+ auto num_str = str.substr(2);
+ auto interval = GET_OR_RET(ParseInt<int>(std::string(num_str.begin(),
num_str.end()), minmax)
+ .Prefixed("an integer is expected after
`*/` in a cron expression"));
+
+ if (interval == 0) {
+ return {Status::NotOK, "interval value after `*/` cannot be zero"};
+ }
+
+ return CronPattern{Interval{interval}};
+ } else {
+ auto num_strs = util::Split(str, ",");
+
+ Numbers results;
+ for (const auto &num_str : num_strs) {
+ if (auto pos = num_str.find('-'); pos != num_str.npos) {
+ auto l_str = num_str.substr(0, pos);
+ auto r_str = num_str.substr(pos + 1);
+ auto l = GET_OR_RET(ParseInt<int>(std::string(num_str.begin(),
num_str.end()), minmax)
+ .Prefixed("an integer is expected before `-`
in a cron expression"));
+ auto r = GET_OR_RET(ParseInt<int>(std::string(num_str.begin(),
num_str.end()), minmax)
+ .Prefixed("an integer is expected after `-`
in a cron expression"));
+
+ if (l >= r) {
+ return {Status::NotOK, "for pattern `l-r` in cron expression, r
should be larger than l"};
+ }
+ results.push_back(Range(l, r));
+ } else {
+ auto n = GET_OR_RET(ParseInt<int>(std::string(num_str.begin(),
num_str.end()), minmax)
+ .Prefixed("an integer is expected in a cron
expression"));
+ results.push_back(n);
+ }
+ }
+
+ if (results.empty()) {
+ return {Status::NotOK, "invalid cron expression"};
+ }
+
+ return CronPattern{results};
+ }
+ }
+
+ std::string ToString() const {
+ if (std::holds_alternative<Numbers>(val)) {
+ std::string result;
+ bool first = true;
+
+ for (const auto &v : std::get<Numbers>(val)) {
+ if (first)
+ first = false;
+ else
+ result += ",";
+
+ if (std::holds_alternative<Number>(v)) {
+ result += std::to_string(std::get<Number>(v));
+ } else {
+ auto range = std::get<Range>(v);
+ result += std::to_string(range.first) + "-" +
std::to_string(range.second);
+ }
+ }
+
+ return result;
+ } else if (std::holds_alternative<Interval>(val)) {
+ return "*/" + std::to_string(std::get<Interval>(val).interval);
+ } else if (std::holds_alternative<Any>(val)) {
+ return "*";
+ }
+
+ __builtin_unreachable();
+ }
+
+ bool IsMatch(int input, int interval_offset = 0) const {
+ if (std::holds_alternative<Numbers>(val)) {
+ bool result = false;
+ for (const auto &v : std::get<Numbers>(val)) {
+ if (std::holds_alternative<Number>(v)) {
+ result = result || input == std::get<Number>(v);
+ } else {
+ auto range = std::get<Range>(v);
+ result = result || (range.first <= input && input <= range.second);
+ }
+ }
+
+ return result;
+ } else if (std::holds_alternative<Interval>(val)) {
+ return (input - interval_offset) % std::get<Interval>(val).interval == 0;
+ } else if (std::holds_alternative<Any>(val)) {
+ return true;
+ }
+
+ __builtin_unreachable();
+ }
+};
+
+struct CronScheduler {
+ CronPattern minute;
+ CronPattern hour;
+ CronPattern mday;
+ CronPattern month;
+ CronPattern wday;
std::string ToString() const;
+
+ static StatusOr<CronScheduler> Parse(std::string_view minute,
std::string_view hour, std::string_view mday,
+ std::string_view month,
std::string_view wday);
+
+ bool IsMatch(const tm *tm) const;
};
class Cron {
@@ -55,11 +165,6 @@ class Cron {
bool IsEnabled() const;
private:
- std::vector<Scheduler> schedulers_;
+ std::vector<CronScheduler> schedulers_;
tm last_tm_ = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr};
-
- 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 ¶m, int lower_bound,
int upper_bound, bool &is_interval);
};