Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
/*
 * turnstiles_test: Tests turnstile kernel primitive.
 */

#ifdef T_NAMESPACE
#undef T_NAMESPACE
#endif

#include <darwintest.h>
#include <darwintest_multiprocess.h>

#include <pthread.h>
#include <launch.h>
#include <servers/bootstrap.h>
#include <stdlib.h>
#include <sys/event.h>
#include <unistd.h>
#include <crt_externs.h>
#include <sys/sysctl.h>
#include <sys/types.h>

#define SYSCTL_TURNSTILE_TEST_USER_DEFAULT            1
#define SYSCTL_TURNSTILE_TEST_USER_HASHTABLE          2
#define SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT          3
#define SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE        4

T_GLOBAL_META(T_META_NAMESPACE("xnu.turnstiles_test"));

static void
thread_create_at_qos(qos_class_t qos, void * (*function)(void *), int type)
{
	qos_class_t qos_thread;
	pthread_t thread;
	pthread_attr_t attr;
	int ret;

	ret = setpriority(PRIO_DARWIN_ROLE, 0, PRIO_DARWIN_ROLE_UI_FOCAL);
	if (ret != 0) {
		T_LOG("set priority failed\n");
	}

	pthread_attr_init(&attr);
	pthread_attr_set_qos_class_np(&attr, qos, 0);
	pthread_create(&thread, &attr, function, (void *)type);

	T_LOG("pthread created\n");
	pthread_get_qos_class_np(thread, &qos_thread, NULL);
	T_EXPECT_EQ(qos_thread, (qos_class_t)qos, NULL);
}

static int
get_sched_pri(thread_t thread_port)
{
	kern_return_t kr;

	thread_extended_info_data_t extended_info;
	mach_msg_type_number_t count = THREAD_EXTENDED_INFO_COUNT;
	kr = thread_info(thread_port, THREAD_EXTENDED_INFO,
	    (thread_info_t)&extended_info, &count);

	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "thread_info");
	return extended_info.pth_curpri;
}

static int
get_base_pri(thread_t thread_port)
{
	kern_return_t kr;

	thread_extended_info_data_t extended_info;
	mach_msg_type_number_t count = THREAD_EXTENDED_INFO_COUNT;
	kr = thread_info(thread_port, THREAD_EXTENDED_INFO,
	    (thread_info_t)&extended_info, &count);

	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "thread_info");
	return extended_info.pth_priority;
}

static void
turnstile_prim_lock(int type)
{
	int ret;
	uint64_t tid;
	int in_val = type;
	pthread_threadid_np(NULL, &tid);
	T_LOG("sysctlbyname lock type %d called from thread %llu \n", type, tid);
	ret = sysctlbyname("kern.turnstiles_test_lock", NULL, 0, &in_val, sizeof(in_val));
	T_LOG("sysctlbyname lock returned from thread %llu with value %d \n", tid, ret);
}

static void
turnstile_prim_unlock(int type)
{
	int ret;
	uint64_t tid;
	int in_val = type;
	pthread_threadid_np(NULL, &tid);
	T_LOG("sysctlbyname unlock type %d called from thread %llu \n", type, tid);
	ret = sysctlbyname("kern.turnstiles_test_unlock", NULL, 0, &in_val, sizeof(in_val));
	T_LOG("sysctlbyname unlock returned from thread %llu with value %d \n", tid, ret);
}

struct thread_data {
	int pri_to_set;
	int lock1;
	int lock2;
	unsigned int sleep;
	int sched_pri_to_check;
	int base_pri_to_check;
};

