Skip to content

Commit 3c90d67

Browse files
fix: do not cache EOF-backed Excon socket errors in RegistryClient (#15002)
* fix: do not cache EOF-backed Excon socket errors in RegistryClient * test: escape unreachable_url in regex expectations
1 parent eeb0516 commit 3c90d67

2 files changed

Lines changed: 64 additions & 29 deletions

File tree

common/lib/dependabot/registry_client.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def self.get(url:, headers: {}, options: {})
3636
retry_interval: 5
3737
)
3838
rescue Excon::Error::Timeout, Excon::Error::Socket => e
39-
cache_error(url, e)
39+
cache_error(url, e) if cacheable_error?(e)
4040
raise e
4141
end
4242

@@ -57,7 +57,7 @@ def self.head(url:, headers: {}, options: {})
5757
**SharedHelpers.excon_defaults({ headers: headers }.merge(options))
5858
)
5959
rescue Excon::Error::Timeout, Excon::Error::Socket => e
60-
cache_error(url, e)
60+
cache_error(url, e) if cacheable_error?(e)
6161
raise e
6262
end
6363

@@ -77,5 +77,17 @@ def self.clear_cache!
7777
host = URI(url).host
7878
@cached_errors.fetch(host, nil)
7979
end
80+
81+
sig { params(error: CachedErrorType).returns(T::Boolean) }
82+
private_class_method def self.cacheable_error?(error)
83+
if error.is_a?(Excon::Error::Socket)
84+
case error.socket_error
85+
when EOFError
86+
return false
87+
end
88+
end
89+
90+
true
91+
end
8092
end
8193
end

common/spec/dependabot/registry_client_spec.rb

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,12 @@
157157

158158
describe "exception caching" do
159159
let(:unreachable_url) { "https://example.local" }
160+
let(:escaped_unreachable_url) { Regexp.escape(unreachable_url) }
160161

161162
before do
162163
described_class.clear_cache!
163-
allow(Excon).to receive(:get).with(/#{unreachable_url}/, anything).and_raise(error)
164-
allow(Excon).to receive(:head).with(/#{unreachable_url}/, anything).and_raise(error)
164+
allow(Excon).to receive(:get).with(/#{escaped_unreachable_url}/, anything).and_raise(error)
165+
allow(Excon).to receive(:head).with(/#{escaped_unreachable_url}/, anything).and_raise(error)
165166
end
166167

167168
describe "when Excon times out internally" do
@@ -185,38 +186,60 @@
185186
end
186187

187188
describe "when Excon encounters a socket error" do
188-
let(:error) { Excon::Error::Socket.new(EOFError.new) }
189+
context "with EOFError" do
190+
let(:error) { Excon::Error::Socket.new(EOFError.new) }
189191

190-
it "only attempts to reach it once via get and then plays back the first error" do
191-
expect(Excon).to receive(:get).with(unreachable_url, anything).once
192+
it "does not cache get failures" do
193+
expect(Excon).to receive(:get).with(/#{escaped_unreachable_url}/, anything).exactly(3).times
192194

193-
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
194-
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
195-
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
196-
end
195+
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
196+
expect { described_class.get(url: "#{unreachable_url}/foos") }.to raise_error(Excon::Error::Socket)
197+
expect { described_class.get(url: "#{unreachable_url}/foos/bars") }.to raise_error(Excon::Error::Socket)
198+
end
197199

198-
it "replays the first get error for the host on any request path" do
199-
expect(Excon).to receive(:get).with(unreachable_url, anything).once
200+
it "does not cache head failures" do
201+
expect(Excon).to receive(:head).with(/#{escaped_unreachable_url}/, anything).exactly(3).times
200202

201-
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
202-
expect { described_class.get(url: "#{unreachable_url}/foos") }.to raise_error(Excon::Error::Socket)
203-
expect { described_class.get(url: "#{unreachable_url}/foos/bars") }.to raise_error(Excon::Error::Socket)
203+
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
204+
expect { described_class.head(url: "#{unreachable_url}/foos") }.to raise_error(Excon::Error::Socket)
205+
expect { described_class.head(url: "#{unreachable_url}/foos/bars") }.to raise_error(Excon::Error::Socket)
206+
end
204207
end
205208

206-
it "only attempts to reach it once via head and then plays back the first error" do
207-
expect(Excon).to receive(:head).with(unreachable_url, anything).once
209+
context "with non-EOF socket errors" do
210+
let(:error) { Excon::Error::Socket.new(SocketError.new("getaddrinfo failed")) }
208211

209-
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
210-
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
211-
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
212-
end
212+
it "only attempts to reach it once via get and then plays back the first error" do
213+
expect(Excon).to receive(:get).with(unreachable_url, anything).once
214+
215+
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
216+
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
217+
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
218+
end
213219

214-
it "replays the first head error for the host on any request path" do
215-
expect(Excon).to receive(:head).with(unreachable_url, anything).once
220+
it "replays the first get error for the host on any request path" do
221+
expect(Excon).to receive(:get).with(unreachable_url, anything).once
216222

217-
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
218-
expect { described_class.head(url: "#{unreachable_url}/foos") }.to raise_error(Excon::Error::Socket)
219-
expect { described_class.head(url: "#{unreachable_url}/foos/bars") }.to raise_error(Excon::Error::Socket)
223+
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
224+
expect { described_class.get(url: "#{unreachable_url}/foos") }.to raise_error(Excon::Error::Socket)
225+
expect { described_class.get(url: "#{unreachable_url}/foos/bars") }.to raise_error(Excon::Error::Socket)
226+
end
227+
228+
it "only attempts to reach it once via head and then plays back the first error" do
229+
expect(Excon).to receive(:head).with(unreachable_url, anything).once
230+
231+
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
232+
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
233+
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
234+
end
235+
236+
it "replays the first head error for the host on any request path" do
237+
expect(Excon).to receive(:head).with(unreachable_url, anything).once
238+
239+
expect { described_class.head(url: unreachable_url) }.to raise_error(Excon::Error::Socket)
240+
expect { described_class.head(url: "#{unreachable_url}/foos") }.to raise_error(Excon::Error::Socket)
241+
expect { described_class.head(url: "#{unreachable_url}/foos/bars") }.to raise_error(Excon::Error::Socket)
242+
end
220243
end
221244
end
222245

@@ -228,7 +251,7 @@
228251
let(:error) { error_class.new(error_message) }
229252

230253
it "does not cache anything" do
231-
expect(Excon).to receive(:get).with(/#{unreachable_url}/, anything)
254+
expect(Excon).to receive(:get).with(/#{escaped_unreachable_url}/, anything)
232255

233256
expect { described_class.get(url: unreachable_url) }.to raise_error(error_class)
234257
expect { described_class.get(url: "#{unreachable_url}/foos") }.to raise_error(error_class)
@@ -242,7 +265,7 @@
242265
let(:error) { Excon::Error.new("Boom!") }
243266

244267
it "does not cache anything" do
245-
expect(Excon).to receive(:get).with(/#{unreachable_url}/, anything)
268+
expect(Excon).to receive(:get).with(/#{escaped_unreachable_url}/, anything)
246269

247270
expect { described_class.get(url: unreachable_url) }.to raise_error(Excon::Error)
248271
expect { described_class.get(url: "#{unreachable_url}/foos") }.to raise_error(Excon::Error)

0 commit comments

Comments
 (0)