Commit 117c9d29 authored by Tim Peters's avatar Tim Peters

Merge rev 30120 from 3.4 branch.

Port ZEO changes for Windows from ZODB 3.2.

In addition, repaired the useless default logging in runzeo.py.

Here's Sidnei's Zope 2.7 checkin comment:

* Borrow Zope's 'Signal' mechanism for Windows, if available, to
  implement clean shutdown and log rotation handlers for Windows.

* Back to creating a .PID for ZEO, so external programs that wish to set
  the 'signal' can get the PID and therefore derive the signal name.
  Currently only necessary on Windows but created on all platforms which
  implement os.getpid(), as long as the 'pid-filename' option is set,
  or the 'INSTANCE_HOME' environment variable can be found.
parent 90b4830f
...@@ -7,6 +7,33 @@ What's new in ZODB3 3.4a4? ...@@ -7,6 +7,33 @@ What's new in ZODB3 3.4a4?
========================== ==========================
Release date: DD-MMM-YYYY Release date: DD-MMM-YYYY
ZEO
---
The default logging setup in ``runzeo.py`` was broken. It was changed
so that running ``runzeo.py`` from a command line now, and without using
a config file, prints output to the console much as ZODB 3.2 did.
ZEO on Windows
--------------
Thanks to Mark Hammond for these ``runzeo.py`` enhancements on Windows:
- A pid file (containing the process id as a decimal string) is created now
for a ZEO server started via ``runzeo.py``. External programs can
read the pid from this file and derive a "signal name" used in a new
signal-emulation scheme for Windows. This is only necessary on Windows,
but the pid file is created on all platforms that implement
``os.getpid()``, as long as the ``pid-filename`` option is set, or
environment variable ``INSTANCE_HOME`` is defined. The ``pid-filename``
option can be set in a ZEO config file, or passed as the new ``--pid-file``
argument to ``runzeo.py``.
- If available, ``runzeo.py`` now uses Zope's new 'Signal' mechanism for
Windows, to implement clean shutdown and log rotation handlers for Windows.
Note that the Python in use on the ZEO server must also have the Python
Win32 extensions installed for this to be useful.
DemoStorage DemoStorage
----------- -----------
......
...@@ -93,6 +93,15 @@ ...@@ -93,6 +93,15 @@
</description> </description>
</key> </key>
<key name="pid-filename" datatype="existing-dirpath"
required="no">
<description>
The full path to the file in which to write the ZEO server's Process ID
at startup. If omitted, $INSTANCE/var/ZEO.pid is used.
</description>
<metadefault>$INSTANCE/var/ZEO.pid (or $clienthome/ZEO.pid)</metadefault>
</key>
</sectiontype> </sectiontype>
</component> </component>
...@@ -47,6 +47,7 @@ zeo_conf_template = """\ ...@@ -47,6 +47,7 @@ zeo_conf_template = """\
address %(port)d address %(port)d
read-only false read-only false
invalidation-queue-size 100 invalidation-queue-size 100
# pid-filename $INSTANCE/var/ZEO.pid
# monitor-address PORT # monitor-address PORT
# transaction-timeout SECONDS # transaction-timeout SECONDS
</zeo> </zeo>
......
...@@ -24,6 +24,9 @@ Options: ...@@ -24,6 +24,9 @@ Options:
-t/--timeout TIMEOUT -- transaction timeout in seconds (default no timeout) -t/--timeout TIMEOUT -- transaction timeout in seconds (default no timeout)
-h/--help -- print this usage message and exit -h/--help -- print this usage message and exit
-m/--monitor ADDRESS -- address of monitor server ([HOST:]PORT or PATH) -m/--monitor ADDRESS -- address of monitor server ([HOST:]PORT or PATH)
--pid-file PATH -- relative path to output file containing this process's pid;
default $(INSTANCE_HOME)/var/ZEO.pid but only if envar
INSTANCE_HOME is defined
Unless -C is specified, -a and -f are required. Unless -C is specified, -a and -f are required.
""" """
...@@ -50,12 +53,15 @@ def log(msg, level=logging.INFO, exc_info=False): ...@@ -50,12 +53,15 @@ def log(msg, level=logging.INFO, exc_info=False):
message = "(%s) %s" % (_pid, msg) message = "(%s) %s" % (_pid, msg)
logger.log(level, message, exc_info=exc_info) logger.log(level, message, exc_info=exc_info)
def parse_address(arg): def parse_address(arg):
# Caution: Not part of the official ZConfig API. # Caution: Not part of the official ZConfig API.
obj = ZConfig.datatypes.SocketAddress(arg) obj = ZConfig.datatypes.SocketAddress(arg)
return obj.family, obj.address return obj.family, obj.address
def windows_shutdown_handler():
# Called by the signal mechanism on Windows to perform shutdown.
import asyncore
asyncore.close_all()
class ZEOOptionsMixin: class ZEOOptionsMixin:
...@@ -104,6 +110,8 @@ class ZEOOptionsMixin: ...@@ -104,6 +110,8 @@ class ZEOOptionsMixin:
None, 'auth-database=') None, 'auth-database=')
self.add('auth_realm', 'zeo.authentication_realm', self.add('auth_realm', 'zeo.authentication_realm',
None, 'auth-realm=') None, 'auth-realm=')
self.add('pid_file', 'zeo.pid_filename',
None, 'pid-file=')
class ZEOOptions(ZDOptions, ZEOOptionsMixin): class ZEOOptions(ZDOptions, ZEOOptionsMixin):
...@@ -126,6 +134,7 @@ class ZEOServer: ...@@ -126,6 +134,7 @@ class ZEOServer:
self.setup_default_logging() self.setup_default_logging()
self.check_socket() self.check_socket()
self.clear_socket() self.clear_socket()
self.make_pidfile()
try: try:
self.open_storages() self.open_storages()
self.setup_signals() self.setup_signals()
...@@ -134,15 +143,20 @@ class ZEOServer: ...@@ -134,15 +143,20 @@ class ZEOServer:
finally: finally:
self.close_storages() self.close_storages()
self.clear_socket() self.clear_socket()
self.remove_pidfile()
def setup_default_logging(self): def setup_default_logging(self):
if self.options.config_logger is not None: if self.options.config_logger is not None:
return return
# No log file is configured; default to stderr. # No log file is configured; default to stderr.
logger = logging.getLogger() root = logging.getLogger()
root.setLevel(logging.INFO)
fmt = logging.Formatter(
"------\n%(asctime)s %(levelname)s %(name)s %(message)s",
"%Y-%m-%dT%H:%M:%S")
handler = logging.StreamHandler() handler = logging.StreamHandler()
handler.setLevel(logging.INFO) handler.setFormatter(fmt)
logger.addHandler(handler) root.addHandler(handler)
def check_socket(self): def check_socket(self):
if self.can_connect(self.options.family, self.options.address): if self.can_connect(self.options.family, self.options.address):
...@@ -182,6 +196,8 @@ class ZEOServer: ...@@ -182,6 +196,8 @@ class ZEOServer:
method is called without additional arguments. method is called without additional arguments.
""" """
if os.name != "posix": if os.name != "posix":
if os.name == "nt":
self.setup_win32_signals()
return return
if hasattr(signal, 'SIGXFSZ'): if hasattr(signal, 'SIGXFSZ'):
signal.signal(signal.SIGXFSZ, signal.SIG_IGN) # Special case signal.signal(signal.SIGXFSZ, signal.SIG_IGN) # Special case
...@@ -193,6 +209,27 @@ class ZEOServer: ...@@ -193,6 +209,27 @@ class ZEOServer:
method() method()
signal.signal(sig, wrapper) signal.signal(sig, wrapper)
def setup_win32_signals(self):
# Borrow the Zope Signals package win32 support, if available.
# Signals does a check/log for the availability of pywin32.
try:
import Signals.Signals
except ImportError:
logger.debug("Signals package not found. "
"Windows-specific signal handler "
"will *not* be installed.")
return
SignalHandler = Signals.Signals.SignalHandler
if SignalHandler is not None: # may be None if no pywin32.
SignalHandler.registerHandler(signal.SIGTERM,
windows_shutdown_handler)
SignalHandler.registerHandler(signal.SIGINT,
windows_shutdown_handler)
# Can use the log rotate handler too.
from Signals.Signals import logfileRotateHandler
SIGUSR2 = 12 # not in signal module on Windows.
SignalHandler.registerHandler(SIGUSR2, logfileRotateHandler)
def create_server(self): def create_server(self):
from ZEO.StorageServer import StorageServer from ZEO.StorageServer import StorageServer
self.server = StorageServer( self.server = StorageServer(
...@@ -237,6 +274,52 @@ class ZEOServer: ...@@ -237,6 +274,52 @@ class ZEOServer:
log("failed to close storage %r" % name, log("failed to close storage %r" % name,
level=logging.EXCEPTION, exc_info=True) level=logging.EXCEPTION, exc_info=True)
def _get_pidfile(self):
pidfile = self.options.pid_file
# 'pidfile' is marked as not required.
if not pidfile:
# Try to find a reasonable location if the pidfile is not
# set. If we are running in a Zope environment, we can
# safely assume INSTANCE_HOME.
instance_home = os.environ.get("INSTANCE_HOME")
if not instance_home:
# If all our attempts failed, just log a message and
# proceed.
logger.debug("'pidfile' option not set, and 'INSTANCE_HOME' "
"environment variable could not be found. "
"Cannot guess pidfile location.")
return
self.options.pid_file = os.path.join(instance_home,
"var", "ZEO.pid")
def make_pidfile(self):
if not self.options.read_only:
self._get_pidfile()
pidfile = self.options.pid_file
if pidfile is None:
return
pid = os.getpid()
try:
if os.path.exists(pidfile):
os.unlink(pidfile)
f = open(pidfile, 'w')
print >> f, pid
f.close()
log("created PID file '%s'" % pidfile)
except IOError:
logger.error("PID file '%s' cannot be opened" % pidfile)
def remove_pidfile(self):
if not self.options.read_only:
pidfile = self.options.pid_file
if pidfile is None:
return
try:
if os.path.exists(pidfile):
os.unlink(pidfile)
log("removed PID file '%s'" % pidfile)
except IOError:
logger.error("PID file '%s' could not be removed" % pidfile)
# Signal names # Signal names
......
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