/*-------------------------------------------------------------------------
 *
 * timeout.c
 *      Routines to multiplex SIGALRM interrupts for multiple timeout reasons.
 *
 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/backend/utils/misc/timeout.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/time.h>

#include "miscadmin.h"
#include "storage/proc.h"
#include "utils/timestamp.h"
#include "libpq/pqsignal.h"
#include "utils/timeout.h"


/* Data about any one timeout reason */
typedef struct timeout_params {
    TimeoutId    index;            /* identifier of timeout reason */

    /* volatile because these may be changed from the signal handler */
    volatile bool active;        /* true if timeout is in active_timeouts[] */
    volatile bool indicator;    /* true if timeout has occurred */

    /* callback function for timeout, or NULL if timeout not registered */
    timeout_handler_proc timeout_handler;

    TimestampTz start_time;        /* time that timeout was last activated */
    TimestampTz fin_time;        /* time it is, or was last, due to fire */
} timeout_params;

/*
 * List of possible timeout reasons in the order of enum TimeoutId.
 */
static timeout_params all_timeouts[MAX_TIMEOUTS];
static bool all_timeouts_initialized = false;

/*
 * List of active timeouts ordered by their fin_time and priority.
 * This list is subject to change by the interrupt handler, so it's volatile.
 */
static volatile int num_active_timeouts = 0;
static timeout_params *volatile active_timeouts[MAX_TIMEOUTS];

/*
 * Flag controlling whether the signal handler is allowed to do anything.
 * This is useful to avoid race conditions with the handler.  Note in
 * particular that this lets us make changes in the data structures without
 * tediously disabling and re-enabling the timer signal.  Most of the time,
 * no interrupt would happen anyway during such critical sections, but if
 * one does, this rule ensures it's safe.  Leaving the signal enabled across
 * multiple operations can greatly reduce the number of kernel calls we make,
 * too.  See comments in schedule_alarm() about that.
 *
 * We leave this "false" when we're not expecting interrupts, just in case.
 */
static volatile sig_atomic_t alarm_enabled = false;

#define disable_alarm() (alarm_enabled = false)
#define enable_alarm()    (alarm_enabled = true)

/*
 * State recording if and when we next expect the interrupt to fire.
 * (signal_due_at is valid only when signal_pending is true.)
 * Note that the signal handler will unconditionally reset signal_pending to
 * false, so that can change asynchronously even when alarm_enabled is false.
 */
static volatile sig_atomic_t signal_pending = false;
static volatile TimestampTz signal_due_at = 0;


/*****************************************************************************
 * Internal helper functions
 *
 * For all of these, it is caller's responsibility to protect them from
 * interruption by the signal handler.  Generally, call disable_alarm()
 * first to prevent interruption, then update state, and last call
 * schedule_alarm(), which will re-enable the signal handler if needed.
 *****************************************************************************/

/*
 * Find the index of a given timeout reason in the active array.
 * If it's not there, return -1.
 */
static int find_active_timeout(TimeoutId id)
{
    int i;

    for (i = 0; i < num_active_timeouts; i++) {
        if (active_timeouts[i]->index == id) {
            return i;
        }
    }

    return -1;
}

/*
 * Insert specified timeout reason into the list of active timeouts
 * at the given index.
 */
static void insert_timeout(TimeoutId id, int index)
{
    int            i;

    if (index < 0 || index > num_active_timeouts)
        elog(FATAL, "timeout index %d out of range 0..%d", index,
             num_active_timeouts);

    Assert(!all_timeouts[id].active);
    all_timeouts[id].active = true;

    for (i = num_active_timeouts - 1; i >= index; i--)
        active_timeouts[i + 1] = active_timeouts[i];

    active_timeouts[index] = &all_timeouts[id];

    num_active_timeouts++;
}

/*
 * Remove the index'th element from the timeout list.
 */
static void remove_timeout_index(int index)
{
    int            i;

    if (index < 0 || index >= num_active_timeouts)
        elog(FATAL, "timeout index %d out of range 0..%d", index,
             num_active_timeouts - 1);

    Assert(active_timeouts[index]->active);
    active_timeouts[index]->active = false;

    for (i = index + 1; i < num_active_timeouts; i++)
        active_timeouts[i - 1] = active_timeouts[i];

    num_active_timeouts--;
}

/*
 * Enable the specified timeout reason
 */
