background_lite 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +79 -0
- data/init.rb +13 -0
- data/lib/background.rb +100 -0
- data/lib/core_ext/class.rb +113 -0
- data/lib/core_ext/error_reporters/exception_notification_error_reporter.rb +47 -0
- data/lib/core_ext/error_reporters/silent_error_reporter.rb +9 -0
- data/lib/core_ext/error_reporters/stderr_error_reporter.rb +9 -0
- data/lib/core_ext/error_reporters/stdout_error_reporter.rb +10 -0
- data/lib/core_ext/error_reporters/test_error_reporter.rb +11 -0
- data/lib/core_ext/handlers/active_messaging_handler.rb +65 -0
- data/lib/core_ext/handlers/disk_handler.rb +32 -0
- data/lib/core_ext/handlers/drb_handler.rb +51 -0
- data/lib/core_ext/handlers/forget_handler.rb +10 -0
- data/lib/core_ext/handlers/fork_handler.rb +11 -0
- data/lib/core_ext/handlers/in_process_handler.rb +10 -0
- data/lib/core_ext/handlers/resque_handler.rb +45 -0
- data/lib/core_ext/handlers/runner_handler.rb +40 -0
- data/lib/core_ext/handlers/test_handler.rb +35 -0
- data/lib/core_ext/nil_class.rb +5 -0
- data/lib/core_ext/numeric.rb +5 -0
- data/lib/core_ext/object.rb +7 -0
- data/lib/core_ext/symbol.rb +5 -0
- data/lib/rails_ext/activerecord/base.rb +31 -0
- data/rails/init.rb +1 -0
- metadata +85 -0
data/README.rdoc
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
=== BackgroundLite
|
2
|
+
|
3
|
+
This Rails plugin supersedes the background plugin, available here:
|
4
|
+
|
5
|
+
http://github.com/imedo/background/tree/master
|
6
|
+
|
7
|
+
It allows you to execute methods in a background task with a very intuitive and
|
8
|
+
easy syntax. It is as easy as that:
|
9
|
+
|
10
|
+
class MyModel
|
11
|
+
def complex_operation(argument)
|
12
|
+
...
|
13
|
+
end
|
14
|
+
background_method :complex_operation
|
15
|
+
end
|
16
|
+
|
17
|
+
Now, whenever MyModel#complex_operation is called, it will be run in the
|
18
|
+
background process.
|
19
|
+
|
20
|
+
Refer to the Class#background_method documentation for details.
|
21
|
+
|
22
|
+
=== What this plugin is
|
23
|
+
|
24
|
+
This plugin is an easy-to-use interface for background processing frameworks.
|
25
|
+
Theoretically, you can use it in combination with any of the messaging /
|
26
|
+
background processing frameworks out there, though there are not many handlers
|
27
|
+
implemented yet. However, implementing a handler is very easy.
|
28
|
+
|
29
|
+
=== What this plugin is NOT
|
30
|
+
|
31
|
+
This plugin is NOT a background processing framework. To make it work
|
32
|
+
efficiently, you need an existing framework installed and configured. Right now,
|
33
|
+
support for the ActiveMessaging framework is implemented.
|
34
|
+
|
35
|
+
However, there is out-of-the-box support for using script/runner to perform
|
36
|
+
background tasks, as well as forking.
|
37
|
+
|
38
|
+
=== When to use it
|
39
|
+
|
40
|
+
* Your Rails process gets unresponsive due to some time-consuming task.
|
41
|
+
* The task does not need to show an immediate effect.
|
42
|
+
|
43
|
+
=== Features
|
44
|
+
|
45
|
+
* Background processing using multiple background processing frameworks.
|
46
|
+
* Fallback processing, if your background processing framework isn't responding
|
47
|
+
correctly.
|
48
|
+
* Works out-of-the-box with script/runner and forking.
|
49
|
+
* Fallback handler that streams messages containing background tasks to disk, to
|
50
|
+
later .
|
51
|
+
* Error reporting through different channels, depending on the task at hand.
|
52
|
+
* Exception notification.
|
53
|
+
* Stdout (useful for debugging).
|
54
|
+
* Stderr (useful for debugging).
|
55
|
+
* Silent, which swallows all exceptions.
|
56
|
+
* Supported processing frameworks.
|
57
|
+
* ActiveMessaging.
|
58
|
+
* script/runner.
|
59
|
+
* fork (works only on Unix-like environments).
|
60
|
+
* others might follow (it's really easy to write a handler).
|
61
|
+
* Easy configuration, depending on the environment, in config/background.yml.
|
62
|
+
* Ability to override the configuration per method.
|
63
|
+
|
64
|
+
=== Dependencies
|
65
|
+
|
66
|
+
There are no dependencies besides ActiveSupport, which is required by Rails
|
67
|
+
anyways.
|
68
|
+
|
69
|
+
=== Installation
|
70
|
+
|
71
|
+
Just copy the background_lite folder into vendor/plugins, and you're good to go.
|
72
|
+
|
73
|
+
=== Configuration
|
74
|
+
|
75
|
+
Depending on the background processing framework that you are using, you might
|
76
|
+
have to do some configuration. See the documentation of the respective
|
77
|
+
background handler for details.
|
78
|
+
|
79
|
+
Copyright (c) 2008-2009 imedo GmbH, released under the MIT license
|
data/init.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/background'
|
2
|
+
require File.dirname(__FILE__) + '/lib/core_ext/object'
|
3
|
+
require File.dirname(__FILE__) + '/lib/core_ext/class'
|
4
|
+
require File.dirname(__FILE__) + '/lib/core_ext/numeric'
|
5
|
+
require File.dirname(__FILE__) + '/lib/core_ext/symbol'
|
6
|
+
require File.dirname(__FILE__) + '/lib/core_ext/nil_class'
|
7
|
+
Dir.glob(File.dirname(__FILE__) + '/lib/core_ext/handlers/*.rb').each do |handler|
|
8
|
+
require handler
|
9
|
+
end
|
10
|
+
Dir.glob(File.dirname(__FILE__) + '/lib/core_ext/error_reporters/*.rb').each do |reporter|
|
11
|
+
require reporter
|
12
|
+
end
|
13
|
+
require File.dirname(__FILE__) + '/lib/rails_ext/activerecord/base'
|
data/lib/background.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# This module holds methods for background handling
|
2
|
+
module BackgroundLite
|
3
|
+
# This class is for configuring defaults of the background processing
|
4
|
+
# framework(s) you use. You can configure the frameworks either using the
|
5
|
+
# accessors in this class, or by listing them in the config/background.yml
|
6
|
+
# configuration file. See Class#background_method for details.
|
7
|
+
class Config
|
8
|
+
# Contains the default background handler that is chosen, if none is
|
9
|
+
# specified in the call to Kernel#background.
|
10
|
+
@@default_handler = [:in_process, :forget]
|
11
|
+
cattr_accessor :default_handler
|
12
|
+
|
13
|
+
# Contains the default error reporter.
|
14
|
+
@@default_error_reporter = :stdout
|
15
|
+
cattr_accessor :default_error_reporter
|
16
|
+
|
17
|
+
def self.config #:nodoc:
|
18
|
+
@config ||= YAML.load(File.read("#{RAILS_ROOT}/config/background.yml")) rescue { RAILS_ENV => {} }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.default_config #:nodoc:
|
22
|
+
@default_config ||= (config['default'] || {})
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.load(configuration) #:nodoc:
|
26
|
+
if configuration.blank?
|
27
|
+
default_config
|
28
|
+
else
|
29
|
+
loaded_config = ((config[RAILS_ENV] || {})[configuration] || {})
|
30
|
+
default_config.merge(loaded_config.symbolize_keys || {})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# holds whether or not background handling is disabled.
|
36
|
+
mattr_accessor :disabled
|
37
|
+
self.disabled = false
|
38
|
+
|
39
|
+
# Disables background handling.
|
40
|
+
def self.disable!
|
41
|
+
BackgroundLite.disabled = true
|
42
|
+
end
|
43
|
+
|
44
|
+
# Enables background handling.
|
45
|
+
def self.enable!
|
46
|
+
BackgroundLite.disabled = false
|
47
|
+
end
|
48
|
+
|
49
|
+
# Disables background handling for the given block.
|
50
|
+
def self.disable(&block)
|
51
|
+
value = BackgroundLite.disabled
|
52
|
+
begin
|
53
|
+
BackgroundLite.disable!
|
54
|
+
yield
|
55
|
+
ensure
|
56
|
+
BackgroundLite.disabled = value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Sends a message to the background. The message contains an object, a method,
|
61
|
+
# and the methods arguments. The object and the arguments will be cloned for
|
62
|
+
# background handling.
|
63
|
+
#
|
64
|
+
# The options hash lets you choose the background handler(s) and their
|
65
|
+
# configuration, if available.
|
66
|
+
#
|
67
|
+
# You should rarely need to use this method directly. Rather use
|
68
|
+
# Class#background_method to mark a method to be executed in the background.
|
69
|
+
def self.send_to_background(object, method, args = [], options = {})
|
70
|
+
object = object.clone_for_background
|
71
|
+
args = args.collect { |a| a.clone_for_background }
|
72
|
+
|
73
|
+
config = (BackgroundLite::Config.load(options[:config].to_s) || {})
|
74
|
+
handler = if BackgroundLite.disabled
|
75
|
+
[:in_process]
|
76
|
+
else
|
77
|
+
[options.delete(:handler) || config[:handler] || BackgroundLite::Config.default_handler].flatten
|
78
|
+
end
|
79
|
+
reporter = options.delete(:reporter) || config[:reporter] || BackgroundLite::Config.default_error_reporter
|
80
|
+
|
81
|
+
handler.each do |hand|
|
82
|
+
options = {}
|
83
|
+
if hand.is_a? Hash
|
84
|
+
raise "Malformed handler options Hash" if hand.keys.size != 1
|
85
|
+
options = hand.values.first
|
86
|
+
hand = hand.keys.first
|
87
|
+
end
|
88
|
+
|
89
|
+
begin
|
90
|
+
BackgroundLite.disable do
|
91
|
+
"BackgroundLite::#{hand.to_s.camelize}Handler".constantize.handle(object, method, args, options)
|
92
|
+
end
|
93
|
+
|
94
|
+
return hand
|
95
|
+
rescue Exception => e
|
96
|
+
"BackgroundLite::#{reporter.to_s.camelize}ErrorReporter".constantize.report(e)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class Class
|
2
|
+
def clone_for_background
|
3
|
+
self
|
4
|
+
end
|
5
|
+
|
6
|
+
# Decorates a method to be executed in the background.
|
7
|
+
#
|
8
|
+
# To decorate a class' method, background_method must be called from inside a
|
9
|
+
# class and the first argument must be a symbol, containing the name of the
|
10
|
+
# method to decorate.
|
11
|
+
#
|
12
|
+
# class FactorialClass
|
13
|
+
# def factorial(number)
|
14
|
+
# result = (1..number).inject(1) { |num, res| res * num }
|
15
|
+
# Logger.log("The result is #{result}")
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # execute all calls to FactorialClass#factorial in the background
|
19
|
+
# background_method :factorial
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# === Choosing a handler
|
23
|
+
#
|
24
|
+
# There are several ways to execute a task in the background. To choose your
|
25
|
+
# particular handler and optionally some fallback handlers, in case the
|
26
|
+
# background process doesn't respond, use the :handler option.
|
27
|
+
#
|
28
|
+
# background_method :handler => [:active_messaging, :disk]
|
29
|
+
#
|
30
|
+
# To configure a handler, use a Hash instead of a Symbol like this:
|
31
|
+
#
|
32
|
+
# background_method :handler => [{ :active_messaging => { :queue => :my_queue } }, :disk]
|
33
|
+
#
|
34
|
+
# === Options
|
35
|
+
#
|
36
|
+
# handler:: The background handler to use.
|
37
|
+
# If none is specified, the BackgroundLite::Config.default_handler
|
38
|
+
# is used. Available handlers are :active_messaging, :in_process,
|
39
|
+
# :forget, :disk, and :test. This option can also be an array, in
|
40
|
+
# which case all of the handlers are tried in order, until one
|
41
|
+
# succeeds. Each element of the array may be a Symbol or a hash with
|
42
|
+
# one element. If it is a hash, the key is the handler name, and the
|
43
|
+
# value contains configuration options for the handler.
|
44
|
+
#
|
45
|
+
# reporter:: A reporter class that reports errors to the user. Available
|
46
|
+
# reporters are :stdout, :silent, :exception_notification, and
|
47
|
+
# :test.
|
48
|
+
#
|
49
|
+
# === Background Configurations
|
50
|
+
#
|
51
|
+
# Instead of specifying the :handler: and :reporter: params directly, you can
|
52
|
+
# also specify a configuration for your particular background call, which is
|
53
|
+
# configured in RAILS_ROOT/config/background.yml. This file has the following
|
54
|
+
# format:
|
55
|
+
#
|
56
|
+
# test:
|
57
|
+
# queue:
|
58
|
+
# :handler: test
|
59
|
+
# :reporter: silent
|
60
|
+
# production
|
61
|
+
# queue:
|
62
|
+
# :handler:
|
63
|
+
# - :active_messaging:
|
64
|
+
# :queue: background
|
65
|
+
# :reporter: exception_notification
|
66
|
+
#
|
67
|
+
# You can also specify a default configuration like this:
|
68
|
+
#
|
69
|
+
# default:
|
70
|
+
# :handler:
|
71
|
+
# - :in_process:
|
72
|
+
# - :disk:
|
73
|
+
#
|
74
|
+
# === Precedence
|
75
|
+
#
|
76
|
+
# For the handler and reporter options, the precedence is as follows, from
|
77
|
+
# high to low:
|
78
|
+
#
|
79
|
+
# - method argument
|
80
|
+
# - background.yml configuration, if supplied
|
81
|
+
# - background.yml default configuration
|
82
|
+
# - BackgroundLite::Config.default_handler / BackgroundLite::Config.default_error_reporter
|
83
|
+
#
|
84
|
+
# === Writing own handlers
|
85
|
+
#
|
86
|
+
# Writing handlers is easy. A background handler class must implement a
|
87
|
+
# self.handle method that accepts a hash containing local variables as well as
|
88
|
+
# an options hash for the block to execute. An error reporter must implement a
|
89
|
+
# self.report method that accepts an exception object. Note that for most
|
90
|
+
# non-fallback handlers you need to write a background task that accepts and
|
91
|
+
# executes the block. See BackgroundLite::ActiveMessagingHandler for an
|
92
|
+
# example on how to do that.
|
93
|
+
#
|
94
|
+
# === Things to note
|
95
|
+
#
|
96
|
+
# * Since it is not possible to serialize singleton objects, all objects are
|
97
|
+
# dup'ed before serialization. This means that all singleton methods get
|
98
|
+
# stripped away on serialization.
|
99
|
+
# * Every class used in a background method must be available in the
|
100
|
+
# background process as well.
|
101
|
+
# * Subject to the singleton restriction mentioned above, the self object is
|
102
|
+
# correctly and automatically serialized and can be referenced in the
|
103
|
+
# background method using the self keyword.
|
104
|
+
def background_method(method, options = {})
|
105
|
+
alias_method_chain method, :background do |aliased_target, punctuation|
|
106
|
+
self.class_eval do
|
107
|
+
define_method "#{aliased_target}_with_background#{punctuation}" do |*args|
|
108
|
+
BackgroundLite.send_to_background(self, "#{aliased_target}_without_background#{punctuation}", args, options)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class MockController #:nodoc:
|
2
|
+
def controller_name
|
3
|
+
"BackgroundLite"
|
4
|
+
end
|
5
|
+
def action_name
|
6
|
+
"send_to_background"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class MockRequest #:nodoc:
|
11
|
+
attr_accessor :format
|
12
|
+
|
13
|
+
def initialize(message, options = {})
|
14
|
+
@message = message
|
15
|
+
options.each do |k, v|
|
16
|
+
self.instance_variable_set(:"@#{k}", v)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def env
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
def protocol
|
24
|
+
"none"
|
25
|
+
end
|
26
|
+
def request_uri
|
27
|
+
"none"
|
28
|
+
end
|
29
|
+
def parameters
|
30
|
+
@message || "nil message. this does not happen (TM)."
|
31
|
+
end
|
32
|
+
def session
|
33
|
+
"none"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module BackgroundLite
|
38
|
+
# Notifies developers about errors per e-mail.
|
39
|
+
class ExceptionNotificationErrorReporter
|
40
|
+
# This method uses the exception notification plugin to deliver the exception
|
41
|
+
# together with a backtrace to the developers. Refer to the ExceptionNotification
|
42
|
+
# documentation for details.
|
43
|
+
def self.report(error)
|
44
|
+
ExceptionNotifier.deliver_exception_notification(error, MockController.new, MockRequest.new(error.message), {})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# This class is used for reporting errors in a test environment.
|
3
|
+
class TestErrorReporter
|
4
|
+
# Stores the last error
|
5
|
+
cattr_accessor :last_error
|
6
|
+
# Does not actually report any error, but stores it in last_error.
|
7
|
+
def self.report(error)
|
8
|
+
self.last_error = error
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# This background handler sends the method as well as the arguments through
|
3
|
+
# ActiveMessaging to the background poller. If you don't use the
|
4
|
+
# ActiveMessaging plugin, then this handler won't work.
|
5
|
+
#
|
6
|
+
# To make the background_lite plugin work with ActiveMessaging, you need to
|
7
|
+
# put the following processor in app/processors:
|
8
|
+
#
|
9
|
+
# class BackgroundProcessor < ApplicationProcessor
|
10
|
+
# subscribes_to :background
|
11
|
+
#
|
12
|
+
# def on_message(message)
|
13
|
+
# puts "BackgroundProcessor"
|
14
|
+
# BackgroundLite::ActiveMessagingHandler.execute(message)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
class ActiveMessagingHandler
|
18
|
+
# The ActiveMessaging queue name through which the message should be
|
19
|
+
# serialized.
|
20
|
+
@@queue_name = :background
|
21
|
+
cattr_accessor :queue_name
|
22
|
+
|
23
|
+
# Marshals the method and the arguments and sends it through ActiveMessaging
|
24
|
+
# to the background processor.
|
25
|
+
#
|
26
|
+
# === Options
|
27
|
+
#
|
28
|
+
# queue:: The name of the queue to use to send the message to the background
|
29
|
+
# process.
|
30
|
+
def self.handle(object, method, args, options = {})
|
31
|
+
ActiveMessaging::Gateway.publish((options[:queue] || self.queue_name).to_sym, Marshal.dump([object, method, args]))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Decodes a marshalled message which was previously sent over
|
35
|
+
# ActiveMessaging. Returns an array containing the object, the method name
|
36
|
+
# as a string, and the method arguments.
|
37
|
+
def self.decode(message)
|
38
|
+
begin
|
39
|
+
object, method, args = Marshal.load(message)
|
40
|
+
rescue ArgumentError => e
|
41
|
+
# Marshal.load does not trigger const_missing, so we have to do this
|
42
|
+
# ourselves.
|
43
|
+
e.message.split(' ').last.constantize
|
44
|
+
retry
|
45
|
+
end
|
46
|
+
[object, method, args]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Executes a marshalled message which was previously sent over
|
50
|
+
# ActiveMessaging, in the context of the object, with all the arguments
|
51
|
+
# passed.
|
52
|
+
def self.execute(message)
|
53
|
+
begin
|
54
|
+
object, method, args = self.decode(message)
|
55
|
+
puts "--- executing method: #{method}\n--- with variables: #{args.inspect}\n--- in object: #{object.class.name}, #{object.id}"
|
56
|
+
|
57
|
+
object.send(method, *args)
|
58
|
+
puts "--- it happened!"
|
59
|
+
rescue Exception => e
|
60
|
+
puts e.message
|
61
|
+
puts e.backtrace
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# Stores the serialized message on disk. This handler is probably most useful
|
3
|
+
# as a fallback handler.
|
4
|
+
class DiskHandler
|
5
|
+
# The directory in which the serialized messages should be stored.
|
6
|
+
@@dirname = nil
|
7
|
+
cattr_accessor :dirname
|
8
|
+
|
9
|
+
# Marshals the message and the locals into a file in the folder specified by
|
10
|
+
# dirname.
|
11
|
+
def self.handle(object, method, args, options = {})
|
12
|
+
filename = "background_#{Time.now.to_f.to_s}"
|
13
|
+
File.open("#{dirname}/#{filename}", 'w') do |file|
|
14
|
+
file.print(Marshal.dump([object, method, args]))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Replays all marshalled background tasks in the order in which they were
|
19
|
+
# stored into the folder specified by dirname.
|
20
|
+
def self.recover(handler)
|
21
|
+
handler_class = "BackgroundLite::#{handler.to_s.camelize}Handler".constantize
|
22
|
+
Dir.entries(dirname).grep(/^background/).sort.each do |filename|
|
23
|
+
path = "#{dirname}/#{filename}"
|
24
|
+
File.open(path, 'r') do |file|
|
25
|
+
object, method, args = Marshal.load(file)
|
26
|
+
handler_class.handle(object, method, args)
|
27
|
+
end
|
28
|
+
FileUtils.rm(path)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# This background handler sends the method as well as the arguments through
|
3
|
+
# DRb to the background process.
|
4
|
+
class DrbHandler
|
5
|
+
def self.background_queue
|
6
|
+
@background_queue ||= begin
|
7
|
+
require 'drb'
|
8
|
+
|
9
|
+
DRb.start_service
|
10
|
+
DRbObject.new(nil, "druby://localhost:2251")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Marshals the method and the arguments and sends it through DRb
|
15
|
+
# to the background process.
|
16
|
+
def self.handle(object, method, args, options = {})
|
17
|
+
background_queue.push(Marshal.dump([object, method, args]))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Decodes a marshalled message which was previously sent over
|
21
|
+
# DRb. Returns an array containing the object, the method name
|
22
|
+
# as a string, and the method arguments.
|
23
|
+
def self.decode(message)
|
24
|
+
begin
|
25
|
+
object, method, args = Marshal.load(message)
|
26
|
+
rescue ArgumentError => e
|
27
|
+
# Marshal.load does not trigger const_missing, so we have to do this
|
28
|
+
# ourselves.
|
29
|
+
e.message.split(' ').last.constantize
|
30
|
+
retry
|
31
|
+
end
|
32
|
+
[object, method, args]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Executes a marshalled message which was previously sent over
|
36
|
+
# DRb, in the context of the object, with all the arguments
|
37
|
+
# passed.
|
38
|
+
def self.execute(message)
|
39
|
+
begin
|
40
|
+
object, method, args = self.decode(message)
|
41
|
+
puts "--- executing method: #{method}\n--- with variables: #{args.inspect}\n--- in object: #{object.class.name}, #{object.id}"
|
42
|
+
|
43
|
+
object.send(method, *args)
|
44
|
+
puts "--- it happened!"
|
45
|
+
rescue Exception => e
|
46
|
+
puts e.message
|
47
|
+
puts e.backtrace
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# This background handler runs the given method in a forked child process.
|
3
|
+
class ForkHandler
|
4
|
+
# Runs the method in a forked child process
|
5
|
+
def self.handle(object, method, args, options = {})
|
6
|
+
fork do
|
7
|
+
object.send(method, *args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# Executes the method in-process. This handler is probably most useful as a
|
3
|
+
# fallback handler.
|
4
|
+
class InProcessHandler
|
5
|
+
# Executes the method in-process.
|
6
|
+
def self.handle(object, method, args, options = {})
|
7
|
+
object.send(method, *args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# This background handler sends the method as well as the arguments through
|
3
|
+
# Resque to the background process.
|
4
|
+
class ResqueHandler
|
5
|
+
@queue = :background
|
6
|
+
|
7
|
+
# Marshals the method and the arguments and sends it through Resque
|
8
|
+
# to the background process.
|
9
|
+
def self.handle(object, method, args, options = {})
|
10
|
+
require 'resque'
|
11
|
+
Resque.enqueue(self, Base64.encode64(Marshal.dump([object, method, args])))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Decodes a marshalled message which was previously sent over
|
15
|
+
# Resque. Returns an array containing the object, the method name
|
16
|
+
# as a string, and the method arguments.
|
17
|
+
def self.decode(message)
|
18
|
+
begin
|
19
|
+
object, method, args = Marshal.load(Base64.decode64(message))
|
20
|
+
rescue ArgumentError => e
|
21
|
+
# Marshal.load does not trigger const_missing, so we have to do this
|
22
|
+
# ourselves.
|
23
|
+
e.message.split(' ').last.constantize
|
24
|
+
retry
|
25
|
+
end
|
26
|
+
[object, method, args]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Executes a marshalled message which was previously sent over
|
30
|
+
# Resque, in the context of the object, with all the arguments
|
31
|
+
# passed.
|
32
|
+
def self.perform(message)
|
33
|
+
begin
|
34
|
+
object, method, args = self.decode(message)
|
35
|
+
puts "--- executing method: #{method}\n--- with variables: #{args.inspect}\n--- in object: #{object.class.name}, #{object.id}"
|
36
|
+
|
37
|
+
object.send(method, *args)
|
38
|
+
puts "--- it happened!"
|
39
|
+
rescue Exception => e
|
40
|
+
puts e.message
|
41
|
+
puts e.backtrace
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module BackgroundLite
|
4
|
+
# This background handler runs the method block via script/runner.
|
5
|
+
class RunnerHandler
|
6
|
+
# Marshals the method and arguments and sends them to script/runner.
|
7
|
+
def self.handle(object, method, args, options = {})
|
8
|
+
fork do
|
9
|
+
system(%{script/runner "BackgroundLite::RunnerHandler.execute '#{encode(object)}', '#{method}', '#{encode(args)}'"})
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Executes an encoded message which was sent via command line to runner
|
14
|
+
def self.execute(object, method, args)
|
15
|
+
object, args = self.decode(object), self.decode(args)
|
16
|
+
puts "--- executing method: #{method}\n--- with variables: #{args.inspect}\n--- in object: #{object.inspect}"
|
17
|
+
|
18
|
+
object.send(method, *args)
|
19
|
+
puts "--- it happened!"
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def self.encode(obj)
|
24
|
+
Base64.encode64(Marshal.dump(obj))
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.decode(string)
|
28
|
+
message = Base64.decode64(string)
|
29
|
+
begin
|
30
|
+
obj = Marshal.load(message)
|
31
|
+
rescue ArgumentError => e
|
32
|
+
# Marshal.load does not trigger const_missing, so we have to do this
|
33
|
+
# ourselves.
|
34
|
+
e.message.split(' ').last.constantize
|
35
|
+
retry
|
36
|
+
end
|
37
|
+
obj
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module BackgroundLite
|
2
|
+
# This handler is used in a testing environment. It allows for introspection
|
3
|
+
# of last call to the handle method.
|
4
|
+
class TestHandler
|
5
|
+
# contains the object on which the method is executed.
|
6
|
+
cattr_accessor :object
|
7
|
+
# contains the method name to execute
|
8
|
+
cattr_accessor :method
|
9
|
+
# contains the method's arguments
|
10
|
+
cattr_accessor :args
|
11
|
+
# If true, the execution of TestHandler#handle will fail the next time it's
|
12
|
+
# called.
|
13
|
+
cattr_accessor :fail_next_time
|
14
|
+
# True, if TestHandler#handle was executed.
|
15
|
+
cattr_accessor :executed
|
16
|
+
# Stores the last options hash given to handle
|
17
|
+
cattr_accessor :options
|
18
|
+
|
19
|
+
# Does not call the block, but sets some variables for introspection.
|
20
|
+
def self.handle(object, method, args, options = {})
|
21
|
+
self.executed = true
|
22
|
+
if self.fail_next_time
|
23
|
+
self.fail_next_time = false
|
24
|
+
raise "TestHandler.handle: Failed on purpose"
|
25
|
+
end
|
26
|
+
|
27
|
+
self.object, self.method, self.args, self.options = object, method, args, options
|
28
|
+
end
|
29
|
+
|
30
|
+
# Resets the class' accessors.
|
31
|
+
def self.reset
|
32
|
+
object = method = args = options = fail_next_time = executed = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Base
|
3
|
+
# Override this method to strip your object from data that doesn't have to
|
4
|
+
# be transmitted to the background process. Note that you don't need to
|
5
|
+
# clear the association cache, as this is already done for you in
|
6
|
+
# clone_for_background.
|
7
|
+
def cleanup_for_background
|
8
|
+
end
|
9
|
+
|
10
|
+
# Prepares the object to be transmitted to the background. This method dups
|
11
|
+
# the object and strips some instance variables, most notably the
|
12
|
+
# association cache, in order to prevent all associations to be transmitted
|
13
|
+
# with the object in full length.
|
14
|
+
#
|
15
|
+
# To clean up data specific to your class, use cleanup_for_background.
|
16
|
+
def clone_for_background
|
17
|
+
returning dup do |x|
|
18
|
+
x.cleanup_for_background
|
19
|
+
|
20
|
+
# taken from ActiveRecord::AttributeMethods::ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
21
|
+
type_to_preserve = [DateTime, Time, Date]
|
22
|
+
attr_cache = x.instance_variable_get(:@attributes_cache)
|
23
|
+
attr_cache.each do |key, value|
|
24
|
+
attr_cache[key] = nil unless type_to_preserve.include?(attr_cache[key].class)
|
25
|
+
end
|
26
|
+
x.instance_variable_set(:@errors, nil)
|
27
|
+
x.clear_association_cache
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../init'
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: background_lite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Thomas Kadauke
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-17 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: tkadauke@imedo.de
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- lib/background.rb
|
31
|
+
- lib/core_ext/class.rb
|
32
|
+
- lib/core_ext/error_reporters/exception_notification_error_reporter.rb
|
33
|
+
- lib/core_ext/error_reporters/silent_error_reporter.rb
|
34
|
+
- lib/core_ext/error_reporters/stderr_error_reporter.rb
|
35
|
+
- lib/core_ext/error_reporters/stdout_error_reporter.rb
|
36
|
+
- lib/core_ext/error_reporters/test_error_reporter.rb
|
37
|
+
- lib/core_ext/handlers/active_messaging_handler.rb
|
38
|
+
- lib/core_ext/handlers/disk_handler.rb
|
39
|
+
- lib/core_ext/handlers/drb_handler.rb
|
40
|
+
- lib/core_ext/handlers/forget_handler.rb
|
41
|
+
- lib/core_ext/handlers/fork_handler.rb
|
42
|
+
- lib/core_ext/handlers/in_process_handler.rb
|
43
|
+
- lib/core_ext/handlers/resque_handler.rb
|
44
|
+
- lib/core_ext/handlers/runner_handler.rb
|
45
|
+
- lib/core_ext/handlers/test_handler.rb
|
46
|
+
- lib/core_ext/nil_class.rb
|
47
|
+
- lib/core_ext/numeric.rb
|
48
|
+
- lib/core_ext/object.rb
|
49
|
+
- lib/core_ext/symbol.rb
|
50
|
+
- lib/rails_ext/activerecord/base.rb
|
51
|
+
- rails/init.rb
|
52
|
+
- init.rb
|
53
|
+
- README.rdoc
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://www.imedo.de/
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.6
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Run any method in the background
|
84
|
+
test_files: []
|
85
|
+
|