|
@@ -1,29 +1,31 @@
|
|
|
-from concurrent.futures import ThreadPoolExecutor
|
|
|
+# from concurrent.futures import ThreadPoolExecutor
|
|
|
from time import time
|
|
|
from types import GeneratorType
|
|
|
from typing import Callable, List, Generator, Optional, Any
|
|
|
|
|
|
-from eventlet import spawn_n, tpool
|
|
|
+# import eventlet
|
|
|
+# from eventlet import spawn, spawn_n, tpool
|
|
|
from eventlet.event import Event
|
|
|
-from eventlet.queue import Queue
|
|
|
+
|
|
|
|
|
|
from pycs.database.Project import Project
|
|
|
from pycs.jobs.Job import Job
|
|
|
from pycs.jobs.JobGroupBusyException import JobGroupBusyException
|
|
|
|
|
|
+from pycs.util.green_worker import GreenWorker
|
|
|
|
|
|
-class JobRunner:
|
|
|
+class JobRunner(GreenWorker):
|
|
|
"""
|
|
|
run jobs in a thread pool, but track progress and process results in eventlet queue
|
|
|
"""
|
|
|
|
|
|
# pylint: disable=too-many-arguments
|
|
|
def __init__(self):
|
|
|
+ super().__init__()
|
|
|
self.__jobs = []
|
|
|
self.__groups = {}
|
|
|
|
|
|
- self.__executor = ThreadPoolExecutor(1)
|
|
|
- self.__queue = Queue()
|
|
|
+ # self.__executor = ThreadPoolExecutor(1)
|
|
|
|
|
|
self.__create_listeners = []
|
|
|
self.__start_listeners = []
|
|
@@ -31,8 +33,6 @@ class JobRunner:
|
|
|
self.__finish_listeners = []
|
|
|
self.__remove_listeners = []
|
|
|
|
|
|
- spawn_n(self.__run)
|
|
|
-
|
|
|
def list(self) -> List[Job]:
|
|
|
"""
|
|
|
get a list of all jobs including finished ones
|
|
@@ -150,13 +150,95 @@ class JobRunner:
|
|
|
callback(job)
|
|
|
|
|
|
# add to execution queue
|
|
|
- self.__queue.put((group, executable, job, progress, result, result_event, args, kwargs))
|
|
|
+ # self.__queue.put((group, executable, job, progress, result, result_event, args, kwargs))
|
|
|
+ self.queue.put((group, executable, job, progress, result, result_event, args, kwargs))
|
|
|
|
|
|
# return job object
|
|
|
return job
|
|
|
|
|
|
+ def process_iterator(self, iterator, job, progress_fun):
|
|
|
+ try:
|
|
|
+ iterator = iter(generator)
|
|
|
+
|
|
|
+ while True:
|
|
|
+ # run until next progress event
|
|
|
+ # future = self.__executor.submit(next, iterator)
|
|
|
+ # progress = tpool.execute(future.result)
|
|
|
+
|
|
|
+ # progress = future.result()
|
|
|
+ progress = next(iterator)
|
|
|
+
|
|
|
+ # execute progress function
|
|
|
+ if progress_fun is not None:
|
|
|
+ if isinstance(progress, tuple):
|
|
|
+ progress = progress_fun(*progress)
|
|
|
+ else:
|
|
|
+ progress = progress_fun(progress)
|
|
|
+
|
|
|
+ # execute progress listeners
|
|
|
+ job.progress = progress
|
|
|
+ job.updated = int(time())
|
|
|
+
|
|
|
+ for callback in self.__progress_listeners:
|
|
|
+ callback(job)
|
|
|
+
|
|
|
+ except StopIteration as stop_iteration_exception:
|
|
|
+ return stop_iteration_exception.value
|
|
|
+
|
|
|
+
|
|
|
+ # done in a separate green thread
|
|
|
+ def work(self, group, executable, job, progress_fun, result_fun, result_event, args, kwargs):
|
|
|
+ # execute start listeners
|
|
|
+ job.started = int(time())
|
|
|
+ job.updated = int(time())
|
|
|
+
|
|
|
+ for callback in self.__start_listeners:
|
|
|
+ callback(job)
|
|
|
+
|
|
|
+ try:
|
|
|
+ result = generator = executable(*args, **kwargs)
|
|
|
+ if isinstance(generator, GeneratorType):
|
|
|
+ result = self.process_iterator(iterator, job, progress_fun)
|
|
|
+
|
|
|
+ # update progress
|
|
|
+ job.progress = 1
|
|
|
+ job.updated = int(time())
|
|
|
+
|
|
|
+ for callback in self.__progress_listeners:
|
|
|
+ callback(job)
|
|
|
+
|
|
|
+ # execute result function
|
|
|
+ if result_fun is not None:
|
|
|
+ if isinstance(result, tuple):
|
|
|
+ result_fun(*result)
|
|
|
+ else:
|
|
|
+ result_fun(result)
|
|
|
+
|
|
|
+ # execute event
|
|
|
+ if result_event is not None:
|
|
|
+ result_event.send(result)
|
|
|
+
|
|
|
+ # save exceptions to show in ui
|
|
|
+ except Exception as e:
|
|
|
+ import traceback
|
|
|
+ traceback.print_exc()
|
|
|
+ job.exception = f'{type(e).__name__} ({str(e)})'
|
|
|
+
|
|
|
+ # remove from group dict
|
|
|
+ if group is not None:
|
|
|
+ del self.__groups[group]
|
|
|
+
|
|
|
+ # finish job
|
|
|
+ job.finished = int(time())
|
|
|
+ job.updated = int(time())
|
|
|
+
|
|
|
+ for callback in self.__finish_listeners:
|
|
|
+ callback(job)
|
|
|
+
|
|
|
def __run(self):
|
|
|
+
|
|
|
while True:
|
|
|
+
|
|
|
# get execution function and job from queue
|
|
|
group, executable, job, progress_fun, result_fun, result_event, args, kwargs \
|
|
|
= self.__queue.get(block=True)
|
|
@@ -170,9 +252,9 @@ class JobRunner:
|
|
|
|
|
|
# run function and track progress
|
|
|
try:
|
|
|
+ # result = generator = executable(*args, **kwargs)
|
|
|
future = self.__executor.submit(executable, *args, **kwargs)
|
|
|
- generator = tpool.execute(future.result)
|
|
|
- result = generator
|
|
|
+ result = generator = tpool.execute(future.result)
|
|
|
|
|
|
if isinstance(generator, GeneratorType):
|
|
|
iterator = iter(generator)
|
|
@@ -182,6 +264,8 @@ class JobRunner:
|
|
|
# run until next progress event
|
|
|
future = self.__executor.submit(next, iterator)
|
|
|
progress = tpool.execute(future.result)
|
|
|
+ # progress = next(iterator)
|
|
|
+
|
|
|
|
|
|
# execute progress function
|
|
|
if progress_fun is not None:
|