static void enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
{
    int            i;

    /* Assert request is sane */
    Assert(all_timeouts_initialized);
    Assert(all_timeouts[id].timeout_handler != NULL);

    /*
     * If this timeout was already active, momentarily disable it.  We
     * interpret the call as a directive to reschedule the timeout.
     */
    if (all_timeouts[id].active)
        remove_timeout_index(find_active_timeout(id));

    /*
     * Find out the index where to insert the new timeout.  We sort by
     * fin_time, and for equal fin_time by priority.
     */
    for (i = 0; i < num_active_timeouts; i++) {
        timeout_params *old_timeout = active_timeouts[i];

        if (fin_time < old_timeout->fin_time) {
            break;
        }
        if (fin_time == old_timeout->fin_time && id < old_timeout->index) {
            break;
        }
    }

    /*
     * Mark the timeout active, and insert it into the active list.
     */
    all_timeouts[id].indicator = false;
    all_timeouts[id].start_time = now;
    all_timeouts[id].fin_time = fin_time;

    insert_timeout(id, i);
}

/*
 * Schedule alarm for the next active timeout, if any
 *
 * We assume the caller has obtained the current time, or a close-enough
 * approximation.  (It's okay if a tick or two has passed since "now", or
 * if a little more time elapses before we reach the kernel call; that will
 * cause us to ask for an interrupt a tick or two later than the nearest
 * timeout, which is no big deal.  Passing a "now" value that's in the future
 * would be bad though.)
 */
static void schedule_alarm(TimestampTz now)
{
    if (num_active_timeouts > 0) {
        struct itimerval timeval;
        TimestampTz nearest_timeout;
        long        secs;
        int            usecs;

        MemSet(&timeval, 0, sizeof(struct itimerval));

        /*
         * If we think there's a signal pending, but current time is more than
         * 10ms past when the signal was due, then assume that the timeout
         * request got lost somehow; clear signal_pending so that we'll reset
         * the interrupt request below.  (10ms corresponds to the worst-case
         * timeout granularity on modern systems.)  It won't hurt us if the
         * interrupt does manage to fire between now and when we reach the
         * setitimer() call.
         */
        if (signal_pending && now > signal_due_at + 10 * 1000)
            signal_pending = false;

        /*
         * Get the time remaining till the nearest pending timeout.  If it is
         * negative, assume that we somehow missed an interrupt, and clear
         * signal_pending.  This gives us another chance to recover if the
         * kernel drops a timeout request for some reason.
         */
        nearest_timeout = active_timeouts[0]->fin_time;
        if (now > nearest_timeout) {
            signal_pending = false;
            /* force an interrupt as soon as possible */
            secs = 0;
            usecs = 1;
        } else {
            TimestampDifference(now, nearest_timeout,
                                &secs, &usecs);

            /*
             * It's possible that the difference is less than a microsecond;
             * ensure we don't cancel, rather than set, the interrupt.
             */
            if (secs == 0 && usecs == 0) {
                usecs = 1;
            }
        }

        timeval.it_value.tv_sec = secs;
        timeval.it_value.tv_usec = usecs;

        /*
         * We must enable the signal handler before calling setitimer(); if we
         * did it in the other order, we'd have a race condition wherein the
         * interrupt could occur before we can set alarm_enabled, so that the
         * signal handler would fail to do anything.
         *
         * Because we didn't bother to disable the timer in disable_alarm(),
         * it's possible that a previously-set interrupt will fire between
         * enable_alarm() and setitimer().  This is safe, however.  There are
         * two possible outcomes:
         *
         * 1. The signal handler finds nothing to do (because the nearest
         * timeout event is still in the future).  It will re-set the timer
         * and return.  Then we'll overwrite the timer value with a new one.
         * This will mean that the timer fires a little later than we
         * intended, but only by the amount of time it takes for the signal
         * handler to do nothing useful, which shouldn't be much.
         *
         * 2. The signal handler executes and removes one or more timeout
         * events.  When it returns, either the queue is now empty or the
         * frontmost event is later than the one we looked at above.  So we'll
         * overwrite the timer value with one that is too soon (plus or minus
         * the signal handler's execution time), causing a useless interrupt
         * to occur.  But the handler will then re-set the timer and
         * everything will still work as expected.
         *
         * Since these cases are of very low probability (the window here
         * being quite narrow), it's not worth adding cycles to the mainline
         * code to prevent occasional wasted interrupts.
         */
        enable_alarm();

        /*
         * If there is already an interrupt pending that's at or before the
         * needed time, we need not do anything more.  The signal handler will
         * do the right thing in the first case, and re-schedule the interrupt
         * for later in the second case.  It might seem that the extra
         * interrupt is wasted work, but it's not terribly much work, and this
         * method has very significant advantages in the common use-case where
         * we repeatedly set a timeout that we don't expect to reach and then
         * cancel it.  Instead of invoking setitimer() every time the timeout
         * is set or canceled, we perform one interrupt and a re-scheduling
         * setitimer() call at intervals roughly equal to the timeout delay.
         * For example, with statement_timeout = 1s and a throughput of
         * thousands of queries per second, this method requires an interrupt
         * and setitimer() call roughly once a second, rather than thousands
         * of setitimer() calls per second.
         *
         * Because of the possible passage of time between when we obtained
         * "now" and when we reach setitimer(), the kernel's opinion of when
         * to trigger the interrupt is likely to be a bit later than
         * signal_due_at.  That's fine, for the same reasons described above.
         */
        if (signal_pending && nearest_timeout >= signal_due_at) {
            return;
        }

        /*
         * As with calling enable_alarm(), we must set signal_pending *before*
         * calling setitimer(); if we did it after, the signal handler could
         * trigger before we set it, leaving us with a false opinion that a
         * signal is still coming.
         *
         * Other race conditions involved with setting/checking signal_pending
         * are okay, for the reasons described above.  One additional point is
         * that the signal handler could fire after we set signal_due_at, but
         * still before the setitimer() call.  Then the handler could
         * overwrite signal_due_at with a value it computes, which will be the
         * same as or perhaps later than what we just computed.  After we
         * perform setitimer(), the net effect would be that signal_due_at
         * gives a time later than when the interrupt will really happen;
         * which is a safe situation.
         */
        signal_due_at = nearest_timeout;
        signal_pending = true;

        /* Set the alarm timer */
        if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) {
            /*
             * Clearing signal_pending here is a bit pro forma, but not
             * entirely so, since something in the FATAL exit path could try
             * to use timeout facilities.
             */
            signal_pending = false;
            elog(FATAL, "could not enable SIGALRM timer: %m");
        }
    }
}


