Module: Gouda::Scheduler

Defined in:
lib/gouda/scheduler.rb

Defined Under Namespace

Classes: Entry

Class Method Summary collapse

Class Method Details

.build_scheduler_entries_list!(cron_table_hash = nil) ⇒ Object

Takes in a Hash formatted with cron entries in the format similar to good_job, and builds a table of scheduler entries. A scheduler entry references a particular job class name, the set of arguments to be passed to the job when performing it, and either the interval to repeat the job after or a cron pattern. This method does not insert the actual Workloads into the database but just builds the table of the entries. That table gets consulted when workloads finish to determine whether the workload that just ran was scheduled or ad-hoc, and whether the subsequent workload has to be enqueued.

If no table is given the method will attempt to read the table from Rails application config from ‘[:gouda]`.

The table is a Hash of entries, and the keys are the names of the workload to be enqueued - those keys are also used to ensure scheduled workloads only get scheduled once.

Parameters:

  • cron_table_hash (Hash) (defaults to: nil)

    a hash of the following shape: {

    download_invoices_every_minute: {
      cron: "* * * * *",
      class: "DownloadInvoicesJob",
      args: ["immediate"]
    }
    

    }



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/gouda/scheduler.rb', line 88

def self.build_scheduler_entries_list!(cron_table_hash = nil)
  Gouda.logger.info "Updating scheduled workload entries..."
  if cron_table_hash.nil? # An empty hash indicates that an empty crontab will be loaded
    config_from_rails = Rails.application.config.try(:gouda)

    cron_table_hash = if config_from_rails.present?
      config_from_rails.dig(:cron).to_h if config_from_rails.dig(:enable_cron)
    elsif Gouda.config.enable_cron
      Gouda.config.cron
    end

    return unless cron_table_hash
  end

  defaults = {cron: nil, interval_seconds: nil, kwargs: nil, args: nil}
  @cron_table = cron_table_hash.map do |(name, cron_entry_params)|
    # `class` is a reserved keyword and a method that exists on every Ruby object so...
    cron_entry_params[:job_class] ||= cron_entry_params.delete(:class)
    params_with_defaults = defaults.merge(cron_entry_params)
    Entry.new(name: name, **params_with_defaults)
  end
  @known_scheduler_keys = Set.new(@cron_table.map(&:scheduler_key))

  @cron_table
end

.enqueue_next_scheduled_workload_for(finished_workload) ⇒ Object

Once a workload has finished (doesn’t matter whether it raised an exception or completed successfully), it is going to be passed to this method to enqueue the next scheduled workload

Parameters:

Returns:

  • void



120
121
122
123
124
125
126
127
128
# File 'lib/gouda/scheduler.rb', line 120

def self.enqueue_next_scheduled_workload_for(finished_workload)
  return unless finished_workload.scheduler_key

  timer_table = @cron_table.to_a.index_by(&:scheduler_key)
  timer_entry = timer_table[finished_workload.scheduler_key]
  return unless timer_entry

  Gouda.enqueue_jobs_via_their_adapters([timer_entry.build_active_job])
end

.entriesObject

Returns the list of entries of the scheduler which are currently known. Normally the scheduler will hold the list of entries loaded from the Rails config.



134
135
136
# File 'lib/gouda/scheduler.rb', line 134

def self.entries
  @cron_table || []
end

.known_scheduler_keysObject

Returns the set of known scheduler keys that may be present in the workloads table and are defined by the current entries.



142
143
144
# File 'lib/gouda/scheduler.rb', line 142

def self.known_scheduler_keys
  @known_scheduler_keys || Set.new
end

.upsert_workloads_from_entries_list!Object

Will upsert (‘INSERT … ON CONFLICT UPDATE`) workloads for all entries which are in the scheduler entries table (the table needs to be read or hydrated first using `build_scheduler_entries_list!`). This is done in a transaction. Any workloads which have been previously inserted from the scheduled entries, but no longer have a corresponding scheduler entry, will be deleted from the database. If there already are workloads with the corresponding scheduler key they will not be touched and will be performed with their previously-defined arguments.

Returns:

  • void



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/gouda/scheduler.rb', line 154

def self.upsert_workloads_from_entries_list!
  table_entries = @cron_table || []

  # Remove any cron keyed workloads which no longer match config-wise.
  # We do this to keep things clean (but it is not enough, an extra guard is needed in Workload checkout)
  known_keys = table_entries.map(&:scheduler_key).uniq
  Gouda::Workload.transaction do
    # We do this to keep things a bit clean
    Gouda::Workload.where.not(scheduler_key: known_keys).delete_all

    # Insert the next iteration for every "next" entry in the crontab.
    active_jobs_to_enqueue = table_entries.filter_map(&:build_active_job)
    Gouda.logger.info "#{active_jobs_to_enqueue.size} job(s) to enqueue from the scheduler."
    enqjobs = Gouda.enqueue_jobs_via_their_adapters(active_jobs_to_enqueue)
    Gouda.logger.info "#{enqjobs.size} scheduled job(s) enqueued."
  end
end