def __init__(self, name, schedule, threadpool, ioloop=None): ''' Create a new task based on a schedule in ioloop (default to current). The schedule configuration accepts: - startup: True to run at startup, '*' to run on every config change - minutes, hours, dates, months, weekdays, years: cron schedule - thread: True to run in a separate thread ''' self.name = name self.utc = schedule.get('utc', False) self.thread = schedule.get('thread', False) if 'function' not in schedule: raise ValueError('schedule %s has no function:' % name) if callable(schedule['function']): self.function = schedule['function'] else: self.function = build_transform(schedule, vars={}, filename='schedule:%s' % name) self.ioloop = ioloop or tornado.ioloop.IOLoop.current() self._call_later(None) if self.thread: fn = self.function def on_done(future): exception = future.exception(timeout=0) if exception: app_log.error('%s (thread): %s', name, exception) def run_function(*args, **kwargs): future = threadpool.submit(fn, *args, **kwargs) future.add_done_callback(on_done) return future self.function = run_function # Run on schedule if any of the schedule periods are specified periods = 'minutes hours dates months weekdays years'.split() if any(schedule.get(key) for key in periods): # Convert all valid values into strings (e.g. 30 => '30'), and ignore any spaces cron = (str(schedule.get(key, '*')).replace(' ', '') for key in periods) self.cron_str = ' '.join(cron) self.cron = CronTab(self.cron_str) self.call_later() elif not schedule.get('startup'): app_log.warning('schedule:%s has no schedule nor startup', name) # Run now if the task is to be run on startup. Don't re-run if the config was reloaded startup = schedule.get('startup') if startup == '*' or (startup is True and not ioloop_running(self.ioloop)): self.function()
def callback(): '''Called after all services are started. Opens browser if required''' if ioloop_running(ioloop): return # If enterprise version is installed, user must accept license try: import gramexenterprise # noqa gramex.license.accept() except ImportError: pass app_log.info('Listening on port %d', conf.listen.port) app_log_extra['port'] = conf.listen.port # browser: True opens the application home page on localhost. # browser: url opens the application to a specific URL url = 'http://127.0.0.1:%d/' % conf.listen.port if conf.browser: if isinstance(conf.browser, str): url = urlparse.urljoin(url, conf.browser) try: browser = webbrowser.get() app_log.info('Opening %s in %s browser', url, browser.__class__.__name__) browser.open(url) except webbrowser.Error: app_log.info('Unable to open browser') else: app_log.info( '<Ctrl-B> opens the browser. <Ctrl-D> starts the debugger.' ) # Ensure that we call shutdown() on Ctrl-C. # On Windows, Tornado does not exit on Ctrl-C. This also fixes that. # When Ctrl-C is pressed, signal_handler() sets _exit to [True]. # check_exit() periodically watches and calls shutdown(). # But signal handlers can only be set in the main thread. # So ignore if we're not in the main thread (e.g. for nosetests, Windows service) # # Note: The PeriodicCallback takes up a small amount of CPU time. # Note: getch() doesn't handle keyboard buffer queue. # Note: This is no guarantee that shutdown() will be called. if isinstance(threading.current_thread(), threading._MainThread): exit = [False] def check_exit(): if exit[0] is True: shutdown() # If Ctrl-D is pressed, run the Python debugger char = debug.getch() if char == b'\x04': import ipdb as pdb # noqa pdb.set_trace() # noqa # If Ctrl-B is pressed, start the browser if char == b'\x02': browser = webbrowser.get() browser.open(url) def signal_handler(signum, frame): exit[0] = True try: signal.signal(signal.SIGINT, signal_handler) except ValueError: # When running as a Windows Service (winservice.py), python # itself is on a thread, I think. So ignore the # ValueError: signal only works in main thread. pass else: tornado.ioloop.PeriodicCallback(check_exit, callback_time=500).start() info._main_ioloop = ioloop ioloop.start()
def shutdown(): '''Shut down this instance''' ioloop = tornado.ioloop.IOLoop.current() if ioloop_running(ioloop): app_log.info('Shutting down Gramex...') ioloop.stop()
def app(conf): '''Set up tornado.web.Application() -- only if the ioloop hasn't started''' import tornado.ioloop ioloop = tornado.ioloop.IOLoop.current() if ioloop_running(ioloop): app_log.warning('Ignoring app config change when running') else: info.app = GramexApp(**conf.settings) try: info.app.listen(**conf.listen) except socket.error as e: port_used_codes = dict(windows=10048, linux=98) if e.errno not in port_used_codes.values(): raise logging.error( 'Port %d is busy. Use --listen.port= for a different port', conf.listen.port) sys.exit(1) def callback(): '''Called after all services are started. Opens browser if required''' if ioloop_running(ioloop): return gramex.license.accept() app_log.info('Listening on port %d', conf.listen.port) app_log_extra['port'] = conf.listen.port # browser: True opens the application home page on localhost. # browser: url opens the application to a specific URL url = 'http://127.0.0.1:%d/' % conf.listen.port if conf.browser: if isinstance(conf.browser, str): url = urlparse.urljoin(url, conf.browser) try: browser = webbrowser.get() app_log.info('Opening %s in %s browser', url, browser.__class__.__name__) browser.open(url) except webbrowser.Error: app_log.info('Unable to open browser') else: app_log.info( '<Ctrl-B> opens the browser. <Ctrl-D> starts the debugger.' ) # Ensure that we call shutdown() on Ctrl-C. # On Windows, Tornado does not exit on Ctrl-C. This also fixes that. # When Ctrl-C is pressed, signal_handler() sets _exit to [True]. # check_exit() periodically watches and calls shutdown(). # But signal handlers can only be set in the main thread. # So ignore if we're not in the main thread (e.g. for nosetests, Windows service) # # Note: The PeriodicCallback takes up a small amount of CPU time. # Note: getch() doesn't handle keyboard buffer queue. # Note: This is no guarantee that shutdown() will be called. if isinstance(threading.current_thread(), threading._MainThread): exit = [False] def check_exit(): if exit[0] is True: shutdown() # If Ctrl-D is pressed, run the Python debugger char = debug.getch() if char == b'\x04': import ipdb as pdb # noqa pdb.set_trace() # noqa # If Ctrl-B is pressed, start the browser if char == b'\x02': browser = webbrowser.get() browser.open(url) def signal_handler(signum, frame): exit[0] = True try: signal.signal(signal.SIGINT, signal_handler) except ValueError: # When running as a Windows Service (winservice.py), python # itself is on a thread, I think. So ignore the # ValueError: signal only works in main thread. pass else: tornado.ioloop.PeriodicCallback(check_exit, callback_time=500).start() ioloop.start() return callback