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 |
|
22 | typedef std::map<std::string, std::string> map_t;
|
23 |
|
24 | void* MapThread(void* p) {
|
25 | map_t& m = *(static_cast<map_t*>(p));
|
26 | m["foo"] = "bar";
|
27 | return nullptr;
|
28 | }
|
29 |
|
30 | TEST 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 |
|
42 | void* AppendListThread(void* p) {
|
43 | List<BigStr*>* mylist = static_cast<List<BigStr*>*>(p);
|
44 | mylist->append(StrFromC("thread"));
|
45 | return nullptr;
|
46 | }
|
47 |
|
48 | TEST 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 |
|
67 | void* 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 |
|
82 | TEST 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 |
|
104 | TEST 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 |
|
120 | TEST 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 |
|
135 | TEST 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 |
|
150 | TEST 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 |
|
165 | TEST 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 |
|
180 | GREATEST_MAIN_DEFS();
|
181 |
|
182 | int 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 | }
|