void luasandbox_timer_start(luasandbox_timer_set * lts)
{
	if (lts->is_running) {
		// Already running
		return;
	}
	lts->is_running = 1;
	// Initialise usage timer
	clock_gettime(LUASANDBOX_CLOCK_ID, &lts->usage_start);

	// Create normal timer if requested
	if (!luasandbox_timer_is_zero(&lts->normal_remaining)) {
		lts->normal_running = 1;
		luasandbox_timer_create_one(&lts->normal_timer, lts->sandbox, LUASANDBOX_TIMER_NORMAL);
		luasandbox_timer_set_one_time(&lts->normal_timer, &lts->normal_remaining);
	} else {
		lts->normal_running = 0;
	}
	// Create emergency timer if requested
	if (!luasandbox_timer_is_zero(&lts->emergency_remaining)) {
		lts->emergency_running = 1;
		luasandbox_timer_create_one(&lts->emergency_timer, lts->sandbox, LUASANDBOX_TIMER_EMERGENCY);
		luasandbox_timer_set_one_time(&lts->emergency_timer, &lts->emergency_remaining);
	} else {
		lts->emergency_running = 0;
	}
}
int luasandbox_timer_start(luasandbox_timer_set * lts)
{
	if (lts->is_running) {
		// Already running
		return 1;
	}
	lts->is_running = 1;
	// Initialise usage timer
	clock_gettime(LUASANDBOX_CLOCK_ID, &lts->usage_start);

	// Create limiter timer if requested
	if (!luasandbox_timer_is_zero(&lts->limiter_remaining)) {
		luasandbox_timer * timer = luasandbox_timer_create_one(
			lts->sandbox, LUASANDBOX_TIMER_LIMITER);
		if (!timer) {
			lts->limiter_running = 0;
			return 0;
		}
		lts->limiter_timer = timer;
		lts->limiter_running = 1;
		luasandbox_timer_set_one_time(timer, &lts->limiter_remaining);
	} else {
		lts->limiter_running = 0;
	}
	return 1;
}
static void luasandbox_timer_handle_signal(int signo, siginfo_t * info, void * context)
{
	luasandbox_timer_callback_data * data;
	
	if (signo != LUASANDBOX_SIGNAL
			|| info->si_code != SI_TIMER
			|| !info->si_value.sival_ptr)
	{
		return;
	}

	data = (luasandbox_timer_callback_data*)info->si_value.sival_ptr;

	lua_State * L = data->sandbox->state;

	if (data->type == LUASANDBOX_TIMER_EMERGENCY) {
		sigset_t set;
		sigemptyset(&set);
		sigprocmask(SIG_SETMASK, &set, NULL);
		data->sandbox->timed_out = 1;
		data->sandbox->emergency_timed_out = 1;
		if (data->sandbox->in_php) {
			// The whole PHP request context is dirty now. We need to kill it,
			// like what happens if there is a max_execution_time timeout.
			zend_error(E_ERROR, "The maximum execution time for a Lua sandbox script "
					"was exceeded and a PHP callback failed to return");
		} else {
			// The Lua state is dirty now and can't be used again.
			lua_pushstring(L, "emergency timeout");
			luasandbox_wrap_fatal(L);
			lua_error(L);
		}
	} else {
		luasandbox_timer_set * lts = &data->sandbox->timer;
		if (luasandbox_timer_is_paused(lts)) {
			// The timer is paused. luasandbox_timer_unpause will reschedule when unpaused.
			clock_gettime(LUASANDBOX_CLOCK_ID, &lts->normal_expired_at);
		} else if (!luasandbox_timer_is_zero(&lts->pause_delta)) {
			// The timer is not paused, but we have a pause delta. Reschedule.
			luasandbox_timer_subtract(&lts->usage, &lts->pause_delta);
			lts->normal_remaining = lts->pause_delta;
			luasandbox_timer_zero(&lts->pause_delta);
			luasandbox_timer_set_one_time(&lts->normal_timer, &lts->normal_remaining);
		} else {
			// Set a hook which will terminate the script execution in a graceful way
			data->sandbox->timed_out = 1;
			lua_sethook(L, luasandbox_timer_timeout_hook,
				LUA_MASKCOUNT | LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE, 1);
		}
	}
}
static void luasandbox_timer_handle_limiter(luasandbox_timer * lt)
{
	lua_State * L = lt->sandbox->state;

	luasandbox_timer_set * lts = &lt->sandbox->timer;
	if (luasandbox_timer_is_paused(lts)) {
		// The timer is paused. luasandbox_timer_unpause will reschedule when unpaused.
		// Note that we need to use lt->clock_id here since CLOCK_THREAD_CPUTIME_ID
		// would get the time usage of the timer thread rather than the Lua thread.
		clock_gettime(lt->clock_id, &lts->limiter_expired_at);
	} else if (!luasandbox_timer_is_zero(&lts->pause_delta)) {
		// The timer is not paused, but we have a pause delta. Reschedule.
		luasandbox_timer_subtract(&lts->usage, &lts->pause_delta);
		lts->limiter_remaining = lts->pause_delta;
		luasandbox_timer_zero(&lts->pause_delta);
		luasandbox_timer_set_one_time(lts->limiter_timer, &lts->limiter_remaining);
	} else {
		// Set a hook which will terminate the script execution in a graceful way
		lt->sandbox->timed_out = 1;
		lua_sethook(L, luasandbox_timer_timeout_hook,
			LUA_MASKCOUNT | LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE, 1);
	}
}
void luasandbox_timer_unpause(luasandbox_timer_set * lts) {
	struct timespec delta;

	if (!luasandbox_timer_is_zero(&lts->pause_start)) {
		clock_gettime(LUASANDBOX_CLOCK_ID, &delta);
		luasandbox_timer_subtract(&delta, &lts->pause_start);

		if (luasandbox_timer_is_zero(&lts->normal_expired_at)) {
			// Easy case, timer didn't expire while paused. Throw the whole delta
			// into pause_delta for later timer and usage adjustment.
			luasandbox_timer_add(&lts->pause_delta, &delta);
			luasandbox_timer_zero(&lts->pause_start);
		} else {
			// If the normal limit expired, we need to fold the whole
			// accumulated delta into usage immediately, and then restart the
			// timer with the portion before the expiry.

			// adjust usage
			luasandbox_timer_subtract(&lts->usage, &delta);
			luasandbox_timer_subtract(&lts->usage, &lts->pause_delta);

			// calculate timer delta
			delta = lts->normal_expired_at;
			luasandbox_timer_subtract(&delta, &lts->pause_start);
			luasandbox_timer_add(&delta, &lts->pause_delta);

			// Zero out pause vars and expired timestamp (since we handled it)
			luasandbox_timer_zero(&lts->pause_start);
			luasandbox_timer_zero(&lts->pause_delta);
			luasandbox_timer_zero(&lts->normal_expired_at);

			// Restart timer
			lts->normal_remaining = delta;
			luasandbox_timer_set_one_time(&lts->normal_timer, &lts->normal_remaining);
		}
	}
}