From c5fe259ae0034f4b8e53b7e2f24035a031ba9f90 Mon Sep 17 00:00:00 2001 From: Pavel Labath Date: Fri, 24 Dec 2010 15:03:54 +0100 Subject: [PATCH] a new update callback system it should replace both timed_thread and run_update_callback() systems it features: - automatic removal of callbacks which are not used - ability to run callback less frequent than the update_interval - avoidance of running the same callback multiple times --- src/CMakeLists.txt | 2 +- src/semaphore.hh | 74 ++++++++++++++++ src/update-cb.cc | 126 ++++++++++++++++++++++++++ src/update-cb.hh | 216 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 src/semaphore.hh create mode 100644 src/update-cb.cc create mode 100644 src/update-cb.hh diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f09f338..57b1b891 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,7 +38,7 @@ set(conky_sources colours.cc combine.cc common.cc conky.cc core.cc diskio.cc entropy.cc exec.cc fs.cc mail.cc mixer.cc net_stat.cc template.cc mboxscan.cc read_tcpip.cc scroll.cc specials.cc tailhead.cc temphelper.cc text_object.cc timeinfo.cc top.cc algebra.cc prioqueue.cc proc.cc - user.cc luamm.cc data-source.cc lua-config.cc setting.cc llua.cc) + user.cc luamm.cc data-source.cc lua-config.cc setting.cc llua.cc update-cb.cc) # add timed thread library add_library(timed-thread timed-thread.cc) diff --git a/src/semaphore.hh b/src/semaphore.hh new file mode 100644 index 00000000..68f5ca3d --- /dev/null +++ b/src/semaphore.hh @@ -0,0 +1,74 @@ +/* -*- mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=cpp + * + * Conky, a system monitor, based on torsmo + * + * Please see COPYING for details + * + * Copyright (C) 2010 Pavel Labath et al. + * + * 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 SEMAPHORE_HH +#define SEMAPHORE_HH + +#include +#include +#include + +#include + +class semaphore { + sem_t sem; + + semaphore(const semaphore &) = delete; + semaphore& operator=(const semaphore &) = delete; +public: + semaphore(unsigned int value = 0) throw(std::logic_error) + { + if(sem_init(&sem, 0, value)) + throw std::logic_error(strerror(errno)); + } + + ~semaphore() throw() + { sem_destroy(&sem); } + + void post() throw(std::overflow_error) + { + if(sem_post(&sem)) + throw std::overflow_error(strerror(errno)); + } + + void wait() throw() + { + while(sem_wait(&sem)) { + if(errno != EINTR) + abort(); + } + } + + bool trywait() throw() + { + while(sem_trywait(&sem)) { + if(errno == EAGAIN) + return false; + else if(errno != EINTR) + abort(); + } + return true; + } +}; + +#endif diff --git a/src/update-cb.cc b/src/update-cb.cc new file mode 100644 index 00000000..b2f9009f --- /dev/null +++ b/src/update-cb.cc @@ -0,0 +1,126 @@ +/* -*- mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=cpp + * + * Conky, a system monitor, based on torsmo + * + * Please see COPYING for details + * + * Copyright (C) 2010 Pavel Labath et al. + * + * 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 "config.h" + +#include "update-cb.hh" + +#include + +namespace conky { + namespace { + semaphore sem_wait; + enum {UNUSED_MAX = 5}; + } + + namespace priv { + callback_base::~callback_base() + { + if(thread) { + done = true; + sem_start.post(); + thread->join(); + delete thread; + } + } + + inline size_t callback_base::get_hash(const handle &h) + { return h->hash; } + + inline bool callback_base::is_equal(const handle &a, const handle &b) + { + if(a->hash != b->hash) + return false; + + if(typeid(*a) != typeid(*b)) + return false; + + return *a == *b; + } + + callback_base::handle callback_base::do_register_cb(const handle &h) + { + const handle &ret = *callbacks.insert(h).first; + + if(h->period < ret->period) { + ret->period = h->period; + ret->remaining = 0; + } + assert(ret->wait == h->wait); + ret->unused = 0; + + return ret; + } + + void callback_base::run() + { + if(not thread) + thread = new std::thread(&callback_base::start_routine, this); + + sem_start.post(); + } + + void callback_base::start_routine() + { + for(;;) { + sem_start.wait(); + if(done) + return; + work(); + if(wait) + sem_wait.post(); + } + } + + callback_base::Callbacks callback_base::callbacks(1, get_hash, is_equal); + } + + + void run_all_callbacks() + { + using priv::callback_base; + + size_t wait = 0; + for(auto i = callback_base::callbacks.begin(); i != callback_base::callbacks.end(); ) { + callback_base &cb = **i; + + if(cb.remaining-- == 0) { + if(!i->unique() || ++cb.unused < UNUSED_MAX) { + cb.remaining = cb.period-1; + cb.run(); + if(cb.wait) + ++wait; + } + } + if(cb.unused == UNUSED_MAX) { + auto t = i; + ++i; + callback_base::callbacks.erase(t); + } else + ++i; + } + + while(wait-- > 0) + sem_wait.wait(); + } +} diff --git a/src/update-cb.hh b/src/update-cb.hh new file mode 100644 index 00000000..cb6138bc --- /dev/null +++ b/src/update-cb.hh @@ -0,0 +1,216 @@ +/* -*- mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=cpp + * + * Conky, a system monitor, based on torsmo + * + * Please see COPYING for details + * + * Copyright (C) 2010 Pavel Labath et al. + * + * 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 UPDATE_CB_HH +#define UPDATE_CB_HH + +#include +#include +#include +#include +#include + +#include + +#include "semaphore.hh" + +namespace conky { + // forward declarations + template + class callback_handle; + void run_all_callbacks(); + template + callback_handle register_cb(uint32_t period, Params&&... params); + + namespace priv { + class callback_base { + typedef callback_handle handle; + typedef std::unordered_set + Callbacks; + + + semaphore sem_start; + std::thread *thread; + const size_t hash; + uint32_t period; + uint32_t remaining; + const bool wait; + bool done; + uint8_t unused; + + callback_base(const callback_base &) = delete; + callback_base& operator=(const callback_base &) = delete; + + virtual bool operator==(const callback_base &) = 0; + + void run(); + void start_routine(); + + // a list of registered callbacks + static Callbacks callbacks; + + // used by the callbacks list + static inline size_t get_hash(const handle &h); + static inline bool is_equal(const handle &a, const handle &b); + + static handle do_register_cb(const handle &h); + + template + friend callback_handle + conky::register_cb(uint32_t period, Params&&... params); + + friend void conky::run_all_callbacks(); + + protected: + callback_base(size_t hash_, uint32_t period_, bool wait_) + : thread(NULL), hash(hash_), period(period_), remaining(0), wait(wait_), + done(false), unused(0) + {} + + // to be implemented by descendant classes + virtual void work() = 0; + + public: + std::mutex result_mutex; + + virtual ~callback_base(); + }; + + } + + template + class callback_handle: private std::shared_ptr { + typedef std::shared_ptr Base; + + callback_handle(Callback *ptr) + : Base(ptr) + {} + + callback_handle(Base &&ptr) + : Base(std::move(ptr)) + {} + + public: + + using Base::operator->; + using Base::operator*; + + friend void conky::run_all_callbacks(); + template + friend callback_handle register_cb(uint32_t period, Params&&... params); + }; + + + template + callback_handle register_cb(uint32_t period, Params&&... params) + { + return std::dynamic_pointer_cast(priv::callback_base::do_register_cb( + priv::callback_base::handle( + new Callback(period, std::forward(params)...) + ) + )); + } + + namespace priv { + template + struct hash_tuple { + typedef std::tuple Tuple; + typedef typename std::tuple_element::type Element; + + static inline size_t hash(const Tuple &tuple) + { + return std::hash()(std::get(tuple)) + + 47 * hash_tuple::hash(tuple); + } + }; + + template + struct hash_tuple<0, Elements...> { + static inline size_t hash(const std::tuple &) + { return 0; } + }; + } + + /* + * To create a callback, inherit from this class. The Result template parameter should be the + * type of your output, so that your users can retrieve it with the get_result* functions. + * + * get_result() returns a reference to the internal variable. It can be used without locking + * if the object has wait set to true (wait=true means that the run_all_callbacks() waits for + * the callback to finish work()ing before returning). If object has wait=false then the user + * must first lock the result_mutex. + * + * get_result_copy() returns a copy of the result object and it handles the necessary + * locking. Don't call it if you hold a lock on the result_mutex. + * + * You should implement the work() function to do the actual updating and store the result in + * the result variable (lock the mutex while you are doing it, especially if you have + * wait=false). + * + * The Keys... template parameters are parameters for your work function. E.g., a curl + * callback can have one parameter - the url to retrieve,. hddtemp may have two - host and + * port number of the hddtemp server, etc. The register_cb() function make sure that there + * exists only one object (of the same type) with the same values for all the keys. + * + * Callbacks are registered with the register_cb() function. You pass the class name as the + * template parameter, and any additional parameters to the constructor as function + * parameters. The period parameter specifies how often the callback will run. It should be + * left for the user to decide that. register_cb() returns a pointer to the newly created + * object. As long as someone holds a pointer to the object, the callback will be run. + * + * run_all_callbacks() runs the registered callbacks (with the specified periodicity). It + * should be called from somewhere inside the main loop, according to the update_interval + * setting. It waits for the callbacks which have wait=true. It leaves the rest to run in + * background. + */ + template + class callback: public priv::callback_base { + virtual bool operator==(const callback_base &other) + { return tuple == dynamic_cast(other).tuple; } + + protected: + typedef std::tuple Tuple; + + const Tuple tuple; + Result result; + + public: + callback(uint32_t period_, bool wait_, const Tuple &tuple_) + : callback_base(priv::hash_tuple::hash(tuple_), + period_, wait_), + tuple(tuple_) + {} + + const Result& get_result() + { return result; } + + Result get_result_copy() + { + std::lock_guard l(result_mutex); + return result; + } + }; +} + +#endif /* LUA_CONFIG_HH */