rake_server 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 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
+