xref: /illumos-gate/usr/src/test/libc-tests/tests/clocklock/clock_lock.c (revision 068cf9dc70b00292d5e5ff1ab17f1b046215d6f6)
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
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
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
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 *
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
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
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