Skip to content

Commit 8bbcbdf

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 23f527e commit 8bbcbdf

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
@@ -2,6 +2,7 @@
22

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

67
## 1.2.5
78

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

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

142144
# @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
@@ -17,15 +17,24 @@
1717
after(:all) { JRuby::Rack.context = nil }
1818

1919
before do
20-
@rack_env = ENV['RACK_ENV']
21-
@gem_path = Gem.path.to_a
22-
@env_gem_path = ENV['GEM_PATH']
20+
# start clean, in case another test was messing with paths
21+
Gem.clear_paths
22+
@original_rack_env = ENV['RACK_ENV']
23+
@original_gem_path = Gem.path.to_a
24+
@original_env_gem_path = ENV['GEM_PATH']
2325
end
2426

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

3140
it "should determine the public html root from the 'public.root' init parameter" do
@@ -90,58 +99,85 @@
9099
end
91100

92101
it "prepends gem_path to Gem.path (when configured to not mangle with ENV)" do
93-
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'false'
94102
Gem.path.replace ['/opt/gems']
103+
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'false'
104+
95105
booter.gem_path = "wsjar:file:/opt/deploy/sample.war!/WEB-INF/gems"
96106
booter.boot!
97107

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

101111
it "prepends gem_path to Gem.path if not already present" do
102-
Gem.path.replace ["file:/home/gems", "/usr/local/gems"]
112+
ENV['GEM_PATH'] = "file:/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
113+
Gem.clear_paths
114+
103115
booter.gem_path = '/usr/local/gems'
104116
booter.boot!
105117

106-
expect(Gem.path).to eql ["file:/home/gems", "/usr/local/gems"]
118+
expect(Gem.path).to start_with ["file:/home/gems", "/usr/local/gems"]
119+
expect(ENV['GEM_PATH']).to eq "file:/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
107120
end
108121

109122
it "does not change Gem.path if gem_path empty" do
110-
Gem.path.replace ['/opt/gems']
123+
ENV['GEM_PATH'] = '/opt/gems'
124+
Gem.clear_paths
125+
111126
booter.gem_path = ""
112127
booter.boot!
113128

114-
expect(Gem.path).to eql ['/opt/gems']
129+
expect(Gem.path).to start_with ['/opt/gems']
130+
expect(ENV['GEM_PATH']).to eq '/opt/gems'
115131
end
116132

117133
it "prepends gem_path to ENV['GEM_PATH'] if jruby.rack.gem_path set to true" do
118134
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'true'
119135
ENV['GEM_PATH'] = '/opt/gems'
136+
Gem.clear_paths
120137
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF").and_return "/opt/deploy/sample.war!/WEB-INF"
121138
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF/gems").and_return "/opt/deploy/sample.war!/WEB-INF/gems"
122139

123140
booter.boot!
124141

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

128146
it "does not prepend gem_path to ENV['GEM_PATH'] if jruby.rack.gem_path set not set" do
129147
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return ''
130148
ENV['GEM_PATH'] = '/opt/gems'
149+
Gem.clear_paths
131150
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF").and_return "/opt/deploy/sample.war!/WEB-INF"
132151
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF/gems").and_return "/opt/deploy/sample.war!/WEB-INF/gems"
133152

134153
booter.boot!
135154

136155
expect(ENV['GEM_PATH']).to eq "/opt/gems"
156+
expect(Gem.path).to start_with ["/opt/gems"]
137157
end
138158

139159
it "prepends gem_path to ENV['GEM_PATH'] if not already present" do
140160
ENV['GEM_PATH'] = "/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
161+
Gem.clear_paths
141162
booter.gem_path = '/usr/local/gems'
142163
booter.boot!
143164

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

147183
it "sets ENV['GEM_PATH'] to the value of gem_path if ENV['GEM_PATH'] is not present" do
@@ -153,6 +189,7 @@
153189
booter.boot!
154190

155191
expect(ENV['GEM_PATH']).to eq "/blah/gems"
192+
expect(Gem.path).to start_with ["/blah/gems"]
156193
end
157194

158195
it "creates a logger that writes messages to the servlet context (by default)" do
@@ -214,11 +251,12 @@
214251
# at RUBY.boot!(classpath:/jruby/rack/booter.rb:105)
215252
# at RUBY.(root)(classpath:/jruby/rack/boot/rack.rb:10)
216253
app_dir = "#{File.absolute_path Dir.pwd}/sample.war!/WEB-INF"
217-
allow(File).to receive(:directory?).with(app_dir).and_return true
218254
allow(booter).to receive(:layout).and_return layout = double('layout')
219255
allow(layout).to receive(:app_path).and_return app_dir
220256
allow(layout).to receive(:gem_path)
221257
allow(layout).to receive(:public_path)
258+
allow(File).to receive(:directory?).and_wrap_original { |m, *args| m.call(*args) }
259+
expect(File).to receive(:directory?).with(app_dir).and_return true
222260

223261
booter.boot! # expect to_not raise_error
224262
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)