occlum/test/exit_group/main.c
Tate, Hongliang Tian 663f548f94 Workaround exit_group syscall
BACKGROUND

The exit_group syscall, which is implicitly called by libc after the main function
returns, kills all threads in a thread group, even if these threads are
running, sleeping, or waiting on a futex.

PROBLEM

In normal use cases, exit_group does nothing since a well-written program
should terminate all threads before the main function returns. But when this is
not the case, exit_group can clean up the mess.

Currently, Occlum does not implement exit_group. And the Occlum PAL process
waits for all tasks (i.e., SGX threads) to finish before exiting. So without
exit_group implemented, some tasks may be still running if after the main task
exits. And this causes the Occlum PAL process to wait---forever.

WORKAROUND

To implement a real exit_group, we need signals to kill threads. But we do not
have signals, yet. So we come up with a workaround: instead of waiting all
tasks to finish in PAL, we just wait for the main task. As soon as the main
task exits, the PAL process terminates, killing the remaining tasks.
2019-11-07 13:34:53 +00:00

84 lines
2.5 KiB
C

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <linux/futex.h>
#include "test.h"
// ============================================================================
// Test case
// ============================================================================
//
// Three types of threads that will not exit voluntarily
//
// Type 1: a busy loop thread
static void* busyloop_thread_func(void* _) {
while (1) {
// By calling getpid, we give the LibOS a chance to force the thread
// to terminate if exit_group is called by any thread in a thread group
getpid();
}
return NULL;
}
// Type 2: a sleeping thread
static void* sleeping_thread_func(void* _) {
unsigned int a_year_in_sec = 365 * 24 * 60 * 60;
sleep(a_year_in_sec);
return NULL;
}
// Type 3: a thead that keeps waiting on a futex
static void* futex_wait_thread_func(void* _) {
// Wait on a futex forever
int my_private_futex = 0;
syscall(SYS_futex, &my_private_futex, FUTEX_WAIT, my_private_futex);
return NULL;
}
// exit_group syscall should terminate all threads in a thread group.
int test_exit_group_to_force_threads_terminate(void) {
// Create three types of threads that will not exit voluntarily
pthread_t busyloop_thread;
if (pthread_create(&busyloop_thread, NULL, busyloop_thread_func, NULL) < 0) {
printf("ERROR: pthread_create failed\n");
return -1;
}
pthread_t sleeping_thread;
if (pthread_create(&sleeping_thread, NULL, sleeping_thread_func, NULL) < 0) {
printf("ERROR: pthread_create failed\n");
return -1;
}
pthread_t futex_wait_thread;
if (pthread_create(&futex_wait_thread, NULL, futex_wait_thread_func, NULL) < 0) {
printf("ERROR: pthread_create failed\n");
return -1;
}
// Sleep for a while to make sure all three threads are running
useconds_t _200ms = 200 * 1000;
usleep(_200ms);
// exit_group syscall will be called eventually by libc's exit, after the
// main function returns. If Occlum can terminate normally, this means
// exit_group syscall taking effect.
return 0;
}
// ============================================================================
// Test suite
// ============================================================================
static test_case_t test_cases[] = {
TEST_CASE(test_exit_group_to_force_threads_terminate)
};
int main() {
return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}