Commit f1f9d02d authored by Chris McDonough's avatar Chris McDonough

These patches provide clean signal handling and logfile rotation to Zope.

All Zope process will respond to signals in the specified manner:

  SIGHUP -  close open database connections and sockets, then restart the
            process

  SIGTERM - close open database connections and sockets, then shut down.

  SIGINT  - same as SIGTERM

  SIGUSR2 - rotate all Zope log files (z2.log, event log, detailed log)

The common idiom for doing automated logfile rotation will become:

kill -USR2 `cat /path/to/var/z2.pid`

The common idiom for doing "prophylactic" restarts will become:

kill -HUP `cat /path/to/var/z2.pid`

When a process is interrupted via ctrl-C or via a TERM signal (INT, TERM),
all open database connections and sockets will be closed before
the process dies.  This will speed up restart time for sites that
use a FileStorage as its index will be written to the filesystem before
shutdown.

Unspecified signals kill the process without doing cleanup.
parent b087d5f4
......@@ -39,12 +39,18 @@ class DebugLogger:
"""
def __init__(self, filename):
self.filename = filename
self.file=open(filename, 'a+b')
l=thread.allocate_lock()
self._acquire=l.acquire
self._release=l.release
self.log('U', '000000000', 'System startup')
def reopen(self):
self.file.close()
self.file=open(self.filename, 'a+b')
self.log('U', '000000000', 'Logfile reopened')
def log(self, code, request_id, data=''):
self._acquire()
try:
......
......@@ -15,5 +15,24 @@
"""
from ZServer.medusa.thread.select_trigger import trigger
from ZServer.medusa.asyncore import socket_map
class simple_trigger(trigger):
def handle_close(self):
pass
the_trigger=simple_trigger()
def Wakeup(thunk=None):
try:
the_trigger.pull_trigger(thunk)
except OSError, why:
# this broken pipe as a result of perhaps a signal
# we want to handle this gracefully so we get rid of the old
# trigger and install a new one.
if why[0] == 32:
del socket_map[the_trigger._fileno]
global the_trigger
the_trigger = simple_trigger() # adds itself back into socket_map
the_trigger.pull_trigger(thunk)
Wakeup=trigger().pull_trigger
......@@ -353,6 +353,10 @@ class dispatcher:
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
self.handle_close()
return ''
if why[0] == EAGAIN:
# Happens as a result of a nonfatal signal when select is
# interrupted
return ''
else:
raise socket.error, why
......@@ -524,12 +528,15 @@ if os.name == 'posix':
# NOTE: this is a difference from the Python 2.2 library
# version of asyncore.py. This prevents a hanging condition
# on Linux 2.2 based systems.
while 1:
i = 0
while i < 5: # this is a guess
try:
return apply (os.read, (self.fd,)+args)
except exceptions.OSError, why:
if why[0] != EAGAIN:
raise
else:
i = i + 1
def send (self, *args):
return apply (os.write, (self.fd,)+args)
......
......@@ -6,7 +6,7 @@
# All Rights Reserved.
#
RCS_ID = '$Id: http_server.py,v 1.31 2002/03/21 15:48:53 htrd Exp $'
RCS_ID = '$Id: http_server.py,v 1.32 2002/06/11 22:02:47 chrism Exp $'
# python modules
import os
......@@ -627,22 +627,28 @@ class http_server (asyncore.dispatcher):
def handle_accept (self):
self.total_clients.increment()
try:
conn, addr = self.accept()
tup = self.accept()
except socket.error:
# linux: on rare occasions we get a bogus socket back from
# accept. socketmodule.c:makesockaddr complains that the
# address family is unknown. We don't want the whole server
# to shut down because of this.
self.log_info ('warning: server accept() threw an exception', 'warning')
self.log_info ('warning: server accept() threw an exception',
'warning')
self.total_clients.decrement()
return
try:
conn, addr = tup
except TypeError:
# unpack non-sequence. this can happen when a read event
# fires on a listening socket, but when we call accept()
# we get EWOULDBLOCK, so dispatcher.accept() returns None.
# Seen on FreeBSD3.
self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
# unpack non-sequence. this can happen when a read event
# fires on a listening socket, but when we call accept()
# we get EWOULDBLOCK, so dispatcher.accept() returns None.
# Seen on FreeBSD3 and Linux.
#self.log_info ('warning: server accept() returned %s '
# '(EWOULDBLOCK?)' % tup, 'warning')
self.total_clients.decrement()
return
self.channel_class (self, conn, addr)
def install_handler (self, handler, back=0):
......
......@@ -35,16 +35,25 @@ class file_logger:
# pass this either a path or a file object.
def __init__ (self, file, flush=1, mode='a'):
self.filename = None
if type(file) == type(''):
if (file == '-'):
import sys
self.file = sys.stdout
else:
self.filename = file
self.file = open (file, mode)
else:
self.file = file
self.do_flush = flush
def reopen(self):
if self.filename:
self.file.close()
self.file = open(self.filename,'a')
def __repr__ (self):
return '<file logger: %s>' % self.file
......
......@@ -39,12 +39,18 @@ class DebugLogger:
"""
def __init__(self, filename):
self.filename = filename
self.file=open(filename, 'a+b')
l=thread.allocate_lock()
self._acquire=l.acquire
self._release=l.release
self.log('U', '000000000', 'System startup')
def reopen(self):
self.file.close()
self.file=open(self.filename, 'a+b')
self.log('U', '000000000', 'Logfile reopened')
def log(self, code, request_id, data=''):
self._acquire()
try:
......
......@@ -15,5 +15,24 @@
"""
from ZServer.medusa.thread.select_trigger import trigger
from ZServer.medusa.asyncore import socket_map
class simple_trigger(trigger):
def handle_close(self):
pass
the_trigger=simple_trigger()
def Wakeup(thunk=None):
try:
the_trigger.pull_trigger(thunk)
except OSError, why:
# this broken pipe as a result of perhaps a signal
# we want to handle this gracefully so we get rid of the old
# trigger and install a new one.
if why[0] == 32:
del socket_map[the_trigger._fileno]
global the_trigger
the_trigger = simple_trigger() # adds itself back into socket_map
the_trigger.pull_trigger(thunk)
Wakeup=trigger().pull_trigger
......@@ -353,6 +353,10 @@ class dispatcher:
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
self.handle_close()
return ''
if why[0] == EAGAIN:
# Happens as a result of a nonfatal signal when select is
# interrupted
return ''
else:
raise socket.error, why
......@@ -524,12 +528,15 @@ if os.name == 'posix':
# NOTE: this is a difference from the Python 2.2 library
# version of asyncore.py. This prevents a hanging condition
# on Linux 2.2 based systems.
while 1:
i = 0
while i < 5: # this is a guess
try:
return apply (os.read, (self.fd,)+args)
except exceptions.OSError, why:
if why[0] != EAGAIN:
raise
else:
i = i + 1
def send (self, *args):
return apply (os.write, (self.fd,)+args)
......
......@@ -6,7 +6,7 @@
# All Rights Reserved.
#
RCS_ID = '$Id: http_server.py,v 1.31 2002/03/21 15:48:53 htrd Exp $'
RCS_ID = '$Id: http_server.py,v 1.32 2002/06/11 22:02:47 chrism Exp $'
# python modules
import os
......@@ -627,22 +627,28 @@ class http_server (asyncore.dispatcher):
def handle_accept (self):
self.total_clients.increment()
try:
conn, addr = self.accept()
tup = self.accept()
except socket.error:
# linux: on rare occasions we get a bogus socket back from
# accept. socketmodule.c:makesockaddr complains that the
# address family is unknown. We don't want the whole server
# to shut down because of this.
self.log_info ('warning: server accept() threw an exception', 'warning')
self.log_info ('warning: server accept() threw an exception',
'warning')
self.total_clients.decrement()
return
try:
conn, addr = tup
except TypeError:
# unpack non-sequence. this can happen when a read event
# fires on a listening socket, but when we call accept()
# we get EWOULDBLOCK, so dispatcher.accept() returns None.
# Seen on FreeBSD3.
self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
# unpack non-sequence. this can happen when a read event
# fires on a listening socket, but when we call accept()
# we get EWOULDBLOCK, so dispatcher.accept() returns None.
# Seen on FreeBSD3 and Linux.
#self.log_info ('warning: server accept() returned %s '
# '(EWOULDBLOCK?)' % tup, 'warning')
self.total_clients.decrement()
return
self.channel_class (self, conn, addr)
def install_handler (self, handler, back=0):
......
......@@ -35,16 +35,25 @@ class file_logger:
# pass this either a path or a file object.
def __init__ (self, file, flush=1, mode='a'):
self.filename = None
if type(file) == type(''):
if (file == '-'):
import sys
self.file = sys.stdout
else:
self.filename = file
self.file = open (file, mode)
else:
self.file = file
self.do_flush = flush
def reopen(self):
if self.filename:
self.file.close()
self.file = open(self.filename,'a')
def __repr__ (self):
return '<file logger: %s>' % self.file
......
......@@ -253,6 +253,75 @@ program=sys.argv[0]
here=os.path.join(os.getcwd(), os.path.split(program)[0])
Zpid=''
# Install signal handlers.
# SIGTERM - cleanly shuts down.
# SIGHUP - cleanly restarts.
# SIGUSR2 - open and close all log files (for log rotation)
if os.name == 'posix': # signal.signal() not reliable on Windows
import signal
def closeall():
zLOG.LOG('Z2', zLOG.INFO, "Closing all open network connections")
for socket in asyncore.socket_map.values():
try:
socket.close()
except:
pass
zLOG.LOG('Z2', zLOG.INFO, "Closing all open ZODB databases")
import Globals
for db in Globals.opened:
try:
db.close()
finally:
pass
def sighandler(signum, frame):
signame = zdaemon.Daemon.get_signal_name(signum)
zLOG.LOG('Z2', zLOG.INFO , "Caught signal %s" % signame)
if signum in [signal.SIGTERM, signal.SIGINT]:
closeall()
zLOG.LOG('Z2', zLOG.INFO , "Shutting down")
sys.exit(0)
if signum == signal.SIGHUP:
closeall()
zLOG.LOG('Z2', zLOG.INFO , "Restarting")
sys.exit(1)
if signum == signal.SIGUSR2:
zLOG.LOG('Z2', zLOG.INFO , "Reopening log files")
if hasattr(sys, '__lg') and hasattr(sys.__lg, 'reopen'):
sys.__lg.reopen()
zLOG.LOG('Z2', zLOG.BLATHER, "Reopened Z2.log")
if (hasattr(sys, '__detailedlog') and
hasattr(sys.__detailedlog, 'reopen')):
zLOG.LOG('Z2', zLOG.BLATHER,"Reopened detailed request log")
sys.__detailedlog.reopen()
if hasattr(zLOG, '_set_stupid_dest'):
zLOG._set_stupid_dest(None)
else:
zLOG._stupid_dest = None
ZLogger.stupidFileLogger._stupid_dest = None
zLOG.LOG('Z2', zLOG.BLATHER, "Reopened event log")
zLOG.LOG('Z2', zLOG.INFO, "Log files reopened successfully")
INTERESTING_SIGNALS = {
signal.SIGTERM: sighandler,
signal.SIGHUP: sighandler,
signal.SIGUSR2: sighandler,
signal.SIGINT: sighandler,
}
def installsighandlers():
# normal kill, restart, and open&close logs respectively
for signum, sighandler in INTERESTING_SIGNALS.items():
signal.signal(signum, sighandler)
signame = zdaemon.Daemon.get_signal_name(signum)
zLOG.LOG('Z2',zLOG.BLATHER,"Installed sighandler for %s" % signame)
########################################################################
# Configuration section
......@@ -510,7 +579,8 @@ if Zpid and not READ_ONLY:
import zdaemon, App.FindHomes, posix
sys.ZMANAGED=1
zdaemon.run(sys.argv, os.path.join(CLIENT_HOME, Zpid))
zdaemon.run(sys.argv, os.path.join(CLIENT_HOME, Zpid),
INTERESTING_SIGNALS.keys())
os.chdir(CLIENT_HOME)
......@@ -535,7 +605,12 @@ try:
if DETAILED_LOG_FILE:
from ZServer import DebugLogger
logfile=os.path.join(CLIENT_HOME, DETAILED_LOG_FILE)
DebugLogger.log=DebugLogger.DebugLogger(logfile).log
zLOG.LOG('z2', zLOG.BLATHER,
'Using detailed request log file %s' % logfile)
DL=DebugLogger.DebugLogger(logfile)
DebugLogger.log=DL.log
DebugLogger.reopen=DL.reopen
sys.__detailedlog=DL
# Import Zope (or Main)
exec "import "+MODULE in {}
......@@ -579,16 +654,21 @@ try:
if READ_ONLY:
lg = logger.file_logger('-') # log to stdout
zLOG.LOG('z2', zLOG.BLATHER, 'Logging access log to stdout')
elif os.environ.has_key('ZSYSLOG_ACCESS'):
if os.environ.has_key("ZSYSLOG_ACCESS_FACILITY"):
lg = logger.syslog_logger(os.environ['ZSYSLOG_ACCESS'],facility=os.environ['ZSYSLOG_ACCESS_FACILITY'])
else:
lg = logger.syslog_logger(os.environ['ZSYSLOG_ACCESS'])
zLOG.LOG('z2', zLOG.BLATHER, 'Using local syslog access log')
elif os.environ.has_key('ZSYSLOG_ACCESS_SERVER'):
(addr, port) = os.environ['ZSYSLOG_ACCESS_SERVER'].split( ':')
lg = logger.syslog_logger((addr, int(port)))
zLOG.LOG('z2', zLOG.BLATHER, 'Using remote syslog access log')
else:
lg = logger.file_logger(LOG_PATH)
zLOG.LOG('z2', zLOG.BLATHER, 'Using access log file %s' % LOG_PATH)
sys.__lg = lg
# HTTP Server
if HTTP_PORT:
......@@ -786,7 +866,7 @@ try:
except:
raise
# Check umask sanity.
# Check umask sanity and install signal handlers if we're on posix.
if os.name == 'posix':
# umask is silly, blame POSIX. We have to set it to get its value.
current_umask = os.umask(0)
......@@ -796,6 +876,8 @@ try:
zLOG.LOG("z2", zLOG.INFO, 'Your umask of ' + current_umask + \
' may be too permissive; for the security of your ' + \
'Zope data, it is recommended you use 077')
# we've deferred til now to actuall install signal handlers
installsighandlers()
except:
# Log startup exception and tell zdaemon not to restart us.
......
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