/*****************************************************************************
 * Signal handler
 *****************************************************************************/

/*
 * Signal handler for SIGALRM
 *
 * Process any active timeout reasons and then reschedule the interrupt
 * as needed.
 */
void pg_handle_sig_alarm(SIGNAL_ARGS)
{
    int            save_errno = errno;

    /*
     * Bump the holdoff counter, to make sure nothing we call will process
     * interrupts directly. No timeout handler should do that, but these
     * failures are hard to debug, so better be sure.
     */
    HOLD_INTERRUPTS();

    /*
     * SIGALRM is always cause for waking anything waiting on the process
     * latch.
     */
    SetLatch(&t_thrd.proc->procLatch);

    /*
     * Always reset signal_pending, even if !alarm_enabled, since indeed no
     * signal is now pending.
     */
    signal_pending = false;

    /*
     * Fire any pending timeouts, but only if we're enabled to do so.
     */
    if (alarm_enabled) {
        /*
         * Disable alarms, just in case this platform allows signal handlers
         * to interrupt themselves.  schedule_alarm() will re-enable if
         * appropriate.
         */
        disable_alarm();

        if (num_active_timeouts > 0) {
            TimestampTz now = GetCurrentTimestamp();

            /* While the first pending timeout has been reached ... */
            while (num_active_timeouts > 0 &&
                   now >= active_timeouts[0]->fin_time) {
                timeout_params *this_timeout = active_timeouts[0];

                /* Remove it from the active list */
                remove_timeout_index(0);

                /* Mark it as fired */
                this_timeout->indicator = true;

                /* And call its handler function */
                this_timeout->timeout_handler();

                /*
                 * The handler might not take negligible time (CheckDeadLock
                 * for instance isn't too cheap), so let's update our idea of
                 * "now" after each one.
                 */
                now = GetCurrentTimestamp();
            }

            /* Done firing timeouts, so reschedule next interrupt if any */
            schedule_alarm(now);
        }
    }

    RESUME_INTERRUPTS();

    errno = save_errno;
}


/*****************************************************************************
 * Public API
 *****************************************************************************/

/*
 * Initialize timeout module.
 *
 * This must be called in every process that wants to use timeouts.
 *
 * If the process was forked from another one that was also using this
 * module, be sure to call this before re-enabling signals; else handlers
 * meant to run in the parent process might get invoked in this one.
 */
