[Zope-Checkins] SVN: Zope/trunk/ Major service enhancements. Service cleanly shuts down child, and if child

Sidnei da Silva sidnei at awkly.org
Wed Apr 13 22:00:15 EDT 2005


Log message for revision 29975:
  
  Major service enhancements.  Service cleanly shuts down child, and if child
  fails the tail of the process output (which generally contains a traceback)
  is
  written to the event log.
  
  Minor tweaks to the Windows build 'clean' process and documentation tweaks.
  
  Don't kill the service if we can't write to the event log
  

Changed:
  U   Zope/trunk/doc/INSTALL.txt
  A   Zope/trunk/doc/WINDOWS.txt
  U   Zope/trunk/inst/Makefile.win.in
  U   Zope/trunk/inst/configure.py
  U   Zope/trunk/lib/python/nt_svcutils/service.py
  U   Zope/trunk/skel/bin/runzope.bat.in
  U   Zope/trunk/skel/bin/zopeservice.py.in
  U   Zope/trunk/utilities/mkzopeinstance.py

-=-
Modified: Zope/trunk/doc/INSTALL.txt
===================================================================
--- Zope/trunk/doc/INSTALL.txt	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/doc/INSTALL.txt	2005-04-14 02:00:15 UTC (rev 29975)
@@ -3,6 +3,9 @@
 
   Welcome to Zope!  This document describes building and installing
   Zope on UNIX and Linux.
+  
+  See WINDOWS.txt for information about Windows.  See the PLATFORMS
+  directory for notes about various other platforms.
 
 System requirements when building from source
 

