[Zope-Checkins] CVS: Packages/Signals - WinSignalHandler.py:1.1.2.1
Signals.py:1.1.60.2
Sidnei da Silva
sidnei at awkly.org
Tue Apr 12 23:08:57 EDT 2005
Update of /cvs-repository/Packages/Signals
In directory cvs.zope.org:/tmp/cvs-serv19174
Modified Files:
Tag: Zope-2_7-branch
Signals.py
Added Files:
Tag: Zope-2_7-branch
WinSignalHandler.py
Log Message:
* Implement a signal mechanism for Windows utilising Windows 'Named Events'.
See comments in WinSignalHandler.py for details.
* As Windows can not rename an open file, when the 'reopen' signal is
received try and call a 'rotate' method rather than a reopen. Rotation
itself is implemented in zLOG.LogHandler
=== Added File Packages/Signals/WinSignalHandler.py ===
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Signal handling dispatcher for Windows."""
# This code "simulates" Unix signals via Windows events. When a signal is
# registered, we simply create a global named event for that signal. The
# signal can be set by any user with the correct permission opening and
# setting the event.
#
# One event is used per signal, and the event name is based on both the
# Zope process ID and the signal number. For example, assuming a process
# ID of 123, a SIGINT handler would create an event called "Zope-123-2"
# (as signal.SIGINT==2). The logfile reopen handler uses an event named
# "Zope-123-12" (as the logfile handler uses SIGUSR2, which == 12)
# The following program will send such an event:
# import sys, win32event
# hev = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0, sys.argv[1])
# win32event.SetEvent(hev)
# A good way to get the PID is to read the var/*.pid file for the app.
# This code is only the generic signal mechanism for Windows.
# The signal handlers are still external, just like for other platforms.
# NOTE: There is one huge semantic difference between these "signals"
# and signals on Unix. On Windows, the signals are delivered asynchronously
# to a thread inside this module. This thread calls the event handler
# directly - there is no magic to switch the call back to the main thread.
# If this is a problem (not currently, but likely later), one option may be
# to add yet another asyncore handler - the thread in this module could
# then "post" the request to the main thread via this asyncore handler.
import sys, os
import signal
import threading
import asyncore
import atexit
import Lifetime
# SetConsoleCtrlHandler not in early pywin32 versions - Signals.py will
# catch the import error.
from win32api import SetConsoleCtrlHandler
import win32con
import win32event
import pywintypes
import ntsecuritycon
# PEP 282/Z3 style logging.
import zLOG
def _log(msg, severity=zLOG.INFO, error=None):
zLOG.LOG("WinSignalHandler", severity, msg, "", error)
class Logger:
def error(self, msg):
_log(msg, zLOG.ERROR)
def exception(self, msg):
_log(msg, zLOG.ERROR, error=sys.exc_info())
def warn(self, msg):
_log(msg, zLOG.PROBLEM)
warning=warn
def info(self, msg):
_log(msg, zLOG.INFO)
def debug(self, msg):
_log(msg, zLOG.DEBUG)
logger=Logger()
# We simulate signals via win32 named events. This is the event name
# prefix we use - the "signal number" is appended to this name.
event_name_prefix = "Zope-%d-" % os.getpid()
# For Windows 2000 and later, we prefix "Global\" to the name, so that
# it works correctly in a Terminal Services environment.
winver = sys.getwindowsversion()
# 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_prefix = "Global\\" + event_name_prefix
def createEventSecurityObject():
# Create a security object giving World read/write access,
# but only "Owner" modify access.
sa = pywintypes.SECURITY_ATTRIBUTES()
sidEveryone = pywintypes.SID()
sidEveryone.Initialize(ntsecuritycon.SECURITY_WORLD_SID_AUTHORITY,1)
sidEveryone.SetSubAuthority(0, ntsecuritycon.SECURITY_WORLD_RID)
sidCreator = pywintypes.SID()
sidCreator.Initialize(ntsecuritycon.SECURITY_CREATOR_SID_AUTHORITY,1)
sidCreator.SetSubAuthority(0, ntsecuritycon.SECURITY_CREATOR_OWNER_RID)
acl = pywintypes.ACL()
acl.AddAccessAllowedAce(win32event.EVENT_MODIFY_STATE, sidEveryone)
acl.AddAccessAllowedAce(ntsecuritycon.FILE_ALL_ACCESS, sidCreator)
sa.SetSecurityDescriptorDacl(1, acl, 0)
return sa
def wakeSelect():
"""Interrupt a sleeping asyncore 'select' call"""
# What is the right thing to do here?
# asyncore.close_all() works, but I fear that would
# prevent the poll based graceful cleanup code from working.
# This seems to work :)
for fd, obj in asyncore.socket_map.items():
if hasattr(obj, "pull_trigger"):
obj.pull_trigger()
class SignalHandler:
def __init__(self):
self.registry = {}
self.event_handles = {}
self.admin_event_handle = win32event.CreateEvent(None, 0, 0, None)
self.shutdown_requested = False
# Register a "console control handler" for Ctrl+C/Break notification.
SetConsoleCtrlHandler(consoleCtrlHandler)
# Start the thread that is watching for events.
thread = threading.Thread(target=self.signalCheckerThread)
# If something goes terribly wrong, don't wait for this thread!
thread.setDaemon(True)
thread.start()
self.signal_thread = thread
def shutdown(self):
# Shutdown our signal watcher thread.
logger.debug("signal handler shutdown starting.")
self.shutdown_requested = 1
win32event.SetEvent(self.admin_event_handle)
# sadly, this can deadlock at shutdown when Ctrl+C is used
# (although not then the event is used to trigger shutdown)
# at least in build 204. Further updates as they come to hand...
# Remove the Windows control handler
#SetConsoleCtrlHandler(consoleCtrlHandler, 0)
self.signal_thread.join(5) # should never block for long!
self.registry = None
self.event_handles = None
self.admin_event_handle = None
logger.debug("signal handler shutdown complete.")
def consoleCtrlHandler(self, ctrlType):
"""Called by Windows on a new thread whenever a console control
event is raised."""
logger.debug("Windows control event %d" % ctrlType)
sig = None
if ctrlType == win32con.CTRL_C_EVENT:
# user pressed Ctrl+C or someone did GenerateConsoleCtrlEvent
sig = signal.SIGINT
elif ctrlType == win32con.CTRL_BREAK_EVENT:
sig = signal.SIGTERM
elif ctrlType == win32con.CTRL_CLOSE_EVENT:
# Console is about to die.
# CTRL_CLOSE_EVENT gives us 5 seconds before displaying
# the "End process" dialog - so treat as 'fast'
sig = signal.SIGTERM
elif ctrlType in (win32con.CTRL_LOGOFF_EVENT,
win32con.CTRL_SHUTDOWN_EVENT):
# MSDN says:
# "Note that this signal is received only by services.
# Interactive applications are terminated at logoff, so
# they are not present when the system sends this signal."
# We can therefore ignore it (our service framework
# manages shutdown in this case)
pass
else:
logger.info("Unexpected windows control event %d" % ctrlType)
# Call the signal handler - we could also do it asynchronously
# by setting the relevant event, but we need it synchronous so
# that we don't wake the select loop until after the shutdown
# flags have been set.
result = 0
if sig is not None and self.registry.has_key(sig):
self.signalHandler(sig, None)
result = 1 # don't call other handlers.
return result
def signalCheckerThread(self):
while not self.shutdown_requested:
handles = [self.admin_event_handle]
signums = [None]
for signum, handle in self.event_handles.items():
signums.append(signum)
handles.append(handle)
rc = win32event.WaitForMultipleObjects(handles, False,
win32event.INFINITE)
logger.debug("signalCheckerThread awake with %s" % rc)
signum = signums[rc - win32event.WAIT_OBJECT_0]
if signum is None:
# Admin event - either shutdown, or new event object created.
pass
else:
logger.debug("signalCheckerThread calling %s" % signum)
self.signalHandler(signum, None)
logger.debug("signalCheckerThread back")
logger.debug("signalCheckerThread stopped")
def registerHandler(self, signum, handler):
"""Register a handler function that will be called when the process
recieves the signal signum. The signum argument must be a signal
constant such as SIGTERM. The handler argument must be a function
or method that takes no arguments."""
items = self.registry.get(signum)
if items is None:
items = self.registry[signum] = []
# Create an event for this signal.
event_name = event_name_prefix + str(signum)
sa = createEventSecurityObject()
hevent = win32event.CreateEvent(sa, 0, 0, event_name)
self.event_handles[signum] = hevent
# Let the worker thread know there is a new handle.
win32event.SetEvent(self.admin_event_handle)
signame = get_signal_name(signum)
logger.debug("Installed sighandler for %s (%s)" % (signame, event_name))
items.insert(0, handler)
def getRegisteredSignals(self):
"""Return a list of the signals that have handlers registered. This
is used to pass the signals through to the ZDaemon code."""
return self.registry.keys()
def signalHandler(self, signum, frame):
"""Meta signal handler that dispatches to registered handlers."""
signame = get_signal_name(signum)
logger.info("Caught signal %s" % signame)
for handler in self.registry.get(signum, []):
# Never let a bad handler prevent the standard signal
# handlers from running.
try: handler()
except SystemExit, rc:
# On Unix, signals are delivered to the main thread, so a
# SystemExit does the right thing. On Windows, we are on
# our own thread, so throwing SystemExit there isn't a great
# idea. Just shutdown the main loop.
logger.debug("Trapped SystemExit(%s) - doing Lifetime shutdown" % (rc,))
Lifetime.shutdown(rc)
except:
logger.exception("A handler for %s failed!'" % signame)
wakeSelect() # trigger a walk around the Lifetime loop.
_signals = None
def get_signal_name(n):
"""Return the symbolic name for signal n.
Returns 'signal n' if there is no SIG name bound to n in the signal
module.
"""
global _signals
if _signals is None:
_signals = {}
for k, v in signal.__dict__.items():
startswith = getattr(k, 'startswith', None)
if startswith is None:
continue
if startswith('SIG') and not startswith('SIG_'):
_signals[v] = k
# extra ones that aren't (weren't?) in Windows.
for name, val in ("SIGHUP", 1), ("SIGUSR1", 10), ("SIGUSR2", 12):
if not _signals.has_key(name):
_signals[val] = name
return _signals.get(n, 'signal %d' % n)
# The win32 ConsoleCtrlHandler
def consoleCtrlHandler(ctrlType):
return SignalHandler.consoleCtrlHandler(ctrlType)
# The SignalHandler is actually a singleton.
SignalHandler = SignalHandler()
# Need to be careful at shutdown - the 'signal watcher' thread which triggers
# the shutdown may still be running when the main thread terminates and
# Python starts cleaning up.
atexit.register(SignalHandler.shutdown)
=== Packages/Signals/Signals.py 1.1.60.1 => 1.1.60.2 ===
--- Packages/Signals/Signals.py:1.1.60.1 Wed Nov 12 15:42:00 2003
+++ Packages/Signals/Signals.py Tue Apr 12 23:08:56 2005
@@ -17,8 +17,28 @@
"""
__version__='$Revision$'[11:-2]
-from SignalHandler import SignalHandler
+# Interesting signals and associated actions...
+# SIGHUP (1) - restart
+# SIGINT (2) - shutdown clean
+# SIGUSR1 (10) - pack (reserved, not used)
+# SIGUSR2 (12) - log reopen
+# SIGTERM (15) - shutdown fast
+
+import os
import zLOG
+
+if os.name == 'nt':
+ try:
+ from WinSignalHandler import SignalHandler
+ except ImportError:
+ msg = ('Can not install signal handlers. Please install '
+ '(or upgrade) your pywin32 installation '
+ '(https://sf.net/projects/pywin32)')
+ zLOG.LOG('Z2', zLOG.PROBLEM, msg)
+ SignalHandler = None
+else:
+ from SignalHandler import SignalHandler
+
import sys
import Lifetime
@@ -51,6 +71,22 @@
logger.reopen()
zLOG.LOG('Z2', zLOG.INFO, "Log files reopened successfully")
+# On Windows, a 'reopen' is useless - the file can not be renamed
+# while open, so we perform a trivial 'rotate'.
+def logfileRotateHandler():
+ """Rotate log files on SIGUSR2. Only called on Windows. This is
+ registered first if we are on Windows, so it should be called
+ after all other SIGUSR2 handlers."""
+ zLOG.LOG('Z2', zLOG.DEBUG, "Log files rotation starting...")
+ from zLOG.EventLogger import event_logger
+ from ZServer.AccessLogger import access_logger
+ from ZServer.DebugLogger import debug_logger
+ for logger in (event_logger, access_logger, debug_logger):
+ for handler in logger.logger.handlers:
+ if hasattr(handler, 'rotate') and callable(handler.rotate):
+ handler.rotate()
+ zLOG.LOG('Z2', zLOG.INFO, "Log files rotation complete")
+
def packHandler():
""" Packs the main database. Not safe to call under a signal
handler, because it blocks the main thread """
@@ -64,16 +100,35 @@
except:
zLOG.LOG('Z2', zLOG.INFO,
'Call to pack failed!', error=sys.exc_info())
-
def registerZopeSignals():
- import signal
- SignalHandler.registerHandler(signal.SIGTERM, shutdownFastHandler)
- SignalHandler.registerHandler(signal.SIGINT, shutdownHandler)
- SignalHandler.registerHandler(signal.SIGHUP, restartHandler)
- SignalHandler.registerHandler(signal.SIGUSR2, logfileReopenHandler)
+ from signal import SIGTERM, SIGINT
+ try:
+ from signal import SIGHUP, SIGUSR1, SIGUSR2
+ except ImportError:
+ # Windows doesn't have these (but also doesn't care what the exact
+ # numbers are)
+ SIGHUP = 1
+ SIGUSR1 = 10
+ SIGUSR2 = 12
+
+ if not SignalHandler:
+ return
+ SignalHandler.registerHandler(SIGTERM, shutdownFastHandler)
+ SignalHandler.registerHandler(SIGINT, shutdownHandler)
+ if os.name != 'nt':
+ SignalHandler.registerHandler(SIGUSR2, logfileReopenHandler)
+ SignalHandler.registerHandler(SIGHUP, restartHandler)
+ else:
+ # See comment above - Windows 'rotates' rather than 'reopens'
+ SignalHandler.registerHandler(SIGUSR2, logfileRotateHandler)
+ # Restart not currently implemented on Windows (the service itself
+ # generally is restarted when necessary)
# SIGUSR1 is nominally reserved for pack, but we dont have an
# implementation that is stable yet because if the signal handler
# fires it will be caught in the main thread and all network operations
# will cease until it's finished.
- #SignalHandler.registerHandler(signal.SIGUSR1, packHandler)
+ # (The above is *not* True for Windows - a different thread is used to
+ # catch the signals. This probably could be switched on for Windows
+ # if anyone cares)
+ #SignalHandler.registerHandler(SIGUSR1, packHandler)
More information about the Zope-Checkins
mailing list