static void *
chain_locking(void* args)
{
	struct thread_data* data = (struct thread_data*) args;
	int policy, pri;
	int ret;
	struct sched_param param;

	/* Change our priority to pri_to_set */
	ret = pthread_getschedparam(pthread_self(), &policy, &param);
	T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "pthread_getschedparam");

	param.sched_priority = data->pri_to_set;

	/* this sets both sched and base pri */
	ret = pthread_setschedparam(pthread_self(), policy, &param);
	T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "pthread_setschedparam");

	pri = get_sched_pri(mach_thread_self());

	T_ASSERT_EQ(pri, data->pri_to_set, "Priority before holding locks");

	/* take lock1 */
	if (data->lock1) {
		turnstile_prim_lock(data->lock1);
	}

	/* take lock2 */
	if (data->lock2) {
		turnstile_prim_lock(data->lock2);
	}

	if (data->sleep) {
		sleep(data->sleep);
	}

	if (data->sched_pri_to_check) {
		pri = get_sched_pri(mach_thread_self());
		T_ASSERT_EQ(pri, data->sched_pri_to_check, "Sched priority while holding locks");
	}

	if (data->base_pri_to_check) {
		pri = get_base_pri(mach_thread_self());
		T_ASSERT_EQ(pri, data->base_pri_to_check, "Base priority while holding locks");
	}

	if (data->lock2) {
		turnstile_prim_unlock(data->lock2);
	}

	if (data->lock1) {
		turnstile_prim_unlock(data->lock1);
	}

	pri = get_sched_pri(mach_thread_self());
	T_ASSERT_EQ(pri, data->pri_to_set, "Priority after releasing locks");

	return NULL;
}

static void *
take_lock_check_priority(void * arg)
{
	int old_pri = get_base_pri(mach_thread_self());
	int unboosted_pri;
	int boosted_pri;
	int after_unlock_pri;
	uint64_t tid;
	int type = (int)arg;

	pthread_threadid_np(NULL, &tid);

	T_ASSERT_EQ(old_pri, 37, "thread(%llu) priority before acquiring the lock is %d\n", tid, old_pri);

	/* Take the test lock */
	turnstile_prim_lock(type);

	unboosted_pri = get_base_pri(mach_thread_self());
	T_ASSERT_EQ(unboosted_pri, 37, "thread(%llu) priority after acquiring the lock (uncontended) is %d\n", tid, unboosted_pri);

	sleep(8);

	/* Check for elevated priority */
	boosted_pri =  get_base_pri(mach_thread_self());
	T_ASSERT_EQ(boosted_pri, 47, "thread(%llu) priority after contention by 47 thread is %d\n", tid, boosted_pri);

	/* Drop the lock */
	turnstile_prim_unlock(type);

	/* Check for regular priority */
	after_unlock_pri =  get_base_pri(mach_thread_self());
	T_ASSERT_EQ(after_unlock_pri, 37, "thread(%llu) priority after dropping lock is %d\n", tid, after_unlock_pri);

	return NULL;
}

static void *
try_to_take_lock_and_unlock(void *arg)
{
	uint64_t tid;
	int type = (int)arg;

	pthread_threadid_np(NULL, &tid);
	sleep(4);

	int old_pri = get_base_pri(mach_thread_self());
	T_ASSERT_EQ(old_pri, 47, "thread(%llu) priority before acquiring the lock is %d\n", tid, old_pri);

	/* Try taking the test lock */
	turnstile_prim_lock(type);
	sleep(2);
	turnstile_prim_unlock(type);
	return NULL;
}

static void *
take_lock_and_exit(void * arg)
{
	int old_pri = get_base_pri(mach_thread_self());
	int unboosted_pri;
	int boosted_pri;
	uint64_t tid;
	int type = (int)arg;

	pthread_threadid_np(NULL, &tid);

	T_ASSERT_EQ(old_pri, 37, "thread(%llu) priority before acquiring the lock is %d\n", tid, old_pri);

	/* Take the test lock */
	turnstile_prim_lock(type);

	unboosted_pri =  get_base_pri(mach_thread_self());
	T_ASSERT_EQ(unboosted_pri, 37, "thread(%llu) priority after acquiring the lock (uncontended) is %d\n", tid, unboosted_pri);

	sleep(8);

	/* Check for elevated priority */
	boosted_pri =  get_base_pri(mach_thread_self());
	T_ASSERT_EQ(boosted_pri, 47, "thread(%llu) priority after contention by 47 thread is %d\n", tid, boosted_pri);

	/* return without unlocking the lock */
	return NULL;
}

static void *
unlock_an_owner_exited_lock(void *arg)
{
	uint64_t tid;
	int type = (int)arg;

	pthread_threadid_np(NULL, &tid);
	sleep(12);

	int old_pri = get_base_pri(mach_thread_self());
	T_ASSERT_EQ(old_pri, 47, "thread(%llu) priority before acquiring the lock is %d\n", tid, old_pri);

	/* Unlock the test lock causing the turnstile code to call thread_deallocate_safe */
	turnstile_prim_unlock(type);
	return NULL;
}