void InitializeTimeouts(void)
{
    int            i;

    /* Initialize, or re-initialize, all local state */
    disable_alarm();

    num_active_timeouts = 0;

    for (i = 0; i < MAX_TIMEOUTS; i++) {
        all_timeouts[i].index = (TimeoutId)i;
        all_timeouts[i].active = false;
        all_timeouts[i].indicator = false;
        all_timeouts[i].timeout_handler = NULL;
        all_timeouts[i].start_time = 0;
        all_timeouts[i].fin_time = 0;
    }

    all_timeouts_initialized = true;

    /* Now establish the signal handler */
    pqsignal(SIGALRM, pg_handle_sig_alarm);
}

/*
 * Register a timeout reason
 *
 * For predefined timeouts, this just registers the callback function.
 *
 * For user-defined timeouts, pass id == USER_TIMEOUT; we then allocate and
 * return a timeout ID.
 */
TimeoutId RegisterTimeout(TimeoutId id, timeout_handler_proc handler)
{
    Assert(all_timeouts_initialized);

    /* There's no need to disable the signal handler here. */
    int iid = (int)id;

    if ((TimeoutId)iid >= USER_TIMEOUT) {
        /* Allocate a user-defined timeout reason */
        for (iid = (int)USER_TIMEOUT; (TimeoutId)iid < MAX_TIMEOUTS; iid++)
            if (all_timeouts[iid].timeout_handler == NULL)
                break;
        if ((TimeoutId)iid >= MAX_TIMEOUTS)
            ereport(FATAL,
                    (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
                     errmsg("cannot add more timeout reasons")));
    }

    Assert(all_timeouts[iid].timeout_handler == NULL);

    all_timeouts[iid].timeout_handler = handler;

    return (TimeoutId)iid;
}

/*
 * Reschedule any pending SIGALRM interrupt.
 *
 * This can be used during error recovery in case query cancel resulted in loss
 * of a SIGALRM event (due to longjmp'ing out of pg_handle_sig_alarm before it
 * could do anything).  But note it's not necessary if any of the public
 * enable_ or disable_timeout functions are called in the same area, since
 * those all do schedule_alarm() internally if needed.
 */
void reschedule_timeouts(void)
{
    /* For flexibility, allow this to be called before we're initialized. */
    if (!all_timeouts_initialized) {
        return;
    }

    /* Disable timeout interrupts for safety. */
    disable_alarm();

    /* Reschedule the interrupt, if any timeouts remain active. */
    if (num_active_timeouts > 0) {
        schedule_alarm(GetCurrentTimestamp());
    }
}

/*
 * Enable the specified timeout to fire after the specified delay.
 *
 * Delay is given in milliseconds.
 */
void enable_timeout_after(TimeoutId id, int delay_ms)
{
    TimestampTz now;
    TimestampTz fin_time;

    /* Disable timeout interrupts for safety. */
    disable_alarm();

    /* Queue the timeout at the appropriate time. */
    now = GetCurrentTimestamp();
    fin_time = TimestampTzPlusMilliseconds(now, delay_ms);
    enable_timeout(id, now, fin_time);

    /* Set the timer interrupt. */
    schedule_alarm(now);
}

/*
 * Enable the specified timeout to fire at the specified time.
 *
 * This is provided to support cases where there's a reason to calculate
 * the timeout by reference to some point other than "now".  If there isn't,
 * use enable_timeout_after(), to avoid calling GetCurrentTimestamp() twice.
 */
void enable_timeout_at(TimeoutId id, TimestampTz fin_time)
{
    TimestampTz now;

    /* Disable timeout interrupts for safety. */
    disable_alarm();

    /* Queue the timeout at the appropriate time. */
    now = GetCurrentTimestamp();
    enable_timeout(id, now, fin_time);

    /* Set the timer interrupt. */
    schedule_alarm(now);
}

/*
 * Enable multiple timeouts at once.
 *
 * This works like calling enable_timeout_after() and/or enable_timeout_at()
 * multiple times.  Use this to reduce the number of GetCurrentTimestamp()
 * and setitimer() calls needed to establish multiple timeouts.
 */
void enable_timeouts(const EnableTimeoutParams *timeouts, int count)
{
    TimestampTz now;
    int            i;

    /* Disable timeout interrupts for safety. */
    disable_alarm();

    /* Queue the timeout(s) at the appropriate times. */
    now = GetCurrentTimestamp();

    for (i = 0; i < count; i++) {
        TimeoutId    id = timeouts[i].id;
        TimestampTz fin_time;

        switch (timeouts[i].type) {
            case TMPARAM_AFTER:
                fin_time = TimestampTzPlusMilliseconds(now,
                                                       timeouts[i].delay_ms);
                enable_timeout(id, now, fin_time);
                break;

            case TMPARAM_AT:
                enable_timeout(id, now, timeouts[i].fin_time);
                break;

            default:
                elog(ERROR, "unrecognized timeout type %d",
                     (int) timeouts[i].type);
                break;
        }
    }

    /* Set the timer interrupt. */
    schedule_alarm(now);
}

