josip-backgroundrb_merb 1.0.3

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/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