Skip to content

Commit 71e25f2

Browse files
committed
[fix] Ensure that jruby-rack leaves ENV vars and Gem.path in consistent state
As noted at jruby/warbler#575 for warbler, it is similarly possible for jruby-rack to leave ENV['GEM_PATH'] and Gem.path in an inconsistent state after it updates them. This would make any later Gem.clear_paths or Bundler usage (driven from the ENV vars) unpredictable. This happens because jruby-rack alters `ENV['GEM_PATH']``, in some cases but does not force Rubygems to recalculate Gem.paths. Normally this is fine, however if Gem.paths had already been used and cached prior to this mangling `Gem.path` will contain a "stale" value with respect to ENV. (e.g possibly missing the `GEM_HOME`/`Gem.default_dir` value, or retaining values from an old `GEM_PATH`. Clearing the paths is the only way to ensure things are consistent for later usage. In particular this currently can happen on JRuby 9.4+ because often `Gem.path` is already initialized by the time we boot, due to the use of default gems such as `stringio`. Rather than assume a given state, it would be better to ensure it is consistent by clearing paths whenever we touch `ENV['GEM_PATH']`.
1 parent f8b25bc commit 71e25f2

File tree

4 files changed

+57
-16
lines changed

4 files changed

+57
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
- Add missing block-only signature for debug logging
2525
- update (bundled) rack to 2.2.20
26+
- Ensure rack boot process leaves ENV['GEM_PATH'] and Gem.paths in a consistent state
2627

2728
## 1.2.5
2829

src/main/ruby/jruby/rack/booter.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ def adjust_gem_path
131131
else # 'jruby.rack.env.gem_path' "forced" to an explicit value
132132
ENV['GEM_PATH'] = set_gem_path
133133
end
134+
# Whenever we touch ENV['GEM_PATH`], ensure we clear any cached paths. All other cases should exit early.
135+
Gem.clear_paths if defined?(Gem.clear_paths)
134136
end
135137

136138
# @return whether to update Gem.path and/or the environment GEM_PATH

src/spec/ruby/jruby/rack/booter_spec.rb

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,24 @@
2020
end
2121

2222
before do
23-
@rack_env = ENV['RACK_ENV']
24-
@gem_path = Gem.path.to_a
25-
@env_gem_path = ENV['GEM_PATH']
23+
# start clean, in case another test was messing with paths
24+
Gem.clear_paths
25+
@original_rack_env = ENV['RACK_ENV']
26+
@original_gem_path = Gem.path.to_a
27+
@original_env_gem_path = ENV['GEM_PATH']
2628
end
2729

2830
after do
29-
@rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @rack_env
30-
Gem.path.replace(@gem_path)
31-
@env_gem_path.nil? ? ENV.delete('GEM_PATH') : ENV['GEM_PATH'] = @env_gem_path
31+
# Ensure everything is reset how it was
32+
@original_rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @original_rack_env
33+
@original_env_gem_path.nil? ? ENV.delete('GEM_PATH') : ENV['GEM_PATH'] = @original_env_gem_path
34+
Gem.clear_paths
35+
Gem.path.replace(@original_gem_path)
36+
37+
aggregate_failures("expected Gem.path to be restored after test") do
38+
expect(ENV['GEM_PATH']).to eq @original_env_gem_path
39+
expect(Gem.path).to eql @original_gem_path
40+
end
3241
end
3342

3443
it "should determine the public html root from the 'public.root' init parameter" do
@@ -93,58 +102,85 @@
93102
end
94103

95104
it "prepends gem_path to Gem.path (when configured to not mangle with ENV)" do
96-
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'false'
97105
Gem.path.replace ['/opt/gems']
106+
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'false'
107+
98108
booter.gem_path = "wsjar:file:/opt/deploy/sample.war!/WEB-INF/gems"
99109
booter.boot!
100110

101111
expect(Gem.path).to eql ['wsjar:file:/opt/deploy/sample.war!/WEB-INF/gems', '/opt/gems']
102112
end
103113

104114
it "prepends gem_path to Gem.path if not already present" do
105-
Gem.path.replace ["file:/home/gems", "/usr/local/gems"]
115+
ENV['GEM_PATH'] = "file:/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
116+
Gem.clear_paths
117+
106118
booter.gem_path = '/usr/local/gems'
107119
booter.boot!
108120

109-
expect(Gem.path).to eql ["file:/home/gems", "/usr/local/gems"]
121+
expect(Gem.path).to start_with ["file:/home/gems", "/usr/local/gems"]
122+
expect(ENV['GEM_PATH']).to eq "file:/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
110123
end
111124

112125
it "does not change Gem.path if gem_path empty" do
113-
Gem.path.replace ['/opt/gems']
126+
ENV['GEM_PATH'] = '/opt/gems'
127+
Gem.clear_paths
128+
114129
booter.gem_path = ""
115130
booter.boot!
116131

