spring 1.1.0.beta1 → 1.1.0.beta2
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.
- 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
|
-
[](https://travis-ci.org/rails/spring)
|
4
4
|
[](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: {}
|