rake_server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,25 @@
1
+ = RakeServer
2
+
3
+ === What is it?
4
+
5
+ RakeServer is a lightweight client/server architecture for running Rake tasks.
6
+ The server is a long-running process that loads tasks from a Rakefile and waits
7
+ for requests to execute tasks. Each time a task is requested, the server forks,
8
+ runs the task, and streams the output from the task back to the client.
9
+
10
+ When the server is started, it can be given tasks to eager-run; this is
11
+ mainly useful for loading application code into the pre-fork environment.
12
+
13
+ === What's the point?
14
+
15
+ The main intended use case for RakeServer is to provide a means for production
16
+ deployments to run Rake tasks in the background (e.g. via a cron) without
17
+ needing to load the application environment from scratch each time a task is
18
+ executed. It may also be useful for remote invocation of Rake tasks, but that is
19
+ not the main goal.
20
+
21
+ === How do I use it?
22
+
23
+ $ cd /path/to/my/rails/app
24
+ $ rake-server environment
25
+ $ rake-client db:migrate
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ FileList['tasks/**/*.rake'].each { |tasks| load(tasks) }
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ * Start task should block until child has finished starting
2
+ * Fork hooks
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/rake-client ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'rake_server')
4
+
5
+ RakeServer::Client.run(ARGV)
data/bin/rake-server ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'rake_server')
4
+
5
+ options = {}
6
+ OptionParser.new do |opts|
7
+ opts.banner = "Usage: rake-server [options] [startup-tasks] (start|stop|run|restart)"
8
+
9
+ opts.on("-q", "--quiet", "Don't output anything") { |q| options[:quiet] = q }
10
+ opts.on("-h", "--host [HOST]", "Host to listen on") { |h| options[:host] = h }
11
+ opts.on("-p", "--port [PORT]", "Port to listen on") { |p| options[:port] = p }
12
+ end.parse!
13
+
14
+ command = ARGV.shift
15
+ case command
16
+ when 'start'
17
+ RakeServer::Server.start(ARGV, options)
18
+ when 'run'
19
+ RakeServer::Server.run(ARGV, options)
20
+ when 'stop'
21
+ RakeServer::Server.stop(options)
22
+ when 'restart'
23
+ RakeServer::Server.stop(options)
24
+ RakeServer::Server.start(options)
25
+ else
26
+ abort("Unknown command #{command.inspect}")
27
+ end
@@ -0,0 +1,58 @@
1
+ begin
2
+ require 'eventmachine'
3
+ rescue LoadError => e
4
+ if require('rubygems') then retry
5
+ else raise(e)
6
+ end
7
+ end
8
+
9
+ module RakeServer
10
+ class Client < EventMachine::Connection
11
+ include EventMachine::Protocols::ObjectProtocol
12
+
13
+ class <<self
14
+ def run(args, options = {})
15
+ options = DEFAULT_OPTIONS.merge(options)
16
+ EventMachine.run do
17
+ EventMachine.connect options[:host], options[:port], self, args
18
+ end
19
+ end
20
+ end
21
+
22
+ ENV_PATTERN = /^(\w+)=(.*)$/
23
+
24
+ def initialize(args)
25
+ begin
26
+ super()
27
+ @tasks, @env = [], {}
28
+ args.each do |arg|
29
+ if match = ENV_PATTERN.match(arg)
30
+ @env[match[1]] = match[2]
31
+ else
32
+ @tasks << arg
33
+ end
34
+ end
35
+ rescue => e
36
+ STDERR.puts(e.inspect)
37
+ STDERR.puts(e.backtrace)
38
+ end
39
+ end
40
+
41
+ def post_init
42
+ message = Message.new(@tasks, @env)
43
+ send_object(message)
44
+ end
45
+
46
+ def receive_object(data)
47
+ if data.nil?
48
+ unbind
49
+ else
50
+ puts(data)
51
+ end
52
+ end
53
+
54
+ def unbind
55
+ EventMachine.stop_event_loop
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,138 @@
1
+ begin
2
+ require 'eventmachine'
3
+ require 'rake'
4
+ rescue LoadError => e
5
+ if require('rubygems') then retry
6
+ else raise(e)
7
+ end
8
+ end
9
+
10
+ module RakeServer
11
+ class Server < EventMachine::Connection
12
+ include EventMachine::Protocols::ObjectProtocol
13
+
14
+ class <<self
15
+ def start(eager_tasks, options = {})
16
+ pid_file = File.join(pid_dir(options), "rake-server.pid")
17
+ pid = fork do
18
+ fork do
19
+ File.open(pid_file, 'w') { |f| f << Process.pid }
20
+ run(eager_tasks, options)
21
+ end
22
+ end
23
+ Process.waitpid(pid)
24
+ end
25
+
26
+ def stop(options = {})
27
+ pid_file = File.join(pid_dir(options), "rake-server.pid")
28
+ pid = IO.read(pid_file).to_i
29
+ Process.kill("TERM", pid)
30
+ FileUtils.rm(pid_file)
31
+ end
32
+
33
+ def run(eager_tasks, options = {})
34
+ options = DEFAULT_OPTIONS.merge(options)
35
+ EventMachine.run do
36
+ Rake.application.init
37
+ Rake.application.load_rakefile
38
+ eager_tasks.each { |task| Rake.application[task].invoke }
39
+ EventMachine.start_server(options[:host], options[:port], self)
40
+ unless options[:quiet]
41
+ puts "rake-server listening on #{options[:host]}:#{options[:port]}"
42
+ end
43
+ end
44
+ end
45
+
46
+ def before_fork(&block)
47
+ @before_fork = block
48
+ end
49
+
50
+ def after_fork(&block)
51
+ @after_fork = block
52
+ end
53
+
54
+ def run_before_fork
55
+ @before_fork.call if @before_fork
56
+ end
57
+
58
+ def run_after_fork
59
+ @after_fork.call if @after_fork
60
+ end
61
+
62
+ private
63
+
64
+ def pid_dir(options)
65
+ pid_dir = options[:pid_dir] || File.join(Dir.pwd, 'tmp', 'pids')
66
+ unless File.directory?(pid_dir)
67
+ raise "PID dir #{pid_dir} does not exist -- can't daemonize"
68
+ end
69
+ pid_dir
70
+ end
71
+ end
72
+
73
+ def receive_object(message)
74
+ begin
75
+ tasks = message.tasks.map { |task| Rake.application[task.to_sym] }
76
+ pid = fork_and_run_tasks(tasks, message.env || {})
77
+ rescue => e
78
+ send_object("ERR #{e.message}\n")
79
+ send_object(nil)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def fork_and_run_tasks(tasks, env)
86
+ input, output = IO.pipe
87
+ self.class.run_before_fork
88
+ pid = fork do
89
+ self.class.run_after_fork
90
+ env.each_pair do |key, value|
91
+ ENV[key] = value
92
+ end
93
+ input.close
94
+ STDOUT.reopen(output)
95
+ STDERR.reopen(output)
96
+ begin
97
+ tasks.each { |task| task.invoke }
98
+ rescue => e
99
+ STDERR.puts(e.message)
100
+ STDERR.puts(e.backtrace)
101
+ ensure
102
+ output.close
103
+ end
104
+ end
105
+ output.close
106
+ monitor_tasks(pid, input)
107
+ end
108
+
109
+ def monitor_tasks(pid, input)
110
+ EventMachine.defer(monitor(pid, input), done(input))
111
+ end
112
+
113
+ def monitor(pid, input)
114
+ proc do
115
+ begin
116
+ until Process.waitpid(pid, Process::WNOHANG)
117
+ begin
118
+ until input.eof? || (data = input.read_nonblock(4096)).empty?
119
+ send_object(data)
120
+ end
121
+ rescue Errno::EAGAIN
122
+ end
123
+ end
124
+ sleep(0.1)
125
+ rescue => e
126
+ STDERR.puts(e.inspect)
127
+ end
128
+ end
129
+ end
130
+
131
+ def done(input)
132
+ lambda do
133
+ send_object(input.read) until input.eof?
134
+ send_object(nil)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,10 @@
1
+ module RakeServer
2
+ autoload :Server, File.join(File.dirname(__FILE__), 'rake_server', 'server')
3
+ autoload :Client, File.join(File.dirname(__FILE__), 'rake_server', 'client')
4
+
5
+ DEFAULT_OPTIONS = {
6
+ :host => '127.0.0.1',
7
+ :port => 7253
8
+ }
9
+ Message = Struct.new(:tasks, :env)
10
+ end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "rake_server"
5
+ gemspec.summary = "Run rake tasks in a client-server architecture"
6
+ gemspec.description = <<DESC
7
+ RakeServer is a library which allows Rake tasks to be run using client requests
8
+ to a long-running rake server, which can eagerly load required code into memory
9
+ for fast task invocation.
10
+ DESC
11
+ gemspec.email = "mat@patch.com"
12
+ gemspec.homepage = "http://github.com/outoftime/rake_server"
13
+ gemspec.authors = ['Mat Brown', 'Cedric Howe']
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ STDERR.puts("Jeweler not available. Install it with `gem install jeweler'")
18
+ end
data/test/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ task :init do
2
+ system('notify-send "starting up"')
3
+ end
4
+
5
+ task :shout do
6
+ system('notify-send "Hey!"')
7
+ end
8
+
9
+ task :pid do
10
+ system("notify-send #{Process.pid}")
11
+ end
12
+
13
+ task :relax do
14
+ sleep(10)
15
+ end
16
+
17
+ task :print do
18
+ puts ENV['MESSAGE']
19
+ end
20
+
21
+ task :hello do
22
+ puts "Hello, world!"
23
+ end
24
+
25
+ task :setup_server do
26
+ RakeServer::Server.before_fork do
27
+ `notify-send "#{Process.pid}"`
28
+ end
29
+ RakeServer::Server.after_fork do
30
+ `notify-send "#{Process.pid}"`
31
+ end
32
+ end
33
+
34
+ namespace :namespace do
35
+ task :shout do
36
+ system('notify-send "Namespace::Hey!"')
37
+ end
38
+ end
data/tmp/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rake_server
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
+ - Mat Brown
13
+ - Cedric Howe
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-04-01 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: |
23
+ RakeServer is a library which allows Rake tasks to be run using client requests
24
+ to a long-running rake server, which can eagerly load required code into memory
25
+ for fast task invocation.
26
+
27
+ email: mat@patch.com
28
+ executables:
29
+ - rake-client
30
+ - rake-server
31
+ extensions: []
32
+
33
+ extra_rdoc_files:
34
+ - README.rdoc
35
+ - TODO
36
+ files:
37
+ - README.rdoc
38
+ - Rakefile
39
+ - TODO
40
+ - VERSION
41
+ - bin/rake-client
42
+ - bin/rake-server
43
+ - lib/rake_server.rb
44
+ - lib/rake_server/client.rb
45
+ - lib/rake_server/server.rb
46
+ - tasks/gemspec.rake
47
+ - test/Rakefile
48
+ - tmp/.gitignore
49
+ has_rdoc: true
50
+ homepage: http://github.com/outoftime/rake_server
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.6
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Run rake tasks in a client-server architecture
79
+ test_files: []
80
+