xcraft-core-utils 3.1.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -2
- package/lib/.babelrc +8 -0
- package/lib/arrayCollector.js +4 -3
- package/lib/async.js +34 -0
- package/lib/batch.js +1 -1
- package/lib/crypto.js +1 -1
- package/lib/cursorPump.js +2 -0
- package/lib/eventDebouncer.js +7 -6
- package/lib/files.js +1 -0
- package/lib/job-queue.js +59 -16
- package/lib/log.js +6 -5
- package/lib/runnerInstance.js +77 -23
- package/lib/whereIs.js +13 -0
- package/package.json +10 -8
- package/lib/persistant-job-queue.js +0 -144
- package/lib/sqlite.js +0 -141
package/index.js
CHANGED
|
@@ -16,10 +16,9 @@ exports.propTypes = require('./lib/prop-types.js');
|
|
|
16
16
|
exports.reflect = require('./lib/reflect.js');
|
|
17
17
|
exports.regex = require('./lib/regex.js');
|
|
18
18
|
exports.string = require('./lib/string.js');
|
|
19
|
+
exports.whereIs = require('./lib/whereIs.js');
|
|
19
20
|
exports.yaml = require('./lib/yaml.js');
|
|
20
21
|
|
|
21
|
-
exports.SQLite = require('./lib/sqlite.js');
|
|
22
22
|
exports.RankedCache = require('./lib/ranked-cache.js');
|
|
23
23
|
exports.JobQueue = require('./lib/job-queue.js');
|
|
24
|
-
exports.PersistantJobQueue = require('./lib/persistant-job-queue.js');
|
|
25
24
|
exports.CursorPump = require('./lib/cursorPump.js');
|
package/lib/.babelrc
ADDED
package/lib/arrayCollector.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
const {throttle} = require('lodash');
|
|
2
4
|
const watt = require('gigawatts');
|
|
3
5
|
|
|
4
6
|
class ArrayCollector {
|
|
5
|
-
constructor(wait = 20, onCollect, leading = true) {
|
|
7
|
+
constructor(resp, wait = 20, onCollect, leading = true) {
|
|
6
8
|
this.onCollect = watt(onCollect);
|
|
7
9
|
this.entries = {};
|
|
8
|
-
|
|
9
|
-
this.resp = busClient.newResponse('collector', 'token');
|
|
10
|
+
this.resp = resp;
|
|
10
11
|
this.release = throttle(this._release, wait, {leading});
|
|
11
12
|
}
|
|
12
13
|
|
package/lib/async.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const watt = require('gigawatts');
|
|
4
|
+
|
|
5
|
+
class Async {
|
|
6
|
+
constructor() {
|
|
7
|
+
watt.wrapAll(this);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reduce an array to a map with an async iteration.
|
|
12
|
+
*
|
|
13
|
+
* It's useful in the case where the main event loop must not
|
|
14
|
+
* be blocked and the array is very large. The purpose of this
|
|
15
|
+
* helper is only for task which be done with an usual sync
|
|
16
|
+
* map reduce.
|
|
17
|
+
*
|
|
18
|
+
* @param {*} keyFunc - Key for the item in the map.
|
|
19
|
+
* @param {*} valueFunc - Value for the item in the map.
|
|
20
|
+
* @param {*} list - The input array.
|
|
21
|
+
* @returns {Object} the map.
|
|
22
|
+
*/
|
|
23
|
+
*mapReduce(keyFunc, valueFunc, list) {
|
|
24
|
+
const state = {};
|
|
25
|
+
for (const item of list) {
|
|
26
|
+
state[keyFunc(item)] = yield new Promise((resolve) =>
|
|
27
|
+
resolve(valueFunc(item))
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return state;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = new Async();
|
package/lib/batch.js
CHANGED
|
@@ -9,7 +9,7 @@ exports.run = function (filter, location, callbackAction) {
|
|
|
9
9
|
|
|
10
10
|
files.forEach(function (file) {
|
|
11
11
|
var fullPath = path.join(location, file);
|
|
12
|
-
var st = fs.
|
|
12
|
+
var st = fs.lstatSync(fullPath);
|
|
13
13
|
|
|
14
14
|
if (st.isDirectory()) {
|
|
15
15
|
exports.run(filter, fullPath, callbackAction);
|
package/lib/crypto.js
CHANGED
package/lib/cursorPump.js
CHANGED
package/lib/eventDebouncer.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
const {debounce} = require('lodash');
|
|
2
4
|
|
|
3
5
|
class EventDebouncer {
|
|
4
|
-
constructor(wait = 1000) {
|
|
6
|
+
constructor(resp, wait = 1000) {
|
|
5
7
|
this.debouncers = {};
|
|
6
8
|
this.wait = wait;
|
|
7
|
-
|
|
8
|
-
this.resp = busClient.newResponse('event-debouncer', 'token');
|
|
9
|
+
this.resp = resp;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
publish(topic) {
|
|
12
|
+
publish(topic, data) {
|
|
12
13
|
if (!this.debouncers[topic]) {
|
|
13
|
-
const send = (topic) => this.resp.events.send(topic);
|
|
14
|
+
const send = (topic, data) => this.resp.events.send(topic, data);
|
|
14
15
|
this.debouncers[topic] = debounce(send, this.wait);
|
|
15
16
|
}
|
|
16
|
-
this.debouncers[topic](topic);
|
|
17
|
+
this.debouncers[topic](topic, data);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
package/lib/files.js
CHANGED
package/lib/job-queue.js
CHANGED
|
@@ -1,35 +1,64 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
var clc = require('cli-color');
|
|
3
2
|
|
|
4
3
|
const moduleName = 'job-queue';
|
|
4
|
+
const clc = require('cli-color');
|
|
5
5
|
const watt = require('gigawatts');
|
|
6
6
|
const {locks} = require('xcraft-core-utils');
|
|
7
7
|
const runner = require('./runnerInstance.js');
|
|
8
8
|
const throttle = require('lodash/throttle');
|
|
9
|
+
const defaultOptions = {
|
|
10
|
+
priorityGroup: 'default',
|
|
11
|
+
waitOn: [], //groups to wait
|
|
12
|
+
waitDelay: 50,
|
|
13
|
+
maxAttempt: 100,
|
|
14
|
+
useLogger: false,
|
|
15
|
+
};
|
|
9
16
|
class JobQueue {
|
|
10
|
-
constructor(name, runner, parallelLimit,
|
|
11
|
-
|
|
17
|
+
constructor(name, runner, parallelLimit, options) {
|
|
18
|
+
//override default options
|
|
19
|
+
if (!options) {
|
|
20
|
+
options = defaultOptions;
|
|
21
|
+
} else {
|
|
22
|
+
options = {...defaultOptions, ...options};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.log =
|
|
26
|
+
options.useLogger && require('xcraft-core-log')(`${moduleName}`, null);
|
|
12
27
|
this.name = name;
|
|
28
|
+
this.priorityGroup = options.priorityGroup;
|
|
29
|
+
this.waitOn = options.waitOn;
|
|
30
|
+
this.maxAttempt = options.maxAttempt;
|
|
31
|
+
this.attempt = 0;
|
|
32
|
+
this.waitDelay = options.waitDelay;
|
|
13
33
|
this.channel = name.toLowerCase().replace(/\.|\[|\//g, '-');
|
|
14
|
-
this.runner = watt(
|
|
34
|
+
this.runner = watt(function* (...args) {
|
|
35
|
+
this.running++;
|
|
36
|
+
this.runningSamples++;
|
|
37
|
+
yield* runner(...args);
|
|
38
|
+
});
|
|
15
39
|
this.parallelLimit = parallelLimit;
|
|
16
40
|
this.waiting = new Map();
|
|
17
41
|
this.running = 0;
|
|
42
|
+
this.runningSamples = 0;
|
|
18
43
|
this._mutex = new locks.Mutex();
|
|
19
44
|
const busClient = require('xcraft-core-busclient').getGlobal();
|
|
20
45
|
this.resp = busClient.newResponse('job-queue', 'token');
|
|
21
46
|
watt.wrapAll(this);
|
|
22
|
-
this.notify = throttle(this._notify, 500);
|
|
47
|
+
this.notify = throttle(this._notify, 500).bind(this);
|
|
23
48
|
}
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
get maxAttemptReached() {
|
|
51
|
+
return this.attempt >= this.maxAttempt;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_notify(runningSamples) {
|
|
27
55
|
this.resp.events.send('<job-queue.sampled>', {
|
|
28
56
|
channel: this.channel,
|
|
29
|
-
sample:
|
|
57
|
+
sample: runningSamples,
|
|
30
58
|
current: 0,
|
|
31
59
|
total: 0,
|
|
32
60
|
});
|
|
61
|
+
this.runningSamples = 0;
|
|
33
62
|
}
|
|
34
63
|
|
|
35
64
|
_log() {
|
|
@@ -37,11 +66,10 @@ class JobQueue {
|
|
|
37
66
|
return;
|
|
38
67
|
}
|
|
39
68
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
69
|
+
const msg = `«${clc.blackBright.bold(this.name)}» waiting:${
|
|
70
|
+
this.waiting.size
|
|
71
|
+
} running:${this.running}`;
|
|
72
|
+
this.log.info(msg);
|
|
45
73
|
}
|
|
46
74
|
|
|
47
75
|
_dbg(debugMessage) {
|
|
@@ -49,19 +77,34 @@ class JobQueue {
|
|
|
49
77
|
return;
|
|
50
78
|
}
|
|
51
79
|
|
|
52
|
-
|
|
80
|
+
const msg = `«${clc.blackBright.bold(this.name)}» ${debugMessage}`;
|
|
81
|
+
this.log.dbg(msg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
err(ex) {
|
|
85
|
+
const msg = `«${clc.blackBright.bold(this.name)}» ${
|
|
86
|
+
ex.stack || ex.message || ex
|
|
87
|
+
}`;
|
|
88
|
+
|
|
89
|
+
if (!this.log) {
|
|
90
|
+
console.error(msg);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.log.err(msg);
|
|
53
95
|
}
|
|
54
96
|
|
|
55
97
|
push(job) {
|
|
56
98
|
try {
|
|
57
99
|
this.waiting.set(job.id, job);
|
|
58
100
|
} finally {
|
|
59
|
-
|
|
60
|
-
|
|
101
|
+
//launch macro-task
|
|
102
|
+
setTimeout(runner.instance.run, 0, this);
|
|
61
103
|
}
|
|
62
104
|
}
|
|
63
105
|
|
|
64
106
|
dispose() {
|
|
107
|
+
this.notify(0);
|
|
65
108
|
this.resp.events.send('<job-queue.disposed>', {
|
|
66
109
|
channel: this.channel,
|
|
67
110
|
});
|
package/lib/log.js
CHANGED
|
@@ -64,13 +64,13 @@ exports.decorate = function (mode, prefix, mod, log, maxWidth, stripBegin) {
|
|
|
64
64
|
|
|
65
65
|
var beginLength = begin.replace(ansiRegex(), '').length;
|
|
66
66
|
var availableSpace = maxWidth - beginLength - 1;
|
|
67
|
-
if (availableSpace <=
|
|
67
|
+
if (availableSpace <= 1) {
|
|
68
68
|
return util.format('%s%s', begin, text);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
let beginEmbedded = '';
|
|
72
72
|
let textNoColor = text.replace(ansiRegex(), '');
|
|
73
|
-
const embedded = /^[a-zA-Z]+ \[[a-zA-Z/.\-_]+\] (?:Verb|Info|Warn|Err):/.test(
|
|
73
|
+
const embedded = /^[a-zA-Z]+ \[[a-zA-Z/.\-_]+\] (?:Verb|Info|Warn|Err|Dbg):/.test(
|
|
74
74
|
textNoColor
|
|
75
75
|
);
|
|
76
76
|
|
|
@@ -90,16 +90,17 @@ exports.decorate = function (mode, prefix, mod, log, maxWidth, stripBegin) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
var output = '';
|
|
93
|
+
let _output = '';
|
|
93
94
|
spaces = new Array(beginLength + 1).join(' ');
|
|
94
95
|
|
|
95
96
|
if (embedded) {
|
|
96
97
|
const beginEmbeddedNoCol = beginEmbedded.replace(ansiRegex(), '');
|
|
97
98
|
const beginEmbeddedLength = beginEmbeddedNoCol.length;
|
|
98
|
-
const isSmall = /Err:$/.test(beginEmbeddedNoCol); // only 3 chars
|
|
99
|
+
const isSmall = /(Err|Dbg):$/.test(beginEmbeddedNoCol); // only 3 chars
|
|
99
100
|
const length = beginLength - beginEmbeddedLength + (isSmall ? -1 : 0);
|
|
100
101
|
const padding = length > 0 ? new Array(length).join(' ') : '';
|
|
101
102
|
if (!stripBegin) {
|
|
102
|
-
|
|
103
|
+
_output += `${begin}...\n`;
|
|
103
104
|
}
|
|
104
105
|
begin = `${padding}${beginEmbedded}` + (isSmall ? ' ' : '');
|
|
105
106
|
}
|
|
@@ -141,7 +142,7 @@ exports.decorate = function (mode, prefix, mod, log, maxWidth, stripBegin) {
|
|
|
141
142
|
begin = spaces;
|
|
142
143
|
});
|
|
143
144
|
|
|
144
|
-
return output;
|
|
145
|
+
return _output + output;
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
exports.graffiti = function (text, callback) {
|
package/lib/runnerInstance.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
// create a unique, global symbol name
|
|
2
4
|
// -----------------------------------
|
|
3
5
|
const RUNNER_INSTANCE_KEY = Symbol.for('xcraft-core-utils.runnerInstance');
|
|
@@ -6,47 +8,99 @@ const watt = require('gigawatts');
|
|
|
6
8
|
class Runner {
|
|
7
9
|
constructor() {
|
|
8
10
|
this.totalRunning = 0;
|
|
11
|
+
this.runningsGroups = [];
|
|
12
|
+
this.runnings = {};
|
|
13
|
+
this.run = this.run.bind(this);
|
|
9
14
|
watt.wrapAll(this);
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
run(jobQueue) {
|
|
18
|
+
//must be postponed?
|
|
19
|
+
if (jobQueue.waitOn.length > 0) {
|
|
20
|
+
const mustWait = this.runningsGroups.some((q) =>
|
|
21
|
+
jobQueue.waitOn.includes(q)
|
|
22
|
+
);
|
|
23
|
+
if (mustWait) {
|
|
24
|
+
if (!jobQueue.maxAttemptReached) {
|
|
25
|
+
jobQueue.attempt++;
|
|
26
|
+
setTimeout(this.run, jobQueue.waitDelay, jobQueue);
|
|
27
|
+
return;
|
|
28
|
+
} else {
|
|
29
|
+
jobQueue.attempt = 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//queue is draining?
|
|
13
35
|
if (jobQueue.running > 0 && jobQueue.waiting.size === 0) {
|
|
14
36
|
return;
|
|
15
37
|
}
|
|
16
|
-
yield jobQueue._mutex.lock();
|
|
17
|
-
for (let x = jobQueue.running; x < jobQueue.parallelLimit; x++) {
|
|
18
|
-
setTimeout(jobQueue._log.bind(jobQueue), 1);
|
|
19
38
|
|
|
39
|
+
//jobqueue releasing
|
|
40
|
+
for (let x = jobQueue.running; x < jobQueue.parallelLimit; x++) {
|
|
20
41
|
const nextEntry = jobQueue.waiting.entries().next();
|
|
21
42
|
if (nextEntry.done) {
|
|
43
|
+
//we can leave, nothing is waiting us
|
|
22
44
|
break;
|
|
23
45
|
}
|
|
46
|
+
|
|
47
|
+
//remove the jobEntry from queue
|
|
24
48
|
const jobEntry = Object.assign({}, nextEntry.value);
|
|
25
49
|
jobQueue.waiting.delete(jobEntry[0]);
|
|
26
50
|
|
|
51
|
+
//log for journal inspections
|
|
27
52
|
if (this.totalRunning === 0 && jobQueue.parallelLimit > 1) {
|
|
28
|
-
jobQueue._dbg(`
|
|
53
|
+
jobQueue._dbg(`system are running new jobs`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//priority tracking
|
|
57
|
+
if (jobQueue.priorityGroup !== 'default' && jobQueue.running === 0) {
|
|
58
|
+
if (!this.runnings[jobQueue.priorityGroup]) {
|
|
59
|
+
this.runnings[jobQueue.priorityGroup] = {};
|
|
60
|
+
}
|
|
61
|
+
this.runnings[jobQueue.priorityGroup][jobQueue.name] = true;
|
|
62
|
+
//update runnings group
|
|
63
|
+
this.runningsGroups = Object.entries(this.runnings)
|
|
64
|
+
.filter((e) => Object.values(e[1]).some((r) => r === true))
|
|
65
|
+
.map((e) => e[0]);
|
|
29
66
|
}
|
|
30
|
-
|
|
67
|
+
|
|
68
|
+
//update counters
|
|
31
69
|
this.totalRunning++;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
|
|
71
|
+
//trigger job start on queue runner
|
|
72
|
+
jobQueue.runner(jobEntry[1], (err) => {
|
|
73
|
+
if (err) {
|
|
74
|
+
jobQueue.err(err);
|
|
75
|
+
}
|
|
76
|
+
//end callback
|
|
77
|
+
|
|
78
|
+
jobQueue.notify(jobQueue.runningSamples);
|
|
79
|
+
|
|
80
|
+
//update counter
|
|
81
|
+
jobQueue.running--;
|
|
82
|
+
this.totalRunning--;
|
|
83
|
+
|
|
84
|
+
//priority tracking
|
|
85
|
+
if (jobQueue.priorityGroup !== 'default' && jobQueue.running === 0) {
|
|
86
|
+
this.runnings[jobQueue.priorityGroup][jobQueue.name] = false;
|
|
87
|
+
//update runnings group
|
|
88
|
+
this.runningsGroups = Object.entries(this.runnings)
|
|
89
|
+
.filter((e) => Object.values(e[1]).some((r) => r === true))
|
|
90
|
+
.map((e) => e[0]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//log
|
|
94
|
+
if (this.totalRunning === 0 && jobQueue.waiting.size === 0) {
|
|
95
|
+
jobQueue._dbg(`no more jobs running`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//schedule a new run for this queue if needed
|
|
99
|
+
if (jobQueue.waiting.size > 0) {
|
|
100
|
+
setTimeout(this.run, 0, jobQueue);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
48
103
|
}
|
|
49
|
-
jobQueue._mutex.unlock();
|
|
50
104
|
}
|
|
51
105
|
}
|
|
52
106
|
|
package/lib/whereIs.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fse = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
module.exports = function whereIs(bin) {
|
|
5
|
+
var fullLocation = null;
|
|
6
|
+
|
|
7
|
+
var exists = process.env.PATH.split(path.delimiter).some(function (location) {
|
|
8
|
+
fullLocation = path.join(location, bin);
|
|
9
|
+
return fse.existsSync(fullLocation);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return exists ? fullLocation : null;
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xcraft-core-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Xcraft utils",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=14"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -23,26 +23,28 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"ansi-regex": "^2.1.1",
|
|
25
25
|
"cli-color": "^1.2.0",
|
|
26
|
-
"escape-
|
|
26
|
+
"escape-regexp": "0.0.1",
|
|
27
27
|
"escape-string-regexp": "^1.0.5",
|
|
28
28
|
"figlet": "^1.2.1",
|
|
29
|
+
"fs-extra": "^9.1.0",
|
|
29
30
|
"gigawatts": "^4.0.1",
|
|
30
31
|
"js-yaml": "^3.9.0",
|
|
31
32
|
"linked-list": "^1.0.4",
|
|
32
33
|
"lodash": "^4.17.20",
|
|
33
34
|
"locks": "^0.2.2",
|
|
34
35
|
"prop-types": "^15.7.2",
|
|
35
|
-
"uuid": "^
|
|
36
|
-
"xcraft-core-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
36
|
+
"uuid": "^8.3.2",
|
|
37
|
+
"xcraft-core-busclient": "^5.0.0",
|
|
38
|
+
"xcraft-core-fs": "^2.0.0",
|
|
39
|
+
"xcraft-core-log": "^2.0.0",
|
|
40
|
+
"xcraft-core-utils": "^4.0.0"
|
|
40
41
|
},
|
|
41
42
|
"bugs": {
|
|
42
43
|
"url": "https://github.com/Xcraft-Inc/xcraft-core-utils/issues"
|
|
43
44
|
},
|
|
44
45
|
"homepage": "https://github.com/Xcraft-Inc/xcraft-core-utils#readme",
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"should": "^11.2.1",
|
|
46
48
|
"prettier": "2.0.4",
|
|
47
49
|
"xcraft-dev-prettier": "^2.0.0",
|
|
48
50
|
"xcraft-dev-rules": "^2.0.0"
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var clc = require('cli-color');
|
|
4
|
-
|
|
5
|
-
const moduleName = 'job-queue';
|
|
6
|
-
const watt = require('gigawatts');
|
|
7
|
-
const locks = require('xcraft-core-utils/lib/locks.js');
|
|
8
|
-
|
|
9
|
-
class PersistantJobQueue {
|
|
10
|
-
constructor(
|
|
11
|
-
path,
|
|
12
|
-
name,
|
|
13
|
-
runner,
|
|
14
|
-
parallelLimit,
|
|
15
|
-
useLogger = true,
|
|
16
|
-
options = {}
|
|
17
|
-
) {
|
|
18
|
-
// Status db
|
|
19
|
-
const Database = require('better-sqlite3');
|
|
20
|
-
this.db = new Database(path, options);
|
|
21
|
-
|
|
22
|
-
this.db.exec(`
|
|
23
|
-
CREATE TABLE IF NOT EXISTS JobQueue (
|
|
24
|
-
jobId TEXT PRIMARY KEY,
|
|
25
|
-
topic TEXT,
|
|
26
|
-
seq NUMBER,
|
|
27
|
-
status TEXT,
|
|
28
|
-
work TEXT
|
|
29
|
-
);
|
|
30
|
-
`);
|
|
31
|
-
|
|
32
|
-
this.insertJob = this.db.prepare(`
|
|
33
|
-
INSERT OR REPLACE INTO JobQueue (jobId,topic,seq,status, work)
|
|
34
|
-
VALUES ($jobId, $topic, $seq, $status, $work);
|
|
35
|
-
`);
|
|
36
|
-
|
|
37
|
-
this.getJob = this.db.prepare(`
|
|
38
|
-
SELECT * FROM JobQueue WHERE status='waiting' ORDER BY seq LIMIT 0,1;
|
|
39
|
-
`);
|
|
40
|
-
|
|
41
|
-
this.changeJobStatus = this.db.prepare(`
|
|
42
|
-
UPDATE JobQueue
|
|
43
|
-
SET status=$status
|
|
44
|
-
WHERE jobId=$jobId;
|
|
45
|
-
`);
|
|
46
|
-
|
|
47
|
-
this.deleteJobDone = this.db.prepare(`
|
|
48
|
-
DELETE FROM JobQueue WHERE jobId=$jobId;
|
|
49
|
-
`);
|
|
50
|
-
|
|
51
|
-
this.getJobsCount = () => {
|
|
52
|
-
const row = this.db.prepare(
|
|
53
|
-
`SELECT COUNT(*) as count FROM JobQueue WHERE status='waiting';`
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
const r = row.get();
|
|
57
|
-
return parseInt(r.count);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
this.count = this.getJobsCount();
|
|
61
|
-
|
|
62
|
-
this.log = useLogger && require('xcraft-core-log')(`${moduleName}`, null);
|
|
63
|
-
this.name = name;
|
|
64
|
-
this.runner = runner;
|
|
65
|
-
this.parallelLimit = parallelLimit;
|
|
66
|
-
this.running = 0;
|
|
67
|
-
this.paused = false;
|
|
68
|
-
this._mutex = new locks.Mutex();
|
|
69
|
-
watt.wrapAll(this);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
_log() {
|
|
73
|
-
if (!this.log) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const count = this.getJobsCount();
|
|
78
|
-
|
|
79
|
-
/* FIXME: change dbg to info when it's no longer necessary */
|
|
80
|
-
this.log.dbg(
|
|
81
|
-
`«${clc.blackBright.bold(this.name)}» waiting:${count} running:${
|
|
82
|
-
this.running
|
|
83
|
-
}`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
run() {
|
|
88
|
-
if (this.paused) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (this.getJobsCount() === 0) {
|
|
92
|
-
this._log();
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (this.running >= this.parallelLimit) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Change status running
|
|
101
|
-
const job = this.getJob.get();
|
|
102
|
-
this.changeJobStatus.run({jobId: job.jobId, status: 'running'});
|
|
103
|
-
this.running++;
|
|
104
|
-
this._log();
|
|
105
|
-
job.work = JSON.parse(job.work);
|
|
106
|
-
setImmediate(this.runner, job, () => {
|
|
107
|
-
this.deleteJobDone.run({jobId: job.jobId});
|
|
108
|
-
this.running--;
|
|
109
|
-
this.run();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
this.run();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
*push(job) {
|
|
116
|
-
yield this._mutex.lock();
|
|
117
|
-
try {
|
|
118
|
-
this.insertJob.run({
|
|
119
|
-
jobId: job.id,
|
|
120
|
-
topic: job.topic || this.name,
|
|
121
|
-
seq: this.count++,
|
|
122
|
-
status: 'waiting',
|
|
123
|
-
work: JSON.stringify(job.work || {}),
|
|
124
|
-
});
|
|
125
|
-
} catch (ex) {
|
|
126
|
-
throw ex;
|
|
127
|
-
} finally {
|
|
128
|
-
this._log();
|
|
129
|
-
this._mutex.unlock();
|
|
130
|
-
this.run();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
resume() {
|
|
135
|
-
this.paused = false;
|
|
136
|
-
this.run();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
pause() {
|
|
140
|
-
this.paused = true;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
module.exports = PersistantJobQueue;
|
package/lib/sqlite.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const xFs = require('xcraft-core-fs');
|
|
5
|
-
|
|
6
|
-
class SQLite {
|
|
7
|
-
constructor(location, skip) {
|
|
8
|
-
this._stmts = {};
|
|
9
|
-
this._db = {};
|
|
10
|
-
this._dir = location;
|
|
11
|
-
SQLite.prototype._init.call(this, skip);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
_init(skip) {
|
|
15
|
-
try {
|
|
16
|
-
this.Database = skip ? null : require('better-sqlite3');
|
|
17
|
-
} catch (ex) {
|
|
18
|
-
/* ... */
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
_path(dbName) {
|
|
23
|
-
return path.join(this._dir, `${dbName}.db`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
_onError(resp) {
|
|
27
|
-
resp.log.info('sqlite3 is not supported on this platform');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
_prepare(dbName, query, sql) {
|
|
31
|
-
this._stmts[dbName][query] = this._db[dbName].prepare(sql);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
stmts(dbName) {
|
|
35
|
-
return this._stmts[dbName];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getLocation() {
|
|
39
|
-
return this._dir;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
setEnable(en) {
|
|
43
|
-
SQLite.prototype._init.call(this, !en);
|
|
44
|
-
|
|
45
|
-
if (!en) {
|
|
46
|
-
Object.keys(this._db).forEach((db) => this.close(db));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Check if SQLite is usable.
|
|
52
|
-
*
|
|
53
|
-
* @return {Boolean} true if SQLite is available.
|
|
54
|
-
*/
|
|
55
|
-
usable() {
|
|
56
|
-
return !!this.Database;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
61
|
-
* @param {Object} resp - Response object provided by busClient.
|
|
62
|
-
* @returns {Boolean} true if usable.
|
|
63
|
-
*/
|
|
64
|
-
tryToUse(resp) {
|
|
65
|
-
if (!this.usable()) {
|
|
66
|
-
SQLite.prototype._onError.call(this, resp);
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
timestamp() {
|
|
73
|
-
return new Date().toISOString();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Open (and create if necessary) a SQLite database.
|
|
78
|
-
*
|
|
79
|
-
* @param {string} dbName - Database name used for the database file.
|
|
80
|
-
* @param {string} tables - Main queries for creating the tables.
|
|
81
|
-
* @param {Object} queries - Raw queries to prepare.
|
|
82
|
-
* @param {function} onOpen - Callback just after opening the database.
|
|
83
|
-
* @param {function} onMigrate - Callback for migrations.
|
|
84
|
-
* @return {Boolean} false if SQLite is not available.
|
|
85
|
-
*/
|
|
86
|
-
open(dbName, tables, queries, onOpen, onMigrate) {
|
|
87
|
-
if (!this.usable()) {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (this._db[dbName]) {
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
xFs.mkdir(this._dir);
|
|
96
|
-
|
|
97
|
-
const dbPath = this._path(dbName);
|
|
98
|
-
this._db[dbName] = new this.Database(dbPath, {timeout: 30000});
|
|
99
|
-
this._stmts[dbName] = {};
|
|
100
|
-
|
|
101
|
-
if (onOpen) {
|
|
102
|
-
onOpen();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
this._db[dbName].exec(tables);
|
|
106
|
-
|
|
107
|
-
if (onMigrate) {
|
|
108
|
-
onMigrate();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
for (const query in queries) {
|
|
112
|
-
SQLite.prototype._prepare.call(this, dbName, query, queries[query]);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
close(dbName) {
|
|
119
|
-
if (!this._db[dbName]) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
this._db[dbName].close();
|
|
123
|
-
delete this._db[dbName];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
exec(dbName, query) {
|
|
127
|
-
if (!this.usable() || !this._db[dbName]) {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
this._db[dbName].exec(query);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
pragma(dbName, pragma) {
|
|
134
|
-
if (!this.usable() || !this._db[dbName]) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
return this._db[dbName].pragma(pragma, {simple: true});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
module.exports = SQLite;
|