117-
expect(Gem.path).to eql ['/opt/gems']
132+
expect(Gem.path).to start_with ['/opt/gems']
133+
expect(ENV['GEM_PATH']).to eq '/opt/gems'
118134
end
119135

120136
it "prepends gem_path to ENV['GEM_PATH'] if jruby.rack.gem_path set to true" do
121137
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'true'
122138
ENV['GEM_PATH'] = '/opt/gems'
139+
Gem.clear_paths
123140
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF").and_return "/opt/deploy/sample.war!/WEB-INF"
124141
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF/gems").and_return "/opt/deploy/sample.war!/WEB-INF/gems"
125142

126143
booter.boot!
127144

128145
expect(ENV['GEM_PATH']).to eq "/opt/deploy/sample.war!/WEB-INF/gems#{File::PATH_SEPARATOR}/opt/gems"
146+
expect(Gem.path).to start_with ["/opt/deploy/sample.war!/WEB-INF/gems", "/opt/gems"]
129147
end
130148

131149
it "does not prepend gem_path to ENV['GEM_PATH'] if jruby.rack.gem_path set not set" do
132150
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return ''
133151
ENV['GEM_PATH'] = '/opt/gems'
152+
Gem.clear_paths
134153
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF").and_return "/opt/deploy/sample.war!/WEB-INF"
135154
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF/gems").and_return "/opt/deploy/sample.war!/WEB-INF/gems"
136155

137156
booter.boot!
138157

139158
expect(ENV['GEM_PATH']).to eq "/opt/gems"
159+
expect(Gem.path).to start_with ["/opt/gems"]
140160
end
141161

142162
it "prepends gem_path to ENV['GEM_PATH'] if not already present" do
143163
ENV['GEM_PATH'] = "/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
164+
Gem.clear_paths
144165
booter.gem_path = '/usr/local/gems'
145166
booter.boot!
146167

147168
expect(ENV['GEM_PATH']).to eq "/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
169+
expect(Gem.path).to start_with ["/home/gems", "/usr/local/gems"]
170+
end
171+
172+
it "keeps ENV['GEM_PATH'] when gem_path is nil" do
173+
ENV['GEM_PATH'] = '/usr/local/gems'
174+
Gem.clear_paths
175+
176+
booter.layout = layout = double('layout')
177+
allow(layout).to receive(:app_path).and_return '.'
178+
allow(layout).to receive(:public_path).and_return nil
179+
expect(layout).to receive(:gem_path).and_return nil
180+
booter.boot!
181+
182+
expect(ENV['GEM_PATH']).to eq "/usr/local/gems"
183+
expect(Gem.path).to start_with ["/usr/local/gems"]
148184
end
149185

150186
it "sets ENV['GEM_PATH'] to the value of gem_path if ENV['GEM_PATH'] is not present" do
@@ -156,6 +192,7 @@
156192
booter.boot!
157193

158194
expect(ENV['GEM_PATH']).to eq "/blah/gems"
195+
expect(Gem.path).to start_with ["/blah/gems"]
159196
end
160197

161198
before { $loaded_init_rb = nil }
@@ -209,11 +246,12 @@
209246
# at RUBY.boot!(classpath:/jruby/rack/booter.rb:105)
210247
# at RUBY.(root)(classpath:/jruby/rack/boot/rack.rb:10)
211248
app_dir = "#{File.absolute_path Dir.pwd}/sample.war!/WEB-INF"
212-
allow(File).to receive(:directory?).with(app_dir).and_return true
213249
allow(booter).to receive(:layout).and_return layout = double('layout')
214250
allow(layout).to receive(:app_path).and_return app_dir
215251
allow(layout).to receive(:gem_path)
216252
allow(layout).to receive(:public_path)
253+
allow(File).to receive(:directory?).and_wrap_original { |m, *args| m.call(*args) }
254+
expect(File).to receive(:directory?).with(app_dir).and_return true
217255

218256
booter.boot! # expect to_not raise_error
219257
end

src/spec/ruby/jruby/rack/rails_booter_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@
3030
end
3131

3232
before do
33-
@rails_env = ENV['RAILS_ENV']
34-
@rack_env = ENV['RACK_ENV']
33+
@original_rails_env = ENV['RAILS_ENV']
34+
@original_rack_env = ENV['RACK_ENV']
3535
end
3636

3737
after do
38-
@rails_env.nil? ? ENV.delete('RAILS_ENV') : ENV['RAILS_ENV'] = @rails_env
39-
@rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @rack_env
38+
@original_rails_env.nil? ? ENV.delete('RAILS_ENV') : ENV['RAILS_ENV'] = @original_rails_env
39+
@original_rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @original_rack_env
4040
end
4141

4242
it "should default rails path to /WEB-INF" do

0 commit comments

Comments
 (0)