[Zope-Checkins] CVS: Zope/lib/python/nt_svcutils - service.py:1.5
Jeremy Hylton
jeremy at zope.com
Mon Mar 29 11:07:48 EST 2004
Update of /cvs-repository/Zope/lib/python/nt_svcutils
In directory cvs.zope.org:/tmp/cvs-serv31778
Modified Files:
service.py
Log Message:
Merge jeremy-windows-service-branch to the trunk.
There are no tests for this code, but the branch was tested by using
it for releases of some Zope Corp. products.
=== Zope/lib/python/nt_svcutils/service.py 1.4 => 1.5 ===
--- Zope/lib/python/nt_svcutils/service.py:1.4 Tue Jan 13 17:37:02 2004
+++ Zope/lib/python/nt_svcutils/service.py Mon Mar 29 11:07:48 2004
@@ -14,6 +14,7 @@
"""Windows Services installer/controller for Zope/ZEO/ZRS instance homes"""
+import msvcrt
import win32api
import win32con
import win32event
@@ -37,8 +38,13 @@
BACKOFF_INITIAL_INTERVAL = 5
class Service(win32serviceutil.ServiceFramework):
- """ A class representing a Windows NT service that can manage an
- instance-home-based Zope/ZEO/ZRS processes """
+ """Base class for a Windows Server to manage an external process.
+
+ Subclasses can be used to managed an instance home-based Zope or
+ ZEO process. The win32 Python service module registers a specific
+ file and class for a service. To manage an instance, a subclass
+ should be created in the instance home.
+ """
# The PythonService model requires that an actual on-disk class declaration
# represent a single service. Thus, the below definition of start_cmd,
@@ -54,7 +60,14 @@
r'"C:\Program Files\Zope-2.7.0-a1\lib\python\Zope\Startup\run.py" '
r'-C "C:\Zope-Instance\etc\zope.conf"'
)
-
+
+ # If capture_io is True, then log_file must be the path of a file
+ # that the controlled process's stdout and stderr will be written to.
+ # The I/O capture is immature. It does not handle buffering in the
+ # controlled process or sensible interleaving of output between
+ # stdout and stderr. It is intended primarily as a stopgap when
+ # the controlled process produces critical output that can't be
+ # written to a log file using mechanism inside that process.
capture_io = False
log_file = None
@@ -67,6 +80,7 @@
def SvcStop(self):
# Before we do anything, tell the SCM we are starting the stop process.
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+ self.onStop()
# stop the process if necessary
try:
win32process.TerminateProcess(self.hZope, 0)
@@ -76,8 +90,14 @@
# And set my event.
win32event.SetEvent(self.hWaitStop)
+ def onStop(self):
+ # A hook for subclasses to override
+ pass
+
def createProcess(self, cmd):
+ self.start_time = time.time()
if self.capture_io:
+ self.log = open(self.log_file, "ab")
return self.createProcessCaptureIO(cmd)
else:
return win32process.CreateProcess(
@@ -126,56 +146,108 @@
# BACKOFF_CLEAR_TIME seconds, the backoff stats are reset.
# the initial number of seconds between process start attempts
- backoff_interval = BACKOFF_INITIAL_INTERVAL
+ self.backoff_interval = BACKOFF_INITIAL_INTERVAL
# the cumulative backoff seconds counter
- backoff_cumulative = 0
+ self.backoff_cumulative = 0
import servicemanager
self.logmsg(servicemanager.PYS_SERVICE_STARTED)
while 1:
- start_time = time.time()
info, handles = self.createProcess(self.start_cmd)
- # XXX integrate handles into the wait and make a loop
- # that reads data and writes it into a logfile
- self.hZope = info[0] # the pid
- if backoff_interval > BACKOFF_INITIAL_INTERVAL:
+ self.hZope = info[0] # process handle
+ # XXX why the test before the log message?
+ if self.backoff_interval > BACKOFF_INITIAL_INTERVAL:
self.info("created process")
- rc = win32event.WaitForMultipleObjects(
- (self.hWaitStop, self.hZope) + handles, 0, win32event.INFINITE)
+ if not (self.run(handles) and self.checkRestart()):
+ break
+ self.logmsg(servicemanager.PYS_SERVICE_STOPPED)
+
+ def run(self, handles):
+ """Monitor the daemon process.
+
+ Returns True if the service should continue running and
+ False if the service process should exit. On True return,
+ the process exited unexpectedly and the caller should restart
+ it.
+ """
+
+ keep_running = True
+ # Assume that the controlled program isn't expecting anything
+ # on stdin.
+ if handles:
+ handles[0].Close()
+
+ if handles:
+ waitfor = [self.hWaitStop, self.hZope, handles[1], handles[2]]
+ else:
+ waitfor = [self.hWaitStop, self.hZope]
+ while 1:
+ rc = win32event.WaitForMultipleObjects(waitfor, 0,
+ win32event.INFINITE)
if rc == win32event.WAIT_OBJECT_0:
# user sent a stop service request
self.SvcStop()
+ keep_running = False
break
- else:
+ elif rc == win32event.WAIT_OBJECT_0 + 1:
# user did not send a service stop request, but
# the process died; this may be an error condition
status = win32process.GetExitCodeProcess(self.hZope)
- if status == 0:
- # the user shut the process down from the web
- # interface (or it otherwise exited cleanly)
- break
- else:
- # this was an abormal shutdown.
- if backoff_cumulative > BACKOFF_MAX:
- self.error("restarting too frequently; quit")
- self.SvcStop()
- break
- self.warning("sleep %s to avoid rapid restarts"
- % backoff_interval)
- if time.time() - start_time > BACKOFF_CLEAR_TIME:
- backoff_interval = BACKOFF_INITIAL_INTERVAL
- backoff_cumulative = 0
- # XXX Since this is async code, it would be better
- # done by sending and catching a timed event (a
- # service stop request will need to wait for us to
- # stop sleeping), but this works well enough for me.
- time.sleep(backoff_interval)
- backoff_cumulative += backoff_interval
- backoff_interval *= 2
-
- self.logmsg(servicemanager.PYS_SERVICE_STOPPED)
-
+ # exit status 0 means the user caused a clean shutdown,
+ # presumably via the web interface
+ keep_running = status != 0
+ break
+ else:
+ i = rc - win32event.WAIT_OBJECT_0
+ if not self.redirect(waitfor[i]):
+ del waitfor[i]
+ if handles:
+ handles[1].Close()
+ handles[2].Close()
+ return keep_running
+
+ def redirect(self, handle):
+ # This call will block until 80 bytes of output are ready.
+ # If the controlled program is buffering its I/O, it's
+ # possible for this to take a long time. Don't know if
+ # there is a better solution.
+ try:
+ ec, data = win32file.ReadFile(handle, 80)
+ except pywintypes.error, err:
+ # 109 means that the pipe was closed by the controlled
+ # process. Other errors might have similarly inocuous
+ # explanations, but we haven't run into them yet.
+ if err[0] != 109:
+ self.warning("Error reading output from process: %s" % err)
+ return False
+ # In the absence of overlapped I/O, the Python win32api
+ # turns all error codes into exceptions.
+ assert ec == 0
+ self.log.write(data)
+ self.log.flush()
+ return True
+
+ def checkRestart(self):
+ # this was an abormal shutdown.
+ if self.backoff_cumulative > BACKOFF_MAX:
+ self.error("restarting too frequently; quit")
+ self.SvcStop()
+ return False
+ self.warning("sleep %s to avoid rapid restarts"
+ % self.backoff_interval)
+ if time.time() - self.start_time > BACKOFF_CLEAR_TIME:
+ self.backoff_interval = BACKOFF_INITIAL_INTERVAL
+ self.backoff_cumulative = 0
+ # XXX Since this is async code, it would be better
+ # done by sending and catching a timed event (a
+ # service stop request will need to wait for us to
+ # stop sleeping), but this works well enough for me.
+ time.sleep(self.backoff_interval)
+ self.backoff_cumulative += self.backoff_interval
+ self.backoff_interval *= 2
+ return True
+
def createProcessCaptureIO(self, cmd):
stdin = self.newPipe()
stdout = self.newPipe()
@@ -198,10 +270,9 @@
# circumstances of a service process.
info = win32process.CreateProcess(None, cmd, None, None, True, 0,
None, None, si)
-
- win32file.CloseHandle(stdin[0])
- win32file.CloseHandle(stdout[1])
- win32file.CloseHandle(stderr[1])
+ stdin[0].Close()
+ stdout[1].Close()
+ stderr[1].Close()
return info, (c_stdin, c_stdout, c_stderr)
@@ -217,8 +288,9 @@
pid = win32api.GetCurrentProcess()
dup = win32api.DuplicateHandle(pid, pipe, pid, 0, 0,
win32con.DUPLICATE_SAME_ACCESS)
- win32file.CloseHandle(pipe)
+ pipe.Close()
return dup
if __name__ == '__main__':
win32serviceutil.HandleCommandLine(Service)
+
More information about the Zope-Checkins
mailing list