ravenflow 0.4.0 → 0.4.1
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/dist/index.d.mts +13 -162
- package/dist/index.d.ts +13 -162
- package/dist/index.js +616 -629
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +617 -630
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { Redis } from 'ioredis';
|
|
2
3
|
import pino from 'pino';
|
|
3
4
|
import { MongoClient, ObjectId, MongoBulkWriteError, BSON } from 'mongodb';
|
|
4
|
-
import { Queue, Worker, MetricsTime,
|
|
5
|
-
import { Redis } from 'ioredis';
|
|
5
|
+
import { Queue, Worker, MetricsTime, Job, isRedisInstance, DelayedError } from 'bullmq';
|
|
6
6
|
import { format, inspect } from 'util';
|
|
7
7
|
|
|
8
8
|
// src/config/validate-config/schemas/config.schema.ts
|
|
@@ -260,145 +260,6 @@ function throwIfAny(errors) {
|
|
|
260
260
|
throw new Error(`[ravenflow] Config validation failed:
|
|
261
261
|
- ${errors.join("\n - ")}`);
|
|
262
262
|
}
|
|
263
|
-
function createLogger(options = {}) {
|
|
264
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
265
|
-
const usePretty = options.pretty ?? isDev;
|
|
266
|
-
return pino({
|
|
267
|
-
level: options.level ?? (isDev ? "debug" : "info"),
|
|
268
|
-
transport: usePretty ? {
|
|
269
|
-
target: "pino-pretty",
|
|
270
|
-
options: {
|
|
271
|
-
colorize: true,
|
|
272
|
-
translateTime: "HH:MM:ss",
|
|
273
|
-
ignore: "pid,hostname,job",
|
|
274
|
-
messageFormat: "{if job}({job}) {end}{msg}"
|
|
275
|
-
}
|
|
276
|
-
} : void 0
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
var MongoConnection = class {
|
|
280
|
-
constructor(uri) {
|
|
281
|
-
this.uri = uri;
|
|
282
|
-
this.client = null;
|
|
283
|
-
this.database = null;
|
|
284
|
-
this.connecting = null;
|
|
285
|
-
}
|
|
286
|
-
async connect() {
|
|
287
|
-
if (this.database) return this.database;
|
|
288
|
-
this.connecting ?? (this.connecting = this.openTheDatabase().catch(
|
|
289
|
-
(error) => this.forgetTheAttempt(error)
|
|
290
|
-
));
|
|
291
|
-
return this.connecting;
|
|
292
|
-
}
|
|
293
|
-
async close() {
|
|
294
|
-
const closing = this.client;
|
|
295
|
-
this.client = null;
|
|
296
|
-
this.database = null;
|
|
297
|
-
this.connecting = null;
|
|
298
|
-
await closing?.close();
|
|
299
|
-
}
|
|
300
|
-
async openTheDatabase() {
|
|
301
|
-
const opened = new MongoClient(this.uri);
|
|
302
|
-
await opened.connect();
|
|
303
|
-
this.client = opened;
|
|
304
|
-
this.database = opened.db();
|
|
305
|
-
return this.database;
|
|
306
|
-
}
|
|
307
|
-
forgetTheAttempt(error) {
|
|
308
|
-
this.connecting = null;
|
|
309
|
-
throw error;
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// src/queues/declared-queues.ts
|
|
314
|
-
function declaredQueueNames(config) {
|
|
315
|
-
const queueOfEachJob = Object.values(config.jobs).map(
|
|
316
|
-
(job) => queueOf(job, config)
|
|
317
|
-
);
|
|
318
|
-
return [.../* @__PURE__ */ new Set([config.defaultQueue, ...queueOfEachJob])];
|
|
319
|
-
}
|
|
320
|
-
function jobsDeclaredOn(queue, config) {
|
|
321
|
-
return Object.fromEntries(
|
|
322
|
-
Object.entries(config.jobs).filter(
|
|
323
|
-
([, job]) => queueOf(job, config) === queue
|
|
324
|
-
)
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
function queueOf(job, config) {
|
|
328
|
-
return job.queue ?? config.defaultQueue;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// src/queues/open-queues.ts
|
|
332
|
-
function openQueues(config, connection) {
|
|
333
|
-
const settings = { connection, retention: config.queuesRetention };
|
|
334
|
-
return Object.fromEntries(
|
|
335
|
-
declaredQueueNames(config).map((name) => [
|
|
336
|
-
name,
|
|
337
|
-
openQueue(name, settings)
|
|
338
|
-
])
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
function openQueue(name, settings) {
|
|
342
|
-
return new Queue(name, {
|
|
343
|
-
connection: settings.connection,
|
|
344
|
-
defaultJobOptions: defaultJobOptionsFor(settings.retention)
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
function defaultJobOptionsFor(retention = {}) {
|
|
348
|
-
return {
|
|
349
|
-
removeOnComplete: {
|
|
350
|
-
count: retention.completedCount ?? DEFAULT_COMPLETED_COUNT_RETENTION
|
|
351
|
-
},
|
|
352
|
-
removeOnFail: {
|
|
353
|
-
count: retention.failedCount ?? DEFAULT_FAILED_COUNT_RETENTION
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// src/queues/close-queues.ts
|
|
359
|
-
async function closeQueues(queues) {
|
|
360
|
-
await Promise.all(Object.values(queues).map((queue) => queue.close()));
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// src/queues/build-job-options.ts
|
|
364
|
-
function buildJobOptions(job) {
|
|
365
|
-
const options = {};
|
|
366
|
-
if (job.attempts !== void 0) options.attempts = job.attempts;
|
|
367
|
-
if (job.backoff !== void 0) options.backoff = backoffOptions(job.backoff);
|
|
368
|
-
return options;
|
|
369
|
-
}
|
|
370
|
-
function backoffOptions(backoff) {
|
|
371
|
-
return {
|
|
372
|
-
type: backoff.type,
|
|
373
|
-
delay: backoff.delayMs,
|
|
374
|
-
jitter: backoff.jitter
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/queues/link-id.ts
|
|
379
|
-
var LINK_SEPARATOR = ".";
|
|
380
|
-
var A_NUMBERED_LINK = /^(.+)\.([1-9]\d*)$/;
|
|
381
|
-
function nextLinkIdOf(linkId) {
|
|
382
|
-
const { runId, link } = linkOfItsRun(linkId);
|
|
383
|
-
return linkIdOf({ runId, link: link + 1 });
|
|
384
|
-
}
|
|
385
|
-
function linkIdOf({ runId, link }) {
|
|
386
|
-
if (link === 0) return runId;
|
|
387
|
-
return `${asACustomJobId(runId)}${LINK_SEPARATOR}${link}`;
|
|
388
|
-
}
|
|
389
|
-
function linkOfItsRun(linkId) {
|
|
390
|
-
const numbered = A_NUMBERED_LINK.exec(linkId);
|
|
391
|
-
if (numbered === null) return { runId: linkId, link: 0 };
|
|
392
|
-
return { runId: numbered[1], link: Number(numbered[2]) };
|
|
393
|
-
}
|
|
394
|
-
function asACustomJobId(runId) {
|
|
395
|
-
if (holdsColonsBullmqRefuses(runId)) return encodeURIComponent(runId);
|
|
396
|
-
return runId;
|
|
397
|
-
}
|
|
398
|
-
function holdsColonsBullmqRefuses(id) {
|
|
399
|
-
const parts = id.split(":").length;
|
|
400
|
-
return parts !== 1 && parts !== 3;
|
|
401
|
-
}
|
|
402
263
|
|
|
403
264
|
// src/control-plane/control-key.ts
|
|
404
265
|
var JOB_PREFIX = "job:";
|
|
@@ -884,83 +745,222 @@ function initControlPlane(runtime) {
|
|
|
884
745
|
});
|
|
885
746
|
return new ControlPlane({ store, channel, logger: runtime.logger });
|
|
886
747
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
return items.map((_, position) => insertedIds[position].toHexString());
|
|
910
|
-
}
|
|
911
|
-
async purge() {
|
|
912
|
-
const backlog = await this.collection();
|
|
913
|
-
const { deletedCount } = await backlog.deleteMany(NEVER_CLAIMED);
|
|
914
|
-
return {
|
|
915
|
-
dropped: deletedCount,
|
|
916
|
-
inFlight: await backlog.countDocuments(CLAIMED)
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
async indexForTheClaim() {
|
|
920
|
-
const backlog = await this.collection();
|
|
921
|
-
await backlog.createIndex(CLAIM_INDEX);
|
|
922
|
-
}
|
|
923
|
-
async countInFlight() {
|
|
924
|
-
const backlog = await this.collection();
|
|
925
|
-
return backlog.countDocuments(CLAIMED);
|
|
748
|
+
function createLogger(options = {}) {
|
|
749
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
750
|
+
const usePretty = options.pretty ?? isDev;
|
|
751
|
+
return pino({
|
|
752
|
+
level: options.level ?? (isDev ? "debug" : "info"),
|
|
753
|
+
transport: usePretty ? {
|
|
754
|
+
target: "pino-pretty",
|
|
755
|
+
options: {
|
|
756
|
+
colorize: true,
|
|
757
|
+
translateTime: "HH:MM:ss",
|
|
758
|
+
ignore: "pid,hostname,job",
|
|
759
|
+
messageFormat: "{if job}({job}) {end}{msg}"
|
|
760
|
+
}
|
|
761
|
+
} : void 0
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
var MongoConnection = class {
|
|
765
|
+
constructor(uri) {
|
|
766
|
+
this.uri = uri;
|
|
767
|
+
this.client = null;
|
|
768
|
+
this.database = null;
|
|
769
|
+
this.connecting = null;
|
|
926
770
|
}
|
|
927
|
-
async
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
return deletedCount;
|
|
771
|
+
async connect() {
|
|
772
|
+
if (this.database) return this.database;
|
|
773
|
+
this.connecting ?? (this.connecting = this.openTheDatabase().catch(
|
|
774
|
+
(error) => this.forgetTheAttempt(error)
|
|
775
|
+
));
|
|
776
|
+
return this.connecting;
|
|
934
777
|
}
|
|
935
|
-
async
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
return this.claimAll(stale, staleClaim, now);
|
|
778
|
+
async close() {
|
|
779
|
+
const closing = this.client;
|
|
780
|
+
this.client = null;
|
|
781
|
+
this.database = null;
|
|
782
|
+
this.connecting = null;
|
|
783
|
+
await closing?.close();
|
|
942
784
|
}
|
|
943
|
-
async
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
785
|
+
async openTheDatabase() {
|
|
786
|
+
const opened = new MongoClient(this.uri);
|
|
787
|
+
await opened.connect();
|
|
788
|
+
this.client = opened;
|
|
789
|
+
this.database = opened.db();
|
|
790
|
+
return this.database;
|
|
948
791
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
await backlog.deleteMany({ _id: { $in: ids } });
|
|
792
|
+
forgetTheAttempt(error) {
|
|
793
|
+
this.connecting = null;
|
|
794
|
+
throw error;
|
|
953
795
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
// src/queues/declared-queues.ts
|
|
799
|
+
function declaredQueueNames(config) {
|
|
800
|
+
const queueOfEachJob = Object.values(config.jobs).map(
|
|
801
|
+
(job) => queueOf(job, config)
|
|
802
|
+
);
|
|
803
|
+
return [.../* @__PURE__ */ new Set([config.defaultQueue, ...queueOfEachJob])];
|
|
804
|
+
}
|
|
805
|
+
function jobsDeclaredOn(queue, config) {
|
|
806
|
+
return Object.fromEntries(
|
|
807
|
+
Object.entries(config.jobs).filter(
|
|
808
|
+
([, job]) => queueOf(job, config) === queue
|
|
809
|
+
)
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
function queueOf(job, config) {
|
|
813
|
+
return job.queue ?? config.defaultQueue;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/queues/open-queues.ts
|
|
817
|
+
function openQueues(config, connection) {
|
|
818
|
+
const settings = { connection, retention: config.queuesRetention };
|
|
819
|
+
return Object.fromEntries(
|
|
820
|
+
declaredQueueNames(config).map((name) => [
|
|
821
|
+
name,
|
|
822
|
+
openQueue(name, settings)
|
|
823
|
+
])
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
function openQueue(name, settings) {
|
|
827
|
+
return new Queue(name, {
|
|
828
|
+
connection: settings.connection,
|
|
829
|
+
defaultJobOptions: defaultJobOptionsFor(settings.retention)
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
function defaultJobOptionsFor(retention = {}) {
|
|
833
|
+
return {
|
|
834
|
+
removeOnComplete: {
|
|
835
|
+
count: retention.completedCount ?? DEFAULT_COMPLETED_COUNT_RETENTION
|
|
836
|
+
},
|
|
837
|
+
removeOnFail: {
|
|
838
|
+
count: retention.failedCount ?? DEFAULT_FAILED_COUNT_RETENTION
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/queues/close-queues.ts
|
|
844
|
+
async function closeQueues(queues) {
|
|
845
|
+
await Promise.all(Object.values(queues).map((queue) => queue.close()));
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/queues/build-job-options.ts
|
|
849
|
+
function buildJobOptions(job) {
|
|
850
|
+
const options = {};
|
|
851
|
+
if (job.attempts !== void 0) options.attempts = job.attempts;
|
|
852
|
+
if (job.backoff !== void 0) options.backoff = backoffOptions(job.backoff);
|
|
853
|
+
return options;
|
|
854
|
+
}
|
|
855
|
+
function backoffOptions(backoff) {
|
|
856
|
+
return {
|
|
857
|
+
type: backoff.type,
|
|
858
|
+
delay: backoff.delayMs,
|
|
859
|
+
jitter: backoff.jitter
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/queues/link-id.ts
|
|
864
|
+
var LINK_SEPARATOR = ".";
|
|
865
|
+
var A_NUMBERED_LINK = /^(.+)\.([1-9]\d*)$/;
|
|
866
|
+
function nextLinkIdOf(linkId) {
|
|
867
|
+
const { runId, link } = linkOfItsRun(linkId);
|
|
868
|
+
return linkIdOf({ runId, link: link + 1 });
|
|
869
|
+
}
|
|
870
|
+
function linkIdOf({ runId, link }) {
|
|
871
|
+
if (link === 0) return runId;
|
|
872
|
+
return `${asACustomJobId(runId)}${LINK_SEPARATOR}${link}`;
|
|
873
|
+
}
|
|
874
|
+
function linkOfItsRun(linkId) {
|
|
875
|
+
const numbered = A_NUMBERED_LINK.exec(linkId);
|
|
876
|
+
if (numbered === null) return { runId: linkId, link: 0 };
|
|
877
|
+
return { runId: numbered[1], link: Number(numbered[2]) };
|
|
878
|
+
}
|
|
879
|
+
function asACustomJobId(runId) {
|
|
880
|
+
if (holdsColonsBullmqRefuses(runId)) return encodeURIComponent(runId);
|
|
881
|
+
return runId;
|
|
882
|
+
}
|
|
883
|
+
function holdsColonsBullmqRefuses(id) {
|
|
884
|
+
const parts = id.split(":").length;
|
|
885
|
+
return parts !== 1 && parts !== 3;
|
|
886
|
+
}
|
|
887
|
+
var BACKLOG_COLLECTION_PREFIX = "rav:spool-backlog:";
|
|
888
|
+
var NEVER_CLAIMED = { claimedAt: { $exists: false } };
|
|
889
|
+
var CLAIMED = { claimedAt: { $type: "date" } };
|
|
890
|
+
var CLAIM_INDEX = { claimedAt: 1, _id: 1 };
|
|
891
|
+
var OLDEST_FIRST = { _id: 1 };
|
|
892
|
+
var MS_PER_SECOND = 1e3;
|
|
893
|
+
var Backlog = class {
|
|
894
|
+
constructor(spoolName, connection) {
|
|
895
|
+
this.spoolName = spoolName;
|
|
896
|
+
this.connection = connection;
|
|
897
|
+
}
|
|
898
|
+
async push(item) {
|
|
899
|
+
const backlog = await this.collection();
|
|
900
|
+
const { insertedId } = await backlog.insertOne({ item });
|
|
901
|
+
return insertedId.toHexString();
|
|
902
|
+
}
|
|
903
|
+
async pushMany(items) {
|
|
904
|
+
if (items.length === 0) return [];
|
|
905
|
+
const backlog = await this.collection();
|
|
906
|
+
const { insertedIds } = await backlog.insertMany(
|
|
907
|
+
items.map((item) => ({ item }))
|
|
908
|
+
);
|
|
909
|
+
return items.map((_, position) => insertedIds[position].toHexString());
|
|
910
|
+
}
|
|
911
|
+
async purge() {
|
|
912
|
+
const backlog = await this.collection();
|
|
913
|
+
const { deletedCount } = await backlog.deleteMany(NEVER_CLAIMED);
|
|
914
|
+
return {
|
|
915
|
+
dropped: deletedCount,
|
|
916
|
+
inFlight: await backlog.countDocuments(CLAIMED)
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
async indexForTheClaim() {
|
|
920
|
+
const backlog = await this.collection();
|
|
921
|
+
await backlog.createIndex(CLAIM_INDEX);
|
|
922
|
+
}
|
|
923
|
+
async countInFlight() {
|
|
924
|
+
const backlog = await this.collection();
|
|
925
|
+
return backlog.countDocuments(CLAIMED);
|
|
926
|
+
}
|
|
927
|
+
async expireUnclaimedPushedBefore(pushedBefore) {
|
|
928
|
+
const backlog = await this.collection();
|
|
929
|
+
const { deletedCount } = await backlog.deleteMany({
|
|
930
|
+
...NEVER_CLAIMED,
|
|
931
|
+
_id: { $lt: ObjectId.createFromTime(secondsOf(pushedBefore)) }
|
|
932
|
+
});
|
|
933
|
+
return deletedCount;
|
|
934
|
+
}
|
|
935
|
+
async reclaimStaleClaims(claimedBefore, now) {
|
|
936
|
+
const staleClaim = {
|
|
937
|
+
claimedAt: { $lte: claimedBefore }
|
|
938
|
+
};
|
|
939
|
+
const backlog = await this.collection();
|
|
940
|
+
const stale = await backlog.find(staleClaim).toArray();
|
|
941
|
+
return this.claimAll(stale, staleClaim, now);
|
|
942
|
+
}
|
|
943
|
+
async claimOldestNeverClaimed(count, now) {
|
|
944
|
+
if (count <= 0) return [];
|
|
945
|
+
const backlog = await this.collection();
|
|
946
|
+
const oldest = await backlog.find(NEVER_CLAIMED, { sort: OLDEST_FIRST, limit: count }).toArray();
|
|
947
|
+
return this.claimAll(oldest, NEVER_CLAIMED, now);
|
|
948
|
+
}
|
|
949
|
+
async removeAll(ids) {
|
|
950
|
+
if (ids.length === 0) return;
|
|
951
|
+
const backlog = await this.collection();
|
|
952
|
+
await backlog.deleteMany({ _id: { $in: ids } });
|
|
953
|
+
}
|
|
954
|
+
async claimAll(entries, stillClaimable, claimedAt) {
|
|
955
|
+
if (entries.length === 0) return [];
|
|
956
|
+
const ids = entries.map(({ _id }) => _id);
|
|
957
|
+
const backlog = await this.collection();
|
|
958
|
+
const { modifiedCount } = await backlog.updateMany(
|
|
959
|
+
{ _id: { $in: ids }, ...stillClaimable },
|
|
960
|
+
{ $set: { claimedAt } }
|
|
961
|
+
);
|
|
962
|
+
if (modifiedCount < entries.length) {
|
|
963
|
+
return this.whatThisClaimTook(ids, claimedAt);
|
|
964
964
|
}
|
|
965
965
|
return entries;
|
|
966
966
|
}
|
|
@@ -1172,172 +1172,68 @@ function buildSendOrIngest(job, controls) {
|
|
|
1172
1172
|
return { send: buildSend(job) };
|
|
1173
1173
|
}
|
|
1174
1174
|
|
|
1175
|
-
// src/client/ravenflow-client.ts
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1175
|
+
// src/client/create-ravenflow-client.ts
|
|
1176
|
+
function createRavenflowClient(config, options) {
|
|
1177
|
+
validateConfig(config);
|
|
1178
|
+
const logger = createLogger(config.logger);
|
|
1179
|
+
const mongo = new MongoConnection(options.mongo);
|
|
1180
|
+
const queues = openQueues(config, options.redis);
|
|
1181
|
+
const controlPlane = initControlPlane({ redis: options.redis, mongo, logger });
|
|
1182
|
+
return {
|
|
1183
|
+
jobs: initJobProxies(config, { queues, mongo, controls: controlPlane }),
|
|
1184
|
+
close: async () => {
|
|
1185
|
+
await controlPlane.close();
|
|
1186
|
+
await closeQueues(queues);
|
|
1187
|
+
await mongo.close();
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/worker/control-plane-reader.ts
|
|
1193
|
+
var ControlPlaneReader = class {
|
|
1194
|
+
constructor(settings) {
|
|
1195
|
+
this.mirrorConnection = new MongoConnection(settings.mongo);
|
|
1185
1196
|
this.controlPlane = initControlPlane({
|
|
1186
|
-
redis:
|
|
1187
|
-
mongo: this.
|
|
1188
|
-
logger:
|
|
1189
|
-
});
|
|
1190
|
-
this.jobs = initJobProxies(config, {
|
|
1191
|
-
queues: this.queues,
|
|
1192
|
-
mongo: this.mongo,
|
|
1193
|
-
controls: this.controlPlane
|
|
1197
|
+
redis: settings.redis,
|
|
1198
|
+
mongo: this.mirrorConnection,
|
|
1199
|
+
logger: settings.logger
|
|
1194
1200
|
});
|
|
1195
1201
|
}
|
|
1202
|
+
async open() {
|
|
1203
|
+
await this.controlPlane.rehydrate();
|
|
1204
|
+
await this.mirrorConnection.close();
|
|
1205
|
+
}
|
|
1206
|
+
read(jobName) {
|
|
1207
|
+
return this.controlPlane.read(jobName);
|
|
1208
|
+
}
|
|
1196
1209
|
async close() {
|
|
1197
1210
|
await this.controlPlane.close();
|
|
1198
|
-
await
|
|
1199
|
-
await this.mongo.close();
|
|
1211
|
+
await this.mirrorConnection.close();
|
|
1200
1212
|
}
|
|
1201
1213
|
};
|
|
1202
1214
|
|
|
1203
|
-
// src/
|
|
1204
|
-
function
|
|
1205
|
-
|
|
1215
|
+
// src/worker/log-bullmq-errors.ts
|
|
1216
|
+
function logBullmqErrors(source, logger) {
|
|
1217
|
+
source.on("error", (error) => {
|
|
1218
|
+
logger.error(
|
|
1219
|
+
{ err: error, queue: source.name },
|
|
1220
|
+
`[ravenflow] BullMQ reported an error on "${source.name}".`
|
|
1221
|
+
);
|
|
1222
|
+
});
|
|
1206
1223
|
}
|
|
1207
1224
|
|
|
1208
|
-
// src/
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
1215
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551
|
|
1216
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551
|
|
1217
|
-
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D
|
|
1218
|
-
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D
|
|
1219
|
-
`;
|
|
1220
|
-
var RULE = "\u2550".repeat(85);
|
|
1221
|
-
var FEEDS_IN_ORDER = ["nothing", "clock", "spool"];
|
|
1222
|
-
function printBanner(facts) {
|
|
1223
|
-
console.log(
|
|
1224
|
-
[
|
|
1225
|
-
RAVENFLOW,
|
|
1226
|
-
RULE,
|
|
1227
|
-
"[Config]",
|
|
1228
|
-
...configLines(facts),
|
|
1229
|
-
"",
|
|
1230
|
-
"[Registered Jobs]",
|
|
1231
|
-
...registeredJobLines(facts.jobs),
|
|
1232
|
-
"",
|
|
1233
|
-
"[Scheduled Jobs]",
|
|
1234
|
-
...schedulerLines(facts.schedulers),
|
|
1235
|
-
RULE,
|
|
1236
|
-
"Ready to process jobs..."
|
|
1237
|
-
].join("\n")
|
|
1238
|
-
);
|
|
1225
|
+
// src/worker/mongodb-deadline-on-close.ts
|
|
1226
|
+
var MONGODB_DEADLINE_ON_CLOSE_MS = 5e3;
|
|
1227
|
+
async function waitOrCutShort(waitingOnMongoDB, cutItShort) {
|
|
1228
|
+
if (await settlesBeforeTheDeadline(waitingOnMongoDB)) return;
|
|
1229
|
+
await cutItShort();
|
|
1230
|
+
await waitingOnMongoDB;
|
|
1239
1231
|
}
|
|
1240
|
-
function
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
` -> mongo: ${mongoLineOf(facts)}`
|
|
1246
|
-
];
|
|
1247
|
-
}
|
|
1248
|
-
function addressOf(redis) {
|
|
1249
|
-
if (isRedisInstance(redis)) return "an ioredis client";
|
|
1250
|
-
const {
|
|
1251
|
-
url,
|
|
1252
|
-
host = "localhost",
|
|
1253
|
-
port = 6379,
|
|
1254
|
-
db = 0
|
|
1255
|
-
} = redis;
|
|
1256
|
-
return url ? withoutPassword(url) : `${host}:${port}/${db}`;
|
|
1257
|
-
}
|
|
1258
|
-
function mongoLineOf({ mongo, recorderOpening }) {
|
|
1259
|
-
const address = withoutPassword(mongo);
|
|
1260
|
-
if (!recorderOpening.opened) {
|
|
1261
|
-
return `${address} (job results are not recorded: ${recorderOpening.because})`;
|
|
1262
|
-
}
|
|
1263
|
-
return address;
|
|
1264
|
-
}
|
|
1265
|
-
function withoutPassword(uri) {
|
|
1266
|
-
return uri.replace(/\/\/([^:/@]*):[^@]*@/, "//$1:***@");
|
|
1267
|
-
}
|
|
1268
|
-
function registeredJobLines(jobs) {
|
|
1269
|
-
const lines = Object.entries(jobs).map(([name, job]) => ({ name, feed: feedOf(job) })).sort((a, b) => feedRank(a.feed) - feedRank(b.feed)).map(({ name, feed }) => ` . ${name}${feed.label}`);
|
|
1270
|
-
return lines.length > 0 ? lines : [" (none)"];
|
|
1271
|
-
}
|
|
1272
|
-
function feedOf(job) {
|
|
1273
|
-
const cron = job.trigger?.cron;
|
|
1274
|
-
if (cron) return { kind: "clock", label: ` (\u21BB ${cron})` };
|
|
1275
|
-
if (isSpoolFed(job)) return { kind: "spool", label: spoolLabelOf(job) };
|
|
1276
|
-
return { kind: "nothing", label: "" };
|
|
1277
|
-
}
|
|
1278
|
-
function spoolLabelOf({ trigger, pace }) {
|
|
1279
|
-
const ceiling = inFlightCeilingOf(trigger.spool, pace);
|
|
1280
|
-
return ` (\u2190 ${trigger.spool.name}, ${ceiling} in flight at most)`;
|
|
1281
|
-
}
|
|
1282
|
-
function feedRank(feed) {
|
|
1283
|
-
return FEEDS_IN_ORDER.indexOf(feed.kind);
|
|
1284
|
-
}
|
|
1285
|
-
function schedulerLines(changes) {
|
|
1286
|
-
const lines = [
|
|
1287
|
-
...changes.added.map(({ name, pattern }) => ` + ${name} \u2192 ${pattern}`),
|
|
1288
|
-
...changes.rescheduled.map(
|
|
1289
|
-
({ name, pattern, previousPattern }) => ` \u21BB ${name} \u2192 ${pattern} (was ${previousPattern})`
|
|
1290
|
-
),
|
|
1291
|
-
...changes.removed.map((name) => ` - ${name}`)
|
|
1292
|
-
];
|
|
1293
|
-
return lines.length > 0 ? lines : [" (no change)"];
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
// src/worker/control-plane-reader.ts
|
|
1297
|
-
var ControlPlaneReader = class {
|
|
1298
|
-
constructor(settings) {
|
|
1299
|
-
this.mirrorConnection = new MongoConnection(settings.mongo);
|
|
1300
|
-
this.controlPlane = initControlPlane({
|
|
1301
|
-
redis: settings.redis,
|
|
1302
|
-
mongo: this.mirrorConnection,
|
|
1303
|
-
logger: settings.logger
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
async open() {
|
|
1307
|
-
await this.controlPlane.rehydrate();
|
|
1308
|
-
await this.mirrorConnection.close();
|
|
1309
|
-
}
|
|
1310
|
-
read(jobName) {
|
|
1311
|
-
return this.controlPlane.read(jobName);
|
|
1312
|
-
}
|
|
1313
|
-
async close() {
|
|
1314
|
-
await this.controlPlane.close();
|
|
1315
|
-
await this.mirrorConnection.close();
|
|
1316
|
-
}
|
|
1317
|
-
};
|
|
1318
|
-
|
|
1319
|
-
// src/worker/log-bullmq-errors.ts
|
|
1320
|
-
function logBullmqErrors(source, logger) {
|
|
1321
|
-
source.on("error", (error) => {
|
|
1322
|
-
logger.error(
|
|
1323
|
-
{ err: error, queue: source.name },
|
|
1324
|
-
`[ravenflow] BullMQ reported an error on "${source.name}".`
|
|
1325
|
-
);
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
// src/worker/mongodb-deadline-on-close.ts
|
|
1330
|
-
var MONGODB_DEADLINE_ON_CLOSE_MS = 5e3;
|
|
1331
|
-
async function waitOrCutShort(waitingOnMongoDB, cutItShort) {
|
|
1332
|
-
if (await settlesBeforeTheDeadline(waitingOnMongoDB)) return;
|
|
1333
|
-
await cutItShort();
|
|
1334
|
-
await waitingOnMongoDB;
|
|
1335
|
-
}
|
|
1336
|
-
async function settlesBeforeTheDeadline(work) {
|
|
1337
|
-
const deadline = new Promise((resolve) => {
|
|
1338
|
-
setTimeout(() => resolve(false), MONGODB_DEADLINE_ON_CLOSE_MS).unref();
|
|
1339
|
-
});
|
|
1340
|
-
return Promise.race([work.then(() => true), deadline]);
|
|
1232
|
+
async function settlesBeforeTheDeadline(work) {
|
|
1233
|
+
const deadline = new Promise((resolve) => {
|
|
1234
|
+
setTimeout(() => resolve(false), MONGODB_DEADLINE_ON_CLOSE_MS).unref();
|
|
1235
|
+
});
|
|
1236
|
+
return Promise.race([work.then(() => true), deadline]);
|
|
1341
1237
|
}
|
|
1342
1238
|
var FLUSH_INTERVAL_MS = 3e4;
|
|
1343
1239
|
var JOB_RESULTS_PER_BATCH = 1e3;
|
|
@@ -2700,100 +2596,6 @@ function abortWhenClosing(worker, shutdown) {
|
|
|
2700
2596
|
shutdown.abort(new Error("[ravenflow] the worker is shutting down"));
|
|
2701
2597
|
});
|
|
2702
2598
|
}
|
|
2703
|
-
|
|
2704
|
-
// src/worker/schedulers.ts
|
|
2705
|
-
async function reconcileSchedulers(queue, jobs) {
|
|
2706
|
-
const declared = cronSchedulesOf(jobs);
|
|
2707
|
-
const existing = await patternsByScheduler(queue);
|
|
2708
|
-
const changes = changesBetween(declared, existing);
|
|
2709
|
-
await Promise.all(
|
|
2710
|
-
changes.removed.map((key) => queue.removeJobScheduler(key))
|
|
2711
|
-
);
|
|
2712
|
-
await Promise.all(declared.map((schedule) => upsert(queue, schedule)));
|
|
2713
|
-
return changes;
|
|
2714
|
-
}
|
|
2715
|
-
function cronSchedulesOf(jobs) {
|
|
2716
|
-
return Object.entries(jobs).flatMap(([name, job]) => {
|
|
2717
|
-
const pattern = job.trigger?.cron;
|
|
2718
|
-
if (pattern === void 0) return [];
|
|
2719
|
-
return [{ name, pattern, options: buildJobOptions(job) }];
|
|
2720
|
-
});
|
|
2721
|
-
}
|
|
2722
|
-
async function patternsByScheduler(queue) {
|
|
2723
|
-
const schedulers = await queue.getJobSchedulers();
|
|
2724
|
-
return new Map(
|
|
2725
|
-
schedulers.map(({ key, pattern }) => [key, pattern ?? "no pattern"])
|
|
2726
|
-
);
|
|
2727
|
-
}
|
|
2728
|
-
function changesBetween(declared, existing) {
|
|
2729
|
-
const declaredNames = new Set(declared.map(({ name }) => name));
|
|
2730
|
-
return {
|
|
2731
|
-
added: declared.filter(({ name }) => !existing.has(name)),
|
|
2732
|
-
rescheduled: declared.flatMap(
|
|
2733
|
-
(schedule) => reschedulingOf(schedule, existing)
|
|
2734
|
-
),
|
|
2735
|
-
removed: [...existing.keys()].filter((key) => !declaredNames.has(key))
|
|
2736
|
-
};
|
|
2737
|
-
}
|
|
2738
|
-
function reschedulingOf(schedule, existing) {
|
|
2739
|
-
const previousPattern = existing.get(schedule.name);
|
|
2740
|
-
if (previousPattern === void 0) return [];
|
|
2741
|
-
if (previousPattern === schedule.pattern) return [];
|
|
2742
|
-
return [{ ...schedule, previousPattern }];
|
|
2743
|
-
}
|
|
2744
|
-
async function upsert(queue, { name, pattern, options }) {
|
|
2745
|
-
try {
|
|
2746
|
-
await queue.upsertJobScheduler(name, { pattern }, { name, opts: options });
|
|
2747
|
-
} catch (error) {
|
|
2748
|
-
throw new Error(
|
|
2749
|
-
`[ravenflow] "${name}" could not be scheduled on "${pattern}": ${describeError(error)}`
|
|
2750
|
-
);
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
// src/worker/served-queue.ts
|
|
2755
|
-
function refuseAQueueItsConfigDoesNotDeclare(queue, config) {
|
|
2756
|
-
const declared = declaredQueueNames(config);
|
|
2757
|
-
if (declared.includes(queue)) return;
|
|
2758
|
-
const quoted = declared.map((name) => `"${name}"`).join(", ");
|
|
2759
|
-
throw new Error(
|
|
2760
|
-
`[ravenflow] no job of this config runs on "${queue}": its queues are ${quoted}.`
|
|
2761
|
-
);
|
|
2762
|
-
}
|
|
2763
|
-
|
|
2764
|
-
// src/worker/serving.ts
|
|
2765
|
-
async function closeInOrder(serving) {
|
|
2766
|
-
await stopWatchingMongoDB(serving);
|
|
2767
|
-
await stopTheTicks(serving);
|
|
2768
|
-
await stopTheWorker(serving);
|
|
2769
|
-
await writeWhatItsJobsLeft(serving);
|
|
2770
|
-
await closeTheConnections(serving);
|
|
2771
|
-
}
|
|
2772
|
-
async function stopWatchingMongoDB(serving) {
|
|
2773
|
-
await serving.mongoWatch.close();
|
|
2774
|
-
}
|
|
2775
|
-
async function stopTheTicks(serving) {
|
|
2776
|
-
await Promise.all([
|
|
2777
|
-
serving.jobResultsAggregator.stopTicking(),
|
|
2778
|
-
serving.spoolFeeder.stopTicking()
|
|
2779
|
-
]);
|
|
2780
|
-
}
|
|
2781
|
-
async function stopTheWorker(serving) {
|
|
2782
|
-
serving.jobResultsRecorder.stopHoldingJobs();
|
|
2783
|
-
await serving.worker.close();
|
|
2784
|
-
}
|
|
2785
|
-
async function writeWhatItsJobsLeft(serving) {
|
|
2786
|
-
await Promise.all([
|
|
2787
|
-
serving.jobResultsRecorder.close(),
|
|
2788
|
-
serving.spoolFeeder.finishTheRemovals()
|
|
2789
|
-
]);
|
|
2790
|
-
}
|
|
2791
|
-
async function closeTheConnections(serving) {
|
|
2792
|
-
await serving.spoolFeeder.close();
|
|
2793
|
-
await serving.jobResultsAggregator.close();
|
|
2794
|
-
await serving.controlPlaneReader.close();
|
|
2795
|
-
await serving.queue.close();
|
|
2796
|
-
}
|
|
2797
2599
|
var THE_DRIVERS_RECOVERY_LAG_MS = 1e4;
|
|
2798
2600
|
var MongoWatch = class {
|
|
2799
2601
|
constructor(settings) {
|
|
@@ -2915,189 +2717,374 @@ async function callOnWithdraw({ logger, onWithdraw }, reason) {
|
|
|
2915
2717
|
}
|
|
2916
2718
|
}
|
|
2917
2719
|
|
|
2918
|
-
// src/worker/
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2720
|
+
// src/worker/open-serving.ts
|
|
2721
|
+
function openServing(settings, onDurableLoss) {
|
|
2722
|
+
const { config, options, jobs, logger } = settings;
|
|
2723
|
+
const queue = openQueue(options.queue, {
|
|
2724
|
+
connection: options.redis,
|
|
2725
|
+
retention: config.queuesRetention
|
|
2726
|
+
});
|
|
2727
|
+
logBullmqErrors(queue, logger);
|
|
2728
|
+
const controlPlaneReader = buildControlPlaneReader(settings);
|
|
2729
|
+
const jobResultsRecorder = buildJobResultsRecorder(settings, queue);
|
|
2730
|
+
const worker = openWorker(options, {
|
|
2731
|
+
jobs,
|
|
2732
|
+
logger,
|
|
2733
|
+
queue,
|
|
2734
|
+
controls: controlPlaneReader,
|
|
2735
|
+
jobResultsRecorder
|
|
2736
|
+
});
|
|
2737
|
+
return {
|
|
2738
|
+
queue,
|
|
2739
|
+
controlPlaneReader,
|
|
2740
|
+
jobResultsRecorder,
|
|
2741
|
+
jobResultsAggregator: buildJobResultsAggregator(settings),
|
|
2742
|
+
spoolFeeder: buildSpoolFeeder(settings, { queue, controlPlaneReader }),
|
|
2743
|
+
mongoWatch: buildMongoWatch(settings, onDurableLoss),
|
|
2744
|
+
worker
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
function buildControlPlaneReader({
|
|
2748
|
+
options,
|
|
2749
|
+
logger
|
|
2750
|
+
}) {
|
|
2751
|
+
return new ControlPlaneReader({
|
|
2752
|
+
redis: options.redis,
|
|
2753
|
+
mongo: options.mongo,
|
|
2754
|
+
logger
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
function buildJobResultsRecorder({ config, options, jobs, logger }, queue) {
|
|
2758
|
+
return new JobResultsRecorder({
|
|
2759
|
+
mongo: options.mongo,
|
|
2760
|
+
retentionHours: config.jobResultsRetentionHours ?? DEFAULT_JOB_RESULTS_RETENTION_HOURS,
|
|
2761
|
+
queue,
|
|
2762
|
+
jobs,
|
|
2763
|
+
logger
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
function buildJobResultsAggregator({
|
|
2767
|
+
config,
|
|
2768
|
+
options,
|
|
2769
|
+
logger
|
|
2770
|
+
}) {
|
|
2771
|
+
return new JobResultsAggregator({
|
|
2772
|
+
redis: options.redis,
|
|
2773
|
+
mongo: options.mongo,
|
|
2774
|
+
retentionHours: config.aggregatedResultsRetentionHours ?? DEFAULT_AGGREGATED_RESULTS_RETENTION_HOURS,
|
|
2775
|
+
logger
|
|
2776
|
+
});
|
|
2777
|
+
}
|
|
2778
|
+
function buildSpoolFeeder({ options, jobs, logger }, { queue, controlPlaneReader }) {
|
|
2779
|
+
return new SpoolFeeder({
|
|
2780
|
+
redis: options.redis,
|
|
2781
|
+
mongo: options.mongo,
|
|
2782
|
+
queue,
|
|
2783
|
+
controls: controlPlaneReader,
|
|
2784
|
+
jobs,
|
|
2785
|
+
logger
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
function buildMongoWatch({ options, logger }, onDurableLoss) {
|
|
2789
|
+
return new MongoWatch({
|
|
2790
|
+
mongo: options.mongo,
|
|
2791
|
+
gracePeriodMs: options.withdrawal?.afterMs ?? DEFAULT_GRACE_PERIOD_MS,
|
|
2792
|
+
logger,
|
|
2793
|
+
onDurableLoss
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
// src/worker/served-queue.ts
|
|
2798
|
+
function refuseAQueueItsConfigDoesNotDeclare(queue, config) {
|
|
2799
|
+
const declared = declaredQueueNames(config);
|
|
2800
|
+
if (declared.includes(queue)) return;
|
|
2801
|
+
const quoted = declared.map((name) => `"${name}"`).join(", ");
|
|
2802
|
+
throw new Error(
|
|
2803
|
+
`[ravenflow] no job of this config runs on "${queue}": its queues are ${quoted}.`
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
// src/describe-error.ts
|
|
2808
|
+
function describeError(error) {
|
|
2809
|
+
return error instanceof Error ? error.message : String(error);
|
|
2810
|
+
}
|
|
2811
|
+
var RAVENFLOW = `
|
|
2812
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
|
|
2813
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
2814
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551
|
|
2815
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551
|
|
2816
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D
|
|
2817
|
+
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D
|
|
2818
|
+
`;
|
|
2819
|
+
var RULE = "\u2550".repeat(85);
|
|
2820
|
+
var FEEDS_IN_ORDER = ["nothing", "clock", "spool"];
|
|
2821
|
+
function printBanner(facts) {
|
|
2822
|
+
console.log(
|
|
2823
|
+
[
|
|
2824
|
+
RAVENFLOW,
|
|
2825
|
+
RULE,
|
|
2826
|
+
"[Config]",
|
|
2827
|
+
...configLines(facts),
|
|
2828
|
+
"",
|
|
2829
|
+
"[Registered Jobs]",
|
|
2830
|
+
...registeredJobLines(facts.jobs),
|
|
2831
|
+
"",
|
|
2832
|
+
"[Scheduled Jobs]",
|
|
2833
|
+
...schedulerLines(facts.schedulers),
|
|
2834
|
+
RULE,
|
|
2835
|
+
"Ready to process jobs..."
|
|
2836
|
+
].join("\n")
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
function configLines(facts) {
|
|
2840
|
+
return [
|
|
2841
|
+
` -> queue: ${facts.queue}`,
|
|
2842
|
+
` -> concurrency: ${facts.concurrency}`,
|
|
2843
|
+
` -> redis: ${addressOf(facts.redis)}`,
|
|
2844
|
+
` -> mongo: ${mongoLineOf(facts)}`
|
|
2845
|
+
];
|
|
2846
|
+
}
|
|
2847
|
+
function addressOf(redis) {
|
|
2848
|
+
if (isRedisInstance(redis)) return "an ioredis client";
|
|
2849
|
+
const {
|
|
2850
|
+
url,
|
|
2851
|
+
host = "localhost",
|
|
2852
|
+
port = 6379,
|
|
2853
|
+
db = 0
|
|
2854
|
+
} = redis;
|
|
2855
|
+
return url ? withoutPassword(url) : `${host}:${port}/${db}`;
|
|
2856
|
+
}
|
|
2857
|
+
function mongoLineOf({ mongo, recorderOpening }) {
|
|
2858
|
+
const address = withoutPassword(mongo);
|
|
2859
|
+
if (!recorderOpening.opened) {
|
|
2860
|
+
return `${address} (job results are not recorded: ${recorderOpening.because})`;
|
|
2861
|
+
}
|
|
2862
|
+
return address;
|
|
2863
|
+
}
|
|
2864
|
+
function withoutPassword(uri) {
|
|
2865
|
+
return uri.replace(/\/\/([^:/@]*):[^@]*@/, "//$1:***@");
|
|
2866
|
+
}
|
|
2867
|
+
function registeredJobLines(jobs) {
|
|
2868
|
+
const lines = Object.entries(jobs).map(([name, job]) => ({ name, feed: feedOf(job) })).sort((a, b) => feedRank(a.feed) - feedRank(b.feed)).map(({ name, feed }) => ` . ${name}${feed.label}`);
|
|
2869
|
+
return lines.length > 0 ? lines : [" (none)"];
|
|
2870
|
+
}
|
|
2871
|
+
function feedOf(job) {
|
|
2872
|
+
const cron = job.trigger?.cron;
|
|
2873
|
+
if (cron) return { kind: "clock", label: ` (\u21BB ${cron})` };
|
|
2874
|
+
if (isSpoolFed(job)) return { kind: "spool", label: spoolLabelOf(job) };
|
|
2875
|
+
return { kind: "nothing", label: "" };
|
|
2876
|
+
}
|
|
2877
|
+
function spoolLabelOf({ trigger, pace }) {
|
|
2878
|
+
const ceiling = inFlightCeilingOf(trigger.spool, pace);
|
|
2879
|
+
return ` (\u2190 ${trigger.spool.name}, ${ceiling} in flight at most)`;
|
|
2880
|
+
}
|
|
2881
|
+
function feedRank(feed) {
|
|
2882
|
+
return FEEDS_IN_ORDER.indexOf(feed.kind);
|
|
2883
|
+
}
|
|
2884
|
+
function schedulerLines(changes) {
|
|
2885
|
+
const lines = [
|
|
2886
|
+
...changes.added.map(({ name, pattern }) => ` + ${name} \u2192 ${pattern}`),
|
|
2887
|
+
...changes.rescheduled.map(
|
|
2888
|
+
({ name, pattern, previousPattern }) => ` \u21BB ${name} \u2192 ${pattern} (was ${previousPattern})`
|
|
2889
|
+
),
|
|
2890
|
+
...changes.removed.map((name) => ` - ${name}`)
|
|
2891
|
+
];
|
|
2892
|
+
return lines.length > 0 ? lines : [" (no change)"];
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// src/worker/schedulers.ts
|
|
2896
|
+
async function reconcileSchedulers(queue, jobs) {
|
|
2897
|
+
const declared = cronSchedulesOf(jobs);
|
|
2898
|
+
const existing = await patternsByScheduler(queue);
|
|
2899
|
+
const changes = changesBetween(declared, existing);
|
|
2900
|
+
await Promise.all(
|
|
2901
|
+
changes.removed.map((key) => queue.removeJobScheduler(key))
|
|
2902
|
+
);
|
|
2903
|
+
await Promise.all(declared.map((schedule) => upsert(queue, schedule)));
|
|
2904
|
+
return changes;
|
|
2905
|
+
}
|
|
2906
|
+
function cronSchedulesOf(jobs) {
|
|
2907
|
+
return Object.entries(jobs).flatMap(([name, job]) => {
|
|
2908
|
+
const pattern = job.trigger?.cron;
|
|
2909
|
+
if (pattern === void 0) return [];
|
|
2910
|
+
return [{ name, pattern, options: buildJobOptions(job) }];
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
async function patternsByScheduler(queue) {
|
|
2914
|
+
const schedulers = await queue.getJobSchedulers();
|
|
2915
|
+
return new Map(
|
|
2916
|
+
schedulers.map(({ key, pattern }) => [key, pattern ?? "no pattern"])
|
|
2917
|
+
);
|
|
2918
|
+
}
|
|
2919
|
+
function changesBetween(declared, existing) {
|
|
2920
|
+
const declaredNames = new Set(declared.map(({ name }) => name));
|
|
2921
|
+
return {
|
|
2922
|
+
added: declared.filter(({ name }) => !existing.has(name)),
|
|
2923
|
+
rescheduled: declared.flatMap(
|
|
2924
|
+
(schedule) => reschedulingOf(schedule, existing)
|
|
2925
|
+
),
|
|
2926
|
+
removed: [...existing.keys()].filter((key) => !declaredNames.has(key))
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
function reschedulingOf(schedule, existing) {
|
|
2930
|
+
const previousPattern = existing.get(schedule.name);
|
|
2931
|
+
if (previousPattern === void 0) return [];
|
|
2932
|
+
if (previousPattern === schedule.pattern) return [];
|
|
2933
|
+
return [{ ...schedule, previousPattern }];
|
|
2934
|
+
}
|
|
2935
|
+
async function upsert(queue, { name, pattern, options }) {
|
|
2936
|
+
try {
|
|
2937
|
+
await queue.upsertJobScheduler(name, { pattern }, { name, opts: options });
|
|
2938
|
+
} catch (error) {
|
|
2939
|
+
throw new Error(
|
|
2940
|
+
`[ravenflow] "${name}" could not be scheduled on "${pattern}": ${describeError(error)}`
|
|
2941
|
+
);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
// src/worker/serving.ts
|
|
2946
|
+
async function serve(serving, settings) {
|
|
2947
|
+
const { options, jobs } = settings;
|
|
2948
|
+
const { queue, worker } = serving;
|
|
2949
|
+
serving.mongoWatch.open();
|
|
2950
|
+
await serving.controlPlaneReader.open();
|
|
2951
|
+
const recorderOpening = await openRecorder(serving, settings);
|
|
2952
|
+
await serving.jobResultsAggregator.open();
|
|
2953
|
+
await serving.spoolFeeder.open(worker);
|
|
2954
|
+
const schedulers = await reconcileSchedulers(queue, jobs);
|
|
2955
|
+
takeJobs(worker, settings);
|
|
2956
|
+
await worker.waitUntilReady();
|
|
2957
|
+
printBanner({
|
|
2958
|
+
queue: options.queue,
|
|
2959
|
+
concurrency: worker.concurrency,
|
|
2960
|
+
redis: options.redis,
|
|
2961
|
+
mongo: options.mongo,
|
|
2962
|
+
recorderOpening,
|
|
2963
|
+
jobs,
|
|
2964
|
+
schedulers
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
async function openRecorder({ jobResultsRecorder, worker }, settings) {
|
|
2968
|
+
try {
|
|
2969
|
+
await jobResultsRecorder.open(worker);
|
|
2970
|
+
} catch (error) {
|
|
2971
|
+
sayJobResultsAreNotRecorded(settings, error);
|
|
2972
|
+
return { opened: false, because: describeError(error) };
|
|
2973
|
+
}
|
|
2974
|
+
return { opened: true };
|
|
2975
|
+
}
|
|
2976
|
+
function sayJobResultsAreNotRecorded({ options, logger }, error) {
|
|
2977
|
+
logger.error(
|
|
2978
|
+
{ err: error },
|
|
2979
|
+
`[ravenflow] the job results recorder could not start: the worker serves "${options.queue}", and its job results are not recorded.`
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
function takeJobs(worker, { options, logger }) {
|
|
2983
|
+
worker.run().catch((error) => {
|
|
2984
|
+
logger.error(
|
|
2985
|
+
{ err: error },
|
|
2986
|
+
`[ravenflow] the worker serving "${options.queue}" stopped taking jobs.`
|
|
2987
|
+
);
|
|
2988
|
+
});
|
|
2989
|
+
}
|
|
2990
|
+
async function closeInOrder(serving) {
|
|
2991
|
+
await stopWatchingMongoDB(serving);
|
|
2992
|
+
await stopTheTicks(serving);
|
|
2993
|
+
await stopTheWorker(serving);
|
|
2994
|
+
await writeWhatItsJobsLeft(serving);
|
|
2995
|
+
await closeTheConnections(serving);
|
|
2996
|
+
}
|
|
2997
|
+
async function stopWatchingMongoDB(serving) {
|
|
2998
|
+
await serving.mongoWatch.close();
|
|
2999
|
+
}
|
|
3000
|
+
async function stopTheTicks(serving) {
|
|
3001
|
+
await Promise.all([
|
|
3002
|
+
serving.jobResultsAggregator.stopTicking(),
|
|
3003
|
+
serving.spoolFeeder.stopTicking()
|
|
3004
|
+
]);
|
|
3005
|
+
}
|
|
3006
|
+
async function stopTheWorker(serving) {
|
|
3007
|
+
serving.jobResultsRecorder.stopHoldingJobs();
|
|
3008
|
+
await serving.worker.close();
|
|
3009
|
+
}
|
|
3010
|
+
async function writeWhatItsJobsLeft(serving) {
|
|
3011
|
+
await Promise.all([
|
|
3012
|
+
serving.jobResultsRecorder.close(),
|
|
3013
|
+
serving.spoolFeeder.finishTheRemovals()
|
|
3014
|
+
]);
|
|
3015
|
+
}
|
|
3016
|
+
async function closeTheConnections(serving) {
|
|
3017
|
+
await serving.spoolFeeder.close();
|
|
3018
|
+
await serving.jobResultsAggregator.close();
|
|
3019
|
+
await serving.controlPlaneReader.close();
|
|
3020
|
+
await serving.queue.close();
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
// src/worker/create-ravenflow-worker.ts
|
|
3024
|
+
function createRavenflowWorker(config, options) {
|
|
3025
|
+
validateConfig(config);
|
|
3026
|
+
refuseAQueueItsConfigDoesNotDeclare(options.queue, config);
|
|
3027
|
+
return workerOf({
|
|
3028
|
+
config,
|
|
3029
|
+
options,
|
|
3030
|
+
jobs: jobsDeclaredOn(options.queue, config),
|
|
3031
|
+
logger: createLogger(config.logger)
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
function workerOf(settings) {
|
|
3035
|
+
let serving = null;
|
|
3036
|
+
let starting = null;
|
|
3037
|
+
let closing = null;
|
|
3038
|
+
return { start, close };
|
|
3039
|
+
async function start() {
|
|
3040
|
+
if (serving) throw aSecondStart(settings);
|
|
3041
|
+
const opened = openServing(settings, beginAWithdrawal);
|
|
3042
|
+
serving = opened;
|
|
3043
|
+
starting = serveOrStop(opened);
|
|
2936
3044
|
try {
|
|
2937
|
-
await
|
|
3045
|
+
await starting;
|
|
2938
3046
|
} finally {
|
|
2939
|
-
|
|
3047
|
+
starting = null;
|
|
2940
3048
|
}
|
|
2941
3049
|
}
|
|
2942
|
-
async close() {
|
|
2943
|
-
await Promise.allSettled([
|
|
2944
|
-
if (
|
|
2945
|
-
|
|
2946
|
-
await
|
|
3050
|
+
async function close() {
|
|
3051
|
+
await Promise.allSettled([starting]);
|
|
3052
|
+
if (serving === null) return;
|
|
3053
|
+
closing ?? (closing = stopServing(serving));
|
|
3054
|
+
await closing;
|
|
2947
3055
|
}
|
|
2948
|
-
async serveOrStop(
|
|
3056
|
+
async function serveOrStop(opened) {
|
|
2949
3057
|
try {
|
|
2950
|
-
await
|
|
3058
|
+
await serve(opened, settings);
|
|
2951
3059
|
} catch (error) {
|
|
2952
|
-
await
|
|
3060
|
+
await stopServing(opened);
|
|
2953
3061
|
throw error;
|
|
2954
3062
|
}
|
|
2955
3063
|
}
|
|
2956
|
-
async stopServing(
|
|
3064
|
+
async function stopServing(opened) {
|
|
2957
3065
|
try {
|
|
2958
|
-
await closeInOrder(
|
|
3066
|
+
await closeInOrder(opened);
|
|
2959
3067
|
} finally {
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
2964
|
-
refuseASecondStart() {
|
|
2965
|
-
if (!this.serving) return;
|
|
2966
|
-
throw new Error(
|
|
2967
|
-
`[ravenflow] this worker has already started on "${this.options.queue}".`
|
|
2968
|
-
);
|
|
2969
|
-
}
|
|
2970
|
-
openServing() {
|
|
2971
|
-
const queue = openQueue(this.options.queue, {
|
|
2972
|
-
connection: this.options.redis,
|
|
2973
|
-
retention: this.config.queuesRetention
|
|
2974
|
-
});
|
|
2975
|
-
logBullmqErrors(queue, this.logger);
|
|
2976
|
-
const controlPlaneReader = this.buildControlPlaneReader();
|
|
2977
|
-
const jobResultsRecorder = this.buildJobResultsRecorder(queue);
|
|
2978
|
-
const worker = openWorker(this.options, {
|
|
2979
|
-
jobs: this.jobs,
|
|
2980
|
-
logger: this.logger,
|
|
2981
|
-
queue,
|
|
2982
|
-
controls: controlPlaneReader,
|
|
2983
|
-
jobResultsRecorder
|
|
2984
|
-
});
|
|
2985
|
-
return {
|
|
2986
|
-
queue,
|
|
2987
|
-
controlPlaneReader,
|
|
2988
|
-
jobResultsRecorder,
|
|
2989
|
-
jobResultsAggregator: this.buildJobResultsAggregator(),
|
|
2990
|
-
spoolFeeder: this.buildSpoolFeeder(queue, controlPlaneReader),
|
|
2991
|
-
mongoWatch: this.buildMongoWatch(),
|
|
2992
|
-
worker
|
|
2993
|
-
};
|
|
2994
|
-
}
|
|
2995
|
-
buildControlPlaneReader() {
|
|
2996
|
-
return new ControlPlaneReader({
|
|
2997
|
-
redis: this.options.redis,
|
|
2998
|
-
mongo: this.options.mongo,
|
|
2999
|
-
logger: this.logger
|
|
3000
|
-
});
|
|
3001
|
-
}
|
|
3002
|
-
buildJobResultsRecorder(queue) {
|
|
3003
|
-
return new JobResultsRecorder({
|
|
3004
|
-
mongo: this.options.mongo,
|
|
3005
|
-
retentionHours: this.config.jobResultsRetentionHours ?? DEFAULT_JOB_RESULTS_RETENTION_HOURS,
|
|
3006
|
-
queue,
|
|
3007
|
-
jobs: this.jobs,
|
|
3008
|
-
logger: this.logger
|
|
3009
|
-
});
|
|
3010
|
-
}
|
|
3011
|
-
buildJobResultsAggregator() {
|
|
3012
|
-
return new JobResultsAggregator({
|
|
3013
|
-
redis: this.options.redis,
|
|
3014
|
-
mongo: this.options.mongo,
|
|
3015
|
-
retentionHours: this.config.aggregatedResultsRetentionHours ?? DEFAULT_AGGREGATED_RESULTS_RETENTION_HOURS,
|
|
3016
|
-
logger: this.logger
|
|
3017
|
-
});
|
|
3018
|
-
}
|
|
3019
|
-
buildSpoolFeeder(queue, controls) {
|
|
3020
|
-
return new SpoolFeeder({
|
|
3021
|
-
redis: this.options.redis,
|
|
3022
|
-
mongo: this.options.mongo,
|
|
3023
|
-
queue,
|
|
3024
|
-
controls,
|
|
3025
|
-
jobs: this.jobs,
|
|
3026
|
-
logger: this.logger
|
|
3027
|
-
});
|
|
3028
|
-
}
|
|
3029
|
-
buildMongoWatch() {
|
|
3030
|
-
return new MongoWatch({
|
|
3031
|
-
mongo: this.options.mongo,
|
|
3032
|
-
gracePeriodMs: this.options.withdrawal?.afterMs ?? DEFAULT_GRACE_PERIOD_MS,
|
|
3033
|
-
logger: this.logger,
|
|
3034
|
-
onDurableLoss: (unreachableForMs) => this.beginAWithdrawal(unreachableForMs)
|
|
3035
|
-
});
|
|
3036
|
-
}
|
|
3037
|
-
beginAWithdrawal(unreachableForMs) {
|
|
3038
|
-
const withdrawing = {
|
|
3039
|
-
queue: this.options.queue,
|
|
3040
|
-
logger: this.logger,
|
|
3041
|
-
onWithdraw: this.options.withdrawal?.onWithdraw,
|
|
3042
|
-
close: () => this.close()
|
|
3043
|
-
};
|
|
3044
|
-
void withdraw(withdrawing, unreachableForMs);
|
|
3045
|
-
}
|
|
3046
|
-
async serve({
|
|
3047
|
-
queue,
|
|
3048
|
-
controlPlaneReader,
|
|
3049
|
-
jobResultsRecorder,
|
|
3050
|
-
jobResultsAggregator,
|
|
3051
|
-
spoolFeeder,
|
|
3052
|
-
mongoWatch,
|
|
3053
|
-
worker
|
|
3054
|
-
}) {
|
|
3055
|
-
mongoWatch.open();
|
|
3056
|
-
await controlPlaneReader.open();
|
|
3057
|
-
const recorderOpening = await this.openRecorder(jobResultsRecorder, worker);
|
|
3058
|
-
await jobResultsAggregator.open();
|
|
3059
|
-
await spoolFeeder.open(worker);
|
|
3060
|
-
const schedulers = await reconcileSchedulers(queue, this.jobs);
|
|
3061
|
-
this.takeJobs(worker);
|
|
3062
|
-
await worker.waitUntilReady();
|
|
3063
|
-
printBanner({
|
|
3064
|
-
queue: this.options.queue,
|
|
3065
|
-
concurrency: worker.concurrency,
|
|
3066
|
-
redis: this.options.redis,
|
|
3067
|
-
mongo: this.options.mongo,
|
|
3068
|
-
recorderOpening,
|
|
3069
|
-
jobs: this.jobs,
|
|
3070
|
-
schedulers
|
|
3071
|
-
});
|
|
3072
|
-
}
|
|
3073
|
-
async openRecorder(jobResultsRecorder, worker) {
|
|
3074
|
-
try {
|
|
3075
|
-
await jobResultsRecorder.open(worker);
|
|
3076
|
-
} catch (error) {
|
|
3077
|
-
this.sayJobResultsAreNotRecorded(error);
|
|
3078
|
-
return { opened: false, because: describeError(error) };
|
|
3068
|
+
serving = null;
|
|
3069
|
+
closing = null;
|
|
3079
3070
|
}
|
|
3080
|
-
return { opened: true };
|
|
3081
3071
|
}
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
{ err: error },
|
|
3085
|
-
`[ravenflow] the job results recorder could not start: the worker serves "${this.options.queue}", and its job results are not recorded.`
|
|
3086
|
-
);
|
|
3072
|
+
function beginAWithdrawal(unreachableForMs) {
|
|
3073
|
+
void withdraw(withdrawingOf(settings, close), unreachableForMs);
|
|
3087
3074
|
}
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3075
|
+
}
|
|
3076
|
+
function aSecondStart({ options }) {
|
|
3077
|
+
return new Error(
|
|
3078
|
+
`[ravenflow] this worker has already started on "${options.queue}".`
|
|
3079
|
+
);
|
|
3080
|
+
}
|
|
3081
|
+
function withdrawingOf({ options, logger }, close) {
|
|
3082
|
+
return {
|
|
3083
|
+
queue: options.queue,
|
|
3084
|
+
logger,
|
|
3085
|
+
onWithdraw: options.withdrawal?.onWithdraw,
|
|
3086
|
+
close
|
|
3087
|
+
};
|
|
3101
3088
|
}
|
|
3102
3089
|
|
|
3103
3090
|
// src/config/ravenflow-config.ts
|