Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions core/include/userver/utils/periodic_task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ class PeriodicTask final {

/// Configuration parameters for PeriodicTask.
struct Settings final {
struct TimePoint {
std::uint8_t hour;
std::uint8_t minute;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

если есть что-то про время, должно быть и про таймзону

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше инициализировать нулями, чем оставлять мусорные значения


bool operator==(const TimePoint& other) const {
return std::tie(hour, minute) == std::tie(other.hour, other.minute);
}
};

static constexpr uint8_t kDistributionPercent = 25;

constexpr /*implicit*/ Settings(
Expand Down Expand Up @@ -91,6 +100,12 @@ class PeriodicTask final {
UASSERT(distribution_percent <= 100);
}

constexpr Settings(
TimePoint time_point,
logging::Level span_level = logging::Level::kInfo
)
: strong_mode_anchor(time_point), span_level(span_level) {}

template <class Rep, class Period>
constexpr /*implicit*/ Settings(std::chrono::duration<Rep, Period> period)
: Settings(period, kDistributionPercent, {}, logging::Level::kInfo) {}
Expand All @@ -110,6 +125,10 @@ class PeriodicTask final {
/// `(period +/- distribution) - time of previous execution`
std::chrono::milliseconds distribution{};

/// @brief Time point for the task execution. Task is repeated every
/// hour:minute UTC of the current/next day
std::optional<TimePoint> strong_mode_anchor;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

как-то костыльненько выглядит
у нас есть параметры внутри Settings, и тут они частично переопределяются
лучше написать отдельный класс, который работает по расписанию и не обладает инвариантами PeriodicTask


/// @brief Used instead of `period` in case of exception, if set.
std::optional<std::chrono::milliseconds> exception_period;

Expand Down
20 changes: 17 additions & 3 deletions core/src/utils/periodic_task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ namespace {

auto TieSettings(const PeriodicTask::Settings& settings) {
// Can't use Boost.Pfr, because Settings has custom constructors.
const auto& [f1, f2, f3, f4, f5, f6] = settings;
return std::tie(f1, f2, f3, f4, f5, f6);
const auto& [f1, f2, f3, f4, f5, f6, f7] = settings;
return std::tie(f1, f2, f3, f4, f5, f6, f7);
}

} // namespace
Expand Down Expand Up @@ -156,7 +156,21 @@ void PeriodicTask::Run() {
if (!no_exception) period = exception_period;

std::chrono::steady_clock::time_point start;
if (settings->flags & Flags::kStrong) {

if (settings->strong_mode_anchor) {
const auto now = utils::datetime::WallCoarseClock::now();
const auto today = std::chrono::time_point_cast<std::chrono::duration<long long, std::ratio<86400>>>(now);

const auto [hours, minutes] = settings->strong_mode_anchor.value();
auto anchor_time_point = today + std::chrono::hours(hours) + std::chrono::minutes(minutes);
if (now > anchor_time_point) {
anchor_time_point += std::chrono::hours(24);
}

start = std::chrono::steady_clock::now()
+ std::chrono::duration_cast<std::chrono::steady_clock::duration>(anchor_time_point - now);
}
else if (settings->flags & Flags::kStrong) {
start = before;
} else {
start = std::chrono::steady_clock::now();
Expand Down
41 changes: 41 additions & 0 deletions core/src/utils/periodic_task_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,47 @@ UTEST(PeriodicTask, Strong) {
task.Stop();
}

UTEST(PeriodicTask, StrongModeAnchor) {
SimpleTaskData simple;

simple.sleep = 6s;

const std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();

auto tp_days = std::chrono::time_point_cast<std::chrono::duration<int, std::ratio<86400>>>(tp);

auto tod = tp - tp_days;

auto h = std::chrono::duration_cast<std::chrono::hours>(tod);
tod -= h;
auto m = std::chrono::duration_cast<std::chrono::minutes>(tod);

auto hour = static_cast<uint8_t>(h.count());
auto minute = static_cast<uint8_t>(m.count());

minute++;
if (minute >= 60) {
minute = 0;
hour++;
if (hour >= 24) {
hour = 0;
}
}

utils::PeriodicTask::Settings::TimePoint mode_anchor{
.hour = hour,
.minute = minute
};
utils::PeriodicTask task(
"task",
utils::PeriodicTask::Settings(mode_anchor),
simple.GetTaskFunction()
);
EXPECT_TRUE(simple.WaitFor(simple.sleep * kSlowRatio, [&] { return simple.GetCount() == 1; }));

task.Stop();
}

UTEST(PeriodicTask, Now) {
SimpleTaskData simple;

Expand Down