import traceback from pytz import timezone import sys import threading from datetime import datetime, timedelta from random import randint import time # third-party from apscheduler.jobstores.base import JobLookupError from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor from apscheduler.triggers.cron import CronTrigger jobstores = {"default": SQLAlchemyJobStore(url="sqlite:///data/db/gommi.db")} excutors = {"default": ThreadPoolExecutor(20)} job_defaults = {"coalesce": False, "max_instances": 1} # gommi 공용 from framework.logger import get_logger # 패키지 # ========================================================================== package_name = __name__.split(".")[0] logger = get_logger(package_name) class Scheduler(object): job_list = [] first_run_check_thread = None def __init__(self, args): # (jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc) # self.sched = GeventScheduler( executors=executors) try: if args is None or (args is not None and args.use_gevent): from apscheduler.schedulers.gevent import GeventScheduler self.sched = GeventScheduler(timezone="Asia/Seoul") else: raise Exception("") except Exception: from apscheduler.schedulers.background import BackgroundScheduler self.sched = BackgroundScheduler(timezone="Asia/Seoul") # jobstores=jobstores, 에러 pickle 직렬화 할수없다 # self.sched.configure(executors=executors, job_defaults=job_defaults, timezone='Asia/Seoul') # self.sched.configure(executors=executors, timezone='Asia/Seoul') self.sched.start() logger.debug("SCHEDULER...") def first_run_check_thread_function(self): logger.warning("XX first_run_check_thread_function") try: # time.sleep(60) # for i in range(5): flag_exit = True for job_instance in self.job_list: if not job_instance.run: continue if ( job_instance.count == 0 and not job_instance.is_running and job_instance.is_interval ): # if job_instance.count == 0 and not job_instance.is_running: job = self.sched.get_job(job_instance.job_id) if job is not None: logger.warning("job_instance : %s", job_instance.plugin) logger.warning("XX job re-sched:%s", job) flag_exit = False tmp = randint(1, 20) job.modify( next_run_time=datetime.now(timezone("Asia/Seoul")) + timedelta(seconds=tmp) ) # break else: pass if flag_exit: self.remove_job("scheduler_check") # time.sleep(30) logger.warning("first_run_check_thread_function end!!") except Exception as exception: logger.error("Exception:%s", exception) logger.error(traceback.format_exc()) def get_job_list_info(self): ret = [] idx = 0 job_list = self.sched.get_jobs() # logger.debug('len jobs %s %s', len(jobs), len(Scheduler.job_list)) for j in job_list: idx += 1 entity = {} entity["no"] = idx entity["id"] = j.id entity["next_run_time"] = j.next_run_time.strftime("%m-%d %H:%M:%S") remain = j.next_run_time - datetime.now(timezone("Asia/Seoul")) tmp = "" if remain.days > 0: tmp += "%s일 " % (remain.days) remain = remain.seconds if remain // 3600 > 0: tmp += "%s시간 " % (remain // 3600) remain = remain % 3600 if remain // 60 > 0: tmp += "%s분 " % (remain // 60) tmp += "%s초" % (remain % 60) # entity['remain_time'] = (j.next_run_time - datetime.now(timezone('Asia/Seoul'))).seconds entity["remain_time"] = tmp job = self.get_job_instance(j.id) if job is not None: entity["count"] = job.count entity["plugin"] = job.plugin if job.is_cron: entity["interval"] = job.interval elif job.interval == 9999: entity["interval"] = "항상 실행" entity["remain_time"] = "" else: entity["interval"] = "%s분 %s초" % ( job.interval, job.interval_seconds, ) entity["is_running"] = job.is_running entity["description"] = job.description entity["running_timedelta"] = ( job.running_timedelta.seconds if job.running_timedelta is not None else "-" ) entity["make_time"] = job.make_time.strftime("%m-%d %H:%M:%S") entity["run"] = job.run else: entity["count"] = "" entity["plugin"] = "" entity["interval"] = "" entity["is_running"] = "" entity["description"] = "" entity["running_timedelta"] = "" entity["make_time"] = "" entity["run"] = True ret.append(entity) return ret def add_job_instance(self, job_instance, run=True): from framework import app if app.config["config"]["run_by_real"] and app.config["config"]["auth_status"]: if not self.is_include(job_instance.job_id): job_instance.run = run Scheduler.job_list.append(job_instance) if job_instance.is_interval: self.sched.add_job( job_instance.job_function, "interval", minutes=job_instance.interval, seconds=job_instance.interval_seconds, id=job_instance.job_id, args=(None), ) elif job_instance.is_cron: self.sched.add_job( job_instance.job_function, CronTrigger.from_crontab(job_instance.interval), id=job_instance.job_id, args=(None), ) # self.sched.add_job(job_instance.job_function, 'interval', minutes=job_instance.interval, # id=job_instance.job_id, args=(None)) job = self.sched.get_job(job_instance.job_id) if run and job_instance.is_interval: tmp = randint(5, 20) job.modify( next_run_time=datetime.now(timezone("Asia/Seoul")) + timedelta(seconds=tmp) ) def is_include(self, job_id): job = self.sched.get_job(job_id) return job is not None