SimonPratt has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,

I have built a couple of Windows Services using Win32::Daemon. Individually, these services work very well over many days of operation. The problem occurs when I have both services installed on the same machine - After a few hours one (or, very rarely, both) of these services stops running.

Upon investigation, it appears the running callback stops being called after a random amount of time (usually between 5 and 8 hours) and usually only for the second service that was installed (usually the first service installed, whichever one that is, will continue to function normally).

When the running callback stops being called, the service is still functional (it completes any work it is currently doing and also responds correctly to the pause, continue and stop callbacks), however in order for it to start working again, the service must be stopped and restarted.

I would really appreciate any assistance / advice regarding this issue, thanks.

Please find simplified example code here that exhibits the same problem:

use strict; use warnings; use Win32::Daemon; use constant SERVICE_POLL_RATE => 1E3; if (my $opt = shift @ARGV){ my %serviceSettings = ( name => 'MyExampleService', #name => 'MyExampleService2', display => 'My Example Service', #display => 'My Example Service 2', path => $^X, parameters => $0, ); if ($opt eq '-i') { installService(%serviceSettings); } elsif ($opt eq '-r'){ deleteService(%serviceSettings); } exit; } my %Context = ( last_state => SERVICE_STOPPED, start_time => time, ); my $logFile = $0; $logFile =~ s/\w+$/log/; open STDOUT, ">", $logFile; $|=1; print "Register callbacks\n"; Win32::Daemon::RegisterCallbacks( { start => \&callbackStart, stop => \&callbackStop, running => \&callbackRunning, pause => \&callbackPause, continue => \&callbackContinue, } ); print "Start service\n"; Win32::Daemon::StartService( \%Context, SERVICE_POLL_RATE ); print "Service finished, cleaning up\n"; close STDOUT; ################################################################## sub deleteService { my (%serviceSettings) = @_; Win32::Daemon::DeleteService('', $serviceSettings{name}) or die "D +elete service failed, error:\n\t".Win32::Daemon::GetLastError().": ". +Win32::FormatMessage(Win32::Daemon::GetLastError()); print "Service deleted\n"; } sub installService { my (%serviceSettings) = @_; Win32::Daemon::CreateService(\%serviceSettings) or die "Install se +rvice failed, error:\n\t".Win32::Daemon::GetLastError().": ".Win32::F +ormatMessage(Win32::Daemon::GetLastError()); print "Service created\n"; } sub callbackContinue { my ($event, $Context) = @_; print "Event $event: Continue ...\n"; $Context->{last_state} = SERVICE_RUNNING; Win32::Daemon::State( SERVICE_RUNNING ); } sub callbackPause { my ($event, $Context) = @_; print "Event $event: Pause ...\n"; $Context->{last_state} = SERVICE_PAUSED; Win32::Daemon::State( SERVICE_PAUSED ); } sub callbackRunning { my ($event, $Context) = @_; return Win32::Daemon::State() if SERVICE_PAUSED eq Win32::Daemon:: +State(); if (SERVICE_RUNNING eq Win32::Daemon::State()) { my ($ss, $mm, $hh, $dd, $nn, $yy) = gmtime; my $timestamp = sprintf("%04d%02d%02d %02d +:%02d:%02d", $yy + 1900, $nn + 1, $dd, $hh, $mm, $ss); print "$timestamp: Running ...\n" if (time % 60); } Win32::Daemon::State(); } sub callbackStart { my ($event, $Context) = @_; print "Event $event: Start ...\n"; $Context->{last_state} = SERVICE_RUNNING; Win32::Daemon::State( SERVICE_RUNNING ); } sub callbackStop { my ($event, $Context) = @_; print "Event $event: Stopped ...\n"; $Context->{last_state} = SERVICE_STOPPED; Win32::Daemon::State( SERVICE_STOPPED ); Win32::Daemon::StopService(); }

Replies are listed 'Best First'.
Re: Running callback stops working in Win32::Daemon
by Athanasius (Archbishop) on Jan 24, 2014 at 13:15 UTC

    Hello SimonPratt, and welcome to the Monastery!

    You don’t say which version of Win32::Daemon you’re running. I see from the documentation for the latest version (20131206):

    Timer/Running Callbacks:

    Starting with build 20080321 the "running" callback is deprecated and replaced with the "timer" callback. Scripts should no longer test for a state of SERVICE_RUNNING but instead check for the state of SERVICE_CONTROL_TIMER to indicate whether or not a callback has occurred due to a timer.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Hi, Athanasius

      Sorry, I'm not sure this is actually going to help.

      Specifically, further on in the docs, it states:

      ...registers for the "running" callback it will continue to work as expected: timer expiration results in a callback to the subroutine registered for the "running" callback passing in a value of SERVICE_RUNNING.

      Now, I have tested switching the running callback registration to use a timer callback registration and the actual behaviour doesn't appear to match what is defined in the documentation (in that the STATE eq SERVICE_RUNNING, regardless of whether I register a timer callback or a running callback). Instead of the STATE changing, the EVENT passed to the function is changed from SERVICE_CONTROL_RUNNING to SERVICE_CONTROL_TIMER, which kind of makes sense.

      If you comment out the return and the check in callbackRunning in the example I supplied (lines 85 and 87), you'll (eventually) see that callbackRunning simply stops being called in one of the services after several hours with both copies of the service running, although if you only have one copy installed and running, it will just keep running.

      I'll test it now with a timer callback registered instead of a running callback and let you know how it goes.

      Thanks for your help so far though, it is very much appreciated.

      Heh, I feel a bit stupid now :-)

      Thanks for your help, I'm sure that will put me on the right track.

      In answer to your question, I'm still on 20110117, however the changelog doesn't appear to address anything more serious than typos and documentation fixes since that version anyway (although hopefully these changes mean that maintenance of this library will pick up again).

Re: Running callback stops working in Win32::Daemon
by SimonPratt (Friar) on Jan 28, 2014 at 09:16 UTC

    OK, I have done some further research and testing. The testing outlined below was all completed with version 20110117 (I have tried numerous times to get the latest version installed on my machine here, however we use ActiveState Perl at work and the only c++ compiler I have access to is through VS Express 2013, which hobbles my ability to compile the module, due to missing libraries - I'll try to get some time to try at home, where I have Strawberry). In addition, the testing was done with both the deprecated running callback and the new(er) timer callback, with the same results in both cases. Wherever I refer to the running callback, I do in fact mean both.

    Bug 64717 shows that obtaining the status of the service resets the timer in Win32::Daemon. This was identified in v20101014 and my testing shows that it still exists in v20110117. However, this did give me a couple of ideas:

    1. Something is resetting the timer
    2. Something is blocking the timer

    With this in mind, I tried having the running callback reset the timer every time it is called through use of CallbackTimer. This was ineffective, leading me to believe that whilst the service is still responsive to commands sent by the SCM, there is some interaction happening between the two installed services, causing one of the services to end up having its timer blocked (or potentially indefinitely resetting at a rate faster than the running callback was being called).

    Shifting the running callback into a separate thread that performs its own timing and removing the registration is the final piece of testing I have started now. I'll report back on how that performs, though I do think it will work.

    UPDATE: As expected, removing the running callback and shifting this control out to a separate thread is working correctly.