from AppServer import AppServer
import os
from threading import Thread
import time
import sys
import select
''' The purpose of this module is to notice changes to source files, including
servlets, PSPs, templates or changes to the Webware source file themselves,
and reload the server as necessary to pick up the changes.
The server will also be restarted if a file which Webware _tried_ to import
is modified. This is so that changes to a file containing a syntax error
(which would have prevented it from being imported) will also cause the server
to restart.
'''
# Attempt to use python-fam (fam = File Alteration Monitor) instead of polling
# to see if files have changed. If python-fam is not installed, we fall back
# to polling.
try:
import _fam
haveFam = 1
except:
haveFam = 0
from ImportSpy import modloader
standardLibraryPrefix = '%s/lib/python%i.%i' % \
(sys.prefix, sys.version_info[0], sys.version_info[1])
DefaultConfig = {
'AutoReload': 0,
'AutoReloadPollInterval': 1, # in seconds
}
class AutoReloadingAppServer(AppServer):
## AppServer methods which this class overrides
def __init__(self,path=None):
AppServer.__init__(self,path)
self._autoReload = 0
self._shouldRestart = 0
self._requests = []
self._pipe = None
if self.isPersistent():
if self.setting('AutoReload'):
self.activateAutoReload()
def defaultConfig(self):
conf = AppServer.defaultConfig(self)
conf.update(DefaultConfig)
return conf
def shutDown(self):
print 'Stopping AutoReload Monitor'
sys.stdout.flush()
self._shuttingDown = 1
self.deactivateAutoReload()
AppServer.shutDown(self)
## Activatation of AutoReload
def activateAutoReload(self):
"""Start the monitor thread"""
modloader.activate()
if not self._autoReload:
if haveFam:
print 'AutoReload Monitor started, using FAM'
self._fileMonitorThread = t = Thread(target=self.fileMonitorThreadLoopFAM)
else:
print 'AutoReload Monitor started, polling every %d seconds' % self.setting('AutoReloadPollInterval')
self._fileMonitorThread = t = Thread(target=self.fileMonitorThreadLoop)
self._autoReload = 1
t.setName('AutoReloadMonitor')
t.start()
def deactivateAutoReload(self):
"""Tell the monitor thread to stop."""
self._autoReload = 0
if haveFam:
# send a message down the pipe to wake up the monitor
# thread and tell him to quit
self._pipe[1].write('close')
self._pipe[1].flush()
try:
self._fileMonitorThread.join()
except:
pass
## Restart methods
def restartIfNecessary(self):
"""
This should be called regularly to see if a restart is
required.
Tavis Rudd claims: "this method can only be called by
the main thread. If a worker thread calls it, the
process will freeze up."
I've implemented it so that the ThreadedAppServer's
control thread calls this. That thread is _not_ the
MainThread (the initial thread created by the Python
interpreter), but I've never encountered any problems.
Most likely Tavis meant a freeze would occur if a
_worker_ called this.
"""
if self._shouldRestart:
self.restart()
def restart(self):
"""Do the actual restart."""
self.initiateShutdown()
self._closeThread.join()
sys.stdout.flush()
sys.stderr.flush()
# calling execve() is problematic, since the file descriptors don't
# get closed by the OS. This can result in leaked database connections.
# Instead, we exit with a special return code which is recognized by
# the AppServer script, which will restart us upon receiving that code.
sys.exit(3)
## Callbacks
def monitorNewModule(self, filepath, mtime):
"""
This is a callback which ImportSpy invokes to notify
us of new files to monitor. This is only used when we
are using FAM.
"""
self._requests.append(self._fc.monitorFile(filepath, filepath) )
## Internal methods
def shouldRestart(self):
"""Tell the main thread to restart the server."""
self._shouldRestart = 1
def fileMonitorThreadLoop(self, getmtime=os.path.getmtime):
pollInterval = self.setting('AutoReloadPollInterval')
while self._autoReload:
time.sleep(pollInterval)
for f, mtime in modloader.fileList().items():
try:
if mtime < getmtime(f):
print '*** The file', f, 'has changed. The server is restarting now.'
self._autoReload = 0
return self.shouldRestart()
except OSError:
print '*** The file', f, 'is no longer accessible. The server is restarting now.'
self._autoReload = 0
return self.shouldRestart()
print 'Autoreload Monitor stopped'
sys.stdout.flush()
def fileMonitorThreadLoopFAM(self, getmtime=os.path.getmtime):
modloader.notifyOfNewFiles(self.monitorNewModule)
self._fc = fc = _fam.open()
# for all of the modules which have _already_ been loaded, we check
# to see if they've already been modified or deleted
for f, mtime in modloader.fileList().items():
if mtime < getmtime(f):
try:
if mtime < getmtime(f):
print '*** The file', f, 'has changed. The server is restarting now.'
self._autoReload = 0
return self.shouldRestart()
except OSError:
print '*** The file', f, 'is no longer accessible The server is restarting now.'
self._autoReload = 0
return self.shouldRestart()
# request that this file be monitored for changes
self._requests.append( fc.monitorFile(f, f) )
# create a pipe so that this thread can be notified when the
# server is shutdown. We use a pipe because it needs to be an object
# which will wake up the call to 'select'
r,w = os.pipe()
r = os.fdopen(r,'r')
w = os.fdopen(w,'w')
self._pipe = pipe = (r,w)
while self._autoReload:
try:
# we block here until a file has been changed, or until
# we receive word that we should shutdown (via the pipe)
ri, ro, re = select.select([fc,pipe[0]], [], [])
except select.error, er:
errnumber, strerr = er
if errnumber == errno.EINTR:
continue
else:
print strerr
sys.exit(1)
while fc.pending():
fe = fc.nextEvent()
if fe.code2str() in ['changed','deleted','created']:
print '*** The file %s has changed. The server is restarting now.' % fe.userData
self._autoReload = 0
return self.shouldRestart()
for req in self._requests:
req.cancelMonitor()
fc.close()
print 'Autoreload Monitor stopped'
sys.stdout.flush()