The header was not merged with queryclassifier.hh since the latter does not include the former.
		
			
				
	
	
		
			337 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			337 lines
		
	
	
		
			7.7 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/bsl11.
 | 
						|
 *
 | 
						|
 * Change Date: 2022-01-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/ccdefs.hh>
 | 
						|
 | 
						|
#include <cstdlib>
 | 
						|
#include <list>
 | 
						|
#include <string.h>
 | 
						|
#include <string>
 | 
						|
#include <thread>
 | 
						|
#include <mutex>
 | 
						|
 | 
						|
#include <maxbase/semaphore.h>
 | 
						|
#include <maxscale/alloc.h>
 | 
						|
#include <maxbase/atomic.hh>
 | 
						|
#include <maxscale/clock.h>
 | 
						|
#include <maxscale/config.hh>
 | 
						|
#include <maxscale/housekeeper.h>
 | 
						|
#include <maxscale/json_api.h>
 | 
						|
#include <maxscale/query_classifier.hh>
 | 
						|
 | 
						|
/**
 | 
						|
 * @file housekeeper.cc  Provide a mechanism to run periodic tasks
 | 
						|
 *
 | 
						|
 * The housekeeper provides a mechanism to allow for tasks, function
 | 
						|
 * calls basically, to be run on a time 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 that
 | 
						|
 * is incremented every 100ms and can be read with the mxs_clock() function.
 | 
						|
 */
 | 
						|
 | 
						|
// Helper struct used when starting the housekeeper
 | 
						|
