Correct behavior with passing back errors on onStart, onStop.

This commit is contained in:
Daniel Theophanes
2012-03-08 16:45:31 -08:00
parent 9c12e059b5
commit ad4beb2a49
2 changed files with 166 additions and 161 deletions
+3
View File
@@ -15,6 +15,9 @@ type Service interface {
// Call quickly after initial entry point. Does not return until // Call quickly after initial entry point. Does not return until
// service is ready to stop. onStart is called when the service is // service is ready to stop. onStart is called when the service is
// starting, returning an error will fail to start the service. // starting, returning an error will fail to start the service.
// If an error is returned from onStop, the service will still stop.
// An error passed from onStart or onStop will be returned as
// an error from Run.
// Both callbacks should return quickly and not block. // Both callbacks should return quickly and not block.
Run(onStart, onStop func() error) error Run(onStart, onStop func() error) error
+163 -161
View File
@@ -1,190 +1,192 @@
package service package service
/* /*
#include <windows.h> #include <windows.h>
SERVICE_STATUS gSvcStatus; SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle; SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE ghSvcStopEvent = NULL; HANDLE ghSvcStopEvent = NULL;
void SvcInstall(void); void SvcInstall(void);
void WINAPI SvcCtrlHandler( DWORD ); void WINAPI SvcCtrlHandler( DWORD );
void WINAPI SvcMain( DWORD, LPTSTR * ); void WINAPI SvcMain( DWORD, LPTSTR * );
void ReportSvcStatus( DWORD, DWORD, DWORD ); void ReportSvcStatus( DWORD, DWORD, DWORD );
static char *goServiceName; static char *goServiceName;
int goSigStart = 0; int goSigStart = 0;
int goAckStart = 0; int goAckStart = 0;
HANDLE goWaitStart = NULL; HANDLE goWaitStart = NULL;
int goSigStop = 0; int goSigStop = 0;
int goAckStop = 0; int goAckStop = 0;
HANDLE goWaitStop = NULL; HANDLE goWaitStop = NULL;
int goSigError = 0; int goSigError = 0;
char *errorText; char *errorText;
void void
continueStart(int response) { continueStart(int response) {
goSigStart = 0; goSigStart = 0;
goAckStart = response; goAckStart = response;
SetEvent(goWaitStart); SetEvent(goWaitStart);
} }
void void
continueStop(int response) { continueStop(int response) {
goSigStop = 0; goSigStop = 0;
goAckStop = response; goAckStop = response;
SetEvent(goWaitStop); SetEvent(goWaitStop);
} }
void void
signalError(char *text) { signalError(char *text) {
errorText = text; errorText = text;
goSigError = 1; goSigError = 1;
} }
void void
initService(char *serviceName) { initService(char *serviceName) {
if(!goServiceName) { if(!goServiceName) {
free(goServiceName); free(goServiceName);
} }
goServiceName = serviceName; goServiceName = serviceName;
SERVICE_TABLE_ENTRY DispatchTable[] = { SERVICE_TABLE_ENTRY DispatchTable[] = {
{ serviceName, (LPSERVICE_MAIN_FUNCTION) SvcMain }, { serviceName, (LPSERVICE_MAIN_FUNCTION) SvcMain },
{ NULL, NULL } { NULL, NULL }
}; };
// This call returns when the service has stopped. // This call returns when the service has stopped.
// The process should simply terminate when the call returns. // The process should simply terminate when the call returns.
if (!StartServiceCtrlDispatcher( DispatchTable )) { if (!StartServiceCtrlDispatcher( DispatchTable )) {
signalError("StartServiceCtrlDispatcher"); signalError("StartServiceCtrlDispatcher");
} }
} }
// Entry point for the service // Entry point for the service
// dwArgc - Number of arguments in the lpszArgv array // dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of // lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process // the service and subsequent strings are passed by the process
// that called the StartService function to start the service. // that called the StartService function to start the service.
VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv ) VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )
{ {
// Register the handler function for the service // Register the handler function for the service
gSvcStatusHandle = RegisterServiceCtrlHandler( gSvcStatusHandle = RegisterServiceCtrlHandler(
goServiceName, goServiceName,
SvcCtrlHandler); SvcCtrlHandler);
if( !gSvcStatusHandle ) { if( !gSvcStatusHandle ) {
signalError("RegisterServiceCtrlHandler"); signalError("RegisterServiceCtrlHandler");
return; return;
} }
// These SERVICE_STATUS members remain as set here // These SERVICE_STATUS members remain as set here
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0; gSvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM // Report initial status to the SCM
ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 ); ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );
goWaitStart = CreateEvent(NULL, TRUE, FALSE, NULL); goWaitStart = CreateEvent(NULL, TRUE, FALSE, NULL);
goWaitStop = CreateEvent(NULL, TRUE, FALSE, NULL); goWaitStop = CreateEvent(NULL, TRUE, FALSE, NULL);
// signal go to start // signal go to start
// wait for go to confirm // wait for go to confirm
goSigStart = 1; goSigStart = 1;
WaitForSingleObject(goWaitStart, INFINITE); WaitForSingleObject(goWaitStart, INFINITE);
if(goAckStart != 1) { if(goAckStart != 1) {
return; ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
} return;
}
// TODO: Declare and set any required variables. // TODO: Declare and set any required variables.
// Be sure to periodically call ReportSvcStatus() with // Be sure to periodically call ReportSvcStatus() with
// SERVICE_START_PENDING. If initialization fails, call // SERVICE_START_PENDING. If initialization fails, call
// ReportSvcStatus with SERVICE_STOPPED. // ReportSvcStatus with SERVICE_STOPPED.
// Create an event. The control handler function, SvcCtrlHandler, // Create an event. The control handler function, SvcCtrlHandler,
// signals this event when it receives the stop control code. // signals this event when it receives the stop control code.
ghSvcStopEvent = CreateEvent( ghSvcStopEvent = CreateEvent(
NULL, // default security attributes NULL, // default security attributes
TRUE, // manual reset event TRUE, // manual reset event
FALSE, // not signaled FALSE, // not signaled
NULL); // no name NULL); // no name
if ( ghSvcStopEvent == NULL) if ( ghSvcStopEvent == NULL)
{ {
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return; return;
} }
// Report running status when initialization is complete. // Report running status when initialization is complete.
ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 ); ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
while(1) { while(1) {
WaitForSingleObject(ghSvcStopEvent, INFINITE); WaitForSingleObject(ghSvcStopEvent, INFINITE);
// Signal service Stopped // Signal service Stopped
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return; return;
} }
} }
// Sets the current service status and reports it to the SCM. // Sets the current service status and reports it to the SCM.
// dwCurrentState - The current state (see SERVICE_STATUS) // dwCurrentState - The current state (see SERVICE_STATUS)
// dwWin32ExitCode - The system error code // dwWin32ExitCode - The system error code
// dwWaitHint - Estimated time for pending operation, in milliseconds // dwWaitHint - Estimated time for pending operation, in milliseconds
VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) {
static DWORD dwCheckPoint = 1; static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure. // Fill in the SERVICE_STATUS structure.
gSvcStatus.dwCurrentState = dwCurrentState; gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
gSvcStatus.dwWaitHint = dwWaitHint; gSvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING) { if (dwCurrentState == SERVICE_START_PENDING) {
gSvcStatus.dwControlsAccepted = 0; gSvcStatus.dwControlsAccepted = 0;
} else { } else {
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
} }
if ( (dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED) ) { if ( (dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED) ) {
gSvcStatus.dwCheckPoint = 0; gSvcStatus.dwCheckPoint = 0;
} else { } else {
gSvcStatus.dwCheckPoint = dwCheckPoint++; gSvcStatus.dwCheckPoint = dwCheckPoint++;
} }
// Report the status of the service to the SCM. // Report the status of the service to the SCM.
SetServiceStatus( gSvcStatusHandle, &gSvcStatus ); SetServiceStatus( gSvcStatusHandle, &gSvcStatus );
} }
// Called by SCM whenever a control code is sent to the service // Called by SCM whenever a control code is sent to the service
// using the ControlService function. // using the ControlService function.
VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ) { VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ) {
// Handle the requested control code. // Handle the requested control code.
switch(dwCtrl) { switch(dwCtrl) {
case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
goSigStop = 1; goSigStop = 1;
WaitForSingleObject(goWaitStop, INFINITE); WaitForSingleObject(goWaitStop, INFINITE);
if(goAckStop != 1) { if(goAckStop != 1) {
return; ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
} return;
}
// Now signal on the initial thread to stop blocking. // Now signal on the initial thread to stop blocking.
SetEvent(ghSvcStopEvent); SetEvent(ghSvcStopEvent);
return; return;
case SERVICE_CONTROL_INTERROGATE: case SERVICE_CONTROL_INTERROGATE:
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
break; break;
default: default:
break; break;
} }
} }
*/ */
import "C" import "C"
@@ -193,26 +195,26 @@ import (
"time" "time"
) )
// Starts a windows service routine. Service must be registered first. // Starts a windows service routine. Service must be registered first.
// Call blocks until an error occurs or the service stops. If onStart returns // Call blocks until an error occurs or the service stops. If onStart returns
// an error, service will not start. If onStop returns an error, the service will // an error, service will not start. If onStop returns an error, the service will
// not stop. // not stop.
func runService(serviceName string, onStart, onStop func() error) error { func runService(serviceName string, onStart, onStop func() error) error {
// We alloc a c string here, but do not free it here // We alloc a c string here, but do not free it here
cname := C.CString(serviceName) cname := C.CString(serviceName)
retErr := make(chan error, 1) retErr := make(chan error, 1)
go func() { go func() {
// Check C vars on timer. // Check C vars on timer.
ticker := time.NewTicker(time.Second * 1) ticker := time.NewTicker(time.Second * 1)
defer ticker.Stop() defer ticker.Stop()
for _ = range ticker.C { for _ = range ticker.C {
if C.goSigStart == 1 { if C.goSigStart == 1 {
err := onStart() err := onStart()
if err != nil { if err != nil {
// An error was returned. // An error was returned.
// Signal to NOT start the service. // Signal to NOT start the service.
C.continueStart(-1) C.continueStart(-1)
retErr <- err retErr <- err
return return
@@ -221,8 +223,8 @@ func runService(serviceName string, onStart, onStop func() error) error {
} else if C.goSigStop == 1 { } else if C.goSigStop == 1 {
err := onStop() err := onStop()
if err != nil { if err != nil {
// An error was returned. // An error was returned.
// Signal to NOT stop the service. // Will signal to stop service.
C.continueStop(-1) C.continueStop(-1)
retErr <- err retErr <- err
return return
@@ -230,7 +232,7 @@ func runService(serviceName string, onStart, onStop func() error) error {
C.continueStop(1) C.continueStop(1)
retErr <- nil retErr <- nil
} else if C.goSigError == 1 { } else if C.goSigError == 1 {
// Check for service errors. // Check for service errors.
errText := "SERVICE ERROR: " errText := "SERVICE ERROR: "
if C.errorText != nil { if C.errorText != nil {
errText += C.GoString(C.errorText) errText += C.GoString(C.errorText)