Added: Zope/trunk/doc/WINDOWS.txt
===================================================================
--- Zope/trunk/doc/WINDOWS.txt	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/doc/WINDOWS.txt	2005-04-14 02:00:15 UTC (rev 29975)
@@ -0,0 +1,69 @@
+How to build and install Zope from source code on Windows.
+----------------------------------------------------------
+These instructions appear to work for 2.7 and the trunk:
+
+* Ensure you have the correct MSVC version installed for the
+  version of Python you will be using.
+  
+* Install (or build from sources) Python
+  http://www.python.org
+  
+* Install (or build from sources) the Python for Windows extensions
+  http://sourceforge.net/projects/pywin32/
+
+* Unpack the Zope source distribution or pull from CVS.  Change
+  to that directory.
+
+* Execute:
+  % python.exe inst\configure.py
+  It should say something like:
+  >
+  > - Zope top-level binary directory will be c:\Zope-2.7.
+  > - Makefile written.
+  >
+  > Next, run the Visual C++ batch file "VCVARS32.bat" and then "nmake".
+
+  (run 'configure.py --help' to see how to change things)
+
+* 'makefile' will have ben created.  As instructed, execute 'nmake'.  
+  If the build succeeds, the last message printed should be:
+  > Zope built.  Next, do 'nmake install'.
+
+* As instructed, execute 'nmake install'.  A few warnings will be generated, 
+  but they can be ignored.  The last message in the build process should be:
+  > Zope binaries installed successfully.
+
+* If you are running from CVS, the build may fail:
+  See http://collector.zope.org/Zope/1530
+  > running install_data
+  > error: can't copy 'version.txt': no matching files
+  > NMAKE : fatal error U1077: '"e:\src\python-2.3-cvs\pcbuild\python.exe"' : return code '0x1'
+  > Stop.
+
+  If you see this error, edit setup.py and comment the line referencing 
+  'version.txt'
+
+* Zope itself has now been installed.  We need to create an instance.  Run:
+  % python.exe {install_path}\bin\mkzopeinstance.py
+  
+  We will be prompted, via the console, for the instance directory and 
+  username/password for the admin user.
+
+* We are now ready to start zope.  Run:
+  % {zope_instance}\run_zope.bat.
+  Zope should start with nice log messages being printed to
+  stdout.  When Zope is ready, you should see:
+  > ------
+  > 2004-10-13T12:27:58 INFO(0) Zope Ready to handle requests
+  
+  Press Ctrl+C to stop this instance of the server.
+
+* Optionally, install as a Windows service.  Execute:
+  % python {zope_instance}\zope_service.py
+  to see the valid options.  You may want something like:
+  % python {zope_instance}\zope_service.py --startup=auto install
+
+  Once installed, it can be started any number of ways:
+  - python {zope_instance}\zope_service.py start
+  - Control Panel
+  - net start service_short_name (eg, "net start Zope_-1227678699"


Property changes on: Zope/trunk/doc/WINDOWS.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: Zope/trunk/inst/Makefile.win.in
===================================================================
--- Zope/trunk/inst/Makefile.win.in	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/inst/Makefile.win.in	2005-04-14 02:00:15 UTC (rev 29975)
@@ -31,15 +31,15 @@
 XCOPY=xcopy /i /s /e /y
 COPY=copy
 
-.PHONY: clean install build unbuild
-.PHONY: default
-
 default: build
 # default:     The default step (invoked when make is called without a target)
 	@ echo.
 	@ echo Zope built.  Next, do 'nmake install'.
-	@ echo
+	@ echo.
 
+.PHONY: clean install build unbuild
+.PHONY: default
+
 # build:       Do whatever 'setup.py build' implies
 build:
 	$(PYTHON) "$(BASE_DIR)\setup.py" \
@@ -47,7 +47,7 @@
 
 # unbuild:     Remove the build directory (undo the make build step)
 unbuild:
-	$(RMRF) $(BUILD_BASE)
+	-$(RMRF) $(BUILD_BASE)
 
 # install:     Install a software home.
 install: build
@@ -62,7 +62,7 @@
 #              the source directory for good measure.
 clean: unbuild
 	$(CD) "$(BASE_DIR)
-	$(RM) /s *.pyc *.pyo *.dll *.o *.obj *.pyd
+	-$(RM) /s *.pyc *.pyo *.dll *.o *.obj *.pyd
 
 
 

Modified: Zope/trunk/inst/configure.py
===================================================================
--- Zope/trunk/inst/configure.py	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/inst/configure.py	2005-04-14 02:00:15 UTC (rev 29975)
@@ -23,7 +23,7 @@
 if sys.platform == 'win32':
     PREFIX = 'c:\\Zope-' + versions.ZOPE_MAJOR_VERSION
     IN_MAKEFILE = 'Makefile.win.in'
-    MAKE_COMMAND='the Visual C++ batch file "VCVARS32.bat" and then "nmake build"'
+    MAKE_COMMAND='the Visual C++ batch file "VCVARS32.bat" and then "nmake"'
 else:
     PREFIX = '/opt/Zope-' + versions.ZOPE_MAJOR_VERSION
     IN_MAKEFILE = 'Makefile.in'

Modified: Zope/trunk/lib/python/nt_svcutils/service.py
===================================================================
--- Zope/trunk/lib/python/nt_svcutils/service.py	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/lib/python/nt_svcutils/service.py	2005-04-14 02:00:15 UTC (rev 29975)
@@ -14,19 +14,10 @@
 
 """Windows Services installer/controller for Zope/ZEO/ZRS instance homes"""
 
-import msvcrt
-import win32api
-import win32con
-import win32event
-import win32file
-import win32pipe
-import win32process
-import win32security
-import win32service
-import win32serviceutil
-import pywintypes
-import time
-import os
+import sys, os, time, threading, signal
+import win32api, win32event, win32file, win32pipe, win32process, win32security
+import win32service, win32serviceutil, servicemanager
+import pywintypes, winerror, win32con
 
 # the max seconds we're allowed to spend backing off
 BACKOFF_MAX = 300
@@ -37,6 +28,15 @@
 # a dead process)
 BACKOFF_INITIAL_INTERVAL = 5
 
+# We execute a new thread that captures the tail of the output from our child
+# process. If the child fails, it is written to the event log.
+# This process is unconditional, and the output is never written to disk
+# (except obviously via the event log entry)
+# Size of the blocks we read from the child process's output.
+CHILDCAPTURE_BLOCK_SIZE = 80
+# The number of BLOCKSIZE blocks we keep as process output.
+CHILDCAPTURE_MAX_BLOCKS = 200
+
 class Service(win32serviceutil.ServiceFramework):
     """Base class for a Windows Server to manage an external process.
 
@@ -47,47 +47,40 @@
     """
 
     # The PythonService model requires that an actual on-disk class declaration
-    # represent a single service.  Thus, the below definition of start_cmd,
+    # represent a single service.  Thus, the definitions below for the instance
     # must be overridden in a subclass in a file within the instance home for
-    # each instance.  The below-defined start_cmd (and _svc_display_name_
-    # and _svc_name_) are just examples.
-
+    # each instance.
+    # The values below are just examples.
     _svc_name_ = r'Zope-Instance'
     _svc_display_name_ = r'Zope instance at C:\Zope-Instance'
 
-    start_cmd = (
-        r'"C:\Program Files\Zope-2.7.0-a1\bin\python.exe" '
-        r'"C:\Program Files\Zope-2.7.0-a1\lib\python\Zope\Startup\run.py" '
-        r'-C "C:\Zope-Instance\etc\zope.conf"'
-        )
+    process_runner = r'C:\Program Files\Zope-2.7.0-a1\bin\python.exe'
+    process_args = r'{path_to}\run.py -C {path_to}\zope.conf'
+    evtlog_name = 'Zope'
 
-    # 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
-
     def __init__(self, args):
         win32serviceutil.ServiceFramework.__init__(self, args)
+        # Just say "Zope", instead of "Zope_-xxxxx"
+        try:
+            servicemanager.SetEventSourceName(self.evtlog_name)
+        except AttributeError:
+            # old pywin32 - that's ok.
+            pass
         # Create an event which we will use to wait on.
         # The "service stop" request will set this event.
-        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
+        # We create it inheritable so we can pass it to the child process, so
+        # it too can act on the stop event.
+        sa = win32security.SECURITY_ATTRIBUTES()
+        sa.bInheritHandle = True
 
+        self.hWaitStop = win32event.CreateEvent(sa, 0, 0, None)
+        self.redirect_thread = None
+
     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)
