spring 1.1.0.beta1 → 1.1.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/CHANGELOG.md +13 -1
- data/README.md +47 -11
- data/lib/spring/application.rb +118 -45
- data/lib/spring/application/boot.rb +10 -0
- data/lib/spring/application_manager.rb +43 -39
- data/lib/spring/boot.rb +12 -0
- data/lib/spring/client/binstub.rb +14 -11
- data/lib/spring/configuration.rb +11 -2
- data/lib/spring/env.rb +10 -9
- data/lib/spring/server.rb +5 -27
- data/lib/spring/version.rb +1 -1
- data/lib/spring/watcher/abstract.rb +7 -8
- data/spring.gemspec +1 -1
- data/test/acceptance/app_test.rb +20 -13
- data/test/acceptance/helper.rb +28 -7
- data/test/unit/watcher_test.rb +12 -8
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9be8bb694add40714afc5a6b350c2efc4a5348d0
|
4
|
+
data.tar.gz: 5af55d085e93f225c9d3ad9cdcbf89739a8dc6e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38c97ef6b3025ed82fca2d2784ead4c978deaab7226bf8504da55e4e8e7aa9d51ac49f5379ec1d27ab2fa9dd077623b99c8685e9f71c018dbfb74457b9f66958
|
7
|
+
data.tar.gz: d9501f771bdaf0f9b9da74bc4353b6b73baeb99671384034fd8307c90c91ba212aa31e45f868ee6d92bd418da02404570722be2fe2e6a2688acb4600cace8af5
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
* A `bin/spring` binstub is now generated. This allows us to load spring
|
4
4
|
correctly if you have it installed locally with a `BUNDLE_PATH`, so
|
5
|
-
it's no longer necessary to install spring system-wide.
|
5
|
+
it's no longer necessary to install spring system-wide. We also
|
6
|
+
activate the correct version from your Gemfile.lock. Note that you
|
6
7
|
still can't have spring in your Gemfile as a git repository or local
|
7
8
|
path; it must be a proper gem.
|
8
9
|
* Various changes to how springified binstubs are implemented. Existing
|
@@ -12,6 +13,17 @@
|
|
12
13
|
binstubs. This won't work unless you have upgraded your binstubs to
|
13
14
|
the new format.
|
14
15
|
* `config/database.yml` is watched
|
16
|
+
* Better application restarts - if you introduce an error, for example
|
17
|
+
by editing `config/application.rb`, spring will now continue to watch
|
18
|
+
your files and will immediately try to restart the application when
|
19
|
+
you edit `config/application.rb` again (hopefully to correct the error).
|
20
|
+
This means that by the time you come to run a command the application
|
21
|
+
may well already be running.
|
22
|
+
* Gemfile changes are now gracefully handled. Previously they would
|
23
|
+
cause spring to simply quit, meaning that you'd incur the full startup
|
24
|
+
penalty on the next run. Now spring doesn't quit, and will try to load
|
25
|
+
up your new bundle in the background.
|
26
|
+
* Fix support for using spring with Rails engines/plugins
|
15
27
|
|
16
28
|
## 1.0.0
|
17
29
|
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Spring
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/
|
3
|
+
[![Build Status](https://travis-ci.org/rails/spring.png?branch=master)](https://travis-ci.org/rails/spring)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/spring.png)](http://badge.fury.io/rb/spring)
|
5
5
|
|
6
6
|
Spring is a Rails application preloader. It speeds up development by
|
@@ -222,6 +222,52 @@ To use spring like this, do a `gem install spring` and then prefix
|
|
222
222
|
commands with `spring`. For example, rather than running `bin/rake -T`,
|
223
223
|
you'd run `spring rake -T`.
|
224
224
|
|
225
|
+
## Temporarily disabling Spring
|
226
|
+
|
227
|
+
If you're using Spring binstubs, but temporarily don't want commands to
|
228
|
+
run through Spring, set the `DISABLE_SPRING` environment variable.
|
229
|
+
|
230
|
+
## Class reloading
|
231
|
+
|
232
|
+
Spring uses Rails' class reloading mechanism
|
233
|
+
(`ActiveSupport::Dependencies`) to keep your code up to date between
|
234
|
+
test runs. This is the same mechanism which allows you to see changes
|
235
|
+
during development when you refresh the page. However, you may never
|
236
|
+
have used this mechanism with your `test` environment before, and this
|
237
|
+
can cause problems.
|
238
|
+
|
239
|
+
It's important to realise that code reloading means that the constants
|
240
|
+
in your application are *different objects* after files have changed:
|
241
|
+
|
242
|
+
```
|
243
|
+
$ bin/rails runner 'puts User.object_id'
|
244
|
+
70127987886040
|
245
|
+
$ touch app/models/user.rb
|
246
|
+
$ bin/rails runner 'puts User.object_id'
|
247
|
+
70127976764620
|
248
|
+
```
|
249
|
+
|
250
|
+
Suppose you have an initializer `config/initializers/save_user_class.rb`
|
251
|
+
like so:
|
252
|
+
|
253
|
+
``` ruby
|
254
|
+
USER_CLASS = User
|
255
|
+
```
|
256
|
+
|
257
|
+
This saves off the *first* version of the `User` class, which will not
|
258
|
+
be the same object as `User` after the code has been reloaded:
|
259
|
+
|
260
|
+
```
|
261
|
+
$ bin/rails runner 'puts User == USER_CLASS'
|
262
|
+
true
|
263
|
+
$ touch app/models/user.rb
|
264
|
+
$ bin/rails runner 'puts User == USER_CLASS'
|
265
|
+
false
|
266
|
+
```
|
267
|
+
|
268
|
+
So to avoid this problem, don't save off references to application
|
269
|
+
constants in your initialization code.
|
270
|
+
|
225
271
|
## Configuration
|
226
272
|
|
227
273
|
Spring will read `~/.spring.rb` and `config/spring.rb` for custom
|
@@ -250,16 +296,6 @@ which get run when your application initializers, such as
|
|
250
296
|
`config/application.rb`, `config/environments/*.rb` or
|
251
297
|
`config/initializers/*.rb`.
|
252
298
|
|
253
|
-
For example, if loading your test helper is slow, you might like to
|
254
|
-
preload it to speed up your test runs. To do this you could add the
|
255
|
-
following to your `config/environments/test.rb`:
|
256
|
-
|
257
|
-
``` ruby
|
258
|
-
config.after_initialize do
|
259
|
-
require Rails.root.join("test/helper")
|
260
|
-
end
|
261
|
-
```
|
262
|
-
|
263
299
|
### Running code after forking
|
264
300
|
|
265
301
|
You might want to run code after Spring forked off the process but
|
data/lib/spring/application.rb
CHANGED
@@ -1,40 +1,79 @@
|
|
1
|
+
require "spring/boot"
|
1
2
|
require "set"
|
2
|
-
require "spring/watcher"
|
3
|
-
require "thread"
|
4
3
|
|
5
4
|
module Spring
|
6
5
|
class Application
|
7
|
-
attr_reader :manager, :watcher, :spring_env
|
6
|
+
attr_reader :manager, :watcher, :spring_env, :original_env
|
7
|
+
|
8
|
+
def initialize(manager, original_env)
|
9
|
+
@manager = manager
|
10
|
+
@original_env = original_env
|
11
|
+
@spring_env = Env.new
|
12
|
+
@mutex = Mutex.new
|
13
|
+
@waiting = 0
|
14
|
+
@preloaded = false
|
15
|
+
@state = :initialized
|
16
|
+
@interrupt = IO.pipe
|
17
|
+
end
|
8
18
|
|
9
|
-
def
|
10
|
-
|
11
|
-
@
|
12
|
-
@
|
13
|
-
|
14
|
-
@mutex = Mutex.new
|
15
|
-
@waiting = 0
|
16
|
-
@exiting = false
|
19
|
+
def state(val)
|
20
|
+
return if exiting?
|
21
|
+
log "#{@state} -> #{val}"
|
22
|
+
@state = val
|
23
|
+
end
|
17
24
|
|
18
|
-
|
19
|
-
|
20
|
-
@
|
25
|
+
def state!(val)
|
26
|
+
state val
|
27
|
+
@interrupt.last.write "."
|
28
|
+
end
|
29
|
+
|
30
|
+
def app_env
|
31
|
+
ENV['RAILS_ENV']
|
21
32
|
end
|
22
33
|
|
23
34
|
def log(message)
|
24
|
-
spring_env.log "[application:#{
|
35
|
+
spring_env.log "[application:#{app_env}] #{message}"
|
25
36
|
end
|
26
37
|
|
27
38
|
def preloaded?
|
28
39
|
@preloaded
|
29
40
|
end
|
30
41
|
|
42
|
+
def preload_failed?
|
43
|
+
@preloaded == :failure
|
44
|
+
end
|
45
|
+
|
31
46
|
def exiting?
|
32
|
-
@exiting
|
47
|
+
@state == :exiting
|
48
|
+
end
|
49
|
+
|
50
|
+
def terminating?
|
51
|
+
@state == :terminating
|
52
|
+
end
|
53
|
+
|
54
|
+
def watcher_stale?
|
55
|
+
@state == :watcher_stale
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialized?
|
59
|
+
@state == :initialized
|
60
|
+
end
|
61
|
+
|
62
|
+
def start_watcher
|
63
|
+
@watcher = Spring.watcher
|
64
|
+
@watcher.on_stale { state! :watcher_stale }
|
65
|
+
@watcher.start
|
33
66
|
end
|
34
67
|
|
35
68
|
def preload
|
36
69
|
log "preloading app"
|
37
70
|
|
71
|
+
begin
|
72
|
+
require "spring/commands"
|
73
|
+
ensure
|
74
|
+
start_watcher
|
75
|
+
end
|
76
|
+
|
38
77
|
require Spring.application_root_path.join("config", "application")
|
39
78
|
|
40
79
|
# config/environments/test.rb will have config.cache_classes = true. However
|
@@ -50,45 +89,43 @@ module Spring
|
|
50
89
|
Rails.application.config.cache_classes = false
|
51
90
|
disconnect_database
|
52
91
|
|
92
|
+
@preloaded = :success
|
93
|
+
rescue Exception => e
|
94
|
+
@preloaded = :failure
|
95
|
+
watcher.add e.backtrace.map { |line| line.match(/^(.*)\:\d+\:in /)[1] }
|
96
|
+
raise e unless initialized?
|
97
|
+
ensure
|
53
98
|
watcher.add loaded_application_features
|
54
99
|
watcher.add Spring.gemfile, "#{Spring.gemfile}.lock"
|
55
|
-
watcher.add Rails.application.paths["config/initializers"]
|
56
|
-
watcher.add Rails.application.paths["config/database"]
|
57
100
|
|
58
|
-
|
101
|
+
if defined?(Rails)
|
102
|
+
watcher.add Rails.application.paths["config/initializers"]
|
103
|
+
watcher.add Rails.application.paths["config/database"]
|
104
|
+
end
|
59
105
|
end
|
60
106
|
|
61
107
|
def run
|
62
|
-
|
63
|
-
|
108
|
+
state :running
|
109
|
+
manager.puts
|
110
|
+
update_process_title
|
64
111
|
|
65
112
|
loop do
|
66
|
-
IO.select
|
67
|
-
|
68
|
-
if
|
69
|
-
|
70
|
-
manager.close
|
71
|
-
@exiting = true
|
72
|
-
try_exit
|
73
|
-
sleep
|
113
|
+
IO.select [manager, @interrupt.first]
|
114
|
+
|
115
|
+
if terminating? || watcher_stale? || preload_failed?
|
116
|
+
exit
|
74
117
|
else
|
75
118
|
serve manager.recv_io(UNIXSocket)
|
76
119
|
end
|
77
120
|
end
|
78
121
|
end
|
79
122
|
|
80
|
-
def try_exit
|
81
|
-
@mutex.synchronize {
|
82
|
-
exit if exiting? && @waiting == 0
|
83
|
-
}
|
84
|
-
end
|
85
|
-
|
86
123
|
def serve(client)
|
87
124
|
log "got client"
|
88
125
|
manager.puts
|
89
126
|
|
90
|
-
streams = 3.times.map { client.recv_io }
|
91
|
-
[STDOUT, STDERR].zip(
|
127
|
+
stdout, stderr, stdin = streams = 3.times.map { client.recv_io }
|
128
|
+
[STDOUT, STDERR].zip([stdout, stderr]).each { |a, b| a.reopen(b) }
|
92
129
|
|
93
130
|
preload unless preloaded?
|
94
131
|
|
@@ -105,13 +142,14 @@ module Spring
|
|
105
142
|
|
106
143
|
pid = fork {
|
107
144
|
Process.setsid
|
108
|
-
STDIN.reopen(
|
145
|
+
STDIN.reopen(stdin)
|
109
146
|
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
147
|
+
trap("TERM", "DEFAULT")
|
110
148
|
|
111
149
|
ARGV.replace(args)
|
112
150
|
|
113
151
|
# Delete all env vars which are unchanged from before spring started
|
114
|
-
|
152
|
+
original_env.each { |k, v| ENV.delete k if ENV[k] == v }
|
115
153
|
|
116
154
|
# Load in the current env vars, except those which *were* changed when spring started
|
117
155
|
env.each { |k, v| ENV[k] ||= v }
|
@@ -147,15 +185,37 @@ module Spring
|
|
147
185
|
client.close
|
148
186
|
|
149
187
|
@mutex.synchronize { @waiting -= 1 }
|
150
|
-
|
188
|
+
exit_if_finished
|
151
189
|
}
|
152
190
|
|
153
|
-
rescue => e
|
191
|
+
rescue Exception => e
|
154
192
|
log "exception: #{e}"
|
155
|
-
|
156
|
-
|
193
|
+
manager.puts unless pid
|
194
|
+
|
195
|
+
if streams && !e.is_a?(SystemExit)
|
196
|
+
print_exception(stderr, e)
|
197
|
+
streams.each(&:close)
|
198
|
+
end
|
199
|
+
|
200
|
+
client.puts(1) if pid
|
157
201
|
client.close
|
158
|
-
|
202
|
+
end
|
203
|
+
|
204
|
+
def terminate
|
205
|
+
state! :terminating
|
206
|
+
end
|
207
|
+
|
208
|
+
def exit
|
209
|
+
state :exiting
|
210
|
+
manager.shutdown(:RDWR)
|
211
|
+
exit_if_finished
|
212
|
+
sleep
|
213
|
+
end
|
214
|
+
|
215
|
+
def exit_if_finished
|
216
|
+
@mutex.synchronize {
|
217
|
+
Kernel.exit if exiting? && @waiting == 0
|
218
|
+
}
|
159
219
|
end
|
160
220
|
|
161
221
|
# The command might need to require some files in the
|
@@ -174,7 +234,8 @@ module Spring
|
|
174
234
|
end
|
175
235
|
|
176
236
|
def loaded_application_features
|
177
|
-
|
237
|
+
root = Spring.application_root_path.to_s
|
238
|
+
$LOADED_FEATURES.select { |f| f.start_with?(root) }
|
178
239
|
end
|
179
240
|
|
180
241
|
def disconnect_database
|
@@ -202,5 +263,17 @@ module Spring
|
|
202
263
|
end
|
203
264
|
end
|
204
265
|
end
|
266
|
+
|
267
|
+
def print_exception(stream, error)
|
268
|
+
first, rest = error.backtrace.first, error.backtrace.drop(1)
|
269
|
+
stream.puts("#{first}: #{error} (#{error.class})")
|
270
|
+
rest.each { |line| stream.puts("\tfrom #{line}") }
|
271
|
+
end
|
272
|
+
|
273
|
+
def update_process_title
|
274
|
+
ProcessTitleUpdater.run { |distance|
|
275
|
+
"spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
|
276
|
+
}
|
277
|
+
end
|
205
278
|
end
|
206
279
|
end
|
@@ -1,20 +1,15 @@
|
|
1
|
-
require "socket"
|
2
|
-
require "thread"
|
3
|
-
require "spring/application"
|
4
|
-
|
5
1
|
module Spring
|
6
2
|
class ApplicationManager
|
7
|
-
attr_reader :pid, :child, :app_env, :spring_env, :
|
3
|
+
attr_reader :pid, :child, :app_env, :spring_env, :status
|
8
4
|
|
9
|
-
def initialize(
|
10
|
-
@server = server
|
5
|
+
def initialize(app_env)
|
11
6
|
@app_env = app_env
|
12
7
|
@spring_env = Env.new
|
13
8
|
@mutex = Mutex.new
|
14
9
|
end
|
15
10
|
|
16
11
|
def log(message)
|
17
|
-
|
12
|
+
spring_env.log "[application_manager:#{app_env}] #{message}"
|
18
13
|
end
|
19
14
|
|
20
15
|
# We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
|
@@ -28,7 +23,6 @@ module Spring
|
|
28
23
|
|
29
24
|
def start
|
30
25
|
start_child
|
31
|
-
start_wait_thread
|
32
26
|
end
|
33
27
|
|
34
28
|
def restart
|
@@ -63,13 +57,15 @@ module Spring
|
|
63
57
|
def run(client)
|
64
58
|
with_child do
|
65
59
|
child.send_io client
|
66
|
-
child.gets
|
60
|
+
child.gets or raise Errno::EPIPE
|
67
61
|
end
|
68
62
|
|
69
|
-
pid = child.gets
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
pid = child.gets.to_i
|
64
|
+
|
65
|
+
unless pid.zero?
|
66
|
+
log "got worker pid #{pid}"
|
67
|
+
pid
|
68
|
+
end
|
73
69
|
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
74
70
|
log "#{e} while reading from child; returning no pid"
|
75
71
|
nil
|
@@ -84,39 +80,47 @@ module Spring
|
|
84
80
|
private
|
85
81
|
|
86
82
|
def start_child(preload = false)
|
87
|
-
server.application_starting
|
88
|
-
|
89
83
|
@child, child_socket = UNIXSocket.pair
|
90
|
-
@pid = fork {
|
91
|
-
(ObjectSpace.each_object(IO).to_a - [STDOUT, STDERR, STDIN, child_socket])
|
92
|
-
.reject(&:closed?)
|
93
|
-
.each(&:close)
|
94
84
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
85
|
+
Bundler.with_clean_env do
|
86
|
+
@pid = Process.spawn(
|
87
|
+
{
|
88
|
+
"RAILS_ENV" => app_env,
|
89
|
+
"RACK_ENV" => app_env,
|
90
|
+
"SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV),
|
91
|
+
"SPRING_PRELOAD" => preload ? "1" : "0"
|
92
|
+
},
|
93
|
+
"ruby",
|
94
|
+
"-I", File.expand_path("../..", __FILE__),
|
95
|
+
"-e", "require 'spring/application/boot'",
|
96
|
+
3 => child_socket
|
97
|
+
)
|
98
|
+
end
|
100
99
|
|
101
|
-
|
102
|
-
app.preload if preload
|
103
|
-
app.run
|
104
|
-
}
|
100
|
+
start_wait_thread(pid, child) if child.gets
|
105
101
|
child_socket.close
|
106
102
|
end
|
107
103
|
|
108
|
-
def start_wait_thread
|
109
|
-
|
110
|
-
Thread.current.abort_on_exception = true
|
111
|
-
|
112
|
-
while alive?
|
113
|
-
_, status = Process.wait2(pid)
|
114
|
-
@pid = nil
|
104
|
+
def start_wait_thread(pid, child)
|
105
|
+
Process.detach(pid)
|
115
106
|
|
116
|
-
|
117
|
-
|
118
|
-
|
107
|
+
Thread.new {
|
108
|
+
# The recv can raise an ECONNRESET, killing the thread, but that's ok
|
109
|
+
# as if it does we're no longer interested in the child
|
110
|
+
loop do
|
111
|
+
IO.select([child])
|
112
|
+
break if child.recv(1, Socket::MSG_PEEK).empty?
|
113
|
+
sleep 0.01
|
119
114
|
end
|
115
|
+
|
116
|
+
log "child #{pid} shutdown"
|
117
|
+
|
118
|
+
synchronize {
|
119
|
+
if @pid == pid
|
120
|
+
@pid = nil
|
121
|
+
restart
|
122
|
+
end
|
123
|
+
}
|
120
124
|
}
|
121
125
|
end
|
122
126
|
end
|
data/lib/spring/boot.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
# readline must be required before we setpgid, otherwise the require may hang,
|
5
|
+
# if readline has been built against libedit. See issue #70.
|
6
|
+
require "readline"
|
7
|
+
|
8
|
+
require "spring/configuration"
|
9
|
+
require "spring/env"
|
10
|
+
require "spring/process_title_updater"
|
11
|
+
require "spring/json"
|
12
|
+
require "spring/watcher"
|
@@ -23,22 +23,27 @@ CODE
|
|
23
23
|
# process we'll execute the lines which come after the LOADER block, which
|
24
24
|
# is what we want.
|
25
25
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
|
30
|
-
SPRING = <<CODE
|
26
|
+
# Parsing the lockfile in this way is pretty nasty but reliable enough
|
27
|
+
# The regex ensures that the match must be between a GEM line and an empty
|
28
|
+
# line, so it won't go on to the next section.
|
29
|
+
SPRING = <<'CODE'
|
31
30
|
#!/usr/bin/env ruby
|
32
31
|
|
32
|
+
# This file loads spring without using Bundler, in order to be fast
|
33
|
+
# It gets overwritten when you run the `spring binstub` command
|
34
|
+
|
33
35
|
unless defined?(Spring)
|
34
36
|
require "rubygems"
|
35
37
|
require "bundler"
|
36
38
|
|
39
|
+
match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m)
|
40
|
+
version = match && match[1]
|
41
|
+
|
42
|
+
ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR)
|
37
43
|
ENV["GEM_HOME"] = ""
|
38
|
-
ENV["GEM_PATH"] = Bundler.bundle_path.to_s
|
39
44
|
Gem.paths = ENV
|
40
45
|
|
41
|
-
|
46
|
+
gem "spring", version
|
42
47
|
require "spring/binstub"
|
43
48
|
end
|
44
49
|
CODE
|
@@ -167,10 +172,8 @@ CODE
|
|
167
172
|
when :add
|
168
173
|
bindir.mkdir unless bindir.exist?
|
169
174
|
|
170
|
-
|
171
|
-
|
172
|
-
spring_binstub.chmod 0755
|
173
|
-
end
|
175
|
+
File.write(spring_binstub, SPRING)
|
176
|
+
spring_binstub.chmod 0755
|
174
177
|
|
175
178
|
items.each(&:add)
|
176
179
|
when :remove
|
data/lib/spring/configuration.rb
CHANGED
@@ -22,15 +22,24 @@ module Spring
|
|
22
22
|
|
23
23
|
def application_root_path
|
24
24
|
@application_root_path ||= begin
|
25
|
-
|
25
|
+
if application_root
|
26
|
+
path = Pathname.new(File.expand_path(application_root))
|
27
|
+
else
|
28
|
+
path = project_root_path
|
29
|
+
end
|
30
|
+
|
26
31
|
raise MissingApplication.new(path) unless path.join("config/application.rb").exist?
|
27
32
|
path
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
36
|
+
def project_root_path
|
37
|
+
@project_root_path ||= find_project_root(Pathname.new(File.expand_path(Dir.pwd)))
|
38
|
+
end
|
39
|
+
|
31
40
|
private
|
32
41
|
|
33
|
-
def find_project_root(current_dir
|
42
|
+
def find_project_root(current_dir)
|
34
43
|
if current_dir.join(gemfile).exist?
|
35
44
|
current_dir
|
36
45
|
elsif current_dir.root?
|
data/lib/spring/env.rb
CHANGED
@@ -14,14 +14,19 @@ module Spring
|
|
14
14
|
attr_reader :log_file
|
15
15
|
|
16
16
|
def initialize(root = nil)
|
17
|
-
@root
|
18
|
-
@
|
17
|
+
@root = root
|
18
|
+
@project_root = root
|
19
|
+
@log_file = File.open(ENV["SPRING_LOG"] || "/dev/null", "a")
|
19
20
|
end
|
20
21
|
|
21
22
|
def root
|
22
23
|
@root ||= Spring.application_root_path
|
23
24
|
end
|
24
25
|
|
26
|
+
def project_root
|
27
|
+
@project_root ||= Spring.project_root_path
|
28
|
+
end
|
29
|
+
|
25
30
|
def version
|
26
31
|
Spring::VERSION
|
27
32
|
end
|
@@ -33,7 +38,7 @@ module Spring
|
|
33
38
|
end
|
34
39
|
|
35
40
|
def application_id
|
36
|
-
Digest::MD5.hexdigest(
|
41
|
+
Digest::MD5.hexdigest(project_root.to_s)
|
37
42
|
end
|
38
43
|
|
39
44
|
def socket_path
|
@@ -60,7 +65,7 @@ module Spring
|
|
60
65
|
end
|
61
66
|
|
62
67
|
def server_running?
|
63
|
-
pidfile = pidfile_path.open('r')
|
68
|
+
pidfile = pidfile_path.open('r+')
|
64
69
|
!pidfile.flock(File::LOCK_EX | File::LOCK_NB)
|
65
70
|
rescue Errno::ENOENT
|
66
71
|
false
|
@@ -71,12 +76,8 @@ module Spring
|
|
71
76
|
end
|
72
77
|
end
|
73
78
|
|
74
|
-
def bundle_mtime
|
75
|
-
[Bundler.default_lockfile, Bundler.default_gemfile].select(&:exist?).map(&:mtime).max
|
76
|
-
end
|
77
|
-
|
78
79
|
def log(message)
|
79
|
-
log_file.puts "[#{Time.now}] #{message}"
|
80
|
+
log_file.puts "[#{Time.now}] [#{Process.pid}] #{message}"
|
80
81
|
log_file.flush
|
81
82
|
end
|
82
83
|
end
|
data/lib/spring/server.rb
CHANGED
@@ -1,26 +1,13 @@
|
|
1
1
|
module Spring
|
2
|
-
|
3
|
-
attr_reader :original_env
|
4
|
-
end
|
5
|
-
@original_env = ENV.to_hash
|
2
|
+
ORIGINAL_ENV = ENV.to_hash
|
6
3
|
end
|
7
4
|
|
8
|
-
require "
|
9
|
-
require "thread"
|
10
|
-
|
11
|
-
require "spring/configuration"
|
12
|
-
require "spring/env"
|
5
|
+
require "spring/boot"
|
13
6
|
require "spring/application_manager"
|
14
|
-
require "spring/process_title_updater"
|
15
|
-
require "spring/json"
|
16
7
|
|
17
|
-
# Must be last, as it requires bundler/setup
|
8
|
+
# Must be last, as it requires bundler/setup, which alters the load path
|
18
9
|
require "spring/commands"
|
19
10
|
|
20
|
-
# readline must be required before we setpgid, otherwise the require may hang,
|
21
|
-
# if readline has been built against libedit. See issue #70.
|
22
|
-
require "readline"
|
23
|
-
|
24
11
|
module Spring
|
25
12
|
class Server
|
26
13
|
def self.boot
|
@@ -31,7 +18,7 @@ module Spring
|
|
31
18
|
|
32
19
|
def initialize(env = Env.new)
|
33
20
|
@env = env
|
34
|
-
@applications = Hash.new { |h, k| h[k] = ApplicationManager.new(
|
21
|
+
@applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k) }
|
35
22
|
@pidfile = env.pidfile_path.open('a')
|
36
23
|
@mutex = Mutex.new
|
37
24
|
end
|
@@ -48,7 +35,6 @@ module Spring
|
|
48
35
|
ignore_signals
|
49
36
|
set_exit_hook
|
50
37
|
set_process_title
|
51
|
-
watch_bundle
|
52
38
|
start_server
|
53
39
|
end
|
54
40
|
|
@@ -95,7 +81,7 @@ module Spring
|
|
95
81
|
# Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line
|
96
82
|
# will kill the server/application.
|
97
83
|
def ignore_signals
|
98
|
-
IGNORE_SIGNALS.each { |sig| trap(sig,
|
84
|
+
IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
|
99
85
|
end
|
100
86
|
|
101
87
|
def set_exit_hook
|
@@ -138,13 +124,5 @@ module Spring
|
|
138
124
|
"spring server | #{env.app_name} | started #{distance} ago"
|
139
125
|
}
|
140
126
|
end
|
141
|
-
|
142
|
-
def watch_bundle
|
143
|
-
@bundle_mtime = env.bundle_mtime
|
144
|
-
end
|
145
|
-
|
146
|
-
def application_starting
|
147
|
-
@mutex.synchronize { exit if env.bundle_mtime != @bundle_mtime }
|
148
|
-
end
|
149
127
|
end
|
150
128
|
end
|
data/lib/spring/version.rb
CHANGED
@@ -22,7 +22,7 @@ module Spring
|
|
22
22
|
@files = Set.new
|
23
23
|
@directories = Set.new
|
24
24
|
@stale = false
|
25
|
-
@
|
25
|
+
@listeners = []
|
26
26
|
end
|
27
27
|
|
28
28
|
def add(*items)
|
@@ -55,15 +55,14 @@ module Spring
|
|
55
55
|
@stale
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
@
|
60
|
-
@io_listener.write "." if @io_listener
|
58
|
+
def on_stale(&block)
|
59
|
+
@listeners << block
|
61
60
|
end
|
62
61
|
|
63
|
-
def
|
64
|
-
|
65
|
-
@
|
66
|
-
|
62
|
+
def mark_stale
|
63
|
+
return if stale?
|
64
|
+
@stale = true
|
65
|
+
@listeners.each(&:call)
|
67
66
|
end
|
68
67
|
|
69
68
|
def restart
|
data/spring.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.email = ["j@jonathanleighton.com"]
|
11
11
|
gem.description = %q{Rails application preloader}
|
12
12
|
gem.summary = %q{Rails application preloader}
|
13
|
-
gem.homepage = "http://github.com/
|
13
|
+
gem.homepage = "http://github.com/rails/spring"
|
14
14
|
gem.license = "MIT"
|
15
15
|
|
16
16
|
gem.files = `git ls-files`.split($/)
|
data/test/acceptance/app_test.rb
CHANGED
@@ -21,28 +21,22 @@ class AppTest < ActiveSupport::TestCase
|
|
21
21
|
@app ||= Spring::Test::Application.new("#{TEST_ROOT}/apps/tmp")
|
22
22
|
end
|
23
23
|
|
24
|
-
def debug(artifacts)
|
25
|
-
artifacts = artifacts.dup
|
26
|
-
artifacts.delete :status
|
27
|
-
app.dump_streams(artifacts.delete(:command), artifacts)
|
28
|
-
end
|
29
|
-
|
30
24
|
def assert_output(artifacts, expected)
|
31
25
|
expected.each do |stream, output|
|
32
26
|
assert artifacts[stream].include?(output),
|
33
|
-
"expected #{stream} to include '#{output}'.\n\n#{debug(artifacts)}"
|
27
|
+
"expected #{stream} to include '#{output}'.\n\n#{app.debug(artifacts)}"
|
34
28
|
end
|
35
29
|
end
|
36
30
|
|
37
31
|
def assert_success(command, expected_output = nil)
|
38
32
|
artifacts = app.run(*Array(command))
|
39
|
-
assert artifacts[:status].success?, "expected successful exit status\n\n#{debug(artifacts)}"
|
33
|
+
assert artifacts[:status].success?, "expected successful exit status\n\n#{app.debug(artifacts)}"
|
40
34
|
assert_output artifacts, expected_output if expected_output
|
41
35
|
end
|
42
36
|
|
43
37
|
def assert_failure(command, expected_output = nil)
|
44
38
|
artifacts = app.run(*Array(command))
|
45
|
-
assert !artifacts[:status].success?, "expected unsuccessful exit status\n\n#{debug(artifacts)}"
|
39
|
+
assert !artifacts[:status].success?, "expected unsuccessful exit status\n\n#{app.debug(artifacts)}"
|
46
40
|
assert_output artifacts, expected_output if expected_output
|
47
41
|
end
|
48
42
|
|
@@ -196,7 +190,7 @@ class AppTest < ActiveSupport::TestCase
|
|
196
190
|
|
197
191
|
test "binstub when spring is uninstalled" do
|
198
192
|
app.run! "gem uninstall --ignore-dependencies spring"
|
199
|
-
File.write(app.gemfile, app.gemfile.read.
|
193
|
+
File.write(app.gemfile, app.gemfile.read.gsub(/gem 'spring.*/, ""))
|
200
194
|
assert_success "bin/rake -T", stdout: "rake db:migrate"
|
201
195
|
end
|
202
196
|
|
@@ -296,16 +290,29 @@ CODE
|
|
296
290
|
assert_success "bin/rake", stdout: "test"
|
297
291
|
end
|
298
292
|
|
299
|
-
test "changing the Gemfile
|
293
|
+
test "changing the Gemfile works" do
|
300
294
|
assert_success %(bin/rails runner 'require "sqlite3"')
|
301
295
|
|
302
296
|
File.write(app.gemfile, app.gemfile.read.sub(%{gem 'sqlite3'}, %{# gem 'sqlite3'}))
|
303
|
-
app.bundle
|
304
|
-
|
305
297
|
app.await_reload
|
298
|
+
|
306
299
|
assert_failure %(bin/rails runner 'require "sqlite3"'), stderr: "sqlite3"
|
307
300
|
end
|
308
301
|
|
302
|
+
test "changing the Gemfile works when spring calls into itself" do
|
303
|
+
File.write(app.path("script.rb"), <<-CODE)
|
304
|
+
gemfile = Rails.root.join("Gemfile")
|
305
|
+
File.write(gemfile, "\#{gemfile.read}gem 'devise'\\n")
|
306
|
+
Bundler.with_clean_env do
|
307
|
+
system(#{app.env.inspect}, "bundle install")
|
308
|
+
end
|
309
|
+
output = `\#{Rails.root.join('bin/rails')} runner 'require "devise"; puts "done";'`
|
310
|
+
exit output == "done\n"
|
311
|
+
CODE
|
312
|
+
|
313
|
+
assert_success [%(bin/rails runner 'load Rails.root.join("script.rb")'), timeout: 60]
|
314
|
+
end
|
315
|
+
|
309
316
|
test "changing the environment between runs" do
|
310
317
|
File.write(app.application_config, "#{app.application_config.read}\nENV['BAR'] = 'bar'")
|
311
318
|
|
data/test/acceptance/helper.rb
CHANGED
@@ -57,8 +57,12 @@ module Spring
|
|
57
57
|
@stderr ||= IO.pipe
|
58
58
|
end
|
59
59
|
|
60
|
+
def log_file_path
|
61
|
+
path("tmp/spring.log")
|
62
|
+
end
|
63
|
+
|
60
64
|
def log_file
|
61
|
-
@log_file ||=
|
65
|
+
@log_file ||= log_file_path.open("w+")
|
62
66
|
end
|
63
67
|
|
64
68
|
def env
|
@@ -68,7 +72,7 @@ module Spring
|
|
68
72
|
"HOME" => user_home.to_s,
|
69
73
|
"RAILS_ENV" => nil,
|
70
74
|
"RACK_ENV" => nil,
|
71
|
-
"SPRING_LOG" =>
|
75
|
+
"SPRING_LOG" => log_file_path.to_s
|
72
76
|
}
|
73
77
|
end
|
74
78
|
|
@@ -202,6 +206,12 @@ module Spring
|
|
202
206
|
output
|
203
207
|
end
|
204
208
|
|
209
|
+
def debug(artifacts)
|
210
|
+
artifacts = artifacts.dup
|
211
|
+
artifacts.delete :status
|
212
|
+
dump_streams(artifacts.delete(:command), artifacts)
|
213
|
+
end
|
214
|
+
|
205
215
|
def await_reload
|
206
216
|
raise "no pid" if @application_pids.nil? || @application_pids.empty?
|
207
217
|
|
@@ -212,13 +222,15 @@ module Spring
|
|
212
222
|
|
213
223
|
def run!(*args)
|
214
224
|
artifacts = run(*args)
|
215
|
-
|
225
|
+
unless artifacts[:status].success?
|
226
|
+
raise "command failed\n\n#{debug(artifacts)}"
|
227
|
+
end
|
216
228
|
artifacts
|
217
229
|
end
|
218
230
|
|
219
231
|
def bundle
|
220
232
|
run! "(gem list bundler | grep bundler) || gem install bundler", timeout: nil
|
221
|
-
run! "bundle
|
233
|
+
run! "bundle update", timeout: nil
|
222
234
|
end
|
223
235
|
|
224
236
|
private
|
@@ -238,16 +250,23 @@ module Spring
|
|
238
250
|
@version_constraint = version_constraint
|
239
251
|
@version = RailsVersion.new(version_constraint.split(' ').last)
|
240
252
|
@application = Application.new(root)
|
253
|
+
@bundled = false
|
241
254
|
end
|
242
255
|
|
243
256
|
def root
|
244
|
-
"#{TEST_ROOT}/apps/rails-#{version.major}-#{version.minor}"
|
257
|
+
"#{TEST_ROOT}/apps/rails-#{version.major}-#{version.minor}-spring-#{Spring::VERSION}"
|
245
258
|
end
|
246
259
|
|
247
260
|
def system(command)
|
248
261
|
Kernel.system("#{command} > /dev/null") or raise "command failed: #{command}"
|
249
262
|
end
|
250
263
|
|
264
|
+
def bundle
|
265
|
+
return if @bundled
|
266
|
+
application.bundle
|
267
|
+
@bundled = true
|
268
|
+
end
|
269
|
+
|
251
270
|
# Sporadic SSL errors keep causing test failures so there are anti-SSL workarounds here
|
252
271
|
def generate
|
253
272
|
Bundler.with_clean_env do
|
@@ -263,6 +282,8 @@ module Spring
|
|
263
282
|
FileUtils.mkdir_p(application.user_home)
|
264
283
|
FileUtils.rm_rf(application.path("test/performance"))
|
265
284
|
|
285
|
+
File.write(application.gemfile, "#{application.gemfile.read}gem 'spring', '#{Spring::VERSION}'\n")
|
286
|
+
|
266
287
|
if version.needs_testunit?
|
267
288
|
File.write(application.gemfile, "#{application.gemfile.read}gem 'spring-commands-testunit'\n")
|
268
289
|
end
|
@@ -274,7 +295,7 @@ module Spring
|
|
274
295
|
end
|
275
296
|
end
|
276
297
|
|
277
|
-
|
298
|
+
bundle
|
278
299
|
application.run! "bundle exec rails g scaffold post title:string"
|
279
300
|
application.run! "bundle exec rake db:migrate db:test:clone"
|
280
301
|
end
|
@@ -287,7 +308,7 @@ module Spring
|
|
287
308
|
unless @installed
|
288
309
|
# Need to do this here too because the app may have been generated with
|
289
310
|
# a different ruby
|
290
|
-
|
311
|
+
bundle
|
291
312
|
|
292
313
|
system("gem build spring.gemspec 2>/dev/null")
|
293
314
|
application.run! "gem install ../../../spring-#{Spring::VERSION}.gem", timeout: nil
|
data/test/unit/watcher_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "helper"
|
2
2
|
require "tmpdir"
|
3
3
|
require "fileutils"
|
4
|
+
require "timeout"
|
4
5
|
require "active_support/core_ext/numeric/time"
|
5
6
|
require "spring/watcher"
|
6
7
|
require "spring/watcher/polling"
|
@@ -120,22 +121,25 @@ module WatcherTests
|
|
120
121
|
assert_stale
|
121
122
|
end
|
122
123
|
|
123
|
-
def
|
124
|
+
def test_on_stale
|
124
125
|
file = "#{@dir}/omg"
|
125
126
|
touch file, Time.now - 2.seconds
|
126
127
|
|
127
128
|
watcher.add file
|
128
129
|
watcher.start
|
129
130
|
|
130
|
-
|
131
|
+
stale = false
|
132
|
+
watcher.on_stale { stale = true }
|
131
133
|
|
132
|
-
|
133
|
-
sleep LATENCY * 3
|
134
|
-
touch file, Time.now
|
135
|
-
}
|
134
|
+
touch file, Time.now
|
136
135
|
|
137
|
-
|
138
|
-
assert
|
136
|
+
Timeout.timeout(1) { sleep 0.01 until stale }
|
137
|
+
assert stale
|
138
|
+
|
139
|
+
# Check that we only get notified once
|
140
|
+
stale = false
|
141
|
+
sleep LATENCY * 3
|
142
|
+
assert !stale
|
139
143
|
end
|
140
144
|
|
141
145
|
def test_add_relative_path
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spring
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.0.
|
4
|
+
version: 1.1.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Leighton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -55,8 +55,10 @@ files:
|
|
55
55
|
- Rakefile
|
56
56
|
- bin/spring
|
57
57
|
- lib/spring/application.rb
|
58
|
+
- lib/spring/application/boot.rb
|
58
59
|
- lib/spring/application_manager.rb
|
59
60
|
- lib/spring/binstub.rb
|
61
|
+
- lib/spring/boot.rb
|
60
62
|
- lib/spring/client.rb
|
61
63
|
- lib/spring/client/binstub.rb
|
62
64
|
- lib/spring/client/command.rb
|
@@ -92,7 +94,7 @@ files:
|
|
92
94
|
- test/unit/commands_test.rb
|
93
95
|
- test/unit/process_title_updater_test.rb
|
94
96
|
- test/unit/watcher_test.rb
|
95
|
-
homepage: http://github.com/
|
97
|
+
homepage: http://github.com/rails/spring
|
96
98
|
licenses:
|
97
99
|
- MIT
|
98
100
|
metadata: {}
|