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'
|
_HISTORY_KIND = '_Shell_History'
|
||||||
|
|
||||||
# Types that can't be pickled.
|
# Types that can't be pickled.
|
||||||
UNPICKLABLE_TYPES = (
|
UNPICKLABLE_TYPES = [
|
||||||
types.ModuleType,
|
types.ModuleType,
|
||||||
types.TypeType,
|
types.TypeType,
|
||||||
types.ClassType,
|
types.ClassType,
|
||||||
types.FunctionType,
|
types.FunctionType,
|
||||||
)
|
]
|
||||||
|
|
||||||
# Unpicklable statements to seed new historys with.
|
# Unpicklable statements to seed new historys with.
|
||||||
INITIAL_UNPICKLABLES = [
|
INITIAL_UNPICKLABLES = [
|
||||||
@@ -245,7 +245,7 @@ def run(history, statement, env={}):
|
|||||||
if name not in old_globals or represent(val) != old_globals[name]:
|
if name not in old_globals or represent(val) != old_globals[name]:
|
||||||
new_globals[name] = val
|
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()]:
|
for val in new_globals.values()]:
|
||||||
# this statement added an unpicklable global. store the statement and
|
# this statement added an unpicklable global. store the statement and
|
||||||
# the names of all of the globals it added in the unpicklables.
|
# 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.
|
# new globals back into the datastore.
|
||||||
for name, val in new_globals.items():
|
for name, val in new_globals.items():
|
||||||
if not name.startswith('__'):
|
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:
|
finally:
|
||||||
sys.modules['__main__'] = old_main
|
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,
|
if a cron job started before 60 seconds and did not stop,
|
||||||
a warning is issue "Stale cron.master detected"
|
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:
|
if portalocker.LOCK_EX is None:
|
||||||
logger.warning('WEB2PY CRON: Disabled because no file locking')
|
logger.warning('WEB2PY CRON: Disabled because no file locking')
|
||||||
return None
|
return None
|
||||||
@@ -128,7 +132,7 @@ class Token(object):
|
|||||||
(start, stop) = cPickle.load(self.master)
|
(start, stop) = cPickle.load(self.master)
|
||||||
except:
|
except:
|
||||||
(start, stop) = (0, 1)
|
(start, stop) = (0, 1)
|
||||||
if startup or self.now - start > 59.99:
|
if startup or self.now - start > locktime:
|
||||||
ret = self.now
|
ret = self.now
|
||||||
if not stop:
|
if not stop:
|
||||||
# this happens if previous cron job longer than 1 minute
|
# this happens if previous cron job longer than 1 minute
|
||||||
@@ -136,6 +140,7 @@ class Token(object):
|
|||||||
logger.debug('WEB2PY CRON: Acquiring lock')
|
logger.debug('WEB2PY CRON: Acquiring lock')
|
||||||
self.master.seek(0)
|
self.master.seek(0)
|
||||||
cPickle.dump((self.now, 0), self.master)
|
cPickle.dump((self.now, 0), self.master)
|
||||||
|
self.master.flush()
|
||||||
finally:
|
finally:
|
||||||
portalocker.unlock(self.master)
|
portalocker.unlock(self.master)
|
||||||
if not ret:
|
if not ret:
|
||||||
@@ -304,7 +309,11 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
|
|||||||
for task in tasks:
|
for task in tasks:
|
||||||
if _cron_stopping:
|
if _cron_stopping:
|
||||||
break
|
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)
|
w2p_path = fileutils.abspath('web2py.py', gluon=True)
|
||||||
if os.path.exists(w2p_path):
|
if os.path.exists(w2p_path):
|
||||||
commands.append(w2p_path)
|
commands.append(w2p_path)
|
||||||
|
|||||||
+53
-15
@@ -36,6 +36,7 @@ class Service(win32serviceutil.ServiceFramework):
|
|||||||
|
|
||||||
_svc_name_ = '_unNamed'
|
_svc_name_ = '_unNamed'
|
||||||
_svc_display_name_ = '_Service Template'
|
_svc_display_name_ = '_Service Template'
|
||||||
|
_svc_description_ = '_Service Template description'
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
win32serviceutil.ServiceFramework.__init__(self, *args)
|
win32serviceutil.ServiceFramework.__init__(self, *args)
|
||||||
@@ -44,6 +45,12 @@ class Service(win32serviceutil.ServiceFramework):
|
|||||||
def log(self, msg):
|
def log(self, msg):
|
||||||
servicemanager.LogInfoMsg(str(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):
|
def SvcDoRun(self):
|
||||||
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
|
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
|
||||||
try:
|
try:
|
||||||
@@ -80,6 +87,7 @@ class Web2pyService(Service):
|
|||||||
|
|
||||||
_svc_name_ = 'web2py'
|
_svc_name_ = 'web2py'
|
||||||
_svc_display_name_ = 'web2py Service'
|
_svc_display_name_ = 'web2py Service'
|
||||||
|
_svc_description_ = 'Web2py application framework service'
|
||||||
_exe_args_ = 'options'
|
_exe_args_ = 'options'
|
||||||
server = None
|
server = None
|
||||||
|
|
||||||
@@ -132,17 +140,16 @@ class Web2pyService(Service):
|
|||||||
server_name=options.server_name,
|
server_name=options.server_name,
|
||||||
request_queue_size=options.request_queue_size,
|
request_queue_size=options.request_queue_size,
|
||||||
timeout=options.timeout,
|
timeout=options.timeout,
|
||||||
|
socket_timeout=options.socket_timeout,
|
||||||
shutdown_timeout=options.shutdown_timeout,
|
shutdown_timeout=options.shutdown_timeout,
|
||||||
path=options.folder
|
path=options.folder,
|
||||||
|
interfaces=options.interfaces
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
from rewrite import load
|
from rewrite import load
|
||||||
load()
|
load()
|
||||||
self.server.start()
|
self.server.start()
|
||||||
except:
|
except:
|
||||||
|
|
||||||
# self.server.stop()
|
|
||||||
|
|
||||||
self.server = None
|
self.server = None
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -159,12 +166,18 @@ class Web2pyCronService(Web2pyService):
|
|||||||
|
|
||||||
_svc_name_ = 'web2py_cron'
|
_svc_name_ = 'web2py_cron'
|
||||||
_svc_display_name_ = 'web2py Cron Service'
|
_svc_display_name_ = 'web2py Cron Service'
|
||||||
|
_svc_description_ = 'web2py Windows cron service for scheduled tasks'
|
||||||
_exe_args_ = 'options'
|
_exe_args_ = 'options'
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
import newcron
|
import newcron
|
||||||
import global_settings
|
import logging
|
||||||
self.log('web2py server starting')
|
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():
|
if not self.chdir():
|
||||||
return
|
return
|
||||||
if len(sys.argv) == 2:
|
if len(sys.argv) == 2:
|
||||||
@@ -172,25 +185,50 @@ class Web2pyCronService(Web2pyService):
|
|||||||
else:
|
else:
|
||||||
opt_mod = self._exe_args_
|
opt_mod = self._exe_args_
|
||||||
options = __import__(opt_mod, [], [], '')
|
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
|
if options.scheduler: # -K
|
||||||
apps = [app.strip() for app in options.scheduler.split(
|
apps = [app.strip() for app in options.scheduler.split(
|
||||||
',') if check_existent_app(options, app.strip())]
|
',') if check_existent_app(options, app.strip())]
|
||||||
else:
|
else:
|
||||||
apps = None
|
apps = None
|
||||||
self.extcron = newcron.extcron(options.folder, apps=apps)
|
|
||||||
try:
|
misfire_gracetime = float(options.misfire_gracetime) if 'misfire_gracetime' in dir(options) else 0.0
|
||||||
self.extcron.start()
|
logger.info('Starting Window cron service with %0.2f secs gracetime.' % misfire_gracetime)
|
||||||
except:
|
self._started = True
|
||||||
# self.server.stop()
|
wait_full_min = lambda: 60 - time.time() % 60
|
||||||
self.extcron = None
|
while True:
|
||||||
raise
|
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):
|
def stop(self):
|
||||||
self.log('web2py cron stopping')
|
self.log('web2py Cron service stopping')
|
||||||
if not self.chdir():
|
if not self.chdir():
|
||||||
return
|
return
|
||||||
if self.extcron:
|
if self.extcron:
|
||||||
|
self._started = False
|
||||||
self.extcron.join()
|
self.extcron.join()
|
||||||
|
|
||||||
def register_service_handler(argv=None, opt_file='options', cls=Web2pyService):
|
def register_service_handler(argv=None, opt_file='options', cls=Web2pyService):
|
||||||
|
|||||||
Reference in New Issue
Block a user