-        except pywintypes.error:
-            # the process may already have been terminated
-            pass
-        # And set my event.
+        # Set the stop event - the main loop takes care of termination.
         win32event.SetEvent(self.hWaitStop)
 
     def onStop(self):
@@ -96,34 +89,39 @@
 
     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(
-                None, cmd, None, None, 0, 0, None, None,
-                win32process.STARTUPINFO()), None
+        return self.createProcessCaptureIO(cmd)
 
     def logmsg(self, event):
         # log a service event using servicemanager.LogMsg
-        from servicemanager import LogMsg, EVENTLOG_INFORMATION_TYPE
-        LogMsg(EVENTLOG_INFORMATION_TYPE, event,
-               (self._svc_name_, " (%s)" % self._svc_display_name_))
+        try:
+            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
+                                  event,
+                                  (self._svc_name_,
+                                   " (%s)" % self._svc_display_name_))
+        except win32api.error, details:
+            # Failed to write a log entry - most likely problem is
+            # that the event log is full.  We don't want this to kill us
+            print "FAILED to write INFO event", event, ":", details
 
+    def _dolog(self, func, msg):
+        try:
+            fullmsg = "%s (%s): %s" % \
+                      (self._svc_name_, self._svc_display_name_, msg)
+            func(fullmsg)
+        except win32api.error, details:
+            # Failed to write a log entry - most likely problem is
+            # that the event log is full.  We don't want this to kill us
+            print "FAILED to write event log entry:", details
+            print msg
+
     def info(self, s):
-        from servicemanager import LogInfoMsg
-        LogInfoMsg("%s (%s): %s" %
-                   (self._svc_name_, self._svc_display_name_, s))
+        self._dolog(servicemanager.LogInfoMsg, s)
 
     def warning(self, s):
