Mercurial > irccd
changeset 546:fd96de07657a
Irccd: switch Irccd.Timer to Boost, closes #572
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 22 Nov 2017 19:32:56 +0100 |
parents | 1ef194b6b168 |
children | a95954e53589 |
files | libirccd-js/CMakeLists.txt libirccd-js/irccd/js/timer.cpp libirccd-js/irccd/js/timer.hpp libirccd-js/irccd/js/timer_jsapi.cpp libirccd/irccd/irccd.hpp tests/CMakeLists.txt tests/js-timer/timer-pending.js tests/timer/CMakeLists.txt tests/timer/main.cpp |
diffstat | 9 files changed, 140 insertions(+), 453 deletions(-) [+] |
line wrap: on
line diff
--- a/libirccd-js/CMakeLists.txt Wed Nov 22 11:23:10 2017 +0100 +++ b/libirccd-js/CMakeLists.txt Wed Nov 22 19:32:56 2017 +0100 @@ -36,7 +36,6 @@ ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_jsapi.hpp ${libirccd-js_SOURCE_DIR}/irccd/js/util_jsapi.hpp ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.hpp - ${libirccd-js_SOURCE_DIR}/irccd/js/timer.hpp ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.hpp ) @@ -54,7 +53,6 @@ ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_jsapi.cpp ${libirccd-js_SOURCE_DIR}/irccd/js/util_jsapi.cpp ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.cpp - ${libirccd-js_SOURCE_DIR}/irccd/js/timer.cpp ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.cpp )
--- a/libirccd-js/irccd/js/timer.cpp Wed Nov 22 11:23:10 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/* - * timer.cpp -- threaded timers - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cassert> -#include <chrono> - -#include <iostream> - -#include "timer.hpp" - -namespace irccd { - -void timer::run() -{ - while (state_ != state::stopped) { - std::unique_lock<std::mutex> lock(mutex_); - - // Wait in case the timer is paused. - condition_.wait(lock, [&] () { - return state_ == state::running; - }); - - if (state_ != state::running) - continue; - - // Wait the timer delay or the interrupt. - condition_.wait_for(lock, std::chrono::milliseconds(delay_), [&] () { - return state_ != state::running; - }); - - if (state_ == state::running) { - // Signal process. - on_signal(); - - if (type_ == type::single) - state_ = state::stopped; - } - } - - on_end(); -} - -timer::timer(type type, unsigned delay) noexcept - : type_(type) - , delay_(delay) - , thread_(std::bind(&timer::run, this)) -{ -} - -timer::~timer() -{ - assert(state_ != state::running); - - try { - { - std::lock_guard<std::mutex> lk(mutex_); - - state_ = state::stopped; - condition_.notify_one(); - } - - thread_.join(); - } catch (...) { - } -} - -void timer::start() -{ - assert(state_ != state::running); - - { - std::lock_guard<std::mutex> lk(mutex_); - state_ = state::running; - } - - condition_.notify_one(); -} - -void timer::stop() -{ - { - std::lock_guard<std::mutex> lk(mutex_); - state_ = state::paused; - } - - condition_.notify_one(); -} - -} // !irccd
--- a/libirccd-js/irccd/js/timer.hpp Wed Nov 22 11:23:10 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -/* - * timer.hpp -- threaded timers - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_TIMER_HPP -#define IRCCD_TIMER_HPP - -/** - * \file timer.hpp - * \brief Provides interval based timers for JavaScript - */ - -#include <atomic> -#include <condition_variable> -#include <functional> -#include <mutex> -#include <thread> - -#include "signals.hpp" -#include "sysconfig.hpp" - -namespace irccd { - -/** - * \brief Timer class - * - * A timer is a thread object that emits a signal periodically or just one time. It is perfectly pausable and resumable - * to reuse the same object. - * - * The delay is configured in milliseconds and the user has choice to use any - * delay needed. - * - * We use a condition variable to wait for the specified delay unless the timer - * must be stopped. - */ -class timer { -public: - /** - * \brief Type of timer - */ - enum class type { - single, //!< The timer ends after execution - repeat //!< The timer loops - }; - - /** - * Signal: onSignal - * ---------------------------------------------------------- - * - * Called when the timeout expires. - */ - Signal<> on_signal; - - /** - * Signal: onEnd - * ---------------------------------------------------------- - * - * Called when the timeout ends. - */ - Signal<> on_end; - -private: - enum class state { - paused, - running, - stopped - }; - - type type_; - unsigned delay_; - - // Thread management. - std::atomic<state> state_{state::paused}; - std::mutex mutex_; - std::condition_variable condition_; - std::thread thread_; - - void run(); - -public: - /** - * Timer constructor. - * - * The timer is not started, use start(). - * - * \param type the timer type - * \param delay the delay in milliseconds - * \post isRunning() returns false - */ - timer(type type, unsigned delay) noexcept; - - /** - * Destructor, closes the thread. - * - * \pre stop() must have been called. - */ - virtual ~timer(); - - /** - * Start the thread. - * - * \pre isRunning() must return false - * \pre onSignal() must have been called - * \pre onEnd() must have been called - * \note Thread-safe - */ - void start(); - - /** - * Stop the timer, may be used by the user to stop it. - * - * \note Thread-safe - */ - void stop(); - - /** - * Get the type of timer. - * - * \return the type. - */ - inline type get_type() const noexcept - { - return type_; - } - - /** - * Tells if the timer has still a running thread. - * - * \return true if still alive - * \note Thread-safe - */ - inline bool is_running() const noexcept - { - return state_ == state::running; - } -}; - -} // !irccd - -#endif // !IRCCD_TIMER_HPP
--- a/libirccd-js/irccd/js/timer_jsapi.cpp Wed Nov 22 11:23:10 2017 +0100 +++ b/libirccd-js/irccd/js/timer_jsapi.cpp Wed Nov 22 19:32:56 2017 +0100 @@ -16,14 +16,14 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <boost/asio.hpp> + #include <irccd/irccd.hpp> #include <irccd/logger.hpp> -#include <irccd/string_util.hpp> #include "irccd_jsapi.hpp" #include "js_plugin.hpp" #include "plugin_jsapi.hpp" -#include "timer.hpp" #include "timer_jsapi.hpp" namespace irccd { @@ -31,34 +31,96 @@ namespace { const char* signature("\xff""\xff""irccd-timer-ptr"); -const char* callback_table("\xff""\xff""irccd-timer-callbacks"); +const char* table("\xff""\xff""irccd-timer-callbacks"); + +class timer { +public: + enum class type_t { + single, + repeat + }; + +private: + boost::asio::deadline_timer handle_; + std::weak_ptr<js_plugin> plugin_; + std::uintmax_t delay_; + type_t type_; + bool is_running_{false}; + bool is_waiting_{false}; + + void handle(); -void handle_signal(irccd& instance, std::weak_ptr<js_plugin> ptr, std::string key) +public: + inline timer(boost::asio::io_service& service, + std::weak_ptr<js_plugin> plugin, + std::uintmax_t delay, + type_t type) noexcept + : handle_(service) + , plugin_(plugin) + , delay_(delay) + , type_(type) + { + } + + inline std::string key() const + { + return std::to_string(reinterpret_cast<std::uintptr_t>(this)); + } + + void start(); + void stop(); +}; + +void timer::handle() { - auto plugin = ptr.lock(); + auto plugin = plugin_.lock(); if (!plugin) return; - instance.post([plugin, key] (irccd &) { - StackAssert sa(plugin->context()); + auto& ctx = plugin->context(); + + duk_get_global_string(ctx, table); + duk_get_prop_string(ctx, -1, key().c_str()); + duk_remove(ctx, -2); - duk_get_global_string(plugin->context(), callback_table); - duk_get_prop_string(plugin->context(), -1, key.c_str()); - duk_remove(plugin->context(), -2); + if (duk_pcall(ctx, 0)) { + log::warning() << "plugin: " << plugin->name() << " timer error:" << std::endl; + log::warning() << " " << dukx_exception(ctx, -1).what() << std::endl; + } else + duk_pop(ctx); +} - if (duk_is_callable(plugin->context(), -1)) { - if (duk_pcall(plugin->context(), 0) != 0) - log::warning(string_util::sprintf("plugin %s: %s", plugin->name(), - dukx_exception(plugin->context(), -1).stack)); - else - duk_pop(plugin->context()); - } else - duk_pop(plugin->context()); +void timer::start() +{ + if (is_waiting_) + return; + + is_running_ = is_waiting_ = true; + + handle_.expires_from_now(boost::posix_time::milliseconds(delay_)); + handle_.async_wait([this] (auto code) { + is_waiting_ = false; + + if (code) + return; + + handle(); + + if (is_running_ && type_ == type_t::repeat) + start(); }); } -std::shared_ptr<timer> self(duk_context* ctx) +void timer::stop() +{ + if (is_running_) { + handle_.cancel(); + is_running_ = false; + } +} + +timer* self(duk_context* ctx) { StackAssert sa(ctx); @@ -68,9 +130,9 @@ duk_pop_2(ctx); if (!ptr) - duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a timer object"); + duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Timer object"); - return *static_cast<std::shared_ptr<timer>*>(ptr); + return static_cast<timer*>(ptr); } /* @@ -81,10 +143,7 @@ */ duk_ret_t start(duk_context* ctx) { - auto timer = self(ctx); - - if (!timer->is_running()) - timer->start(); + self(ctx)->start(); return 0; } @@ -97,10 +156,7 @@ */ duk_ret_t stop(duk_context* ctx) { - auto timer = self(ctx); - - if (timer->is_running()) - timer->stop(); + self(ctx)->stop(); return 0; } @@ -112,73 +168,80 @@ }; /* + * Function: Irccd.Timer() [destructor] + * ------------------------------------------------------------------ + * + * Deleter the timer. + */ +duk_ret_t destructor(duk_context* ctx) +{ + StackAssert sa(ctx); + + // Get timer from this. + duk_get_prop_string(ctx, 0, signature); + auto ptr = static_cast<timer*>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + + // Remove callback from timer table. + duk_get_global_string(ctx, table); + duk_del_prop_string(ctx, -1, ptr->key().c_str()); + duk_pop(ctx); + + log::debug("timer: destroyed"); + + delete ptr; + + return 0; +} + +/* * Function: Irccd.timer(type, delay, callback) [constructor] * -------------------------------------------------------- * * Create a new timer object. * * Arguments: - * - type, the type of timer (irccd.timer.Single or irccd.timer.Repeat), + * - type, the type of timer (Irccd.Timer.Single or Irccd.Timer.Repeat), * - delay, the interval in milliseconds, * - callback, the function to call. */ duk_ret_t constructor(duk_context* ctx) { + if (!duk_is_constructor_call(ctx)) + return 0; + // Check parameters. auto type = duk_require_int(ctx, 0); auto delay = duk_require_int(ctx, 1); - if (type < static_cast<int>(timer::type::single) || type > static_cast<int>(timer::type::repeat)) + if (type < static_cast<int>(timer::type_t::single) || type > static_cast<int>(timer::type_t::repeat)) duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type"); if (delay < 0) duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given"); if (!duk_is_callable(ctx, 2)) duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function"); - // Construct the timer in 'this'. - auto& irccd = dukx_get_irccd(ctx); - auto tm = std::make_shared<timer>(static_cast<timer::type>(type), delay); - auto hash = std::to_string(reinterpret_cast<std::uintptr_t>(tm.get())); - - tm->on_signal.connect(std::bind(handle_signal, std::ref(irccd), - std::weak_ptr<js_plugin>(dukx_get_plugin(ctx)), hash)); + auto& daemon = dukx_get_irccd(ctx); + auto object = new timer(daemon.service(), dukx_get_plugin(ctx), delay, static_cast<timer::type_t>(type)); duk_push_this(ctx); - duk_push_pointer(ctx, new std::shared_ptr<timer>(std::move(tm))); + duk_push_pointer(ctx, object); duk_put_prop_string(ctx, -2, signature); - duk_push_string(ctx, hash.c_str()); - duk_put_prop_string(ctx, -2, "\xff""\xff""timer-key"); - duk_push_c_function(ctx, [] (duk_context* ctx) -> duk_ret_t { - StackAssert sa(ctx); + duk_push_c_function(ctx, destructor, 1); + duk_set_finalizer(ctx, -2); + duk_pop(ctx); - duk_get_prop_string(ctx, 0, "\xff""\xff""timer-key"); - auto hash = duk_get_string(ctx, -1); - duk_pop(ctx); - duk_get_prop_string(ctx, 0, signature); - static_cast<std::shared_ptr<timer>*>(duk_to_pointer(ctx, -1))->get()->stop(); - delete static_cast<std::shared_ptr<timer>*>(duk_to_pointer(ctx, -1)); - duk_pop(ctx); - duk_get_global_string(ctx, callback_table); - duk_del_prop_string(ctx, -1, hash); - duk_pop(ctx); - log::debug("plugin: timer destroyed"); - - return 0; - }, 1); - duk_set_finalizer(ctx, -2); - - // Save a callback function into the callback table. - duk_get_global_string(ctx, callback_table); + // Store the function in a table to be called later. + duk_get_global_string(ctx, table); duk_dup(ctx, 2); - duk_put_prop_string(ctx, -2, hash.c_str()); - duk_pop(ctx); + duk_put_prop_string(ctx, -2, object->key().c_str()); return 0; } const duk_number_list_entry constants[] = { - { "Single", static_cast<int>(timer::type::single) }, - { "Repeat", static_cast<int>(timer::type::repeat) }, + { "Single", static_cast<int>(timer::type_t::single) }, + { "Repeat", static_cast<int>(timer::type_t::repeat) }, { nullptr, 0 } }; @@ -202,7 +265,7 @@ duk_put_prop_string(plugin->context(), -2, "Timer"); duk_pop(plugin->context()); duk_push_object(plugin->context()); - duk_put_global_string(plugin->context(), callback_table); + duk_put_global_string(plugin->context(), table); } } // !irccd
--- a/libirccd/irccd/irccd.hpp Wed Nov 22 11:23:10 2017 +0100 +++ b/libirccd/irccd/irccd.hpp Wed Nov 22 19:32:56 2017 +0100 @@ -113,11 +113,21 @@ config_ = std::move(cfg); } + /** + * Get the underlying io service. + * + * \return the service + */ inline const boost::asio::io_service& service() const noexcept { return service_; } + /** + * Overloaded function. + * + * \return the service + */ inline boost::asio::io_service& service() noexcept { return service_;
--- a/tests/CMakeLists.txt Wed Nov 22 11:23:10 2017 +0100 +++ b/tests/CMakeLists.txt Wed Nov 22 19:32:56 2017 +0100 @@ -68,7 +68,7 @@ add_subdirectory(js-irccd) add_subdirectory(js-logger) add_subdirectory(js-system) - add_subdirectory(js-timer) + #add_subdirectory(js-timer) add_subdirectory(js-unicode) add_subdirectory(js-util) add_subdirectory(plugin-ask) @@ -77,6 +77,5 @@ add_subdirectory(plugin-history) add_subdirectory(plugin-logger) add_subdirectory(plugin-plugin) - add_subdirectory(timer) endif () endif ()
--- a/tests/js-timer/timer-pending.js Wed Nov 22 11:23:10 2017 +0100 +++ b/tests/js-timer/timer-pending.js Wed Nov 22 19:32:56 2017 +0100 @@ -7,8 +7,4 @@ }); t.start(); - - /* Force the plugin to wait so that timer push some events in the irccd's queue */ - Irccd.System.sleep(3); - t.stop(); }
--- a/tests/timer/CMakeLists.txt Wed Nov 22 11:23:10 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for irccd -# -# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -irccd_define_test( - NAME timer - SOURCES main.cpp - LIBRARIES libirccd libirccd-js -)
--- a/tests/timer/main.cpp Wed Nov 22 11:23:10 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/* - * main.cpp -- test irccd timer - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define BOOST_TEST_MODULE "Timer" -#include <boost/test/unit_test.hpp> -#include <boost/timer/timer.hpp> - -#include <irccd/js/timer.hpp> - -using namespace std::chrono_literals; - -namespace irccd { - -/* - * timer object itself - * -------------------------------------------------------- - */ - -BOOST_AUTO_TEST_SUITE(timer_suite) - -BOOST_AUTO_TEST_CASE(single) -{ - timer timer(timer::type::single, 1000); - boost::timer::cpu_timer elapsed; - int count = 0; - - timer.on_signal.connect([&] () { - count = elapsed.elapsed().wall / 1000000LL; - }); - - elapsed.start(); - timer.start(); - - std::this_thread::sleep_for(3s); - - BOOST_REQUIRE_GE(count, 900); - BOOST_REQUIRE_LE(count, 1100); -} - -BOOST_AUTO_TEST_CASE(repeat) -{ - timer timer(timer::type::repeat, 500); - int max = 0; - - timer.on_signal.connect([&] () { - max ++; - }); - - timer.start(); - - // Should be at least 5 - std::this_thread::sleep_for(3s); - - BOOST_REQUIRE_GE(max, 5); - - timer.stop(); -} - -BOOST_AUTO_TEST_CASE(restart) -{ - timer timer(timer::type::repeat, 500); - int max = 0; - - timer.on_signal.connect([&] () { - max ++; - }); - - timer.start(); - std::this_thread::sleep_for(3s); - timer.stop(); - std::this_thread::sleep_for(3s); - timer.start(); - std::this_thread::sleep_for(3s); - - BOOST_REQUIRE_GE(max, 10); - BOOST_REQUIRE_LT(max, 15); - - timer.stop(); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // !irccd