Adding comments and allowing base-services to be skipped

This commit is contained in:
Martin Karkowski 2022-01-20 12:59:32 +01:00
parent cf5aec61c0
commit b99c1edc6c
4 changed files with 394 additions and 58 deletions

View File

@ -3,8 +3,219 @@
* @author Martin Karkowski * @author Martin Karkowski
* @email m.karkowski@zema.de * @email m.karkowski@zema.de
* *
* # NopeConnectivityManager * # NoPE - Connectivity Manager
* *
* 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.
*
* ## Master
*
* Defaultly a `ConnectivityManager` is elected as `master`. The master is defined as the `ConnectivityManager` with the highest `upTime`.
*
* > Alternativly a master can be forced.
*
* ## Synchronizing time
*
* 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`))
*
*
*
* ```javascript
* // First lets install nope using npm
* const nope = require("../dist-nodejs/index.nodejs")
*
* // Create a communicator:
* // We will use the event layer (which just runs internally)
* const communicator = nope.getLayer("event");
*
* // Lets create our dispatcher
*
* // 1. Dispatcher simulates our local system
* const localDispatcher = nope.dispatcher.getDispatcher({
* communicator,
* id: "local"
* }, {
* singleton: false,
* useBaseServices: false
* });
* ```
*
* > For Jupyter we need an extra async wrapper to wait for initalizing the dispatcher:
*
* see here for the details in Jupyter: https://n-riesco.github.io/ijavascript/doc/async.ipynb.html
*
*
* ```javascript
* $$.async();
* // Lets wait for our element to be ready.
* localDispatcher.ready.waitFor().then($$.done);
* ```
*
* Now we want to listen to newly connected dispatchers. For this purpose, we create an observer, which will listen to changes.
*
*
* ```javascript
* // Subscribe to changes
* const observer = localDispatcher.connectivityManager.dispatchers.onChange.subscribe(data => {
* // Log the changes
* console.log((new Date()).toISOString(),"onChange - listener");
* console.log("\tadded =", data.added);
* console.log("\tremoved =", data.removed);
* });
* ```
*
* Additionally we want to show the currently connected dispatchers. In this data the own dispatcher will **allways** be included:
*
*
* ```javascript
* // Show our connected Dispatchers
* let connectedDispatchers = localDispatcher.connectivityManager.dispatchers.data.getContent();
* let localDispatcherIncluded = connectedDispatchers.includes(localDispatcher.id);
*
* // Now lets log our results.
* console.log("connectedDispatchers =", connectedDispatchers);
* console.log("localDispatcherIncluded =", localDispatcherIncluded);
* ```
*
* >```
* > connectedDispatchers = [ 'local' ]
* > localDispatcherIncluded = true
* >```
*
* 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
*
*
* ```javascript
* // 2. Dispatcher simulates our remote system
* const remoteDispatcher = nope.dispatcher.getDispatcher({
* communicator,
* id: "remote"
* }, {
* singleton: false,
* useBaseServices: false
* });
*
* ```
*
* >```
* > 2022-01-20T11:39:55.766Z onChange - listener
* > added = [ 'remote' ]
* > removed = []
* >```
*
* Now we want to see, which system is the current master. This should be our `local`.
*
*
* ```javascript
* // We expect to be the master, because the localDispatcher has been created first.
* console.log("master =", localDispatcher.connectivityManager.master.id);
* ```
*
* > `master = local`
*
*
* 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)
*
*
* ```javascript
* $$.async();
*
* remoteDispatcher.connectivityManager.isMaster = true;
* localDispatcher.connectivityManager.isMaster = false;
*
* // Our messaging is async ==> we wait an amount of time
* setTimeout(() => $$.done(),1000);
* ```
*
*
* ```javascript
* // We expect the master to be the remote.
* console.log("master =", localDispatcher.connectivityManager.master.id);
* console.log("master-info =", localDispatcher.connectivityManager.master);
* ```
*
* >```
* > master = remote
* > master-info = {
* > id: 'remote',
* > env: 'javascript',
* > version: '1.0.0',
* > isMaster: true,
* > host: {
* > cores: 8,
* > cpu: {
* > model: 'Intel(R) Core(TM) i7-8565U CPU',
* > speed: 1992,
* > usage: 0.0038778477944740875
* > },
* > os: 'win32',
* > ram: { usedPerc: 0.362681220626356, free: 20676, total: 32442 },
* > name: 'nz-078'
* > },
* > pid: 18068,
* > timestamp: 1642678798813,
* > upTime: 3049,
* > status: 0
* > }
* >```
*
*
* Now lets see what happens if we adapt the heartbeat intervall of our *local* instance. We want to receive every 50 ms a heartbeat:
*
*
* ```javascript
* $$.async()
*
* const renderStatus = () => {
* console.log((new Date()).toISOString(),"master-info =", localDispatcher.connectivityManager.master.status)
* }
*
* setTimeout(renderStatus, 50);
* setTimeout(renderStatus, 750);
* setTimeout(renderStatus, 1500);
* setTimeout(renderStatus, 2500);
*
*
* localDispatcher.connectivityManager.setTimings({
* // our system will send every 50 ms an heartbeat.
* sendAliveInterval: 250,
* // we will check that after
* checkInterval: 125,
* // will mark dispatchers as slow after not receiving heartbeats for 50ms
* slow: 500,
* // we will mark dispatchers with a warning flag after 50 ms
* warn: 1000,
* // we mark it as dead after 0.5 s
* dead: 2000,
* // We will remove the dispatcher after 1 s
* remove: 3000,
* });
*
* remoteDispatcher.connectivityManager.setTimings({
* // our system will send every 50 ms an heartbeat.
* sendAliveInterval: 5000,
* });
*
*
*
* // We reset the timeouts.
* setTimeout(() => localDispatcher.connectivityManager.setTimings({}), 3000);
* setTimeout(() => remoteDispatcher.connectivityManager.setTimings({}), 3000);
* setTimeout(() => $$.done(), 5000);
*
* ```
*
* >```
* > 2022-01-20T11:40:01.089Z master-info = 0
* > 2022-01-20T11:40:01.789Z master-info = 1
* > 2022-01-20T11:40:02.536Z master-info = 2
* > 2022-01-20T11:40:03.543Z master-info = 3
* > 2022-01-20T11:40:03.977Z onChange - listener
* > added = []
* > removed = [ 'remote' ]
* > 2022-01-20T11:40:04.547Z onChange - listener
* > added = [ 'remote' ]
* > removed = []
* >```
* *
*/ */

View File

@ -19,24 +19,39 @@ import { NopeDispatcher } from "./nopeDispatcher";
/** /**
* Function to extract a Singleton Dispatcher * Function to extract a Singleton Dispatcher
* @param options The provided options for the Dispatcher * @param dispatcherOptions The provided options for the Dispatcher
* *
* ```typescript * ```typescript
* *
* ``` * ```
*/ */
export function getDispatcher( export function getDispatcher(
options: INopeDispatcherOptions, dispatcherOptions: INopeDispatcherOptions,
constructorClass: IDispatcherConstructor = null, options: {
singleton = true constructorClass?: IDispatcherConstructor;
singleton?: boolean;
useBaseServices?: boolean;
} = {}
): INopeDispatcher { ): INopeDispatcher {
if (constructorClass === null || constructorClass === undefined) { if (
constructorClass = NopeDispatcher; options.constructorClass === null ||
options.constructorClass === undefined
) {
options.constructorClass = NopeDispatcher;
} }
options = Object.assign(
{
constructorClass: null,
singleton: true,
useBaseServices: true,
},
options
);
const create = () => { const create = () => {
const dispatcher = new constructorClass( const dispatcher = new options.constructorClass(
options, dispatcherOptions,
() => new NopeObservable() () => new NopeObservable()
); );
@ -52,13 +67,15 @@ export function getDispatcher(
} }
); );
if (options.useBaseServices) {
addAllBaseServices(dispatcher); addAllBaseServices(dispatcher);
}
// Return the Dispathcer // Return the Dispathcer
return dispatcher as INopeDispatcher; return dispatcher as INopeDispatcher;
}; };
if (singleton) { if (options.singleton) {
// Create a singaleton if required. // Create a singaleton if required.
// use the container to receive the // use the container to receive the
// singleton object // singleton object

View File

@ -17,7 +17,7 @@ import {
DISPATCHER_INSTANCE, DISPATCHER_INSTANCE,
DISPATCHER_OPTIONS, DISPATCHER_OPTIONS,
OBSERVABLE_FACTORY, OBSERVABLE_FACTORY,
OBSERVABLE_INSTANCE, OBSERVABLE_INSTANCE
} from "../symbols/identifiers"; } from "../symbols/identifiers";
import { INopeDispatcherOptions } from "../types/nope/nopeDispatcher.interface"; import { INopeDispatcherOptions } from "../types/nope/nopeDispatcher.interface";
import { IPackageDescription } from "../types/nope/nopePackage.interface"; import { IPackageDescription } from "../types/nope/nopePackage.interface";
@ -58,7 +58,9 @@ export function generateNopeBasicPackage(
selector: TYPES.dispatcher, selector: TYPES.dispatcher,
// We want to provide in this Situation allways the same dispatcher. // We want to provide in this Situation allways the same dispatcher.
// type: !singleton ? InjectableNopeDispatcher : getDispatcher(options, null, singleton), // type: !singleton ? InjectableNopeDispatcher : getDispatcher(options, null, singleton),
type: getDispatcher(options, null, singleton), type: getDispatcher(options, {
singleton
}),
options: { options: {
// Shouldn't be required: // Shouldn't be required:
// scope: singleton ? "inSingletonScope" : undefined, // scope: singleton ? "inSingletonScope" : undefined,

View File

@ -21,7 +21,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 1,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -38,7 +38,10 @@
"const localDispatcher = nope.dispatcher.getDispatcher({\n", "const localDispatcher = nope.dispatcher.getDispatcher({\n",
" communicator,\n", " communicator,\n",
" id: \"local\"\n", " id: \"local\"\n",
"}, null, false);" "}, {\n",
" singleton: false,\n",
" useBaseServices: false\n",
"});"
] ]
}, },
{ {
@ -52,9 +55,20 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 2,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"$$.async();\n", "$$.async();\n",
"// Lets wait for our element to be ready.\n", "// Lets wait for our element to be ready.\n",
@ -70,17 +84,16 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 3,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"// Subscribe to changes\n", "// Subscribe to changes\n",
"const observer = localDispatcher.connectivityManager.dispatchers.onChange.subscribe(data => {\n", "const observer = localDispatcher.connectivityManager.dispatchers.onChange.subscribe(data => {\n",
" // Log the changes\n", " // Log the changes\n",
" console.log(\"onChange - listener\");\n", " console.log((new Date()).toISOString(),\"onChange - listener\");\n",
" console.log(\"-------------------\");\n", " console.log(\"\\tadded =\", data.added);\n",
" console.log(\"added =\", data.added);\n", " console.log(\"\\tremoved =\", data.removed);\n",
" console.log(\"removed =\", data.removed);\n",
"});" "});"
] ]
}, },
@ -93,9 +106,18 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 4,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"connectedDispatchers = [ 'local' ]\n",
"localDispatcherIncluded = true\n"
]
}
],
"source": [ "source": [
"// Show our connected Dispatchers\n", "// Show our connected Dispatchers\n",
"let connectedDispatchers = localDispatcher.connectivityManager.dispatchers.data.getContent();\n", "let connectedDispatchers = localDispatcher.connectivityManager.dispatchers.data.getContent();\n",
@ -115,15 +137,28 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 5,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2022-01-20T11:39:55.766Z onChange - listener\n",
"\tadded = [ 'remote' ]\n",
"\tremoved = []\n"
]
}
],
"source": [ "source": [
"// 2. Dispatcher simulates our remote system\n", "// 2. Dispatcher simulates our remote system\n",
"const remoteDispatcher = nope.dispatcher.getDispatcher({\n", "const remoteDispatcher = nope.dispatcher.getDispatcher({\n",
" communicator,\n", " communicator,\n",
" id: \"remote\"\n", " id: \"remote\"\n",
"}, null, false);\n" "}, {\n",
" singleton: false,\n",
" useBaseServices: false\n",
"});\n"
] ]
}, },
{ {
@ -135,9 +170,17 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 6,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"master = local\n"
]
}
],
"source": [ "source": [
"// We expect to be the master, because the localDispatcher has been created first.\n", "// We expect to be the master, because the localDispatcher has been created first.\n",
"console.log(\"master =\", localDispatcher.connectivityManager.master.id);" "console.log(\"master =\", localDispatcher.connectivityManager.master.id);"
@ -152,7 +195,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 7,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -162,14 +205,43 @@
"localDispatcher.connectivityManager.isMaster = false;\n", "localDispatcher.connectivityManager.isMaster = false;\n",
"\n", "\n",
"// Our messaging is async ==> we wait an amount of time\n", "// Our messaging is async ==> we wait an amount of time\n",
"setTimeout($$.done,1000)" "setTimeout(() => $$.done(),1000);"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 8,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"master = remote\n",
"master-info = {\n",
" id: 'remote',\n",
" env: 'javascript',\n",
" version: '1.0.0',\n",
" isMaster: true,\n",
" host: {\n",
" cores: 8,\n",
" cpu: {\n",
" model: 'Intel(R) Core(TM) i7-8565U CPU',\n",
" speed: 1992,\n",
" usage: 0.0038778477944740875\n",
" },\n",
" os: 'win32',\n",
" ram: { usedPerc: 0.362681220626356, free: 20676, total: 32442 },\n",
" name: 'nz-078'\n",
" },\n",
" pid: 18068,\n",
" timestamp: 1642678798813,\n",
" upTime: 3049,\n",
" status: 0\n",
"}\n"
]
}
],
"source": [ "source": [
"// We expect the master to be the remote.\n", "// We expect the master to be the remote.\n",
"console.log(\"master =\", localDispatcher.connectivityManager.master.id);\n", "console.log(\"master =\", localDispatcher.connectivityManager.master.id);\n",
@ -185,39 +257,73 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 9,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2022-01-20T11:40:01.089Z master-info = 0\n",
"2022-01-20T11:40:01.789Z master-info = 1\n",
"2022-01-20T11:40:02.536Z master-info = 2\n",
"2022-01-20T11:40:03.543Z master-info = 3\n",
"2022-01-20T11:40:03.977Z onChange - listener\n",
"\tadded = []\n",
"\tremoved = [ 'remote' ]\n",
"2022-01-20T11:40:04.547Z onChange - listener\n",
"\tadded = [ 'remote' ]\n",
"\tremoved = []\n"
]
}
],
"source": [ "source": [
"$$.async()\n", "$$.async()\n",
"\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", "const renderStatus = () => {\n",
" console.log(\"master-info =\", localDispatcher.connectivityManager.master.status)\n", " console.log((new Date()).toISOString(),\"master-info =\", localDispatcher.connectivityManager.master.status)\n",
"}\n", "}\n",
"\n", "\n",
"setTimeout(renderStatus, 75);\n", "setTimeout(renderStatus, 50);\n",
"setTimeout(renderStatus, 250);\n",
"setTimeout(renderStatus, 750);\n", "setTimeout(renderStatus, 750);\n",
"setTimeout(renderStatus, 1500);\n",
"setTimeout(renderStatus, 2500);\n",
"\n",
"\n",
"localDispatcher.connectivityManager.setTimings({\n",
" // our system will send every 50 ms an heartbeat.\n",
" sendAliveInterval: 250,\n",
" // we will check that after\n",
" checkInterval: 125,\n",
" // will mark dispatchers as slow after not receiving heartbeats for 50ms\n",
" slow: 500,\n",
" // we will mark dispatchers with a warning flag after 50 ms\n",
" warn: 1000,\n",
" // we mark it as dead after 0.5 s\n",
" dead: 2000,\n",
" // We will remove the dispatcher after 1 s\n",
" remove: 3000,\n",
"});\n",
"\n",
"remoteDispatcher.connectivityManager.setTimings({\n",
" // our system will send every 50 ms an heartbeat.\n",
" sendAliveInterval: 5000,\n",
"});\n",
"\n",
"\n",
"\n", "\n",
"// We reset the timeouts.\n", "// We reset the timeouts.\n",
"setTimeout(() => localDispatcher.connectivityManager.setTimings({}), 1200);\n", "setTimeout(() => localDispatcher.connectivityManager.setTimings({}), 3000);\n",
"setTimeout(() => $$.done(), 2000);\n" "setTimeout(() => remoteDispatcher.connectivityManager.setTimings({}), 3000);\n",
"setTimeout(() => $$.done(), 5000);\n"
] ]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
} }
], ],
"metadata": { "metadata": {