/*
 * Cancel the specified timeout.
 *
 * The timeout's I've-been-fired indicator is reset,
 * unless keep_indicator is true.
 *
 * When a timeout is canceled, any other active timeout remains in force.
 * It's not an error to disable a timeout that is not enabled.
 */
void disable_timeout(TimeoutId id, bool keep_indicator)
{
    /* Assert request is sane */
    Assert(all_timeouts_initialized);
    Assert(all_timeouts[id].timeout_handler != NULL);

    /* Disable timeout interrupts for safety. */
    disable_alarm();

    /* Find the timeout and remove it from the active list. */
    if (all_timeouts[id].active) {
        remove_timeout_index(find_active_timeout(id));
    }

    /* Mark it inactive, whether it was active or not. */
    if (!keep_indicator) {
        all_timeouts[id].indicator = false;
    }

    /* Reschedule the interrupt, if any timeouts remain active. */
    if (num_active_timeouts > 0) {
        schedule_alarm(GetCurrentTimestamp());
    }
}

/*
 * Cancel multiple timeouts at once.
 *
 * The timeouts' I've-been-fired indicators are reset,
 * unless timeouts[i].keep_indicator is true.
 *
 * This works like calling disable_timeout() multiple times.
 * Use this to reduce the number of GetCurrentTimestamp()
 * and setitimer() calls needed to cancel multiple timeouts.
 */
void disable_timeouts(const DisableTimeoutParams *timeouts, int count)
{
    int            i;

    Assert(all_timeouts_initialized);

    /* Disable timeout interrupts for safety. */
    disable_alarm();

    /* Cancel the timeout(s). */
    for (i = 0; i < count; i++) {
        TimeoutId    id = timeouts[i].id;

        Assert(all_timeouts[id].timeout_handler != NULL);

        if (all_timeouts[id].active) {
            remove_timeout_index(find_active_timeout(id));
        }

        if (!timeouts[i].keep_indicator) {
            all_timeouts[id].indicator = false;
        }
    }

    /* Reschedule the interrupt, if any timeouts remain active. */
    if (num_active_timeouts > 0) {
        schedule_alarm(GetCurrentTimestamp());
    }
}

/*
 * Disable the signal handler, remove all timeouts from the active list,
 * and optionally reset their timeout indicators.
 */
void disable_all_timeouts(bool keep_indicators)
{
    int            i;

    disable_alarm();

    /*
     * We used to disable the timer interrupt here, but in common usage
     * patterns it's cheaper to leave it enabled; that may save us from having
     * to enable it again shortly.  See comments in schedule_alarm().
     */

    num_active_timeouts = 0;

    for (i = 0; i < MAX_TIMEOUTS; i++) {
        all_timeouts[i].active = false;
        if (!keep_indicators) {
            all_timeouts[i].indicator = false;
        }
    }
}

/*
 * Return true if the timeout is active (enabled and not yet fired)
 *
 * This is, of course, subject to race conditions, as the timeout could fire
 * immediately after we look.
 */
bool get_timeout_active(TimeoutId id)
{
    return all_timeouts[id].active;
}

/*
 * Return the timeout's I've-been-fired indicator
 *
 * If reset_indicator is true, reset the indicator when returning true.
 * To avoid missing timeouts due to race conditions, we are careful not to
 * reset the indicator when returning false.
 */
bool get_timeout_indicator(TimeoutId id, bool reset_indicator)
{
    if (all_timeouts[id].indicator) {
        if (reset_indicator) {
            all_timeouts[id].indicator = false;
        }
        return true;
    }
    return false;
}

/*
 * Return the time when the timeout was most recently activated
 *
 * Note: will return 0 if timeout has never been activated in this process.
 * However, we do *not* reset the start_time when a timeout occurs, so as
 * not to create a race condition if SIGALRM fires just as some code is
 * about to fetch the value.
 */
TimestampTz get_timeout_start_time(TimeoutId id)
{
    return all_timeouts[id].start_time;
}

/*
 * Return the time when the timeout is, or most recently was, due to fire
 *
 * Note: will return 0 if timeout has never been activated in this process.
 * However, we do *not* reset the fin_time when a timeout occurs, so as
 * not to create a race condition if SIGALRM fires just as some code is
 * about to fetch the value.
 */
TimestampTz get_timeout_finish_time(TimeoutId id)
{
    return all_timeouts[id].fin_time;
}