Class: Revenant::Task

Inherits:
Object
  • Object
show all
Defined in:
lib/revenant/task.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = nil) ⇒ Task

Returns a new instance of Task.



10
11
12
13
14
15
16
# File 'lib/revenant/task.rb', line 10

def initialize(name = nil)
  unless String === name || Symbol === name
    raise ArgumentError, "Usage: new(task_name)"
  end
  @name = name.to_sym
  @options = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object

Used to lazily store/retrieve options that may be needed by plugins. We may want to capture, say, log_file before actually loading the code that might care about such a concept.



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/revenant/task.rb', line 170

def method_missing(name, *args)
  name = name.to_s
  last_char = name[-1,1]
  super(name, *args) unless last_char == "=" || last_char == "?"
  attr_name = name[0..-2].to_sym # :foo for 'foo=' or 'foo?'
  if last_char == "="
    @options[attr_name] = args.at(0)
  else
    @options[attr_name]
  end
end

Instance Attribute Details

#loggerObject



190
191
192
# File 'lib/revenant/task.rb', line 190

def logger
  @logger ||= STDERR
end

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/revenant/task.rb', line 6

def name
  @name
end

#optionsObject

Returns the value of attribute options.



7
8
9
# File 'lib/revenant/task.rb', line 7

def options
  @options
end

Instance Method Details

#error(message) ⇒ Object



186
187
188
# File 'lib/revenant/task.rb', line 186

def error(message)
  logger.puts "[#{$$}] #{Time.now.iso8601(2)} - ERROR: #{message}"
end

#install_pluginsObject

Install any plugins that have registered themselves, or a custom list if the user has set it themselves.



196
197
198
199
200
# File 'lib/revenant/task.rb', line 196

def install_plugins
  ::Revenant.plugins.each do |name, plugin|
    plugin.install(self)
  end
end

#lock_function(&block) ⇒ Object

