keuss 1.6.14 → 1.7.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.
@@ -0,0 +1,72 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '26 13 * * 1'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'javascript' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38
+
39
+ steps:
40
+ - name: Checkout repository
41
+ uses: actions/checkout@v3
42
+
43
+ # Initializes the CodeQL tools for scanning.
44
+ - name: Initialize CodeQL
45
+ uses: github/codeql-action/init@v2
46
+ with:
47
+ languages: ${{ matrix.language }}
48
+ # If you wish to specify custom queries, you can do so here or in a config file.
49
+ # By default, queries listed here will override any specified in a config file.
50
+ # Prefix the list here with "+" to use these queries and those in the config file.
51
+
52
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53
+ # queries: security-extended,security-and-quality
54
+
55
+
56
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57
+ # If this step fails, then you should remove it and run the build manually (see below)
58
+ - name: Autobuild
59
+ uses: github/codeql-action/autobuild@v2
60
+
61
+ # ℹ️ Command-line programs to run using the OS shell.
62
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63
+
64
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
65
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66
+
67
+ # - run: |
68
+ # echo "Run, Build Application using script"
69
+ # ./location_of_script_within_repo/buildscript.sh
70
+
71
+ - name: Perform CodeQL Analysis
72
+ uses: github/codeql-action/analyze@v2
package/Queue.js CHANGED
@@ -98,6 +98,9 @@ class Queue {
98
98
  // remove (and return if possible) by id
99
99
  remove (id, callback) {callback(null, null);}
100
100
 
101
+ // extra information & status
102
+ extra_info (cb) {cb (null, {});}
103
+
101
104
  // end of expected redefinitions on subclasses
102
105
  ////////////////////////////////////////////////////////////////////////////
103
106
 
@@ -113,6 +116,29 @@ class Queue {
113
116
  capabilities () {
114
117
  return this._factory.capabilities ();
115
118
  }
119
+
120
+
121
+ info (cb) {
122
+ async.parallel ({
123
+ size: cb => this.size (cb),
124
+ totalSize: cb => this.totalSize (cb),
125
+ schedSize: cb => this.schedSize (cb),
126
+ resvSize: cb => this.resvSize (cb),
127
+ next_t: cb => this.next_t (cb),
128
+ stats: cb => this.stats (cb),
129
+ paused: cb => this.paused (cb),
130
+ extra: cb => this.extra_info (cb),
131
+ }, (err, res) => {
132
+ if (err) return cb (err);
133
+
134
+ res.name = this.name();
135
+ res.ns = this.ns();
136
+ res.type = this.type();
137
+ res.capabilities = this.capabilities();
138
+
139
+ cb (null, res);
140
+ });
141
+ }
116
142
 
117
143
  // T of next mature
118
144
  nextMatureDate () {return this._next_mature_t;}
package/Signal.js CHANGED
@@ -73,6 +73,12 @@ class Signal {
73
73
  }
74
74
 
75
75
 
76
+ // to be extended: generic pubsub service
77
+ subscribe_extra (topic, on_cb) {return false}
78
+ unsubscribe_extra (subscr) {}
79
+ emit_extra (topic, ev, cb) {if (cb) cb ();}
80
+
81
+
76
82
  static _hrtimeAsMSecs (hrtime) {
77
83
  return (hrtime[0] * 1000) + (hrtime[1] / 1e6);
78
84
  }
@@ -14,6 +14,8 @@ class PersistentMongoQueue extends Queue {
14
14
  constructor (name, factory, opts, orig_opts) {
15
15
  super (name, factory, opts, orig_opts);
16
16
 
17
+ if (!this._opts.ttl) this._opts.ttl = 3600;
18
+
17
19
  this._factory = factory;
18
20
  this._col = factory._db.collection (name);
19
21
  this.ensureIndexes (function (err) {});
@@ -50,7 +52,10 @@ class PersistentMongoQueue extends Queue {
50
52
  };
51
53
 
52
54
  var updt = {
53
- $set: {processed: new Date ()}
55
+ $set: {
56
+ processed: new Date (),
57
+ mature: Queue.nowPlusSecs (100 * this._opts.ttl)
58
+ }
54
59
  };
55
60
 
56
61
  var opts = {
@@ -78,7 +83,10 @@ class PersistentMongoQueue extends Queue {
78
83
  };
79
84
 
80
85
  var update = {
81
- $set: {mature: Queue.nowPlusSecs (delay), reserved: new Date ()},
86
+ $set: {
87
+ mature: Queue.nowPlusSecs (delay),
88
+ reserved: new Date ()
89
+ },
82
90
  $inc: {tries: 1}
83
91
  };
84
92
 
@@ -113,7 +121,10 @@ class PersistentMongoQueue extends Queue {
113
121
  }
114
122
 
115
123
  var updt = {
116
- $set: {processed: new Date ()},
124
+ $set: {
125
+ processed: new Date (),
126
+ mature: Queue.nowPlusSecs (100 * this._opts.ttl)
127
+ },
117
128
  $unset: {reserved: ''}
118
129
  };
119
130
 
@@ -226,7 +237,11 @@ class PersistentMongoQueue extends Queue {
226
237
  }
227
238
 
228
239
  var updt = {
229
- $set: {processed: new Date (), removed: true},
240
+ $set: {
241
+ processed: new Date (),
242
+ mature: Queue.nowPlusSecs (100 * this._opts.ttl),
243
+ removed: true
244
+ },
230
245
  };
231
246
 
232
247
  var opts = {};
@@ -261,7 +276,7 @@ class PersistentMongoQueue extends Queue {
261
276
  ensureIndexes (cb) {
262
277
  this._col.createIndex ({mature : 1}, err => {
263
278
  if (err) return cb (err);
264
- this._col.createIndex({processed: 1}, {expireAfterSeconds: this._opts.ttl || 3600}, err => cb (err));
279
+ this._col.createIndex({processed: 1}, {expireAfterSeconds: this._opts.ttl}, err => cb (err));
265
280
  });
266
281
  }
267
282
  }
@@ -0,0 +1,476 @@
1
+ const _ = require ('lodash');
2
+ const async = require ('async');
3
+
4
+ const MongoClient = require ('mongodb').MongoClient;
5
+ const mongo = require ('mongodb');
6
+
7
+ const Queue = require ('../Queue');
8
+ const QFactory_MongoDB_defaults = require ('../QFactory-MongoDB-defaults');
9
+
10
+ var debug = require('debug')('keuss:Queue:StreamMongo');
11
+
12
+ class StreamMongoQueue extends Queue {
13
+
14
+ //////////////////////////////////////////////
15
+ constructor (name, factory, opts, orig_opts) {
16
+ super (name, factory, opts, orig_opts);
17
+
18
+ if (!this._opts.ttl) this._opts.ttl = 3600;
19
+
20
+ this._factory = factory;
21
+ this._col = factory._db.collection (name);
22
+ this._groups_str = this._opts.groups || 'A,B:C';
23
+ this._groups_vector = this._groups_str.split (/[:,;.-]/).map (i => i.trim());
24
+ this._gid = this._opts.group || this._groups_vector[0];
25
+
26
+ this.ensureIndexes (err => {
27
+ if (err) {
28
+ console.error ('keuss:Queue:StreamMongo: index creation failed, queues performance will be severely impacted:', err);
29
+ }
30
+ else {
31
+ debug ('indexes created');
32
+ }
33
+ });
34
+
35
+ debug ('created with groups %j and gid %s (used for pop/reserve only)', this._groups_vector, this._gid);
36
+ }
37
+
38
+
39
+ /////////////////////////////////////////
40
+ static Type () {
41
+ return 'mongo:stream';
42
+ }
43
+
44
+
45
+ /////////////////////////////////////////
46
+ type () {
47
+ return 'mongo:stream';
48
+ }
49
+
50
+
51
+ /////////////////////////////////////////
52
+ _vector (item) {
53
+ const r = {};
54
+ this._groups_vector.forEach (i => r[i] = item);
55
+ return r;
56
+ }
57
+
58
+
59
+ /////////////////////////////////////////
60
+ // add element to queue
61
+ insert (entry, callback) {
62
+ const mtr = entry.mature;
63
+ const tr = entry.tries;
64
+
65
+ entry.tries = this._vector (tr);
66
+ entry.mature = this._vector (mtr);
67
+ entry.processed = this._vector (false);
68
+
69
+ entry.t = new Date();
70
+
71
+ this._col.insertOne (entry, {}, (err, result) => {
72
+ if (err) return callback (err);
73
+ // TODO result.insertedCount must be 1
74
+ callback (null, result.insertedId);
75
+ this._groups_vector.forEach (i => this._stats.incr (`stream.${i}.put`));
76
+ });
77
+ }
78
+
79
+
80
+ /////////////////////////////////////////
81
+ // get element from queue
82
+ get (callback) {
83
+ const gid = this._gid;
84
+ const q = {};
85
+
86
+ q[`mature.${gid}`] = {$lte: Queue.nowPlusSecs (0)};
87
+ q[`processed.${gid}`] = false;
88
+
89
+ const updt = {
90
+ $set: {}
91
+ };
92
+ updt.$set[`processed.${gid}`] = new Date ();
93
+ updt.$set[`mature.${gid}`] = Queue.nowPlusSecs (100 * this._opts.ttl);
94
+
95
+ const opts = {
96
+ sort: {}
97
+ };
98
+ opts.sort[`mature.${gid}`] = 1;
99
+
100
+ debug ('get() with q %O, upd %O, opts %o', q, updt, opts);
101
+
102
+ this._col.findOneAndUpdate (q, updt, opts, (err, result) => {
103
+ if (err) return callback (err);
104
+ const v = result && result.value;
105
+ if (!v) return callback ();
106
+ if (v.payload._bsontype == 'Binary') v.payload = v.payload.buffer;
107
+ v.mature = v.mature[gid];
108
+ v.tries = v.tries[gid];
109
+ delete v.processed;
110
+ delete v.t;
111
+ callback (null, v);
112
+ this._stats.incr (`stream.${gid}.get`);
113
+ });
114
+ }
115
+
116
+
117
+ //////////////////////////////////
118
+ // reserve element: call cb (err, pl) where pl has an id
119
+ reserve (callback) {
120
+ const gid = this._gid;
121
+ const delay = this._opts.reserve_delay || 120;
122
+
123
+ const q = {};
124
+
125
+ q[`mature.${gid}`] = {$lte: Queue.nowPlusSecs (0)};
126
+ q[`processed.${gid}`] = false;
127
+
128
+ const updt = {
129
+ $set: {},
130
+ $inc: {}
131
+ };
132
+ updt.$set[`reserved.${gid}`] = new Date ();
133
+ updt.$set[`mature.${gid}`] = Queue.nowPlusSecs (delay);
134
+ updt.$inc[`tries.${gid}`] = 1;
135
+
136
+ const opts = {
137
+ sort: {},
138
+ returnDocument: 'before'
139
+ };
140
+ opts.sort[`mature.${gid}`] = 1;
141
+
142
+ debug ('reserve() with q %O, upd %O, opts %o', q, updt, opts);
143
+
144
+ this._col.findOneAndUpdate (q, updt, opts, (err, result) => {
145
+ if (err) return callback (err);
146
+ const v = result && result.value;
147
+ if (!v) return callback ();
148
+ if (v.payload._bsontype == 'Binary') v.payload = v.payload.buffer;
149
+ v.mature = v.mature[gid];
150
+ v.tries = v.tries[gid];
151
+ delete v.processed;
152
+ delete v.t;
153
+ callback (null, v);
154
+ this._stats.incr (`stream.${gid}.reserve`);
155
+ });
156
+ }
157
+
158
+
159
+ //////////////////////////////////
160
+ // commit previous reserve, by p.id
161
+ commit (id, callback) {
162
+ const gid = this._gid;
163
+ let q;
164
+
165
+ try {
166
+ q = {
167
+ _id: (_.isString(id) ? new mongo.ObjectID (id) : id),
168
+ };
169
+ q[`reserved.${gid}`] = {$exists: true};
170
+ }
171
+ catch (e) {
172
+ return callback ('id [' + id + '] can not be used as rollback id: ' + e);
173
+ }
174
+
175
+ const updt = {
176
+ $set: {},
177
+ $unset: {}
178
+ };
179
+ updt.$set[`processed.${gid}`] = new Date ();
180
+ updt.$set[`mature.${gid}`] = Queue.nowPlusSecs (100 * this._opts.ttl);
181
+ updt.$unset[`reserved.${gid}`] = '';
182
+
183
+ const opts = {};
184
+
185
+ debug ('commit() with q %O, upd %O, opts %o', q, updt, opts);
186
+
187
+ this._col.updateOne (q, updt, opts, (err, result) => {
188
+ if (err) return callback (err);
189
+ callback (null, result && (result.modifiedCount == 1));
190
+ this._stats.incr (`stream.${gid}.commit`);
191
+ });
192
+ }
193
+
194
+
195
+ //////////////////////////////////
196
+ // rollback previous reserve, by p.id
197
+ rollback (id, next_t, callback) {
198
+ const gid = this._gid;
199
+ let q;
200
+
201
+ if (_.isFunction (next_t)) {
202
+ callback = next_t;
203
+ next_t = null;
204
+ }
205
+
206
+ try {
207
+ q = {
208
+ _id: (_.isString(id) ? new mongo.ObjectID (id) : id),
209
+ };
210
+ q[`reserved.${gid}`] = {$exists: true};
211
+ }
212
+ catch (e) {
213
+ return callback ('id [' + id + '] can not be used as rollback id: ' + e);
214
+ }
215
+
216
+ const updt = {
217
+ $set: {},
218
+ $unset: {}
219
+ };
220
+ updt.$set[`mature.${gid}`] = (next_t ? new Date (next_t) : Queue.now ());
221
+ updt.$unset[`reserved.${gid}`] = '';
222
+
223
+ const opts = {};
224
+
225
+ debug ('rollback() with q %O, upd %O, opts %o', q, updt, opts);
226
+
227
+ this._col.updateOne (q, updt, opts, (err, result) => {
228
+ if (err) return callback (err);
229
+ callback (null, result && (result.modifiedCount == 1));
230
+ this._stats.incr (`stream.${gid}.rollback`);
231
+ });
232
+ }
233
+
234
+
235
+ //////////////////////////////////
236
+ // queue size including non-mature elements
237
+ totalSize (callback, gid) {
238
+ const gr = gid || this._gid;
239
+
240
+ const q = {};
241
+ q[`processed.${gr}`] = false;
242
+
243
+ const opts = {};
244
+ this._col.countDocuments (q, opts, callback);
245
+ }
246
+
247
+
248
+ //////////////////////////////////
249
+ // queue size NOT including non-mature elements
250
+ size (callback, gid) {
251
+ const gr = gid || this._gid;
252
+
253
+ const q = {};
254
+ q[`processed.${gr}`] = false;
255
+ q[`mature.${gr}`] = {$lte: Queue.now()};
256
+
257
+ const opts = {};
258
+ this._col.countDocuments (q, opts, callback);
259
+ }
260
+
261
+
262
+ //////////////////////////////////
263
+ // queue size of non-mature elements only
264
+ schedSize (callback, gid) {
265
+ const gr = gid || this._gid;
266
+
267
+ const q = {};
268
+ q[`processed.${gr}`] = false;
269
+ q[`reserved.${gr}`] = {$exists: false};
270
+ q[`mature.${gr}`] = {$gt: Queue.now()};
271
+
272
+ const opts = {};
273
+ this._col.countDocuments (q, opts, callback);
274
+ }
275
+
276
+
277
+ //////////////////////////////////
278
+ // queue size of reserved elements only
279
+ resvSize (callback, gid) {
280
+ const gr = gid || this._gid;
281
+
282
+ const q = {};
283
+ q[`processed.${gr}`] = false;
284
+ q[`reserved.${gr}`] = {$exists: true};
285
+ q[`mature.${gr}`] = {$gt: Queue.now()};
286
+
287
+ const opts = {};
288
+ this._col.countDocuments (q, opts, callback);
289
+ }
290
+
291
+
292
+ /////////////////////////////////////////
293
+ // get element from queue
294
+ next_t (callback, gid) {
295
+ const gr = gid || this._gid;
296
+
297
+ const q = {};
298
+ q[`processed.${gr}`] = false;
299
+
300
+ const sort = {};
301
+ sort[`mature.${gr}`] = 1;
302
+
303
+ this._col
304
+ .find (q)
305
+ .limit(1)
306
+ .sort (sort)
307
+ .project ({mature:1})
308
+ .next ((err, result) => {
309
+ if (err) return callback (err);
310
+ debug ('next_t with git %s: got %o', gr, result);
311
+ callback (null, result && result.mature && result.mature[gr]);
312
+ });
313
+ }
314
+
315
+
316
+ //////////////////////////////////
317
+ // queue size of non-mature elements only.
318
+ // COMMENTED OUT: takes 1 sec per each 100K elements in collection
319
+ /*
320
+ extra_info (callback) {
321
+ const cursor = this._col.aggregate ([
322
+ {
323
+ $group : {
324
+ _id : "v",
325
+ r: {
326
+ $accumulator: {
327
+ init: `function() {
328
+ return { size: {}, totalSize: {}, resvSize: {}, schedSize: {}, processed: {} }
329
+ }`,
330
+ accumulate: `function(state, mature, reserved, processed) {
331
+ for (const gr in mature) {
332
+ if (!state.size[gr]) state.size[gr] = 0;
333
+ if (!state.totalSize[gr]) state.totalSize[gr] = 0;
334
+ if (!state.resvSize[gr]) state.resvSize[gr] = 0;
335
+ if (!state.schedSize[gr]) state.schedSize[gr] = 0;
336
+ if (!state.processed[gr]) state.processed[gr] = 0;
337
+
338
+ const mtr = (mature[gr].getTime() < new Date().getTime());
339
+ const rsv = (reserved && ((reserved[gr] != null) && (reserved[gr] != undefined)));
340
+ const prc = processed[gr];
341
+
342
+ if (prc) {
343
+ state.processed[gr]++;
344
+ }
345
+ else {
346
+ state.totalSize[gr]++;
347
+
348
+ if (mtr) {
349
+ state.size[gr]++;
350
+ }
351
+ else {
352
+ if (rsv) state.resvSize[gr]++;
353
+ else state.schedSize[gr]++;
354
+ }
355
+ }
356
+ }
357
+
358
+ return state;
359
+ }`,
360
+ accumulateArgs: ['$mature', '$reserved', '$processed'],
361
+ merge: `function(state1, state2) {
362
+ const res = { size: {}, totalSize: {}, resvSize: {}, schedSize: {} }
363
+ for (const gr in state1) {
364
+ res.size[gr] = state1.size[gr] + (state2.size[gr] || 0)
365
+ res.totalSize[gr] = state1.totalSize[gr] + (state2.totalSize[gr] || 0)
366
+ res.resvSize[gr] = state1.resvSize[gr] + (state2.resvSize[gr] || 0)
367
+ res.schedSize[gr] = state1.schedSize[gr] + (state2.schedSize[gr] || 0)
368
+ res.processed[gr] = state1.processed[gr] + (state2.processed[gr] || 0)
369
+ }
370
+ for (const gr in state2) {
371
+ if (!state1.size[gr]) res.size[gr] = state2.size[gr]
372
+ if (!state1.totalSize[gr]) res.totalSize[gr] = state2.totalSize[gr]
373
+ if (!state1.schedSize[gr]) res.schedSize[gr] = state2.schedSize[gr]
374
+ if (!state1.resvSize[gr]) res.resvSize[gr] = state2.resvSize[gr]
375
+ if (!state1.processed[gr]) res.processed[gr] = state2.processed[gr]
376
+ }
377
+ return res
378
+ }`,
379
+ lang: "js"
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ ]);
386
+
387
+
388
+
389
+
390
+ cursor.toArray ((err, res) => {
391
+ debug ('calculating resvSize: aggregation pipeline returns %o', err);
392
+ debug ('calculating resvSize: aggregation pipeline returns %o', res);
393
+ if (err) return callback (err);
394
+ if (res.length == 0) return callback (null, 0);
395
+ callback (null, res[0].r);
396
+ });
397
+ }
398
+ */
399
+
400
+
401
+ ///////////////////////////////////////////////////////////////////////////////
402
+ // private parts
403
+
404
+ //////////////////////////////////////////////////////////////////
405
+ // create needed indexes for O(1) functioning
406
+ ensureIndexes (cb) {
407
+ const tasks = [];
408
+
409
+ this._groups_vector.forEach (i => {
410
+ const idx = {};
411
+ idx[`mature.${i}`] = 1;
412
+ tasks.push (cb => this._col.createIndex (idx, cb));
413
+ });
414
+ tasks.push (cb => this._col.createIndex ({t: 1}, {expireAfterSeconds: this._opts.ttl}, cb));
415
+ async.series (tasks, cb);
416
+ }
417
+ }
418
+
419
+
420
+ class Factory extends QFactory_MongoDB_defaults {
421
+ constructor (opts, mongo_conn) {
422
+ super (opts);
423
+ this._mongo_conn = mongo_conn;
424
+ this._db = mongo_conn.db();
425
+ }
426
+
427
+ queue (name, opts) {
428
+ const full_opts = {};
429
+ _.merge(full_opts, this._opts, opts);
430
+ return new StreamMongoQueue (name, this, full_opts, opts);
431
+ }
432
+
433
+ close (cb) {
434
+ super.close (() => {
435
+ if (this._mongo_conn) {
436
+ this._mongo_conn.close ();
437
+ this._mongo_conn = null;
438
+ }
439
+
440
+ if (cb) return cb ();
441
+ });
442
+ }
443
+
444
+ type () {
445
+ return StreamMongoQueue.Type ();
446
+ }
447
+
448
+ capabilities () {
449
+ return {
450
+ sched: true,
451
+ reserve: true,
452
+ pipeline: false,
453
+ tape: true,
454
+ remove: false,
455
+ stream: true
456
+ };
457
+ }
458
+ }
459
+
460
+ function creator (opts, cb) {
461
+ const _opts = opts || {};
462
+ const m_url = _opts.url || 'mongodb://localhost:27017/keuss';
463
+
464
+ MongoClient.connect (m_url, { useNewUrlParser: true }, (err, cl) => {
465
+ if (err) return cb (err);
466
+ const F = new Factory (_opts, cl);
467
+ F.async_init (err => cb (null, F));
468
+ });
469
+ }
470
+
471
+ module.exports = creator;
472
+
473
+
474
+
475
+
476
+