/*
 * Test 1: test if lock contended by a UI thread boosts the owner to UI qos.
 */
static void
test1(int type)
{
	T_LOG("Test 1: test if lock contended by a UI thread boosts the owner to UI qos");

	/* Create a thread at IN and take lock */
	thread_create_at_qos(QOS_CLASS_USER_INITIATED, &take_lock_check_priority, type);

	/* Create a thread at UI and try to take lock */
	thread_create_at_qos(QOS_CLASS_USER_INTERACTIVE, &try_to_take_lock_and_unlock, type);

	sleep(12);
	return;
}

/*
 * Test 2: test if lock contended by a 2 UI thread boosts the owner to UI qos.
 */
static void
test2(int type)
{
	T_LOG("Test 2: test if lock contended by a 2 UI thread boosts the owner to UI qos");

	/* Create a thread at IN and take lock */
	thread_create_at_qos(QOS_CLASS_USER_INITIATED, &take_lock_check_priority, type);

	/* Create a thread at UI and try to take lock */
	thread_create_at_qos(QOS_CLASS_USER_INTERACTIVE, &try_to_take_lock_and_unlock, type);

	/* Create a thread at UI and try to take lock */
	thread_create_at_qos(QOS_CLASS_USER_INTERACTIVE, &try_to_take_lock_and_unlock, type);

	sleep(16);
	return;
}

/*
 * Test 3: test if lock owner thread exiting without unlocking allows turnstile to work correctly.
 */
static void
test3(int type)
{
	T_LOG("Test 3: test if lock owner thread exiting without unlocking allows turnstile to work correctly");

	/* Create a thread at IN and take lock */
	thread_create_at_qos(QOS_CLASS_USER_INITIATED, &take_lock_and_exit, type);

	/* Create a thread at UI and try to take lock */
	thread_create_at_qos(QOS_CLASS_USER_INTERACTIVE, &try_to_take_lock_and_unlock, type);

	/* Create a thread at UI and try to take lock */
	thread_create_at_qos(QOS_CLASS_USER_INTERACTIVE, &unlock_an_owner_exited_lock, type);

	sleep(16);
	return;
}

/*
 * Test 4: test if a chain of user-space turnstile primitives followed by kernel primitives works correctly.
 */
