/* * This file is distributed as part of the MariaDB Corporation MaxScale. It 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, * version 2. * * 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, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright MariaDB Corporation Ab 2014 */ #include #include #include #include #include /** * @file housekeeper.c Provide a mechanism to run periodic tasks * * The housekeeper provides a mechanism to allow for tasks, function * calls basically, to be run on a tiem basis. A task may be run * repeatedly, with a given frequency (in seconds), or may be a one * shot task that will only be run once after a specified number of * seconds. * * The housekeeper also maintains a global variable, hkheartbeat, that * is incremented every 100ms. * * @verbatim * Revision History * * Date Who Description * 29/08/14 Mark Riddoch Initial implementation * 22/10/14 Mark Riddoch Addition of one-shot tasks * * @endverbatim */ /** * List of all tasks that need to be run */ static HKTASK *tasks = NULL; /** * Spinlock to protect the tasks list */ static SPINLOCK tasklock = SPINLOCK_INIT; static int do_shutdown = 0; unsigned long hkheartbeat = 0; static void hkthread(void *); /** * Initialise the housekeeper thread */ void hkinit() { thread_start(hkthread, NULL); } /** * Add a new task to the housekeepers lists of tasks that should be * run periodically. * * The task will be first run frequency seconds after this call is * made and will the be executed repeatedly every frequency seconds * until the task is removed. * * Task names must be unique. * * @param name The unique name for this housekeeper task * @param taskfn The function to call for the task * @param data Data to pass to the task function * @param frequency How often to run the task, expressed in seconds * @return Return the time in seconds when the task will be first run if the task was added, otherwise 0 */ int hktask_add(char *name, void (*taskfn)(void *), void *data, int frequency) { HKTASK *task, *ptr; if ((task = (HKTASK *)malloc(sizeof(HKTASK))) == NULL) { return 0; } if ((task->name = strdup(name)) == NULL) { free(task); return 0; } task->task = taskfn; task->data = data; task->frequency = frequency; task->type = HK_REPEATED; task->nextdue = time(0) + frequency; task->next = NULL; spinlock_acquire(&tasklock); ptr = tasks; while (ptr && ptr->next) { if (strcmp(ptr->name, name) == 0) { spinlock_release(&tasklock); free(task->name); free(task); return 0; } ptr = ptr->next; } if (ptr) { if (strcmp(ptr->name, name) == 0) { spinlock_release(&tasklock); free(task->name); free(task); return 0; } ptr->next = task; } else { tasks = task; } spinlock_release(&tasklock); return task->nextdue; } /** * Add a one-shot task to the housekeeper task list * * Task names must be unique. * * @param name The unique name for this housekeeper task * @param taskfn The function to call for the task * @param data Data to pass to the task function * @param when How many second until the task is executed * @return Return the time in seconds when the task will be first run if the task was added, otherwise 0 * */ int hktask_oneshot(char *name, void (*taskfn)(void *), void *data, int when) { HKTASK *task, *ptr; if ((task = (HKTASK *)malloc(sizeof(HKTASK))) == NULL) { return 0; } if ((task->name = strdup(name)) == NULL) { free(task); return 0; } task->task = taskfn; task->data = data; task->frequency = 0; task->type = HK_ONESHOT; task->nextdue = time(0) + when; task->next = NULL; spinlock_acquire(&tasklock); ptr = tasks; while (ptr && ptr->next) { if (strcmp(ptr->name, name) == 0) { spinlock_release(&tasklock); free(task->name); free(task); return 0; } ptr = ptr->next; } if (ptr) ptr->next = task; else tasks = task; spinlock_release(&tasklock); return task->nextdue; } /** * Remove a named task from the housekeepers task list * * @param name The task name to remove * @return Returns 0 if the task could not be removed */ int hktask_remove(char *name) { HKTASK *ptr, *lptr = NULL; spinlock_acquire(&tasklock); ptr = tasks; while (ptr && strcmp(ptr->name, name) != 0) { lptr = ptr; ptr = ptr->next; } if (ptr && lptr) lptr->next = ptr->next; else if (ptr) tasks = ptr->next; spinlock_release(&tasklock); if (ptr) { free(ptr->name); free(ptr); return 1; } else { return 0; } } /** * The housekeeper thread implementation. * * This function is responsible for executing the housekeeper tasks. * * The implementation of the callng of the task functions is such that * the tasks are called without the tasklock spinlock being held. This * allows manipulation of the housekeeper task list during execution of * one of the tasks. The resutl is that upon completion of a task the * search for tasks to run must restart from the start of the queue. * It is vital that the task->nextdue tiem is updated before the task * is run. * * @param data Unused, here to satisfy the thread system */ void hkthread(void *data) { HKTASK *ptr; time_t now; void (*taskfn)(void *); void *taskdata; int i; for (;;) { for (i = 0; i < 10; i++) { if (do_shutdown) return; thread_millisleep(100); hkheartbeat++; } now = time(0); spinlock_acquire(&tasklock); ptr = tasks; while (ptr) { if (ptr->nextdue <= now) { ptr->nextdue = now + ptr->frequency; taskfn = ptr->task; taskdata = ptr->data; spinlock_release(&tasklock); (*taskfn)(taskdata); if (ptr->type == HK_ONESHOT) hktask_remove(ptr->name); spinlock_acquire(&tasklock); ptr = tasks; } else ptr = ptr->next; } spinlock_release(&tasklock); } } /** * Called to shutdown the housekeeper * */ void hkshutdown() { do_shutdown = 1; } /** * Show the tasks that are scheduled for the house keeper * * @param pdcb The DCB to send to output */ void hkshow_tasks(DCB *pdcb) { HKTASK *ptr; struct tm tm; char buf[40]; dcb_printf(pdcb, "%-25s | Type | Frequency | Next Due\n", "Name"); dcb_printf(pdcb, "--------------------------+----------+-----------+-------------------------\n"); spinlock_acquire(&tasklock); ptr = tasks; while (ptr) { localtime_r(&ptr->nextdue, &tm); asctime_r(&tm, buf); dcb_printf(pdcb, "%-25s | %-8s | %-9d | %s", ptr->name, ptr->type == HK_REPEATED ? "Repeated" : "One-Shot", ptr->frequency, buf); ptr = ptr->next; } spinlock_release(&tasklock); }