parts/jobs/retry.js

'use strict';

const log = require('../../utils/log.js')('jobs:retry');


/**
 * By default job will be retried 3 times with 1, 2 and 3 second delays for any error = new DefaultRetryPolicy(3)
 */
class DefaultRetryPolicy {
    /**
    * Constructor
    * @param {numbe} retries
    **/
    constructor(retries) {
        this._retries = retries;
        this._retried = 0;
        this._retrying = null;
    }

    /**
    * Delay for retrying
    * @returns {Promise} promise
    **/
    delay() {
        if (!this._retrying) {
            this._retrying = new Promise((resolve) => {
                setTimeout(() => {
                    this._retrying = null;
                    resolve();
                }, this._retried * 1000);
            });
        }
        return this._retrying;
    }

    /**
    * Check if error is retryable
    * @returns {boolean} true
    **/
    errorIsRetriable(/*error*/) {
        return true;
    }

    /**
    * Run
    * @param {function} runFun - function to run
    * @returns {Promise} promise
    **/
    run(runFun) {
        return new Promise((resolve, reject) => {
            try {
                log.d('Running job %d time out of %d attempts', this._retried + 1, this._retries);
                runFun().then(resolve, (error) => {
                    log.w('Error in retry: ', error, error.stack);
                    if (this.errorIsRetriable(error)) {
                        log.w('Retriable error in retry: spent %d, left %d attempts', this._retried, this._retries);
                        if (this._retries) {
                            this._retries--;
                            this._retried++;

                            this.delay().then(() => {
                                this.run(runFun).then(resolve, reject);
                            });
                        }
                        else {
                            reject(error);
                        }
                    }
                    else {
                        try {
                            log.e('Non-retriable error in retry: spent %d attempts, error %j', this._retried, error.stack);
                            reject(error);
                        }
                        catch (e) {
                            log.e(e, e.stack);
                        }
                    }
                });
            }
            catch (e) {
                log.e('Error in retry.run', e, e.stack);
                reject(e);
            }
        });
    }
}

/**
 * RetryPolicy which retries only crashed & timed out resources, default for IPCJob
 */
class IPCRetryPolicy extends DefaultRetryPolicy {
    /**
    * Check if error is retryable
    * @param {Error} error - error to check
    * @returns {boolean} true if retryable
    **/
    errorIsRetriable(error) {
        return (error === 'Process exited') ||
				(error.message === require('./job.js').ERROR.TIMEOUT) ||
				(typeof error === 'object' && error.length === 2 &&
					(error[0] === require('./job.js').ERROR.CRASH || error[0] === require('./job.js').ERROR.TIMEOUT));
    }
}

/**
 * Never retry
 */
class NoRetryPolicy extends DefaultRetryPolicy {
    /** Constructor **/
    constructor() {
        super(0);
    }

    /**
    * Check if error is retryable
    * @returns {boolean} false
    **/
    errorIsRetriable(/*error*/) {
        return false;
    }
}

module.exports = {
    DefaultRetryPolicy: DefaultRetryPolicy,
    IPCRetryPolicy: IPCRetryPolicy,
    NoRetryPolicy: NoRetryPolicy
};