1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2024 Oxide Computer Company
14 */
15
16 /*
17 * Test the various pthreads related clock based locking routines. These all
18 * attempt to take some form of lock and utilize a timeout that can be specified
19 * in terms of a given clock (i.e. CLOCK_REALTIME and CLOCK_HIGHRES). In
20 * particular we want to cover:
21 *
22 * - Invalid clock sources
23 * - Invalid timeouts ignored when acquired
24 * - Invalid timeouts caught when used
25 * - We can successfully get an ETIMEDOUT and that time has advanced at least
26 * that much
27 */
28
29 #include <err.h>
30 #include <stdlib.h>
31 #include <pthread.h>
32 #include <sys/debug.h>
33 #include <sys/sysmacros.h>
34 #include <stdbool.h>
35 #include <errno.h>
36 #include <string.h>
37
38 #include "clock_lock.h"
39
40 /*
41 * This is a generic 100ms timeout that we can use. We use it for some tests
42 * that require an absolute timeout that is in the future but we don't want to
43 * bother computing.
44 */
45 const struct timespec clock_to_100ms = { 0, MSEC2NSEC(100) };
46
47 /*
48 * A series of invalid clocks. The first is usable for both relative and
49 * absolute operations. The others which use negative times should only fail for
50 * the relative operations at this time.
51 */
52 const struct timespec clock_to_invns = { 0, NANOSEC * 2 };
53 const struct timespec clock_to_invnegs = { -12345, 0 };
54 const struct timespec clock_to_invnegns = { 100, -0x23 };
55
56 void
clock_rel_to_abs(clockid_t clock,const struct timespec * restrict rel,struct timespec * restrict abs)57 clock_rel_to_abs(clockid_t clock, const struct timespec *restrict rel,
58 struct timespec *restrict abs)
59 {
60 if (clock_gettime(clock, abs) != 0) {
61 err(EXIT_FAILURE, "failed to get absolute time for clock %d",
62 clock);
63 }
64
65 abs->tv_nsec += rel->tv_nsec;
66 abs->tv_sec += rel->tv_sec;
67 if (abs->tv_nsec > NANOSEC) {
68 abs->tv_sec += abs->tv_nsec / NANOSEC;
69 abs->tv_nsec %= NANOSEC;
70 }
71 }
72
73 bool
clock_abs_after(clockid_t clock,const struct timespec * to)74 clock_abs_after(clockid_t clock, const struct timespec *to)
75 {
76 struct timespec now;
77
78 if (clock_gettime(clock, &now) != 0) {
79 err(EXIT_FAILURE, "failed to get absolute time for clock %d",
80 clock);
81 }
82
83 if (now.tv_sec > to->tv_sec)
84 return (true);
85
86 return (now.tv_sec == to->tv_sec && now.tv_nsec > to->tv_nsec);
87 }
88
89 bool
clock_rel_after(clockid_t clock,const struct timespec * start,const struct timespec * to)90 clock_rel_after(clockid_t clock, const struct timespec *start,
91 const struct timespec *to)
92 {
93 struct timespec now, absto;
94
95 if (clock_gettime(clock, &now) != 0) {
96 err(EXIT_FAILURE, "failed to get absolute time for clock %d",
97 clock);
98 }
99
100 absto.tv_nsec = start->tv_nsec + to->tv_nsec;
101 absto.tv_sec = start->tv_sec + to->tv_sec;
102 if (absto.tv_nsec > NANOSEC) {
103 absto.tv_sec += absto.tv_nsec / NANOSEC;
104 absto.tv_nsec %= NANOSEC;
105 }
106
107 if (now.tv_sec > absto.tv_sec)
108 return (true);
109
110 return (now.tv_sec == absto.tv_sec && now.tv_nsec > absto.tv_nsec);
111 }
112
113 typedef struct {
114 const clock_test_t *cthr_test;
115 void *cthr_prim;
116 bool cthr_ret;
117 } clock_test_thr_t;
118
119 static void *
clock_test_thr(void * arg)120 clock_test_thr(void *arg)
121 {
122 clock_test_thr_t *thr = arg;
123 thr->cthr_ret = thr->cthr_test->ct_test(thr->cthr_test,
124 thr->cthr_prim);
125 return (NULL);
126 }
127
128 static bool
clock_test_one(const clock_test_t * test)129 clock_test_one(const clock_test_t *test)
130 {
131 void *prim;
132 bool ret;
133
134 test->ct_ops->lo_create(test->ct_desc, &prim);
135
136 /*
137 * If the test requires that the lock be held in some way, then we spawn
138 * the test to run in another thread to avoid any issues with recursive
139 * actions. Otherwise we let it run locally.
140 */
141 if (test->ct_enter) {
142 clock_test_thr_t thr_test;
143 pthread_t thr;
144 int pret;
145
146 test->ct_ops->lo_lock(prim);
147 thr_test.cthr_test = test;
148 thr_test.cthr_prim = prim;
149 thr_test.cthr_ret = false;
150
151 if ((pret = pthread_create(&thr, NULL, clock_test_thr,
152 &thr_test)) != 0) {
153 errc(EXIT_FAILURE, pret, "TEST FAILED: %s: internal "
154 "error creating test thread", test->ct_desc);
155 }
156
157 if ((pret = pthread_join(thr, NULL)) != 0) {
158 errc(EXIT_FAILURE, pret, "TEST FAILED: %s: internal "
159 "error joining test thread", test->ct_desc);
160 }
161 ret = thr_test.cthr_ret;
162 test->ct_ops->lo_unlock(prim);
163 } else {
164 ret = test->ct_test(test, prim);
165 }
166
167 test->ct_ops->lo_destroy(prim);
168
169 if (ret) {
170 (void) printf("TEST PASSED: %s\n", test->ct_desc);
171 }
172
173 return (ret);
174 }
175
176 int
main(void)177 main(void)
178 {
179 int ret = EXIT_SUCCESS;
180
181 for (size_t i = 0; i < clock_mutex_ntests; i++) {
182 if (!clock_test_one(&clock_mutex_tests[i])) {
183 ret = EXIT_FAILURE;
184 }
185 }
186
187 for (size_t i = 0; i < clock_rwlock_ntests; i++) {
188 if (!clock_test_one(&clock_rwlock_tests[i])) {
189 ret = EXIT_FAILURE;
190 }
191 }
192
193 for (size_t i = 0; i < clock_sem_ntests; i++) {
194 if (!clock_test_one(&clock_sem_tests[i])) {
195 ret = EXIT_FAILURE;
196 }
197 }
198
199 for (size_t i = 0; i < clock_cond_ntests; i++) {
200 if (!clock_test_one(&clock_cond_tests[i])) {
201 ret = EXIT_FAILURE;
202 }
203 }
204
205
206 return (ret);
207 }
208