-        from servicemanager import LogWarningMsg
-        LogWarningMsg("%s (%s): %s" %
-                      (self._svc_name_, self._svc_display_name_, s))
+        self._dolog(servicemanager.LogWarningMsg, s)
 
     def error(self, s):
-        from servicemanager import LogErrorMsg
-        LogErrorMsg("%s (%s): %s" %
-                    (self._svc_name_, self._svc_display_name_, s))
+        self._dolog(servicemanager.LogErrorMsg, s)
 
     def SvcDoRun(self):
         # indicate to Zope that the process is daemon managed (restartable)
@@ -150,20 +148,79 @@
         # the cumulative backoff seconds counter
         self.backoff_cumulative = 0
 
-        import servicemanager
         self.logmsg(servicemanager.PYS_SERVICE_STARTED)
-        
+
         while 1:
-            info, handles = self.createProcess(self.start_cmd)
+            # We pass *this* file and the handle as the first 2 params, then
+            # the 'normal' startup args.
+            # See the bottom of this script for how that is handled.
+            cmd = '"%s" %s' % (self.process_runner, self.process_args)
+            info = self.createProcess(cmd)
+            # info is (hProcess, hThread, pid, tid)
             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")
-            if not (self.run(handles) and self.checkRestart()):
+            if not (self.run() and self.checkRestart()):            
                 break
+
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+        # Stop the child process by opening the special named event.
+        # We give it 90 seconds to shutdown normally.  If that doesn't
+        # stop things, we give it 30 seconds to do a "fast" shutdown.
+        # After that, we just knock it on the head.
+        winver = sys.getwindowsversion()
+        for sig, timeout in ((signal.SIGINT, 30), (signal.SIGTERM, 10)):
+            event_name = "Zope-%d-%d" % (info[2], sig)
+            # sys.getwindowsversion() -> major, minor, build, platform_id, ver_string
+            # for platform_id, 2==VER_PLATFORM_WIN32_NT
+            if winver[0] >= 5 and winver[3] == 2:
+                event_name = "Global\\" + event_name
+            try:
+                he = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0,
+                                          event_name)
+            except win32event.error, details:
+                if details[0] == winerror.ERROR_FILE_NOT_FOUND:
+                    # process already dead!
+                    break
+                # no other expected error - report it.
+                self.warning("Failed to open child shutdown event %s"
+                             % (event_name,))
+                continue
+
+            win32event.SetEvent(he)
+            # It should be shutting down now - wait for termination, reporting
+            # progress as we go.
+            for i in range(timeout):
+                self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+                rc = win32event.WaitForSingleObject(self.hZope, 3000)
+                if rc == win32event.WAIT_OBJECT_0:
+                    break
+            # Process terminated - no need to try harder.
+            if rc == win32event.WAIT_OBJECT_0:
+                break
+
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+        # If necessary, kill it
+        if win32process.GetExitCodeProcess(self.hZope)==win32con.STILL_ACTIVE:
+            win32api.TerminateProcess(self.hZope, 3)
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+
+        # Wait for the redirect thread - it should have died as the remote 
+        # process terminated.
+        # As we are shutting down, we do the join with a little more care,
+        # reporting progress as we wait (even though we never will <wink>)
+        if self.redirect_thread is not None:
+            for i in range(5):
+                self.redirect_thread.join(1)
+                self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+                if not self.redirect_thread.isAlive():
+                    break
+            else:
+                self.warning("Redirect thread did not stop!")
         self.logmsg(servicemanager.PYS_SERVICE_STOPPED)
 
