[python-win32] automatically restart python service after crash

Aaron Burrow burrows at preveil.com
Mon Jan 9 03:25:39 EST 2017


I developed a python service using win32serviceutil.ServiceFramework
and PythonService.exe.  If the service
crashes (eg, an unhandled exception is raised) I want the service to
restart.  So, I configured the service to restart
using `ChangeServiceConfig2`. Code looks like this.

```
import win32serviceutil
import win32service
import os

from . import makeWin32Service

win32serviceutil.HandleCommandLine(makeWin32Service.PreVeilService)

service_name = os.environ.get("PV_SERVICE_NAME")
win32service.ChangeServiceConfig2
hscm = win32service.OpenSCManager(None,None,win32service.SC_MANAGER_ALL_ACCESS)
try:
    hs = win32service.OpenService(hscm, service_name,
win32service.SERVICE_ALL_ACCESS)
    try:
        win32service.ChangeServiceConfig2(hs,
win32service.SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, True)

        service_failure_actions = {
                'ResetPeriod': 60*60,       # Time in seconds after
which to reset the failure count to zero.
                'RebootMsg': u'',           # Not using reboot option
                'Command': u'',             # Not using run-command option
                'Actions': [
                        (win32service.SC_ACTION_RESTART, 1000*5),   #
first action after failure, delay in ms
                        (win32service.SC_ACTION_RESTART, 1000*5),   #
second action after failure
                        (win32service.SC_ACTION_NONE, 0)            #
subsequent actions after failure
                    ]
            }
        win32service.ChangeServiceConfig2(hs,
win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
    except (win32service.error, NotImplementedError):
        print "ChangeServiceConfig2 failed to set restart behavior"
    finally:
        win32service.CloseServiceHandle(hs)
finally:
    win32service.CloseServiceHandle(hscm)
```

This does not work with vanilla pywin32. From the MSDN page for
`SERVICE_CONFIG_FAILURE_ACTIONS_FLAG`:

   "If this member [fFailureActionsOnNonCrashFailures] is TRUE and the
service has configured failure actions, the failure
    actions are queued if the service process terminates without
reporting a status of SERVICE_STOPPED or if it enters
    the SERVICE_STOPPED state but the dwWin32ExitCode member of the
SERVICE_STATUS structure is not
    ERROR_SUCCESS (0).

   If this member is FALSE and the service has configured failure
actions, the failure actions are queued only if the
   service terminates without reporting a status of SERVICE_STOPPED.

   This setting is ignored unless the service has configured failure
actions. For information on configuring failure actions,
   see ChangeServiceConfig2."

In order for the process to restart PythonService.exe needs to NOT set
status to SERVICE_STOPPED or set
dwWin32ExitCode to some non zero value.  These conditions are not met
in PythonService.cpp:

```
void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv)
{
  // ...

  start = PyObject_GetAttrString(instance, "SvcRun");
  if (start==NULL)
  ReportPythonError(E_PYS_NO_RUN_METHOD);
  else {
    // Call the Python service entry point - when this returns, the
    // service has stopped!
    PyObject *result = PyObject_CallObject(start, NULL);
    if (result==NULL)
      ReportPythonError(E_PYS_START_FAILED);
    else
      Py_DECREF(result);
  }
  // We are all done.
cleanup:
  // try to report the stopped status to the service control manager.
  Py_XDECREF(start);
  Py_XDECREF(instance);
  if (pe && pe->sshStatusHandle) { // Wont be true if debugging.
    if (!SetServiceStatus( pe->sshStatusHandle, &stoppedStatus ))
      ReportAPIError(PYS_E_API_CANT_SET_STOPPED);
    }
  return;
}
```

Presumably we should do something with the return value from `SvcRun`
then conditionally report an error with
dwWin32ExitCode. Here is my patch that gives restarts when `SvcRun`
throws an exception.  The ability to
let Windows handle service restarts is important; I would like to get
this fixed in upstream.

```
--- win32/src/PythonService.cpp 2017-01-07 14:58:48.156762600 -0500
+++ win32/src/PythonService.cpp 2017-01-09 03:11:08.821727300 -0500
@@ -144,6 +144,15 @@
     0, // dwCheckPoint;
     5000 };

+SERVICE_STATUS stoppedErrorStatus = {
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_STOPPED,
+    0, // dwControlsAccepted,
+    ERROR_SERVICE_SPECIFIC_ERROR, // dwWin32ExitCode;
+    1, // dwServiceSpecificExitCode;
+    0, // dwCheckPoint;
+    5000 };
+
 SERVICE_STATUS startingStatus = {
  SERVICE_WIN32_OWN_PROCESS,
  SERVICE_START_PENDING,
@@ -916,9 +925,13 @@
  // Call the Python service entry point - when this returns, the
  // service has stopped!
  PyObject *result = PyObject_CallObject(start, NULL);
- if (result==NULL)
+ if (result==NULL) {
  ReportPythonError(E_PYS_START_FAILED);
- else
+ if (pe && pe->sshStatusHandle) { // Wont be true if debugging.
+ SetServiceStatus( pe->sshStatusHandle, &stoppedErrorStatus );
+ pe->sshStatusHandle = 0; // reset so we don't attempt to set 'stopped'
+ }
+ } else
  Py_DECREF(result);
  }
  // We are all done.
```


More information about the python-win32 mailing list