diff --git a/packages/job-manager/lib/JobManager.js b/packages/job-manager/lib/JobManager.js index fda14a80..79e0c85e 100644 --- a/packages/job-manager/lib/JobManager.js +++ b/packages/job-manager/lib/JobManager.js @@ -206,7 +206,7 @@ class JobManager { * @prop {Object} [GhostJob.data] - data to be passed into the job * @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 */ - addJob({name, at, job, data, offloaded = true}) { + async addJob({name, at, job, data, offloaded = true}) { if (offloaded) { logging.info('Adding offloaded job to the inline job queue'); let schedule; @@ -242,7 +242,7 @@ class JobManager { } const breeJob = assembleBreeJob(at, job, data, name); - this.bree.add(breeJob); + await this.bree.add(breeJob); return this.bree.start(name); } else { logging.info(`Adding one-off job to inlineQueue with current length = ${this.inlineQueue.length()} called '${name || 'anonymous'}'`); diff --git a/packages/job-manager/package.json b/packages/job-manager/package.json index ff76a4a5..ea64634c 100644 --- a/packages/job-manager/package.json +++ b/packages/job-manager/package.json @@ -32,7 +32,7 @@ "@breejs/later": "4.2.0", "@tryghost/errors": "^2.1.0", "@tryghost/logging": "^3.1.0", - "bree": "6.5.0", + "bree": "9.2.9", "cron-validate": "1.4.5", "fastq": "1.20.1", "p-wait-for": "3.2.0", diff --git a/packages/job-manager/test/job-manager.test.js b/packages/job-manager/test/job-manager.test.js index 43f230d1..8db673a2 100644 --- a/packages/job-manager/test/job-manager.test.js +++ b/packages/job-manager/test/job-manager.test.js @@ -99,12 +99,12 @@ describe('Job Manager', function () { }); describe('Offloaded jobs', function () { - it('accepts cron schedule when worker scheduling is stubbed', function () { - sandbox.stub(jobManager.bree, 'add').returns(); - sandbox.stub(jobManager.bree, 'start').returns(); + it('accepts cron schedule when worker scheduling is stubbed', async function () { + sandbox.stub(jobManager.bree, 'add').resolves(); + sandbox.stub(jobManager.bree, 'start').resolves(); const jobPath = path.resolve(__dirname, './jobs/simple.js'); - jobManager.addJob({ + await jobManager.addJob({ at: '* * * * * *', job: jobPath, name: 'cron-job' @@ -141,105 +141,107 @@ describe('Job Manager', function () { const jobPath = path.resolve(__dirname, './jobs/simple.js'); const clock = FakeTimers.install({now: Date.now()}); - jobManager.addJob({ - at: timeInTenSeconds, - job: jobPath, - name: 'job-in-ten' - }); + try { + await jobManager.addJob({ + at: timeInTenSeconds, + job: jobPath, + name: 'job-in-ten' + }); - assert.equal(typeof jobManager.bree.timeouts['job-in-ten'], 'object'); - assert.equal(typeof jobManager.bree.workers['job-in-ten'], 'undefined'); + assert.equal(jobManager.bree.timeouts.has('job-in-ten'), true); + assert.equal(jobManager.bree.workers.has('job-in-ten'), false); - // allow to run the job and start the worker - await clock.nextAsync(); + // allow to run the job and start the worker + await clock.nextAsync(); - assert.equal(typeof jobManager.bree.workers['job-in-ten'], 'object'); + assert.equal(jobManager.bree.workers.has('job-in-ten'), true); - const promise = new Promise((resolve, reject) => { - jobManager.bree.workers['job-in-ten'].on('error', reject); - jobManager.bree.workers['job-in-ten'].on('exit', (code) => { - assert.equal(code, 0); - resolve(); + const promise = new Promise((resolve, reject) => { + jobManager.bree.workers.get('job-in-ten').on('error', reject); + jobManager.bree.workers.get('job-in-ten').on('exit', (code) => { + assert.equal(code, 0); + resolve(); + }); }); - }); - // allow job to finish execution and exit - clock.next(); + // allow job to finish execution and exit + clock.next(); - await promise; + await promise; - assert.equal(typeof jobManager.bree.workers['job-in-ten'], 'undefined'); - - clock.uninstall(); + assert.equal(jobManager.bree.workers.has('job-in-ten'), false); + } finally { + clock.uninstall(); + } }); it('schedules a job to run immediately', async function () { const clock = FakeTimers.install({now: Date.now()}); + try { + const jobPath = path.resolve(__dirname, './jobs/simple.js'); + await jobManager.addJob({ + job: jobPath, + name: 'job-now' + }); - const jobPath = path.resolve(__dirname, './jobs/simple.js'); - jobManager.addJob({ - job: jobPath, - name: 'job-now' - }); - - assert.equal(typeof jobManager.bree.timeouts['job-now'], 'object'); + assert.equal(jobManager.bree.timeouts.has('job-now'), true); - // allow scheduler to pick up the job - clock.tick(1); + // allow scheduler to pick up the job + await clock.tickAsync(1); - assert.equal(typeof jobManager.bree.workers['job-now'], 'object'); + assert.equal(jobManager.bree.workers.has('job-now'), true); - const promise = new Promise((resolve, reject) => { - jobManager.bree.workers['job-now'].on('error', reject); - jobManager.bree.workers['job-now'].on('exit', (code) => { - assert.equal(code, 0); - resolve(); + const promise = new Promise((resolve, reject) => { + jobManager.bree.workers.get('job-now').on('error', reject); + jobManager.bree.workers.get('job-now').on('exit', (code) => { + assert.equal(code, 0); + resolve(); + }); }); - }); - await promise; + await promise; - assert.equal(typeof jobManager.bree.workers['job-now'], 'undefined'); - - clock.uninstall(); + assert.equal(jobManager.bree.workers.has('job-now'), false); + } finally { + clock.uninstall(); + } }); it('fails to schedule a job with the same name to run immediately one after another', async function () { const clock = FakeTimers.install({now: Date.now()}); + try { + const jobPath = path.resolve(__dirname, './jobs/simple.js'); + await jobManager.addJob({ + job: jobPath, + name: 'job-now' + }); - const jobPath = path.resolve(__dirname, './jobs/simple.js'); - jobManager.addJob({ - job: jobPath, - name: 'job-now' - }); - - assert.equal(typeof jobManager.bree.timeouts['job-now'], 'object'); + assert.equal(jobManager.bree.timeouts.has('job-now'), true); - // allow scheduler to pick up the job - clock.tick(1); + // allow scheduler to pick up the job + await clock.tickAsync(1); - assert.equal(typeof jobManager.bree.workers['job-now'], 'object'); + assert.equal(jobManager.bree.workers.has('job-now'), true); - const promise = new Promise((resolve, reject) => { - jobManager.bree.workers['job-now'].on('error', reject); - jobManager.bree.workers['job-now'].on('exit', (code) => { - assert.equal(code, 0); - resolve(); + const promise = new Promise((resolve, reject) => { + jobManager.bree.workers.get('job-now').on('error', reject); + jobManager.bree.workers.get('job-now').on('exit', (code) => { + assert.equal(code, 0); + resolve(); + }); }); - }); - await promise; + await promise; - assert.equal(typeof jobManager.bree.workers['job-now'], 'undefined'); + assert.equal(jobManager.bree.workers.has('job-now'), false); - assert.throws(() => { - jobManager.addJob({ + await assert.rejects(() => jobManager.addJob({ job: jobPath, name: 'job-now' - }); - }, /Job #1 has a duplicate job name of job-now/); - - clock.uninstall(); + }), /Job #1 has a duplicate job name of job-now/); + } finally { + clock.uninstall(); + } }); it('uses custom error handler when job fails', async function (){ @@ -250,7 +252,7 @@ describe('Job Manager', function () { jobManager = new JobManager({errorHandler: spyHandler, config: stubConfig}); const completion = jobManager.awaitCompletion('will-fail'); - jobManager.addJob({ + await jobManager.addJob({ job, name: 'will-fail' }); @@ -267,13 +269,13 @@ describe('Job Manager', function () { jobManager = new JobManager({workerMessageHandler: workerMessageHandlerSpy, config: stubConfig}); const completion = jobManager.awaitCompletion('will-send-msg'); - jobManager.addJob({ + await jobManager.addJob({ job: path.resolve(__dirname, './jobs/message.js'), name: 'will-send-msg' }); - jobManager.bree.run('will-send-msg'); + await jobManager.bree.run('will-send-msg'); await delay(100); - jobManager.bree.workers['will-send-msg'].postMessage('hello from Ghost!'); + jobManager.bree.workers.get('will-send-msg').postMessage('hello from Ghost!'); await completion; @@ -547,7 +549,7 @@ describe('Job Manager', function () { // allow job to get picked up and executed await delay(100); - jobManager.bree.workers['successful-oneoff'].postMessage('be done!'); + jobManager.bree.workers.get('successful-oneoff').postMessage('be done!'); // allow the message to be passed around await jobCompletion; @@ -694,7 +696,7 @@ describe('Job Manager', function () { const timeInTenSeconds = new Date(Date.now() + 10); const jobPath = path.resolve(__dirname, './jobs/simple.js'); - jobManager.addJob({ + await jobManager.addJob({ at: timeInTenSeconds, job: jobPath, name: 'job-in-ten' @@ -727,20 +729,20 @@ describe('Job Manager', function () { it('gracefully shuts down an interval job', async function () { jobManager = new JobManager({config: stubConfig}); - jobManager.addJob({ + await jobManager.addJob({ at: 'every 5 seconds', job: path.resolve(__dirname, './jobs/graceful.js') }); await delay(1); // let the job execution kick in - assert.equal(Object.keys(jobManager.bree.workers).length, 0); - assert.equal(Object.keys(jobManager.bree.timeouts).length, 0); - assert.equal(Object.keys(jobManager.bree.intervals).length, 1); + assert.equal(jobManager.bree.workers.size, 0); + assert.equal(jobManager.bree.timeouts.size, 0); + assert.equal(jobManager.bree.intervals.size, 1); await jobManager.shutdown(); - assert.equal(Object.keys(jobManager.bree.intervals).length, 0); + assert.equal(jobManager.bree.intervals.size, 0); }); it('gracefully shuts down the job queue worker pool'); diff --git a/yarn.lock b/yarn.lock index 262fcba0..2655337a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1413,7 +1413,7 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa" integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA== -"@breejs/later@4.2.0", "@breejs/later@^4.1.0": +"@breejs/later@4.2.0", "@breejs/later@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@breejs/later/-/later-4.2.0.tgz#669661f3a02535ef900f360c74e48c3f5483c786" integrity sha512-EVMD0SgJtOuFeg0lAVbCwa+qeTKILb87jqvLyUtQswGD9+ce2nB52Y5zbTF1Hc0MDFfbydcMcxb47jSdhikVHA== @@ -4654,11 +4654,6 @@ body-parser@~1.20.3: type-is "~1.6.18" unpipe "~1.0.0" -boolean@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" - integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== - bowser@^2.11.0: version "2.14.1" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.14.1.tgz#4ea39bf31e305184522d7ad7bfd91389e4f0cb79" @@ -4686,23 +4681,18 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -bree@6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/bree/-/bree-6.5.0.tgz#f7aeb2d65c2837a733edb6c93d1f354643942e27" - integrity sha512-Yzoflt/zwaRQeF1Gurjbn0g49kZ9QTA7rWR0IKQliKiUJSrsuGbtyBToI5WV60dmd+SEVPtu0oJCjYbEeduYyw== +bree@9.2.9: + version "9.2.9" + resolved "https://registry.yarnpkg.com/bree/-/bree-9.2.9.tgz#30ccffe435d3ce71b3013a3266efc2411301d862" + integrity sha512-LGAU0iJXRZp146SweBB37Xm44z4jCmsuD97W73747KpeZXYKcO8Vj3Th7xPeeTVmxCGzaV8KaMMtRAXCoujdBA== dependencies: - "@babel/runtime" "^7.12.5" - "@breejs/later" "^4.1.0" - boolean "^3.0.2" - bthreads "^0.5.1" + "@breejs/later" "^4.2.0" combine-errors "^3.0.3" - cron-validate "^1.4.1" - debug "^4.3.1" - human-interval "^2.0.0" - is-string-and-not-blank "^0.0.2" - is-valid-path "^0.1.1" - ms "^2.1.2" - p-wait-for "3.1.0" + cron-validate "^1.5.3" + human-interval "^2.0.1" + is-invalid-path "^0.1.0" + ms "^2.1.3" + p-wait-for "3" safe-timers "^1.1.0" browser-stdout@^1.3.1: @@ -4728,13 +4718,6 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -bthreads@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/bthreads/-/bthreads-0.5.1.tgz#c7a4dacc2d159c50de08b37b1e2a7da836171063" - integrity sha512-nK7Jo9ll+r1FRMNPWEFRTZMQrX6HhX8JjPAofxmbTNILHqWVIJPmWzCi9JlX/K0DL5AKZTFZg2Qser5C6gVs9A== - dependencies: - bufio "~1.0.5" - buffer-crc32@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" @@ -4766,11 +4749,6 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -bufio@~1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/bufio/-/bufio-1.0.7.tgz#b7f63a1369a0829ed64cc14edf0573b3e382a33e" - integrity sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A== - builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -5187,7 +5165,7 @@ cron-validate@1.4.5: dependencies: yup "0.32.9" -cron-validate@^1.4.1: +cron-validate@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/cron-validate/-/cron-validate-1.5.3.tgz#cf2d6aad35692dee1e7338995e638ad0f0d981ab" integrity sha512-jcu8g/3wZL8OBr4MkEcbeIdLpM8pp5Y6UoOlRktcJG3WjgpifijR0s26Yac7ywR0gC2ABtevOsz5mlD3l3gzwA== @@ -6647,7 +6625,7 @@ httpreq@>=0.4.22: resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-1.1.1.tgz#b8818316cdfd6b1bfb0f68b822fa1306cd24be68" integrity sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw== -human-interval@^2.0.0: +human-interval@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/human-interval/-/human-interval-2.0.1.tgz#655baf606c7067bb26042dcae14ec777b099af15" integrity sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ== @@ -6829,18 +6807,6 @@ is-stream@^4.0.1: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-4.0.1.tgz#375cf891e16d2e4baec250b85926cffc14720d9b" integrity sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== -is-string-and-not-blank@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz#cd19eded2ca4a514f79ca528915f1fb28e5dd38a" - integrity sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ== - dependencies: - is-string-blank "^1.0.1" - -is-string-blank@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-string-blank/-/is-string-blank-1.0.1.tgz#866dca066d41d2894ebdfd2d8fe93e586e583a03" - integrity sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw== - is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -6851,13 +6817,6 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-valid-path@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df" - integrity sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A== - dependencies: - is-invalid-path "^0.1.0" - is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -7656,7 +7615,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.2, ms@^2.1.3: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -8034,14 +7993,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -p-wait-for@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.1.0.tgz#9da568a2adda3ea8175a3c43f46a5317e28c0e47" - integrity sha512-0Uy19uhxbssHelu9ynDMcON6BmMk6pH8551CvxROhiz3Vx+yC4RqxjyIDk2V4ll0g9177RKT++PK4zcV58uJ7A== - dependencies: - p-timeout "^3.0.0" - -p-wait-for@3.2.0: +p-wait-for@3, p-wait-for@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.2.0.tgz#640429bcabf3b0dd9f492c31539c5718cb6a3f1f" integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==