sp-job 0.1.17 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +40 -17
- data/VERSION +1 -1
- data/bin/console +1 -1
- data/bin/unique-file +55 -0
- data/lib/sp-job.rb +9 -8
- data/lib/sp/job/back_burner.rb +301 -42
- data/lib/sp/job/broker.rb +59 -21
- data/lib/sp/job/common.rb +288 -156
- data/lib/sp/job/job_db_adapter.rb +63 -0
- data/lib/sp/job/{engine.rb → jwt.rb} +14 -12
- data/lib/sp/job/mail_queue.rb +60 -0
- data/lib/sp/job/pg_connection.rb +68 -76
- data/lib/sp/job/unique_file.rb +73 -0
- data/lib/sp/job/uploaded_image_converter.rb +35 -16
- data/lib/sp/job/worker.rb +1 -8
- data/lib/sp/job/worker_thread.rb +57 -0
- data/lib/tasks/configure.rake +66 -16
- data/sp-job.gemspec +10 -8
- metadata +23 -17
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 759aab586beb6fd8f184cd2649a53973fbc789f5
         | 
| 4 | 
            +
              data.tar.gz: 1827e99f054c36f10130ab89958601ea59f19627
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6311403a456ac54698ee3ecca61e7d9f192191b445bc338ce5e0496f6a7189ef8b7d11bbcf363c86d5f8f3f0bb3e4327663c17d0c6d879f1c96c08241b42a59f
         | 
| 7 | 
            +
              data.tar.gz: 198cb7fd83ea854ed76c5b8195c0af534923d2ef46a4eedd275b7736f0b1740b041c3e5eda45b0be7df19dd7e2064a3ea3ba76e5d61ced7ee32991248710a0c6
         | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,36 +1,59 @@ | |
| 1 1 | 
             
            # Sp::Job
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            # Execute the job
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 5 | 
            +
            The job is executed method perform of the job class. If the tube is called 'park-fun' 
         | 
| 6 6 |  | 
| 7 | 
            -
             | 
| 7 | 
            +
            ```ruby
         | 
| 8 | 
            +
              class ParkFun
         | 
| 8 9 |  | 
| 9 | 
            -
             | 
| 10 | 
            +
              def self.perform (job)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              end
         | 
| 10 13 |  | 
| 11 | 
            -
            ```ruby
         | 
| 12 | 
            -
            gem 'sp-job'
         | 
| 13 14 | 
             
            ```
         | 
| 14 15 |  | 
| 15 | 
            -
             | 
| 16 | 
            +
            ## Return the job result
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            use send_response(result: object)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## Report a non-fatal error
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            return report_error(message: 'i18n_message_key", args)
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            ## Report a fatal error
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            Use raise_error(message:  'i18n_message_key", args)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            # Database access 
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            Use the `db` object. 
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            ## db.exec
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            The first argument is the query string followed by a variable number of arguments that are bound to the query.
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            ```ruby
         | 
| 37 | 
            +
               db.exec('SELECT fun FROM public.park where id=$1', id_value)
         | 
