Commit fd335bd0 authored by Sidnei da Silva's avatar Sidnei da Silva

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
parent baff3bac
......@@ -3,6 +3,9 @@ Building and installing Zope from source
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
......
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"
......@@ -31,14 +31,14 @@ CD=cd
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:
......@@ -47,7 +47,7 @@ build:
# 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 @@ install: build
# 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
......@@ -23,7 +23,7 @@ QUIET=0
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'
......
......@@ -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 @@ BACKOFF_CLEAR_TIME = 30
# 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 @@ class Service(win32serviceutil.ServiceFramework):
"""
# 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"'
)
# 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
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'
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 @@ class Service(win32serviceutil.ServiceFramework):
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 @@ class Service(win32serviceutil.ServiceFramework):
# 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 @@ class Service(win32serviceutil.ServiceFramework):
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,42 +268,76 @@ class Service(win32serviceutil.ServiceFramework):
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()
return info, (c_stdin, c_stdout, c_stderr)
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()
# 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()
......@@ -291,6 +354,8 @@ class Service(win32serviceutil.ServiceFramework):
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)
......@@ -4,5 +4,5 @@
@set SOFTWARE_HOME=<<SOFTWARE_HOME>>
@set CONFIG_FILE=<<INSTANCE_HOME>>\etc\zope.conf
@set PYTHONPATH=%SOFTWARE_HOME%
@set ZOPE_RUN=%SOFTWARE_HOME%\Zope\Startup\run.py
@set ZOPE_RUN=%SOFTWARE_HOME%\Zope2\Startup\run.py
"%PYTHON%" "%ZOPE_RUN%" -C "%CONFIG_FILE%" %1 %2 %3 %4 %5 %6 %7
......@@ -38,8 +38,9 @@ Usage:
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 @@ Usage:
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,40 +71,61 @@ Usage:
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 os.path
from os.path import dirname as dn
import sys
"""
import sys, os
# 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
......
......@@ -93,9 +93,11 @@ def main():
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')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment