diff --git a/src/apptaskmanager.js b/src/apptaskmanager.js index b74049523..6e8fe2e78 100644 --- a/src/apptaskmanager.js +++ b/src/apptaskmanager.js @@ -49,13 +49,16 @@ async function drain() { scheduler.suspendAppJobs(appId); - tasks.startTask(taskId, Object.assign(options, { logFile }), async function (error, result) { - onFinished(error, result); + // background + tasks.startTask(taskId, Object.assign(options, { logFile })) + .then(async (result) => { + onFinished(null, result); - delete gActiveTasks[appId]; - await locks.release(`${locks.TYPE_APP_PREFIX}${appId}`); - scheduler.resumeAppJobs(appId); - }); + delete gActiveTasks[appId]; + await locks.release(`${locks.TYPE_APP_PREFIX}${appId}`); + scheduler.resumeAppJobs(appId); + }) + .catch(onFinished); } gDrainTimerId = null; diff --git a/src/backups.js b/src/backups.js index 9e5d4e973..29c688ce4 100644 --- a/src/backups.js +++ b/src/backups.js @@ -255,16 +255,20 @@ async function startBackupTask(auditSource) { await eventlog.add(eventlog.ACTION_BACKUP_START, auditSource, { taskId }); - tasks.startTask(taskId, { timeout: 24 * 60 * 60 * 1000 /* 24 hours */, nice: 15, memoryLimit, oomScoreAdjust: -999 }, async function (error, backupId) { - await locks.release(locks.TYPE_BACKUP_TASK); - await locks.releaseByTaskId(taskId); - - const errorMessage = error ? error.message : ''; - const timedOut = error ? error.code === tasks.ETIMEOUT : false; - - const backup = backupId ? await get(backupId) : null; - await safe(eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, errorMessage, timedOut, backupId, remotePath: backup?.remotePath }), { debug }); - }); + // background + tasks.startTask(taskId, { timeout: 24 * 60 * 60 * 1000 /* 24 hours */, nice: 15, memoryLimit, oomScoreAdjust: -999 }) + .then(async (backupId) => { + const backup = await get(backupId); + await eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, backupId, remotePath: backup.remotePath }); + }) + .catch(async (error) => { + const timedOut = error.code === tasks.ETIMEOUT; + await safe(eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, errorMessage: error.message, timedOut })); + }) + .finally(async () => { + await locks.release(locks.TYPE_BACKUP_TASK); + await locks.releaseByTaskId(taskId); + }); return taskId; } @@ -326,16 +330,15 @@ async function startCleanupTask(auditSource) { const taskId = await tasks.add(tasks.TASK_CLEAN_BACKUPS, []); - tasks.startTask(taskId, {}, async (error, result) => { // result is { removedBoxBackupPaths, removedAppBackupPaths, removedMailBackupPaths, missingBackupPaths } - await safe(eventlog.add(eventlog.ACTION_BACKUP_CLEANUP_FINISH, auditSource, { - taskId, - errorMessage: error ? error.message : null, - removedBoxBackupPaths: result ? result.removedBoxBackupPaths : [], - removedMailBackupPaths: result ? result.removedMailBackupPaths : [], - removedAppBackupPaths: result ? result.removedAppBackupPaths : [], - missingBackupPaths: result ? result.missingBackupPaths : [] - }), { debug }); - }); + // background + tasks.startTask(taskId, {}) + .then(async (result) => { // { removedBoxBackupPaths, removedAppBackupPaths, removedMailBackupPaths, missingBackupPaths } + await eventlog.add(eventlog.ACTION_BACKUP_CLEANUP_FINISH, auditSource, { taskId, errorMessage: null, ...result }); + }) + .catch(async (error) => { + await eventlog.add(eventlog.ACTION_BACKUP_CLEANUP_FINISH, auditSource, { taskId, errorMessage: error.message }); + + }); return taskId; } diff --git a/src/dashboard.js b/src/dashboard.js index 04a9d387c..80856d611 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -100,7 +100,7 @@ async function startPrepareLocation(domain, auditSource) { } const taskId = await tasks.add(tasks.TASK_PREPARE_DASHBOARD_LOCATION, [ constants.DASHBOARD_SUBDOMAIN, domain, auditSource ]); - tasks.startTask(taskId, {}); + safe(tasks.startTask(taskId, {}), { debug }); // background return taskId; } diff --git a/src/dns.js b/src/dns.js index 798102b1f..ad01c81db 100644 --- a/src/dns.js +++ b/src/dns.js @@ -377,6 +377,6 @@ async function startSyncDnsRecords(options) { assert.strictEqual(typeof options, 'object'); const taskId = await tasks.add(tasks.TASK_SYNC_DNS_RECORDS, [ options ]); - tasks.startTask(taskId, {}); + safe(tasks.startTask(taskId, {}), { debug }); // background return taskId; } diff --git a/src/dyndns.js b/src/dyndns.js index ae4c2c22c..53359ffb3 100644 --- a/src/dyndns.js +++ b/src/dyndns.js @@ -11,6 +11,7 @@ const apps = require('./apps.js'), debug = require('debug')('box:dyndns'), dns = require('./dns.js'), eventlog = require('./eventlog.js'), + fs = require('fs'), mailServer = require('./mailserver.js'), network = require('./network.js'), paths = require('./paths.js'), @@ -35,19 +36,18 @@ async function refreshDns(auditSource) { debug(`refreshDns: updating IP from ${info.ipv4} to ipv4: ${ipv4} (changed: ${ipv4Changed}) ipv6: ${ipv6} (changed: ${ipv6Changed})`); - // update dns in a separate task so we can have trackable logs const taskId = await tasks.add(tasks.TASK_SYNC_DYNDNS, [ ipv4Changed ? ipv4 : null, ipv6Changed ? ipv6 : null, auditSource ]); - tasks.startTask(taskId, {}, async function (error) { - if (error) { + // background + tasks.startTask(taskId, {}) + .then(async () => { + await eventlog.add(eventlog.ACTION_DYNDNS_UPDATE, auditSource, { taskId, fromIpv4: info.ipv4, fromIpv6: info.ipv6, toIpv4: ipv4, toIpv6: ipv6 }); + info.ipv4 = ipv4; + info.ipv6 = ipv6; + await fs.promises.writeFile(paths.DYNDNS_INFO_FILE, JSON.stringify(info), 'utf8'); + }) + .catch(async (error) => { await eventlog.add(eventlog.ACTION_DYNDNS_UPDATE, auditSource, { taskId, fromIpv4: info.ipv4, fromIpv6: info.ipv6, toIpv4: ipv4, toIpv6: ipv6, errorMessage: error.message }); - return; - } - - await eventlog.add(eventlog.ACTION_DYNDNS_UPDATE, auditSource, { taskId, fromIpv4: info.ipv4, fromIpv6: info.ipv6, toIpv4: ipv4, toIpv6: ipv6 }); - info.ipv4 = ipv4; - info.ipv6 = ipv6; - safe.fs.writeFileSync(paths.DYNDNS_INFO_FILE, JSON.stringify(info), 'utf8'); - }); + }); return taskId; } diff --git a/src/externalldap.js b/src/externalldap.js index 9a579a76d..5cd0fc743 100644 --- a/src/externalldap.js +++ b/src/externalldap.js @@ -358,11 +358,7 @@ async function startSyncer() { if (config.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); const taskId = await tasks.add(tasks.TASK_SYNC_EXTERNAL_LDAP, []); - - tasks.startTask(taskId, {}, function (error, result) { - debug('sync: done. %o %j', error, result); - }); - + safe(tasks.startTask(taskId, {}), { debug }); // background return taskId; } diff --git a/src/mailserver.js b/src/mailserver.js index d8554b8c6..66090e475 100644 --- a/src/mailserver.js +++ b/src/mailserver.js @@ -321,10 +321,12 @@ async function startChangeLocation(subdomain, domain, auditSource) { await setLocation(subdomain, domain); const taskId = await tasks.add(tasks.TASK_CHANGE_MAIL_LOCATION, [ auditSource ]); - tasks.startTask(taskId, {}, async (error) => { - if (error) return; - await platform.onMailServerLocationChanged(auditSource); - }); + // background + tasks.startTask(taskId, {}) + .then(async () => { + await platform.onMailServerLocationChanged(auditSource); + }) + .catch((error) => debug(`startChangeLocation`, error)); await eventlog.add(eventlog.ACTION_MAIL_LOCATION, auditSource, { subdomain, domain, taskId }); return taskId; diff --git a/src/reverseproxy.js b/src/reverseproxy.js index 534cac315..392b94561 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -707,7 +707,7 @@ async function startRenewCerts(options, auditSource) { assert.strictEqual(typeof auditSource, 'object'); const taskId = await tasks.add(tasks.TASK_CHECK_CERTS, [ options, auditSource ]); - tasks.startTask(taskId, {}); + safe(tasks.startTask(taskId, {}), { debug }); // background return taskId; } diff --git a/src/routes/test/tasks-test.js b/src/routes/test/tasks-test.js index 980d0b1d8..50d1ae98e 100644 --- a/src/routes/test/tasks-test.js +++ b/src/routes/test/tasks-test.js @@ -7,6 +7,7 @@ const common = require('./common.js'), expect = require('expect.js'), + safe = require('safetydance'), superagent = require('../../superagent.js'), tasks = require('../../tasks.js'); @@ -19,99 +20,80 @@ describe('Tasks API', function () { it('can get task', async function () { const taskId = await tasks.add(tasks._TASK_IDENTITY, [ 'ping' ]); - return new Promise((resolve) => { - tasks.startTask(taskId, {}, async function () { - const response = await superagent.get(`${serverUrl}/api/v1/tasks/${taskId}`) - .query({ access_token: owner.token }); + await tasks.startTask(taskId, {}); - expect(response.status).to.equal(200); - expect(response.body.percent).to.be(100); - expect(response.body.args).to.be(undefined); - expect(response.body.active).to.be(false); // finished - expect(response.body.success).to.be(true); - expect(response.body.result).to.be('ping'); - expect(response.body.error).to.be(null); - resolve(); - }); - }); + const response = await superagent.get(`${serverUrl}/api/v1/tasks/${taskId}`) + .query({ access_token: owner.token }); + + expect(response.status).to.equal(200); + expect(response.body.percent).to.be(100); + expect(response.body.args).to.be(undefined); + expect(response.body.active).to.be(false); // finished + expect(response.body.success).to.be(true); + expect(response.body.result).to.be('ping'); + expect(response.body.error).to.be(null); }); it('can get logs', async function () { - const taskId = await tasks.add(tasks._TASK_CRASH, [ 'ping' ]); + const taskId = await tasks.add(tasks._TASK_IDENTITY, [ 'ping' ]); - return new Promise((resolve, reject) => { - tasks.startTask(taskId, {}, async function () { - const response = await superagent.get(`${serverUrl}/api/v1/tasks/${taskId}/logs`) - .query({ access_token: owner.token }); + await tasks.startTask(taskId, {}); + const response = await superagent.get(`${serverUrl}/api/v1/tasks/${taskId}/logs`) + .query({ access_token: owner.token }); - if (response.status !== 200) return reject(new Error('Expecting 200')); - resolve(); - }); - }); + if (response.status !== 200) throw new Error('Expecting 200'); }); it('cannot stop inactive task', async function () { const taskId = await tasks.add(tasks._TASK_IDENTITY, [ 'ping' ]); - return new Promise((resolve, reject) => { - tasks.startTask(taskId, {}, async function () { - const response = await superagent.post(`${serverUrl}/api/v1/tasks/${taskId}/stop`) - .query({ access_token: owner.token }) - .send({}) - .ok(() => true); + await tasks.startTask(taskId, {}); + const response = await superagent.post(`${serverUrl}/api/v1/tasks/${taskId}/stop`) + .query({ access_token: owner.token }) + .send({}) + .ok(() => true); - if (response.status !== 409) return reject(new Error('Expecting 409')); - resolve(); - }); - }); + if (response.status !== 409) throw Error('Expecting 409'); }); it('can stop task', async function () { const taskId = await tasks.add(tasks._TASK_SLEEP, [ 10000 ]); - return new Promise((resolve) => { - tasks.startTask(taskId, {}, async function () { - const response = await superagent.get(`${serverUrl}/api/v1/tasks/${taskId}`) - .query({ access_token: owner.token }); + setTimeout(async function () { + const response = await superagent.post(`${serverUrl}/api/v1/tasks/${taskId}/stop`) + .query({ access_token: owner.token }); - expect(response.status).to.equal(200); - expect(response.body.percent).to.be(100); - expect(response.body.active).to.be(false); // finished - expect(response.body.success).to.be(false); - expect(response.body.result).to.be(null); - expect(response.body.error.message).to.contain('stopped'); - resolve(); - }); + expect(response.status).to.equal(204); + }, 100); - setTimeout(async function () { - const response = await superagent.post(`${serverUrl}/api/v1/tasks/${taskId}/stop`) - .query({ access_token: owner.token }); + await safe(tasks.startTask(taskId, {})); + const response = await superagent.get(`${serverUrl}/api/v1/tasks/${taskId}`) + .query({ access_token: owner.token }); - expect(response.status).to.equal(204); - }, 100); - }); + expect(response.status).to.equal(200); + expect(response.body.percent).to.be(100); + expect(response.body.active).to.be(false); // finished + expect(response.body.success).to.be(false); + expect(response.body.result).to.be(null); + expect(response.body.error.message).to.contain('stopped'); }); it('can list tasks', async function () { const taskId = await tasks.add(tasks._TASK_IDENTITY, [ 'ping' ]); - return new Promise((resolve) => { - tasks.startTask(taskId, {}, async function () { - const response = await superagent.get(`${serverUrl}/api/v1/tasks?type=${tasks._TASK_IDENTITY}`) - .query({ access_token: owner.token }); + await tasks.startTask(taskId, {}); + const response = await superagent.get(`${serverUrl}/api/v1/tasks?type=${tasks._TASK_IDENTITY}`) + .query({ access_token: owner.token }); - expect(response.status).to.equal(200); - expect(response.body.tasks.length >= 1).to.be(true); - expect(response.body.tasks[0].id).to.be(taskId); - expect(response.body.tasks[0].percent).to.be(100); - expect(response.body.tasks[0].args).to.be(undefined); - expect(response.body.tasks[0].active).to.be(false); // finished - expect(response.body.tasks[0].success).to.be(true); // finished - expect(response.body.tasks[0].result).to.be('ping'); - expect(response.body.tasks[0].error).to.be(null); - resolve(); - }); - }); + expect(response.status).to.equal(200); + expect(response.body.tasks.length >= 1).to.be(true); + expect(response.body.tasks[0].id).to.be(taskId); + expect(response.body.tasks[0].percent).to.be(100); + expect(response.body.tasks[0].args).to.be(undefined); + expect(response.body.tasks[0].active).to.be(false); // finished + expect(response.body.tasks[0].success).to.be(true); // finished + expect(response.body.tasks[0].result).to.be('ping'); + expect(response.body.tasks[0].error).to.be(null); }); }); diff --git a/src/system.js b/src/system.js index 92a779a94..83775ed6b 100644 --- a/src/system.js +++ b/src/system.js @@ -306,7 +306,7 @@ async function getInfo() { async function startUpdateDiskUsage() { const taskId = await tasks.add(tasks.TASK_UPDATE_DISK_USAGE, []); - tasks.startTask(taskId, {}); + safe(tasks.startTask(taskId, {}), { debug }); // background return taskId; } diff --git a/src/tasks.js b/src/tasks.js index 71a3422ca..a6039bf19 100644 --- a/src/tasks.js +++ b/src/tasks.js @@ -156,10 +156,9 @@ async function add(type, args) { return String(result.insertId); } -function startTask(id, options, onTaskFinished) { +async function startTask(id, options) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof options, 'object'); - assert(typeof onTaskFinished === 'undefined' || typeof onTaskFinished === 'function'); const logFile = options.logFile || `${paths.TASKS_LOG_DIR}/${id}.log`; debug(`startTask - starting task ${id} with options ${JSON.stringify(options)}. logs at ${logFile}`); @@ -168,56 +167,60 @@ function startTask(id, options, onTaskFinished) { const sudoOptions = { preserveEnv: true, logStream: null }; if (constants.TEST) sudoOptions.logStream = fs.createWriteStream('/dev/null'); // without this output is messed up, not sure why - gTasks[id] = shell.sudo([ START_TASK_CMD, id, logFile, options.nice || 0, options.memoryLimit || 400, options.oomScoreAdjust || 0 ], sudoOptions, async function (sudoError) { - if (!gTasks[id]) return; // ignore task exit since we are shutting down. see stopAllTasks - const code = sudoError ? sudoError.code : 0; + const p = new Promise((resolve, reject) => { + gTasks[id] = shell.sudo([ START_TASK_CMD, id, logFile, options.nice || 0, options.memoryLimit || 400, options.oomScoreAdjust || 0 ], sudoOptions, async function (sudoError) { + if (!gTasks[id]) return; // ignore task exit since we are shutting down. see stopAllTasks - debug(`startTask: ${id} completed with code ${code}`); + const code = sudoError ? sudoError.code : 0; - if (options.timeout) clearTimeout(killTimerId); + debug(`startTask: ${id} completed with code ${code}`); - const [getError, task] = await safe(get(id)); + if (options.timeout) clearTimeout(killTimerId); - let taskError = null; - if (!getError && task.percent !== 100) { // taskworker crashed or was killed by us - if (code === 0) { - taskError = { - message: `Task ${id} ${timedOut ? 'timed out' : 'stopped'}` , - code: timedOut ? exports.ETIMEOUT : exports.ESTOPPED - }; - } else { // task crashed. for code, maybe we can check systemctl show box-task-1707 -p ExecMainStatus - taskError = { - message: code === 2 ? `Task ${id} crashed as it ran out of memory` : `Task ${id} crashed with code ${code}`, - code: exports.ECRASHED - }; + const [getError, task] = await safe(get(id)); + + let taskError = null; + if (!getError && task.percent !== 100) { // taskworker crashed or was killed by us + if (code === 0) { + taskError = { + message: `Task ${id} ${timedOut ? 'timed out' : 'stopped'}` , + code: timedOut ? exports.ETIMEOUT : exports.ESTOPPED + }; + } else { // task crashed. for code, maybe we can check systemctl show box-task-1707 -p ExecMainStatus + taskError = { + message: code === 2 ? `Task ${id} crashed as it ran out of memory` : `Task ${id} crashed with code ${code}`, + code: exports.ECRASHED + }; + } + + // note that despite the update() here, we should handle the case where the box code was restarted and never got taskworker exit + await safe(setCompleted(id, { error: taskError })); + } else if (!getError && task.error) { + taskError = task.error; + } else if (!task) { // db got cleared in tests + taskError = new BoxError(BoxError.NOT_FOUND, `No such task ${id}`); } - // note that despite the update() here, we should handle the case where the box code was restarted and never got taskworker exit - await safe(setCompleted(id, { error: taskError })); - } else if (!getError && task.error) { - taskError = task.error; - } else if (!task) { // db got cleared in tests - taskError = new BoxError(BoxError.NOT_FOUND, `No such task ${id}`); + delete gTasks[id]; + + debug(`startTask: ${id} done. error: %o`, taskError); + + if (taskError) reject(taskError); else resolve(task.result); + }); + + if (options.timeout) { + killTimerId = setTimeout(async function () { + debug(`startTask: task ${id} took too long. killing`); + timedOut = true; + const [error] = await safe(stopTask(id)); + if (error) debug(`startTask: error stopping task: ${error.message}`); + }, options.timeout); } - - delete gTasks[id]; - - if (onTaskFinished) onTaskFinished(taskError, task ? task.result : null); - - debug(`startTask: ${id} done. error: %o`, taskError); }); - if (options.timeout) { - killTimerId = setTimeout(async function () { - debug(`startTask: task ${id} took too long. killing`); - timedOut = true; - const [error] = await safe(stopTask(id)); - if (error) debug(`startTask: error stopping task: ${error.message}`); - }, options.timeout); - } - - update(id, { pending: false }); // fixme: make async + await update(id, { pending: false }); + return p; } async function stopTask(id) { diff --git a/src/test/tasks-test.js b/src/test/tasks-test.js index 38a234469..c7aa62c6e 100644 --- a/src/test/tasks-test.js +++ b/src/test/tasks-test.js @@ -85,58 +85,42 @@ describe('task', function () { it('can run valid task - success', async function () { const taskId = await tasks.add(tasks._TASK_IDENTITY, [ 'ping' ]); - return new Promise((resolve, reject) => { - tasks.startTask(taskId, {}, function (error, result) { - if (error) return reject(error); - expect(result).to.equal('ping'); - resolve(); - }); - }); + const [error, result] = await safe(tasks.startTask(taskId, {})); + if (error) throw error; + expect(result).to.equal('ping'); }); it('can run valid task - error', async function () { const taskId = await tasks.add(tasks._TASK_ERROR, [ 'ping' ]); - return new Promise((resolve, reject) => { - tasks.startTask(taskId, {}, function (error, result) { - if (!error) return reject(new Error('expecting task to fail')); - expect(error.message).to.be('Failed for arg: ping'); - expect(result).to.be(null); - resolve(); - }); - }); + const [error, result] = await safe(tasks.startTask(taskId, {})); + if (!error) throw new Error('expecting task to fail'); + expect(error.message).to.be('Failed for arg: ping'); + expect(result).to.not.be.ok(); }); it('can get logs of crash', async function () { const taskId = await tasks.add(tasks._TASK_CRASH, [ 'ping' ]); - return new Promise((resolve, reject) => { - tasks.startTask(taskId, {}, function (error, result) { - if (!error) return reject(new Error('expecting task to crash')); - expect(error.message).to.contain(`Task ${taskId} crashed`); - expect(result).to.be(null); + const [error, result] = await safe(tasks.startTask(taskId, {})); + if (!error) throw new Error('expecting task to crash'); + expect(error.message).to.contain(`Task ${taskId} crashed`); + expect(result).to.not.be.ok(); - const logs = fs.readFileSync(`${paths.TASKS_LOG_DIR}/${taskId}.log`, 'utf8'); - expect(logs).to.contain('Crashing for arg: ping'); - resolve(); - }); - }); + const logs = fs.readFileSync(`${paths.TASKS_LOG_DIR}/${taskId}.log`, 'utf8'); + expect(logs).to.contain('Crashing for arg: ping'); }); it('can stop task', async function () { const taskId = await tasks.add(tasks._TASK_SLEEP, [ 10000 ]); - return new Promise((resolve, reject) => { - tasks.startTask(taskId, {}, function (error, result) { - if (!error) return reject(new Error('expecting task to stop')); - expect(error.message).to.contain('stopped'); - expect(result).to.be(null); - resolve(); - }); + setTimeout(async function () { + await tasks.stopTask(taskId); + }, 2000); - setTimeout(async function () { - await tasks.stopTask(taskId); - }, 2000); - }); + const [error, result] = await safe(tasks.startTask(taskId, {})); + if (!error) throw new Error('expecting task to stop'); + expect(error.message).to.contain('stopped'); + expect(result).to.not.be.ok(); }); }); diff --git a/src/updater.js b/src/updater.js index 697cf6a27..b20659031 100644 --- a/src/updater.js +++ b/src/updater.js @@ -216,15 +216,18 @@ async function updateToLatest(options, auditSource) { const taskId = await tasks.add(tasks.TASK_UPDATE, [ boxUpdateInfo, options ]); await eventlog.add(eventlog.ACTION_UPDATE, auditSource, { taskId, boxUpdateInfo }); - tasks.startTask(taskId, { timeout: 20 * 60 * 60 * 1000 /* 20 hours */, nice: 15, memoryLimit }, async (error) => { - await locks.release(locks.TYPE_UPDATE_TASK); - await locks.releaseByTaskId(taskId); + // background + tasks.startTask(taskId, { timeout: 20 * 60 * 60 * 1000 /* 20 hours */, nice: 15, memoryLimit }) + .then(() => debug('updateToLatest: internal error. impossible code path')) + .catch(async (error) => { + debug('Update failed with error. %o', error); - debug('Update failed with error. %o', error); + await locks.release(locks.TYPE_UPDATE_TASK); + await locks.releaseByTaskId(taskId); - const timedOut = error.code === tasks.ETIMEOUT; - await safe(eventlog.add(eventlog.ACTION_UPDATE_FINISH, auditSource, { taskId, errorMessage: error.message, timedOut })); - }); + const timedOut = error.code === tasks.ETIMEOUT; + await eventlog.add(eventlog.ACTION_UPDATE_FINISH, auditSource, { taskId, errorMessage: error.message, timedOut }); + }); return taskId; }