1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <inttypes.h>
6#include <stddef.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <threads.h>
11#include <unittest/unittest.h>
12#include <zircon/syscalls.h>
13#include <zircon/thread_annotations.h>
14#include <zircon/time.h>
15#include <zircon/types.h>
16
17static mtx_t g_mutex = MTX_INIT;
18
19static void xlog(const char* str) {
20    zx_time_t now = zx_clock_get(ZX_CLOCK_UTC);
21    unittest_printf("[%08" PRIu64 ".%08" PRIu64 "]: %s",
22                    now / 1000000000, now % 1000000000, str);
23}
24
25static int mutex_thread_1(void* arg) {
26    xlog("thread 1 started\n");
27
28    for (int times = 0; times < 300; times++) {
29        mtx_lock(&g_mutex);
30        zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
31        mtx_unlock(&g_mutex);
32    }
33
34    xlog("thread 1 done\n");
35    return 0;
36}
37
38static int mutex_thread_2(void* arg) {
39    xlog("thread 2 started\n");
40
41    for (int times = 0; times < 150; times++) {
42        mtx_lock(&g_mutex);
43        zx_nanosleep(zx_deadline_after(ZX_USEC(2)));
44        mtx_unlock(&g_mutex);
45    }
46
47    xlog("thread 2 done\n");
48    return 0;
49}
50
51static int mutex_thread_3(void* arg) {
52    xlog("thread 3 started\n");
53
54    for (int times = 0; times < 100; times++) {
55        mtx_lock(&g_mutex);
56        zx_nanosleep(zx_deadline_after(ZX_USEC(3)));
57        mtx_unlock(&g_mutex);
58    }
59
60    xlog("thread 3 done\n");
61    return 0;
62}
63
64static bool got_lock_1 = false;
65static bool got_lock_2 = false;
66static bool got_lock_3 = false;
67
68// These tests all conditionally acquire the lock, by design. The
69// thread safety analysis is not up to this, so disable it.
70static int mutex_try_thread_1(void* arg) TA_NO_THREAD_SAFETY_ANALYSIS {
71    xlog("thread 1 started\n");
72
73    for (int times = 0; times < 300 || !got_lock_1; times++) {
74        int status = mtx_trylock(&g_mutex);
75        zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
76        if (status == thrd_success) {
77            got_lock_1 = true;
78            mtx_unlock(&g_mutex);
79        }
80    }
81
82    xlog("thread 1 done\n");
83    return 0;
84}
85
86static int mutex_try_thread_2(void* arg) TA_NO_THREAD_SAFETY_ANALYSIS {
87    xlog("thread 2 started\n");
88
89    for (int times = 0; times < 150 || !got_lock_2; times++) {
90        int status = mtx_trylock(&g_mutex);
91        zx_nanosleep(zx_deadline_after(ZX_USEC(2)));
92        if (status == thrd_success) {
93            got_lock_2 = true;
94            mtx_unlock(&g_mutex);
95        }
96    }
97
98    xlog("thread 2 done\n");
99    return 0;
100}
101
102static int mutex_try_thread_3(void* arg) TA_NO_THREAD_SAFETY_ANALYSIS {
103    xlog("thread 3 started\n");
104
105    for (int times = 0; times < 100 || !got_lock_3; times++) {
106        int status = mtx_trylock(&g_mutex);
107        zx_nanosleep(zx_deadline_after(ZX_USEC(3)));
108        if (status == thrd_success) {
109            got_lock_3 = true;
110            mtx_unlock(&g_mutex);
111        }
112    }
113
114    xlog("thread 3 done\n");
115    return 0;
116}
117
118static bool test_initializer(void) {
119    BEGIN_TEST;
120
121    int ret = mtx_init(&g_mutex, mtx_timed);
122    ASSERT_EQ(ret, thrd_success, "failed to initialize mtx_t");
123
124    END_TEST;
125}
126
127static bool test_mutexes(void) {
128    BEGIN_TEST;
129    thrd_t thread1, thread2, thread3;
130
131    thrd_create_with_name(&thread1, mutex_thread_1, NULL, "thread 1");
132    thrd_create_with_name(&thread2, mutex_thread_2, NULL, "thread 2");
133    thrd_create_with_name(&thread3, mutex_thread_3, NULL, "thread 3");
134
135    thrd_join(thread1, NULL);
136    thrd_join(thread2, NULL);
137    thrd_join(thread3, NULL);
138
139    END_TEST;
140}
141
142static bool test_try_mutexes(void) {
143    BEGIN_TEST;
144    thrd_t thread1, thread2, thread3;
145
146    thrd_create_with_name(&thread1, mutex_try_thread_1, NULL, "thread 1");
147    thrd_create_with_name(&thread2, mutex_try_thread_2, NULL, "thread 2");
148    thrd_create_with_name(&thread3, mutex_try_thread_3, NULL, "thread 3");
149
150    thrd_join(thread1, NULL);
151    thrd_join(thread2, NULL);
152    thrd_join(thread3, NULL);
153
154    EXPECT_TRUE(got_lock_1, "failed to get lock 1");
155    EXPECT_TRUE(got_lock_2, "failed to get lock 2");
156    EXPECT_TRUE(got_lock_3, "failed to get lock 3");
157
158    END_TEST;
159}
160
161static bool test_static_initializer(void) {
162    BEGIN_TEST;
163
164    static mtx_t static_mutex = MTX_INIT;
165    mtx_t auto_mutex;
166    memset(&auto_mutex, 0xae, sizeof(auto_mutex));
167    mtx_init(&auto_mutex, mtx_plain);
168
169    EXPECT_BYTES_EQ((const uint8_t*)&static_mutex, (const uint8_t*)&auto_mutex, sizeof(mtx_t), "MTX_INIT and mtx_init differ!");
170
171    END_TEST;
172}
173
174typedef struct {
175    mtx_t mutex;
176    zx_handle_t start_event;
177    zx_handle_t done_event;
178} timeout_args;
179
180static int test_timeout_helper(void* ctx) TA_NO_THREAD_SAFETY_ANALYSIS {
181    timeout_args* args = ctx;
182    ASSERT_EQ(mtx_lock(&args->mutex), thrd_success, "f to lock");
183    // Inform the main thread that we have acquired the lock.
184    ASSERT_EQ(zx_object_signal(args->start_event, 0, ZX_EVENT_SIGNALED), ZX_OK,
185              "failed to signal");
186    // Wait until the main thread has completed its test.
187    ASSERT_EQ(zx_object_wait_one(args->done_event, ZX_EVENT_SIGNALED, ZX_TIME_INFINITE, NULL),
188              ZX_OK, "failed to wait");
189    ASSERT_EQ(mtx_unlock(&args->mutex), thrd_success, "failed to unlock");
190    return 0;
191}
192
193static bool test_timeout_elapsed(void) {
194    BEGIN_TEST;
195
196    const zx_duration_t kRelativeDeadline = ZX_MSEC(100);
197
198    timeout_args args;
199    ASSERT_EQ(thrd_success, mtx_init(&args.mutex, mtx_plain), "could not create mutex");
200    ASSERT_EQ(zx_event_create(0, &args.start_event), ZX_OK, "could not create event");
201    ASSERT_EQ(zx_event_create(0, &args.done_event), ZX_OK, "could not create event");
202
203    thrd_t helper;
204    ASSERT_EQ(thrd_create(&helper, test_timeout_helper, &args), thrd_success, "");
205    // Wait for the helper thread to acquire the lock.
206    ASSERT_EQ(zx_object_wait_one(args.start_event, ZX_EVENT_SIGNALED, ZX_TIME_INFINITE, NULL),
207              ZX_OK, "failed to wait");
208
209    for (int i = 0; i < 5; ++i) {
210        zx_time_t now = zx_clock_get(ZX_CLOCK_UTC);
211        struct timespec then = {
212            .tv_sec = now / ZX_SEC(1),
213            .tv_nsec = now % ZX_SEC(1),
214        };
215        then.tv_nsec += kRelativeDeadline;
216        if (then.tv_nsec > (long)ZX_SEC(1)) {
217            then.tv_nsec -= ZX_SEC(1);
218            then.tv_sec += 1;
219        }
220        int rc = mtx_timedlock(&args.mutex, &then);
221        ASSERT_EQ(rc, thrd_timedout, "wait should time out");
222        zx_duration_t elapsed = zx_time_sub_time(zx_clock_get(ZX_CLOCK_UTC), now);
223        EXPECT_GE(elapsed, kRelativeDeadline, "wait returned early");
224    }
225
226    // Inform the helper thread that we are done.
227    ASSERT_EQ(zx_object_signal(args.done_event, 0, ZX_EVENT_SIGNALED),
228              ZX_OK, "failed to signal");
229    ASSERT_EQ(thrd_join(helper, NULL), thrd_success, "failed to join");
230
231    mtx_destroy(&args.mutex);
232    ASSERT_EQ(zx_handle_close(args.start_event), ZX_OK, "failed to close event");
233    ASSERT_EQ(zx_handle_close(args.done_event), ZX_OK, "failed to close event");
234
235    END_TEST;
236}
237
238BEGIN_TEST_CASE(mtx_tests)
239RUN_TEST(test_initializer)
240RUN_TEST(test_mutexes)
241RUN_TEST(test_try_mutexes)
242RUN_TEST(test_static_initializer)
243RUN_TEST(test_timeout_elapsed)
244END_TEST_CASE(mtx_tests)
245
246#ifndef BUILD_COMBINED_TESTS
247int main(int argc, char** argv) {
248    return unittest_run_all_tests(argc, argv) ? 0 : -1;
249}
250#endif
251