struct hkstart_result
 | 
						|
{
 | 
						|
    sem_t sem;
 | 
						|
    bool  ok;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static void hkthread(hkstart_result*);
 | 
						|
 | 
						|
// TODO: Move these into a separate file
 | 
						|
static int64_t mxs_clock_ticks = 0;     /*< One clock tick is 100 milliseconds */
 | 
						|
 | 
						|
int64_t mxs_clock()
 | 
						|
{
 | 
						|
    return mxb::atomic::load(&mxs_clock_ticks, mxb::atomic::RELAXED);
 | 
						|
}
 | 
						|
 | 
						|
namespace
 | 
						|
{
 | 
						|
 | 
						|
// A task to perform
 | 
						|
struct Task
 | 
						|
{
 | 
						|
    Task(std::string name, TASKFN func, void* data, int frequency)
 | 
						|
        : name(name)
 | 
						|
        , func(func)
 | 
						|
        , data(data)
 | 
						|
        , frequency(frequency)
 | 
						|
        , nextdue(time(0) + frequency)
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    struct NameMatch
 | 
						|
    {
 | 
						|
        NameMatch(std::string name)
 | 
						|
            : m_name(name)
 | 
						|
        {
 | 
						|
        }
 | 
						|
 | 
						|
        bool operator()(const Task& task)
 | 
						|
        {
 | 
						|
            return task.name == m_name;
 | 
						|
        }
 | 
						|
 | 
						|
        std::string m_name;
 | 
						|
    };
 | 
						|
 | 
						|
 | 
						|
    std::string name;       /*< Task name */
 | 
						|
    TASKFN      func;       /*< The function to call */
 | 
						|
    void*       data;       /*< Data to pass to the function */
 | 
						|
    int         frequency;  /*< How often to call the tasks, in seconds */
 | 
						|
    time_t      nextdue;    /*< When the task should be next run */
 | 
						|
};
 | 
						|
 | 
						|
class Housekeeper
 | 
						|
{
 | 
						|
public:
 | 
						|
    Housekeeper();
 | 
						|
 | 
						|
    static bool init();
 | 
						|
    static bool start();
 | 
						|
    void        stop();
 | 
						|
    void        run();
 | 
						|
    void        add(const Task& task);
 | 
						|
    void        remove(std::string name);
 | 
						|
 | 
						|
    void    print_tasks(DCB* pDcb);
 | 
						|
    json_t* tasks_json(const char* host);
 | 
						|
 | 
						|
private:
 | 
						|
    std::thread     m_thread;
 | 
						|
    uint32_t        m_running;
 | 
						|
    std::list<Task> m_tasks;
 | 
						|
    std::mutex      m_lock;
 | 
						|
 | 
						|
    bool is_running() const
 | 
						|
    {
 | 
						|
        return mxb::atomic::load(&m_running);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
// The Housekeeper instance
 | 
						|
static Housekeeper* hk = NULL;
 | 
						|
 | 
						|
Housekeeper::Housekeeper()
 | 
						|
    : m_running(1)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
bool Housekeeper::init()
 | 
						|
{
 | 
						|
    hk = new(std::nothrow) Housekeeper;
 | 
						|
    return hk != nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool Housekeeper::start()
 | 
						|
{
 | 
						|
    mxb_assert(hk);                                         // init() has been called.
 | 
						|
    mxb_assert(hk->m_thread.get_id() == std::thread::id()); // start has not been called.
 | 
						|
 | 
						|
    struct hkstart_result res;
 | 
						|
    sem_init(&res.sem, 0, 0);
 | 
						|
    res.ok = false;
 | 
						|
 | 
						|
    try
 | 
						|
    {
 | 
						|
        hk->m_thread = std::thread(hkthread, &res);
 | 
						|
        sem_wait(&res.sem);
 | 
						|
    }
 | 
						|
    catch (const std::exception& x)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Could not start housekeeping thread: %s", x.what());
 | 
						|
    }
 | 
						|
 | 
						|
    sem_destroy(&res.sem);
 | 
						|
    return res.ok;
 | 
						|
}
 | 
						|
 | 
						|
void Housekeeper::run()
 | 
						|
{
 | 
						|
    while (is_running())
 | 
						|
    {
 | 
						|
        for (int i = 0; i < 10; i++)
 | 
						|
        {
 | 
						|
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
 | 
						|
            mxb::atomic::add(&mxs_clock_ticks, 1, mxb::atomic::RELAXED);
 | 
						|
        }
 | 
						|
 | 
						|
        std::lock_guard<std::mutex> guard(m_lock);
 | 
						|
        time_t now = time(0);
 | 
						|
        auto it = m_tasks.begin();
 | 
						|
 | 
						|
        while (it != m_tasks.end() && is_running())
 | 
						|
        {
 | 
						|
            if (it->nextdue <= now)
 | 
						|
            {
 | 
						|
                it->nextdue = now + it->frequency;
 | 
						|
 | 
						|
                if (!it->func(it->data))
 | 
						|
                {
 | 
						|
                    it = m_tasks.erase(it);
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            it++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Housekeeper::stop()
 | 
						|
{
 | 
						|
    mxb_assert(hk);                                         // init() has been called.
 | 
						|
    mxb_assert(hk->m_thread.get_id() != std::thread::id()); // start has been called.
 | 
						|
 | 
						|
    mxb::atomic::store(&m_running, 0);
 | 
						|
    m_thread.join();
 | 
						|
}
 | 
						|
 | 
						|
void Housekeeper::add(const Task& task)
 | 
						|
{
 | 
						|
    std::lock_guard<std::mutex> guard(m_lock);
 | 
						|
    m_tasks.push_back(task);
 | 
						|
}
 | 
						|
 | 
						|
void Housekeeper::remove(std::string name)
 | 
						|
{
 | 
						|
    std::lock_guard<std::mutex> guard(m_lock);
 | 
						|
    m_tasks.remove_if(Task::NameMatch(name));
 | 
						|
}
 | 
						|
 | 
						|
void Housekeeper::print_tasks(DCB* pDcb)
 | 
						|
{
 | 
						|
    std::lock_guard<std::mutex> guard(m_lock);
 | 
						|
    dcb_printf(pDcb, "%-25s | Type     | Frequency | Next Due\n", "Name");
 | 
						|
    dcb_printf(pDcb, "--------------------------+----------+-----------+-------------------------\n");
 | 
						|
 | 
						|
    for (auto ptr = m_tasks.begin(); ptr != m_tasks.end(); ptr++)
 | 
						|
    {
 | 
						|
        struct tm tm;
 | 
						|
        char buf[40];
 | 
						|
        localtime_r(&ptr->nextdue, &tm);
 | 
						|
        asctime_r(&tm, buf);
 | 
						|
        dcb_printf(pDcb, "%-25s | %-9d | %s", ptr->name.c_str(), ptr->frequency, buf);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
json_t* Housekeeper::tasks_json(const char* host)
 | 
						|
{
 | 
						|
    json_t* arr = json_array();
 | 
						|
 | 
						|
    std::lock_guard<std::mutex> guard(m_lock);
 | 
						|
 | 
						|
    for (auto ptr = m_tasks.begin(); ptr != m_tasks.end(); ptr++)
 | 
						|
    {
 | 
						|
        struct tm tm;
 | 
						|
        char buf[40];
 | 
						|
        localtime_r(&ptr->nextdue, &tm);
 | 
						|
        asctime_r(&tm, buf);
 | 
						|
        char* nl = strchr(buf, '\n');
 | 
						|
        mxb_assert(nl);
 | 
						|
        *nl = '\0';
 | 
						|
 | 
						|
        json_t* obj = json_object();
 | 
						|
 | 
						|
        json_object_set_new(obj, CN_ID, json_string(ptr->name.c_str()));
 | 
						|
        json_object_set_new(obj, CN_TYPE, json_string("tasks"));
 | 
						|
 | 
						|
        json_t* attr = json_object();
 | 
						|
        json_object_set_new(attr, "frequency", json_integer(ptr->frequency));
 | 
						|
        json_object_set_new(attr, "next_execution", json_string(buf));
 | 
						|
 | 
						|
        json_object_set_new(obj, CN_ATTRIBUTES, attr);
 | 
						|
        json_array_append_new(arr, obj);
 | 
						|
    }
 | 
						|
 | 
						|
    return mxs_json_resource(host, MXS_JSON_API_TASKS, arr);
 | 
						|
}
 | 
						|
}
 | 
						|
 | 
						|
void hktask_add(const char* name, TASKFN func, void* data, int frequency)
 | 
						|
{
 | 
						|
    mxb_assert(hk);
 | 
						|
    Task task(name, func, data, frequency);
 | 
						|
    hk->add(task);
 | 
						|
}
 | 
						|
 | 
						|
void hktask_remove(const char* name)
 | 
						|
{
 | 
						|
    mxb_assert(hk);
 | 
						|
    hk->remove(name);
 | 
						|
}
 | 
						|
 | 
						|
void hkthread(hkstart_result* res)
 | 
						|
{
 | 
						|
    // res is on the stack in Housekeeper::start() and remains valid only
 | 
						|
    // until sem_post() is called.
 | 
						|
    res->ok = qc_thread_init(QC_INIT_BOTH);
 | 
						|
 | 
						|
    if (!res->ok)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Could not initialize query classifier in housekeeper thread.");
 | 
						|
    }
 | 
						|
 | 
						|
    bool ok = res->ok;
 | 
						|
    sem_post(&res->sem);
 | 
						|
    // NOTE: res is no longer valid here.
 | 
						|
 | 
						|
    if (ok)
 | 
						|
    {
 | 
						|
        MXS_NOTICE("Housekeeper thread started.");
 | 
						|
        hk->run();
 | 
						|
        qc_thread_end(QC_INIT_BOTH);
 | 
						|
    }
 | 
						|
 | 
						|
    MXS_NOTICE("Housekeeper shutting down.");
 | 
						|
}
 | 
						|
 | 
						|
bool hkinit()
 | 
						|
{
 | 
						|
    return Housekeeper::init();
 | 
						|
}
 | 
						|
 | 
						|
bool hkstart()
 | 
						|
{
 | 
						|
    return Housekeeper::start();
 | 
						|
}
 | 
						|
 | 
						|
void hkfinish()
 | 
						|
{
 | 
						|
    if (hk)
 | 
						|
    {
 | 
						|
        MXS_NOTICE("Waiting for housekeeper to shut down.");
 | 
						|
        hk->stop();
 | 
						|
        delete hk;
 | 
						|
        hk = NULL;
 | 
						|
        MXS_NOTICE("Housekeeper has shut down.");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void hkshow_tasks(DCB* pDcb)
 | 
						|
{
 | 
						|
    mxb_assert(hk);
 | 
						|
    hk->print_tasks(pDcb);
 | 
						|
}
 | 
						|
 | 
						|
json_t* hk_tasks_json(const char* host)
 | 
						|
{
 | 
						|
    mxb_assert(hk);
 | 
						|
    return hk->tasks_json(host);
 | 
						|
}
 |