-    def run(self, handles):
+    def run(self):
         """Monitor the daemon process.
 
         Returns True if the service should continue running and
@@ -171,63 +228,35 @@
         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]]
+        rc = win32event.WaitForMultipleObjects([self.hWaitStop, self.hZope],
+                                               0, # bWaitAll
+                                               win32event.INFINITE)
+        if rc == win32event.WAIT_OBJECT_0:
+            # user sent a stop service request
+            self.SvcStop()
+            keep_running = False
+        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)
+            # exit status 0 means the user caused a clean shutdown,
+            # presumably via the web interface.  Any other status
+            # is an error that gets written to the event log.
+            if status != 0:
+                # This should never block - the child process terminating
+                # has closed the redirection pipe, so our thread dies.
+                self.redirect_thread.join(5)
+                if self.redirect_thread.isAlive():
+                    self.warning("Redirect thread did not stop!")
+                self.warning("process terminated with exit code %d.\n%s" \
+                             % (status, "".join(self.captured_blocks)))
+            keep_running = status != 0
         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
-            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)
-                # 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()
+            # No other valid return codes.
+            assert 0, rc
         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:
@@ -239,43 +268,77 @@
         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)
+        # sleep for our backoff, but still respond to stop requests.
+        if win32event.WAIT_OBJECT_0 == \
+           win32event.WaitForSingleObject(self.hWaitStop,
+                                          self.backoff_interval * 1000):
+            return False
         self.backoff_cumulative += self.backoff_interval
         self.backoff_interval *= 2
         return True
         
     def createProcessCaptureIO(self, cmd):
-        stdin = self.newPipe()
-        stdout = self.newPipe()
-        stderr = self.newPipe()
+        hInputRead, hInputWriteTemp = self.newPipe()
+        hOutReadTemp, hOutWrite = self.newPipe()
+        pid = win32api.GetCurrentProcess()
+        # This one is duplicated as inheritable.
+        hErrWrite = win32api.DuplicateHandle(pid, hOutWrite, pid, 0, 1,
+                                       win32con.DUPLICATE_SAME_ACCESS)
 
+        # These are non-inheritable duplicates.
+        hOutRead = self.dup(hOutReadTemp)
+        hInputWrite = self.dup(hInputWriteTemp)
+        # dup() closed hOutReadTemp, hInputWriteTemp
+
         si = win32process.STARTUPINFO()
-        si.hStdInput = stdin[0]
-        si.hStdOutput = stdout[1]
-        si.hStdError = stderr[1]
-        si.dwFlags = (win32process.STARTF_USESTDHANDLES
-                      | win32process.STARTF_USESHOWWINDOW)
+        si.hStdInput = hInputRead
+        si.hStdOutput = hOutWrite
+        si.hStdError = hErrWrite
+        si.dwFlags = win32process.STARTF_USESTDHANDLES | \
+                     win32process.STARTF_USESHOWWINDOW
         si.wShowWindow = win32con.SW_HIDE
 
-        c_stdin = self.dup(stdin[1])
-        c_stdout = self.dup(stdout[0])
-        c_stderr = self.dup(stderr[0])
-
         # pass True to allow handles to be inherited.  Inheritance is
         # problematic in general, but should work in the controlled
         # circumstances of a service process.
-        info = win32process.CreateProcess(None, cmd, None, None, True, 0,
-                                          None, None, si)
-        stdin[0].Close()
-        stdout[1].Close()
-        stderr[1].Close()
+        create_flags = win32process.CREATE_NEW_CONSOLE
+        info = win32process.CreateProcess(None, cmd, None, None, True, 
+                                          create_flags, None, None, si)
+        # (NOTE: these really aren't necessary for Python - they are closed
+        # as soon as they are collected)
+        hOutWrite.Close()
+        hErrWrite.Close()
+        hInputRead.Close()
+        # We don't use stdin
+        hInputWrite.Close()
 
-        return info, (c_stdin, c_stdout, c_stderr)
+        # start a thread collecting output
+        t = threading.Thread(target=self.redirectCaptureThread,
+                             args = (hOutRead,))
+        t.start()
+        self.redirect_thread = t
+        return info
 
+    def redirectCaptureThread(self, handle):
+        # Only one of these running at a time, and handling both stdout and
+        # stderr on a single handle.  The read data is never referenced until
+        # the thread dies - so no need for locks around self.captured_blocks.
+        self.captured_blocks = []
+        #self.info("Redirect thread starting")
+        while 1:
+            try:
+                ec, data = win32file.ReadFile(handle, CHILDCAPTURE_BLOCK_SIZE)
+            except pywintypes.error, err:
+                # ERROR_BROKEN_PIPE means the child process closed the
+                # handle - ie, it terminated.
+                if err[0] != winerror.ERROR_BROKEN_PIPE:
+                    self.warning("Error reading output from process: %s" % err)
+                break
+            self.captured_blocks.append(data)
+            del self.captured_blocks[CHILDCAPTURE_MAX_BLOCKS:]
+        handle.Close()
+        #self.info("Redirect capture thread terminating")
+
     def newPipe(self):
         sa = win32security.SECURITY_ATTRIBUTES()
         sa.bInheritHandle = True
@@ -291,6 +354,8 @@
         pipe.Close()
         return dup
 
+# Real __main__ bootstrap code is in the instance's service module.
 if __name__ == '__main__':
-    win32serviceutil.HandleCommandLine(Service)
-
+    print "This is a framework module - you don't run it directly."
+    print "See your $SOFTWARE_HOME\bin directory for the service script."
+    sys.exit(1)

Modified: Zope/trunk/skel/bin/runzope.bat.in
===================================================================
--- Zope/trunk/skel/bin/runzope.bat.in	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/skel/bin/runzope.bat.in	2005-04-14 02:00:15 UTC (rev 29975)
@@ -4,5 +4,5 @@
 @set SOFTWARE_HOME=<<SOFTWARE_HOME>>
 @set CONFIG_FILE=<<INSTANCE_HOME>>\etc\zope.conf
 @set PYTHONPATH=%SOFTWARE_HOME%
- at set ZOPE_RUN=%SOFTWARE_HOME%\Zope\Startup\run.py
+ at set ZOPE_RUN=%SOFTWARE_HOME%\Zope2\Startup\run.py
 "%PYTHON%" "%ZOPE_RUN%" -C "%CONFIG_FILE%" %1 %2 %3 %4 %5 %6 %7

Modified: Zope/trunk/skel/bin/zopeservice.py.in
===================================================================
--- Zope/trunk/skel/bin/zopeservice.py.in	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/skel/bin/zopeservice.py.in	2005-04-14 02:00:15 UTC (rev 29975)
@@ -38,8 +38,9 @@
 
       install : Installs the service
 
-      update : Updates the service, use this when you change
-               the service class implementation 
+      update : Updates the service.  Use this if you change any
+               configuration settings and need the service to be
+               re-registered.
 
       remove : Removes the service
 
@@ -53,13 +54,9 @@
 
       debug : Runs the service in debug mode
 
-    You can view the usage options by running ntservice.py without any
+    You can view the usage options by running this module without any
     arguments.
 
-    Note: you may have to register the Python service program first,
-
-      win32\PythonService.exe /register
-
   Starting Zope
 
     Start Zope by clicking the 'start' button in the services control
@@ -74,41 +71,62 @@
 
   Event logging
 
-    Zope events are logged to the NT application event log. Use the
-    event viewer to keep track of Zope events.
+    Service related events (such as startup, shutdown, or errors executing
+    the Zope process) are logged to the NT application event log. Use the
+    event viewer to see these events.
 
-Note: to successfully run this script, the Zope software home needs to be on
-the PYTHONPATH.
+    Zope Events are still written to the Zope event logs.
+
 """
