1 : /*
2 : * Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 :
7 : /*
8 : * Exercise the NaClMutex object.
9 : *
10 : * NB: Some tests are death tests, e.g., ensuring that a NaClMutex
11 : * object really implements a binary semaphore instead of a recursive
12 : * semaphore requires intentionally deadlocking and aborting via a
13 : * test time out.
14 : */
15 : #include <string.h>
16 :
17 : #include "native_client/src/include/nacl_macros.h"
18 : #include "native_client/src/include/portability.h"
19 :
20 : #include "native_client/src/shared/platform/platform_init.h"
21 : #include "native_client/src/shared/platform/nacl_log.h"
22 : #include "native_client/src/shared/platform/nacl_sync.h"
23 : #include "native_client/src/shared/platform/nacl_sync_checked.h"
24 : #include "native_client/src/shared/platform/nacl_threads.h"
25 : #include "native_client/src/shared/platform/nacl_time.h"
26 :
27 : #define TIMEOUT_THREAD_STACK_SIZE (8192)
28 : #define ON_TIMEOUT_EXIT_SUCCESS ((void *) 0)
29 : #define ON_TIMEOUT_EXIT_FAILURE ((void *) 1)
30 :
31 : /*
32 : * This test contains several subtests. The goal is to check that
33 : * NaClMutex objects actually implement binary mutexes. To do so, the
34 : * subtests check that:
35 : *
36 : * Lock followed by another Lock leads to a deadlock;
37 : *
38 : * TryLock followed by a Lock leads to a deadlock;
39 : *
40 : * Lock followed by a TryLock leads to an NACL_SYNC_EBUSY return; and
41 : *
42 : * TryLock followed by another TryLock leads to an NACL_SYNC_EBUSY return.
43 : *
44 : * The way that deadlock detection works is not by alarm(3) timeouts,
45 : * since that's not cross platform (not available in Windows).
46 : * Instead, we spawn a separate thread via the NaClThread abstraction
47 : * prior to invoking the expected-to-deadlock operation. That thread
48 : * will use NaClNanosleep (which is the cross platform version of
49 : * nanosleep(2)) to suspend itself for a while, and then invoke
50 : * exit(0) to shut down the whole process, which includes getting rid
51 : * of the deadlocked thread. On the other hand, if the
52 : * expected-to-deadlock operation didn't actually cause a deadlock,
53 : * the main thread immediately exits with a non-zero exit status.
54 : *
55 : * This test strategy is subject to scheduler races. It may be the
56 : * case that we would get a false negative -- the test would report
57 : * that there is no bug, because the scheduler paused the thread that
58 : * invoked the expected-to-deadlock operation long enough that the
59 : * deadlock detection thread fires. This is expected to be extremely
60 : * rare, since the timeout value (defaulting to 500ms) is much larger
61 : * than a single scheduling quantum on any host operating system, and
62 : * the likelihood that the scheduler happens to interrupt the thread
63 : * at precisely the wrong place ought to be low. (We expect that the
64 : * largest scheduling quantum that we would encounter to be ~16.7ms or
65 : * 60Hz.) In any case, a false negative will not create false alarm.
66 : *
67 : * In the case where the second lock operation is a TryLock, we do not
68 : * expect the TryLock to block and just verify proper behavior based
69 : * on the return value. We detect regressions where the TryLock
70 : * blocks by spawning a timeout thread that would cause the test to
71 : * exit with a non-zero exit status. In these cases, we can have a
72 : * false positive due to scheduler problems on an extremely heavily
73 : * loaded machine, but in practice this should not occur.
74 : */
75 :
76 :
77 : /*
78 : * A second thread is spawned to cause the whole process to exit after
79 : * g_timeout_milliseconds.
80 : */
81 : uint32_t g_timeout_milliseconds = 500;
82 :
83 : /*
84 : * TimeOutThread is responsible for doing deadlock detection. If the
85 : * main thread hits a deadlock, then this thread will time out and
86 : * exit with the status specified by the thread_state argument.
87 : *
88 : * If the caller expect a deadlock in the main thread, the
89 : * thread_state value will be 0 for success to indicate that the
90 : * expected deadlock occurred; if caller does not expect a deadlock in
91 : * the main thread, the thread_state value should be non-zero.
92 : */
93 1 : void WINAPI TimeOutThread(void *thread_state) {
94 : struct nacl_abi_timespec ts;
95 1 : int time_out_exit_status = (int) (uintptr_t) thread_state;
96 :
97 1 : ts.tv_sec = g_timeout_milliseconds / 1000;
98 1 : ts.tv_nsec = (g_timeout_milliseconds % 1000) * 1000 * 1000;
99 :
100 1 : while (0 != NaClNanosleep(&ts, &ts)) {
101 : /*
102 : * On POSIX operating systems nanosleep (which underlies
103 : * NaClNanosleep) can return early with errno equal to
104 : * NACL_ABI_EINTR if, for example, a signal interrupted its sleep.
105 : * When this occurs, the second timespec argument (if non-NULL) is
106 : * overwritten with the remaining time to sleep, and so we can
107 : * immediately sleep again. We also expose the EINVAL and EFAULT
108 : * error returns (translated to NACL_ABI_), but since we set the
109 : * initial timespec value those should never occur.
110 : */
111 0 : continue;
112 : }
113 : /*
114 : * If we reach here, we assume that the main thread has deadlocked
115 : * and so we optimistically report that via the exit status.
116 : */
117 1 : exit(time_out_exit_status);
118 : }
119 :
120 : /*
121 : * The following tests do not Dtor the NaClMutex objects, since we
122 : * only run one test before exiting. This is a hard requirement for
123 : * deadlock detection where the second lock is a NaClXMutexLock (the
124 : * trylock case could be simpler, though we also spawn a time-out
125 : * thread in that case in case the error is a deadlock rather than an
126 : * immediate return with NACL_SYNC_BUSY).
127 : */
128 :
129 1 : int TestLockLock(void) {
130 : struct NaClMutex mu;
131 : struct NaClThread nt;
132 1 : printf("TestLockLock\n");
133 1 : printf("Constructing mutex\n");
134 1 : if (!NaClMutexCtor(&mu)) return 1;
135 1 : printf("Locking mutex\n");
136 1 : NaClXMutexLock(&mu);
137 1 : printf("Spawning timeout thread\n");
138 : /* NULL is exit status if timeout occurs */
139 : if (!NaClThreadCtor(&nt, TimeOutThread,
140 1 : ON_TIMEOUT_EXIT_SUCCESS, TIMEOUT_THREAD_STACK_SIZE)) {
141 0 : return 1;
142 : }
143 1 : printf("Locking mutex again\n");
144 1 : NaClXMutexLock(&mu);
145 : /* should deadlock and timeout thread should exit process */
146 0 : printf("ERROR: Double locking succeeded?!?\n");
147 0 : return 1;
148 0 : }
149 :
150 1 : int TestLockTrylock(void) {
151 : struct NaClMutex mu;
152 : struct NaClThread nt;
153 1 : printf("TestLockTrylock\n");
154 1 : printf("Constructing mutex\n");
155 1 : if (!NaClMutexCtor(&mu)) return 1;
156 1 : printf("Locking mutex\n");
157 1 : NaClXMutexLock(&mu);
158 1 : printf("Spawning timeout thread\n");
159 : /* 1 is exit status if timeout occurs */
160 : if (!NaClThreadCtor(&nt, TimeOutThread,
161 1 : ON_TIMEOUT_EXIT_FAILURE, TIMEOUT_THREAD_STACK_SIZE)) {
162 0 : return 1;
163 : }
164 1 : printf("Trylocking mutex\n");
165 : /*
166 : * Here the NaClMutexTryLock should not block, and the role of the
167 : * timeout thread is to detect implementation flaws where a trylock
168 : * blocks.
169 : */
170 1 : if (NaClMutexTryLock(&mu) == NACL_SYNC_BUSY) {
171 1 : printf("OK: trylock failed\n");
172 1 : return 0;
173 : }
174 0 : printf("ERROR: Trylock succeeded?!?\n");
175 0 : return 1;
176 1 : }
177 :
178 1 : int TestTrylockLock(void) {
179 : struct NaClMutex mu;
180 : struct NaClThread nt;
181 1 : printf("TestLockTrylock\n");
182 1 : printf("Constructing mutex\n");
183 1 : if (!NaClMutexCtor(&mu)) return 1;
184 1 : printf("Trylocking mutex\n");
185 1 : if (NaClMutexTryLock(&mu) != NACL_SYNC_OK) {
186 0 : printf("ERROR: Trylock failed\n");
187 0 : return 1;
188 : }
189 1 : printf("Spawning timeout thread\n");
190 : if (!NaClThreadCtor(&nt, TimeOutThread,
191 1 : ON_TIMEOUT_EXIT_SUCCESS, TIMEOUT_THREAD_STACK_SIZE)) {
192 0 : return 1;
193 : }
194 1 : printf("Locking mutex\n");
195 1 : NaClXMutexLock(&mu);
196 0 : printf("ERROR: Lock succeeded?!?\n");
197 0 : return 1;
198 0 : }
199 :
200 1 : int TestTrylockTrylock(void) {
201 : struct NaClMutex mu;
202 : struct NaClThread nt;
203 1 : printf("TestLockTrylock\n");
204 1 : printf("Constructing mutex\n");
205 1 : if (!NaClMutexCtor(&mu)) return 1;
206 1 : printf("Trylocking mutex\n");
207 1 : if (NaClMutexTryLock(&mu) != NACL_SYNC_OK) {
208 0 : printf("ERROR: Trylock failed\n");
209 0 : return 1;
210 : }
211 1 : printf("Spawning timeout thread\n");
212 : if (!NaClThreadCtor(&nt, TimeOutThread,
213 1 : ON_TIMEOUT_EXIT_FAILURE, TIMEOUT_THREAD_STACK_SIZE)) {
214 0 : return 1;
215 : }
216 1 : printf("Trylocking mutex again\n");
217 1 : if (NaClMutexTryLock(&mu) == NACL_SYNC_BUSY) {
218 1 : printf("OK: trylock failed\n");
219 1 : return 0;
220 : }
221 0 : printf("ERROR: Trylock succeeded?!?\n");
222 0 : return 1;
223 1 : }
224 :
225 : struct Tests {
226 : char const *name;
227 : int (*test)(void);
228 : };
229 :
230 : struct Tests const tests[] = {
231 : { "lock_lock", TestLockLock, },
232 : { "lock_trylock", TestLockTrylock, },
233 : { "trylock_lock", TestTrylockLock, },
234 : { "trylock_trylock", TestTrylockTrylock, },
235 : };
236 :
237 0 : void usage(void) {
238 : size_t ix;
239 :
240 : fprintf(stderr,
241 : "Usage: nacl_sync_test [-t timeout_milliseconds] [-T test]\n"
242 0 : " where <test> is one of\n");
243 0 : for (ix = 0; ix < NACL_ARRAY_SIZE(tests); ++ix) {
244 0 : fprintf(stderr, " %s\n", tests[ix].name);
245 0 : }
246 0 : }
247 :
248 1 : int main(int ac, char **av) {
249 : int opt;
250 1 : int (*test_fn)(void) = NULL;
251 : size_t ix;
252 : int retcode;
253 :
254 1 : while (-1 != (opt = getopt(ac, av, "t:T:"))) {
255 1 : switch (opt) {
256 : case 't':
257 0 : g_timeout_milliseconds = strtoul(optarg, (char **) NULL, 0);
258 0 : break;
259 : case 'T':
260 1 : for (ix = 0; ix < NACL_ARRAY_SIZE(tests); ++ix) {
261 1 : if (!strcmp(optarg, tests[ix].name)) {
262 1 : test_fn = tests[ix].test;
263 1 : break;
264 : }
265 1 : }
266 1 : break;
267 : default:
268 0 : usage();
269 0 : return 1;
270 : }
271 1 : }
272 1 : if (test_fn == NULL) {
273 0 : fprintf(stderr, "No test specified\n");
274 0 : usage();
275 0 : return 1;
276 : }
277 1 : NaClPlatformInit();
278 1 : retcode = (*test_fn)();
279 1 : NaClPlatformFini();
280 1 : return retcode;
281 1 : }
|