[MediaWiki-commits] [Gerrit] mediawiki...chromium-render[master]: Abort render on connection close
Pmiazga has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/392932 ) Change subject: Abort render on connection close .. Abort render on connection close When the client closes the connection: - if the task is still waiting in the queue, remove it; - if the task has already started, abort render. Bug: T180604 Change-Id: I47f6847948ed8903c54fdaaf4fcb5ff021d46c76 --- M lib/queue.js M package.json M routes/html2pdf-v1.js M test/lib/queue.js 4 files changed, 129 insertions(+), 15 deletions(-) Approvals: Pmiazga: Verified; Looks good to me, approved Jdlrobson: Looks good to me, but someone else must approve diff --git a/lib/queue.js b/lib/queue.js index ff437ac..525f75b 100644 --- a/lib/queue.js +++ b/lib/queue.js @@ -2,7 +2,6 @@ const asyncQueue = require('async/queue'); const asyncTimeout = require('async/timeout'); -const uuid = require('cassandra-uuid'); // Errors used as the first argument of the callback passed to the queue const callbackErrors = { @@ -79,14 +78,13 @@ const queue = this._queueObject; const timeout = this._options.queueTimeout; -data._id = `${uuid.TimeUuid.now().toString()}|${data.uri}`; data._timeoutID = setTimeout(() => { queue.remove((worker) => { -if (worker.data._id === data._id) { +if (worker.data.id === data.id) { logger.log( 'warn/queue', `Queue is still busy after waiting ` + -`for ${timeout} secs. Data ID: ${data._id}.` +`for ${timeout} secs. Data ID: ${data.id}.` ); callback(callbackErrors.queueBusy, null); return true; @@ -123,11 +121,12 @@ callback(callbackErrors.queueFull, null); return; } + // make sure to cancel the task if it doesn't start within a timeframe this._setCancelTaskTimeout(data, callback); const queueSize = this._countJobsInQueue(); this._logger.log( -'debug/queue', `Job ${data._id} added to the queue. Jobs waiting: ${queueSize}` +'debug/queue', `Job ${data.id} added to the queue. Jobs waiting: ${queueSize}` ); this._queueObject.push(data, callback); } @@ -143,21 +142,20 @@ */ _worker(data, callback) { this._clearCancelTaskTimeout(data); -this._logger.log('info/queue', `Started rendering ${data._id}`); +this._logger.log('info/queue', `Started rendering ${data.id}`); const timeout = this._options.executionTimeout; const timedRender = asyncTimeout(this._render.bind(this), timeout * 1000); -const renderer = data._renderer; timedRender(data, (error, pdf) => { // error returned by async timeout if (error && error.code === 'ETIMEDOUT') { this._logger.log( 'error/render', `Aborting. Render hasn't finished within ${timeout} ` + -`seconds. Data ID: ${data._id}.` +`seconds. Data ID: ${data.id}.` ); -renderer.abortRender(); +data.renderer.abortRender(); callback(callbackErrors.renderTimeout, null); } else { callback(error, pdf); @@ -171,23 +169,53 @@ * @param {Function} callback called on render success/failure */ _render(data, callback) { -data._renderer +data.renderer .articleToPdf(data.uri, data.format, this._puppeteerOptions, this._pdfOptions) .then((pdf) => { this._logger.log( -'debug/queue', `Job ${data._id} rendered successfully` +'debug/queue', `Job ${data.id} rendered successfully` ); callback(null, pdf); }) .catch((error) => { this._logger.log('error/render', { -msg: `Cannot convert page ${data.uri} to PDF. Error: ${error.toString()}`, +msg: `Data ID: ${data.id} to PDF. ${error.toString()}`, error }); callback(callbackErrors.renderFailed, null); }); } + +/** + * Abort task identified by `id` + * @param {string} id ID initially passed as part of data + * @param {Renderer} renderer instance of Renderer + */ +abort(id, renderer) { +let taskStarted = true; + +// has the task started already? +this._queueObject.remove((worker) => { +if (worker.data.id === id) { +this._logger.log( +'debug/queue', +
[MediaWiki-commits] [Gerrit] mediawiki...chromium-render[master]: Abort render on connection close
Bmansurov has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/392932 ) Change subject: Abort render on connection close .. Abort render on connection close When the client closes the connection: - if the task is still waiting in the queue, remove it; - if the task has already started, abort render. Bug: T180604 Change-Id: I47f6847948ed8903c54fdaaf4fcb5ff021d46c76 --- M lib/queue.js M package.json M routes/html2pdf-v1.js M test/lib/queue.js 4 files changed, 134 insertions(+), 20 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/chromium-render refs/changes/32/392932/1 diff --git a/lib/queue.js b/lib/queue.js index 3330f79..360db88 100644 --- a/lib/queue.js +++ b/lib/queue.js @@ -2,8 +2,6 @@ const asyncQueue = require('async/queue'); const asyncTimeout = require('async/timeout'); -const Renderer = require('./renderer'); -const uuid = require('cassandra-uuid'); // Errors used as the first argument of the callback passed to the queue const callbackErrors = { @@ -38,15 +36,16 @@ * @param {Object} puppeteerOptions options used to in starting puppeteer * @param {Object} pdfOptions pdf options passed to Chromium * @param {Object} logger app logger + * @param {Renderer} renderer an instance of PDF renderer */ -constructor(queueOptions, puppeteerOptions, pdfOptions, logger) { +constructor(queueOptions, puppeteerOptions, pdfOptions, logger, renderer) { this._queueObject = asyncQueue(this._worker.bind(this), queueOptions.concurrency); this._puppeteerOptions = puppeteerOptions; this._pdfOptions = pdfOptions; this._options = queueOptions; this._logger = logger; -this._renderer = new Renderer(); +this._renderer = renderer; } /** @@ -81,14 +80,13 @@ const queue = this._queueObject; const timeout = this._options.queueTimeout; -data._id = `${uuid.TimeUuid.now().toString()}|${data.uri}`; data._timeoutID = setTimeout(() => { queue.remove((worker) => { -if (worker.data._id === data._id) { +if (worker.data.id === data.id) { logger.log( 'warn/queue', `Queue is still busy after waiting ` + -`for ${timeout} secs. Data ID: ${data._id}.` +`for ${timeout} secs. Data ID: ${data.id}.` ); callback(callbackErrors.queueBusy, null); return true; @@ -129,7 +127,7 @@ this._setCancelTaskTimeout(data, callback); const queueSize = this._countJobsInQueue(); this._logger.log( -'debug/queue', `Job ${data._id} added to the queue. Jobs waiting: ${queueSize}` +'debug/queue', `Job ${data.id} added to the queue. Jobs waiting: ${queueSize}` ); this._queueObject.push(data, callback); } @@ -145,7 +143,7 @@ */ _worker(data, callback) { this._clearCancelTaskTimeout(data); -this._logger.log('info/queue', `Started rendering ${data._id}`); +this._logger.log('info/queue', `Started rendering ${data.id}`); const timeout = this._options.executionTimeout; const timedRender = asyncTimeout(this._render.bind(this), @@ -157,7 +155,7 @@ this._logger.log( 'error/render', `Aborting. Render hasn't finished within ${timeout} ` + -`seconds. Data ID: ${data._id}.` +`seconds. Data ID: ${data.id}.` ); renderer.abortRender(); callback(callbackErrors.renderTimeout, null); @@ -178,18 +176,47 @@ this._pdfOptions) .then((pdf) => { this._logger.log( -'debug/queue', `Job ${data._id} rendered successfully` +'debug/queue', `Job ${data.id} rendered successfully` ); callback(null, pdf); }) .catch((error) => { this._logger.log('error/render', { -msg: `Cannot convert page ${data.uri} to PDF. Error: ${error.toString()}`, +msg: `Data ID: ${data.id} to PDF. ${error.toString()}`, error }); callback(callbackErrors.renderFailed, null); }); } + +/** + * Abort task identified by `id` + * @param {string} id ID initially passed as part of data + */ +abort(id) { +let taskStarted = true; + +// has the task started already? +this._queueObject.remove((worker) => { +if (worker.data.id === id) { +this._logger.log( +