Jmh2013
Jmh2013

Reputation: 2777

stop multiple service controller queries inside NodeJs promise while loop

I'm trying to start a windows service from a node script. This service has a bad habit of hanging and sometimes requires a retry to start successfully. I have a promise while loop setup (Please feel free to suggest a better way). The problem I'm having, is with each loop the sc.pollInterval output writes duplicate results in the console. Below is an example of the duplicate content I see in the console, this is after the second iteration in the loop, i'd like it to only display that content once.

sc \\abnf34873 start ColdFusion 10 Application Server

sc \\abnf34873 queryex ColdFusion 10 Application Server

SERVICE_NAME: ColdFusion 10 Application Server
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 0
        FLAGS              :

SERVICE_NAME: ColdFusion 10 Application Server
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 13772
        FLAGS              :

Here is the code I have. Basically, I'm going to try to start the service 3 times. If it doesn't, then I throw and error. One thing to note, when I attempt to start the service, but it's stuck in 'Start_pending' state, I kill the process and then try to start it again.

var retryCount = 0;

// Start the colfusion service
gulp.task('start-coldfusion-service', function(done) {
    var serviceStarted = false;
    console.log("Starting coldfusion service..");
    // This says we're going to ask where it's at every 30 seconds until it's in the desired state.
    sc.pollInterval(30);
    sc.timeout(60);
    retryCount = 0;

    tryServiceStart().then(function(result) {
          // process final result here
        done();
    }).catch(function(err) {
        // process error here
    });
});


function tryServiceStart() {
    return startService().then(function(serviceStarted) {
        if (serviceStarted == false) {
            console.log("Retry Count: " + retryCount);
            // Try again..
            return tryServiceStart();
        } else {
             return result;
        }
    });
}

function startService() {
    return new Promise(function(resolve, reject) {
        var started = true;
        // Make sure the coldfusion service exists on the target server
        sc.query(targetServer, { name: 'ColdFusion 10 Application Server'}).done(function(services) {
            // if the service exists and it is currentl stopped, then we're going to start it.
            if (services.length == 1) {
                var pid = services[0].pid;
                if (services[0].state.name == 'STOPPED') {
                    sc.start(targetServer, 'ColdFusion 10 Application Server')
                        .catch(function(error) {
                            started = false;
                            console.log("Problem starting Coldfusion service! error message: " + error.message);
                            console.log("retrying...");
                            retryCount++;
                            if (parseInt(retryCount) > 2) {
                                throw Error(error.message);
                            }
                       })
                       .done(function(displayName) {
                            if (started) {
                                console.log('Coldfusion service started successfully!');
                            }
                            resolve(started);
                       });
                } else if (services[0].state.name == 'START_PENDING') {
                    kill(pid, {force: true}).catch(function (err) {
                        console.log('Problem killing process..');
                    }).then(function() {
                        console.log('Killed hanging process..');
                        resolve(false);
                    });
                }
            } else {
                console.log("Could not find the service in a stopped state.");
                resolve(false);
            }
        });
   });
}

Upvotes: 0

Views: 192

Answers (2)

Roamer-1888
Roamer-1888

Reputation: 19296

Not too sure why you get duplicate results in the console, however below are some ideas on how the code might be better written, chiefly by promisifying at the lowest level.

Sticking fairly closely to the original concept, I ended up with this ...

Promisify sc commands

  • sc commands return something which is promise-like but with a .done() method that does not, in all probability, possess the full power of a genuine .then()
  • promisify each command as .xxxAsync()
  • by adopting each command's .done as .then, Promise.resolve() should be able to assimilate the promise-like thing returned by the command.
;(function() {
    commands.forEach(command => {
        sc[command].then = sc[command].done;
        sc[command + 'Async'] = function() {
            return Promise.resolve(sc[command](...arguments)); 
        };
    }).
}(['start', 'query'])); // add other commands as required

gulp.task()

  • promise chain follows its success path if service was opened, otherwise its error path
  • no need to test a result to detect error conditions in the success path.
gulp.task('start-coldfusion-service', function(done) {
    console.log('Starting coldfusion service..');
    // This says we're going to ask where it's at every 30 seconds until it's in the desired state.
    sc.pollInterval(30);
    sc.timeout(60);
    tryServiceStart(2) // tryServiceStart(maxRetries)
    .then(done) // success! The service was started.
    .catch(function(err) {
        // the only error to end up here should be 'Maximum tries reached'.
        console.err(err);
        // process error here if necessary
    });
});

