diff --git a/CMakeLists.txt b/CMakeLists.txt index 051ba98..31146a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.6) +project(top) add_subdirectory(libcron) add_subdirectory(test) diff --git a/README.md b/README.md index 4a8cfcf..7949218 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Libcron offers an easy to use API to add callbacks with corresponding cron-forma ``` libcron::Cron cron; -cron.add_schedule("Hello from Cron", "* * * * * ?", [=]() { +cron.add_schedule("Hello from Cron", "* * * * * ?", [=](auto&) { std::cout << "Hello from libcron!" std::endl; }); ``` @@ -23,6 +23,28 @@ while(true) } ``` +The callback must have the following signature: + +``` +std::function +``` + +`libcron::Taskinformation` offers a convenient API to retrieve further information: + +- `libcron::TaskInformation::get_delay` informs about the delay between planned and actual execution of the callback. Hence, it is possible to ensure that a task was executed within a specific tolerance: + +``` +libcron::Cron cron; + +cron.add_schedule("Hello from Cron", "* * * * * ?", [=](auto& i) { + using namespace std::chrono_literals; + if (i.get_delay() >= 1s) + { + std::cout << "The Task was executed too late..." << std::endl; + } +}); +``` + ## Removing schedules from `libcron::Cron` diff --git a/libcron/CMakeLists.txt b/libcron/CMakeLists.txt index c57b73a..7a296b7 100644 --- a/libcron/CMakeLists.txt +++ b/libcron/CMakeLists.txt @@ -31,4 +31,4 @@ target_include_directories(${PROJECT_NAME} set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/${CMAKE_BUILD_TYPE}" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/${CMAKE_BUILD_TYPE}") \ No newline at end of file + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/${CMAKE_BUILD_TYPE}") diff --git a/libcron/include/libcron/Cron.h b/libcron/include/libcron/Cron.h index 43651cc..bf6be70 100644 --- a/libcron/include/libcron/Cron.h +++ b/libcron/include/libcron/Cron.h @@ -20,12 +20,10 @@ namespace libcron class Locker { public: - Locker() : lck(m, std::defer_lock) {} - void lock() { lck.lock(); } - void unlock() { lck.unlock(); } + void lock() { m.lock(); } + void unlock() { m.unlock(); } private: - std::mutex m{}; - std::unique_lock lck; + std::recursive_mutex m{}; }; template @@ -38,8 +36,7 @@ namespace libcron class Cron { public: - - bool add_schedule(std::string name, const std::string& schedule, std::function work); + bool add_schedule(std::string name, const std::string& schedule, Task::TaskFunction work); void clear_schedules(); void remove_schedule(const std::string& name); @@ -139,16 +136,16 @@ namespace libcron bool first_tick = true; std::chrono::system_clock::time_point last_tick{}; }; - + template - bool Cron::add_schedule(std::string name, const std::string& schedule, std::function work) + bool Cron::add_schedule(std::string name, const std::string& schedule, Task::TaskFunction work) { auto cron = CronData::create(schedule); bool res = cron.is_valid(); if (res) { tasks.lock_queue(); - Task t{std::move(name), CronSchedule{cron}, std::move(work)}; + Task t{std::move(name), CronSchedule{cron}, work }; if (t.calculate_next(clock.now())) { tasks.push(t); diff --git a/libcron/include/libcron/CronClock.h b/libcron/include/libcron/CronClock.h index 9d89b3b..48e667d 100644 --- a/libcron/include/libcron/CronClock.h +++ b/libcron/include/libcron/CronClock.h @@ -39,4 +39,4 @@ namespace libcron std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override; }; -} \ No newline at end of file +} diff --git a/libcron/include/libcron/Task.h b/libcron/include/libcron/Task.h index 72fcda8..a07d171 100644 --- a/libcron/include/libcron/Task.h +++ b/libcron/include/libcron/Task.h @@ -1,26 +1,42 @@ #pragma once #include -#include "CronData.h" -#include "CronSchedule.h" #include #include +#include "CronData.h" +#include "CronSchedule.h" namespace libcron { - class Task + class TaskInformation { public: + virtual ~TaskInformation() = default; + virtual std::chrono::system_clock::duration get_delay() const = 0; + }; - Task(std::string name, const CronSchedule schedule, std::function task) + class Task : public TaskInformation + { + public: + using TaskFunction = std::function; + + Task(std::string name, const CronSchedule schedule, TaskFunction task) : name(std::move(name)), schedule(std::move(schedule)), task(std::move(task)) { } void execute(std::chrono::system_clock::time_point now) { + // Next Schedule is still the current schedule, calculate delay (actual execution - planned execution) + delay = now - next_schedule; + last_run = now; - task(); + task(*this); + } + + std::chrono::system_clock::duration get_delay() const override + { + return delay; } Task(const Task& other) = default; @@ -50,7 +66,8 @@ namespace libcron std::string name; CronSchedule schedule; std::chrono::system_clock::time_point next_schedule; - std::function task; + std::chrono::system_clock::duration delay = std::chrono::seconds(-1); + TaskFunction task; bool valid = false; std::chrono::system_clock::time_point last_run = std::numeric_limits::min(); }; diff --git a/libcron/src/Task.cpp b/libcron/src/Task.cpp index 69171c0..859250d 100644 --- a/libcron/src/Task.cpp +++ b/libcron/src/Task.cpp @@ -1,4 +1,3 @@ -#include #include "libcron/Task.h" using namespace std::chrono; diff --git a/test/CronRandomizationTest.cpp b/test/CronRandomizationTest.cpp index 0ecef3c..841eb3c 100644 --- a/test/CronRandomizationTest.cpp +++ b/test/CronRandomizationTest.cpp @@ -23,13 +23,13 @@ void test(const char* const random_schedule, bool expect_failure = false) if(expect_failure) { // Parsing of random might succeed, but it yields an invalid schedule. - auto r = std::get<0>(res) && cron.add_schedule("validate schedule", schedule, []() {}); + auto r = std::get<0>(res) && cron.add_schedule("validate schedule", schedule, [](auto&) {}); REQUIRE_FALSE(r); } else { REQUIRE(std::get<0>(res)); - REQUIRE(cron.add_schedule("validate schedule", schedule, []() {})); + REQUIRE(cron.add_schedule("validate schedule", schedule, [](auto&) {})); } } diff --git a/test/CronTest.cpp b/test/CronTest.cpp index aa66ad0..7de24e4 100644 --- a/test/CronTest.cpp +++ b/test/CronTest.cpp @@ -35,7 +35,7 @@ SCENARIO("Adding a task") WHEN("Adding a task that runs every second") { REQUIRE(c.add_schedule("A task", "* * * * * ?", - [&expired]() + [&expired](auto&) { expired = true; }) @@ -68,7 +68,7 @@ SCENARIO("Adding a task that expires in the future") Cron<> c; REQUIRE(c.add_schedule("A task", create_schedule_expiring_in(c.get_clock().now(), hours{0}, minutes{0}, seconds{3}), - [&expired]() + [&expired](auto&) { expired = true; }) @@ -99,6 +99,50 @@ SCENARIO("Adding a task that expires in the future") } } +SCENARIO("Get delay using Task-Information") +{ + using namespace std::chrono_literals; + + GIVEN("A Cron instance with one task expiring in 2 seconds, but taking 3 seconds to execute") + { + auto _2_second_expired = 0; + auto _delay = std::chrono::system_clock::duration(-1s); + + Cron<> c; + REQUIRE(c.add_schedule("Two", + "*/2 * * * * ?", + [&_2_second_expired, &_delay](auto& i) + { + _2_second_expired++; + _delay = i.get_delay(); + std::this_thread::sleep_for(3s); + }) + ); + THEN("Not yet expired") + { + REQUIRE_FALSE(_2_second_expired); + REQUIRE(_delay <= 0s); + } + WHEN("Exactly schedule task") + { + while (_2_second_expired == 0) + c.tick(); + + THEN("Task should have expired within a valid time") + { + REQUIRE(_2_second_expired == 1); + REQUIRE(_delay <= 1s); + } + AND_THEN("Executing another tick again, leading to execute task again immediatly, but not on time as execution has taken 3 seconds.") + { + c.tick(); + REQUIRE(_2_second_expired == 2); + REQUIRE(_delay >= 1s); + } + } + } +} + SCENARIO("Task priority") { GIVEN("A Cron instance with two tasks expiring in 3 and 5 seconds, added in 'reverse' order") @@ -110,7 +154,7 @@ SCENARIO("Task priority") Cron<> c; REQUIRE(c.add_schedule("Five", create_schedule_expiring_in(c.get_clock().now(), hours{0}, minutes{0}, seconds{5}), - [&_5_second_expired]() + [&_5_second_expired](auto&) { _5_second_expired++; }) @@ -118,7 +162,7 @@ SCENARIO("Task priority") REQUIRE(c.add_schedule("Three", create_schedule_expiring_in(c.get_clock().now(), hours{0}, minutes{0}, seconds{3}), - [&_3_second_expired]() + [&_3_second_expired](auto&) { _3_second_expired++; }) @@ -231,7 +275,7 @@ SCENARIO("Clock changes") clock.set(sys_days{2018_y / 05 / 05}); // Every hour - REQUIRE(c.add_schedule("Clock change task", "0 0 * * * ?", []() + REQUIRE(c.add_schedule("Clock change task", "0 0 * * * ?", [](auto&) { }) ); @@ -309,7 +353,7 @@ SCENARIO("Multiple ticks per second") int run_count = 0; // Every 10 seconds - REQUIRE(c.add_schedule("Clock change task", "*/10 0 * * * ?", [&run_count]() + REQUIRE(c.add_schedule("Clock change task", "*/10 0 * * * ?", [&run_count](auto&) { run_count++; }) @@ -346,35 +390,35 @@ SCENARIO("Tasks can be added and removed from the scheduler") WHEN("Adding 5 tasks that runs every second") { REQUIRE(c.add_schedule("Task-1", "* * * * * ?", - [&expired]() + [&expired](auto&) { expired = true; }) ); REQUIRE(c.add_schedule("Task-2", "* * * * * ?", - [&expired]() + [&expired](auto&) { expired = true; }) ); REQUIRE(c.add_schedule("Task-3", "* * * * * ?", - [&expired]() + [&expired](auto&) { expired = true; }) ); REQUIRE(c.add_schedule("Task-4", "* * * * * ?", - [&expired]() + [&expired](auto&) { expired = true; }) ); REQUIRE(c.add_schedule("Task-5", "* * * * * ?", - [&expired]() + [&expired](auto&) { expired = true; })