# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions # of the GNU General Public License v.2. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import threading # noinspection PyUnresolvedReferences from gi.repository import GLib from .job import Job from . import cfg import traceback from .utils import log_error class RequestEntry(object): def __init__(self, tmo, method, arguments, cb, cb_error, return_tuple=True): self.tmo = tmo self.method = method self.arguments = arguments self.cb = cb self.cb_error = cb_error self.timer_id = -1 self.lock = threading.RLock() self.done = False self._result = None self._job = False self._rc = 0 self._rc_error = None self._return_tuple = return_tuple if self.tmo < 0: # Client is willing to block forever pass elif tmo == 0: self._return_job() else: self.timer_id = GLib.timeout_add_seconds( tmo, RequestEntry._request_timeout, self) @staticmethod def _request_timeout(r): """ Method which gets called when the timer runs out! :param r: RequestEntry which timed out :return: Nothing """ r.timer_expired() def _return_job(self): self._job = Job(self) cfg.om.register_object(self._job, True) if self._return_tuple: self.cb(('/', self._job.dbus_object_path())) else: self.cb(self._job.dbus_object_path()) def run_cmd(self): try: result = self.method(*self.arguments) self.register_result(result) except Exception as e: # Use the request entry to return the result as the client may # have gotten a job by the time we hit an error # Lets get the stacktrace and set that to the error message st = traceback.format_exc() log_error("Exception returned to client: \n%s" % st) self.register_error(-1, str(e), e) def is_done(self): with self.lock: rc = self.done return rc def get_errors(self): with self.lock: return (self._rc, self._rc_error) def result(self): with self.lock: if self.done: return self._result return '/' def _reg_ending(self, result, error_rc=0, error_msg=None, error_exception=None): with self.lock: self.done = True if self.timer_id != -1: # Try to prevent the timer from firing GLib.source_remove(self.timer_id) self._result = result self._rc = error_rc self._rc_error = error_msg if not self._job: # We finished and there is no job, so return result or error # now! # Note: If we don't have a valid cb or cbe, this indicates a # request that doesn't need a response as we already returned # one before the request was processed. if error_rc == 0: if self.cb: if self._return_tuple: self.cb((result, '/')) else: self.cb(result) else: if self.cb_error: if not error_exception: if not error_msg: error_exception = Exception( "An error occurred, but no reason was " "given, see service logs!") else: error_exception = Exception(error_msg) self.cb_error(error_exception) else: # We have a job and it's complete, indicate that it's done. # TODO: We need to signal the job is done too. self._job.Complete = True self._job = None def register_error(self, error_rc, error_message, error_exception): self._reg_ending(None, error_rc, error_message, error_exception) def register_result(self, result): self._reg_ending(result) def timer_expired(self): with self.lock: # Set the timer back to -1 as we will get a warning if we try # to remove a timer that doesn't exist self.timer_id = -1 if not self.done: # Create dbus job object and return path to caller self._return_job() else: # The job is done, we have nothing to do pass return False