Set your own lock function. Will be called with a lock name as the arg. Should return true if a lock has been acquired, false otherwise. task.lock_function {|name| # .. }



91
92
93
94
95
96
97
# File 'lib/revenant/task.rb', line 91

def lock_function(&block)
  if block_given?
    @lock_function = block
  else
    @lock_function ||= lock_module.lock_function
  end
end

#lock_moduleObject

Returns a module that knows how to do some distributed locking. May not be the code that actually performs the lock, if this Task has had a lock_function assigned to it explicitly.



102
103
104
# File 'lib/revenant/task.rb', line 102

def lock_module
  ::Revenant.find_module(lock_type)
end

#lock_typeObject

Used to pick the Task’s lock_module Particular lock types may offer various helpful features via this lock module. Defaults to :mysql



79
80
81
# File 'lib/revenant/task.rb', line 79

def lock_type
  @lock_type ||= :mysql
end

#lock_type=(val) ⇒ Object

Set a new lock type for this Task.



84
85
86
# File 'lib/revenant/task.rb', line 84

def lock_type=(val)
  @lock_type = val.to_sym
end

#log(message) ⇒ Object



182
183
184
# File 'lib/revenant/task.rb', line 182

def log(message)
  logger.puts "[#{$$}] #{Time.now.iso8601(2)} - #{message}"
end

#on_exit(&block) ⇒ Object

Code to run when the task is exiting.



71
72
73
# File 'lib/revenant/task.rb', line 71

def on_exit(&block)
  @on_exit ||= block
end

#on_load(&block) ⇒ Object

Code to run just before the task looks for a lock This code runs after any necessary forks, and is therefore the proper place to open databases, logfiles, and any other resources you require.



66
67
68
# File 'lib/revenant/task.rb', line 66

def on_load(&block)
  @on_load ||= block
end

#relock_everyObject

How many work loops to perform before re-acquiring the lock. Defaults to 5. Setting it to 0 or nil will assume the lock is forever valid after acquisition.



110
111
112
# File 'lib/revenant/task.rb', line 110

def relock_every
  @relock_every ||= 5
end

#relock_every=(loops) ⇒ Object

Set the frequency with which locks are re-acquired. Setting it to 0 or nil will assume the lock is forever valid after acquisition.



117
118
119
120
121
122
123
124
# File 'lib/revenant/task.rb', line 117

def relock_every=(loops)
  loops ||= 0
  if Integer === loops && loops >= 0
    @relock_every = loops
  else
    raise ArgumentError, "argument must be nil or an integer >= 0"
  end
end

#restart_pending?Boolean

At last, back to war.

Returns:

  • (Boolean)


149
150
151
# File 'lib/revenant/task.rb', line 149

def restart_pending?
  @restart ||= false
end

#restart_soonObject

Task will restart at the earliest safe opportunity after restart_soon is called.



155
156
157
158
# File 'lib/revenant/task.rb', line 155

def restart_soon
  @restart = true
  @shutdown = true
end

#run(&block) ⇒ Object

Takes actual block of code that is to be guarded by the lock. The run_loop method does the actual work.

If ‘daemon?’ is true, your code (including on_load) will execute after a fork.

Make sure you don’t open files and sockets in the exiting parent process by mistake. Open them in code that is called via on_load.



48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/revenant/task.rb', line 48

def run(&block)
  unless @work = block
    raise ArgumentError, "Usage: run { while_we_have_the_lock }"
  end
  @shutdown = false
  @restart = false
  install_plugins
  startup # typically daemonizes the process, can have various implementations
  on_load.call(self) if on_load
  run_loop(&@work)
  on_exit.call(self) if on_exit
  shutdown
end

#run_loop(&block) ⇒ Object

Run until we receive a shutdown/reload signal, or when the worker raises an Interrupt. Runs after a fork when Revenant::Daemon is enabled.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/revenant/task.rb', line 205

def run_loop(&block)
  acquired = false
  begin
    until shutdown_pending?
      # The usual situation
      if relock_every != 0
        i ||= 0
        # 0 % anything is 0, so we always try to get the lock on the first loop.
        if (i %= relock_every) == 0
          acquired = lock_function.call(@name)
        end
      else
        # With relock_every set to 0, only acquire the lock once.
        # Hope you're sure that lock beongs to you.
        acquired ||= lock_function.call(@name)
        i = 0 # no point in incrementing something we don't check.
      end

      yield if acquired

      # Sleep one second at a time so we can quickly respond to
      # shutdown requests.
      sleep_for.times do
        sleep(1) unless shutdown_pending?
      end
      i += 1
    end # loop
  rescue ::Interrupt => ex
    log "shutting down after interrupt: #{ex.class} - #{ex.message}"
    shutdown_soon # Always shut down from an Interrupt, even mid-restart.
    return
  rescue ::Exception => ex
    error "restarting after error: #{ex.class} - #{ex.message}"
    error "backtrace: #{ex.backtrace.join("\n")}"
    restart_soon # Restart if we run into an exception.
  end # begin block
end

#shutdownObject

Generally overridden when Revenant::Daemon is included The stack gets deeper here on every restart; this is here largely to ease testing. Implement your own plugin providing shutdown if you want to make something serious that calls this code after a restart signal.



30
31
32
33
34
35
36
37
# File 'lib/revenant/task.rb', line 30

def shutdown
  if restart_pending? && @work
    log "#{name} is restarting"
    run(&@work)
  else
    log "#{name} is shutting down"
  end
end

#shutdown_pending?Boolean

This could be the moment.

Returns:

  • (Boolean)


144
145
146
# File 'lib/revenant/task.rb', line 144

def shutdown_pending?
  @shutdown ||= false
end

#shutdown_soonObject

Task will shut down at the earliest safe opportunity after shutdown_soon is called.



162
163
164
165
# File 'lib/revenant/task.rb', line 162

def shutdown_soon
  @restart = false
  @shutdown = true
end

#sleep_forObject

How many seconds to sleep after each work loop. When we don’t have the lock, how long to sleep before checking again. Default is 5 seconds.



129
130
131
# File 'lib/revenant/task.rb', line 129

def sleep_for
  @sleep_for ||= 5
end

#sleep_for=(seconds) ⇒ Object

Set the number of seconds to sleep for after a work loop.



134
135
136
137
138
139
140
141
# File 'lib/revenant/task.rb', line 134

def sleep_for=(seconds)
  seconds ||= 0
  if Integer === seconds && seconds >= 0
    @sleep_for = seconds
  else
    raise ArgumentError, "argument must be nil or an integer >= 0"
  end
end

#startupObject

Generally overridden when Revenant::Daemon is included



19
20
21
22
# File 'lib/revenant/task.rb', line 19

def startup
  log "#{name} is starting"
  trap("INT") { shutdown_soon }
end