+import sys, os
 
-import os.path
-from os.path import dirname as dn
-import sys
-
 # these are replacements from mkzopeinstance
-PYTHONW = r'<<PYTHONW>>'
+PYTHON = r'<<PYTHON>>'
 SOFTWARE_HOME=r'<<SOFTWARE_HOME>>'
 INSTANCE_HOME = r'<<INSTANCE_HOME>>'
 ZOPE_HOME = r'<<ZOPE_HOME>>'
 
-ZOPE_RUN = r'%s\Zope\Startup\run.py' % SOFTWARE_HOME
+ZOPE_RUN = r'%s\Zope2\Startup\run.py' % SOFTWARE_HOME
 CONFIG_FILE= os.path.join(INSTANCE_HOME, 'etc', 'zope.conf')
 PYTHONSERVICE_EXE=r'%s\bin\PythonService.exe' % ZOPE_HOME
 
-sys.path.insert(0, SOFTWARE_HOME)
-sys.path.insert(1, os.path.join(SOFTWARE_HOME, 'third_party', 'docutils'))
-sys.path.insert(2, os.path.join(SOFTWARE_HOME, 'third_party', 'docutils', 'extras'))
+# Setup the environment, so sub-processes see these variables
+for check_dir in (os.path.join(SOFTWARE_HOME, 'third_party', 'docutils', 'extras'),
+                  os.path.join(SOFTWARE_HOME, 'third_party', 'docutils'),
+                  SOFTWARE_HOME,
+                  ):
+    parts = os.environ.get("PYTHONPATH", "").split(os.pathsep)
+    if check_dir not in parts:
+        parts = filter(None, [check_dir] + parts)
+        os.environ["PYTHONPATH"] = os.pathsep.join(parts)
 
