changeset 135:6be066ad2329

Irccd: plugins do not own timer anymore, #411
author David Demelier <markand@malikania.fr>
date Wed, 18 May 2016 13:24:50 +0200
parents dc7d6ba08122
children 01df93b56dde
files lib/irccd/js.hpp lib/irccd/mod-irccd.cpp lib/irccd/mod-timer.cpp lib/irccd/plugin-js.cpp lib/irccd/plugin-js.hpp lib/irccd/plugin.hpp lib/irccd/service-plugin.cpp lib/irccd/service-plugin.hpp
diffstat 8 files changed, 121 insertions(+), 171 deletions(-) [+]
line wrap: on
line diff
--- a/lib/irccd/js.hpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/js.hpp	Wed May 18 13:24:50 2016 +0200
@@ -821,6 +821,17 @@
 }
 
 /**
+ * Wrapper for [duk_set_finalizer](http://duktape.org/api.html#duk_set_finalizer).
+ *
+ * \param ctx the context
+ * \param index the object index
+ */
+inline void setFinalizer(ContextPtr ctx, Index index)
+{
+	duk_set_finalizer(ctx, index);
+}
+
+/**
  * Wrapper for [duk_set_prototype](http://duktape.org/api.html#duk_set_prototype).
  *
  * \param ctx the context
--- a/lib/irccd/mod-irccd.cpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/mod-irccd.cpp	Wed May 18 13:24:50 2016 +0200
@@ -63,7 +63,7 @@
 {
 }
 
-void IrccdModule::load(Irccd &, JsPlugin &plugin)
+void IrccdModule::load(Irccd &irccd, JsPlugin &plugin)
 {
 	duk::StackAssert sa(plugin.context());
 
@@ -89,6 +89,9 @@
 
 	// Set Irccd as global.
 	duk::putGlobal(plugin.context(), "Irccd");
+
+	// Store global instance.
+	duk::putGlobal(plugin.context(), "\xff""\xff""irccd", duk::RawPointer<Irccd>{&irccd});
 }
 
 } // !irccd
--- a/lib/irccd/mod-timer.cpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/mod-timer.cpp	Wed May 18 13:24:50 2016 +0200
@@ -16,13 +16,16 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <cassert>
-#include <cstdint>
+#include <format.h>
 
+#include "irccd.hpp"
+#include "logger.hpp"
 #include "mod-timer.hpp"
 #include "plugin-js.hpp"
 #include "timer.hpp"
 
+using namespace fmt::literals;
+
 namespace irccd {
 
 namespace duk {
@@ -30,7 +33,7 @@
 template <>
 class TypeTraits<Timer> {
 public:
-	static std::string name()
+	static std::string name() noexcept
 	{
 		return "\xff""\xff""Timer";
 	}
@@ -45,12 +48,42 @@
 
 namespace {
 
+const std::string CallbackTable{"\xff""\xff""irccd-timer-callbacks"};
+
+void handleSignal(std::weak_ptr<JsPlugin> ptr, std::string key)
+{
+	auto plugin = ptr.lock();
+
+	if (!plugin) {
+		return;
+	}
+
+	auto irccd = duk::getGlobal<duk::RawPointer<Irccd>>(plugin->context(), "\xff""\xff""irccd");
+
+	irccd->post([plugin, key] (Irccd &) {
+		duk::StackAssert sa(plugin->context());
+
+		duk::getGlobal<void>(plugin->context(), CallbackTable);
+		duk::getProperty<void>(plugin->context(), -1, key);
+		duk::remove(plugin->context(), -2);
+
+		if (duk::is<duk::Function>(plugin->context(), -1)) {
+			if (duk::pcall(plugin->context()) != 0) {
+				log::warning("plugin {}: {}"_format(plugin->name(), duk::error(plugin->context(), -1).stack));
+			}
+
+			duk::pop(plugin->context());
+		} else {
+			duk::pop(plugin->context());
+		}
+	});
+}
+
 /*
  * Method: Timer.start()
  * --------------------------------------------------------
  *
- * Start the timer. If the timer is already started the method
- * is a no-op.
+ * Start the timer. If the timer is already started the method is a no-op.
  */
 duk::Ret start(duk::ContextPtr ctx)
 {
@@ -98,24 +131,49 @@
  */
 duk::Ret constructor(duk::ContextPtr ctx)
 {
-	int type = duk::require<int>(ctx, 0);
-	int delay = duk::require<int>(ctx, 1);
+	// Check parameters.
+	auto type = duk::require<int>(ctx, 0);
+	auto delay = duk::require<int>(ctx, 1);
 
+	if (type < static_cast<int>(TimerType::Single) || type > static_cast<int>(TimerType::Repeat)) {
+		duk::raise(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
+	}
+	if (delay < 0) {
+		duk::raise(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
+	}
 	if (!duk::is<duk::Function>(ctx, 2)) {
-		duk::raise(ctx, duk::TypeError("missing callback function"));
+		duk::raise(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");
 	}
 
+	// Construct the timer in 'this'.
 	auto timer = std::make_shared<Timer>(static_cast<TimerType>(type), delay);
+	auto plugin = std::static_pointer_cast<JsPlugin>(duk::getGlobal<duk::RawPointer<Plugin>>(ctx, "\xff""\xff""plugin")->shared_from_this());
+	auto hash = std::to_string(reinterpret_cast<std::uintptr_t>(timer.get()));
+
+	timer->onSignal.connect(std::bind(handleSignal, std::weak_ptr<JsPlugin>(plugin), hash));
 
-	/* Add this timer to the underlying plugin */
-	duk::getGlobal<duk::RawPointer<JsPlugin>>(ctx, "\xff""\xff""plugin")->addTimer(timer);
+	// Set a finalizer that closes the timer.
+	duk::construct(ctx, duk::Shared<Timer>{timer});
+	duk::push(ctx, duk::This());
+	duk::putProperty(ctx, -1, "\xff""\xff""timer-key", hash);
+	duk::push(ctx, duk::Function{[] (duk::ContextPtr ctx) -> duk::Ret {
+		duk::StackAssert sa(ctx);
 
-	/* Construct object */
-	duk::construct(ctx, duk::Shared<Timer>{timer});
+		duk::get<duk::Shared<Timer>>(ctx, 0)->stop();
+		duk::getGlobal<void>(ctx, CallbackTable);
+		duk::deleteProperty(ctx, -1, duk::getProperty<std::string>(ctx, 0, "\xff""\xff""timer-key"));
+		duk::pop(ctx);
+		log::debug("plugin: timer destroyed");
 
-	/* Now store the JavaScript function to call */
+		return 0;
+	}, 1});
+	duk::setFinalizer(ctx, -2);
+
+	// Save a callback function into the callback table.
+	duk::getGlobal<void>(ctx, CallbackTable);
 	duk::dup(ctx, 2);
-	duk::putGlobal(ctx, "\xff""\xff""timer-" + std::to_string(reinterpret_cast<std::intptr_t>(timer.get())));
+	duk::putProperty(ctx, -2, hash);
+	duk::pop(ctx);
 
 	return 0;
 }
@@ -144,6 +202,7 @@
 	duk::putProperty(plugin.context(), -2, "prototype");
 	duk::putProperty(plugin.context(), -2, "Timer");
 	duk::pop(plugin.context());
+	duk::putGlobal(plugin.context(), CallbackTable, duk::Object{});
 }
 
 } // !irccd
--- a/lib/irccd/plugin-js.cpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/plugin-js.cpp	Wed May 18 13:24:50 2016 +0200
@@ -123,51 +123,6 @@
 {
 }
 
-JsPlugin::~JsPlugin()
-{
-	for (auto &timer : m_timers) {
-		timer->stop();
-	}
-}
-
-void JsPlugin::addTimer(std::shared_ptr<Timer> timer) noexcept
-{
-	std::weak_ptr<Timer> ptr(timer);
-
-	/*
-	 * These signals are called from the Timer thread and are transmitted to irccd so that it can
-	 * calls appropriate timer functions.
-	 */
-	timer->onSignal.connect([this, ptr] () {
-		auto timer = ptr.lock();
-
-		if (timer) {
-			onTimerSignal(move(timer));
-		}
-	});
-	timer->onEnd.connect([this, ptr] () {
-		auto timer = ptr.lock();
-
-		if (timer) {
-			onTimerEnd(move(timer));
-		}
-	});
-
-	m_timers.insert(move(timer));
-}
-
-void JsPlugin::removeTimer(const std::shared_ptr<Timer> &timer) noexcept
-{
-	duk::StackAssert sa(m_context);
-
-	/* Remove the JavaScript function */
-	duk::push(m_context, duk::Null{});
-	duk::putGlobal(m_context, "\xff""\xff""timer-" + std::to_string(reinterpret_cast<std::intptr_t>(timer.get())));
-
-	/* Remove from list */
-	m_timers.erase(timer);
-}
-
 void JsPlugin::onChannelMode(Irccd &,
 			     const std::shared_ptr<Server> &server,
 			     const std::string &origin,
@@ -450,11 +405,15 @@
 	call("onTopic", 4);
 }
 
-void JsPlugin::onUnload(Irccd &)
+void JsPlugin::onUnload(Irccd &irccd)
 {
 	duk::StackAssert sa(m_context);
 
 	call("onUnload");
+
+	for (const auto &module : irccd.moduleService().modules()) {
+		module->unload(irccd, *this);
+	}
 }
 
 void JsPlugin::onWhois(Irccd &, const std::shared_ptr<Server> &server, const ServerWhois &whois)
--- a/lib/irccd/plugin-js.hpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/plugin-js.hpp	Wed May 18 13:24:50 2016 +0200
@@ -24,8 +24,6 @@
  * \brief JavaScript plugins for irccd.
  */
 
-#include <unordered_set>
-
 #include "js.hpp"
 #include "path.hpp"
 #include "plugin.hpp"
@@ -34,49 +32,15 @@
 namespace irccd {
 
 class Module;
-class Timer;
-
-/**
- * \brief Timers that a plugin owns.
- */
-using PluginTimers = std::unordered_set<std::shared_ptr<Timer>>;
 
 /**
  * \brief JavaScript plugins for irccd.
  */
 class JsPlugin : public Plugin {
-public:
-	// TODO: remove with future modules
-
-	/**
-	 * Signal: onTimerSignal
-	 * ------------------------------------------------
-	 *
-	 * When a timer expires.
-	 *
-	 * Arguments:
-	 * - the timer object
-	 */
-	Signal<std::shared_ptr<Timer>> onTimerSignal;
-
-	/**
-	 * Signal: onTimerEnd
-	 * ------------------------------------------------
-	 *
-	 * When a timer is finished.
-	 *
-	 * Arguments:
-	 * - the timer object
-	 */
-	Signal<std::shared_ptr<Timer>> onTimerEnd;
-
 private:
 	// JavaScript context
 	duk::Context m_context;
 
-	// Plugin info and its timers
-	PluginTimers m_timers;
-
 	// Store loaded modules.
 	std::vector<std::shared_ptr<Module>> m_modules;
 
@@ -97,24 +61,10 @@
 	 */
 	JsPlugin(std::string name, std::string path, const PluginConfig &config = PluginConfig());
 
-	/**
-	 * Close timers.
-	 */
-	~JsPlugin();
-
-	/**
-	 * Add a timer to the plugin.
-	 *
-	 * \param timer the timer to add
-	 */
-	void addTimer(std::shared_ptr<Timer> timer) noexcept;
-
-	/**
-	 * Remove a timer from a plugin.
-	 *
-	 * \param timer
-	 */
-	void removeTimer(const std::shared_ptr<Timer> &timer) noexcept;
+	~JsPlugin()
+	{
+		puts("~JsPlugin");
+	}
 
 	/**
 	 * Access the Duktape context.
--- a/lib/irccd/plugin.hpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/plugin.hpp	Wed May 18 13:24:50 2016 +0200
@@ -49,7 +49,7 @@
  * A plugin is identified by name and can be loaded and unloaded
  * at runtime.
  */
-class Plugin {
+class Plugin : public std::enable_shared_from_this<Plugin> {
 private:
 	// Plugin information
 	std::string m_name;
@@ -185,11 +185,21 @@
 		m_version = std::move(version);
 	}
 
+	/**
+	 * Access the plugin configuration.
+	 *
+	 * \return the config
+	 */
 	inline const PluginConfig &config() const noexcept
 	{
 		return m_config;
 	}
 
+	/**
+	 * Overloaded function.
+	 *
+	 * \return the config
+	 */
 	inline PluginConfig &config() noexcept
 	{
 		return m_config;
--- a/lib/irccd/service-plugin.cpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/service-plugin.cpp	Wed May 18 13:24:50 2016 +0200
@@ -22,11 +22,9 @@
 
 #include <format.h>
 
-#include "config.hpp"
 #include "fs.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
-#include "plugin.hpp"
 #include "plugin-js.hpp"
 #include "service-plugin.hpp"
 
@@ -39,6 +37,13 @@
 {
 }
 
+PluginService::~PluginService()
+{
+	for (const auto &plugin : m_plugins) {
+		plugin->onUnload(m_irccd);
+	}
+}
+
 bool PluginService::has(const std::string &name) const noexcept
 {
 	return std::count_if(m_plugins.cbegin(), m_plugins.cend(), [&] (const auto &plugin) {
@@ -72,20 +77,7 @@
 
 void PluginService::add(std::shared_ptr<Plugin> plugin)
 {
-	using namespace std::placeholders;
-
-	// TODO: REMOVE WHEN WE GET THE JAVASCRIPT MODULES
-	std::shared_ptr<JsPlugin> jsp = std::dynamic_pointer_cast<JsPlugin>(plugin);
-
-	if (jsp) {
-		std::weak_ptr<JsPlugin> ptr(jsp);
-
-		jsp->onTimerSignal.connect(std::bind(&PluginService::handleTimerSignal, this, ptr, _1));
-		jsp->onTimerEnd.connect(std::bind(&PluginService::handleTimerEnd, this, ptr, _1));
-
-		// Store reference to irccd.
-		duk::putGlobal(jsp->context(), "\xff""\xff""irccd", duk::RawPointer<Irccd>{&m_irccd});
-	}
+	m_plugins.push_back(std::move(plugin));
 }
 
 std::shared_ptr<Plugin> PluginService::find(std::string name)
@@ -171,39 +163,4 @@
 	}
 }
 
-void PluginService::handleTimerSignal(std::weak_ptr<JsPlugin> ptr, std::shared_ptr<Timer> timer)
-{
-	m_irccd.post([this, ptr, timer] (Irccd &) {
-		auto plugin = ptr.lock();
-
-		if (!plugin) {
-			return;
-		}
-
-		auto &ctx = plugin->context();
-
-		duk::StackAssert sa(ctx);
-
-		// TODO: improve this
-		try {
-			duk::getGlobal<void>(ctx, "\xff""\xff""timer-" + std::to_string(reinterpret_cast<std::intptr_t>(timer.get())));
-			duk::pcall(ctx, 0);
-			duk::pop(ctx);
-		} catch (const std::exception &) {
-		}
-	});
-}
-
-void PluginService::handleTimerEnd(std::weak_ptr<JsPlugin> ptr, std::shared_ptr<Timer> timer)
-{
-	m_irccd.post([this, ptr, timer] (Irccd &) {
-		auto plugin = ptr.lock();
-
-		if (plugin) {
-			log::debug() << "timer: finished, removing from plugin `" << plugin->name() << "'" << std::endl;
-			plugin->removeTimer(timer);
-		}
-	});
-}
-
 } // !irccd
--- a/lib/irccd/service-plugin.hpp	Sun May 15 21:36:04 2016 +0200
+++ b/lib/irccd/service-plugin.hpp	Wed May 18 13:24:50 2016 +0200
@@ -43,10 +43,6 @@
 	std::vector<std::shared_ptr<Plugin>> m_plugins;
 	std::unordered_map<std::string, PluginConfig> m_config;
 
-	// TODO: get rid of this with future JavaScript modules.
-	void handleTimerSignal(std::weak_ptr<JsPlugin>, std::shared_ptr<Timer>);
-	void handleTimerEnd(std::weak_ptr<JsPlugin>, std::shared_ptr<Timer>);
-
 public:
 	/**
 	 * Create the plugin service.
@@ -56,6 +52,11 @@
 	PluginService(Irccd &irccd) noexcept;
 
 	/**
+	 * Destroy plugins.
+	 */
+	~PluginService();
+
+	/**
 	 * Get the list of plugins.
 	 *
 	 * \return the list of plugins