@tryghost/job-manager 0.8.8
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/LICENSE +21 -0
- package/README.md +136 -0
- package/index.js +1 -0
- package/lib/assemble-bree-job.js +43 -0
- package/lib/is-cron-expression.js +36 -0
- package/lib/job-manager.js +158 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013-2021 Ghost Foundation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Job Manager
|
|
2
|
+
|
|
3
|
+
A manager for jobs (aka tasks) that have to be performed asynchronously, optionally recurring, scheduled or one-off in their nature. The job queue is manage in memory without additional dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
`npm install @tryghost/job-manager --save`
|
|
8
|
+
|
|
9
|
+
or
|
|
10
|
+
|
|
11
|
+
`yarn add @tryghost/job-manager`
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Below is a sample code to wire up job manger and initialize jobs:
|
|
17
|
+
```js
|
|
18
|
+
const JobManager = require('@tryghost/job-manager');
|
|
19
|
+
|
|
20
|
+
const logging = {
|
|
21
|
+
info: console.log,
|
|
22
|
+
warn: console.log,
|
|
23
|
+
error: console.error
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const jobManager = new JobManager(logging);
|
|
27
|
+
|
|
28
|
+
// register a job "function" with queued execution in current event loop
|
|
29
|
+
jobManager.addJob({
|
|
30
|
+
job: printWord(word) => console.log(word),
|
|
31
|
+
name: 'hello',
|
|
32
|
+
offloaded: false
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// register a job "module" with queued execution in current even loop
|
|
36
|
+
jobManager.addJob({
|
|
37
|
+
job:'./path/to/email-module.js',
|
|
38
|
+
data: {email: 'send@here.com'},
|
|
39
|
+
offloaded: false
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// register recurring job which needs execution outside of current event loop
|
|
43
|
+
jobManager.addJob({
|
|
44
|
+
at: 'every 5 minutes',
|
|
45
|
+
job: './path/to/jobs/check-emails.js',
|
|
46
|
+
name: 'email-checker'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// register recurring job with cron syntax running every 5 minutes
|
|
50
|
+
// job needs execution outside of current event loop
|
|
51
|
+
// for cron builder check https://crontab.guru/ (first value is seconds)
|
|
52
|
+
jobManager.addJob({
|
|
53
|
+
at: '0 1/5 * * * *',
|
|
54
|
+
job: './path/to/jobs/check-emails.js',
|
|
55
|
+
name: 'email-checker-cron'
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// register a job to un immediately running outside of current even loop
|
|
59
|
+
jobManager.addJob({
|
|
60
|
+
job: './path/to/jobs/check-emails.js',
|
|
61
|
+
name: 'email-checker-now'
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For more examples of JobManager initialization check [test/examples](https://github.com/TryGhost/Utils/tree/master/packages/job-manager/test/examples) directory.
|
|
66
|
+
|
|
67
|
+
### Job types and definitions
|
|
68
|
+
|
|
69
|
+
There are two types of jobs distinguished based on purpose and environment they run in:
|
|
70
|
+
- **"inline"** - job which is run in the same even loop as the caller. Should be used in situations when there is no even loop blocking operations and no need to manage memory leaks in sandboxed way. Sometimes
|
|
71
|
+
- **"offloaded"** - job which is executed in separate to caller's event loop. For Node >v12 clients it spawns a [Worker thread](https://nodejs.org/dist/latest-v12.x/docs/api/worker_threads.html#worker_threads_new_worker_filename_options), for older Node runtimes it is executed in separate process through [child_process](https://nodejs.org/docs/latest-v10.x/api/child_process.html). Comparing to **inline** jobs, **offloaded** jobs are safer to execute as they are run on a dedicated thread (or process) acting like a sandbox. These jobs also give better utilization of multi-core CPUs. This type of jobs is useful when there are heavy computations needed to be done blocking the event loop or need a sandboxed environment to run in safely. Example jobs would be: statistical information processing, memory intensive computations (e.g. recursive algorithms), processing that requires blocking I/O operations etc.
|
|
72
|
+
|
|
73
|
+
Job manager's instance registers jobs through `addJob` method. The `offloaded` parameter controls if the job is **inline** (executed in the same event loop) or is **offloaded** (executed in worker thread/separate process). By default `offloaded` is set to `true` - creates an "offloaded" job.
|
|
74
|
+
|
|
75
|
+
When `offloaded: false` parameter is passed into `addJob` method, job manager registers an **inline** function for execution in FIFO queue. The job should not be computationally intensive and should have small amount of asynchronous operations. The developer should always account that the function will be executed on the **same event loop, thread and process as caller's process**. **inline** jobs should be [JavaScript functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) or a path to a module that exports a function as default. Note, at the moment it's not possible to defined scheduled or recurring **inline** job.
|
|
76
|
+
|
|
77
|
+
When skipped or `offloaded: true` parameter is passed into `addJob` method, job manager registers execution of an **offloaded** job. The job can be scheduled to run immediately, in the future, or in recurring manner (through `at` parameter). Jobs created this way are managed by [bree](https://github.com/breejs/bree) job scheduling library. For examples of job scripts check out [this section](https://github.com/breejs/bree#nodejs-email-queue-job-scheduling-example) of bree's documentation, test [job examples](https://github.com/TryGhost/Utils/tree/master/packages/job-manager/test/jobs).
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
### Offloaded jobs rules of thumb
|
|
81
|
+
To prevent complications around failed job retries and and handling of specific job states here are some rules that should be followed for all scheduled jobs:
|
|
82
|
+
1. Jobs are **self contained** - meaning job manager should be able to run the job with the state information included within the job's parameters. Job script should look up for the rest of needed information from somewhere else, like a database, API, or file.
|
|
83
|
+
2. Jobs should be [idempotent](https://en.wikipedia.org/wiki/Idempotence) - consequent job executions should be safe.
|
|
84
|
+
3. Job **parameters** should be **kept to the minimum**. When passing large amounts of data around performance can suffer from slow JSON serialization. Also, storage size restrictions that can arise if there is a need to store parameters in the future.Job parameters should be kept to only information that is needed to retrieve the rest of information from somewhere else. For example, it's recommended to pass in only an *id* of the resource that could be fetched from the data storage during job execution or pass in a file path which could be read during execution.
|
|
85
|
+
4. Scheduled **job execution time should not overlap**. It's up to the registering service to assure job execution time does not ecceed time between subsequent scheduled jobs. For example, if job is scheduled to run every 5 minutes it should always run under 5 minutes, otherwise next scheduled job would fail to start.
|
|
86
|
+
|
|
87
|
+
### Offloaded jobs lifecycle
|
|
88
|
+
|
|
89
|
+
Offloaded jobs are running on dedicated worker threads which makes their lifecycle a bit different to inline jobs:
|
|
90
|
+
1. When **starting** a job it's only sharing ENV variables with it's parent process. The job itself is run on an independent JavaScript execution thread. The script has to re-initialize any modules it will use. For example it should take care of: model layer initialization, cache initialization, etc.
|
|
91
|
+
2. When **finishing** work in a job prefer to signal successful termination by sending 'done' message to the parent thread: `parentPort.postMessage('done')` ([example use](https://github.com/TryGhost/Utils/blob/0e423f6c5c69b08d81d470f49de95654d8cc90e3/packages/job-manager/test/jobs/graceful.js#L33-L37)). Finishing work this way terminates the thread through [worker.terminate()]((https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html#worker_threads_worker_terminate)), which logs termination in parent process and flushes any pipes opened in thread.
|
|
92
|
+
3. Jobs that have iterative nature, or need cleanup before interrupting work should allow for **graceful shutdown** by listening on `'cancel'` message coming from parent thread ([example use](https://github.com/TryGhost/Utils/blob/0e423f6c5c69b08d81d470f49de95654d8cc90e3/packages/job-manager/test/jobs/graceful.js#L12-L16)).
|
|
93
|
+
4. When **exceptions** happen and expected outcome is to terminate current job, leave the exception unhandled allowing it to bubble up to the job manager. Unhandled exceptions [terminate current thread](https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html#worker_threads_event_error) and allow for next scheduled job execution to happen.
|
|
94
|
+
|
|
95
|
+
For more nuances on job structure best practices check [bree documentation](https://github.com/breejs/bree#writing-jobs-with-promises-and-async-await).
|
|
96
|
+
|
|
97
|
+
### Offloaded job script quirks
|
|
98
|
+
|
|
99
|
+
⚠️ to ensure worker thread back compatibility and correct inter-thread communication use [btrheads](https://github.com/chjj/bthreads) polyfill instead of native [worker_threads](https://nodejs.org/api/worker_threads.html#worker_threads) module in job scripts.
|
|
100
|
+
|
|
101
|
+
Instead of:
|
|
102
|
+
```js
|
|
103
|
+
const {isMainThread, parentPort} = require('worker_threads');
|
|
104
|
+
```
|
|
105
|
+
use
|
|
106
|
+
```js
|
|
107
|
+
const {isMainThread, parentPort} = require('bthreads');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
It should be possible to use native `worker_threads` module once Node v10 [hits EOL](https://nodejs.org/en/about/releases/) (2021-04-30).
|
|
111
|
+
|
|
112
|
+
## Develop
|
|
113
|
+
|
|
114
|
+
This is a mono repository, managed with [lerna](https://lernajs.io/).
|
|
115
|
+
|
|
116
|
+
Follow the instructions for the top-level repo.
|
|
117
|
+
1. `git clone` this repo & `cd` into it as usual
|
|
118
|
+
2. Run `yarn` to install top-level dependencies.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Run
|
|
122
|
+
|
|
123
|
+
- `yarn dev`
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
## Test
|
|
127
|
+
|
|
128
|
+
- `yarn lint` run just eslint
|
|
129
|
+
- `yarn test` run lint and tests
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Copyright & License
|
|
135
|
+
|
|
136
|
+
Copyright (c) 2013-2021 Ghost Foundation - Released under the [MIT license](LICENSE).
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./lib/job-manager');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const isCronExpression = require('./is-cron-expression');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates job Object compatible with bree job definition (https://github.com/breejs/bree#job-options)
|
|
5
|
+
*
|
|
6
|
+
* @param {String | Date} [at] - Date, cron or human readable schedule format
|
|
7
|
+
* @param {Function|String} job - function or path to a module defining a job
|
|
8
|
+
* @param {Object} [data] - data to be passed into the job
|
|
9
|
+
* @param {String} [name] - job name
|
|
10
|
+
*/
|
|
11
|
+
const assemble = (at, job, data, name) => {
|
|
12
|
+
const breeJob = {
|
|
13
|
+
name: name,
|
|
14
|
+
// NOTE: both function and path syntaxes work with 'path' parameter
|
|
15
|
+
path: job
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (data) {
|
|
19
|
+
Object.assign(breeJob, {
|
|
20
|
+
worker: {
|
|
21
|
+
workerData: data
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (at instanceof Date) {
|
|
27
|
+
Object.assign(breeJob, {
|
|
28
|
+
date: at
|
|
29
|
+
});
|
|
30
|
+
} else if (at && isCronExpression(at)) {
|
|
31
|
+
Object.assign(breeJob, {
|
|
32
|
+
cron: at
|
|
33
|
+
});
|
|
34
|
+
} else if (at !== undefined) {
|
|
35
|
+
Object.assign(breeJob, {
|
|
36
|
+
interval: at
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return breeJob;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
module.exports = assemble;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const cronValidate = require('cron-validate');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if expression follows supported crontab format
|
|
5
|
+
* reference: https://www.adminschoice.com/crontab-quick-reference
|
|
6
|
+
* builder: https://crontab.guru/
|
|
7
|
+
*
|
|
8
|
+
* e.g.:
|
|
9
|
+
* "2 * * * *" where:
|
|
10
|
+
*
|
|
11
|
+
* "* * * * * *"
|
|
12
|
+
* ┬ ┬ ┬ ┬ ┬ ┬
|
|
13
|
+
* │ │ │ │ │ |
|
|
14
|
+
* │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
|
|
15
|
+
* │ │ │ │ └───── month (1 - 12)
|
|
16
|
+
* │ │ │ └────────── day of month (1 - 31)
|
|
17
|
+
* │ │ └─────────────── hour (0 - 23)
|
|
18
|
+
* │ └──────────────────── minute (0 - 59)
|
|
19
|
+
* └───────────────────────── second (0 - 59, optional)
|
|
20
|
+
*
|
|
21
|
+
* @param {String} expression in crontab format (https://www.gnu.org/software/mcron/manual/html_node/Crontab-file.html)
|
|
22
|
+
*
|
|
23
|
+
* @returns {boolean} wheather or not the expression is valid
|
|
24
|
+
*/
|
|
25
|
+
const isCronExpression = (expression) => {
|
|
26
|
+
let cronResult = cronValidate(expression, {
|
|
27
|
+
preset: 'default', // second field not supported in default preset
|
|
28
|
+
override: {
|
|
29
|
+
useSeconds: true // override preset option
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return cronResult.isValid();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
module.exports = isCronExpression;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fastq = require('fastq');
|
|
3
|
+
const later = require('@breejs/later');
|
|
4
|
+
const Bree = require('bree');
|
|
5
|
+
const pWaitFor = require('p-wait-for');
|
|
6
|
+
const {IgnitionError, IncorrectUsageError} = require('@tryghost/errors');
|
|
7
|
+
const isCronExpression = require('./is-cron-expression');
|
|
8
|
+
const assembleBreeJob = require('./assemble-bree-job');
|
|
9
|
+
|
|
10
|
+
const worker = async (task, callback) => {
|
|
11
|
+
try {
|
|
12
|
+
let result = await task();
|
|
13
|
+
callback(null, result);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
callback(error);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const handler = (error, result) => {
|
|
20
|
+
if (error) {
|
|
21
|
+
// TODO: this handler should not be throwing as this blocks the queue
|
|
22
|
+
// throw error;
|
|
23
|
+
}
|
|
24
|
+
// Can potentially standardise the result here
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
class JobManager {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} options
|
|
31
|
+
* @param {Object} [options.logging] - custom logging handler, defaults to console
|
|
32
|
+
* @param {Function} [options.errorHandler] - custom job error handler
|
|
33
|
+
* @param {Function} [options.workerMessageHandler] - custom message handler coming from workers
|
|
34
|
+
*/
|
|
35
|
+
constructor({logging, errorHandler, workerMessageHandler}) {
|
|
36
|
+
this.queue = fastq(this, worker, 1);
|
|
37
|
+
|
|
38
|
+
this.bree = new Bree({
|
|
39
|
+
root: false, // set this to `false` to prevent requiring a root directory of jobs
|
|
40
|
+
hasSeconds: true, // precision is needed to avoid task overlaps after immediate execution
|
|
41
|
+
outputWorkerMetadata: true,
|
|
42
|
+
logger: logging,
|
|
43
|
+
errorHandler: errorHandler,
|
|
44
|
+
workerMessageHandler: workerMessageHandler
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.logging = logging;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* By default schedules an "offloaded" job. If `offloaded: true` parameter is set,
|
|
52
|
+
* puts an "inline" immediate job into the queue.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} GhostJob - job options
|
|
55
|
+
* @prop {Function | String} GhostJob.job - function or path to a module defining a job
|
|
56
|
+
* @prop {String} [GhostJob.name] - unique job name, if not provided takes function name or job script filename
|
|
57
|
+
* @prop {String | Date} [GhostJob.at] - Date, cron or human readable schedule format. Manage will do immediate execution if not specified. Not supported for "inline" jobs
|
|
58
|
+
* @prop {Object} [GhostJob.data] - data to be passed into the job
|
|
59
|
+
* @prop {Boolean} [GhostJob.offloaded] - creates an "offloaded" job running in a worker thread by default. If set to "false" runs an "inline" job on the same event loop
|
|
60
|
+
*/
|
|
61
|
+
addJob({name, at, job, data, offloaded = true}) {
|
|
62
|
+
if (offloaded) {
|
|
63
|
+
this.logging.info('Adding offloaded job to the queue');
|
|
64
|
+
let schedule;
|
|
65
|
+
|
|
66
|
+
if (!name) {
|
|
67
|
+
if (typeof job === 'string') {
|
|
68
|
+
name = path.parse(job).name;
|
|
69
|
+
} else {
|
|
70
|
+
throw new IncorrectUsageError({
|
|
71
|
+
message: 'Name parameter should be present if job is a function'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (at && !(at instanceof Date)) {
|
|
77
|
+
if (isCronExpression(at)) {
|
|
78
|
+
schedule = later.parse.cron(at, true);
|
|
79
|
+
} else {
|
|
80
|
+
schedule = later.parse.text(at);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if ((schedule.error && schedule.error !== -1) || schedule.schedules.length === 0) {
|
|
84
|
+
throw new IncorrectUsageError({
|
|
85
|
+
message: 'Invalid schedule format'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.logging.info(`Scheduling job ${name} at ${at}. Next run on: ${later.schedule(schedule).next()}`);
|
|
90
|
+
} else if (at !== undefined) {
|
|
91
|
+
this.logging.info(`Scheduling job ${name} at ${at}`);
|
|
92
|
+
} else {
|
|
93
|
+
this.logging.info(`Scheduling job ${name} to run immediately`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const breeJob = assembleBreeJob(at, job, data, name);
|
|
97
|
+
this.bree.add(breeJob);
|
|
98
|
+
return this.bree.start(name);
|
|
99
|
+
} else {
|
|
100
|
+
this.logging.info('Adding one off inline job to the queue');
|
|
101
|
+
|
|
102
|
+
this.queue.push(async () => {
|
|
103
|
+
try {
|
|
104
|
+
if (typeof job === 'function') {
|
|
105
|
+
await job(data);
|
|
106
|
+
} else {
|
|
107
|
+
await require(job)(data);
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
// NOTE: each job should be written in a safe way and handle all errors internally
|
|
111
|
+
// if the error is caught here jobs implementaton should be changed
|
|
112
|
+
this.logging.error(new IgnitionError({
|
|
113
|
+
level: 'critical',
|
|
114
|
+
errorType: 'UnhandledJobError',
|
|
115
|
+
message: 'Processed job threw an unhandled error',
|
|
116
|
+
context: (typeof job === 'function') ? 'function' : job,
|
|
117
|
+
err
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
}, handler);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Removes an "offloaded" job from scheduled jobs queue.
|
|
128
|
+
* It's NOT yet possible to remove "inline" jobs (will be possible when scheduling is added https://github.com/breejs/bree/issues/68).
|
|
129
|
+
* The method will throw an Error if job with provided name does not exist.
|
|
130
|
+
*
|
|
131
|
+
* NOTE: current implementation does not guarante running job termination
|
|
132
|
+
* for details see https://github.com/breejs/bree/pull/64
|
|
133
|
+
*
|
|
134
|
+
* @param {String} name - job name
|
|
135
|
+
*/
|
|
136
|
+
async removeJob(name) {
|
|
137
|
+
await this.bree.remove(name);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {import('p-wait-for').Options} [options]
|
|
142
|
+
*/
|
|
143
|
+
async shutdown(options) {
|
|
144
|
+
await this.bree.stop();
|
|
145
|
+
|
|
146
|
+
if (this.queue.idle()) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.logging.warn('Waiting for busy job queue');
|
|
151
|
+
|
|
152
|
+
await pWaitFor(() => this.queue.idle() === true, options);
|
|
153
|
+
|
|
154
|
+
this.logging.warn('Job queue finished');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = JobManager;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tryghost/job-manager",
|
|
3
|
+
"version": "0.8.8",
|
|
4
|
+
"repository": "https://github.com/TryGhost/Utils/tree/master/packages/job-manager",
|
|
5
|
+
"author": "Ghost Foundation",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "echo \"Implement me!\"",
|
|
10
|
+
"test": "NODE_ENV=testing c8 mocha './test/**/*.test.js'",
|
|
11
|
+
"lint": "eslint . --ext .js --cache",
|
|
12
|
+
"posttest": "yarn lint"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"lib"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@sinonjs/fake-timers": "8.0.0",
|
|
23
|
+
"c8": "7.9.0",
|
|
24
|
+
"date-fns": "2.24.0",
|
|
25
|
+
"delay": "5.0.0",
|
|
26
|
+
"mocha": "9.0.0",
|
|
27
|
+
"should": "13.2.3",
|
|
28
|
+
"sinon": "11.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@breejs/later": "^4.0.2",
|
|
32
|
+
"bree": "^6.2.0",
|
|
33
|
+
"cron-validate": "^1.4.3",
|
|
34
|
+
"fastq": "^1.11.0",
|
|
35
|
+
"p-wait-for": "^3.2.0"
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "9545d13a14c1b0ac70cd80ee111f66f642eb0a4d"
|
|
38
|
+
}
|