| 38 | 
            +
            ```
         | 
| 16 39 |  | 
| 17 | 
            -
             | 
| 40 | 
            +
            Returns an xxx
         | 
| 18 41 |  | 
| 19 | 
            -
             | 
| 42 | 
            +
            ## db.query
         | 
| 20 43 |  | 
| 21 | 
            -
             | 
| 44 | 
            +
            # Redis
         | 
| 22 45 |  | 
| 23 | 
            -
             | 
| 46 | 
            +
            Use the redis accessor to obtain a Redis object already connected 
         | 
| 24 47 |  | 
| 25 | 
            -
             | 
| 48 | 
            +
            # Sending mails
         | 
| 26 49 |  | 
| 27 | 
            -
             | 
| 50 | 
            +
            Call send_mail
         | 
| 28 51 |  | 
| 29 | 
            -
             | 
| 52 | 
            +
            # Job configuration
         | 
| 30 53 |  | 
| 31 | 
            -
             | 
| 54 | 
            +
            Use config
         | 
| 32 55 |  | 
| 33 | 
            -
             | 
| 56 | 
            +
            # Logging 
         | 
| 34 57 |  | 
| 35 | 
            -
             | 
| 58 | 
            +
            Use logger
         | 
| 36 59 |  | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.2.2
         | 
    
        data/bin/console
    CHANGED
    
    
    
        data/bin/unique-file
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # Copyright (c) 2011-2018 Cloudware S.A. All rights reserved.
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # This file is part of sp-job.
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # sp-job is free software: you can redistribute it and/or modify
         | 
| 8 | 
            +
            # it under the terms of the GNU Affero General Public License as published by
         | 
| 9 | 
            +
            # the Free Software Foundation, either version 3 of the License, or
         | 
| 10 | 
            +
            # (at your option) any later version.
         | 
| 11 | 
            +
            #
         | 
| 12 | 
            +
            # sp-job is distributed in the hope that it will be useful,
         | 
| 13 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 14 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         | 
| 15 | 
            +
            # GNU General Public License for more details.
         | 
| 16 | 
            +
            #
         | 
| 17 | 
            +
            # You should have received a copy of the GNU Affero General Public License
         | 
| 18 | 
            +
            # along with sp-job.  If not, see <http://www.gnu.org/licenses/>.
         | 
| 19 | 
            +
            #
         | 
| 20 | 
            +
            # encoding: utf-8
         | 
| 21 | 
            +
            #
         | 
| 22 | 
            +
             | 
| 23 | 
            +
             | 
| 24 | 
            +
            require 'ap'
         | 
| 25 | 
            +
            require 'bundler/setup'
         | 
| 26 | 
            +
            require 'optparse'
         | 
| 27 | 
            +
            require 'sp/job/unique_file'
         | 
| 28 | 
            +
            require 'syslog/logger'
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            begin
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              $args = {}
         | 
| 33 | 
            +
              $config = {}
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              #
         | 
| 36 | 
            +
              # Parse command line arguments
         | 
| 37 | 
            +
              #
         | 
| 38 | 
            +
              $option_parser = OptionParser.new do |opts|
         | 
| 39 | 
            +
                opts.banner = "Usage: #{$PROGRAM_NAME} ARGS"
         | 
| 40 | 
            +
                opts.on('-p', '--path=PATH', 'Path to where file will be created') { |v| $args[:path] = v }
         | 
| 41 | 
            +
                opts.on('-e', '--extension=EXTENSION', 'File extension')           { |v| $args[:ext]  = v } 
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
              $option_parser.parse!
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              raise 'Must specify path' 	 if $args[:path].nil?
         | 
| 46 | 
            +
              raise 'Must specify extension' if $args[:ext].nil?
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              raise 'Path not writable' unless File.writable?($args[:path])
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              file = SP::Job::Unique::File.create($args[:path], ".#{$args[:ext]}")
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              raise 'Could not create file'.red if file.nil?
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              puts file
         | 
| 55 | 
            +
            end
         | 
    
        data/lib/sp-job.rb
    CHANGED
    
    | @@ -24,22 +24,23 @@ require 'redis' | |
| 24 24 | 
             
            require 'backburner'
         | 
| 25 25 | 
             
            require 'json'
         | 
| 26 26 | 
             
            require 'fileutils'
         | 
| 27 | 
            -
            require 'concurrent'
         | 
| 28 27 | 
             
            require 'optparse'
         | 
| 29 28 | 
             
            require 'os'
         | 
| 30 29 | 
             
            require 'pg'
         | 
| 31 | 
            -
            require ' | 
| 32 | 
            -
            require 'oauth2'
         | 
| 33 | 
            -
            require ' | 
| 34 | 
            -
            require 'curb'
         | 
| 35 | 
            -
            require 'rails'
         | 
| 30 | 
            +
            require 'oauth2'         unless RUBY_ENGINE == 'jruby'
         | 
| 31 | 
            +
            require 'oauth2-client'  unless RUBY_ENGINE == 'jruby'
         | 
| 32 | 
            +
            require 'curb'           unless RUBY_ENGINE == 'jruby'
         | 
| 36 33 | 
             
            require 'erb'
         | 
| 37 34 | 
             
            require 'ostruct'
         | 
| 38 35 | 
             
            require 'json'
         | 
| 39 36 | 
             
            require 'mail'
         | 
| 37 | 
            +
            require 'uri'
         | 
| 40 38 |  | 
| 41 39 | 
             
            require 'sp/job'
         | 
| 42 | 
            -
            require 'sp/job/engine'
         | 
| 43 40 | 
             
            require 'sp/job/version'
         | 
| 44 41 | 
             
            require 'sp/job/worker'
         | 
| 45 | 
            -
            require 'sp/job/ | 
| 42 | 
            +
            require 'sp/job/worker_thread'
         | 
| 43 | 
            +
            require 'sp/job/common'
         | 
| 44 | 
            +
            require 'sp/job/unique_file'
         | 
| 45 | 
            +
            require 'sp/job/broker_http_client'      unless RUBY_ENGINE == 'jruby'
         | 
| 46 | 
            +
            require 'sp/job/broker_oauth2_client'    unless RUBY_ENGINE == 'jruby'
         | 
    
        data/lib/sp/job/back_burner.rb
    CHANGED
    
    | @@ -19,21 +19,75 @@ | |
| 19 19 | 
             
            # encoding: utf-8
         | 
| 20 20 | 
             
            #
         | 
| 21 21 | 
             
            require 'sp/job/pg_connection'
         | 
| 22 | 
            +
            require 'sp/job/job_db_adapter'
         | 
| 22 23 | 
             
            require 'roadie'
         | 
| 24 | 
            +
            require 'thread'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            module SP
         | 
| 27 | 
            +
              module Job
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                class JobCancelled < ::StandardError
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                class JobAborted < ::StandardError
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                class JobException < ::StandardError
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  attr_reader :job
         | 
| 39 | 
            +
                  attr_reader :args
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def initialize (args:, job: nil)
         | 
| 42 | 
            +
            	      super(args[:message] || $current_job[:tube] || $args[:program_name])
         | 
| 43 | 
            +
                    @job     = job
         | 
| 44 | 
            +
                    @args    = args
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                class Logger < ::Logger
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def task (sequence, text, success = true)
         | 
| 52 | 
            +
                    if success
         | 
| 53 | 
            +
                      info "[#{sequence}] #{text} \xE2\x9C\x94".green
         | 
| 54 | 
            +
                    else
         | 
| 55 | 
            +
                      info "[#{sequence}] #{text} \xE2\x9D\x8C".red
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                class ThreadData < Struct.new(:job_status, :report_time_stamp, :exception_reported, :job_id, :publish_key, :job_key, :current_job, :job_notification, :jsonapi)
         | 
| 61 | 
            +
                  def initialize
         | 
| 62 | 
            +
                    @job_status = {}
         | 
| 63 | 
            +
                    if $config[:options] && $config[:options][:jsonapi] == true
         | 
| 64 | 
            +
                      @jsonapi = SP::Duh::JSONAPI::Service.new($pg, nil, SP::Job::JobDbAdapter)
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                class FauxMutex
         | 
| 70 | 
            +
                  def synchronize (&block)
         | 
| 71 | 
            +
                    yield
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
            end
         | 
| 77 | 
            +
             | 
| 23 78 |  | 
| 24 79 | 
             
            #
         | 
| 25 80 | 
             
            # Initialize global data needed for configuration
         | 
| 26 81 | 
             
            #
         | 
| 27 | 
            -
            $prefix | 
| 28 | 
            -
            $rollbar | 
| 29 | 
            -
            $ | 
| 30 | 
            -
            $ | 
| 31 | 
            -
             | 
| 32 | 
            -
               | 
| 33 | 
            -
               | 
| 34 | 
            -
               | 
| 35 | 
            -
               | 
| 36 | 
            -
              log_file:     File.join($prefix, 'var', 'log', 'jobs', "#{File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))}.log")
         | 
| 82 | 
            +
            $prefix           = OS.mac? ? '/usr/local' : ''
         | 
| 83 | 
            +
            $rollbar          = false
         | 
| 84 | 
            +
            $min_progress     = 3
         | 
| 85 | 
            +
            $args = {
         | 
| 86 | 
            +
              stdout:           false,
         | 
| 87 | 
            +
              log_level:        'info',
         | 
| 88 | 
            +
              program_name:     File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)),
         | 
| 89 | 
            +
              config_file:      File.join($prefix, 'etc', File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)), 'conf.json'),
         | 
| 90 | 
            +
              default_log_file: File.join($prefix, 'var', 'log', 'jobs', "#{File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))}.log")
         | 
| 37 91 | 
             
            }
         | 
| 38 92 |  | 
| 39 93 | 
             
            #
         | 
| @@ -41,17 +95,62 @@ $args         = { | |
| 41 95 | 
             
            #
         | 
| 42 96 | 
             
            $option_parser = OptionParser.new do |opts|
         | 
| 43 97 | 
             
              opts.banner = "Usage: #{$PROGRAM_NAME} ARGS"
         | 
| 44 | 
            -
              opts.on('-c', '--config=CONFIG.JSON', "path to json configuration file (default: '#{$args[: | 
| 45 | 
            -
              opts.on('-l', '--log=LOGFILE'       , "path to log file (default: '#{$args[:log_file]}')") | 
| 46 | 
            -
              opts.on('-d', '--debug'             , "developer mode: log to stdout and print job") | 
| 47 | 
            -
              opts.on('-v', '--log_level=LEVEL'   , "Log level DEBUG, INFO, WARN, ERROR, FATAL") | 
| 98 | 
            +
              opts.on('-c', '--config=CONFIG.JSON', "path to json configuration file (default: '#{$args[:default_log_file]}')") { |v| $args[:config_file] = File.expand_path(v) }
         | 
| 99 | 
            +
              opts.on('-l', '--log=LOGFILE'       , "path to log file (default: '#{$args[:log_file]}')")                        { |v| $args[:log_file]    = File.expand_path(v) }
         | 
| 100 | 
            +
              opts.on('-d', '--debug'             , "developer mode: log to stdout and print job")                              { $args[:debug]           = true                }
         | 
| 101 | 
            +
              opts.on('-v', '--log_level=LEVEL'   , "Log level DEBUG, INFO, WARN, ERROR, FATAL")                                { |v| $args[:log_level]   = v                   }
         | 
| 102 | 
            +
              opts.on('-i', '--index=IDX'         , "systemd instance index")                                                   { |v| $args[:index]       = v                   }
         | 
| 48 103 | 
             
            end
         | 
| 49 104 | 
             
            $option_parser.parse!
         | 
| 50 105 |  | 
| 106 | 
            +
            if $args[:debug]
         | 
| 107 | 
            +
              require 'ruby-debug' if RUBY_ENGINE == 'jruby'
         | 
| 108 | 
            +
            end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            # Adjust log file if need, user specified option always takes precedence
         | 
| 111 | 
            +
            #
         | 
| 112 | 
            +
            if $args[:log_file].nil?
         | 
| 113 | 
            +
              if $args[:index].nil?
         | 
| 114 | 
            +
                $args[:log_file] = $args[:default_log_file]
         | 
| 115 | 
            +
              else
         | 
| 116 | 
            +
                $args[:log_file] = File.join($prefix, 'var', 'log', 'jobs', "#{$args[:program_name]}.#{$args[:index]}.log")
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
            end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            #
         | 
| 121 | 
            +
            # Create PID file for this jobs instance
         | 
| 122 | 
            +
            #
         | 
| 123 | 
            +
            if  OS.mac?
         | 
| 124 | 
            +
              Dir.mkdir("#{$prefix}/var/run/jobs") unless Dir.exist? "#{$prefix}/var/run/jobs"
         | 
| 125 | 
            +
            end
         | 
| 126 | 
            +
            File.write("#{$prefix}/var/run/jobs/#{$args[:program_name]}#{$args[:index].nil? ? '' : '.' + $args[:index]}.pid", Process.pid)
         | 
| 127 | 
            +
             | 
| 51 128 | 
             
            #
         | 
| 52 129 | 
             
            # Read configuration
         | 
| 53 130 | 
             
            #
         | 
| 54 131 | 
             
            $config = JSON.parse(File.read(File.expand_path($args[:config_file])), symbolize_names: true)
         | 
| 132 | 
            +
            $min_progress = $config[:options][:min_progress]
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            #
         | 
| 135 | 
            +
            # Global data for mutex and sync
         | 
| 136 | 
            +
            #
         | 
| 137 | 
            +
            $threads = [ Thread.current ]
         | 
| 138 | 
            +
            $thread_data = {}
         | 
| 139 | 
            +
            $thread_data[Thread.current] = ::SP::Job::ThreadData.new
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            #
         | 
| 142 | 
            +
            # Sanity check we only support multithreading on JRUBY
         | 
| 143 | 
            +
            #
         | 
| 144 | 
            +
            if $config[:options] && $config[:options][:threads].to_i > 1
         | 
| 145 | 
            +
              raise 'Multithreading is not supported in MRI/CRuby' unless RUBY_ENGINE == 'jruby'
         | 
| 146 | 
            +
              $redis_mutex = Mutex.new
         | 
| 147 | 
            +
              $roolbar_mutex = Mutex.new
         | 
| 148 | 
            +
              $multithreading = true
         | 
| 149 | 
            +
            else
         | 
| 150 | 
            +
              $redis_mutex = nil
         | 
| 151 | 
            +
              $roolbar_mutex = ::SP::Job::FauxMutex.new
         | 
| 152 | 
            +
              $multithreading = false
         | 
| 153 | 
            +
            end
         | 
| 55 154 |  | 
| 56 155 | 
             
            #
         | 
| 57 156 | 
             
            # Configure rollbar
         | 
| @@ -69,25 +168,50 @@ end | |
| 69 168 | 
             
            #
         | 
| 70 169 | 
             
            Backburner.configure do |config|
         | 
| 71 170 |  | 
| 72 | 
            -
              config.beanstalk_url | 
| 73 | 
            -
              config.on_error | 
| 74 | 
            -
                 | 
| 75 | 
            -
             | 
| 76 | 
            -
                   | 
| 171 | 
            +
              config.beanstalk_url = "beanstalk://#{$config[:beanstalkd][:host]}:#{$config[:beanstalkd][:port]}"
         | 
| 172 | 
            +
              config.on_error      = lambda { |e|
         | 
| 173 | 
            +
                td = thread_data
         | 
| 174 | 
            +
                if td.exception_reported == false
         | 
| 175 | 
            +
                  td.exception_reported = true
         | 
| 176 | 
            +
                  if e.instance_of? Beaneater::DeadlineSoonError
         | 
| 177 | 
            +
                    logger.warn "got a deadline warning".red
         | 
| 178 | 
            +
                  else
         | 
| 179 | 
            +
                    begin
         | 
| 180 | 
            +
                      raise_error(message: e)
         | 
| 181 | 
            +
                    rescue => e
         | 
| 182 | 
            +
                      # Do not retrow!!!!
         | 
| 183 | 
            +
                    end
         | 
| 184 | 
            +
                  end
         | 
| 77 185 | 
             
                end
         | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 186 | 
            +
             | 
| 187 | 
            +
                # Report exception to rollbar
         | 
| 188 | 
            +
                $roolbar_mutex.synchronize {
         | 
| 189 | 
            +
                  if $rollbar
         | 
| 190 | 
            +
                    if e.instance_of? ::SP::Job::JobException
         | 
| 191 | 
            +
                      e.job[:password] = '<redacted>'
         | 
| 192 | 
            +
                      Rollbar.error(e, e.message, { job: e.job, args: e.args})
         | 
| 193 | 
            +
                    else
         | 
| 194 | 
            +
                      Rollbar.error(e)
         | 
| 195 | 
            +
                    end
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                }
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                # Catch fatal exception that must be handled with a restarts (systemctl will restart us)
         | 
| 200 | 
            +
                case e
         | 
| 201 | 
            +
                when PG::UnableToSend, PG::AdminShutdown, PG::ConnectionBad
         | 
| 202 | 
            +
                  logger.fatal "Lost connection to database exiting now"
         | 
| 203 | 
            +
                  exit
         | 
| 204 | 
            +
                when Redis::CannotConnectError
         | 
| 205 | 
            +
                  logger.fatal "Can't connect to redis exiting now"
         | 
| 206 | 
            +
                  exit
         | 
| 80 207 | 
             
                end
         | 
| 81 | 
            -
                catch_fatal_exceptions(e)
         | 
| 82 208 | 
             
              }
         | 
| 83 | 
            -
               | 
| 84 | 
            -
               | 
| 85 | 
            -
              #config.retry_delay         = 5 # default 5 seconds
         | 
| 86 | 
            -
              #config.default_priority    = 65536
         | 
| 209 | 
            +
              config.max_job_retries  = ($config[:options] && $config[:options][:max_job_retries]) ? $config[:options][:max_job_retries] : 0
         | 
| 210 | 
            +
              config.retry_delay      = ($config[:options] && $config[:options][:retry_delay])     ? $config[:options][:retry_delay]     : 5
         | 
| 87 211 | 
             
              config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
         | 
| 88 212 | 
             
              config.respond_timeout  = 120
         | 
| 89 | 
            -
              config.default_worker   = SP::Job::Worker
         | 
| 90 | 
            -
              config.logger           = $args[:debug] ? Logger.new(STDOUT) : Logger.new($args[:log_file])
         | 
| 213 | 
            +
              config.default_worker   = $config[:options] && $config[:options][:threads].to_i > 1 ? SP::Job::WorkerThread : SP::Job::Worker
         | 
| 214 | 
            +
              config.logger           = $args[:debug] ? SP::Job::Logger.new(STDOUT) : SP::Job::Logger.new($args[:log_file])
         | 
| 91 215 | 
             
              config.logger.formatter = proc do |severity, datetime, progname, msg|
         | 
| 92 216 | 
             
                date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
         | 
| 93 217 | 
             
                "[#{date_format}] #{severity}: #{msg}\n"
         | 
| @@ -136,8 +260,42 @@ if $config[:mail] | |
| 136 260 | 
             
            end
         | 
| 137 261 |  | 
| 138 262 | 
             
            #
         | 
| 139 | 
            -
            # Monkey patches  | 
| 263 | 
            +
            # Monkey patches the sad reality of ruby development
         | 
| 140 264 | 
             
            #
         | 
| 265 | 
            +
            if RUBY_ENGINE == 'jruby'
         | 
| 266 | 
            +
             | 
| 267 | 
            +
              class Logger
         | 
| 268 | 
            +
                class LogDevice
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                  def write(message)
         | 
| 271 | 
            +
                    begin
         | 
| 272 | 
            +
                      synchronize do
         | 
| 273 | 
            +
                        if @shift_age and @dev.respond_to?(:stat)
         | 
| 274 | 
            +
                          begin
         | 
| 275 | 
            +
                            check_shift_log
         | 
| 276 | 
            +
                          rescue
         | 
| 277 | 
            +
                            warn("log shifting failed. #{$!}")
         | 
| 278 | 
            +
                          end
         | 
| 279 | 
            +
                        end
         | 
| 280 | 
            +
                        begin
         | 
| 281 | 
            +
                          @dev.write(message)
         | 
| 282 | 
            +
                        rescue ::SP::Job::JobCancelled => jc
         | 
| 283 | 
            +
                          raise jc
         | 
| 284 | 
            +
                        rescue
         | 
| 285 | 
            +
                          warn("log writing failed. #{$!}")
         | 
| 286 | 
            +
                        end
         | 
| 287 | 
            +
                      end
         | 
| 288 | 
            +
                    rescue ::SP::Job::JobCancelled => jc
         | 
| 289 | 
            +
                      raise jc
         | 
| 290 | 
            +
                    rescue Exception => ignored
         | 
| 291 | 
            +
                      warn("log writing failed. #{ignored}")
         | 
| 292 | 
            +
                    end
         | 
| 293 | 
            +
                  end
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                end
         | 
| 296 | 
            +
              end
         | 
| 297 | 
            +
            end
         | 
| 298 | 
            +
             | 
| 141 299 | 
             
            module Backburner
         | 
| 142 300 | 
             
              module Helpers
         | 
| 143 301 |  | 
| @@ -148,19 +306,49 @@ module Backburner | |
| 148 306 | 
             
              end
         | 
| 149 307 |  | 
| 150 308 | 
             
              module Logger
         | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 309 | 
            +
             | 
| 310 | 
            +
                if RUBY_ENGINE != 'jruby'
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                  def log_job_begin(name, args)
         | 
| 313 | 
            +
                    log_info "Work job #{name}"
         | 
| 314 | 
            +
                    @job_started_at = Time.now
         | 
| 315 | 
            +
                  end
         | 
| 316 | 
            +
             | 
| 317 | 
            +
                else
         | 
| 318 | 
            +
             | 
| 319 | 
            +
                  def log_job_begin(name, args)
         | 
| 320 | 
            +
                    log_info "Work job #{name}"
         | 
| 321 | 
            +
                    Thread.current[:job_started_at] = Time.now
         | 
| 322 | 
            +
                  end
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                  # Print out when a job completed
         | 
| 325 | 
            +
                  # If message is nil, job is considered complete
         | 
| 326 | 
            +
                  def log_job_end(name, message = nil)
         | 
| 327 | 
            +
                    ellapsed = Time.now - Thread.current[:job_started_at]
         | 
| 328 | 
            +
                    ms = (ellapsed.to_f * 1000).to_i
         | 
| 329 | 
            +
                    action_word = message ? 'Finished' : 'Completed'
         | 
| 330 | 
            +
                    log_info("#{action_word} #{name} in #{ms}ms #{message}")
         | 
| 331 | 
            +
                  end
         | 
| 332 | 
            +
             | 
| 154 333 | 
             
                end
         | 
| 155 334 | 
             
              end
         | 
| 156 335 |  | 
| 157 336 | 
             
              class Job
         | 
| 337 | 
            +
                extend SP::Job::Common # to bring in logger and report_error into this class
         | 
| 338 | 
            +
             | 
| 158 339 | 
             
                # Processes a job and handles any failure, deleting the job once complete
         | 
| 159 340 | 
             
                #
         | 
| 160 341 | 
             
                # @example
         | 
| 161 342 | 
             
                #   @task.process
         | 
| 162 343 | 
             
                #
         | 
| 163 344 | 
             
                def process
         | 
| 345 | 
            +
                  # Invoke the job setup function, bailout if the setup returns false
         | 
| 346 | 
            +
                  unless job_class.respond_to?(:prepare_job) && job_class.prepare_job(*args)
         | 
| 347 | 
            +
                    task.delete
         | 
| 348 | 
            +
                    logger.warn "Delete stale or preempted task".red
         | 
| 349 | 
            +
                    return false
         | 
| 350 | 
            +
                  end
         | 
| 351 | 
            +
             | 
| 164 352 | 
             
                  # Invoke before hook and stop if false
         | 
| 165 353 | 
             
                  res = @hooks.invoke_hook_events(job_class, :before_perform, *args)
         | 
| 166 354 | 
             
                  unless res
         | 
| @@ -179,6 +367,41 @@ module Backburner | |
| 179 367 | 
             
                  task.delete
         | 
| 180 368 | 
             
                  # Invoke after perform hook
         | 
| 181 369 | 
             
                  @hooks.invoke_hook_events(job_class, :after_perform, *args)
         | 
| 370 | 
            +
                rescue ::SP::Job::JobAborted => ja
         | 
| 371 | 
            +
                  #
         | 
| 372 | 
            +
                  # This exception:
         | 
| 373 | 
            +
                  #  1. is sent to the rollbar
         | 
| 374 | 
            +
                  #  2. does not bury the job, instead the job is deleted
         | 
| 375 | 
            +
                  #
         | 
| 376 | 
            +
                  logger.debug "Received job aborted exception #{Thread.current}".yellow
         | 
| 377 | 
            +
                  unless task.nil?
         | 
| 378 | 
            +
                    logger.debug 'Task deleted'.yellow
         | 
| 379 | 
            +
                    task.delete
         | 
| 380 | 
            +
                  end
         | 
| 381 | 
            +
                  # Invoke after perform hook
         | 
| 382 | 
            +
                  @hooks.invoke_hook_events(job_class, :after_perform, *args)
         | 
| 383 | 
            +
                  thread_data.job_id = nil
         | 
| 384 | 
            +
                rescue ::SP::Job::JobCancelled => jc
         | 
| 385 | 
            +
                  #
         | 
| 386 | 
            +
                  # This exception:
         | 
| 387 | 
            +
                  #  1. is not sent to the rollbar
         | 
| 388 | 
            +
                  #  2. does not bury the job, instead the job is deleted
         | 
| 389 | 
            +
                  #
         | 
| 390 | 
            +
                  logger.debug "Received job cancellation exception #{Thread.current}".yellow
         | 
| 391 | 
            +
                  unless task.nil?
         | 
| 392 | 
            +
                    logger.debug 'Task deleted'.yellow
         | 
| 393 | 
            +
                    task.delete
         | 
| 394 | 
            +
                  end
         | 
| 395 | 
            +
                  @hooks.invoke_hook_events(job_class, :on_failure, jc, *args)
         | 
| 396 | 
            +
                  report_error(message: 'i18n_job_cancelled', status: 'cancelled')
         | 
| 397 | 
            +
                  if $redis_mutex.nil?
         | 
| 398 | 
            +
                    $redis.hset(thread_data.job_key, 'cancelled', true)
         | 
| 399 | 
            +
                  else
         | 
| 400 | 
            +
                    $redis_mutex.synchronize {
         | 
| 401 | 
            +
                      $redis.hset(thread_data.job_key, 'cancelled', true)
         | 
| 402 | 
            +
                    }
         | 
| 403 | 
            +
                  end
         | 
| 404 | 
            +
                  thread_data.job_id = nil
         | 
| 182 405 | 
             
                rescue => e
         | 
| 183 406 | 
             
                  @hooks.invoke_hook_events(job_class, :on_failure, e, *args)
         | 
| 184 407 | 
             
                  raise e
         | 
| @@ -186,20 +409,56 @@ module Backburner | |
| 186 409 | 
             
              end
         | 
| 187 410 | 
             
            end
         | 
| 188 411 |  | 
| 189 | 
            -
            # Mix-in the mix-in  | 
| 190 | 
            -
            require 'sp/job/common'
         | 
| 412 | 
            +
            # Mix-in the common mix-in to make code available for the lambdas used in this file
         | 
| 191 413 | 
             
            extend SP::Job::Common
         | 
| 192 414 |  | 
| 415 | 
            +
            logger.debug "Log file ... #{$args[:log_file]}"
         | 
| 416 | 
            +
            logger.debug "PID ........ #{Process.pid}"
         | 
| 417 | 
            +
             | 
| 193 418 | 
             
            #
         | 
| 194 419 | 
             
            # Now create the global data needed by the mix-in methods
         | 
| 195 420 | 
             
            #
         | 
| 196 | 
            -
            $connected | 
| 197 | 
            -
            $ | 
| 198 | 
            -
            $ | 
| 199 | 
            -
            $ | 
| 200 | 
            -
            $beaneater          = Beaneater.new "#{$config[:beanstalkd][:host]}:#{$config[:beanstalkd][:port]}"
         | 
| 201 | 
            -
            $check_db_life_span = false
         | 
| 202 | 
            -
            $status_dirty       = false
         | 
| 421 | 
            +
            $connected     = false
         | 
| 422 | 
            +
            $redis         = Redis.new(:host => $config[:redis][:host], :port => $config[:redis][:port], :db => 0)
         | 
| 423 | 
            +
            $transient_job = $config[:options] && $config[:options][:transient] == true
         | 
| 424 | 
            +
            $beaneater     = Beaneater.new "#{$config[:beanstalkd][:host]}:#{$config[:beanstalkd][:port]}"
         | 
| 203 425 | 
             
            if $config[:postgres] && $config[:postgres][:conn_str]
         | 
| 204 | 
            -
              $pg = ::SP::Job::PGConnection.new(owner:  | 
| 426 | 
            +
              $pg = ::SP::Job::PGConnection.new(owner: $PROGRAM_NAME, config: $config[:postgres], multithreaded: $multithreading)
         | 
| 427 | 
            +
              if $PROGRAM_NAME.split('/').last == 'saft-importer' || $PROGRAM_NAME.split('/').last == 'saft-destroyer'
         | 
| 428 | 
            +
                $pg.exec("SET log_min_duration_statement TO 0;")
         | 
| 429 | 
            +
              end
         | 
| 430 | 
            +
              if $config[:options][:jsonapi] == true
         | 
| 431 | 
            +
                $jsonapi = SP::Duh::JSONAPI::Service.new($pg, ($jsonapi.nil? ? nil : $jsonapi.url), SP::Job::JobDbAdapter)
         | 
| 432 | 
            +
              end
         | 
| 205 433 | 
             
            end
         | 
| 434 | 
            +
             | 
| 435 | 
            +
            #
         | 
| 436 | 
            +
            # Open a second thread that will listen to cancellation and other "signals"
         | 
| 437 | 
            +
            #
         | 
| 438 | 
            +
            $cancel_thread = Thread.new {
         | 
| 439 | 
            +
              begin
         | 
| 440 | 
            +
                $subscription_redis = Redis.new(:host => $config[:redis][:host], :port => $config[:redis][:port], :db => 0)
         | 
| 441 | 
            +
                $subscription_redis.subscribe($config[:service_id] + ':job-signal') do |on|
         | 
| 442 | 
            +
                  on.message do |channel, msg|
         | 
| 443 | 
            +
                    begin
         | 
| 444 | 
            +
                      message = JSON.parse(msg, {symbolize_names: true})
         | 
| 445 | 
            +
                      $threads.each do |thread|
         | 
| 446 | 
            +
                        if $thread_data[thread].job_id != nil && message[:id].to_s == $thread_data[thread].job_id && message[:status] == 'cancelled'
         | 
| 447 | 
            +
                          logger.info "Received cancel signal for job #{$thread_data[thread].job_id}"
         | 
| 448 | 
            +
                          thread.raise(::SP::Job::JobCancelled.new)
         | 
| 449 | 
            +
                        end
         | 
| 450 | 
            +
                      end
         | 
| 451 | 
            +
                    rescue Exception => e
         | 
| 452 | 
            +
                      # ignore invalid payloads
         | 
| 453 | 
            +
                    end
         | 
| 454 | 
            +
                  end
         | 
| 455 | 
            +
                end
         | 
| 456 | 
            +
              rescue Redis::CannotConnectError => ccc
         | 
| 457 | 
            +
                logger.fatal "Can't connect to redis exiting now".red
         | 
| 458 | 
            +
                exit
         | 
| 459 | 
            +
              rescue Exception => e
         | 
| 460 | 
            +
                # Forward unexpected exceptions to the main thread for proper handling
         | 
| 461 | 
            +
                logger.fatal e.to_s.red
         | 
| 462 | 
            +
                Thread.main.raise(e)
         | 
| 463 | 
            +
              end
         | 
| 464 | 
            +
            }
         |