Add enable_timeout_every() to fire the same timeout repeatedly.
authorRobert Haas <[email protected]>
Thu, 30 Sep 2021 16:04:50 +0000 (12:04 -0400)
committerRobert Haas <[email protected]>
Mon, 25 Oct 2021 15:33:44 +0000 (11:33 -0400)
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

src/backend/utils/misc/timeout.c
src/include/utils/timeout.h

index 95a273d9cfbdbd672e3d3d6eba594df4fec164c5..af74e99ed1af69156f74a682b77ea09efc63001e 100644 (file)
@@ -36,6 +36,7 @@ typedef struct timeout_params
 
        TimestampTz start_time;         /* time that timeout was last activated */
        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;
 
 /*
@@ -153,7 +154,8 @@ remove_timeout_index(int index)
  * Enable the specified timeout reason
  */
 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;
 
@@ -188,6 +190,7 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
        all_timeouts[id].indicator = false;
        all_timeouts[id].start_time = now;
        all_timeouts[id].fin_time = fin_time;
+       all_timeouts[id].interval_in_ms = interval_in_ms;
 
        insert_timeout(id, i);
 }
@@ -399,6 +402,29 @@ handle_sig_alarm(SIGNAL_ARGS)
                                /* And call its handler function */
                                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
                                 * 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].start_time = 0;
                all_timeouts[i].fin_time = 0;
+               all_timeouts[i].interval_in_ms = 0;
        }
 
        all_timeouts_initialized = true;
@@ -532,7 +559,29 @@ enable_timeout_after(TimeoutId id, int delay_ms)
        /* Queue the timeout at the appropriate time. */
        now = GetCurrentTimestamp();
        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. */
        schedule_alarm(now);
@@ -555,7 +604,7 @@ enable_timeout_at(TimeoutId id, TimestampTz fin_time)
 
        /* Queue the timeout at the appropriate time. */
        now = GetCurrentTimestamp();
-       enable_timeout(id, now, fin_time);
+       enable_timeout(id, now, fin_time, 0);
 
        /* Set the timer interrupt. */
        schedule_alarm(now);
@@ -590,11 +639,17 @@ enable_timeouts(const EnableTimeoutParams *timeouts, int count)
                        case TMPARAM_AFTER:
                                fin_time = TimestampTzPlusMilliseconds(now,
                                                                                                           timeouts[i].delay_ms);
-                               enable_timeout(id, now, fin_time);
+                               enable_timeout(id, now, fin_time, 0);
                                break;
 
                        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;
 
                        default:
index 93e6a691b3f36c30e54326527c25651922567861..1b13ac96e0e4788c35ecbdf78e928b39749a9167 100644 (file)
@@ -48,14 +48,15 @@ typedef void (*timeout_handler_proc) (void);
 typedef enum TimeoutType
 {
        TMPARAM_AFTER,
-       TMPARAM_AT
+       TMPARAM_AT,
+       TMPARAM_EVERY
 } TimeoutType;
 
 typedef struct
 {
        TimeoutId       id;                             /* timeout to set */
        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 */
 } EnableTimeoutParams;
 
@@ -75,6 +76,8 @@ extern void reschedule_timeouts(void);
 
 /* timeout operation */
 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_timeouts(const EnableTimeoutParams *timeouts, int count);
 extern void disable_timeout(TimeoutId id, bool keep_indicator);