MaxScale/server/core/housekeeper.c
Johan Wikman 02ec7e9b17 Wait until housekeeper has finished
When calling hkshutdown(), only a shutdown flag will be set.
In main() we will then actually wait for the housekeeper thread
to finish before exiting.
2016-11-09 15:14:52 +02:00

353 lines
8.5 KiB
C

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <maxscale/housekeeper.h>
#include <stdlib.h>
#include <string.h>
#include <maxscale/alloc.h>
#include <maxscale/atomic.h>
#include <maxscale/semaphore.h>
#include <maxscale/spinlock.h>
#include <maxscale/thread.h>
/**
* @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 bool do_shutdown = 0;
long hkheartbeat = 0; /*< One heartbeat is 100 milliseconds */
static THREAD hk_thr_handle;
static void hkthread(void *);
bool
hkinit()
{
bool inited = false;
if (thread_start(&hk_thr_handle, hkthread, NULL) != NULL)
{
inited = true;
}
else
{
MXS_ALERT("Failed to start housekeeper thread.");
}
return inited;
}
/**
* 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(const char *name, void (*taskfn)(void *), void *data, int frequency)
{
HKTASK *task, *ptr;
if ((task = (HKTASK *)MXS_MALLOC(sizeof(HKTASK))) == NULL)
{
return 0;
}
if ((task->name = MXS_STRDUP(name)) == NULL)
{
MXS_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);
MXS_FREE(task->name);
MXS_FREE(task);
return 0;
}
ptr = ptr->next;
}
if (ptr)
{
if (strcmp(ptr->name, name) == 0)
{
spinlock_release(&tasklock);
MXS_FREE(task->name);
MXS_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(const char *name, void (*taskfn)(void *), void *data, int when)
{
HKTASK *task, *ptr;
if ((task = (HKTASK *)MXS_MALLOC(sizeof(HKTASK))) == NULL)
{
return 0;
}
if ((task->name = MXS_STRDUP(name)) == NULL)
{
MXS_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)
{
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(const 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)
{
MXS_FREE(ptr->name);
MXS_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;
while (!do_shutdown)
{
for (i = 0; i < 10; i++)
{
thread_millisleep(100);
hkheartbeat++;
}
now = time(0);
spinlock_acquire(&tasklock);
ptr = tasks;
while (!do_shutdown && ptr)
{
if (ptr->nextdue <= now)
{
ptr->nextdue = now + ptr->frequency;
taskfn = ptr->task;
taskdata = ptr->data;
// We need to copy type and name, in case hktask_remove is called from
// the callback. Otherwise we will access freed data.
HKTASK_TYPE type = ptr->type;
char name[strlen(ptr->name) + 1];
strcpy(name, ptr->name);
spinlock_release(&tasklock);
(*taskfn)(taskdata);
if (type == HK_ONESHOT)
{
hktask_remove(name);
}
spinlock_acquire(&tasklock);
ptr = tasks;
}
else
{
ptr = ptr->next;
}
}
spinlock_release(&tasklock);
}
MXS_NOTICE("Housekeeper shutting down.");
}
void
hkshutdown()
{
do_shutdown = true;
atomic_synchronize();
}
void hkfinish()
{
ss_dassert(do_shutdown);
MXS_NOTICE("Waiting for housekeeper to shut down.");
thread_wait(hk_thr_handle);
do_shutdown = false;
MXS_NOTICE("Housekeeper has shut down.");
}
/**
* 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);
}