platypush/platypush/cron/scheduler.py

121 lines
3.5 KiB
Python
Raw Normal View History

import datetime
2019-09-28 01:34:27 +02:00
import enum
2018-01-15 22:36:24 +01:00
import logging
import threading
2018-01-15 22:36:24 +01:00
2019-09-28 01:34:27 +02:00
import croniter
from dateutil.tz import gettz
2019-09-28 01:34:27 +02:00
from platypush.procedure import Procedure
from platypush.utils import is_functional_cron
2018-01-15 22:36:24 +01:00
2020-09-27 01:33:38 +02:00
logger = logging.getLogger('platypush:cron')
2018-06-06 20:09:18 +02:00
2019-09-28 01:34:27 +02:00
class CronjobState(enum.IntEnum):
IDLE = 0
WAIT = 1
RUNNING = 2
DONE = 3
ERROR = 4
class Cronjob(threading.Thread):
2019-09-28 01:34:27 +02:00
def __init__(self, name, cron_expression, actions):
2018-01-15 22:36:24 +01:00
super().__init__()
self.cron_expression = cron_expression
2018-01-15 22:44:57 +01:00
self.name = name
2019-09-28 01:34:27 +02:00
self.state = CronjobState.IDLE
self._should_stop = threading.Event()
2020-11-11 17:09:32 +01:00
if isinstance(actions, dict) or isinstance(actions, list):
self.actions = Procedure.build(name=name + '__Cron', _async=False, requests=actions)
else:
self.actions = actions
2018-01-15 22:36:24 +01:00
def run(self):
2019-09-28 01:34:27 +02:00
self.state = CronjobState.WAIT
self.wait()
if self.should_stop():
return
2019-09-28 01:34:27 +02:00
self.state = CronjobState.RUNNING
try:
logger.info('Running cronjob {}'.format(self.name))
context = {}
if isinstance(self.actions, Procedure):
response = self.actions.execute(_async=False, **context)
else:
response = self.actions(**context)
2019-09-28 01:34:27 +02:00
logger.info('Response from cronjob {}: {}'.format(self.name, response))
self.state = CronjobState.DONE
except Exception as e:
logger.exception(e)
self.state = CronjobState.ERROR
def wait(self):
now = datetime.datetime.now().replace(tzinfo=gettz())
2019-09-28 01:34:27 +02:00
cron = croniter.croniter(self.cron_expression, now)
next_run = cron.get_next()
self._should_stop.wait(next_run - now.timestamp())
2018-01-15 22:36:24 +01:00
def stop(self):
self._should_stop.set()
def should_stop(self):
return self._should_stop.is_set()
2018-01-15 22:36:24 +01:00
class CronScheduler(threading.Thread):
2019-09-28 01:34:27 +02:00
def __init__(self, jobs):
2018-01-15 22:36:24 +01:00
super().__init__()
self.jobs_config = jobs
2019-09-28 01:34:27 +02:00
self._jobs = {}
self._should_stop = threading.Event()
2019-09-28 01:34:27 +02:00
logger.info('Cron scheduler initialized with {} jobs'.
format(len(self.jobs_config.keys())))
2018-01-15 22:36:24 +01:00
2019-09-28 01:34:27 +02:00
def _get_job(self, name, config):
job = self._jobs.get(name)
if job and job.state not in [CronjobState.DONE, CronjobState.ERROR]:
return job
2018-01-15 22:36:24 +01:00
if isinstance(config, dict):
self._jobs[name] = Cronjob(name=name, cron_expression=config['cron_expression'],
actions=config['actions'])
elif is_functional_cron(config):
self._jobs[name] = Cronjob(name=name, cron_expression=config.cron_expression,
actions=config)
else:
raise AssertionError('Expected type dict or function for cron {}, got {}'.format(
name, type(config)))
2018-01-15 22:36:24 +01:00
2019-09-28 01:34:27 +02:00
return self._jobs[name]
2018-01-15 22:36:24 +01:00
def stop(self):
for job in self._jobs.values():
job.stop()
self._should_stop.set()
def should_stop(self):
return self._should_stop.is_set()
2018-01-15 22:36:24 +01:00
def run(self):
2018-06-06 20:09:18 +02:00
logger.info('Running cron scheduler')
2018-01-15 22:36:24 +01:00
while not self.should_stop():
2018-01-15 22:44:57 +01:00
for (job_name, job_config) in self.jobs_config.items():
2019-09-28 01:34:27 +02:00
job = self._get_job(name=job_name, config=job_config)
if job.state == CronjobState.IDLE:
2018-01-15 22:36:24 +01:00
job.start()
self._should_stop.wait(timeout=0.5)
2018-01-15 22:36:24 +01:00
logger.info('Terminating cron scheduler')
2018-01-15 22:36:24 +01:00
# vim:sw=4:ts=4:et: