Skip to content

Commit 47f581f

Browse files
committed
added more tests
fixed several issues in PeriodicScheduler added cmake utils added options for tsan and asan fixed alias library
1 parent a0ef335 commit 47f581f

8 files changed

Lines changed: 422 additions & 44 deletions

File tree

CMakeLists.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,19 @@ cmake_minimum_required(VERSION 3.24)
22

33
project(oryx-crt-cpp VERSION 0.1.0 LANGUAGES CXX)
44

5+
message(STATUS "Build oryx-crt-cpp: ${PROJECT_VERSION}")
6+
7+
include(cmake/utils.cmake)
8+
59
option(ORYX_CRT_BUILD_SHARED_LIBS "Build shared library" ${BUILD_SHARED_LIBS})
610
option(ORYX_CRT_BUILD_TESTS "Build tests" OFF)
711
option(ORYX_CRT_INSTALL "Install the project" ${PROJECT_IS_TOP_LEVEL})
12+
option(ORYX_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
13+
option(ORYX_SANITIZE_THREAD "Enable thread sanitizer in tests" OFF)
14+
15+
if(ORYX_SANITIZE_ADDRESS AND ORYX_SANITIZE_THREAD)
16+
message(FATAL_ERROR "ORYX_SANITIZE_ADDRESS and ORYX_SANITIZE_THREAD are mutually exclusive")
17+
endif()
818

919
set(CMAKE_CXX_STANDARD_REQUIRED ON)
1020
if(NOT DEFINED CMAKE_CXX_STANDARD)
@@ -19,7 +29,7 @@ else()
1929
add_library(${PROJECT_NAME} STATIC)
2030
endif()
2131

22-
add_library("${PROJECT_NAME}::${PROJECT_NAME}" ALIAS ${PROJECT_NAME})
32+
add_library("oryx::${PROJECT_NAME}" ALIAS ${PROJECT_NAME})
2333

2434
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
2535
target_compile_options(${PROJECT_NAME}
@@ -59,6 +69,12 @@ target_sources(${PROJECT_NAME}
5969
FILES ${ORYX_CRT_HEADERS}
6070
)
6171

72+
if(ORYX_SANITIZE_ADDRESS)
73+
oryx_enable_addr_sanitizer(${PROJECT_NAME})
74+
elseif(ORYX_SANITIZE_THREAD)
75+
oryx_enable_thread_sanitizer(${PROJECT_NAME})
76+
endif()
77+
6278

6379
if(ORYX_CRT_BUILD_TESTS)
6480
file(GLOB_RECURSE TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp")

cmake/utils.cmake

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function(oryx_enable_addr_sanitizer target_name)
2+
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
3+
message(FATAL_ERROR "Sanitizer supported only for gcc/clang!")
4+
endif()
5+
message(STATUS "Address sanitizer enabled!")
6+
target_compile_options(${target_name} PRIVATE -fsanitize=address,undefined)
7+
target_compile_options(${target_name} PRIVATE -fno-sanitize=signed-integer-overflow)
8+
target_compile_options(${target_name} PRIVATE -fno-sanitize-recover=all)
9+
target_compile_options(${target_name} PRIVATE -fno-omit-frame-pointer)
10+
target_link_libraries(${target_name} PRIVATE -fsanitize=address,undefined)
11+
endfunction()
12+
13+
# Enable thread sanitizer (gcc/clang only)
14+
function(oryx_enable_thread_sanitizer target_name)
15+
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
16+
message(FATAL_ERROR "Sanitizer supported only for gcc/clang!")
17+
endif()
18+
message(STATUS "Thread sanitizer enabled!")
19+
target_compile_options(${target_name} PRIVATE -fsanitize=thread)
20+
target_compile_options(${target_name} PRIVATE -fno-omit-frame-pointer)
21+
target_link_libraries(${target_name} PRIVATE -fsanitize=thread)
22+
endfunction()

include/oryx/error.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ class Error {
1919
std::string what_;
2020
};
2121

22-
}; // namespace oryx
22+
} // namespace oryx

include/oryx/periodic_scheduler.hpp

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#pragma once
22

3-
#include <atomic>
43
#include <limits>
5-
#include <optional>
64
#include <string>
75
#include <memory>
86
#include <chrono>
7+
#include <thread>
98
#include <utility>
109
#include <vector>
1110
#include <functional>
1211
#include <mutex>
12+
#include <stop_token>
1313

1414
#include "thread_pool.hpp"
1515

@@ -82,7 +82,7 @@ class PeriodicSchedulerImpl : public std::enable_shared_from_this<PeriodicSchedu
8282

8383
void Reset() {
8484
id_ = kTaskIDMax;
85-
scheduler_ = nullptr;
85+
scheduler_.reset();
8686
}
8787

8888
std::weak_ptr<PeriodicSchedulerImpl> scheduler_{};
@@ -91,21 +91,23 @@ class PeriodicSchedulerImpl : public std::enable_shared_from_this<PeriodicSchedu
9191

9292
auto Schedule(TaskFn&& task, Duration interval, TaskStopPolicy stop_policy = TaskStopPolicy::kWaitForCompletion)
9393
-> TaskHandle {
94-
TaskID id = task_counter_.fetch_add(1, std::memory_order_relaxed);
95-
{
96-
std::lock_guard lock{tasks_mtx_};
97-
tasks_.emplace_back(std::move(task), id, stop_policy, interval, std::future<void>{}, Clock::now());
98-
}
94+
std::unique_lock lock{mtx_};
95+
TaskID id = task_counter_++;
96+
tasks_.emplace_back(std::move(task), id, stop_policy, interval, std::future<void>(), Clock::now());
97+
lock.unlock();
9998
cv_.notify_all();
10099
return TaskHandle(std::enable_shared_from_this<PeriodicSchedulerImpl<Clock>>::weak_from_this(), id);
101100
}
102101

103102
auto GetNumTasks() const -> size_t {
104-
std::lock_guard lock{tasks_mtx_};
103+
std::lock_guard lock{mtx_};
105104
return tasks_.size();
106105
}
107106

108-
auto TotalNumTasks() const -> size_t { return task_counter_.load(std::memory_order_relaxed); }
107+
auto GetTaskCounter() const -> size_t {
108+
std::lock_guard lock{mtx_};
109+
return task_counter_;
110+
}
109111

110112
static auto Create(ThreadPool& pool) -> std::shared_ptr<PeriodicSchedulerImpl> {
111113
return std::shared_ptr<PeriodicSchedulerImpl>(new PeriodicSchedulerImpl(pool));
@@ -126,46 +128,43 @@ class PeriodicSchedulerImpl : public std::enable_shared_from_this<PeriodicSchedu
126128
PeriodicSchedulerImpl(ThreadPool& pool)
127129
: pool_(pool),
128130
tasks_(),
129-
tasks_mtx_(),
131+
mtx_(),
130132
cv_(),
131133
task_counter_(),
132134
worker_(&PeriodicSchedulerImpl<Clock>::ScheduleLoop, this) {}
133135

134136
auto StopTask(TaskHandle& handle) -> bool {
135-
std::optional<Task> task{};
136-
{
137-
std::lock_guard lock{tasks_mtx_};
138-
auto it = std::ranges::find_if(tasks_, [&handle](const auto& t) { return handle.id_ == t.id; });
139-
if (it != tasks_.end()) {
140-
task.emplace(std::move(*it));
141-
tasks_.erase(it);
142-
}
143-
}
144-
145-
if (!task) {
137+
std::unique_lock lock{mtx_};
138+
auto it = std::ranges::find_if(tasks_, [&handle](const auto& task) { return handle.id_ == task.id; });
139+
if (it == tasks_.end()) {
146140
return false;
147141
}
148142

149-
if (task->stop_policy == TaskStopPolicy::kWaitForCompletion) {
150-
if (task->promise.valid()) {
151-
task->promise.wait();
143+
auto task = std::move(*it);
144+
tasks_.erase(it);
145+
lock.unlock();
146+
147+
if (task.stop_policy == TaskStopPolicy::kWaitForCompletion) {
148+
if (task.promise.valid()) {
149+
task.promise.wait();
152150
}
153151
}
154-
155152
handle.Reset();
156153
return true;
157154
}
158155

159156
void ScheduleLoop(const std::stop_token& stoken) {
157+
std::stop_callback scb{stoken, [this] { cv_.notify_all(); }};
158+
160159
while (!stoken.stop_requested()) {
161-
std::unique_lock lock{tasks_mtx_};
160+
std::unique_lock lock{mtx_};
162161
if (tasks_.empty()) {
163-
cv_.wait(lock, stoken, [&] { return stoken.stop_requested() || !tasks_.empty(); });
162+
cv_.wait(lock, stoken, [this] { return !tasks_.empty(); });
164163
continue;
165164
}
166165

167166
auto now = Clock::now();
168-
TimePoint next_wake_time = TimePoint::max();
167+
auto next_wake_time = TimePoint::max();
169168
for (Task& task : tasks_) {
170169
if (now >= task.next_execution) {
171170
bool is_running = task.promise.valid() &&
@@ -180,17 +179,17 @@ class PeriodicSchedulerImpl : public std::enable_shared_from_this<PeriodicSchedu
180179
}
181180

182181
if (next_wake_time != TimePoint::max()) {
183-
cv_.wait_until(lock, stoken, next_wake_time,
184-
[&] { return stoken.stop_requested() || !tasks_.empty(); });
182+
auto id = task_counter_;
183+
cv_.wait_until(lock, stoken, next_wake_time, [this, id]() { return id < task_counter_; });
185184
}
186185
}
187186
}
188187

189188
ThreadPool& pool_;
190189
std::vector<Task> tasks_;
191-
std::mutex tasks_mtx_;
190+
mutable std::mutex mtx_;
192191
std::condition_variable_any cv_;
193-
std::atomic<TaskID> task_counter_;
192+
TaskID task_counter_;
194193
std::jthread worker_;
195194
};
196195

tests/chrono_mock_clock.hpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,39 @@
11
#pragma once
22

33
#include <chrono>
4+
#include <mutex>
45

56
namespace oryx {
67

78
struct ChronoMockClock {
8-
using duration = std::chrono::nanoseconds;
9+
public:
10+
using duration = std::chrono::milliseconds;
911
using rep = duration::rep;
1012
using period = duration::period;
11-
using time_point = std::chrono::time_point<std::chrono::steady_clock, duration>;
13+
using time_point = std::chrono::time_point<ChronoMockClock>;
1214

1315
static constexpr bool is_steady = true;
1416

15-
static auto now() noexcept -> time_point { return now_; }
16-
17-
static inline time_point now_ = {};
17+
static auto now() noexcept -> time_point {
18+
std::lock_guard lock{mtx_};
19+
return current_time;
20+
}
21+
static void set_time(time_point t) {
22+
std::lock_guard lock{mtx_};
23+
current_time = t;
24+
}
25+
static void advance(duration d) {
26+
std::lock_guard lock{mtx_};
27+
current_time += d;
28+
}
29+
static void reset() {
30+
std::lock_guard lock{mtx_};
31+
current_time = time_point{};
32+
}
33+
34+
private:
35+
static inline std::mutex mtx_{};
36+
static inline time_point current_time{};
1837
};
1938

2039
static_assert(std::chrono::is_clock_v<ChronoMockClock>, "ChronoMockClock does not satisfy chrono clock!");

0 commit comments

Comments
 (0)