server-starter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6d6ac01d0b1513979bb1b7d44518ad974250ac2e
4
+ data.tar.gz: 5b5cf5cc27f280472e5c3008127141b665cb56b9
5
+ SHA512:
6
+ metadata.gz: 2afe47513bde1c810d8c9d573cfb1f72c7a7cbbb62374afa4c378a73808d763161be574a5618e83a7d9628557608ffa3bb8e3bc49eb918725ca7af95d8b0520f
7
+ data.tar.gz: 7d384eb0e55be2d485f0739b323297ee69e610392769197750019ecf56300662458b17d450b445bfbe65a22353e494f2983b51190a9906ab3fd75f1a1464b4b6
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /*.gem
2
+ ~*
3
+ #*
4
+ *~
5
+ .bundle
6
+ Gemfile.lock
7
+ .rbenv-version
8
+ vendor
9
+ doc/*
10
+ tmp/*
11
+ coverage
12
+ .yardoc
13
+ .ruby-version
14
+ pkg/*
15
+ .tags
16
+ unicorn.stat
17
+ start_server.pid
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## 0.1.0 (2015/03/16)
2
+
3
+ First version
4
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Naotoshi Seo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # ruby-server-starter
2
+
3
+ a superdaemon for hot-deploying server programs (ruby port of [p5-Server-Starter](https://github.com/kazuho/p5-Server-Starter))
4
+
5
+ # Description
6
+
7
+ *note: this description is almost entirely taken from the original Server::Starter module*
8
+
9
+ The ```start_server``` utility is a superdaemon for hot-deploying server programs.
10
+
11
+ It is often a pain to write a server program that supports graceful restarts, with no resource leaks. Server::Starter solves the problem by splitting the task into two: ```start_server``` works as a superdaemon that binds to zero or more TCP ports or unix sockets, and repeatedly spawns the server program that actually handles the necessary tasks (for example, responding to incoming commenctions). The spawned server programs under ```start_server``` call accept(2) and handle the requests.
12
+
13
+ To gracefully restart the server program, send SIGHUP to the superdaemon. The superdaemon spawns a new server program, and if (and only if) it starts up successfully, sends SIGTERM to the old server program.
14
+
15
+ By using ```start_server``` it is much easier to write a hot-deployable server. Following are the only requirements a server program to be run under ```start_server``` should conform to:
16
+
17
+ - receive file descriptors to listen to through an environment variable
18
+ - perform a graceful shutdown when receiving SIGTERM
19
+
20
+ # Unicorn
21
+
22
+ Following is an example to run unicorn server under ```Server::Starter```.
23
+
24
+ The command line example:
25
+
26
+ ```
27
+ bundle exec start_server.rb --status-file=/path/to/app/log/unicorn.stat \
28
+ --port=10080 --signal-on-hup=CONT --dir=/path/to/app -- \
29
+ bundle exec --keep-file-descriptors unicorn -c config/unicorn.conf.rb config.ru
30
+ ```
31
+
32
+ An example of unicorn.conf:
33
+
34
+ ```ruby
35
+ worker_processes 4
36
+ preload_app true
37
+
38
+ APP_ROOT = File.expand_path('../..', __FILE__)
39
+ status_file = File.join(APP_ROOT, 'log/unicorn.stat')
40
+
41
+ if ENV.key?('SERVER_STARTER_PORT')
42
+ fds = ENV['SERVER_STARTER_PORT'].split(';').map { |x|
43
+ path_or_port, fd = x.split('=', 2)
44
+ fd
45
+ }
46
+ ENV['UNICORN_FD'] = fds.join(',')
47
+ ENV.delete('SERVER_STARTER_PORT')
48
+ else
49
+ listen ENV['PORT'] || '10080'
50
+ end
51
+
52
+ before_fork do |server, worker|
53
+ defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
54
+
55
+ # Throttle the master from forking too quickly by sleeping. Due
56
+ # to the implementation of standard Unix signal handlers, this
57
+ # helps (but does not completely) prevent identical, repeated signals
58
+ # from being lost when the receiving process is busy.
59
+ sleep 1
60
+ end
61
+
62
+ after_fork do |server, worker|
63
+ defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
64
+
65
+ begin
66
+ # This allows a new master process to incrementally
67
+ # phase out the old master process with SIGTTOU to avoid a
68
+ # thundering herd (especially in the "preload_app false" case)
69
+ # when doing a transparent upgrade. The last worker spawned
70
+ # will then kill off the old master process with a SIGQUIT.
71
+ pids = File.readlines(status_file).map {|_| _.chomp.split(':') }.to_h
72
+ old_gen = ENV['SERVER_STARTER_GENERATION'].to_i - 1
73
+ if old_pid = pids[old_gen.to_s]
74
+ sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
75
+ Process.kill(sig, old_pid.to_i)
76
+ end
77
+ rescue Errno::ENOENT, Errno::ESRCH => e
78
+ $stderr.puts "#{e.class} #{e.message}"
79
+ end
80
+ end
81
+ ```
82
+
83
+ ## See Also
84
+
85
+ * [「Server::Starterに対応するとはどういうことか」の補足](http://blog.livedoor.jp/sonots/archives/40248661.html) (Japanese)
86
+ * [Server::Starter で Unicorn を起動する場合の Slow Restart](http://blog.livedoor.jp/sonots/archives/42826057.html) (Japanese)
87
+
88
+ ## ChangeLog
89
+
90
+ See [CHANGELOG.md](CHANGELOG.md) for details.
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new [Pull Request](../../pull/new/master)
99
+
100
+ ## Copyright
101
+
102
+ Copyright (c) 2015 Naotoshi Seo. See [LICENSE](LICENSE) for details.
103
+
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ desc 'Run test_unit based test'
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.test_files = Dir["test/**/test_*.rb"].sort
9
+ t.verbose = true
10
+ #t.warning = true
11
+ end
12
+ task :default => :test
13
+
14
+ desc 'Open an irb session preloaded with the gem library'
15
+ task :console do
16
+ sh 'irb -rubygems -I lib'
17
+ end
18
+ task :c => :console
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'optparse'
4
+ require_relative '../lib/server/starter'
5
+
6
+ opts = {
7
+ :port => [],
8
+ :path => [],
9
+ :interval => 1,
10
+ }
11
+ OptionParser.new do |opt|
12
+ opt.on(
13
+ '--port=(port|host:port),(port|host:port),...',
14
+ 'TCP port to listen to (if omitted, will not bind to any ports)',
15
+ Array
16
+ ) {|v| opts[:port] = v }
17
+ opt.on(
18
+ '--path=path,path,...',
19
+ 'path at where to listen using unix socket (optional)',
20
+ Array,
21
+ ) {|v| opts[:path] = v }
22
+ opt.on(
23
+ '--dir=path',
24
+ 'working directory, start_server do chdir to before exec (optional)',
25
+ ) {|v| opts[:dir] = v }
26
+ opt.on(
27
+ '--interval=seconds',
28
+ 'minimum interval to respawn the server program (default: 1)',
29
+ ) {|v| opts[:interval] = v.to_i }
30
+ opt.on(
31
+ '--signal-on-hup=SIGNAL',
32
+ 'name of the signal to be sent to the server process when start_server receives a SIGHUP (default: SIGTERM). If you use this option, be sure to also use `--signal-on-term` below.',
33
+ ) {|v| opts[:signal_on_hup] = v }
34
+ opt.on(
35
+ '--signal-on-term=SIGNAL',
36
+ 'name of the signal to be sent to the server process when start_server receives a SIGTERM (default: SIGTERM)',
37
+ ) {|v| opts[:signal_on_term] = v }
38
+ opt.on(
39
+ '--pid-file=filename',
40
+ 'if set, writes the process id of the start_server process to the file',
41
+ ) {|v| opts[:pid_file] = v }
42
+ opt.on(
43
+ '--status-file=filename',
44
+ 'if set, writes the status of the server process(es) to the file',
45
+ ) {|v| opts[:status_file] = v }
46
+ opt.on(
47
+ '--envdir=ENVDIR',
48
+ 'directory that contains environment variables to the server processes. It is intended for use with `envdir` in `daemontools`. This can be overwritten by environment variable `ENVDIR`.',
49
+ ) {|v| opts[:envdir] = v }
50
+ opt.on(
51
+ '--enable-auto-restart',
52
+ 'enables automatic restart by time. This can be overwritten by environment variable `ENABLE_AUTO_RESTART`.',
53
+ ) {|v| opts[:enable_auto_restart] = v }
54
+ opt.on(
55
+ '--auto-restart-interval=seconds',
56
+ 'automatic restart interval (default 360). It is used with `--enable-auto-restart` option. This can be overwritten by environment variable `AUTO_RESTART_INTERVAL`.',
57
+ ) {|v| opts[:auto_restart_interval] = v.to_i }
58
+ opt.on(
59
+ '--kill-old-delay=seconds',
60
+ 'time to suspend to send a signal to the old worker. The default value is 5 when `--enable-auto-restart` is set, 0 otherwise. This can be overwritten by environment variable `KILL_OLD_DELAY`.'
61
+ ) {|v| opts[:kill_old_delay] = v.to_i }
62
+ opt.on(
63
+ '--restart',
64
+ 'this is a wrapper command that reads the pid of the start_server process from --pid-file, sends SIGHUP to the process and waits until the server(s) of the older generation(s) die by monitoring the contents of the --status-file'
65
+ ) {|v| opts[:restart] = v }
66
+ opt.on(
67
+ '--backlog=num',
68
+ 'specifies a listen backlog parameter, whose default is SOMAXCONN (usually 128 on Linux). While SOMAXCONN is enough for most loads, large backlog is required for heavy loads.',
69
+ ) {|v| opts[:backlog] = v.to_i }
70
+ opt.on(
71
+ '--version',
72
+ 'print version',
73
+ ) {|v| puts Server::Starter::VERSION; exit 0 }
74
+
75
+ opt.parse!(ARGV)
76
+ end
77
+
78
+ starter = Server::Starter.new
79
+
80
+ if opts[:restart]
81
+ starter.restart_server(opts)
82
+ exit 0;
83
+ end
84
+
85
+ # validate options
86
+ if ARGV.empty?
87
+ $stderr.puts "server program not specified"
88
+ exit 1
89
+ end
90
+
91
+ starter.start_server(opts.merge({:exec => ARGV}))
92
+
93
+ __END__
94
+ #! /usr/bin/perl
95
+
96
+ use strict;
97
+ use warnings;
98
+
99
+ use Getopt::Long;
100
+ use Pod::Usage;
101
+ use Server::Starter qw(start_server restart_server);
102
+
103
+ my %opts = (
104
+ port => [],
105
+ path => [],
106
+ );
107
+
108
+ GetOptions(
109
+ map {
110
+ $_ => do {
111
+ my $name = (split '=', $_, 2)[0];
112
+ $name =~ s/-/_/g;
113
+ $opts{$name} ||= undef;
114
+ ref($opts{$name}) ? $opts{$name} : \$opts{$name};
115
+ },
116
+ } qw(port=s path=s interval=i log-file=s pid-file=s dir=s signal-on-hup=s signal-on-term=s
117
+ backlog=i envdir=s enable-auto-restart=i auto-restart-interval=i kill-old-delay=i
118
+ status-file=s restart help version),
119
+ ) or exit 1;
120
+ pod2usage(
121
+ -exitval => 0,
122
+ -verbose => 1,
123
+ ) if $opts{help};
124
+ if ($opts{version}) {
125
+ print "$Server::Starter::VERSION\n";
126
+ exit 0;
127
+ }
128
+
129
+ if ($opts{restart}) {
130
+ restart_server(%opts);
131
+ exit 0;
132
+ }
133
+
134
+ # validate options
135
+ die "server program not specified\n"
136
+ unless @ARGV;
137
+
138
+ start_server(
139
+ %opts,
140
+ exec => \@ARGV,
141
+ );
142
+ __END__
143
+
144
+ #! /usr/bin/perl
145
+
146
+ use strict;
147
+ use warnings;
148
+
149
+ use Getopt::Long;
150
+ use Pod::Usage;
151
+ use Server::Starter qw(start_server restart_server);
152
+
153
+ my %opts = (
154
+ port => [],
155
+ path => [],
156
+ );
157
+
158
+ GetOptions(
159
+ map {
160
+ $_ => do {
161
+ my $name = (split '=', $_, 2)[0];
162
+ $name =~ s/-/_/g;
163
+ $opts{$name} ||= undef;
164
+ ref($opts{$name}) ? $opts{$name} : \$opts{$name};
165
+ },
166
+ } qw(port=s path=s interval=i log-file=s pid-file=s dir=s signal-on-hup=s signal-on-term=s
167
+ backlog=i envdir=s enable-auto-restart=i auto-restart-interval=i kill-old-delay=i
168
+ status-file=s restart help version),
169
+ ) or exit 1;
170
+ pod2usage(
171
+ -exitval => 0,
172
+ -verbose => 1,
173
+ ) if $opts{help};
174
+ if ($opts{version}) {
175
+ print "$Server::Starter::VERSION\n";
176
+ exit 0;
177
+ }
178
+
179
+ if ($opts{restart}) {
180
+ restart_server(%opts);
181
+ exit 0;
182
+ }
183
+
184
+ # validate options
185
+ die "server program not specified\n"
186
+ unless @ARGV;
187
+
188
+ start_server(
189
+ %opts,
190
+ exec => \@ARGV,
191
+ );
192
+
193
+ __END__
194
+
195
+ =head1 NAME
196
+
197
+ start_server - a superdaemon for hot-deploying server programs
198
+
199
+ =head1 SYNOPSIS
200
+
201
+ start_server [options] -- server-prog server-arg1 server-arg2 ...
202
+
203
+ # start Plack using Starlet listening at TCP port 8000
204
+ start_server --port=8000 -- plackup -s Starlet --max-workers=100 index.psgi
205
+
206
+ =head1 DESCRIPTION
207
+
208
+ This script is a frontend of L<Server::Starter>. For more information please refer to the documentation of the module.
209
+
210
+ =head1 OPTIONS
211
+
212
+ =head2 --port=(port|host:port)
213
+
214
+ TCP port to listen to (if omitted, will not bind to any ports)
215
+
216
+ =head2 --path=path
217
+
218
+ path at where to listen using unix socket (optional)
219
+
220
+ =head2 --dir=path
221
+
222
+ working directory, start_server do chdir to before exec (optional)
223
+
224
+ =head2 --interval=seconds
225
+
226
+ minimum interval to respawn the server program (default: 1)
227
+
228
+ =head2 --signal-on-hup=SIGNAL
229
+
230
+ name of the signal to be sent to the server process when start_server receives a SIGHUP (default: SIGTERM). If you use this option, be sure to also use C<--signal-on-term> below.
231
+
232
+ =head2 --signal-on-term=SIGNAL
233
+
234
+ name of the signal to be sent to the server process when start_server receives a SIGTERM (default: SIGTERM)
235
+
236
+ =head2 --pid-file=filename
237
+
238
+ if set, writes the process id of the start_server process to the file
239
+
240
+ =head2 --status-file=filename
241
+
242
+ if set, writes the status of the server process(es) to the file
243
+
244
+ =head2 --envdir=ENVDIR
245
+
246
+ directory that contains environment variables to the server processes.
247
+ It is intended for use with C<envdir> in C<daemontools>.
248
+ This can be overwritten by environment variable C<ENVDIR>.
249
+
250
+ =head2 --enable-auto-restart
251
+
252
+ enables automatic restart by time.
253
+ This can be overwritten by environment variable C<ENABLE_AUTO_RESTART>.
254
+
255
+ =head2 --auto-restart-interval=seconds
256
+
257
+ automatic restart interval (default 360). It is used with C<--enable-auto-restart> option.
258
+ This can be overwritten by environment variable C<AUTO_RESTART_INTERVAL>.
259
+
260
+ =head2 --kill-old-delay=seconds
261
+
262
+ time to suspend to send a signal to the old worker. The default value is 5 when C<--enable-auto-restart> is set, 0 otherwise.
263
+ This can be overwritten by environment variable C<KILL_OLD_DELAY>.
264
+
265
+ =head2 --restart
266
+
267
+ this is a wrapper command that reads the pid of the start_server process from --pid-file, sends SIGHUP to the process and waits until the server(s) of the older generation(s) die by monitoring the contents of the --status-file
268
+
269
+ =head2 --backlog
270
+ specifies a listen backlog parameter, whose default is SOMAXCONN (usually 128 on Linux). While SOMAXCONN is enough for most loads, large backlog is required for heavy loads.
271
+
272
+ =head2 --help
273
+
274
+ prints this help
275
+
276
+ =head2 --version
277
+
278
+ prints the version number
279
+
280
+ =head1 AUTHOR
281
+
282
+ Kazuho Oku
283
+
284
+ =head1 SEE ALSO
285
+
286
+ L<Server::Starter>
287
+
288
+ =head1 LICENSE
289
+
290
+ This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
291
+
292
+ =cut
data/example/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'server-starter', path: '../'
4
+ gem 'unicorn'
5
+ gem 'pry'
6
+ gem 'pry-nav'
@@ -0,0 +1,46 @@
1
+ worker_processes 2
2
+ preload_app true
3
+
4
+ APP_ROOT = File.expand_path('../..', __FILE__)
5
+ status_file = File.join(APP_ROOT, 'log/unicorn.stat')
6
+
7
+ if ENV.key?('SERVER_STARTER_PORT')
8
+ fds = ENV['SERVER_STARTER_PORT'].split(';').map { |x|
9
+ path_or_port, fd = x.split('=', 2)
10
+ fd
11
+ }
12
+ ENV['UNICORN_FD'] = fds.join(',')
13
+ ENV.delete('SERVER_STARTER_PORT')
14
+ else
15
+ listen ENV['PORT'] || '10080'
16
+ end
17
+
18
+ before_fork do |server, worker|
19
+ defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
20
+
21
+ # Throttle the master from forking too quickly by sleeping. Due
22
+ # to the implementation of standard Unix signal handlers, this
23
+ # helps (but does not completely) prevent identical, repeated signals
24
+ # from being lost when the receiving process is busy.
25
+ sleep 1
26
+ end
27
+
28
+ after_fork do |server, worker|
29
+ defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
30
+
31
+ begin
32
+ # This allows a new master process to incrementally
33
+ # phase out the old master process with SIGTTOU to avoid a
34
+ # thundering herd (especially in the "preload_app false" case)
35
+ # when doing a transparent upgrade. The last worker spawned
36
+ # will then kill off the old master process with a SIGQUIT.
37
+ pids = File.readlines(status_file).map {|_| _.chomp.split(':') }.to_h
38
+ old_gen = ENV['SERVER_STARTER_GENERATION'].to_i - 1
39
+ if old_pid = pids[old_gen.to_s]
40
+ sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
41
+ Process.kill(sig, old_pid.to_i)
42
+ end
43
+ rescue Errno::ENOENT, Errno::ESRCH => e
44
+ $stderr.puts "#{e.class} #{e.message}"
45
+ end
46
+ end
data/example/config.ru ADDED
@@ -0,0 +1,11 @@
1
+ class SimpleApp
2
+ def call(env)
3
+ [
4
+ 200,
5
+ {'Content-Type' => 'text/html'},
6
+ ['Hello World']
7
+ ]
8
+ end
9
+ end
10
+
11
+ run SimpleApp.new
data/example/env/TEST ADDED
@@ -0,0 +1 @@
1
+ foo
File without changes
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ bundle exec start_server.rb --restart --pid-file=log/start_server.pid --status-file=log/unicorn.stat
@@ -0,0 +1,16 @@
1
+ #!/bin/sh
2
+ bundle exec start_server.rb \
3
+ --port=0.0.0.0:10080 \
4
+ --dir=$(pwd) \
5
+ --interval=1 \
6
+ --signal-on-hup=CONT \
7
+ --signal-on-TERM=TERM \
8
+ --pid-file=$(pwd)/log/start_server.pid \
9
+ --status-file=$(pwd)/log/unicorn.stat \
10
+ --envdir=env \
11
+ --enable-auto-restart \
12
+ --auto-restart-interval=100 \
13
+ --kill-old-delay=1 \
14
+ --backlog=100 \
15
+ -- \
16
+ bundle exec --keep-file-descriptors unicorn -c config/unicorn.conf config.ru 2>&1
@@ -0,0 +1,52 @@
1
+ class Server
2
+ class Starter
3
+ module Helper
4
+ def warn(msg)
5
+ $stderr.puts msg
6
+ end
7
+
8
+ def croak(msg)
9
+ $stderr.puts msg
10
+ exit 1
11
+ end
12
+
13
+ def die(*args)
14
+ if args.size == 2
15
+ e, msg = args[0], args[1]
16
+ $stderr.puts "#{msg}:#{e.class} #{e.message}"
17
+ else
18
+ msg = args[0]
19
+ $stderr.puts msg
20
+ end
21
+ exit 1
22
+ end
23
+
24
+ def with_local_env(local_env, &block)
25
+ orig_env = local_env.keys.map {|k| [k, ENV[k]] }.to_h
26
+ ENV.update(local_env)
27
+ yield
28
+ ensure
29
+ ENV.update(orig_env)
30
+ end
31
+
32
+ def bundler_with_clean_env(&block)
33
+ if defined?(Bundler)
34
+ begin
35
+ # Bundler.with_clean_env resets ENV to initial env on loading ruby
36
+ orig_env = ENV.to_hash
37
+ ENV.delete_if { |k,_| k[0,7] == 'BUNDLE_' }
38
+ if ENV.has_key? 'RUBYOPT'
39
+ ENV['RUBYOPT'] = ENV['RUBYOPT'].sub '-rbundler/setup', ''
40
+ end
41
+ %w[RUBYLIB GEM_HOME].each {|key| ENV.delete(key) }
42
+ yield
43
+ ensure
44
+ ENV.replace(orig_env)
45
+ end
46
+ else
47
+ yield
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ class Server
2
+ class Starter
3
+ VERSION = '0.1.0'
4
+ end
5
+ end