public function run() {
        # pull the handler from the db
        $handler = $this->getHandler();
        if (!is_object($handler)) {
            $msg = "[JOB] bad handler for job::{$this->job_id}";
            $this->finishWithError($msg);
            return false;
        }

        # run the handler
        try {

            if ($this->fail_on_output) {
                ob_start();                
            }

            $handler->perform();

            if ($this->fail_on_output) {
                $output = ob_get_contents();
                ob_end_clean();

                if (!empty($output)) {
                    throw new Exception("Job produced unexpected output: $output");
                }
            }

            # cleanup
            $this->finish();
            return true;

        } catch (DJRetryException $e) {
            if ($this->fail_on_output) {
                ob_end_flush();
            }
            
            # attempts hasn't been incremented yet.
            $attempts = $this->getAttempts()+1;

            $msg = "Caught DJRetryException \"{$e->getMessage()}\" on attempt $attempts/{$this->max_attempts}.";

            if($attempts == $this->max_attempts) {
                $msg = "[JOB] job::{$this->job_id} $msg Giving up.";
                $this->finishWithError($msg, $handler);
            } else {
                $this->log("[JOB] job::{$this->job_id} $msg Try again in {$e->getDelay()} seconds.", self::WARN);
                $this->retryLater($e->getDelay());
            }
            return false;

        } catch (Exception $e) {
            if ($this->fail_on_output) {
                ob_end_flush();
            }

            $this->finishWithError($e->getMessage(), $handler);
            return false;

        }
    }