tryServiceStart()

  • orchestrate retries here
function tryServiceStart(maxRetries) {
    return startService()
    // .then(() => {}) // success! No action required here, just stay on the success path.
    .catch((error) => {
        // all throws from startService() end up here
        console.error(error); // log intermediate/final error
        if(--maxRetries > 0) {
            return tryServiceStart();
        } else {
            throw new Error('Maximum tries reached');
        }
    });
}

startService()

  • form a fully capable promise chain by calling the promisified versions of sc.query() and sc.start()
  • console.log() purged in favour of throwing.
  • thrown errors will be caught and logged back in tryServiceStart()
function startService() {
    // Make sure the coldfusion service exists on the target server
    return sc.queryAsync(targetServer, { name: 'ColdFusion 10 Application Server'})
    .then((services) => {
        // if the service exists and it is currently stopped, then start it.
        if (services.length == 1) {
            switch(services[0].state.name) {
                case 'STOPPED':
                    return sc.startAsync(targetServer, 'ColdFusion 10 Application Server')
                    .catch((error) => {
                        throw new Error("Problem starting Coldfusion service! error message: " + error.message);
                    });
                break;
                case 'START_PENDING':
                    return kill(services[0].pid, { 'force': true })
                    .then(() => {
                        throw new Error('Killed hanging process..'); // successful kill but still an error as far as startService() is concerned.
                    })
                    .catch((err) => {
                        throw new Error('Problem killing process..');
                    });
                break;
                default:
                    throw new Error("Service not in a stopped state.");
            }
        } else {
            throw new Error('Could not find the service.');
        }
    });
}

Checked only for syntax error, so may well need debugging.

Offered FWIW. Feel free to adopt/raid as appropriate.

Upvotes: 1

Jmh2013
Jmh2013

Reputation: 2777

I have found another npm package called promise-retry that seems to have addressed the issue I was having. At the same time, I believe it made my code a little more clear as to what it's doing.

gulp.task('start-coldfusion-service', function(done) {
    var serviceStarted = false;
    console.log("Starting coldfusion service..");
    // Since starting a service on another server isn't exactly fast, we have to poll the status of it.
    // This says we're going to ask where it's at every 30 seconds until it's in the desired state.
    sc.pollInterval(30);
    sc.timeout(60);     

    promiseRetry({retries: 3}, function (retry, number) {
        console.log('attempt number', number);

        return startService()
        .catch(function (err) {
            console.log(err);
            if (err.code === 'ETIMEDOUT') {
                retry(err);
            } else if (err === 'killedProcess') {
                retry(err);
            }

            throw Error(err);
        });
    })
    .then(function (value) {
        done();
    }, function (err) {
        console.log("Unable to start the service after 3 tries!");
        process.exit();
    });

});

function startService() {
    var errorMsg = "";
    return new Promise(function(resolve, reject) {
        var started = true;
        // Make sure the coldfusion service exists on the target server
        sc.query(targetServer, { name: 'ColdFusion 10 Application Server'}).done(function(services) {
            // if the service exists and it is currentl stopped, then we're going to start it.
            if (services.length == 1) {
                var pid = services[0].pid;
                if (services[0].state.name == 'STOPPED') {
                    sc.start(targetServer, 'ColdFusion 10 Application Server')
                        .catch(function(error) {
                            started = false;
                            errorMsg = error;
                            console.log("Problem starting Coldfusion service! error message: " + error.message);
                            console.log("retrying...");
                       })
                       .done(function(displayName) {
                            if (started) {
                                console.log('Coldfusion service started successfully!');
                                resolve(started);
                            } else {
                                reject(errorMsg);
                            }
                       });
                } else if (services[0].state.name == 'START_PENDING') {
                    kill(pid, {force: true}).catch(function (err) {
                        console.log('Problem killing process..');
                    }).then(function() {
                        console.log('Killed hanging process..');
                        reject("killedProcess");
                    });
                } else {
                    // Must already be started..
                    resolve(true);
                }
            } else {
                console.log("Could not find the service in a stopped state.");
                resolve(false);
            }
        });

   });

}

Upvotes: 0

Related Questions