josip-backgroundrb_merb 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ Copyright (c) 2007 Hemant Kumar ( mail [at] gnufied [dot] org )
2
+ Copyright (c) 2006 Ezra Zygmuntowicz and skaar[at]waste[dot]org
3
+
4
+ The Library is dual licensed under terms of Ruby License or MIT License.
data/README ADDED
@@ -0,0 +1,45 @@
1
+ = Why double fork?
2
+ Well, to original fork didn't work for me...
3
+
4
+ = Merb fork of BackgrounDRb
5
+ Based on the 1.0.3 version of backgroundrb this is a hacked port to MERB. Some areas need tyding up but it works for all basic cases. Contributions welcome!
6
+
7
+ == Installation
8
+ (1) Install gem provided in /pkg into your app's gem repository (which has to be named gems)
9
+ ex. #cd ~/path/to/app
10
+ #mkdir gems
11
+ #gem install ~/path/to/bdrb_merb.gem -i gems --no-ri --no-rdoc -l
12
+ (2) Add to config/init.rb (Merb):
13
+ require 'backgroundrb_merb'
14
+ (3) Create configuration file:
15
+ #rake bdrb:setup
16
+
17
+ == Usage
18
+ Workers are created in app/workers
19
+ To create new worker:
20
+ merb-gen worker WorkerName
21
+
22
+ To start BackgroundRB:
23
+ rake bdrb:ctl:start
24
+ Control+C to stop
25
+ To run in background:
26
+ rake bdrb:ctl:daemonize
27
+ And to stop it:
28
+ rake bdrb:ctl:stop
29
+
30
+ = BackgrounDRb
31
+ BackgrounDRb is a Ruby job server and scheduler. Its main intent is to be
32
+ used with Ruby on Rails applications for offloading long-running tasks.
33
+ Since a Rails application blocks while serving a request it is best to
34
+ move long-running tasks off into a background process that is divorced
35
+ from http request/response cycle.
36
+
37
+ This new release of BackgrounDRb is also modular and can be used without Rails so that any Ruby program or framework can use it.
38
+
39
+ Copyright (c) 2006 Ezra Zygmuntowicz,skaar[at]waste[dot]org,
40
+ Copyright (c) 2007 Hemant Kumar (gethemant [at] gmail.com )
41
+
42
+
43
+ == Usage
44
+
45
+ Please look into http://backgroundrb.rubyforge.org
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ PLUGIN = "backgroundrb_merb"
5
+ NAME = "backgroundrb_merb"
6
+ VERSION = "1.0.3"
7
+ AUTHOR = "Josip Lisec"
8
+ EMAIL = "josiplisec@gmail.com"
9
+ HOMEPAGE = "not yet"
10
+ SUMMARY = "Merb plugin that provides backgroundrb"
11
+
12
+ spec = Gem::Specification.new do |s|
13
+ s.name = NAME
14
+ s.version = VERSION
15
+ s.platform = Gem::Platform::RUBY
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
18
+ s.summary = SUMMARY
19
+ s.description = s.summary
20
+ s.author = AUTHOR
21
+ s.email = EMAIL
22
+ s.homepage = HOMEPAGE
23
+ s.add_dependency('merb', '>= 0.9.2')
24
+ s.add_dependency('packet', '>= 0.1.5')
25
+ s.add_dependency('chronic', '>= 0.2.3')
26
+ s.require_path = 'lib'
27
+ # s.autorequire = PLUGIN
28
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{config,generators,lib,pids,server,specs}/**/*")
29
+ end
30
+
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ task :install => [:package] do
36
+ sh %{sudo gem install pkg/#{NAME}-#{VERSION}}
37
+ end
38
+
39
+ namespace :jruby do
40
+ desc "Run :package and install the resulting .gem with jruby"
41
+ task :install => :package do
42
+ sh %{#{SUDO} jruby -S gem install pkg/#{NAME}-#{Merb::VERSION}.gem --no-rdoc --no-ri}
43
+ end
44
+ end
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ * Implement a way to share code between workers.
2
+ * Implement a way to test workers.
3
+ * Implement proper logger stuff so as people can set log level and stuff
4
+ * Implement a way to disable logger workers.
5
+ * Implement backgroundrb to work across machines.
6
+ * Make the API more simple use to use and less mouthfull.
7
+
8
+
@@ -0,0 +1,11 @@
1
+ ## YAML Template.
2
+ :backgroundrb:
3
+ :ip: 0.0.0.0
4
+ :port: 11006
5
+
6
+ :schedules:
7
+ :foo_worker:
8
+ :worker_method: foobar
9
+ :trigger_args: */5 * * * * * *
10
+
11
+
@@ -0,0 +1,16 @@
1
+ Description:
2
+ The worker generator creates stubs for a new BackgrounDRb worker.
3
+
4
+ The generator takes a worker name as its argument. The worker name may be
5
+ given in CamelCase or under_score and should not be suffixed with 'Worker'.
6
+
7
+ The generator creates a worker class in app/workers and a test suite in
8
+ test/unit.
9
+
10
+ Example:
11
+ merb-gen worker Tail
12
+
13
+ This will create an Tail worker:
14
+ Model: app/workers/tail_worker.rb
15
+ Test: spec/workers/tail_spec.rb
16
+
@@ -0,0 +1,7 @@
1
+ class <%= worker_class_name %>Worker < BackgrounDRbMerb::MetaWorker
2
+ set_worker_name :<%= worker_file_name %>_worker
3
+
4
+ def create(args = nil)
5
+ # this method is called, when worker is loaded for the first time
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+ require "backgroundrb_merb"
3
+ require "drb"
4
+
5
+ describe <%= worker_class_name %> do
6
+
7
+ it "should have specs"
8
+
9
+ end
@@ -0,0 +1,36 @@
1
+ class WorkerGenerator < Merb::GeneratorBase
2
+ attr_reader :worker_class_name, :worker_file_name
3
+
4
+ def initialize(args, runtime_args = {})
5
+ @base = File.dirname(__FILE__)
6
+ super
7
+ @worker_file_name = args.shift.snake_case
8
+ @worker_class_name = @worker_file_name.to_const_string
9
+ end
10
+
11
+ def manifest
12
+ record do |m|
13
+ @m = m
14
+
15
+ @assigns = {
16
+ :worker_file_name => worker_file_name,
17
+ :worker_class_name => worker_class_name
18
+ }
19
+
20
+ copy_dirs
21
+ copy_files
22
+
23
+ # Not sure what is this for, tests?
24
+ # m.dependency "merb_model_test", [model_file_name], @assigns
25
+ end
26
+ end
27
+
28
+ protected
29
+ def banner
30
+ <<-EOS.split("\n").map{|x| x.strip}.join("\n")
31
+ Creates a basic BackgroundRB worker in app/workers.
32
+
33
+ USAGE: #{spec.name} worker
34
+ EOS
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # FIXME: check if data that we are writing to the socket should end with newline
2
+ require "pathname"
3
+ BACKGROUNDRB_ROOT = Pathname.new(Merb.root).realpath.to_s
4
+
5
+ if defined?(Merb::Plugins)
6
+ if File.exists?(Merb.root / "config" / "backgroundrb.yml") then
7
+ require "packet"
8
+ require "backgroundrb_merb/bdrb_conn_error"
9
+ require "backgroundrb_merb/bdrb_config"
10
+ require "backgroundrb_merb/worker_proxy"
11
+ require "backgroundrb_merb/merb_worker_proxy"
12
+
13
+ Merb::BootLoader.before_app_loads do
14
+ MiddleMan = BackgrounDRbMerb::WorkerProxy.init
15
+ end
16
+ end
17
+
18
+ Merb::Plugins.add_rakefiles "backgroundrb_merb/merbtasks"
19
+ end
@@ -0,0 +1,47 @@
1
+ require 'erb'
2
+
3
+ module BackgrounDRbMerb
4
+ class Config
5
+ def self.parse_cmd_options(argv)
6
+ require 'optparse'
7
+ options = { :environment => (Merb.environment || "development").dup }
8
+
9
+ OptionParser.new do |opts|
10
+ script_name = File.basename($0)
11
+ opts.banner = "Usage: #{$0} [options]"
12
+ opts.separator ""
13
+ opts.on("-e", "--environment=name", String,
14
+ "Specifies the environment to operate under (test/development/production).",
15
+ "Default: development") { |v| options[:environment] = v }
16
+ opts.separator ""
17
+ opts.on("-h", "--help",
18
+ "Show this help message.") { $stderr.puts opts; exit }
19
+ opts.separator ""
20
+ opts.on("-v","--version",
21
+ "Show version.") { $stderr.puts "1.0.3"; exit }
22
+ end.parse!(argv)
23
+ end
24
+
25
+ def self.read_config(config_file)
26
+ config = YAML.load(ERB.new(IO.read(config_file)).result)
27
+
28
+ environment = Merb.environment.to_sym
29
+ config[:backgroundrb][:environment] = environment.to_s
30
+
31
+ if config[environment]
32
+
33
+ # block for deep_merging the hashes
34
+ deep_proc = Proc.new do |key, oldval, newval|
35
+ if oldval.kind_of?(Hash) && newval.kind_of?(Hash)
36
+ next oldval.merge(newval,&deep_proc)
37
+ end
38
+ next newval
39
+ end
40
+
41
+ config.merge!( config[environment], &deep_proc)
42
+ end
43
+
44
+ config
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module BackgrounDRbMerb
2
+ class BdrbConnError < RuntimeError
3
+ attr_accessor :message
4
+
5
+ def initialize(message)
6
+ @message = message
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,57 @@
1
+ module BackgrounDRbMerb
2
+ class MerbWorkerProxy
3
+ attr_accessor :worker_name, :worker_method, :data, :job_key
4
+
5
+ def self.worker(p_worker_name, p_job_key = nil)
6
+ t = new
7
+ t.worker_name = p_worker_name
8
+ t.job_key = p_job_key
9
+ t
10
+ end
11
+
12
+ def method_missing(method_id, *args)
13
+ worker_method = method_id
14
+ data = args[0]
15
+ flag = args[1]
16
+
17
+ case worker_method
18
+ when :ask_status
19
+ MiddleMan.ask_status(compact(
20
+ :worker => worker_name,
21
+ :job_key => job_key
22
+ ))
23
+ when :worker_info
24
+ MiddleMan.worker_info(compact(
25
+ :worker => worker_name,
26
+ :job_key => job_key
27
+ ))
28
+ when :delete
29
+ MiddleMan.delete_worker(compact(
30
+ :worker => worker_name,
31
+ :job_key => job_key
32
+ ))
33
+ else
34
+ if flag
35
+ MiddleMan.send_request(compact(
36
+ :worker => worker_name,
37
+ :job_key => job_key,
38
+ :worker_method => worker_method,
39
+ :data => data
40
+ ))
41
+ else
42
+ MiddleMan.ask_work(compact(
43
+ :worker => worker_name,
44
+ :job_key => job_key,
45
+ :worker_method => worker_method,
46
+ :data => data
47
+ ))
48
+ end
49
+ end
50
+ end
51
+
52
+ def compact(options = { })
53
+ options.delete_if { |key,value| value.nil? }
54
+ options
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,91 @@
1
+ namespace :bdrb do
2
+ require 'yaml'
3
+
4
+ BDRB_DIR = File.expand_path(File.dirname(__FILE__) + '/../../')
5
+
6
+ unless File.exists?(Merb.root/"config"/"backgroundrb.yml") then
7
+ desc 'Create config file'
8
+ task :setup do
9
+ template = BDRB_DIR/"config"/"backgroundrb.yml"
10
+ FileUtils.cp(template, Merb.root/"config")
11
+ puts "Created #{Merb.root}/config/backgroundrb.yml"
12
+ end
13
+ else
14
+ desc 'Control BackgrounDRb server'
15
+ namespace :ctl do
16
+ $LOAD_PATH.unshift(BDRB_DIR)
17
+ $LOAD_PATH.unshift(BDRB_DIR / "lib" / "backgroundrb_merb")
18
+ $LOAD_PATH.unshift(BDRB_DIR / "server" / "lib")
19
+
20
+ WORKER_ROOT = Merb.root / "app" / "workers"
21
+ $LOAD_PATH.unshift(WORKER_ROOT)
22
+
23
+ require 'bdrb_config.rb'
24
+ require 'master_worker.rb'
25
+ require 'log_worker.rb'
26
+ require 'meta_worker.rb'
27
+
28
+ # BackgrounDRbMerb::Config.parse_cmd_options(ARGV[1..-1])
29
+ CONFIG_FILE = BackgrounDRbMerb::Config.read_config(Merb.root / "config" / "backgroundrb.yml")
30
+ bdrb_conf = CONFIG_FILE[:backgroundrb]
31
+ pid_file = BDRB_DIR / "pids" / "drb_#{bdrb_conf[:port]}.pid"
32
+ SERVER_LOGGER = Merb.root / "log" / "bdrb_server_#{bdrb_conf[:port]}.log"
33
+
34
+
35
+ desc 'Start and detach from console'
36
+ task :daemonize do
37
+ if fork then
38
+ exit
39
+ else
40
+ File.open(pid_file, 'w') {|f| f << Process.pid.to_s}
41
+ if bdrb_conf[:log].nil? or bdrb_conf[:log] != 'foreground' then
42
+ log_file = File.open(SERVER_LOGGER, 'w+')
43
+ [STDIN, STDOUT, STDERR].each {|dev| dev.reopen(log_file)}
44
+ end
45
+
46
+ BackgrounDRbMerb::MasterProxy.new
47
+ end
48
+ exit
49
+ end
50
+
51
+ desc 'Stop BackgrounDRb'
52
+ task :stop do
53
+ pid = nil
54
+ File.open(pid_file, 'r') {|f| pid = f.gets.strip.chomp.to_i}
55
+
56
+ begin
57
+ pgid = Process.getpgid(pid)
58
+ Process.kill('TERM', pid)
59
+ Process.kill('-TERM', pgid)
60
+ Process.kill('KILL', pid)
61
+ rescue Errno::ESRCH => e
62
+ puts "Deleting pid file"
63
+ rescue
64
+ puts $!
65
+ ensure
66
+ File.delete(pid_file) if File.exists?(pid_file)
67
+ end
68
+ exit
69
+ end
70
+
71
+ desc 'Start BackgrounDRb'
72
+ task :start do
73
+ BackgrounDRbMerb::MasterProxy.new
74
+ end
75
+ end
76
+
77
+ desc 'Remove BackgroundRB from your app'
78
+ task :remove do
79
+ [Merb.root/"app"/"workers", Merb.root/"spec"/"workers"].each do |dir|
80
+ if File.exists?(dir) && Dir.entries(dir).size == 2
81
+ puts "#{dir} is empty...deleting!"
82
+ sleep 0.5
83
+ FileUtils.rmdir(dir)
84
+ end
85
+ end
86
+ FileUtils.rm(Merb.root/"config"/"backgroundrb.yml")
87
+
88
+ puts %{Don't forget to remove "require 'backgroundrb_merb'" from your init.rb!}
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,174 @@
1
+ module BackgrounDRbMerb
2
+ class WorkerProxy
3
+ include Packet::NbioHelper
4
+
5
+ def self.init
6
+ @config = BackgrounDRbMerb::Config.read_config("#{BACKGROUNDRB_ROOT}/config/backgroundrb.yml")
7
+ @server_ip = @config[:backgroundrb][:ip]
8
+ @server_port = @config[:backgroundrb][:port]
9
+ new
10
+ end
11
+
12
+ def self.server_ip; @server_ip; end
13
+ def self.server_port; @server_port; end
14
+
15
+ def server_ip; self.class.server_ip; end
16
+ def server_port; self.class.server_port; end
17
+
18
+ def self.custom_connection(ip, port)
19
+ @server_ip = ip
20
+ @server_port = port
21
+ new
22
+ end
23
+
24
+ def initialize
25
+ @mutex = Mutex.new
26
+ establish_connection
27
+ end
28
+
29
+ def worker(worker_name, job_key = nil)
30
+ BackgrounDRbMerb::MerbWorkerProxy.worker(worker_name,job_key)
31
+ end
32
+
33
+ def establish_connection
34
+ puts " ~ Backgroundrb server: #{server_ip}:#{server_port}"
35
+ begin
36
+ @connection = TCPSocket.open(server_ip, server_port)
37
+ @connection.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY, 1)
38
+ @connection_status = true
39
+ # rescue Timeout::Error
40
+ # @connection_status = false
41
+ rescue Exception => e
42
+ @connection_status = false
43
+ end
44
+ end
45
+
46
+ def write_data(data)
47
+ begin
48
+ flush_in_loop(data)
49
+ rescue Errno::EAGAIN
50
+ return
51
+ rescue Errno::EPIPE
52
+ establish_connection
53
+ if @connection_status
54
+ flush_in_loop(data)
55
+ else
56
+ raise BackgrounDRbMerb::BdrbConnError.new("Error while writing")
57
+ end
58
+ rescue
59
+ establish_connection
60
+ if @connection_status
61
+ flush_in_loop(data)
62
+ else
63
+ raise BackgrounDRbMerb::BdrbConnError.new("Error while writing")
64
+ end
65
+ end
66
+ end
67
+
68
+ def flush_in_loop(data)
69
+ t_length = data.length
70
+ loop do
71
+ break if t_length <= 0
72
+ written_length = @connection.write(data)
73
+ @connection.flush
74
+ data = data[written_length..-1]
75
+ t_length = data.length
76
+ end
77
+ end
78
+
79
+ def dump_object(data)
80
+ unless @connection_status
81
+ establish_connection
82
+ raise BackgrounDRbMerb::BdrbConnError.new("Error while connecting to the backgroundrb server") unless @connection_status
83
+ end
84
+
85
+ object_dump = Marshal.dump(data)
86
+ dump_length = object_dump.length.to_s
87
+ length_str = dump_length.rjust(9,'0')
88
+ final_data = length_str + object_dump
89
+ @mutex.synchronize { write_data(final_data) }
90
+ end
91
+
92
+ def ask_work(p_data)
93
+ p_data[:type] = :do_work
94
+ dump_object(p_data)
95
+ end
96
+
97
+ def new_worker(p_data)
98
+ p_data[:type] = :start_worker
99
+ dump_object(p_data)
100
+ p_data[:job_key]
101
+ end
102
+
103
+ def worker_info(p_data)
104
+ p_data[:type] = :worker_info
105
+ dump_object(p_data)
106
+ bdrb_response = nil
107
+ @mutex.synchronize { bdrb_response = read_from_bdrb() }
108
+ bdrb_response
109
+ end
110
+
111
+ def all_worker_info
112
+ p_data = { }
113
+ p_data[:type] = :all_worker_info
114
+ dump_object(p_data)
115
+ bdrb_response = nil
116
+ @mutex.synchronize { bdrb_response = read_from_bdrb() }
117
+ bdrb_response
118
+ end
119
+
120
+ def delete_worker(p_data)
121
+ p_data[:type] = :delete_worker
122
+ dump_object(p_data)
123
+ end
124
+
125
+ def read_object
126
+ sock_data = ""
127
+ begin
128
+ while(sock_data << @connection.read_nonblock(1023)); end
129
+ rescue Errno::EAGAIN
130
+ @tokenizer.extract(sock_data) { |b_data| return b_data }
131
+ rescue
132
+ raise BackgrounDRbMerb::BdrbConnError.new("Not able to connect")
133
+ end
134
+ end
135
+
136
+ def query_all_workers
137
+ p_data = { }
138
+ p_data[:type] = :all_worker_status
139
+ dump_object(p_data)
140
+ bdrb_response = nil
141
+ @mutex.synchronize { bdrb_response = read_from_bdrb() }
142
+ bdrb_response
143
+ end
144
+
145
+ def ask_status(p_data)
146
+ p_data[:type] = :get_status
147
+ dump_object(p_data)
148
+ bdrb_response = nil
149
+ @mutex.synchronize { bdrb_response = read_from_bdrb() }
150
+ bdrb_response
151
+ end
152
+
153
+ def read_from_bdrb(timeout = 3)
154
+ @tokenizer = BinParser.new
155
+ begin
156
+ ret_val = select([@connection],nil,nil,timeout)
157
+ return nil unless ret_val
158
+ raw_response = read_object()
159
+ master_response = Marshal.load(raw_response)
160
+ return master_response
161
+ rescue
162
+ return nil
163
+ end
164
+ end
165
+
166
+ def send_request(p_data)
167
+ p_data[:type] = :get_result
168
+ dump_object(p_data)
169
+ bdrb_response = nil
170
+ @mutex.synchronize { bdrb_response = read_from_bdrb(nil) }
171
+ bdrb_response
172
+ end
173
+ end
174
+ end