Add enable_timeout_every() to fire the same timeout repeatedly.

enable_timeout_at() and enable_timeout_after() can still be used
when you want to fire a timeout just once.

Patch by me, per a suggestion from Tom Lane.

Discussion: http://postgr.es/m/2992585.1632938816@sss.pgh.pa.us
Discussion: http://postgr.es/m/CA+TgmoYqSF5sCNrgTom9r3Nh=at4WmYFD=gsV-omStZ60S0ZUQ@mail.gmail.com
This commit is contained in:
Robert Haas 2021-09-30 12:04:50 -04:00
parent 902a2c2800
commit 732e6677a6
2 changed files with 65 additions and 7 deletions

View File

@ -36,6 +36,7 @@ typedef struct timeout_params
TimestampTz start_time; /* time that timeout was last activated */ TimestampTz start_time; /* time that timeout was last activated */
TimestampTz fin_time; /* time it is, or was last, due to fire */ TimestampTz fin_time; /* time it is, or was last, due to fire */
int interval_in_ms; /* time between firings, or 0 if just once */
} timeout_params; } timeout_params;
/* /*
@ -153,7 +154,8 @@ remove_timeout_index(int index)
* Enable the specified timeout reason * Enable the specified timeout reason
*/ */
static void static void
enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time) enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time,
int interval_in_ms)
{ {
int i; int i;
@ -188,6 +190,7 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
all_timeouts[id].indicator = false; all_timeouts[id].indicator = false;
all_timeouts[id].start_time = now; all_timeouts[id].start_time = now;
all_timeouts[id].fin_time = fin_time; all_timeouts[id].fin_time = fin_time;
all_timeouts[id].interval_in_ms = interval_in_ms;
insert_timeout(id, i); insert_timeout(id, i);
} }
@ -399,6 +402,29 @@ handle_sig_alarm(SIGNAL_ARGS)
/* And call its handler function */ /* And call its handler function */
this_timeout->timeout_handler(); this_timeout->timeout_handler();
/* If it should fire repeatedly, re-enable it. */
if (this_timeout->interval_in_ms > 0)
{
TimestampTz new_fin_time;
/*
* To guard against drift, schedule the next instance of
* the timeout based on the intended firing time rather
* than the actual firing time. But if the timeout was so
* late that we missed an entire cycle, fall back to
* scheduling based on the actual firing time.
*/
new_fin_time =
TimestampTzPlusMilliseconds(this_timeout->fin_time,
this_timeout->interval_in_ms);
if (new_fin_time < now)
new_fin_time =
TimestampTzPlusMilliseconds(now,
this_timeout->interval_in_ms);
enable_timeout(this_timeout->index, now, new_fin_time,
this_timeout->interval_in_ms);
}
/* /*
* The handler might not take negligible time (CheckDeadLock * The handler might not take negligible time (CheckDeadLock
* for instance isn't too cheap), so let's update our idea of * for instance isn't too cheap), so let's update our idea of
@ -449,6 +475,7 @@ InitializeTimeouts(void)
all_timeouts[i].timeout_handler = NULL; all_timeouts[i].timeout_handler = NULL;
all_timeouts[i].start_time = 0; all_timeouts[i].start_time = 0;
all_timeouts[i].fin_time = 0; all_timeouts[i].fin_time = 0;
all_timeouts[i].interval_in_ms = 0;
} }
all_timeouts_initialized = true; all_timeouts_initialized = true;
@ -532,7 +559,29 @@ enable_timeout_after(TimeoutId id, int delay_ms)
/* Queue the timeout at the appropriate time. */ /* Queue the timeout at the appropriate time. */
now = GetCurrentTimestamp(); now = GetCurrentTimestamp();
fin_time = TimestampTzPlusMilliseconds(now, delay_ms); fin_time = TimestampTzPlusMilliseconds(now, delay_ms);
enable_timeout(id, now, fin_time); enable_timeout(id, now, fin_time, 0);
/* Set the timer interrupt. */
schedule_alarm(now);
}
/*
* Enable the specified timeout to fire periodically, with the specified
* delay as the time between firings.
*
* Delay is given in milliseconds.
*/
void
enable_timeout_every(TimeoutId id, TimestampTz fin_time, int delay_ms)
{
TimestampTz now;
/* Disable timeout interrupts for safety. */
disable_alarm();
/* Queue the timeout at the appropriate time. */
now = GetCurrentTimestamp();
enable_timeout(id, now, fin_time, delay_ms);
/* Set the timer interrupt. */ /* Set the timer interrupt. */
schedule_alarm(now); schedule_alarm(now);
@ -555,7 +604,7 @@ enable_timeout_at(TimeoutId id, TimestampTz fin_time)
/* Queue the timeout at the appropriate time. */ /* Queue the timeout at the appropriate time. */
now = GetCurrentTimestamp(); now = GetCurrentTimestamp();
enable_timeout(id, now, fin_time); enable_timeout(id, now, fin_time, 0);
/* Set the timer interrupt. */ /* Set the timer interrupt. */
schedule_alarm(now); schedule_alarm(now);
@ -590,11 +639,17 @@ enable_timeouts(const EnableTimeoutParams *timeouts, int count)
case TMPARAM_AFTER: case TMPARAM_AFTER:
fin_time = TimestampTzPlusMilliseconds(now, fin_time = TimestampTzPlusMilliseconds(now,
timeouts[i].delay_ms); timeouts[i].delay_ms);
enable_timeout(id, now, fin_time); enable_timeout(id, now, fin_time, 0);
break; break;
case TMPARAM_AT: case TMPARAM_AT:
enable_timeout(id, now, timeouts[i].fin_time); enable_timeout(id, now, timeouts[i].fin_time, 0);
break;
case TMPARAM_EVERY:
fin_time = TimestampTzPlusMilliseconds(now,
timeouts[i].delay_ms);
enable_timeout(id, now, fin_time, timeouts[i].delay_ms);
break; break;
default: default:

View File

@ -48,14 +48,15 @@ typedef void (*timeout_handler_proc) (void);
typedef enum TimeoutType typedef enum TimeoutType
{ {
TMPARAM_AFTER, TMPARAM_AFTER,
TMPARAM_AT TMPARAM_AT,
TMPARAM_EVERY
} TimeoutType; } TimeoutType;
typedef struct typedef struct
{ {
TimeoutId id; /* timeout to set */ TimeoutId id; /* timeout to set */
TimeoutType type; /* TMPARAM_AFTER or TMPARAM_AT */ TimeoutType type; /* TMPARAM_AFTER or TMPARAM_AT */
int delay_ms; /* only used for TMPARAM_AFTER */ int delay_ms; /* only used for TMPARAM_AFTER/EVERY */
TimestampTz fin_time; /* only used for TMPARAM_AT */ TimestampTz fin_time; /* only used for TMPARAM_AT */
} EnableTimeoutParams; } EnableTimeoutParams;
@ -75,6 +76,8 @@ extern void reschedule_timeouts(void);
/* timeout operation */ /* timeout operation */
extern void enable_timeout_after(TimeoutId id, int delay_ms); extern void enable_timeout_after(TimeoutId id, int delay_ms);
extern void enable_timeout_every(TimeoutId id, TimestampTz fin_time,
int delay_ms);
extern void enable_timeout_at(TimeoutId id, TimestampTz fin_time); extern void enable_timeout_at(TimeoutId id, TimestampTz fin_time);
extern void enable_timeouts(const EnableTimeoutParams *timeouts, int count); extern void enable_timeouts(const EnableTimeoutParams *timeouts, int count);
extern void disable_timeout(TimeoutId id, bool keep_indicator); extern void disable_timeout(TimeoutId id, bool keep_indicator);