From e9f242a003898a825b592ceaf44fe99d255d0aa8 Mon Sep 17 00:00:00 2001 From: Pavel Březina Date: Fri, 26 Apr 2013 14:02:11 +0200 Subject: back end: periodic task API https://fedorahosted.org/sssd/ticket/1891 --- Makefile.am | 2 + src/providers/dp_ptask.c | 354 +++++++++++++++++++++++++++++++++++++++++++++++ src/providers/dp_ptask.h | 93 +++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 src/providers/dp_ptask.c create mode 100644 src/providers/dp_ptask.h diff --git a/Makefile.am b/Makefile.am index f4934c054..1d76ec105 100644 --- a/Makefile.am +++ b/Makefile.am @@ -406,6 +406,7 @@ dist_noinst_HEADERS = \ src/confdb/confdb_setup.h \ src/providers/data_provider.h \ src/providers/dp_backend.h \ + src/providers/dp_ptask.h \ src/providers/fail_over.h \ src/providers/providers.h \ src/util/child_common.h \ @@ -647,6 +648,7 @@ sssd_be_SOURCES = \ src/providers/data_provider_fo.c \ src/providers/data_provider_opts.c \ src/providers/data_provider_callbacks.c \ + src/providers/dp_ptask.c \ $(SSSD_FAILOVER_OBJ) sssd_be_LDADD = \ -ldl \ diff --git a/src/providers/dp_ptask.c b/src/providers/dp_ptask.c new file mode 100644 index 000000000..2a1caeca5 --- /dev/null +++ b/src/providers/dp_ptask.c @@ -0,0 +1,354 @@ +/* + Authors: + Pavel B??ezina + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "util/util.h" +#include "providers/dp_backend.h" +#include "providers/dp_ptask.h" + +enum be_ptask_schedule { + BE_PTASK_SCHEDULE_FROM_NOW, + BE_PTASK_SCHEDULE_FROM_LAST +}; + +struct be_ptask { + struct tevent_context *ev; + struct be_ctx *be_ctx; + time_t period; + time_t enabled_delay; + time_t timeout; + enum be_ptask_offline offline; + be_ptask_send_t send_fn; + be_ptask_recv_t recv_fn; + void *pvt; + const char *name; + + time_t last_execution; /* last time when send was called */ + struct tevent_req *req; /* active tevent request */ + struct tevent_timer *timer; /* active tevent timer */ + bool enabled; +}; + +static void be_ptask_schedule(struct be_ptask *task, + time_t delay, + enum be_ptask_schedule from); + +static int be_ptask_destructor(void *pvt) +{ + struct be_ptask *task; + + task = talloc_get_type(pvt, struct be_ptask); + if (task == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, ("BUG: task is NULL\n")); + return 0; + } + + DEBUG(SSSDBG_TRACE_FUNC, ("Terminating periodic task [%s]\n", task->name)); + + return 0; +} + +static void be_ptask_online_cb(void *pvt) +{ + struct be_ptask *task = NULL; + + task = talloc_get_type(pvt, struct be_ptask); + if (task == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, ("BUG: task is NULL\n")); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, ("Back end is online\n")); + be_ptask_enable(task); +} + +static void be_ptask_offline_cb(void *pvt) +{ + struct be_ptask *task = NULL; + task = talloc_get_type(pvt, struct be_ptask); + + DEBUG(SSSDBG_TRACE_FUNC, ("Back end is offline\n")); + be_ptask_disable(task); +} + +static void be_ptask_timeout(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, + void *pvt) +{ + struct be_ptask *task = NULL; + task = talloc_get_type(pvt, struct be_ptask); + + DEBUG(SSSDBG_OP_FAILURE, ("Task [%s]: timed out\n", task->name)); + + talloc_zfree(task->req); + be_ptask_schedule(task, task->period, BE_PTASK_SCHEDULE_FROM_NOW); +} + +static void be_ptask_done(struct tevent_req *req); + +static void be_ptask_execute(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, + void *pvt) +{ + struct be_ptask *task = NULL; + struct tevent_timer *timeout = NULL; + + task = talloc_get_type(pvt, struct be_ptask); + task->timer = NULL; /* timer is freed by tevent */ + + if (be_is_offline(task->be_ctx)) { + DEBUG(SSSDBG_TRACE_FUNC, ("Back end is offline\n")); + switch (task->offline) { + case BE_PTASK_OFFLINE_SKIP: + be_ptask_schedule(task, task->period, BE_PTASK_SCHEDULE_FROM_NOW); + return; + case BE_PTASK_OFFLINE_DISABLE: + /* This case is handled by offline callback. */ + return; + case BE_PTASK_OFFLINE_EXECUTE: + /* continue */ + break; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, ("Task [%s]: executing task, timeout %lu " + "seconds\n", task->name, task->timeout)); + + task->last_execution = time(NULL); + + task->req = task->send_fn(task, task->ev, task->be_ctx, task, task->pvt); + if (task->req == NULL) { + /* skip this iteration and try again later */ + DEBUG(SSSDBG_OP_FAILURE, ("Task [%s]: failed to execute task, " + "will try again later\n", task->name)); + + be_ptask_schedule(task, task->period, BE_PTASK_SCHEDULE_FROM_NOW); + return; + } + + tevent_req_set_callback(task->req, be_ptask_done, task); + + /* schedule timeout */ + if (task->timeout > 0) { + tv = tevent_timeval_current_ofs(task->timeout, 0); + timeout = tevent_add_timer(task->ev, task->req, tv, + be_ptask_timeout, task); + if (timeout == NULL) { + /* If we can't guarantee a timeout, + * we need to cancel the request. */ + talloc_zfree(task->req); + + DEBUG(SSSDBG_OP_FAILURE, ("Task [%s]: failed to set timeout, " + "the task will be rescheduled\n", task->name)); + + be_ptask_schedule(task, task->period, BE_PTASK_SCHEDULE_FROM_NOW); + } + } + + return; +} + +static void be_ptask_done(struct tevent_req *req) +{ + struct be_ptask *task = NULL; + errno_t ret; + + task = tevent_req_callback_data(req, struct be_ptask); + + ret = task->recv_fn(req); + talloc_zfree(req); + task->req = NULL; + switch (ret) { + case EOK: + DEBUG(SSSDBG_TRACE_FUNC, ("Task [%s]: finished successfully\n", + task->name)); + + be_ptask_schedule(task, task->period, BE_PTASK_SCHEDULE_FROM_LAST); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, ("Task [%s]: failed with [%d]: %s\n", + task->name, ret, strerror(ret))); + + be_ptask_schedule(task, task->period, BE_PTASK_SCHEDULE_FROM_NOW); + break; + } +} + +static void be_ptask_schedule(struct be_ptask *task, + time_t delay, + enum be_ptask_schedule from) +{ + struct timeval tv; + + if (!task->enabled) { + DEBUG(SSSDBG_TRACE_FUNC, ("Task [%s]: disabled\n", task->name)); + return; + } + + switch (from) { + case BE_PTASK_SCHEDULE_FROM_NOW: + tv = tevent_timeval_current_ofs(delay, 0); + + DEBUG(SSSDBG_TRACE_FUNC, ("Task [%s]: scheduling task %lu seconds " + "from now [%lu]\n", task->name, delay, tv.tv_sec)); + break; + case BE_PTASK_SCHEDULE_FROM_LAST: + tv = tevent_timeval_set(task->last_execution + delay, 0); + + DEBUG(SSSDBG_TRACE_FUNC, ("Task [%s]: scheduling task %lu seconds " + "from last execution time [%lu]\n", + task->name, delay, tv.tv_sec)); + break; + } + + if (task->timer != NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Task [%s]: another timer is already " + "active?\n", task->name)); + talloc_zfree(task->timer); + } + + task->timer = tevent_add_timer(task->ev, task, tv, be_ptask_execute, task); + if (task->timer == NULL) { + /* nothing we can do about it */ + DEBUG(SSSDBG_CRIT_FAILURE, ("FATAL: Unable to schedule task [%s]\n", + task->name)); + be_ptask_disable(task); + } +} + +errno_t be_ptask_create(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + time_t period, + time_t first_delay, + time_t enabled_delay, + time_t timeout, + enum be_ptask_offline offline, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt, + const char *name, + struct be_ptask **_task) +{ + struct be_ptask *task = NULL; + errno_t ret; + + if (be_ctx == NULL || period == 0 || send_fn == NULL || recv_fn == NULL + || name == NULL) { + return EINVAL; + } + + task = talloc_zero(mem_ctx, struct be_ptask); + if (task == NULL) { + ret = ENOMEM; + goto done; + } + + task->ev = be_ctx->ev; + task->be_ctx = be_ctx; + task->period = period; + task->enabled_delay = enabled_delay; + task->timeout = timeout; + task->offline = offline; + task->send_fn = send_fn; + task->recv_fn = recv_fn; + task->pvt = pvt; + task->name = talloc_strdup(task, name); + if (task->name == NULL) { + ret = ENOMEM; + goto done; + } + + task->enabled = true; + + talloc_set_destructor((TALLOC_CTX*)task, be_ptask_destructor); + + if (offline == BE_PTASK_OFFLINE_DISABLE) { + /* install offline and online callbacks */ + ret = be_add_online_cb(task, be_ctx, be_ptask_online_cb, task, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("Unable to install online callback " + "[%d]: %s", ret, strerror(ret))); + goto done; + } + + ret = be_add_offline_cb(task, be_ctx, be_ptask_offline_cb, task, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("Unable to install offline callback " + "[%d]: %s", ret, strerror(ret))); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, ("Periodic task [%s] was created\n", task->name)); + + be_ptask_schedule(task, first_delay, BE_PTASK_SCHEDULE_FROM_NOW); + + if (_task != NULL) { + *_task = task; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(task); + } + + return ret; +} + +void be_ptask_enable(struct be_ptask *task) +{ + if (task->enabled) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Task [%s]: already enabled\n", + task->name)); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, ("Task [%s]: enabling task\n", task->name)); + + task->enabled = true; + be_ptask_schedule(task, task->enabled_delay, BE_PTASK_SCHEDULE_FROM_NOW); +} + +/* Disable the task, but if a request already in progress, let it finish. */ +void be_ptask_disable(struct be_ptask *task) +{ + DEBUG(SSSDBG_TRACE_FUNC, ("Task [%s]: disabling task\n", task->name)); + + talloc_zfree(task->timer); + task->enabled = false; +} + +void be_ptask_destroy(struct be_ptask **task) +{ + talloc_zfree(*task); +} + +time_t be_ptask_get_period(struct be_ptask *task) +{ + return task->period; +} diff --git a/src/providers/dp_ptask.h b/src/providers/dp_ptask.h new file mode 100644 index 000000000..3e0fd2dde --- /dev/null +++ b/src/providers/dp_ptask.h @@ -0,0 +1,93 @@ +/* + Authors: + Pavel B??ezina + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _DP_PTASK_H_ +#define _DP_PTASK_H_ + +#include +#include +#include + +#include "providers/dp_backend.h" + +struct be_ptask; + +/** + * Defines how should task behave when back end is offline. + */ +enum be_ptask_offline { + /* current request will be skipped and rescheduled to 'now + period' */ + BE_PTASK_OFFLINE_SKIP, + + /* An offline and online callback is registered. The task is disabled + * immediately when back end goes offline and then enabled again + * when back end goes back online */ + BE_PTASK_OFFLINE_DISABLE, + + /* current request will be executed as planned */ + BE_PTASK_OFFLINE_EXECUTE +}; + +typedef struct tevent_req * +(*be_ptask_send_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +/** + * If EOK, task will be scheduled again to 'last_execution_time + period'. + * If other error code, task will be rescheduled to 'now + period'. + */ +typedef errno_t +(*be_ptask_recv_t)(struct tevent_req *req); + +/** + * The first execution is scheduled first_delay seconds after the task is + * created. + * + * If request does not complete in timeout seconds, it will be + * cancelled and rescheduled to 'now + period'. + * + * If the task is reenabled, it will be scheduled again to + * 'now + enabled_delay'. + * + * If an internal error occurred, the task is automatically disabled. + */ +errno_t be_ptask_create(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + time_t period, + time_t first_delay, + time_t enabled_delay, + time_t timeout, + enum be_ptask_offline offline, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt, + const char *name, + struct be_ptask **_task); + +void be_ptask_enable(struct be_ptask *task); +void be_ptask_disable(struct be_ptask *task); +void be_ptask_destroy(struct be_ptask **task); + +time_t be_ptask_get_period(struct be_ptask *task); + +#endif /* _DP_PTASK_H_ */ -- cgit