tasks: make error a json

also, handle case where we never got to handle task exit cleanly
This commit is contained in:
Girish Ramakrishnan
2019-08-30 13:46:55 -07:00
parent dd0fb8292c
commit bd23abd265
6 changed files with 57 additions and 21 deletions

View File

@@ -25,6 +25,10 @@ exports = module.exports = {
TASK_CLEAN_BACKUPS: 'cleanBackups',
TASK_SYNC_EXTERNAL_LDAP: 'syncExternalLdap',
// error codes
ESTOPPED: 'stopped',
ECRASHED: 'crashed',
// testing
_TASK_IDENTITY: '_identity',
_TASK_CRASH: '_crash',
@@ -45,10 +49,10 @@ let assert = require('assert'),
util = require('util'),
_ = require('underscore');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
let gTasks = {}; // indexed by task id
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
function TaskError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
@@ -76,7 +80,13 @@ function postProcess(result) {
assert.strictEqual(typeof result, 'object');
result.active = !!gTasks[result.id];
result.success = result.percent === 100 && !result.errorMessage;
// we rely on 'percent' to determine success. maybe this can become a db field
result.success = result.percent === 100 && !result.error;
// the error in db will be empty if we didn't get a chance to handle task exit
if (!result.active && result.percent !== 100 && !result.error) {
result.error = { message: 'Cloudron crashed/stopped', code: exports.ECRASHED };
}
}
function get(id, callback) {
@@ -139,20 +149,23 @@ function startTask(taskId, options, callback) {
debug(`startTask: ${taskId} completed with code ${code} and signal ${signal}`);
get(taskId, function (error, task) {
let taskError;
if (!error && task.percent !== 100) { // task crashed or was killed by us
error = code === 0 ? new Error(`Task ${taskId} stopped`) : new Error(`Task ${taskId} crashed with code ${code} and signal ${signal}`);
error.stopped = code === 0;
error.crashed = code !== 0;
update(taskId, { percent: 100, errorMessage: error.message }, NOOP_CALLBACK);
} else if (!error && task.errorMessage) {
error = new Error(task.errorMessage);
taskError = {
message: code === 0 ? `Task ${taskId} stopped` : `Task ${taskId} crashed with code ${code} and signal ${signal}`,
code: code === 0 ? exports.ESTOPPED : exports.ECRASHED
};
// note that despite the update() here, we should handle the case where the box code was restarted and never got taskworker exit
update(taskId, { percent: 100, error: taskError }, NOOP_CALLBACK);
} else if (!error && task.error) {
taskError = task.error;
} else if (!task) { // db got cleared in tests
error = new Error(`No such task ${taskId}`);
taskError = new Error(`No such task ${taskId}`);
}
delete gTasks[taskId];
callback(error, task ? task.result : null);
callback(taskError, task ? task.result : null);
debug(`startTask: ${taskId} done`);
});
@@ -245,6 +258,6 @@ function getLogs(taskId, options, callback) {
// removes all fields that are strictly private and should never be returned by API calls
function removePrivateFields(task) {
var result = _.pick(task, 'id', 'type', 'percent', 'message', 'errorMessage', 'active', 'creationTime', 'result', 'ts', 'success');
var result = _.pick(task, 'id', 'type', 'percent', 'message', 'error', 'active', 'creationTime', 'result', 'ts', 'success');
return result;
}