static void
test4(void)
{
	pthread_t threads[5] = {};
	struct thread_data data[5] = {};

	T_LOG("Test 4: test if a chain of user-space turnstile primitives followed by kernel primitives works correctly");

	/*
	 * Chain: t4->ud->t3->uh->t2->kh->t1->kd->t0
	 * ud and uh (user space turnstiles) will push base pri and sched pri
	 * kd and kh (kernel space turnstiles) will push sched pri
	 * sched pri should be propagated up to the end
	 * kh is the breaking point of the chain for sched pri
	 */


	/* Create a thread at priority 4 and take SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT lock */
	data[0].pri_to_set = 4;
	data[0].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be not locked */
	data[0].lock2 = NULL;
	data[0].sleep = 10; /* long sleep, nothing is blocking this thread */
	data[0].sched_pri_to_check = 60;
	data[0].base_pri_to_check = 4;
	pthread_create(&threads[0], NULL, chain_locking, (void *)&data[0]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 31 and take SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT */
	data[1].pri_to_set = 31;
	data[1].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be not locked */
	data[1].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be locked */
	data[1].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
	data[1].sched_pri_to_check = 60;
	data[1].base_pri_to_check = 31;
	pthread_create(&threads[1], NULL, chain_locking, (void *)&data[1]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 40 and take SYSCTL_TURNSTILE_TEST_USER_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE */
	data[2].pri_to_set = 40;
	data[2].lock1 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be not locked */
	data[2].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be locked */
	data[2].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
	data[2].sched_pri_to_check = 60;
	data[2].base_pri_to_check = 60;
	pthread_create(&threads[2], NULL, chain_locking, (void *)&data[2]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 47 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT lock followed by SYSCTL_TURNSTILE_TEST_USER_HASHTABLE */
	data[3].pri_to_set = 47;
	data[3].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be not locked */
	data[3].lock2 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be locked */
	data[3].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
	data[3].sched_pri_to_check = 60;
	data[3].base_pri_to_check = 60;
	pthread_create(&threads[3], NULL, chain_locking, (void *)&data[3]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 60 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT */
	data[4].pri_to_set = 60;
	data[4].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be locked */
	data[4].lock2 = NULL;
	data[4].sleep = 0; /* no need to sleep, nothing should be pushing by the time it acquires the lock */
	data[4].sched_pri_to_check = 60; /* this is its own priority */
	data[4].base_pri_to_check = 60;
	pthread_create(&threads[4], NULL, chain_locking, (void *)&data[4]);

	sleep(16);
	return;
}

/*
 * Test 5: test if a chain of user-space turnstile primitives interleaved by kernel primitives works correctly.
 */
static void
test5(void)
{
	pthread_t threads[5] = {};
	struct thread_data data[5] = {};

	T_LOG("Test 5: test if a chain of user-space turnstile primitives interleaved by kernel primitives works correctly");

	/*
	 * Chain: t4->ud->t3->kh->t2->uh->t1->kd->t0
	 * ud and uh (user space turnstiles) will push base pri and sched pri
	 * kd and kh (kernel space turnstiles) will push sched pri
	 * uh is the breaking point of the chain for sched pri
	 */

	/* Create a thread at priority 4 and take SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT lock */
	data[0].pri_to_set = 4;
	data[0].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be not locked */
	data[0].lock2 = NULL;
	data[0].sleep = 10; /* long sleep, nothing is blocking this thread */
	data[0].sched_pri_to_check = 41;
	data[0].base_pri_to_check = 4;
	pthread_create(&threads[0], NULL, chain_locking, (void *)&data[0]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 31 and take SYSCTL_TURNSTILE_TEST_USER_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT */
	data[1].pri_to_set = 31;
	data[1].lock1 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be not locked */
	data[1].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be locked */
	data[1].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
	data[1].sched_pri_to_check = 41;
	data[1].base_pri_to_check = 41;
	pthread_create(&threads[1], NULL, chain_locking, (void *)&data[1]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 41 and take SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_USER_HASHTABLE */
	data[2].pri_to_set = 41;
	data[2].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be not locked */
	data[2].lock2 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be locked */
	data[2].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
	data[2].sched_pri_to_check = 60;
	data[2].base_pri_to_check = 41;
	pthread_create(&threads[2], NULL, chain_locking, (void *)&data[2]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 47 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE */
	data[3].pri_to_set = 47;
	data[3].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be not locked */
	data[3].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be locked */
	data[3].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
	data[3].sched_pri_to_check = 60;
	data[3].base_pri_to_check = 60;
	pthread_create(&threads[3], NULL, chain_locking, (void *)&data[3]);
	sleep(2); /* give the thread time to acquire the lock */

	/* Create a thread at priority 60 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT */
	data[4].pri_to_set = 60;
	data[4].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be locked */
	data[4].lock2 = NULL;
	data[4].sleep = 0; /* no need to sleep, nothing should be pushing by the time it acquires the lock */
	data[4].sched_pri_to_check = 60; /* this is its own priority */
	data[4].base_pri_to_check = 60;
	pthread_create(&threads[4], NULL, chain_locking, (void *)&data[4]);

	sleep(16);
	return;
}

T_DECL(turnstile_test, "Turnstile test", T_META_ASROOT(YES))
{
	test1(SYSCTL_TURNSTILE_TEST_USER_DEFAULT);
	test2(SYSCTL_TURNSTILE_TEST_USER_DEFAULT);
	test3(SYSCTL_TURNSTILE_TEST_USER_DEFAULT);

	test1(SYSCTL_TURNSTILE_TEST_USER_HASHTABLE);
	test2(SYSCTL_TURNSTILE_TEST_USER_HASHTABLE);
	test3(SYSCTL_TURNSTILE_TEST_USER_HASHTABLE);

	/*
	 * rdar://problem/46302128
	 * These tests are using a sysctl to lock a dummy kernel resource that uses turnstile.
	 * However a thread holding a kernel push from turnstile should never return in
	 * userspace, and rdar://problem/24194397 adds an assert for it.
	 */
	//test4();
	//test5();
}