spring 4.3.0 → 4.7.0
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/README.md +29 -3
- data/lib/spring/application.rb +57 -10
- data/lib/spring/application_manager.rb +2 -2
- data/lib/spring/client/binstub.rb +1 -1
- data/lib/spring/client/run.rb +50 -9
- data/lib/spring/configuration.rb +14 -0
- data/lib/spring/json.rb +1 -0
- data/lib/spring/server.rb +2 -0
- data/lib/spring/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a37d78748e084596d151cde2918d30799c8dfe9e10e029920ece9e9eee8118ad
|
|
4
|
+
data.tar.gz: c47e0897d3938b7350245854672812436505379cc498638c3eca9e2d52537fe6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cda176e1dd4d22cabb680cb85c4fd562c256f8fb3dc04f9064fc61f910cfb384f044f63bee74a424501505b62f80e0746f6c05bddf327c6be0150f12cc678a3f
|
|
7
|
+
data.tar.gz: e8b1675241b3fc42c52eabb9e2f501d4abae16c35ae3e3d19080de332f3d28b258b7245c127264326871f09c35c333a3b71fcfb8fbbfecdc87648a72e1328c14
|
data/README.md
CHANGED
|
@@ -16,8 +16,8 @@ boot it every time you run a test, rake task or migration.
|
|
|
16
16
|
|
|
17
17
|
## Compatibility
|
|
18
18
|
|
|
19
|
-
* Ruby versions: MRI
|
|
20
|
-
* Rails versions:
|
|
19
|
+
* Ruby versions: MRI 3.1+
|
|
20
|
+
* Rails versions: 7.1+
|
|
21
21
|
* Bundler v2.1+
|
|
22
22
|
|
|
23
23
|
Spring makes extensive use of `Process.fork`, so won't be able to
|
|
@@ -79,6 +79,31 @@ Spring manages. That setting is typically configured in
|
|
|
79
79
|
Note: in versions of Rails before 7.1, the setting is called `cache_classes`,
|
|
80
80
|
and it needs to be `false` for Spring to work.
|
|
81
81
|
|
|
82
|
+
#### Running Spring with reloading disabled (advanced, risky)
|
|
83
|
+
|
|
84
|
+
If you know what you are doing, and if every reloadable resource is configured
|
|
85
|
+
to materialize *lazily in the forked child*, not in the Spring server process,
|
|
86
|
+
then you can decide to run Spring without Rail's in-process reloaders.
|
|
87
|
+
|
|
88
|
+
To opt into this, set in `config/spring.rb`:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
Spring.dangerously_allow_disabling_reloading = true
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If you set this, you should make sure that you are NOT pre-loading reloadable
|
|
95
|
+
resource in the Spring server. One approach is to add boot-time assertions at
|
|
96
|
+
the end of `Spring.after_environment_load`, e.g.:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
Spring.after_environment_load do
|
|
100
|
+
raise "routes were drawn at boot" if Rails.application.routes_reloader.loaded
|
|
101
|
+
raise "i18n locales where loaded at boot" unless I18n.backend.instance_variable_get(:@translations).nil?
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
When unsure, leave this disabled.
|
|
106
|
+
|
|
82
107
|
### Usage
|
|
83
108
|
|
|
84
109
|
For this walkthrough I've generated a new Rails application, and run
|
|
@@ -334,7 +359,8 @@ server process is started, it can be used to add new top-level commands.
|
|
|
334
359
|
Spring must know how to find your Rails application. If you have a
|
|
335
360
|
normal app everything works out of the box. If you are working on a
|
|
336
361
|
project with a special setup (an engine for example), you must tell
|
|
337
|
-
Spring where your app is located
|
|
362
|
+
Spring where your app is located by specifying the below code in a
|
|
363
|
+
`config/spring.rb` file relative to your engines root directory :
|
|
338
364
|
|
|
339
365
|
```ruby
|
|
340
366
|
Spring.application_root = './test/dummy'
|
data/lib/spring/application.rb
CHANGED
|
@@ -101,16 +101,28 @@ module Spring
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
Rails::Application.initializer :ensure_reloading_is_enabled, group: :all do
|
|
104
|
+
next if Spring.dangerously_allow_disabling_reloading
|
|
105
|
+
|
|
104
106
|
if Rails.application.config.cache_classes
|
|
107
|
+
config_name, set_to = if Rails.application.config.respond_to?(:enable_reloading=)
|
|
108
|
+
["enable_reloading", "true"]
|
|
109
|
+
else
|
|
110
|
+
["cache_classes", "false"]
|
|
111
|
+
end
|
|
105
112
|
raise <<-MSG.strip_heredoc
|
|
106
113
|
Spring reloads, and therefore needs the application to have reloading enabled.
|
|
107
|
-
Please, set config
|
|
114
|
+
Please, set config.#{config_name} to #{set_to} in config/environments/#{Rails.env}.rb.
|
|
115
|
+
(If you understand the trade-offs and want to disable Rails' reloader anyway,
|
|
116
|
+
set `Spring.dangerously_allow_disabling_reloading = true` in config/spring.rb.)
|
|
108
117
|
MSG
|
|
109
118
|
end
|
|
110
119
|
end
|
|
111
120
|
|
|
112
121
|
require Spring.application_root_path.join("config", "environment")
|
|
113
122
|
|
|
123
|
+
invoke_after_environment_load_callbacks
|
|
124
|
+
preload_framework_base_classes
|
|
125
|
+
|
|
114
126
|
disconnect_database
|
|
115
127
|
|
|
116
128
|
@preloaded = :success
|
|
@@ -124,8 +136,9 @@ module Spring
|
|
|
124
136
|
|
|
125
137
|
if defined?(Rails) && Rails.application
|
|
126
138
|
watcher.add Rails.application.paths["config/initializers"]
|
|
127
|
-
Rails
|
|
128
|
-
|
|
139
|
+
rails_root = Rails.root.to_s
|
|
140
|
+
Rails::Engine.subclasses.each do |engine|
|
|
141
|
+
if engine.root.to_s.start_with?(rails_root)
|
|
129
142
|
watcher.add engine.paths["config/initializers"].expanded
|
|
130
143
|
end
|
|
131
144
|
end
|
|
@@ -136,6 +149,19 @@ module Spring
|
|
|
136
149
|
end
|
|
137
150
|
end
|
|
138
151
|
|
|
152
|
+
# Eagerly autoload framework base classes
|
|
153
|
+
FRAMEWORK_BASE_CLASSES = %w[
|
|
154
|
+
ActionMailer::Base
|
|
155
|
+
ActionController::Base
|
|
156
|
+
ActionController::API
|
|
157
|
+
].freeze
|
|
158
|
+
|
|
159
|
+
def preload_framework_base_classes
|
|
160
|
+
FRAMEWORK_BASE_CLASSES.each do |const|
|
|
161
|
+
Object.const_get(const) if Object.const_defined?(const)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
139
165
|
def eager_preload
|
|
140
166
|
with_pty do
|
|
141
167
|
# we can't see stderr and there could be issues when it's overflown
|
|
@@ -177,7 +203,7 @@ module Spring
|
|
|
177
203
|
client.puts(0) # preload success
|
|
178
204
|
rescue Exception
|
|
179
205
|
log "preload failed"
|
|
180
|
-
client.puts(1) # preload failure
|
|
206
|
+
ignore_client_disconnect { client.puts(1) } # preload failure
|
|
181
207
|
raise
|
|
182
208
|
end
|
|
183
209
|
end
|
|
@@ -238,15 +264,19 @@ module Spring
|
|
|
238
264
|
|
|
239
265
|
wait pid, streams, client
|
|
240
266
|
rescue Exception => e
|
|
241
|
-
|
|
267
|
+
if e.is_a?(Errno::EPIPE)
|
|
268
|
+
log "client disconnected (#{e.message}), ignoring command"
|
|
269
|
+
else
|
|
270
|
+
log "exception: #{e}"
|
|
271
|
+
end
|
|
242
272
|
manager.puts unless pid
|
|
243
273
|
|
|
244
274
|
if streams && !e.is_a?(SystemExit)
|
|
245
|
-
print_exception(stderr, e)
|
|
246
|
-
streams.each
|
|
275
|
+
ignore_client_disconnect { print_exception(stderr, e) }
|
|
276
|
+
streams.each { |stream| ignore_client_disconnect { stream.close } }
|
|
247
277
|
end
|
|
248
278
|
|
|
249
|
-
client.puts(1) if pid
|
|
279
|
+
ignore_client_disconnect { client.puts(1) if pid }
|
|
250
280
|
client.close
|
|
251
281
|
ensure
|
|
252
282
|
# Redirect STDOUT and STDERR to prevent from keeping the original FDs
|
|
@@ -294,6 +324,12 @@ module Spring
|
|
|
294
324
|
end
|
|
295
325
|
end
|
|
296
326
|
|
|
327
|
+
def invoke_after_environment_load_callbacks
|
|
328
|
+
Spring.after_environment_load_callbacks.each do |callback|
|
|
329
|
+
callback.call
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
297
333
|
def loaded_application_features
|
|
298
334
|
root = Spring.application_root_path.to_s
|
|
299
335
|
$LOADED_FEATURES.select { |f| f.start_with?(root) }
|
|
@@ -320,6 +356,7 @@ module Spring
|
|
|
320
356
|
if $!
|
|
321
357
|
lib = File.expand_path("..", __FILE__)
|
|
322
358
|
$!.backtrace.reject! { |line| line.start_with?(lib) } unless $!.backtrace.frozen?
|
|
359
|
+
$!.backtrace_locations.reject! { |line| line.path&.start_with?(lib) } unless $!.backtrace_locations.frozen?
|
|
323
360
|
end
|
|
324
361
|
end
|
|
325
362
|
end
|
|
@@ -331,6 +368,7 @@ module Spring
|
|
|
331
368
|
if $!
|
|
332
369
|
lib = File.expand_path("..", __FILE__)
|
|
333
370
|
$!.backtrace.reject! { |line| line.start_with?(lib) } unless $!.backtrace.frozen?
|
|
371
|
+
$!.backtrace_locations.reject! { |line| line.path&.start_with?(lib) } unless $!.backtrace_locations.frozen?
|
|
334
372
|
end
|
|
335
373
|
end
|
|
336
374
|
end
|
|
@@ -370,10 +408,10 @@ module Spring
|
|
|
370
408
|
Spring.failsafe_thread {
|
|
371
409
|
begin
|
|
372
410
|
_, status = Process.wait2 pid
|
|
373
|
-
log "#{pid} exited with #{status.exitstatus}"
|
|
411
|
+
log "#{pid} exited with #{status.exitstatus || status.inspect}"
|
|
374
412
|
|
|
375
413
|
streams.each(&:close)
|
|
376
|
-
client.puts(status.exitstatus)
|
|
414
|
+
client.puts(status.exitstatus || status.to_i)
|
|
377
415
|
client.close
|
|
378
416
|
ensure
|
|
379
417
|
@mutex.synchronize { @waiting.delete pid }
|
|
@@ -395,6 +433,15 @@ module Spring
|
|
|
395
433
|
|
|
396
434
|
private
|
|
397
435
|
|
|
436
|
+
# Tolerate Errno::EPIPE on writes to the client socket. Once the client
|
|
437
|
+
# disconnects, every subsequent write to it raises — that's expected
|
|
438
|
+
# during `serve`'s status reporting; we'd be talking to a process
|
|
439
|
+
# that's gone.
|
|
440
|
+
def ignore_client_disconnect
|
|
441
|
+
yield
|
|
442
|
+
rescue Errno::EPIPE
|
|
443
|
+
end
|
|
444
|
+
|
|
398
445
|
def active_record_configured?
|
|
399
446
|
defined?(ActiveRecord::Base) && ActiveRecord::Base.configurations.any?
|
|
400
447
|
end
|
|
@@ -42,7 +42,7 @@ module Spring
|
|
|
42
42
|
if alive?
|
|
43
43
|
begin
|
|
44
44
|
yield
|
|
45
|
-
rescue Errno::ECONNRESET, Errno::EPIPE
|
|
45
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::EINVAL
|
|
46
46
|
# The child has died but has not been collected by the wait thread yet,
|
|
47
47
|
# so start a new child and try again.
|
|
48
48
|
log "child dead; starting"
|
|
@@ -102,7 +102,7 @@ module Spring
|
|
|
102
102
|
"RACK_ENV" => app_env,
|
|
103
103
|
"SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV),
|
|
104
104
|
"SPRING_PRELOAD" => preload ? "1" : "0",
|
|
105
|
-
"SPRING_SPAWN_ENV" => JSON.dump(spawn_env),
|
|
105
|
+
"SPRING_SPAWN_ENV" => JSON.dump(spawn_env.compact),
|
|
106
106
|
**spawn_env,
|
|
107
107
|
},
|
|
108
108
|
"ruby",
|
|
@@ -26,7 +26,7 @@ module Spring
|
|
|
26
26
|
if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"])
|
|
27
27
|
require "bundler"
|
|
28
28
|
|
|
29
|
-
Bundler.
|
|
29
|
+
Bundler.definition.requested_specs.find { |spec| spec.name == "spring" }&.tap do |spring|
|
|
30
30
|
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
|
|
31
31
|
gem "spring", spring.version
|
|
32
32
|
require "spring/binstub"
|
data/lib/spring/client/run.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Spring
|
|
|
6
6
|
module Client
|
|
7
7
|
class Run < Command
|
|
8
8
|
FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO WINCH) & Signal.list.keys
|
|
9
|
+
ServerReadTimeout = Class.new(StandardError)
|
|
9
10
|
|
|
10
11
|
attr_reader :server
|
|
11
12
|
|
|
@@ -120,13 +121,27 @@ module Spring
|
|
|
120
121
|
end
|
|
121
122
|
|
|
122
123
|
def verify_server_version
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
begin
|
|
125
|
+
line = read_server_line
|
|
126
|
+
rescue ServerReadTimeout
|
|
127
|
+
if waiting_for_server_boot?
|
|
128
|
+
begin
|
|
129
|
+
# Try again, but with same timeout as booting, as server might still be booting
|
|
130
|
+
# from another client starting it.
|
|
131
|
+
line = read_server_line(Spring.boot_timeout)
|
|
132
|
+
rescue ServerReadTimeout
|
|
133
|
+
reboot_or_raise_connection_error
|
|
134
|
+
return
|
|
135
|
+
end
|
|
136
|
+
else
|
|
137
|
+
reboot_or_raise_connection_error
|
|
138
|
+
return
|
|
139
|
+
end
|
|
125
140
|
end
|
|
126
141
|
|
|
127
|
-
line
|
|
128
|
-
|
|
129
|
-
|
|
142
|
+
if line.nil?
|
|
143
|
+
reboot_or_raise_connection_error
|
|
144
|
+
return
|
|
130
145
|
end
|
|
131
146
|
|
|
132
147
|
server_version = line.chomp
|
|
@@ -145,6 +160,25 @@ module Spring
|
|
|
145
160
|
end
|
|
146
161
|
end
|
|
147
162
|
|
|
163
|
+
def read_server_line(timeout = Spring.connect_timeout)
|
|
164
|
+
raise ServerReadTimeout if IO.select([server], [], [], timeout).nil?
|
|
165
|
+
|
|
166
|
+
server.gets
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def waiting_for_server_boot?
|
|
170
|
+
!server_booted? && env.server_running?
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def reboot_or_raise_connection_error
|
|
174
|
+
if server_booted?
|
|
175
|
+
raise "Error connecting to Spring server"
|
|
176
|
+
else
|
|
177
|
+
stop_server
|
|
178
|
+
cold_run
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
148
182
|
def connect_to_application(client)
|
|
149
183
|
server.send_io client
|
|
150
184
|
send_json server, "args" => args, "default_rails_env" => default_rails_env, "spawn_env" => spawn_env, "reset_env" => reset_env
|
|
@@ -184,11 +218,16 @@ module Spring
|
|
|
184
218
|
suspend_resume_on_tstp_cont(pid)
|
|
185
219
|
|
|
186
220
|
forward_signals(application)
|
|
187
|
-
status = application.read
|
|
221
|
+
status = application.read
|
|
222
|
+
log "got exit status #{status.inspect}"
|
|
188
223
|
|
|
189
|
-
|
|
224
|
+
# Status should always be an integer. If it is empty, something unexpected must have happened to the server.
|
|
225
|
+
if status.to_s.strip.empty?
|
|
226
|
+
log "unexpected empty exit status, app crashed?"
|
|
227
|
+
exit 1
|
|
228
|
+
end
|
|
190
229
|
|
|
191
|
-
exit status
|
|
230
|
+
exit status.to_i
|
|
192
231
|
else
|
|
193
232
|
log "got no pid"
|
|
194
233
|
exit 1
|
|
@@ -249,7 +288,9 @@ module Spring
|
|
|
249
288
|
end
|
|
250
289
|
|
|
251
290
|
def spawn_env
|
|
252
|
-
|
|
291
|
+
Spring.spawn_on_env.to_h do |key|
|
|
292
|
+
[key, ENV[key]]
|
|
293
|
+
end
|
|
253
294
|
end
|
|
254
295
|
end
|
|
255
296
|
end
|
data/lib/spring/configuration.rb
CHANGED
|
@@ -3,11 +3,17 @@ require "spring/errors"
|
|
|
3
3
|
module Spring
|
|
4
4
|
@connect_timeout = 5
|
|
5
5
|
@boot_timeout = 20
|
|
6
|
+
@dangerously_allow_disabling_reloading = false
|
|
6
7
|
|
|
7
8
|
class << self
|
|
8
9
|
attr_accessor :application_root, :connect_timeout, :boot_timeout
|
|
9
10
|
attr_writer :quiet
|
|
10
11
|
|
|
12
|
+
# Opt-in: skip the `:ensure_reloading_is_enabled` initializer so Spring
|
|
13
|
+
# boots with `config.enable_reloading = false`. Default `false`. See
|
|
14
|
+
# README "Running Spring with reloading disabled" for what this gives up.
|
|
15
|
+
attr_accessor :dangerously_allow_disabling_reloading
|
|
16
|
+
|
|
11
17
|
def gemfile
|
|
12
18
|
require "bundler"
|
|
13
19
|
|
|
@@ -35,6 +41,14 @@ module Spring
|
|
|
35
41
|
after_fork_callbacks << block
|
|
36
42
|
end
|
|
37
43
|
|
|
44
|
+
def after_environment_load_callbacks
|
|
45
|
+
@after_environment_load_callbacks ||= []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def after_environment_load(&block)
|
|
49
|
+
after_environment_load_callbacks << block
|
|
50
|
+
end
|
|
51
|
+
|
|
38
52
|
def spawn_on_env
|
|
39
53
|
@spawn_on_env ||= []
|
|
40
54
|
end
|
data/lib/spring/json.rb
CHANGED
data/lib/spring/server.rb
CHANGED
data/lib/spring/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spring
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jon Leighton
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: Preloads your application so things like console, rake and tests run
|
|
13
13
|
faster
|
|
@@ -56,6 +56,7 @@ licenses:
|
|
|
56
56
|
- MIT
|
|
57
57
|
metadata:
|
|
58
58
|
rubygems_mfa_required: 'true'
|
|
59
|
+
changelog_uri: https://github.com/rails/spring/blob/main/CHANGELOG.md
|
|
59
60
|
rdoc_options: []
|
|
60
61
|
require_paths:
|
|
61
62
|
- lib
|
|
@@ -63,14 +64,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
63
64
|
requirements:
|
|
64
65
|
- - ">="
|
|
65
66
|
- !ruby/object:Gem::Version
|
|
66
|
-
version:
|
|
67
|
+
version: 3.1.0
|
|
67
68
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
69
|
requirements:
|
|
69
70
|
- - ">="
|
|
70
71
|
- !ruby/object:Gem::Version
|
|
71
72
|
version: '0'
|
|
72
73
|
requirements: []
|
|
73
|
-
rubygems_version: 3.6.
|
|
74
|
+
rubygems_version: 3.6.7
|
|
74
75
|
specification_version: 4
|
|
75
76
|
summary: Rails application preloader
|
|
76
77
|
test_files: []
|