]> git.saurik.com Git - apple/xnu.git/blob - tests/memorystatus_freeze_test.c
xnu-6153.81.5.tar.gz
[apple/xnu.git] / tests / memorystatus_freeze_test.c
1 #include <stdio.h>
2 #include <signal.h>
3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <mach-o/dyld.h>
6 #include <mach/mach_vm.h>
7 #include <mach/vm_page_size.h> /* Needed for vm_region info */
8 #include <mach/shared_region.h>
9 #include <mach/mach.h>
10
11 #ifdef T_NAMESPACE
12 #undef T_NAMESPACE
13 #endif
14 #include <darwintest.h>
15 #include <darwintest_utils.h>
16
17 #include "memorystatus_assertion_helpers.h"
18
19 T_GLOBAL_META(
20 T_META_NAMESPACE("xnu.vm"),
21 T_META_CHECK_LEAKS(false)
22 );
23
24 #define MEM_SIZE_MB 10
25 #define NUM_ITERATIONS 5
26 #define FREEZE_PAGES_MAX 256
27
28 #define CREATE_LIST(X) \
29 X(SUCCESS) \
30 X(TOO_FEW_ARGUMENTS) \
31 X(SYSCTL_VM_PAGESIZE_FAILED) \
32 X(VM_PAGESIZE_IS_ZERO) \
33 X(DISPATCH_SOURCE_CREATE_FAILED) \
34 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
35 X(SIGNAL_TO_PARENT_FAILED) \
36 X(MEMORYSTATUS_CONTROL_FAILED) \
37 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
38 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
39 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
40 X(EXIT_CODE_MAX)
41
42 #define EXIT_CODES_ENUM(VAR) VAR,
43 enum exit_codes_num {
44 CREATE_LIST(EXIT_CODES_ENUM)
45 };
46
47 #define EXIT_CODES_STRING(VAR) #VAR,
48 static const char *exit_codes_str[] = {
49 CREATE_LIST(EXIT_CODES_STRING)
50 };
51
52 static int
53 get_vmpage_size()
54 {
55 int vmpage_size;
56 size_t size = sizeof(vmpage_size);
57 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
58 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
59 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
60 return vmpage_size;
61 }
62
63 static pid_t child_pid = -1;
64 static int freeze_count = 0;
65
66 void move_to_idle_band(void);
67 void run_freezer_test(int);
68 void freeze_helper_process(void);
69 /* Gets and optionally sets the freeze pages max threshold */
70 int sysctl_freeze_pages_max(int* new_value);
71
72 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
73 * We're moving away from those unit tests, so they're copied here.
74 */
75
76 /* Cribbed from 'top'... */
77 static int
78 in_shared_region(mach_vm_address_t addr, cpu_type_t type)
79 {
80 mach_vm_address_t base = 0, size = 0;
81
82 switch (type) {
83 case CPU_TYPE_ARM:
84 base = SHARED_REGION_BASE_ARM;
85 size = SHARED_REGION_SIZE_ARM;
86 break;
87
88 case CPU_TYPE_ARM64:
89 base = SHARED_REGION_BASE_ARM64;
90 size = SHARED_REGION_SIZE_ARM64;
91 break;
92
93
94 case CPU_TYPE_X86_64:
95 base = SHARED_REGION_BASE_X86_64;
96 size = SHARED_REGION_SIZE_X86_64;
97 break;
98
99 case CPU_TYPE_I386:
100 base = SHARED_REGION_BASE_I386;
101 size = SHARED_REGION_SIZE_I386;
102 break;
103
104 case CPU_TYPE_POWERPC:
105 base = SHARED_REGION_BASE_PPC;
106 size = SHARED_REGION_SIZE_PPC;
107 break;
108
109 case CPU_TYPE_POWERPC64:
110 base = SHARED_REGION_BASE_PPC64;
111 size = SHARED_REGION_SIZE_PPC64;
112 break;
113
114 default: {
115 int t = type;
116
117 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
118 abort();
119 }
120 }
121
122 return addr >= base && addr < (base + size);
123 }
124
125 /* Get the resident private memory of the given pid */
126 static unsigned long long
127 get_rprvt(pid_t pid)
128 {
129 mach_port_name_t task;
130 kern_return_t kr;
131
132 mach_vm_size_t rprvt = 0;
133 mach_vm_size_t empty = 0;
134 mach_vm_size_t fw_private = 0;
135 mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported
136 // in terms of vm_kernel_page_size.
137 mach_vm_size_t regs = 0;
138
139 mach_vm_address_t addr;
140 mach_vm_size_t size;
141
142 int split = 0;
143
144 kr = task_for_pid(mach_task_self(), pid, &task);
145 T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child");
146
147 for (addr = 0;; addr += size) {
148 vm_region_top_info_data_t info;
149 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
150 mach_port_t object_name;
151
152 kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
153 if (kr != KERN_SUCCESS) {
154 break;
155 }
156
157 #if defined (__arm64__)
158 if (in_shared_region(addr, CPU_TYPE_ARM64)) {
159 #else
160 if (in_shared_region(addr, CPU_TYPE_ARM)) {
161 #endif
162 // Private Shared
163 fw_private += info.private_pages_resident * pagesize;
164
165 /*
166 * Check if this process has the globally shared
167 * text and data regions mapped in. If so, set
168 * split to TRUE and avoid checking
169 * again.
170 */
171 if (split == FALSE && info.share_mode == SM_EMPTY) {
172 vm_region_basic_info_data_64_t b_info;
173 mach_vm_address_t b_addr = addr;
174 mach_vm_size_t b_size = size;
175 count = VM_REGION_BASIC_INFO_COUNT_64;
176
177 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name);
178 if (kr != KERN_SUCCESS) {
179 break;
180 }
181
182 if (b_info.reserved) {
183 split = TRUE;
184 }
185 }
186
187 /*
188 * Short circuit the loop if this isn't a shared
189 * private region, since that's the only region
190 * type we care about within the current address
191 * range.
192 */
193 if (info.share_mode != SM_PRIVATE) {
194 continue;
195 }
196 }
197
198 regs++;
199
200 /*
201 * Update counters according to the region type.
202 */
203
204 if (info.share_mode == SM_COW && info.ref_count == 1) {
205 // Treat single reference SM_COW as SM_PRIVATE
206 info.share_mode = SM_PRIVATE;
207 }
208
209 switch (info.share_mode) {
210 case SM_LARGE_PAGE:
211 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
212 // since they are not shareable and are wired.
213 case SM_PRIVATE:
214 rprvt += info.private_pages_resident * pagesize;
215 rprvt += info.shared_pages_resident * pagesize;
216 break;
217
218 case SM_EMPTY:
219 empty += size;
220 break;
221
222 case SM_COW:
223 case SM_SHARED:
224 if (pid == 0) {
225 // Treat kernel_task specially
226 if (info.share_mode == SM_COW) {
227 rprvt += info.private_pages_resident * pagesize;
228 }
229 break;
230 }
231
232 if (info.share_mode == SM_COW) {
233 rprvt += info.private_pages_resident * pagesize;
234 }
235 break;
236
237 default:
238 assert(0);
239 break;
240 }
241 }
242
243 return rprvt;
244 }
245
246 void
247 move_to_idle_band(void)
248 {
249 memorystatus_priority_properties_t props;
250 /*
251 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
252 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
253 */
254 props.priority = JETSAM_PRIORITY_IDLE;
255 props.user_data = 0;
256
257 /*
258 * This requires us to run as root (in the absence of entitlement).
259 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
260 */
261 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, getpid(), 0, &props, sizeof(props))) {
262 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
263 }
264 }
265
266 void
267 freeze_helper_process(void)
268 {
269 size_t length;
270 int ret, freeze_enabled, errno_freeze_sysctl;
271 uint64_t resident_memory_before, resident_memory_after, vmpage_size;
272 vmpage_size = (uint64_t) get_vmpage_size();
273 resident_memory_before = get_rprvt(child_pid) / vmpage_size;
274
275 T_LOG("Freezing child pid %d", child_pid);
276 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
277 errno_freeze_sysctl = errno;
278 sleep(1);
279
280 /*
281 * The child process toggles its freezable state on each iteration.
282 * So a failure for every alternate freeze is expected.
283 */
284 if (freeze_count % 2) {
285 length = sizeof(freeze_enabled);
286 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
287 "failed to query vm.freeze_enabled");
288 if (freeze_enabled) {
289 errno = errno_freeze_sysctl;
290 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
291 } else {
292 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
293 T_LOG("Freeze has been disabled. Terminating early.");
294 T_END;
295 }
296 resident_memory_after = get_rprvt(child_pid) / vmpage_size;
297 uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL);
298 T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set");
299 if (resident_memory_before > freeze_pages_max) {
300 T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold.");
301 }
302 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
303 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
304 } else {
305 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
306 T_LOG("Freeze failed as expected");
307 }
308
309 freeze_count++;
310
311 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
312 }
313
314 void
315 run_freezer_test(int num_pages)
316 {
317 int ret, freeze_enabled;
318 char sz_str[50];
319 char **launch_tool_args;
320 char testpath[PATH_MAX];
321 uint32_t testpath_buf_size;
322 dispatch_source_t ds_freeze, ds_proc;
323 size_t length;
324
325 length = sizeof(freeze_enabled);
326 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
327 "failed to query vm.freeze_enabled");
328 if (!freeze_enabled) {
329 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
330 T_SKIP("Freeze has been disabled. Skipping test.");
331 }
332
333 signal(SIGUSR1, SIG_IGN);
334 ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
335 T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
336
337 dispatch_source_set_event_handler(ds_freeze, ^{
338 if (freeze_count < NUM_ITERATIONS) {
339 freeze_helper_process();
340 } else {
341 kill(child_pid, SIGKILL);
342 dispatch_source_cancel(ds_freeze);
343 }
344 });
345 dispatch_activate(ds_freeze);
346
347 testpath_buf_size = sizeof(testpath);
348 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
349 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
350 T_LOG("Executable path: %s", testpath);
351
352 sprintf(sz_str, "%d", num_pages);
353 launch_tool_args = (char *[]){
354 testpath,
355 "-n",
356 "allocate_pages",
357 "--",
358 sz_str,
359 NULL
360 };
361
362 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
363 ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
364 if (ret != 0) {
365 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
366 }
367 T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
368
369 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
370 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
371
372 dispatch_source_set_event_handler(ds_proc, ^{
373 int status = 0, code = 0;
374 pid_t rc = waitpid(child_pid, &status, 0);
375 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
376 code = WEXITSTATUS(status);
377
378 if (code == 0) {
379 T_END;
380 } else if (code > 0 && code < EXIT_CODE_MAX) {
381 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
382 } else {
383 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
384 }
385 });
386 dispatch_activate(ds_proc);
387
388 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
389 dispatch_main();
390 }
391
392 static void
393 allocate_pages(int num_pages)
394 {
395 int i, j, vmpgsize;
396 char val;
397 __block int num_iter = 0;
398 __block char **buf;
399 dispatch_source_t ds_signal;
400 vmpgsize = get_vmpage_size();
401 if (num_pages < 1) {
402 printf("Invalid number of pages to allocate: %d\n", num_pages);
403 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
404 }
405
406 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
407
408 /* Gives us the compression ratio we see in the typical case (~2.7) */
409 for (j = 0; j < num_pages; j++) {
410 buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
411 val = 0;
412 for (i = 0; i < vmpgsize; i += 16) {
413 memset(&buf[j][i], val, 16);
414 if (i < 3400 * (vmpgsize / 4096)) {
415 val++;
416 }
417 }
418 }
419
420 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
421 /* Signal to the parent that we're done allocating and it's ok to freeze us */
422 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
423 if (kill(getppid(), SIGUSR1) != 0) {
424 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
425 }
426 });
427
428 signal(SIGUSR1, SIG_IGN);
429 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
430 if (ds_signal == NULL) {
431 exit(DISPATCH_SOURCE_CREATE_FAILED);
432 }
433
434 dispatch_source_set_event_handler(ds_signal, ^{
435 int current_state, new_state;
436 volatile int tmp;
437
438 /* Make sure all the pages are accessed before trying to freeze again */
439 for (int x = 0; x < num_pages; x++) {
440 tmp = buf[x][0];
441 }
442
443 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
444 /* Sysprocs start off as unfreezable. Verify that first. */
445 if (num_iter == 0 && current_state != 0) {
446 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
447 }
448
449 /* Toggle freezable state */
450 new_state = (current_state) ? 0: 1;
451 printf("[%d] Changing state from %s to %s\n", getpid(),
452 (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
453 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
454 exit(MEMORYSTATUS_CONTROL_FAILED);
455 }
456
457 /* Verify that the state has been set correctly */
458 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
459 if (new_state != current_state) {
460 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
461 }
462 num_iter++;
463
464 if (kill(getppid(), SIGUSR1) != 0) {
465 exit(SIGNAL_TO_PARENT_FAILED);
466 }
467 });
468 dispatch_activate(ds_signal);
469 move_to_idle_band();
470
471 dispatch_main();
472 }
473
474 T_HELPER_DECL(allocate_pages,
475 "allocates pages to freeze",
476 T_META_ASROOT(true)) {
477 if (argc < 1) {
478 exit(TOO_FEW_ARGUMENTS);
479 }
480
481 int num_pages = atoi(argv[0]);
482 allocate_pages(num_pages);
483 }
484
485 T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) {
486 run_freezer_test(
487 (MEM_SIZE_MB << 20) / get_vmpage_size());
488 }
489
490 static int old_freeze_pages_max = 0;
491 static void
492 reset_freeze_pages_max()
493 {
494 if (old_freeze_pages_max != 0) {
495 sysctl_freeze_pages_max(&old_freeze_pages_max);
496 }
497 }
498
499 int
500 sysctl_freeze_pages_max(int* new_value)
501 {
502 static int set_end_handler = false;
503 int freeze_pages_max, ret;
504 size_t size = sizeof(freeze_pages_max);
505 ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size);
506 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max");
507 if (!set_end_handler) {
508 // Save the original value and instruct darwintest to restore it after the test completes
509 old_freeze_pages_max = freeze_pages_max;
510 T_ATEND(reset_freeze_pages_max);
511 set_end_handler = true;
512 }
513 return old_freeze_pages_max;
514 }
515
516 T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
517 int freeze_pages_max = FREEZE_PAGES_MAX;
518 sysctl_freeze_pages_max(&freeze_pages_max);
519 run_freezer_test(FREEZE_PAGES_MAX * 2);
520 }
521
522 T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) {
523 kern_return_t kern_ret;
524 /* Set the process to freezable */
525 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
526 T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable");
527 /* Signal to our parent that we can be frozen */
528 if (kill(getppid(), SIGUSR1) != 0) {
529 T_LOG("Unable to signal to parent process!");
530 exit(1);
531 }
532 while (1) {
533 ;
534 }
535 }
536
537 /* Launches the frozen_background helper as a managed process. */
538 static pid_t
539 launch_frozen_background_process()
540 {
541 pid_t pid;
542 char **launch_tool_args;
543 char testpath[PATH_MAX];
544 uint32_t testpath_buf_size;
545 int ret;
546
547 testpath_buf_size = sizeof(testpath);
548 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
549 printf("Launching %s\n", testpath);
550 launch_tool_args = (char *[]){
551 testpath,
552 "-n",
553 "frozen_background",
554 NULL
555 };
556 ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
557 if (ret != 0) {
558 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
559 }
560 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dt_launch_tool");
561 /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
562 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, pid, 1, NULL, 0);
563 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
564 return pid;
565 }
566
567 static void
568 freeze_process(pid_t pid)
569 {
570 int ret, freeze_enabled, errno_freeze_sysctl;
571 size_t length;
572 T_LOG("Freezing pid %d", pid);
573
574 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
575 errno_freeze_sysctl = errno;
576 length = sizeof(freeze_enabled);
577 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
578 "failed to query vm.freeze_enabled");
579 if (freeze_enabled) {
580 errno = errno_freeze_sysctl;
581 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
582 } else {
583 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
584 T_LOG("Freeze has been disabled. Terminating early.");
585 T_END;
586 }
587 }
588
589 static void
590 memorystatus_assertion_test_demote_frozen()
591 {
592 #if !CONFIG_EMBEDDED
593 T_SKIP("Freezing processes is only supported on embedded");
594 #endif
595 /*
596 * Test that if we assert a priority on a process, freeze it, and then demote all frozen processes, it does not get demoted below the asserted priority.
597 * Then remove thee assertion, and ensure it gets demoted properly.
598 */
599 /* these values will remain fixed during testing */
600 int active_limit_mb = 15; /* arbitrary */
601 int inactive_limit_mb = 7; /* arbitrary */
602 /* Launch the child process, and elevate its priority */
603 int requestedpriority;
604 dispatch_source_t ds_signal, ds_exit;
605 requestedpriority = JETSAM_PRIORITY_UI_SUPPORT;
606
607 /* Wait for the child process to tell us that it's ready, and then freeze it */
608 signal(SIGUSR1, SIG_IGN);
609 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
610 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
611 dispatch_source_set_event_handler(ds_signal, ^{
612 int sysctl_ret;
613 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
614 freeze_process(child_pid);
615 /* Agressive demotion */
616 sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, NULL, 0);
617 T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes failed");
618 /* Check */
619 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
620 T_LOG("Relinquishing our assertion.");
621 /* Relinquish our assertion, and check that it gets demoted. */
622 relinquish_assertion_priority(child_pid, 0x0);
623 (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished.");
624 /* Kill the child */
625 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Unable to kill child process");
626 T_END;
627 });
628
629 /* Launch the child process and set the initial properties on it. */
630 child_pid = launch_frozen_background_process();
631 set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false);
632 set_assertion_priority(child_pid, requestedpriority, 0x0);
633 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
634 /* Listen for exit. */
635 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
636 dispatch_source_set_event_handler(ds_exit, ^{
637 int status = 0, code = 0;
638 pid_t rc = waitpid(child_pid, &status, 0);
639 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
640 code = WEXITSTATUS(status);
641 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
642 T_END;
643 });
644
645 dispatch_activate(ds_exit);
646 dispatch_activate(ds_signal);
647 dispatch_main();
648 }
649
650 T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
651 memorystatus_assertion_test_demote_frozen();
652 }