+os.environ["INSTANCE_HOME"] = INSTANCE_HOME
+
+# Ensure SOFTWARE_HOME is on our current sys.path so we can import the
+# nt_svcutils package.  Note we don't need the docutils dirs in sys.path, as
+# only Zope itself (our child process) uses it, and that happens via
+# PYTHONPATH
+if SOFTWARE_HOME not in sys.path:
+    sys.path.insert(0, SOFTWARE_HOME)
+
 from nt_svcutils.service import Service
 
 servicename = 'Zope_%s' % str(hash(INSTANCE_HOME.lower()))
 
 class InstanceService(Service):
-    start_cmd = '"%s" "%s" -C "%s"' % (PYTHONW, ZOPE_RUN, CONFIG_FILE)
     _svc_name_ = servicename
     _svc_display_name_ = 'Zope instance at %s' % INSTANCE_HOME
-    _exe_name_ = PYTHONSERVICE_EXE
+    # _svc_description_ can also be set (but what to say isn't clear!)
+    # If the exe we expect is not there, let the service framework search
+    # for it.  This will be true for people running from source builds and
+    # relying on pre-installed pythonservice.exe.
+    # Note this is only used at install time, not runtime.
+    if os.path.isfile(PYTHONSERVICE_EXE):
+        _exe_name_ = PYTHONSERVICE_EXE
 
+    process_runner = PYTHON
+    process_args = '"%s" -C "%s"' % (ZOPE_RUN, CONFIG_FILE)
+
 if __name__ == '__main__':
     import win32serviceutil
     win32serviceutil.HandleCommandLine(InstanceService)

Modified: Zope/trunk/utilities/mkzopeinstance.py
===================================================================
--- Zope/trunk/utilities/mkzopeinstance.py	2005-04-14 01:57:47 UTC (rev 29974)
+++ Zope/trunk/utilities/mkzopeinstance.py	2005-04-14 02:00:15 UTC (rev 29975)
@@ -93,9 +93,11 @@
         user, password = get_inituser()
 
     # we need to distinguish between python.exe and pythonw.exe under
-    # Windows in order to make Zope run using python.exe when run in a
-    # console window and pythonw.exe when run as a service, so we do a bit
-    # of sniffing here.
+    # Windows.  Zope is always run using 'python.exe' (even for services),
+    # however, it may be installed via pythonw.exe (as a sub-process of an
+    # installer).  Thus, sys.executable may not be the executable we use.
+    # We still provide both PYTHON and PYTHONW, but PYTHONW should never
+    # need be used.
     psplit = os.path.split(sys.executable)
     exedir = os.path.join(*psplit[:-1])
     pythonexe = os.path.join(exedir, 'python.exe')



More information about the Zope-Checkins mailing list