1

So I managed to write the service and its working fine for now. I am suppose to make sure that when the database record are processed, and for some reason if the service is paused or shutting down, the task should be completed instead of leaving the data inconsistent.

I have tried the below code, but I feel it might not be correct, in case it is, it might not be the best. How can I improve this code for the above scenario?

protected override void OnStart(string[] args)
{
    // Update the service state to Start Pending.
    ServiceStatus serviceStatus = new ServiceStatus();
    serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
    serviceStatus.dwWaitHint = 100000;
    SetServiceStatus(this.ServiceHandle, ref serviceStatus);

    _eventLog.WriteEntry("RMS Service Started: " + DateTime.UtcNow.ToLongDateString());
    CheckDBEntryData();

    _timer = new System.Timers.Timer();
    _timer.Interval = 60000;
    _timer.Elapsed += new System.Timers.ElapsedEventHandler(this.SendScheduledSMS);
    _timer.Start();

    // Update the service state to Running.
    serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
    SetServiceStatus(this.ServiceHandle, ref serviceStatus);
}
public void SendScheduledSMS(object sender, System.Timers.ElapsedEventArgs e)
    {
        if (!_isTaskRunning)
        {
            _timer.Enabled = false;
            _isTaskRunning = true;
            _eventLog.WriteEntry("RMS Timer Event started: " + DateTime.UtcNow.ToLongDateString());

            //TODO: Write the code to check pending RMS and process them

            _eventLog.WriteEntry("RMS Timer Event stopped: " + DateTime.UtcNow.ToLongDateString());
            _isTaskRunning = false;
            _timer.Enabled = true;
        }
    }
protected override void OnStop()
{
    CanStop = !_isTaskRunning;
    // Update the service state to Stop Pending.
    ServiceStatus serviceStatus = new ServiceStatus();
    serviceStatus.dwCurrentState = ServiceState.SERVICE_STOP_PENDING;
    serviceStatus.dwWaitHint = 100000;
    SetServiceStatus(this.ServiceHandle, ref serviceStatus);

    _eventLog.WriteEntry("RMS Service Stopped: " + DateTime.UtcNow.ToLongDateString());


    // Update the service state to Stopped.
    serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED;
    SetServiceStatus(this.ServiceHandle, ref serviceStatus);
}

More Info

Service is processing the database record and passing the data to 2nd remote server, which is responsible for sending SMS notification to users. I need to make sure that notification is sent only once. If I use transaction, and a rollback happens for the last record, next time the processing starts, it will send notifications from beginning, thus sending repeat notifications. Therefore I cannot rely on DB transaction modal, as I have no control over the 2nd remote server.

Hitin
  • 442
  • 7
  • 21

2 Answers2

1

The service can stop abruptly any time. It can crash, or power can be interrupted. The result should be consistent in any of the above cases. This is why you use Transactions when interacting with a database: you commit when the DB is in a consistent state. In case of interrupt (crash, SCM stop, power loss, anything) the DB will rollback to the previous consistent state.

You are totally barking up the wrong tree attempting to fix the SCM interaction. Your focus should be around the DB work, make sure your transaction boundaries are correct.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • This is useful, but this won't solve the problem and that is, this service is processing data and sending request to 2nd server. 2nd server will process data and if I rollback and redo it, that means I am processing the same request again. – Hitin Jul 07 '15 at 11:09
  • Enroll the two servers in a distributed transaction. Or use a reliable messaging protocol. This whole problem area is well studied and understood. Attempting to make the service 'fail proof' is not an option. – Remus Rusanu Jul 07 '15 at 11:18
  • I have no control over the remote server, its owned by ISP. I am using windows service to make calls via API they provided, windows service is responsible for fetching the records from DB and passing it onto remote server. I came across this http://stackoverflow.com/questions/22534330/windows-service-onstop-wait-for-finished-processing but really able to understand, the thread part. – Hitin Jul 07 '15 at 11:25
0
serviceStatus.dwCurrentState = ServiceState.SERVICE_STOP_PENDING;
serviceStatus.dwWaitHint = 100000;
SetServiceStatus(this.ServiceHandle, ref serviceStatus);

While completing any existing tasks, you need to regularly set the service status, with a suitably decreasing wait hint.

"Regularly" here requires a little guesswork, but it needs to be often enough to ensure the service control manager does not treat your service as not shutting down. In the past I did it every couple of seconds without issue.

If you have anything that is going to take a significant time (more than a few tens of seconds) to complete, you really need to restructure things so it can be resumed on service restart (remember the shutdown could be because the system is about to lose power or otherwise needs to stop ASAP).

Richard
  • 106,783
  • 21
  • 203
  • 265
  • What happens if the SendScheduledSMS takes more than what I set in the wait hint? How can I updated wait hint if the code is still executing or took longer than expected? – Hitin Jul 07 '15 at 11:20
  • @Hitin I didn't say it was easy: this is why it isn't. You need to do the send on one thread while updating the SCM on another (use the thread pool: don't do your own thread management), but you also need to work out what to do if the send is not completing. – Richard Jul 07 '15 at 11:32