Skip to content

Commit e5be9ab

Browse files
committed
Added tests for data_race_checker
1 parent 1723cba commit e5be9ab

File tree

8 files changed

+215
-34
lines changed

8 files changed

+215
-34
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ set (CMAKE_CXX_STANDARD 23)
66

77
enable_testing()
88
add_subdirectory(tests)
9-
add_subdirectory(include/scl)
9+
add_subdirectory(include/scl)
10+
add_subdirectory(include/scl/utils)

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# scl - Safe Concurrency Library*
2+
[![test](https://github.com/drowaudio/scl/actions/workflows/test.yaml/badge.svg)](https://github.com/drowaudio/scl/actions/workflows/test.yaml)
23

34
This is an experiment to see how much of sync and send from Rust/Swift can be implemented in C++ and what problems arise when we try to do so. Is there anything we can learn about thread safety from these approaches.
45

@@ -15,6 +16,12 @@ This is an experiment to see how much of sync and send from Rust/Swift can be im
1516
- Template errors can be difficult to navigate
1617
- Just having to think about the types passed to scl types (e.g. safe_thread) can help the user think in a more thread-safe way
1718

19+
___
20+
### Supported Platforms
21+
- Clang 18
22+
- GCC 14.2
23+
- AppleClang 16
24+
1825
___
1926
## To Do:
2027
### `sync`/`send`

include/scl/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ include(../../cmake/icm_testing.cmake)
77
# Global include for icm based tests
88
include_directories("../include")
99

10-
file(GLOB_RECURSE pass_files LIST_DIRECTORIES false "${CMAKE_CURRENT_SOURCE_DIR}" *.test.cpp)
10+
icm_glob_build_failure_tests(
11+
PATTERN fail_*
12+
ERROR_MATCHES ${ERROR_STRING}
13+
)
14+
15+
file(GLOB pass_files LIST_DIRECTORIES false "${CMAKE_CURRENT_SOURCE_DIR}" pass_*.cpp)
1116
message(pass_files: ${pass_files})
1217

1318
foreach(pass_file ${pass_files})

include/scl/utils/CMakeLists.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
set(CMAKE_CXX_STANDARD 23)
2+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
3+
4+
include(../../../cmake/icm_build_failure_testing.cmake)
5+
include(../../../cmake/icm_testing.cmake)
6+
7+
# Global include for icm based tests
8+
include_directories("../../include")
9+
10+
file(GLOB test_files LIST_DIRECTORIES false "${CMAKE_CURRENT_SOURCE_DIR}" *_test.cpp)
11+
message(test_file: ${test_files})
12+
13+
foreach(test_file ${test_files})
14+
get_filename_component(name_without_extension "${test_file}" NAME_WE)
15+
icm_add_test(
16+
NAME ${name_without_extension}
17+
SOURCES ${test_file}
18+
)
19+
20+
if(MATCHES ${test_file} fail_*)
21+
set_tests_properties(${name_without_extension} PROPERTIES
22+
WILL_FAIL TRUE)
23+
endif()
24+
endforeach()

include/scl/utils/data_race_checker.h

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@
44

55
#pragma once
66

7+
#include <chrono>
8+
#include <cstdint>
9+
#include <fstream>
10+
#include <iostream>
11+
#include <source_location>
12+
#include <thread>
13+
#include <vector>
714
#include <atomic>
815

16+
#include "extrinsic_storage.h"
17+
918
// No Data race:
1019
// A reader enters a function and there is an active reader
1120
//
@@ -16,6 +25,12 @@
1625
//
1726
// Wait-free? (maybe the CMPX is only lock-free?)
1827

28+
namespace scl {
29+
30+
#ifndef DATA_RACE_DETECTED
31+
#define DATA_RACE_DETECTED std::abort();
32+
#endif
33+
1934
//==========================================
2035
//==========================================
2136
struct check_state
@@ -40,19 +55,19 @@ void read_started (check_state& state)
4055
++state.num_readers; // must be first
4156

4257
if (state.is_writing)
43-
std::terminate();
44-
// read during active write
58+
DATA_RACE_DETECTED
59+
// read during active write
4560
}
4661

4762
void write_started (check_state& state)
4863
{
4964
if (state.is_writing.exchange (true)) // must be first
50-
std::terminate();
51-
// write during active write
65+
DATA_RACE_DETECTED
66+
// write during active write
5267

5368
if (state.num_readers > 0)
54-
std::terminate();
55-
// write during active read
69+
DATA_RACE_DETECTED
70+
// write during active read
5671
}
5772

5873
void read_ended (check_state& state)
@@ -65,6 +80,7 @@ void write_ended (check_state& state)
6580
state.is_writing = false;
6681
}
6782

83+
#undef DATA_RACE_DETECTED
6884

6985
//==========================================
7086
//==========================================
@@ -101,24 +117,15 @@ struct scoped_check
101117

102118
//==========================================
103119
//==========================================
104-
#include "extrinsic_storage.h"
105-
106-
#include <chrono>
107-
#include <cstdint>
108-
#include <fstream>
109-
#include <iostream>
110-
#include <source_location>
111-
#include <thread>
112-
#include <vector>
113-
114120
template<typename Tag = check_state>
115-
class date_race_registry {
121+
class data_race_registry {
116122
static inline auto tags = extrinsic_storage<Tag>{};
117123
static inline auto log = std::ofstream{ "data-race-violations.log" };
118124
public:
119125

120-
static inline auto get_state(void* pobj) noexcept {
121-
return *tags.find_or_insert(pobj);
126+
static inline auto& get_state(const void* pobj) noexcept {
127+
// This const cast should be tidied up
128+
return *tags.find_or_insert(const_cast<void*> (pobj));
122129
}
123130

124131
static inline auto on_destroy(void* pobj) noexcept -> void { tags.erase(pobj); }
@@ -139,17 +146,6 @@ class date_race_registry {
139146
static inline auto on_write_ended(void* pobj) noexcept -> void {
140147
if (auto p = tags.find_or_insert(pobj)) { write_ended (*p); }
141148
}
142-
143-
// static inline auto on_get_alternative(void* pobj, uint32_t alt, std::source_location where = std::source_location::current()) -> void {
144-
// if (auto active = tags.find(pobj);
145-
// active // if we have discriminator info for this union
146-
// && *active != alt // and the discriminator not what is expected
147-
// && *active != unknown // and is not unknown
148-
// )
149-
// {
150-
// log << where.file_name() << '(' << where.line()
151-
// << "): union type safety violation - active member " << (*active == invalid ? "invalid" : std::to_string(*active))
152-
// << ", attempted to access " << alt << "\n";
153-
// }
154-
// }
155149
};
150+
151+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#include "data_race_checker.h"
6+
#include <vector>
7+
#include <thread>
8+
#include <span>
9+
#include <atomic>
10+
11+
template <typename T>
12+
class test_vector
13+
{
14+
public:
15+
test_vector() {
16+
// Create the entry (might allocate)
17+
scl::data_race_registry<>::get_state (this);
18+
}
19+
20+
~test_vector() {
21+
scl::data_race_registry<>::on_destroy (this);
22+
}
23+
24+
void reserve (size_t new_cap) {
25+
scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this));
26+
data.resize (new_cap);
27+
}
28+
29+
void clear() {
30+
scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this));
31+
data.clear();
32+
}
33+
34+
void push_back(const T& value) {
35+
scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this));
36+
data.push_back(value);
37+
}
38+
39+
void pop_back() {
40+
scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this));
41+
data.pop_back();
42+
}
43+
44+
T& operator[](size_t index) {
45+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this));
46+
return data[index];
47+
}
48+
49+
size_t size() const {
50+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this));
51+
return data.size();
52+
}
53+
54+
bool empty() const {
55+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this));
56+
return data.empty();
57+
}
58+
59+
size_t capacity() const {
60+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this));
61+
return data.capacity();
62+
}
63+
64+
private:
65+
std::vector<T> data;
66+
};
67+
68+
inline void test_no_data_race()
69+
{
70+
test_vector<size_t> vec;
71+
72+
// Fill the vector first
73+
for (auto c : std::ranges::iota_view (0uz, 1'000uz))
74+
vec.push_back (c);
75+
76+
// Then only read from the vector - no data race
77+
std::vector<std::thread> threads;
78+
79+
for (auto _ : std::ranges::iota_view (0, 3))
80+
threads.emplace_back([&]
81+
{
82+
for (auto _ : std::ranges::iota_view (0uz, 1'000'000uz))
83+
{
84+
volatile auto c = 0uz;
85+
86+
if (! vec.empty())
87+
c = vec[vec.size() - 1];
88+
}
89+
});
90+
91+
for (auto& t : threads)
92+
t.join();
93+
}
94+
95+
inline void test_data_race()
96+
{
97+
std::vector<std::thread> threads;
98+
test_vector<size_t> vec;
99+
100+
threads.emplace_back([&]
101+
{
102+
for (;;)
103+
{
104+
for (auto c : std::ranges::iota_view (0uz, 1'000uz))
105+
vec.push_back (c);
106+
107+
vec.clear();
108+
}
109+
});
110+
111+
for (auto _ : std::ranges::iota_view (0, 3))
112+
threads.emplace_back([&]
113+
{
114+
for (;;)
115+
{
116+
volatile auto c = 0uz;
117+
118+
if (! vec.empty())
119+
c = vec[vec.size() - 1];
120+
}
121+
});
122+
123+
for (auto& t : threads)
124+
t.join();
125+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
// Use exit(1) as ctest seems to count a raised signal as a pass...
6+
#define DATA_RACE_DETECTED std::exit(1);
7+
#include "data_race_checker.test.h"
8+
9+
int main()
10+
{
11+
test_data_race(); // won't return
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#define DATA_RACE_DETECTED std::exit(1);
6+
#include "data_race_checker.test.h"
7+
8+
int main()
9+
{
10+
test_no_data_race();
11+
}

0 commit comments

Comments
 (0)