updated runner structure so that handlers and conductors actually pull from queues in the runner. changes to logic in both are extensive, but most individual functinos are unaffected. I've also moved several functions that were part of individual monitor, handler and conductors to the base classes.
This commit is contained in:
@ -6,37 +6,50 @@ from for all handler instances.
|
||||
Author(s): David Marchant
|
||||
"""
|
||||
|
||||
from typing import Any, Tuple, Dict
|
||||
|
||||
from threading import Event, Thread
|
||||
from typing import Any, Tuple, Dict, Union
|
||||
from time import sleep
|
||||
|
||||
from meow_base.core.vars import VALID_CHANNELS, \
|
||||
VALID_HANDLER_NAME_CHARS, get_drt_imp_msg
|
||||
from meow_base.core.meow import valid_event
|
||||
from meow_base.functionality.validation import check_implementation, \
|
||||
valid_string
|
||||
valid_string, valid_natural
|
||||
from meow_base.functionality.naming import generate_handler_id
|
||||
|
||||
class BaseHandler:
|
||||
# An identifier for a handler within the runner. Can be manually set in
|
||||
# the constructor, or autogenerated if no name provided.
|
||||
name:str
|
||||
# A channel for sending messages to the runner. Note that this will be
|
||||
# overridden by a MeowRunner, if a handler instance is passed to it, and so
|
||||
# does not need to be initialised within the handler itself.
|
||||
to_runner: VALID_CHANNELS
|
||||
# A channel for sending messages to the runner event queue. Note that this
|
||||
# will be overridden by a MeowRunner, if a handler instance is passed to
|
||||
# it, and so does not need to be initialised within the handler itself,
|
||||
# unless the handler is running independently of a runner.
|
||||
to_runner_event: VALID_CHANNELS
|
||||
# A channel for sending messages to the runner job queue. Note that this
|
||||
# will be overridden by a MeowRunner, if a handler instance is passed to
|
||||
# it, and so does not need to be initialised within the handler itself,
|
||||
# unless the handler is running independently of a runner.
|
||||
to_runner_job: VALID_CHANNELS
|
||||
# Directory where queued jobs are initially written to. Note that this
|
||||
# will be overridden by a MeowRunner, if a handler instance is passed to
|
||||
# it, and so does not need to be initialised within the handler itself.
|
||||
job_queue_dir:str
|
||||
def __init__(self, name:str='')->None:
|
||||
# A count, for how long a handler will wait if told that there are no
|
||||
# events in the runner, before polling again. Default is 5 seconds.
|
||||
pause_time: int
|
||||
def __init__(self, name:str='', pause_time:int=5)->None:
|
||||
"""BaseHandler Constructor. This will check that any class inheriting
|
||||
from it implements its validation functions."""
|
||||
check_implementation(type(self).handle, BaseHandler)
|
||||
check_implementation(type(self).valid_handle_criteria, BaseHandler)
|
||||
check_implementation(type(self).prompt_runner_for_event, BaseHandler)
|
||||
check_implementation(type(self).send_job_to_runner, BaseHandler)
|
||||
if not name:
|
||||
name = generate_handler_id()
|
||||
self._is_valid_name(name)
|
||||
self.name = name
|
||||
self.name = name
|
||||
self._is_valid_pause_time(pause_time)
|
||||
self.pause_time = pause_time
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""A check that this base class is not instantiated itself, only
|
||||
@ -52,24 +65,71 @@ class BaseHandler:
|
||||
overridden by child classes."""
|
||||
valid_string(name, VALID_HANDLER_NAME_CHARS)
|
||||
|
||||
def prompt_runner_for_event(self):
|
||||
pass
|
||||
def _is_valid_pause_time(self, pause_time:int)->None:
|
||||
"""Validation check for 'pause_time' variable from main constructor. Is
|
||||
automatically called during initialisation. This does not need to be
|
||||
overridden by child classes."""
|
||||
valid_natural(pause_time, hint="BaseHandler.pause_time")
|
||||
|
||||
def send_job_to_runner(self, msg):
|
||||
#self.to_runner.send(msg)
|
||||
pass
|
||||
def prompt_runner_for_event(self)->Union[Dict[str,Any],Any]:
|
||||
self.to_runner_event.send(1)
|
||||
|
||||
if self.to_runner_event.poll(self.pause_time):
|
||||
return self.to_runner_event.recv()
|
||||
return None
|
||||
|
||||
def send_job_to_runner(self, job_id:str)->None:
|
||||
self.to_runner_job.send(job_id)
|
||||
|
||||
def start(self)->None:
|
||||
"""Function to start the handler as an ongoing process/thread. May be
|
||||
overidden by any child process. Note that by default this will raise an
|
||||
execption that is automatically handled within a runner instance."""
|
||||
raise NotImplementedError
|
||||
"""Function to start the handler as an ongoing thread, as defined by
|
||||
the main_loop function. Together, these will execute any code in a
|
||||
implemented handlers handle function sequentially, but concurrently to
|
||||
any other handlers running or other runner operations. This is intended
|
||||
as a naive mmultiprocessing implementation, and any more in depth
|
||||
parallelisation of execution must be implemented by a user by
|
||||
overriding this function, and the stop function."""
|
||||
self._stop_event = Event()
|
||||
self._handle_thread = Thread(
|
||||
target=self.main_loop,
|
||||
args=(self._stop_event,),
|
||||
daemon=True,
|
||||
name="handler_thread"
|
||||
)
|
||||
self._handle_thread.start()
|
||||
|
||||
def stop(self)->None:
|
||||
"""Function to stop the handler as an ongoing process/thread. May be
|
||||
overidden by any child process. Note that by default this will raise an
|
||||
execption that is automatically handled within a runner instance."""
|
||||
raise NotImplementedError
|
||||
"""Function to stop the handler as an ongoing thread. May be overidden
|
||||
by any child class. This function should also be overriden if the start
|
||||
function has been."""
|
||||
|
||||
self._stop_event.set()
|
||||
self._handle_thread.join()
|
||||
|
||||
def main_loop(self, stop_event)->None:
|
||||
"""Function defining an ongoing thread, as started by the start
|
||||
function and stoped by the stop function. """
|
||||
|
||||
while not stop_event.is_set():
|
||||
reply = self.prompt_runner_for_event()
|
||||
|
||||
# If we have recieved 'None' then we have already timed out so skip
|
||||
# this loop and start again
|
||||
if reply is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
valid_event(reply)
|
||||
except Exception as e:
|
||||
# Were not given an event, so sleep before trying again
|
||||
sleep(self.pause_time)
|
||||
|
||||
|
||||
try:
|
||||
self.handle(reply)
|
||||
except Exception as e:
|
||||
# TODO some error reporting here
|
||||
pass
|
||||
|
||||
def valid_handle_criteria(self, event:Dict[str,Any])->Tuple[bool,str]:
|
||||
"""Function to determine given an event defintion, if this handler can
|
||||
@ -78,5 +138,7 @@ class BaseHandler:
|
||||
|
||||
def handle(self, event:Dict[str,Any])->None:
|
||||
"""Function to handle a given event. Must be implemented by any child
|
||||
process."""
|
||||
process. Note that once any handling has occured, the
|
||||
send_job_to_runner function should be called to inform the runner of
|
||||
any resultant jobs."""
|
||||
pass
|
||||
|
Reference in New Issue
Block a user