diff --git a/README.md b/README.md index 960c8a0e4ba1948a65f2f1a7460e006cf9c0b877..cfc11823567845f744063dceac51b5fc0338d9d1 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ A simple example implementation of background and cancellable (with timeout) tas Refer to example usage in the [test file](test-tasks.js) file. -Refer to implementation for the documentation in: -- [Task](task_thread.js) -- [WorkerTimeout](worker_timeout.js) +Refer to implementation a API documentation in: +- `Task` class: [API](doc/task_thread.md) [Sources](task_thread.js) +- `WorkerTimeout` class: [API][WorkerTimeout](doc/worker_timeout.md) [Sources](worker_timeout.js) Install @@ -21,25 +21,28 @@ Test Test with: node ./test-tasks.js - TEST: TEST6: ERROR (4): unknown error TEST: TEST7: SUCCESS: result: undefined - TEST: TEST5: ERROR (1): ReferenceError: test_bogus_function_result is not defined + TEST: TEST6: ERROR (4): terminated or unknown error TEST: TEST3: SUCCESS: result: 746450240 TEST: TEST4: SUCCESS: result: 2336000 - TEST: TEST2: TIMEOUT (2): timeout: after 1000 msecs + TEST: TEST5: ERROR (1): ReferenceError: test_bogus_function_result is not defined TEST: TEST1: TIMEOUT (2): timeout: after 1000 msecs + TEST: TEST2: ERROR (2): timeout + Or in sequential mode with the use of promise: + env AWAIT=1 ./test-tasks.js TEST: TEST1: TIMEOUT (2): timeout: after 1000 msecs - TEST: TEST2: TIMEOUT (2): timeout: after 1000 msecs + TEST: TEST2: ERROR (2): timeout TEST: TEST3: SUCCESS: result: 746450240 TEST: TEST4: SUCCESS: result: 2336000 TEST: TEST5: ERROR (1): ReferenceError: test_bogus_function_result is not defined - TEST: TEST6: ERROR (4): unknown error + TEST: TEST6: ERROR (4): terminated or unknown error TEST: TEST7: SUCCESS: result: undefined + Licence ------- -This is distributed under The Unlicence please refer to <https://unlicense.org> +This is distributed unlicensed, refer to The Unlicense: <https://opensource.org/license/unlicense> diff --git a/doc/task_thread.md b/doc/task_thread.md new file mode 100644 index 0000000000000000000000000000000000000000..2496821c310c809292685d90b0f05f8b22a9d5c1 --- /dev/null +++ b/doc/task_thread.md @@ -0,0 +1,168 @@ +<a name="module_task_thread"></a> + +## task\_thread +Implements an interruptible Task and Task utility functions +on top of worker threads with timeout (ref to `WorkerTimeout`). + + +* [task_thread](#module_task_thread) + * [~Task](#module_task_thread..Task) + * [new Task(taskname, funcname, funcargs, filename, [options], [nodeback])](#new_module_task_thread..Task_new) + * [.run()](#module_task_thread..Task+run) + * [.terminate([exit_code], [error])](#module_task_thread..Task+terminate) + * [~isMain](#module_task_thread..isMain) + * [~isTask](#module_task_thread..isTask) + * [~runTask()](#module_task_thread..runTask) ⇒ <code>Task</code> + * [~getTaskData()](#module_task_thread..getTaskData) + * [~postTaskResult()](#module_task_thread..postTaskResult) + +<a name="module_task_thread..Task"></a> + +### task_thread~Task +Task is an asynchroneous and interruptible task (termination or timeout). + +It calls back the given `nodeback(err, res)` with: + +- if `err` is not `null`, an error/terminate/timeout occured and + `err` contains: + - `.taskname`: the task name passed in the constructor + - `.exit_code`: a non 0 exit code, either the exit code from the task itself + if it caused an error or was exited with `process.exit(code)` with `code` + not 0, or 1 if it was terminated from the parent with the `terminate()` + method, or due to the timeout (ref to `.timeout` below) + - `.error`: the error string + - `.timeout`: a boolean specifying if the task was terminated due to + the timeout passed in the constructor +- otherwise, on succesful completion `res` contains: + - `.taskname`: the task name passed in the constructor + - `.result`: the task result as passed fron the task through `postTaskResult(result)` + or `undefined` if the task does not generate result + +Use it for instance as shown below: + + const nodeback = (err, res) => { + if (err && err.timeout) { + console.log(`Timeout`); + } else if (err) { + console.log(`Error (${err.exit_code}): ${err.error}`); + } else { + console.log(`Result:${err.result}`); + } + var task = Task("TASK1", "task1", [1, 2], "tasks.js", { timeout: 1000 }, nodeback) + task.run() + +Where for intance in `tasks.js` we have: + + ... + assert(isTask); + const data = getTaskData(); + if (data.funcname == "task1") { + const res = task1(...data.funcargs); + postTaskResult(res); + } else { + throw new Error(`Unexpeted task function name: ${data.funcname}`) + } + +**Kind**: inner class of [<code>task\_thread</code>](#module_task_thread) + +* [~Task](#module_task_thread..Task) + * [new Task(taskname, funcname, funcargs, filename, [options], [nodeback])](#new_module_task_thread..Task_new) + * [.run()](#module_task_thread..Task+run) + * [.terminate([exit_code], [error])](#module_task_thread..Task+terminate) + +<a name="new_module_task_thread..Task_new"></a> + +#### new Task(taskname, funcname, funcargs, filename, [options], [nodeback]) +Task construction, no execution happens at this point. + + +| Param | Type | Description | +| --- | --- | --- | +| taskname | <code>string</code> | name of the task for identification in the returned response/error | +| funcname | <code>string</code> | name of the function to call, it is up to the task filename to interpret this argument | +| funcargs | <code>Array</code> | arguments to pass to the funciton (or `[]` or `undefined`), it is up to the task filename to interpret this argument | +| filename | <code>string</code> | file to execute for the task (this file must get the `funcname`/`funcargs` from the `getTaskData()` object, execute the request and optionally return a result with `postTaskResult(result)` | +| [options] | <code>object</code> | passed to the underlying `WorkerTimeout` object, for instance: `{ timeout: 1000 }` for setting a task termination timeout of 1 sec | +| [nodeback] | <code>callback</code> | the user callback in nodeback style, i.e. called as `nodeback(err, res)`, see the class usage above for details | + +<a name="module_task_thread..Task+run"></a> + +#### task.run() +Run the Task + +After calling this functionm the task should be considered running in a backgrounbd thread. +The user callback may be called as soon as the event loop is available. +A running task can be interrupted from the launcher either due to: + +- the `timeout` defined in the constructors `options` +- a call to `terminate()` on this object + +Otherwise, the task itself may terminate from the executed `filename` either due to: + +- a succesful end of the execution +- an error while executing +- an explicit call to `process.exit(code)` + +**Kind**: instance method of [<code>Task</code>](#module_task_thread..Task) +<a name="module_task_thread..Task+terminate"></a> + +#### task.terminate([exit_code], [error]) +Terminate the task + +Terminates a running task with optional exit code and error message. + +**Kind**: instance method of [<code>Task</code>](#module_task_thread..Task) + +| Param | Type | Description | +| --- | --- | --- | +| [exit_code] | <code>number</code> | optional exit code to pass to the task `err`, or 1 if undefined. When 0 the task will be seen as succesful from the parent | +| [error] | <code>string</code> | optional error string if `exit_code` is not 0 | + +<a name="module_task_thread..isMain"></a> + +### task_thread~isMain +True only in the main thread (i.e. not in a Task). + +**Kind**: inner constant of [<code>task\_thread</code>](#module_task_thread) +<a name="module_task_thread..isTask"></a> + +### task_thread~isTask +True only in a task thread (i.e. not in the main thread). + +**Kind**: inner constant of [<code>task\_thread</code>](#module_task_thread) +<a name="module_task_thread..runTask"></a> + +### task_thread~runTask() ⇒ <code>Task</code> +Create a task and call its `run()` method. +This function is actually a shortcut for: + + var task = Task(...taskArgs); + task.run(); + +Optionally one may promisify this function and run a task as follow: + + const util = require("util"); + const resolved = (res) => { console.log(`Result: ${err.result}`); }; + const rejected = (err) => { console.log(`Error: ${err.error}`); }; + const promise = util.promisify(runTask); + await promise(...taskArgs).then(resolved, rejected); + +**Kind**: inner method of [<code>task\_thread</code>](#module_task_thread) +**Returns**: <code>Task</code> - the created task +<a name="module_task_thread..getTaskData"></a> + +### task_thread~getTaskData() +Gets the task input data which contains at least: + +- `.taskname`: the task name as passed to the `Task` constructor +- `.funcname`: the function name as passed to the `Task` constructor +- `.funcargs`: the funciton args as passed to the `Task` constructor + +**Kind**: inner method of [<code>task\_thread</code>](#module_task_thread) +<a name="module_task_thread..postTaskResult"></a> + +### task_thread~postTaskResult() +Returns the task result to the parent, can be anything including undefined +if no result is generated. Refer to the class documentation above for details. + +**Kind**: inner method of [<code>task\_thread</code>](#module_task_thread) diff --git a/doc/worker_timeout.md b/doc/worker_timeout.md new file mode 100644 index 0000000000000000000000000000000000000000..9602746a4f7b53d02c5894e4a708154538e1a2a6 --- /dev/null +++ b/doc/worker_timeout.md @@ -0,0 +1,62 @@ +<a name="module_worker_timeout"></a> + +## worker\_timeout +Implements timeout functionality on top of `Worker` class. + +Ref to Worker doc at <https://nodejs.org/api/worker_threads.html#worker-threads> + + +* [worker_timeout](#module_worker_timeout) + * [~WorkerTimeout](#module_worker_timeout..WorkerTimeout) + * [new WorkerTimeout(filename, [object])](#new_module_worker_timeout..WorkerTimeout_new) + * [.is_timeout()](#module_worker_timeout..WorkerTimeout+is_timeout) ⇒ <code>boolean</code> + * [.on(type, callback)](#module_worker_timeout..WorkerTimeout+on) + +<a name="module_worker_timeout..WorkerTimeout"></a> + +### worker_timeout~WorkerTimeout +WorkerTimeout is a Worker with an optional timeout option. + If timeout is specified and > 0, the worker will be terminated + and the on "exit" callback received a code of 2. + +**Kind**: inner class of [<code>worker\_timeout</code>](#module_worker_timeout) + +* [~WorkerTimeout](#module_worker_timeout..WorkerTimeout) + * [new WorkerTimeout(filename, [object])](#new_module_worker_timeout..WorkerTimeout_new) + * [.is_timeout()](#module_worker_timeout..WorkerTimeout+is_timeout) ⇒ <code>boolean</code> + * [.on(type, callback)](#module_worker_timeout..WorkerTimeout+on) + +<a name="new_module_worker_timeout..WorkerTimeout_new"></a> + +#### new WorkerTimeout(filename, [object]) +Same constructor as `Worker` + + +| Param | Type | Description | +| --- | --- | --- | +| filename | <code>string</code> | the file to load and execute | +| [object] | | options passed to the worker, plus an optional `timeout` option specified in msecs | + +<a name="module_worker_timeout..WorkerTimeout+is_timeout"></a> + +#### workerTimeout.is\_timeout() ⇒ <code>boolean</code> +Returns whether the timeout caused the termination. + +**Kind**: instance method of [<code>WorkerTimeout</code>](#module_worker_timeout..WorkerTimeout) +**Returns**: <code>boolean</code> - true if termnated on timeout +<a name="module_worker_timeout..WorkerTimeout+on"></a> + +#### workerTimeout.on(type, callback) +Override exit callback in order to return code 2 for timeout +instead of 1 for a bare `terminate()`. +Note that one should use `is_timeout()` for testing a timeout +condition as a code 2 may be returned by the worker itself on +a call to `process.exit(2)` from the worker thread. + +**Kind**: instance method of [<code>WorkerTimeout</code>](#module_worker_timeout..WorkerTimeout) + +| Param | Type | Description | +| --- | --- | --- | +| type | <code>str</code> | event type | +| callback | <code>callback</code> | user function to call back | +