spring 0.0.5 → 0.0.6
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.md +95 -55
- data/bin/spring +2 -3
- data/lib/spring/application.rb +7 -8
- data/lib/spring/application_manager.rb +12 -5
- data/lib/spring/application_watcher.rb +4 -1
- data/lib/spring/client.rb +31 -0
- data/lib/spring/client/binstub.rb +67 -0
- data/lib/spring/client/command.rb +18 -0
- data/lib/spring/client/help.rb +23 -0
- data/lib/spring/client/run.rb +101 -0
- data/lib/spring/client/stop.rb +11 -0
- data/lib/spring/commands.rb +84 -22
- data/lib/spring/configuration.rb +31 -0
- data/lib/spring/env.rb +43 -5
- data/lib/spring/errors.rb +33 -0
- data/lib/spring/server.rb +9 -2
- data/lib/spring/sid.rb +1 -1
- data/lib/spring/version.rb +2 -2
- data/test/acceptance/app_test.rb +80 -18
- data/test/apps/rails-3-2/.gitignore +3 -0
- data/test/apps/rails-3-2/Gemfile +1 -2
- data/test/helper.rb +0 -1
- data/test/unit/application_watcher_test.rb +15 -3
- data/test/unit/commands_test.rb +34 -0
- metadata +14 -3
- data/lib/spring.rb +0 -151
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Spring
|
2
2
|
|
3
|
+
[](https://travis-ci.org/jonleighton/spring)
|
4
|
+
|
3
5
|
Spring is a Rails application preloader. It's trying to solve the same
|
4
6
|
problem as [spork](https://github.com/sporkrb/spork),
|
5
7
|
[zeus](https://github.com/burke/zeus) and
|
@@ -42,16 +44,9 @@ At the moment only MRI 1.9.3 / Rails 3.2 is supported.
|
|
42
44
|
|
43
45
|
## Usage
|
44
46
|
|
45
|
-
Install the `spring` gem. You
|
46
|
-
it's
|
47
|
-
|
48
|
-
on its own.
|
49
|
-
|
50
|
-
You now have a `spring` command. Do a `rbenv rehash` if necessary. Note
|
51
|
-
that on my machine I had over 700 gems installed, and activating the gem
|
52
|
-
to run the `spring` command added over 0.5s to the runtime. Clearing out
|
53
|
-
my gems solved the problem, but I'd like to figure out a way to speed
|
54
|
-
this up.
|
47
|
+
Install the `spring` gem. You can add it to your Gemfile if you like but
|
48
|
+
it's optional. You now have a `spring` command. Don't use it with
|
49
|
+
`bundle exec` or it will be extremely slow.
|
55
50
|
|
56
51
|
For this walkthrough, I'm using the test app in the Spring repository:
|
57
52
|
|
@@ -69,28 +64,28 @@ Run options:
|
|
69
64
|
|
70
65
|
.......
|
71
66
|
|
72
|
-
Finished tests in 0.
|
67
|
+
Finished tests in 0.127245s, 55.0121 tests/s, 78.5887 assertions/s.
|
73
68
|
|
74
69
|
7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
|
75
70
|
|
76
|
-
real
|
77
|
-
user 0m0.
|
78
|
-
sys 0m0.
|
71
|
+
real 0m2.165s
|
72
|
+
user 0m0.281s
|
73
|
+
sys 0m0.066s
|
79
74
|
```
|
80
75
|
|
81
76
|
That booted our app in the background:
|
82
77
|
|
83
78
|
```
|
84
79
|
$ ps ax | grep spring
|
85
|
-
|
86
|
-
|
80
|
+
26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
|
81
|
+
26155 pts/3 Sl 0:02 spring app | rails-3-2 | started 2013-02-01 20:16:40 +0000 | test mode
|
87
82
|
```
|
88
83
|
|
89
84
|
We can see two processes, one is the Spring server, the other is the
|
90
85
|
application running in the test environment. When we close the terminal,
|
91
86
|
the processes will be killed automatically.
|
92
87
|
|
93
|
-
Running the
|
88
|
+
Running the test is faster next time:
|
94
89
|
|
95
90
|
```
|
96
91
|
$ time spring test test/functional/posts_controller_test.rb
|
@@ -100,42 +95,76 @@ Run options:
|
|
100
95
|
|
101
96
|
.......
|
102
97
|
|
103
|
-
Finished tests in 0.
|
98
|
+
Finished tests in 0.176896s, 39.5714 tests/s, 56.5305 assertions/s.
|
104
99
|
|
105
100
|
7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
|
106
101
|
|
107
|
-
real 0m0.
|
108
|
-
user 0m0.
|
109
|
-
sys 0m0.
|
102
|
+
real 0m0.610s
|
103
|
+
user 0m0.276s
|
104
|
+
sys 0m0.059s
|
105
|
+
```
|
106
|
+
|
107
|
+
Running `spring test`, `spring rake`, `spring console`, etc gets a bit
|
108
|
+
tedious. It also suffers from a performance issue in Rubygems ([which I
|
109
|
+
am actively working on](https://github.com/rubygems/rubygems/pull/435))
|
110
|
+
which means the `spring` command takes a while to start up. The more
|
111
|
+
gems you have, the longer it takes.
|
112
|
+
|
113
|
+
Spring binstubs solve both of these problems. If you will be running the
|
114
|
+
`test` command regularly, run:
|
115
|
+
|
116
|
+
```
|
117
|
+
$ spring binstub test
|
118
|
+
```
|
119
|
+
|
120
|
+
This generates a `bin/spring` and a `bin/test`, which allows you to run
|
121
|
+
`spring` and `spring test` in a way that doesn't trigger the Rubygems
|
122
|
+
performance bug:
|
123
|
+
|
124
|
+
```
|
125
|
+
$ time bin/test test/functional/posts_controller_test.rb
|
126
|
+
Run options:
|
127
|
+
|
128
|
+
# Running tests:
|
129
|
+
|
130
|
+
.......
|
131
|
+
|
132
|
+
Finished tests in 0.166585s, 42.0207 tests/s, 60.0296 assertions/s.
|
133
|
+
|
134
|
+
7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
|
135
|
+
|
136
|
+
real 0m0.407s
|
137
|
+
user 0m0.077s
|
138
|
+
sys 0m0.059s
|
110
139
|
```
|
111
140
|
|
112
141
|
If we edit any of the application files, or test files, the change will
|
113
|
-
be picked up on the next run, without
|
114
|
-
having to be restarted.
|
115
|
-
`Post` model in an initializer and then edited it.
|
142
|
+
be picked up on the next run, without the background process
|
143
|
+
having to be restarted.
|
116
144
|
|
117
145
|
If we edit any of the preloaded files, the application needs to restart
|
118
|
-
automatically.
|
119
|
-
"edit" the `config/application.rb`:
|
146
|
+
automatically. Let's "edit" `config/application.rb`:
|
120
147
|
|
121
148
|
```
|
122
149
|
$ touch config/application.rb
|
123
150
|
$ ps ax | grep spring
|
124
|
-
|
125
|
-
|
151
|
+
26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
|
152
|
+
26556 pts/3 Sl 0:00 spring app | rails-3-2 | started 2013-02-01 20:20:07 +0000 | test mode
|
126
153
|
```
|
127
154
|
|
128
155
|
The application process detected the change and exited. The server process
|
129
156
|
then detected that the application process exited, so it started a new application.
|
130
|
-
All of this
|
131
|
-
command we'll be running against a fresh application.
|
157
|
+
All of this happened automatically. Next time we run a
|
158
|
+
command we'll be running against a fresh application. We can see that
|
159
|
+
the start time and PID of the app process has changed.
|
132
160
|
|
133
161
|
If we run a command that uses a different environment, then it gets
|
134
162
|
booted up. For example, the `rake` command uses the `development`
|
135
163
|
environment by default:
|
136
164
|
|
137
165
|
```
|
138
|
-
$
|
166
|
+
$ spring binstub rake
|
167
|
+
$ bin/rake routes
|
139
168
|
posts GET /posts(.:format) posts#index
|
140
169
|
POST /posts(.:format) posts#create
|
141
170
|
new_post GET /posts/new(.:format) posts#new
|
@@ -143,10 +172,6 @@ edit_post GET /posts/:id/edit(.:format) posts#edit
|
|
143
172
|
post GET /posts/:id(.:format) posts#show
|
144
173
|
PUT /posts/:id(.:format) posts#update
|
145
174
|
DELETE /posts/:id(.:format) posts#destroy
|
146
|
-
|
147
|
-
real 0m0.763s
|
148
|
-
user 0m0.185s
|
149
|
-
sys 0m0.063s
|
150
175
|
```
|
151
176
|
|
152
177
|
We now have 3 processes: the server, and application in test mode and
|
@@ -154,26 +179,9 @@ the application in development mode.
|
|
154
179
|
|
155
180
|
```
|
156
181
|
$ ps ax | grep spring
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
```
|
161
|
-
|
162
|
-
Running rake is faster the second time:
|
163
|
-
|
164
|
-
```
|
165
|
-
$ time spring rake routes
|
166
|
-
posts GET /posts(.:format) posts#index
|
167
|
-
POST /posts(.:format) posts#create
|
168
|
-
new_post GET /posts/new(.:format) posts#new
|
169
|
-
edit_post GET /posts/:id/edit(.:format) posts#edit
|
170
|
-
post GET /posts/:id(.:format) posts#show
|
171
|
-
PUT /posts/:id(.:format) posts#update
|
172
|
-
DELETE /posts/:id(.:format) posts#destroy
|
173
|
-
|
174
|
-
real 0m0.341s
|
175
|
-
user 0m0.177s
|
176
|
-
sys 0m0.070s
|
182
|
+
26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
|
183
|
+
26556 pts/3 Sl 0:08 spring app | rails-3-2 | started 2013-02-01 20:20:07 +0000 | test mode
|
184
|
+
26707 pts/3 Sl 0:00 spring app | rails-3-2 | started 2013-02-01 20:22:41 +0000 | development mode
|
177
185
|
```
|
178
186
|
|
179
187
|
## Commands
|
@@ -184,15 +192,27 @@ Custom commands can be specified in `config/spring.rb`. See
|
|
184
192
|
[`lib/spring/commands.rb`](https://github.com/jonleighton/spring/blob/master/lib/spring/commands.rb)
|
185
193
|
for examples.
|
186
194
|
|
195
|
+
A bunch of different test frameworks are supported at the moment in
|
196
|
+
order to make it easy for people to try spring. However in the future
|
197
|
+
the code to use a specific test framework should not be contained in the
|
198
|
+
spring repository.
|
199
|
+
|
187
200
|
### `test`
|
188
201
|
|
189
202
|
Runs a test (e.g. Test::Unit, MiniTest::Unit, etc.) Preloads the `test_helper` file.
|
190
203
|
|
204
|
+
This command can also recursively run a directory of tests. For example,
|
205
|
+
`spring test test/functional` will run `test/functional/**/*_test.rb`.
|
206
|
+
|
191
207
|
### `rspec`
|
192
208
|
|
193
209
|
Runs an rspec spec, exactly the same as the `rspec` executable. Preloads
|
194
210
|
the `spec_helper` file.
|
195
211
|
|
212
|
+
### `cucumber`
|
213
|
+
|
214
|
+
Runs a cucumber feature.
|
215
|
+
|
196
216
|
### `rake`
|
197
217
|
|
198
218
|
Runs a rake task.
|
@@ -210,3 +230,23 @@ Runs a Rails generator.
|
|
210
230
|
### `runner`
|
211
231
|
|
212
232
|
The Rails runner.
|
233
|
+
|
234
|
+
## Configuration
|
235
|
+
|
236
|
+
### application_root
|
237
|
+
|
238
|
+
Spring must know how to find your rails application. If you have a
|
239
|
+
normal app everything works out of the box. If you are working on a
|
240
|
+
project with a special setup (an engine for example), you must tell
|
241
|
+
Spring where your app is located:
|
242
|
+
|
243
|
+
**config/spring.rb**
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
Spring.application_root = './test/dummy'
|
247
|
+
```
|
248
|
+
|
249
|
+
### tmp directory
|
250
|
+
|
251
|
+
Spring needs a tmp directory. This will default to `Rails.root.join('tmp', 'spring')`.
|
252
|
+
You can set your own configuration directory by setting the `SPRING_TMP_PATH` environment variable.
|
data/bin/spring
CHANGED
data/lib/spring/application.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
require "spring/configuration"
|
1
2
|
require "spring/application_watcher"
|
2
3
|
require "spring/commands"
|
3
4
|
require "set"
|
4
5
|
|
5
|
-
|
6
|
+
module Spring
|
6
7
|
class << self
|
7
8
|
attr_accessor :application_watcher
|
8
9
|
end
|
@@ -21,13 +22,10 @@ class Spring
|
|
21
22
|
|
22
23
|
@stdout = IO.new(STDOUT.fileno)
|
23
24
|
@stderr = IO.new(STDERR.fileno)
|
24
|
-
@stdin = File.open('/dev/null', 'r')
|
25
|
-
|
26
|
-
STDIN.reopen(@stdin)
|
27
25
|
end
|
28
26
|
|
29
27
|
def start
|
30
|
-
require "
|
28
|
+
require Spring.application_root_path.join("config", "application")
|
31
29
|
|
32
30
|
# The test environment has config.cache_classes = true set by default.
|
33
31
|
# However, we don't want this to prevent us from performing class reloading,
|
@@ -36,7 +34,7 @@ class Spring
|
|
36
34
|
ActiveSupport::Dependencies.mechanism = :load
|
37
35
|
end
|
38
36
|
|
39
|
-
require "
|
37
|
+
require Spring.application_root_path.join("config", "environment")
|
40
38
|
|
41
39
|
watcher.add_files $LOADED_FEATURES
|
42
40
|
watcher.add_files ["Gemfile", "Gemfile.lock"].map { |f| "#{Rails.root}/#{f}" }
|
@@ -60,6 +58,7 @@ class Spring
|
|
60
58
|
|
61
59
|
def serve(client)
|
62
60
|
redirect_output(client) do
|
61
|
+
stdin = client.recv_io
|
63
62
|
args_length = client.gets.to_i
|
64
63
|
args = args_length.times.map { client.read(client.gets.to_i) }
|
65
64
|
command = Spring.command(args.shift)
|
@@ -70,6 +69,8 @@ class Spring
|
|
70
69
|
ActionDispatch::Reloader.prepare!
|
71
70
|
|
72
71
|
pid = fork {
|
72
|
+
Process.setsid
|
73
|
+
STDIN.reopen(stdin)
|
73
74
|
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
74
75
|
command.call(args)
|
75
76
|
}
|
@@ -101,13 +102,11 @@ class Spring
|
|
101
102
|
def redirect_output(socket)
|
102
103
|
STDOUT.reopen socket.recv_io
|
103
104
|
STDERR.reopen socket.recv_io
|
104
|
-
STDIN.reopen socket.recv_io
|
105
105
|
|
106
106
|
yield
|
107
107
|
ensure
|
108
108
|
STDOUT.reopen @stdout
|
109
109
|
STDERR.reopen @stderr
|
110
|
-
STDIN.reopen @stdin
|
111
110
|
end
|
112
111
|
end
|
113
112
|
end
|
@@ -2,15 +2,17 @@ require "socket"
|
|
2
2
|
require "spring/application"
|
3
3
|
require "mutex_m"
|
4
4
|
|
5
|
-
|
5
|
+
module Spring
|
6
6
|
class ApplicationManager
|
7
7
|
include Mutex_m
|
8
8
|
|
9
|
-
attr_reader :pid, :child, :
|
9
|
+
attr_reader :pid, :child, :app_env, :spring_env
|
10
10
|
|
11
|
-
def initialize(
|
11
|
+
def initialize(app_env)
|
12
12
|
super()
|
13
|
-
|
13
|
+
|
14
|
+
@app_env = app_env
|
15
|
+
@spring_env = Env.new
|
14
16
|
end
|
15
17
|
|
16
18
|
def start
|
@@ -56,6 +58,10 @@ class Spring
|
|
56
58
|
@client = nil
|
57
59
|
end
|
58
60
|
|
61
|
+
def stop
|
62
|
+
Process.kill('TERM', pid) if pid
|
63
|
+
end
|
64
|
+
|
59
65
|
private
|
60
66
|
|
61
67
|
def start_child(silence = false)
|
@@ -63,7 +69,8 @@ class Spring
|
|
63
69
|
@pid = fork {
|
64
70
|
[STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
|
65
71
|
@client.close if @client
|
66
|
-
ENV['RAILS_ENV'] = ENV['RACK_ENV'] =
|
72
|
+
ENV['RAILS_ENV'] = ENV['RACK_ENV'] = app_env
|
73
|
+
$0 = "spring app | #{spring_env.app_name} | started #{Time.now} | #{app_env} mode"
|
67
74
|
Application.new(child_socket).start
|
68
75
|
}
|
69
76
|
child_socket.close
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
module Spring
|
2
2
|
class ApplicationWatcher
|
3
3
|
attr_reader :mtime, :files, :globs
|
4
4
|
|
@@ -31,6 +31,9 @@ class Spring
|
|
31
31
|
|
32
32
|
def compute_mtime
|
33
33
|
expanded_files.map { |f| File.mtime(f).to_f }.max || 0
|
34
|
+
rescue Errno::ENOENT
|
35
|
+
# if a file does no longer exist, the watcher is always stale.
|
36
|
+
Float::MAX
|
34
37
|
end
|
35
38
|
|
36
39
|
def expanded_files
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spring/configuration"
|
2
|
+
require "spring/client/command"
|
3
|
+
require "spring/client/run"
|
4
|
+
require "spring/client/help"
|
5
|
+
require "spring/client/binstub"
|
6
|
+
require "spring/client/stop"
|
7
|
+
|
8
|
+
module Spring
|
9
|
+
module Client
|
10
|
+
COMMANDS = {
|
11
|
+
"help" => Client::Help,
|
12
|
+
"binstub" => Client::Binstub,
|
13
|
+
"stop" => Client::Stop
|
14
|
+
}
|
15
|
+
|
16
|
+
def self.run(args)
|
17
|
+
command_for(args.first).call(args)
|
18
|
+
rescue ClientError => e
|
19
|
+
STDERR.puts e.message
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.command_for(name)
|
24
|
+
if Spring.command?(name)
|
25
|
+
Client::Run
|
26
|
+
else
|
27
|
+
COMMANDS.fetch(name) { Client::Help }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Spring
|
2
|
+
module Client
|
3
|
+
class Binstub < Command
|
4
|
+
attr_reader :bindir, :name
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
super
|
8
|
+
|
9
|
+
@bindir = env.root.join("bin")
|
10
|
+
@name = args[1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
if Spring.command?(name)
|
15
|
+
bindir.mkdir unless bindir.exist?
|
16
|
+
generate_spring_binstub
|
17
|
+
generate_command_binstub
|
18
|
+
else
|
19
|
+
STDERR.puts "The '#{name}' command is not known to spring."
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def spring_binstub
|
25
|
+
bindir.join("spring")
|
26
|
+
end
|
27
|
+
|
28
|
+
def command_binstub
|
29
|
+
bindir.join(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_spring_binstub
|
33
|
+
File.write(spring_binstub, <<'CODE')
|
34
|
+
#!/usr/bin/env ruby
|
35
|
+
|
36
|
+
# This is a special way of invoking the spring gem in order to
|
37
|
+
# work around the performance issue discussed in
|
38
|
+
# https://github.com/rubygems/rubygems/pull/435
|
39
|
+
|
40
|
+
glob = "{#{Gem::Specification.dirs.join(",")}}/spring-*.gemspec"
|
41
|
+
candidates = Dir[glob].to_a.sort
|
42
|
+
|
43
|
+
spec = Gem::Specification.load(candidates.last)
|
44
|
+
|
45
|
+
if spec
|
46
|
+
spec.activate
|
47
|
+
load spec.bin_file("spring")
|
48
|
+
else
|
49
|
+
STDERR.puts "Could not find spring gem in #{Gem::Specification.dirs.join(", ")}."
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
CODE
|
53
|
+
|
54
|
+
spring_binstub.chmod 0755
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_command_binstub
|
58
|
+
File.write(command_binstub, <<CODE)
|
59
|
+
#!/usr/bin/env bash
|
60
|
+
exec $(dirname $0)/spring #{name} "$@"
|
61
|
+
CODE
|
62
|
+
|
63
|
+
command_binstub.chmod 0755
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|