diff --git a/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts b/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts index 7970f78..a758860 100644 --- a/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts +++ b/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts @@ -263,6 +263,8 @@ export class NopeConnectivityManager implements INopeConnectivityManager { */ public set isMaster(value: boolean) { this.__isMaster = value; + // We want to forward our new status. + this._sendStatus(); } /** @@ -306,7 +308,7 @@ export class NopeConnectivityManager implements INopeConnectivityManager { if (masters.length === 0) { throw Error("No Master has been found !"); } else if (masters.length > 1) { - throw Error("Multiple Masters has been found!"); + throw Error("Multiple Masters has been found!" + JSON.stringify(masters)); } return masters[0]; @@ -375,6 +377,7 @@ export class NopeConnectivityManager implements INopeConnectivityManager { } await this.emitBonjour(); + await this._sendStatus(); this.ready.setContent(true); } @@ -473,7 +476,10 @@ export class NopeConnectivityManager implements INopeConnectivityManager { * @memberof NopeConnectivityManager */ protected _sendStatus(): void { - this._communicator.emit("StatusChanged", this.info); + // Test if we are connected + if (this._communicator.connected.getContent()) { + this._communicator.emit("StatusChanged", this.info); + } } /** diff --git a/wiki/12-Dispatcher.ipynb b/wiki/12-Dispatcher.ipynb index f86f538..4cce7a0 100644 --- a/wiki/12-Dispatcher.ipynb +++ b/wiki/12-Dispatcher.ipynb @@ -25,30 +25,9 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "SyntaxError", - "evalue": "Identifier 'nope' has already been declared", - "output_type": "error", - "traceback": [ - "evalmachine.:1", - "// First lets install nope using npm", - "^", - "", - "SyntaxError: Identifier 'nope' has already been declared", - " at Script.runInThisContext (node:vm:129:12)", - " at Object.runInThisContext (node:vm:305:38)", - " at run ([eval]:1054:15)", - " at onRunRequest ([eval]:888:18)", - " at onMessage ([eval]:848:13)", - " at process.emit (node:events:390:28)", - " at emit (node:internal/child_process:915:12)", - " at processTicksAndRejections (node:internal/process/task_queues:84:21)" - ] - } - ], + "outputs": [], "source": [ "// First lets install nope using npm\n", "const nope = require(\"../dist-nodejs/index.nodejs\")\n", @@ -88,6 +67,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "### `getDispatcher`-function:\n", + "The `getDispatcher`- function automatically trys to return the dispatcher as `Singleton`. This means, that their will be exactly ***1*** dispatcher in a process. To receive a second dispatcher-instance (which is for performance reasons not recommend) in your process you must set the ``singleton``-flag to `false`\n", + "\n", + "\n", "## Settings for creating:\n", "\n", "The relevant Settings are described by the `INopeDispatcherOptions`. This options allows to define:\n", diff --git a/wiki/13-ConnectivityManager.ipynb b/wiki/13-ConnectivityManager.ipynb new file mode 100644 index 0000000..df51bc8 --- /dev/null +++ b/wiki/13-ConnectivityManager.ipynb @@ -0,0 +1,238 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NoPE - Connectivity Manager\n", + "\n", + "The NoPE-Dispatcher uses one `ConnectivityManager`. The manager observes the connection and remotly connected dispatchers (and their `ConnectivityManager`). The Manager detects newly connected dispatchers and disconnected dispatchers. Additionally, it sends a StatusMessage (in the form of `INopeStatusInfo`). This status message is interpreted as heartbeat. The `ConnectivityManager` checks those heartbeats with a defined interval. If a specific amount of time is ellapsed, the remote dispatcher is marked as `slow` -> `warning` -> `dead`. After an additional delay in the state `dead` the dispatcher is altough removed.\n", + "\n", + "## Master\n", + "\n", + "Defaultly a `ConnectivityManager` is elected as `master`. The master is defined as the `ConnectivityManager` with the highest `upTime`. \n", + "\n", + "> Alternativly a master can be forced.\n", + "\n", + "## Synchronizing time\n", + "\n", + "Because we asume, that **NoPE** is running on different computing nodes, we have to be able to synchronize the time between those elements. Therefore the `ConnectivityManager` is able to sync the time (by providing a `timestamp` and an additional `delay` that was needed to get to the call (for instance `ping / 2`))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// First lets install nope using npm\n", + "const nope = require(\"../dist-nodejs/index.nodejs\")\n", + "\n", + "// Create a communicator:\n", + "// We will use the event layer (which just runs internally)\n", + "const communicator = nope.getLayer(\"event\");\n", + "\n", + "// Lets create our dispatcher\n", + "\n", + "// 1. Dispatcher simulates our local system\n", + "const localDispatcher = nope.dispatcher.getDispatcher({\n", + " communicator,\n", + " id: \"local\"\n", + "}, null, false);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> For Jupyter we need an extra async wrapper to wait for initalizing the dispatcher:\n", + "\n", + "see here for the details in Jupyter: https://n-riesco.github.io/ijavascript/doc/async.ipynb.html" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "$$.async();\n", + "// Lets wait for our element to be ready.\n", + "localDispatcher.ready.waitFor().then($$.done);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we want to listen to newly connected dispatchers. For this purpose, we create an observer, which will listen to changes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// Subscribe to changes\n", + "const observer = localDispatcher.connectivityManager.dispatchers.onChange.subscribe(data => {\n", + " // Log the changes\n", + " console.log(\"onChange - listener\");\n", + " console.log(\"-------------------\");\n", + " console.log(\"added =\", data.added);\n", + " console.log(\"removed =\", data.removed);\n", + "});" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Additionally we want to show the currently connected dispatchers. In this data the own dispatcher will **allways** be included:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// Show our connected Dispatchers\n", + "let connectedDispatchers = localDispatcher.connectivityManager.dispatchers.data.getContent();\n", + "let localDispatcherIncluded = connectedDispatchers.includes(localDispatcher.id);\n", + "\n", + "// Now lets log our results.\n", + "console.log(\"connectedDispatchers =\", connectedDispatchers);\n", + "console.log(\"localDispatcherIncluded =\", localDispatcherIncluded);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have implemented our listeners and have seen the connected dispatchers (which is only the `\"local\"`-dispatchre), We will add an additional dispatcher. This should result in calling our `onChange`-listener. Additionally, we wait until our `remoteDispatcher` is initalized" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// 2. Dispatcher simulates our remote system\n", + "const remoteDispatcher = nope.dispatcher.getDispatcher({\n", + " communicator,\n", + " id: \"remote\"\n", + "}, null, false);\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we want to see, which system is the current master. This should be our `local`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// We expect to be the master, because the localDispatcher has been created first.\n", + "console.log(\"master =\", localDispatcher.connectivityManager.master.id);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now force the remote dispatcher to be our master, by setting the master. (For this purpose we can later use a base service ==> then we just have to call the service) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "$$.async();\n", + "\n", + "remoteDispatcher.connectivityManager.isMaster = true;\n", + "localDispatcher.connectivityManager.isMaster = false;\n", + "\n", + "// Our messaging is async ==> we wait an amount of time\n", + "setTimeout($$.done,1000)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// We expect the master to be the remote.\n", + "console.log(\"master =\", localDispatcher.connectivityManager.master.id);\n", + "console.log(\"master-info =\", localDispatcher.connectivityManager.master);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now lets see what happens if we adapt the heartbeat intervall of our *local* instance. We want to receive every 50 ms a heartbeat:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "$$.async()\n", + "\n", + "localDispatcher.connectivityManager.setTimings({\n", + " // our system will send every 50 ms an heartbeat.\n", + " sendAliveInterval: 500,\n", + " // we will check that after\n", + " checkInterval: 25,\n", + " // will mark dispatchers as slow after not receiving heartbeats for 50ms\n", + " slow: 50,\n", + " // we will mark dispatchers with a warning flag after 50 ms\n", + " warn: 100,\n", + " // we mark it as dead after 0.5 s\n", + " dead: 500,\n", + " // We will remove the dispatcher after 1 s\n", + " remove: 1000,\n", + "});\n", + "\n", + "const renderStatus = () => {\n", + " console.log(\"master-info =\", localDispatcher.connectivityManager.master.status)\n", + "}\n", + "\n", + "setTimeout(renderStatus, 75);\n", + "setTimeout(renderStatus, 250);\n", + "setTimeout(renderStatus, 750);\n", + "\n", + "// We reset the timeouts.\n", + "setTimeout(() => localDispatcher.connectivityManager.setTimings({}), 1200);\n", + "setTimeout(() => $$.done(), 2000);\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "JavaScript (Node.js)", + "language": "javascript", + "name": "javascript" + }, + "language_info": { + "file_extension": ".js", + "mimetype": "application/javascript", + "name": "javascript", + "version": "17.3.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}