fixed cron deadlock on windows, thanks Helko Besemann
This commit is contained in:
@@ -1 +1 @@
|
||||
Version 2.6.0-development+timestamp.2013.09.03.09.52.37
|
||||
Version 2.6.0-development+timestamp.2013.09.03.11.52.13
|
||||
|
||||
@@ -48,12 +48,12 @@ _DEBUG = True
|
||||
_HISTORY_KIND = '_Shell_History'
|
||||
|
||||
# Types that can't be pickled.
|
||||
UNPICKLABLE_TYPES = (
|
||||
UNPICKLABLE_TYPES = [
|
||||
types.ModuleType,
|
||||
types.TypeType,
|
||||
types.ClassType,
|
||||
types.FunctionType,
|
||||
)
|
||||
]
|
||||
|
||||
# Unpicklable statements to seed new historys with.
|
||||
INITIAL_UNPICKLABLES = [
|
||||
@@ -244,8 +244,8 @@ def run(history, statement, env={}):
|
||||
for name, val in statement_module.__dict__.items():
|
||||
if name not in old_globals or represent(val) != old_globals[name]:
|
||||
new_globals[name] = val
|
||||
|
||||
if True in [isinstance(val, UNPICKLABLE_TYPES)
|
||||
|
||||
if True in [isinstance(val, tuple(UNPICKLABLE_TYPES))
|
||||
for val in new_globals.values()]:
|
||||
# this statement added an unpicklable global. store the statement and
|
||||
# the names of all of the globals it added in the unpicklables.
|
||||
@@ -256,7 +256,11 @@ def run(history, statement, env={}):
|
||||
# new globals back into the datastore.
|
||||
for name, val in new_globals.items():
|
||||
if not name.startswith('__'):
|
||||
history.set_global(name, val)
|
||||
try:
|
||||
history.set_global(name, val)
|
||||
except TypeError, ex:
|
||||
UNPICKLABLE_TYPES.append(type(val))
|
||||
history.add_unpicklable(statement, new_globals.keys())
|
||||
|
||||
finally:
|
||||
sys.modules['__main__'] = old_main
|
||||
|
||||
+11
-2
@@ -117,6 +117,10 @@ class Token(object):
|
||||
if a cron job started before 60 seconds and did not stop,
|
||||
a warning is issue "Stale cron.master detected"
|
||||
"""
|
||||
if sys.platform == 'win32':
|
||||
locktime = 59.5
|
||||
else:
|
||||
locktime = 59.99
|
||||
if portalocker.LOCK_EX is None:
|
||||
logger.warning('WEB2PY CRON: Disabled because no file locking')
|
||||
return None
|
||||
@@ -128,7 +132,7 @@ class Token(object):
|
||||
(start, stop) = cPickle.load(self.master)
|
||||
except:
|
||||
(start, stop) = (0, 1)
|
||||
if startup or self.now - start > 59.99:
|
||||
if startup or self.now - start > locktime:
|
||||
ret = self.now
|
||||
if not stop:
|
||||
# this happens if previous cron job longer than 1 minute
|
||||
@@ -136,6 +140,7 @@ class Token(object):
|
||||
logger.debug('WEB2PY CRON: Acquiring lock')
|
||||
self.master.seek(0)
|
||||
cPickle.dump((self.now, 0), self.master)
|
||||
self.master.flush()
|
||||
finally:
|
||||
portalocker.unlock(self.master)
|
||||
if not ret:
|
||||
@@ -304,7 +309,11 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
|
||||
for task in tasks:
|
||||
if _cron_stopping:
|
||||
break
|
||||
commands = [sys.executable]
|
||||
if sys.executable.lower().endswith('pythonservice.exe'):
|
||||
_python_exe = os.path.join(sys.exec_prefix, 'python.exe')
|
||||
else:
|
||||
_python_exe = sys.executable
|
||||
commands = [_python_exe]
|
||||
w2p_path = fileutils.abspath('web2py.py', gluon=True)
|
||||
if os.path.exists(w2p_path):
|
||||
commands.append(w2p_path)
|
||||
|
||||
+53
-15
@@ -36,6 +36,7 @@ class Service(win32serviceutil.ServiceFramework):
|
||||
|
||||
_svc_name_ = '_unNamed'
|
||||
_svc_display_name_ = '_Service Template'
|
||||
_svc_description_ = '_Service Template description'
|
||||
|
||||
def __init__(self, *args):
|
||||
win32serviceutil.ServiceFramework.__init__(self, *args)
|
||||
@@ -44,6 +45,12 @@ class Service(win32serviceutil.ServiceFramework):
|
||||
def log(self, msg):
|
||||
servicemanager.LogInfoMsg(str(msg))
|
||||
|
||||
def log_error(self, msg):
|
||||
"""
|
||||
Log an error message to windows application event log.
|
||||
"""
|
||||
servicemanager.LogErrorMsg(str(msg))
|
||||
|
||||
def SvcDoRun(self):
|
||||
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
|
||||
try:
|
||||
@@ -80,6 +87,7 @@ class Web2pyService(Service):
|
||||
|
||||
_svc_name_ = 'web2py'
|
||||
_svc_display_name_ = 'web2py Service'
|
||||
_svc_description_ = 'Web2py application framework service'
|
||||
_exe_args_ = 'options'
|
||||
server = None
|
||||
|
||||
@@ -132,17 +140,16 @@ class Web2pyService(Service):
|
||||
server_name=options.server_name,
|
||||
request_queue_size=options.request_queue_size,
|
||||
timeout=options.timeout,
|
||||
socket_timeout=options.socket_timeout,
|
||||
shutdown_timeout=options.shutdown_timeout,
|
||||
path=options.folder
|
||||
path=options.folder,
|
||||
interfaces=options.interfaces
|
||||
)
|
||||
try:
|
||||
from rewrite import load
|
||||
load()
|
||||
self.server.start()
|
||||
except:
|
||||
|
||||
# self.server.stop()
|
||||
|
||||
self.server = None
|
||||
raise
|
||||
|
||||
@@ -159,12 +166,18 @@ class Web2pyCronService(Web2pyService):
|
||||
|
||||
_svc_name_ = 'web2py_cron'
|
||||
_svc_display_name_ = 'web2py Cron Service'
|
||||
_svc_description_ = 'web2py Windows cron service for scheduled tasks'
|
||||
_exe_args_ = 'options'
|
||||
|
||||
def start(self):
|
||||
import newcron
|
||||
import global_settings
|
||||
self.log('web2py server starting')
|
||||
import logging
|
||||
import logging.config
|
||||
from settings import global_settings
|
||||
from fileutils import abspath
|
||||
from os.path import exists, join
|
||||
|
||||
self.log('web2py Cron service starting')
|
||||
if not self.chdir():
|
||||
return
|
||||
if len(sys.argv) == 2:
|
||||
@@ -172,25 +185,50 @@ class Web2pyCronService(Web2pyService):
|
||||
else:
|
||||
opt_mod = self._exe_args_
|
||||
options = __import__(opt_mod, [], [], '')
|
||||
global_settings.global_settings.web2py_crontype = 'external'
|
||||
logpath = abspath(join(options.folder, "logging.conf"))
|
||||
|
||||
if exists(logpath):
|
||||
logging.config.fileConfig(logpath)
|
||||
else:
|
||||
logging.basicConfig()
|
||||
logger = logging.getLogger("web2py.cron")
|
||||
global_settings.web2py_crontype = 'external'
|
||||
if options.scheduler: # -K
|
||||
apps = [app.strip() for app in options.scheduler.split(
|
||||
',') if check_existent_app(options, app.strip())]
|
||||
else:
|
||||
apps = None
|
||||
self.extcron = newcron.extcron(options.folder, apps=apps)
|
||||
try:
|
||||
self.extcron.start()
|
||||
except:
|
||||
# self.server.stop()
|
||||
self.extcron = None
|
||||
raise
|
||||
|
||||
misfire_gracetime = float(options.misfire_gracetime) if 'misfire_gracetime' in dir(options) else 0.0
|
||||
logger.info('Starting Window cron service with %0.2f secs gracetime.' % misfire_gracetime)
|
||||
self._started = True
|
||||
wait_full_min = lambda: 60 - time.time() % 60
|
||||
while True:
|
||||
try:
|
||||
if wait_full_min() >= misfire_gracetime: # an offset of max. 5 secs before full minute (e.g. time.sleep(60) == 58.99 secs)
|
||||
self.extcron = newcron.extcron(options.folder, apps=apps)
|
||||
self.extcron.start()
|
||||
time.sleep(wait_full_min())
|
||||
else:
|
||||
logger.debug('time.sleep() offset detected: %0.3f s' % wait_full_min())
|
||||
while wait_full_min() <= misfire_gracetime:
|
||||
pass
|
||||
if apps != None:
|
||||
break
|
||||
if not self._started:
|
||||
break
|
||||
except Exception, ex:
|
||||
self.extcron = None
|
||||
self.log_error('%s, restarting service.' % ex)
|
||||
logger.exception('Exception! Restarting Windows cron service.' % ex)
|
||||
self.start()
|
||||
|
||||
def stop(self):
|
||||
self.log('web2py cron stopping')
|
||||
self.log('web2py Cron service stopping')
|
||||
if not self.chdir():
|
||||
return
|
||||
if self.extcron:
|
||||
self._started = False
|
||||
self.extcron.join()
|
||||
|
||||
def register_service_handler(argv=None, opt_file='options', cls=Web2pyService):
|
||||
|
||||
Reference in New Issue
Block a user