/*
 * preempt.c - support for kthread preemption
 */

#include <signal.h>
#include <string.h>

#include "base/log.h"
#include "runtime/thread.h"
#include "runtime/preempt.h"

#include "defs.h"

/* the current preemption count */
volatile __thread unsigned int preempt_cnt = PREEMPT_NOT_PENDING;
volatile __thread bool preempt_cede;

/* set a flag to indicate a preemption request is pending */
static void set_preempt_needed(void)
{
	preempt_cnt &= ~PREEMPT_NOT_PENDING;
}

/* handles preemption cede signals from the iokernel */
static void handle_sigusr1(int s, siginfo_t *si, void *c)
{
	STAT(PREEMPTIONS)++;
	set_preempt_needed();

	/* resume execution if preemption is disabled */
	if (!preempt_enabled()) {
		preempt_cede = true;
		return;
	}

	thread_cede();
}

static void flush_and_thread_yield(void)
{
	/* flush the inflight status */
#ifndef DEBUG
	if (__self)
                __self->status = __status;
#endif
	thread_yield();
}

/* handles preemption yield signals from GC runtime. */
static void handle_sigusr2(int s, siginfo_t *si, void *c)
{
	STAT(PREEMPTIONS)++;

	set_preempt_needed();
	/* resume execution if preemption is disabled */
	if (!preempt_enabled()) {
		return;
	}

	flush_and_thread_yield();
}

/**
 * preempt - entry point for preemption
 */
void preempt(void)
{
	if (preempt_cede) {
		preempt_cede = false;
		thread_cede();
	} else {
		clear_preempt_needed();
		flush_and_thread_yield();
	}
}

/**
 * preempt_init - global initializer for preemption support
 *
 * Returns 0 if successful. otherwise fail.
 */
int preempt_init(void)
{
	struct sigaction act;

	act.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESTART;

	if (sigemptyset(&act.sa_mask) != 0) {
		log_err("couldn't empty the signal handler mask");
		return -errno;
	}

	act.sa_sigaction = handle_sigusr1;
	if (sigaction(SIGUSR1, &act, NULL) == -1) {
		log_err("couldn't register signal handler");
		return -errno;
	}

	act.sa_sigaction = handle_sigusr2;
	if (sigaction(SIGUSR2, &act, NULL) == -1) {
		log_err("couldn't register signal handler");
		return -errno;
	}


	return 0;
}
