OILS / cpp / data_race_test.cc View on Github | oilshell.org

206 lines, 107 significant
1// cpp/data_race_test.cc
2//
3// The idea behind this test is that these two questions have the SAME answer:
4//
5// 1. Is it safe to perform ops X and Y in 2 different threads, without
6// synchronization? (If they share nontrivial state, then NO.)
7// 2. Is it safe to perform op X in a signal handler, and op Y in the main
8// thread, without synchronization?
9
10#include <pthread.h>
11
12#include <atomic>
13#include <map>
14
15#include "cpp/core.h"
16#include "mycpp/runtime.h"
17#include "vendor/greatest.h"
18
19// Example from
20// https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual
21
22typedef std::map<std::string, std::string> map_t;
23
24void* MapThread(void* p) {
25 map_t& m = *(static_cast<map_t*>(p));
26 m["foo"] = "bar";
27 return nullptr;
28}
29
30TEST tsan_demo() {
31 map_t m;
32 pthread_t t;
33#if 1
34 pthread_create(&t, 0, MapThread, &m);
35 printf("foo=%s\n", m["foo"].c_str());
36 pthread_join(t, 0);
37#endif
38
39 PASS();
40}
41
42void* AppendListThread(void* p) {
43 List<BigStr*>* mylist = static_cast<List<BigStr*>*>(p);
44 mylist->append(StrFromC("thread"));
45 return nullptr;
46}
47
48TEST list_test() {
49 List<BigStr*>* mylist = nullptr;
50 StackRoots _roots({&mylist});
51
52 mylist = NewList<BigStr*>({});
53 mylist->append(StrFromC("main"));
54
55 pthread_t t;
56 pthread_create(&t, 0, AppendListThread, mylist);
57 // DATA RACE DETECTED by ThreadSanitizer! You can't append to a List from
58 // two threads concurrently.
59#if 1
60 mylist->append(StrFromC("concurrent"));
61#endif
62 pthread_join(t, 0);
63
64 PASS();
65}
66
67void* SimulateSignalHandlers(void* p) {
68 auto signal_safe = static_cast<pyos::SignalSafe*>(p);
69
70 // Send a whole bunch of SIGINT in a tight loop, which will append to
71 // List<int> signal_queue_.
72 for (int i = 0; i < pyos::kMaxPendingSignals + 10; ++i) {
73 // This line can race with PollSigInt and LastSignal
74 signal_safe->UpdateFromSignalHandler(SIGINT);
75
76 // This line can race with SetSigWinchCode
77 signal_safe->UpdateFromSignalHandler(SIGWINCH);
78 }
79 return nullptr;
80}
81
82TEST take_pending_signals_test() {
83 pyos::SignalSafe signal_safe;
84
85 // Background thread that simulates signal handler
86 pthread_t t;
87 pthread_create(&t, 0, SimulateSignalHandlers, &signal_safe);
88
89 // Concurrent access in main thread
90 List<int>* received = signal_safe.TakePendingSignals();
91 log("(1) received %d signals", len(received));
92
93 received->clear();
94 signal_safe.ReuseEmptyList(received);
95
96 received = signal_safe.TakePendingSignals();
97 log("(2) received %d signals", len(received));
98
99 pthread_join(t, 0);
100
101 PASS();
102}
103
104TEST set_sigwinch_test() {
105 pyos::SignalSafe signal_safe;
106
107 // Background thread that simulates signal handler
108 pthread_t t;
109 pthread_create(&t, 0, SimulateSignalHandlers, &signal_safe);
110
111 // Concurrent access in main thread
112 signal_safe.SetSigWinchCode(pyos::UNTRAPPED_SIGWINCH);
113
114 pthread_join(t, 0);
115 PASS();
116}
117
118// #define LOCK_FREE_ATOMICS in core.h makes this PASS ThreadSanitizer
119
120TEST last_signal_test() {
121 pyos::SignalSafe signal_safe;
122
123 // Background thread that simulates signal handler
124 pthread_t t;
125 pthread_create(&t, 0, SimulateSignalHandlers, &signal_safe);
126
127 // Concurrent access in main thread
128 int last_signal = signal_safe.LastSignal();
129 log("last signal = %d", last_signal);
130
131 pthread_join(t, 0);
132 PASS();
133}
134
135TEST poll_sigint_test() {
136 pyos::SignalSafe signal_safe;
137
138 // Background thread that simulates signal handler
139 pthread_t t;
140 pthread_create(&t, 0, SimulateSignalHandlers, &signal_safe);
141
142 // Concurrent access in main thread
143 bool sigint = signal_safe.PollSigInt();
144 log("sigint? = %d", sigint);
145
146 pthread_join(t, 0);
147 PASS();
148}
149
150TEST poll_sigwinch_test() {
151 pyos::SignalSafe signal_safe;
152
153 // Background thread that simulates signal handler
154 pthread_t t;
155 pthread_create(&t, 0, SimulateSignalHandlers, &signal_safe);
156
157 // Concurrent access in main thread
158 bool sigwinch = signal_safe.PollSigWinch();
159 log("sigwinch? = %d", sigwinch);
160
161 pthread_join(t, 0);
162 PASS();
163}
164
165TEST atomic_demo() {
166 std::atomic<int> a(3);
167
168 // true on my machine
169 log("is_lock_free = %d", a.is_lock_free());
170
171 log("a.load() = %d", a.load());
172
173 a.store(42);
174
175 log("a.load() = %d", a.load());
176
177 PASS();
178}
179
180GREATEST_MAIN_DEFS();
181
182int main(int argc, char** argv) {
183 gHeap.Init();
184
185 GREATEST_MAIN_BEGIN();
186
187 RUN_TEST(tsan_demo);
188 RUN_TEST(list_test);
189
190 // SignalSafe tests
191 RUN_TEST(take_pending_signals_test);
192 RUN_TEST(set_sigwinch_test);
193 RUN_TEST(last_signal_test);
194 RUN_TEST(poll_sigint_test);
195 RUN_TEST(poll_sigwinch_test);
196
197 RUN_TEST(atomic_demo);
198
199 // Also test: Can MarkObjects() race with UpdateFromSignalHandler() ? It
200 // only reads the ObjHeader, so it doesn't seem like it.
201
202 gHeap.CleanProcessExit();
203
204 GREATEST_MAIN_END(); /* display results */
205 return 0;
206}