Module: Scheduled

Defined in:
lib/scheduled.rb,
lib/scheduled/version.rb,
lib/scheduled/cron_parser.rb,
lib/scheduled/instrumenters.rb

Overview

Schedule jobs to run at specific intervals.

Defined Under Namespace

Modules: Instrumenters Classes: Context, CronParser, Job

Constant Summary collapse

VERSION =
"0.2.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.error_notifierObject

An object that responds to call and receives an exception as an argument.



53
54
55
# File 'lib/scheduled.rb', line 53

def error_notifier
  @error_notifier
end

.instrumenter#instrument

Returns an ActiveSupport::Notifications like object which responds to instrument.

Returns:

  • (#instrument)

    an ActiveSupport::Notifications like object which responds to instrument



50
51
52
# File 'lib/scheduled.rb', line 50

def instrumenter
  @instrumenter
end

.logger#info, #debug

Returns a Logger like instance which responds to info and debug.

Returns:

  • (#info, #debug)

    a Logger like instance which responds to info and debug



46
47
48
# File 'lib/scheduled.rb', line 46

def logger
  @logger
end

.task_logger#call(original_logger, task_name)

An object that when called creates a logger for the provided task

Examples:

Scheduled.task_logger = ->(original_logger, task_name) {
  logger = original_logger.dup
  logger.progname = task_name
  logger
}

Returns:

  • (#call(original_logger, task_name))

    a callable object that returns a a Logger like instance which responds to info and debug



42
43
44
# File 'lib/scheduled.rb', line 42

def task_logger
  @task_logger
end

Class Method Details

.every(interval, name: nil, &block) ⇒ void

This method returns an undefined value.

Create task to run every interval.

Examples:

Run every 60 seconds

Scheduled.every(60) { puts "Running every 60 seconds" }

Run every day at 9:10 AM

Scheduled.every("10 9 * * *") { puts "Performing billing" }

Parameters:

  • interval (Integer, String, #call)

    Interval to perform task.

    When provided as an Integer, is the number of seconds between task runs.

    When provided as a String, is a cron-formatted interval line.

    When provided as an object that responds to #call, will run when truthy.

  • name (String, false) (defaults to: nil)

    Name of task, used during logging. Will use block location and line number by default. Use false to prevent a name being automatically assigned.



78
79
80
81
82
83
84
85
86
87
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/scheduled.rb', line 78

def every(interval, name: nil, &block)
  name ||= block_name(block)
  logger = logger_for_task(name, block)
  context = Context.new(logger)

  rescued_block = ->() do
    instrumenter.instrument("scheduled.run", {name: name}) do |payload|
      begin
        result = context.instance_eval(&block)
        payload[:result] = result
        result
      rescue => e
        payload[:exception] = [e.class.to_s, e.message]
        payload[:exception_object] = e

        if error_notifier
          error_notifier.call(e)
        end
      end
    end
  end

  if interval.is_a?(Integer)
    logger.debug { "Running every #{interval} seconds" }

    task = Concurrent::TimerTask.new(execution_interval: interval, run_now: true) do
      rescued_block.call
    end

    task.execute

  elsif interval.is_a?(String)
    run = ->() {
      now = Time.now
      parsed_cron = CronParser.new(interval)
      next_tick_delay = [1, (parsed_cron.next(now) - now).ceil].max

      logger.debug { "Next run at #{now + next_tick_delay} (tick delay of #{next_tick_delay})" }

      task = Concurrent::ScheduledTask.execute(next_tick_delay) do
        rescued_block.call
        run.call
      end

      task.execute
    }

    run.call

  elsif interval.respond_to?(:call)
    job = Job.new

    task = Concurrent::TimerTask.new(execution_interval: 1, run_now: true) do |timer_task|
      case interval.call(job)
      when true
        rescued_block.call

        job.last_run = Time.now
      when :cancel
        logger.debug { "Received :cancel. Shutting down." }
        timer_task.shutdown
      end
    end

    task.execute
  else
    raise ArgumentError, "Unsupported value for interval"
  end
end

.waitvoid

This method returns an undefined value.

Run task scheduler indefinitely.



151
152
153
154
155
156
157
# File 'lib/scheduled.rb', line 151

def wait
  trap("INT") { exit }

  loop do
    sleep 1
  end
end