[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