background_lite 0.0.1
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.
- 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
|
+
|