""" This is the main printing thread. A worker thread consums Tasks from a PrintQueue, while trying to find available printers. """ import threading import time from printers import Printers class PrintWorker(threading.Thread): """ A thread used to consume Tasks added to a Print Queue. On initialisation, the worker will try to find Printers, and on each print, choose an available Printer from a list of Printers. If a print fails, it's not retried, but will be added to a list of completed tasks. """ def __init__(self, app, print_queue): super().__init__(daemon=True) self.app = app self.print_queue = print_queue self.printer = None self._lock = threading.Lock() self.running = True self.state = "idle" # idle, printing, dead, drinking-a-beer self.app.logger.debug("Ho great, I'm alive... I'm ready to work another day...") try: self.printers = Printers(self.app) self.printers_obj = self.printers.printers self.printers = iter(self.printers.printers) except RuntimeError as e: self.app.logger.warning("Could not get any Printers") raise e def run(self): """Background thread that processes queue items""" self.app.logger.debug("Worker %s started working.", threading.get_ident()) self.app.logger.debug("Current threads : %s" , threading.active_count()) self.app.logger.debug("Threads actives : %s " , threading.enumerate()) while True: # If the printer is dead or asleep, it can't work. if not self.running: time.sleep(0.2) continue # If we have no available printer, we look at the list printers # we know about, and try to find one that is available. # When we find a printer, we acquire it # When we are finished with a printer, we release it to the world. while not self.printer or not self.printer.ready: time.sleep(1) try: self.app.logger.debug("Changing printers") self.printer = next(self.printers) self.app.logger.debug( "The worker got a %s printer and it's %s", self.printer.printer_type, "Ready" if self.printer.ready else "Not ready", ) except Exception as e: self.app.logger.error("No printer detected" + str(e)) self.printer = None if self.state != "idle": self.app.logger("We are not idle, waiting...") time.sleep(1) continue self.state = "printing" with self._lock: try: task = self.print_queue.dequeue() except Exception as e: self.app.logger.error("Could not get a new task ! %s ", str(e)) self.state = "idle" raise RuntimeError( "We could not get a new task because " + str(e) ) from e if task: try: self.app.logger.info("Got a new task") self.app.logger.debug("Got task %s", task.task_id) task.status = "processing" print_data = task.get_print_data() try: self.printer.print_task(task.task_type, print_data) except RuntimeError as e: self.state = "idle" self.app.logger.error("Could not print : %s", str(e)) raise e task.status = "completed" self.print_queue.mark_completed(task.task_id, "completed") self.app.logger.debug( "Finished printing task %s " , task.task_id ) self.state = "idle" except RuntimeError as e: task.status = "failed" self.state = "idle" self.print_queue.mark_completed(task.task_id, "failed") self.app.logger.error( "Could not print task %s because %s " , task.task_id, str(e) ) else: # When they are no new tasks to handle, we put the thread to sleep. self.state = "idle" time.sleep(0.1) def stop_worker(self): """ Give the worker a break """ self.app.logger.debug("Giving the worker a break") self.state = "drinking-a-beer" self.running = False def start_worker(self): """ Get the worker back to it """ self.app.logger.debug("Time to work !") self.state = "idle" self.running = True def current_state(self): """ Return the worker state """ return { "is_running": self.running, "queue_size": len(self.print_queue), "state": self.state, "printers": len(self.printers_obj), }