1 #include <darwintest.h>
2
3 #include <stdatomic.h>
4
5 #include <unistd.h>
6 #include <pthread.h>
7 #include <sys/ulock.h>
8
9 #include <os/tsd.h>
10
11 #ifndef __TSD_MACH_THREAD_SELF
12 #define __TSD_MACH_THREAD_SELF 3
13 #endif
14
15 #pragma clang diagnostic push
16 #pragma clang diagnostic ignored "-Wbad-function-cast"
17 __inline static mach_port_name_t
_os_get_self(void)18 _os_get_self(void)
19 {
20 mach_port_name_t self = (mach_port_name_t)_os_tsd_get_direct(__TSD_MACH_THREAD_SELF);
21 return self;
22 }
23 #pragma clang diagnostic pop
24
25 T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true));
26
27 #pragma mark ulock_non_owner_wake
28
29 static _Atomic uint32_t test_ulock;
30
31 static void *
test_waiter(void * arg __unused)32 test_waiter(void *arg __unused)
33 {
34 for (;;) {
35 uint32_t test_ulock_owner = atomic_load_explicit(&test_ulock,
36 memory_order_relaxed);
37 int rc = __ulock_wait(UL_UNFAIR_LOCK | ULF_NO_ERRNO, &test_ulock,
38 test_ulock_owner, 0);
39 if (rc == -EINTR || rc == -EFAULT) {
40 continue;
41 }
42 T_ASSERT_GE(rc, 0, "__ulock_wait");
43 break;
44 }
45
46 T_PASS("Waiter woke");
47 T_END;
48
49 return NULL;
50 }
51
52 static mach_timebase_info_data_t timebase_info;
53
54 static uint64_t
nanos_to_abs(uint64_t nanos)55 nanos_to_abs(uint64_t nanos)
56 {
57 return nanos * timebase_info.denom / timebase_info.numer;
58 }
59
60 static void *
test_waiter_with_timeout(void * arg)61 test_waiter_with_timeout(void *arg)
62 {
63 uint64_t deadline = (uint64_t) arg;
64
65 for (;;) {
66 uint32_t test_ulock_owner = atomic_load_explicit(&test_ulock,
67 memory_order_relaxed);
68 int rc = __ulock_wait2(UL_UNFAIR_LOCK | ULF_NO_ERRNO | ULF_DEADLINE, &test_ulock,
69 test_ulock_owner, deadline, 0);
70 if (rc == -EINTR || rc == -EFAULT) {
71 continue;
72 }
73 T_ASSERT_EQ(rc, -ETIMEDOUT, "__ulock_wait");
74 break;
75 }
76
77 T_ASSERT_GE(mach_absolute_time(), deadline, "Current time is past the deadline specified");
78
79 T_PASS("Waiter woke");
80
81 return NULL;
82 }
83
84 static void *
test_waker(void * arg __unused)85 test_waker(void *arg __unused)
86 {
87 for (;;) {
88 int rc = __ulock_wake(UL_UNFAIR_LOCK | ULF_NO_ERRNO | ULF_WAKE_ALLOW_NON_OWNER,
89 &test_ulock, 0);
90 if (rc == -EINTR) {
91 continue;
92 }
93 T_ASSERT_EQ(rc, 0, "__ulock_wake");
94 break;
95 }
96 return NULL;
97 }
98
99 T_DECL(ulock_non_owner_wake, "ulock_wake respects non-owner wakes",
100 T_META_CHECK_LEAKS(false), T_META_TAG_VM_PREFERRED)
101 {
102 pthread_t waiter, waker;
103
104 atomic_store_explicit(&test_ulock, _os_get_self() & ~0x3u, memory_order_relaxed);
105
106 T_ASSERT_POSIX_ZERO(pthread_create(&waiter, NULL, test_waiter, NULL), "create waiter");
107
108 // wait for the waiter to reach the kernel
109 for (;;) {
110 int kernel_ulocks = __ulock_wake(UL_DEBUG_HASH_DUMP_PID, NULL, 0);
111 T_QUIET; T_ASSERT_NE(kernel_ulocks, -1, "UL_DEBUG_HASH_DUMP_PID");
112
113 if (kernel_ulocks == 1) {
114 T_LOG("waiter is now waiting");
115 break;
116 }
117 usleep(100);
118 }
119
120 T_ASSERT_POSIX_ZERO(pthread_create(&waker, NULL, test_waker, NULL), "create waker");
121
122 // won't ever actually join
123 pthread_join(waiter, NULL);
124 }
125
126 T_DECL(ulock_wait_deadline, "ulock_wait2 with deadline", T_META_CHECK_LEAKS(false), T_META_TAG_VM_PREFERRED)
127 {
128 kern_return_t kr = mach_timebase_info(&timebase_info);
129 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_timebase_info");
130
131 // Take the lock as self
132 atomic_store_explicit(&test_ulock, _os_get_self() & ~0x3u, memory_order_relaxed);
133
134 pthread_t waiter;
135
136 // Deadline in the past
137 uint64_t deadline = mach_absolute_time() - nanos_to_abs(3 * NSEC_PER_SEC);
138 T_ASSERT_POSIX_ZERO(pthread_create(&waiter, NULL, test_waiter_with_timeout, (void *) deadline), "create waiter");
139
140 pthread_join(waiter, NULL);
141
142 // Deadline in the future
143 deadline = mach_absolute_time() + nanos_to_abs(3 * NSEC_PER_SEC);
144 T_ASSERT_POSIX_ZERO(pthread_create(&waiter, NULL, test_waiter_with_timeout, (void *) deadline), "create waiter");
145
146 pthread_join(waiter, NULL);
147 T_END;
148 }
149