summaryrefslogtreecommitdiffstats
path: root/ldap/servers/plugins/replication/repl5_backoff.c
diff options
context:
space:
mode:
Diffstat (limited to 'ldap/servers/plugins/replication/repl5_backoff.c')
-rw-r--r--ldap/servers/plugins/replication/repl5_backoff.c232
1 files changed, 232 insertions, 0 deletions
diff --git a/ldap/servers/plugins/replication/repl5_backoff.c b/ldap/servers/plugins/replication/repl5_backoff.c
new file mode 100644
index 00000000..d0a90878
--- /dev/null
+++ b/ldap/servers/plugins/replication/repl5_backoff.c
@@ -0,0 +1,232 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_backoff.c */
+/*
+
+ The backoff object implements a backoff timer. The timer can operate
+ with a fixed interval, an expontially increasing interval, or a
+ random interval.
+
+ The caller creates a new backoff timer, specifying the backoff behavior
+ desired (fixed, exponential, or random), the initial backoff value,
+ and the maximum backoff interval. This does not start the timer - the
+ backoff_reset() function must be used to actually start the timer.
+
+ The backoff_reset() function takes an optional function that
+ will be called when the backoff time has expired, and a void *
+ that can be used to pass arguments into the callback function.
+
+ When the time expires, the callback function will be called. If no
+ callback function has been provided, the timer simply expires.
+ A timer does not recompute the next interval and begin timing until
+ the backoff_step() function is called. Therefore, callers that
+ do not install a callback function may use the timer by polling.
+ When a callback function is provided, the timer is typically reset
+ inside the callback function.
+
+*/
+
+#include "repl5.h"
+
+
+typedef struct backoff_timer {
+ int type;
+ int running;
+ slapi_eq_fn_t callback;
+ void *callback_arg;
+ time_t initial_interval;
+ time_t next_interval;
+ time_t max_interval;
+ time_t last_fire_time;
+ Slapi_Eq_Context pending_event;
+ PRLock *lock;
+
+} backoff_timer;
+
+/* Forward declarations */
+static PRIntervalTime random_interval_in_range(time_t lower_bound, time_t upper_bound);
+
+
+/*
+ Create a new backoff timer. The timer is initialized, but is not
+ started.
+ */
+Backoff_Timer *
+backoff_new(int timer_type, int initial_interval, int max_interval)
+{
+ Backoff_Timer *bt;
+
+ bt = (Backoff_Timer *)slapi_ch_calloc(1, sizeof(struct backoff_timer));
+ bt->type = timer_type;
+ bt->initial_interval = initial_interval;
+ bt->next_interval = bt->initial_interval;
+ bt->max_interval = max_interval;
+ bt->running = 0;
+ if ((bt->lock = PR_NewLock()) == NULL)
+ {
+ slapi_ch_free((void **)&bt);
+ }
+ return bt;
+}
+
+
+/*
+ * Reset and start the timer. Returns the time (as a time_t) when the
+ * time will next expire.
+ */
+time_t
+backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data)
+{
+ time_t return_value = 0UL;
+
+ PR_ASSERT(NULL != bt);
+ PR_ASSERT(NULL != callback);
+
+ PR_Lock(bt->lock);
+ bt->running = 1;
+ bt->callback = callback;
+ bt->callback_arg = callback_data;
+ /* Cancel any pending events in the event queue */
+ if (NULL != bt->pending_event)
+ {
+ slapi_eq_cancel(bt->pending_event);
+ bt->pending_event = NULL;
+ }
+ /* Compute the first fire time */
+ if (BACKOFF_RANDOM == bt->type)
+ {
+ bt->next_interval = random_interval_in_range(bt->initial_interval,
+ bt->max_interval);
+ }
+ else
+ {
+ bt->next_interval = bt->initial_interval;
+ }
+ /* Schedule the callback */
+ time(&bt->last_fire_time);
+ return_value = bt->last_fire_time + bt->next_interval;
+ bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg,
+ return_value);
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+
+/*
+ Step the timer - compute the new backoff interval and start
+ counting. Note that the next expiration time is based on the
+ last timer expiration time, *not* the current time.
+
+ Returns the time (as a time_t) when the timer will next expire.
+ */
+time_t
+backoff_step(Backoff_Timer *bt)
+{
+ time_t return_value = 0UL;
+
+ PR_ASSERT(NULL != bt);
+
+ /* If the timer has never been reset, then return 0 */
+ PR_Lock(bt->lock);
+ if (bt->running)
+ {
+ time_t previous_interval = bt->next_interval;
+ switch (bt->type) {
+ case BACKOFF_FIXED:
+ /* Interval stays the same */
+ break;
+ case BACKOFF_EXPONENTIAL:
+ /* Interval doubles, up to a maximum */
+ if (bt->next_interval < bt->max_interval)
+ {
+ bt->next_interval *= 2;
+ if (bt->next_interval > bt->max_interval)
+ {
+ bt->next_interval = bt->max_interval;
+ }
+ }
+ break;
+ case BACKOFF_RANDOM:
+ /* Compute the new random interval time */
+ bt->next_interval = random_interval_in_range(bt->initial_interval,
+ bt->max_interval);
+ break;
+ }
+ /* Schedule the callback, if any */
+ bt->last_fire_time += previous_interval;
+ return_value = bt->last_fire_time + bt->next_interval;
+ bt->pending_event = slapi_eq_once(bt->callback, bt->callback_arg,
+ return_value);
+ }
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+
+/*
+ * Return 1 if the backoff timer has expired, 0 otherwise.
+ */
+int
+backoff_expired(Backoff_Timer *bt, int margin)
+{
+ int return_value = 0;
+
+ PR_ASSERT(NULL != bt);
+ PR_Lock(bt->lock);
+ return_value = (current_time() >= (bt->last_fire_time + bt->next_interval + margin));
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+
+/*
+ Destroy and deallocate a timer object
+ */
+void
+backoff_delete(Backoff_Timer **btp)
+{
+ Backoff_Timer *bt;
+
+ PR_ASSERT(NULL != btp && NULL != *btp);
+ bt = *btp;
+ PR_Lock(bt->lock);
+ /* Cancel any pending events in the event queue */
+ if (NULL != bt->pending_event)
+ {
+ slapi_eq_cancel(bt->pending_event);
+ }
+ PR_Unlock(bt->lock);
+ PR_DestroyLock(bt->lock);
+ slapi_ch_free((void **)btp);
+}
+
+
+/*
+ * Return the next fire time for the timer.
+ */
+time_t
+backoff_get_next_fire_time(Backoff_Timer *bt)
+{
+ time_t return_value;
+
+ PR_ASSERT(NULL != bt);
+ PR_Lock(bt->lock);
+ return_value = bt->last_fire_time + bt->next_interval;
+ PR_Unlock(bt->lock);
+ return return_value;
+}
+
+static PRIntervalTime
+random_interval_in_range(time_t lower_bound, time_t upper_bound)
+{
+ /*
+ * slapi_rand() provides some entropy from two or three system timer
+ * calls (depending on the platform) down in NSS. If more entropy is
+ * required, slapi_rand_r(unsigned int *seed) can be called instead.
+ */
+ return(lower_bound + (slapi_rand() % (upper_bound - lower_bound)));
+}
+