New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix leaky file descriptors and reuse socket for persistent connections #976
Conversation
When explicitly calling `Net::HTTP#start`, a stub socket should be initialized.
When using a persistent connection, new sockets will be created for every single request and they are never closed. This commit makes it so that the current socket is closed before a new one is connected.
When using a persistent connection, the point is to reuse the underlying socket. Prior to this commit, the socket would be thrown away before each request unless `net_http_connect_on_start` is `true`. This commit will preserve the previously started socket if the socket represents an actual connection.
👍 I have also been experiencing this issue, this seemed to do the trick for me. |
Hi @rzane. Thank you for the PR with very descriptive commit messages. It's way cleaner and understandable now. 👍 |
Hi, This change resulted in a new error for my test suite. I tracked it down to commit 18dc8a9. The error appears as shown below. At first glance it looks like the new stub objects are missing some methods.
|
@@ -119,26 +114,21 @@ def start_without_connect | |||
raise IOError, 'HTTP session already opened' if @started | |||
if block_given? | |||
begin | |||
@socket = Net::HTTP.socket_type.new |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, Net::HTTP.socket_type
is obsolete:
@jdufresne thank you for reporting. I wonder if this is related to Ruby 3.1 or happens in earlier versions as well. I guess it's not easy for you to provide a code sample or spec to reproduce it, since it's active merchant? Am I right that it happens when the request is stubbed and not for the real request? Perhaps StubIO should have more methods implemented, but then why has it not been happening before. Perhaps StubIO was not used. @rzane any ideas? |
That is right. This only occurs in the test suite when the request is stubbed.
IIUC, StubIO wasn't introduced until 18dc8a9 which is why it was not used before. This has only been included in the most recent release. Downgrading fixes the issue for me. Adding the two methods
|
StubIO was introduced way ago c4b232b but obviously now it's being set in more scenarios than before and I wonder what are these scenarios, where despite stubbed request, normal socket was used instead of stubbed one with StubIO. I would have thought that StubIO was always used for stubbed requests, but obviously not. |
The issue @jdufresne is seeing is coming from the fact that ActiveMerchant are getting information from the Previously, they actually had to work around the fact that WebMock was not assigning I do think WebMock should act like Net::HTTP as much as possible. Therefore, I think the solution here is to add more methods to the StubIO, which as you said @bblimke, has been around forever.
|
Here is a minified test case that can be used to dig deeper. The following script works with 3.15 but not 3.16. require 'activemerchant'
require 'webmock'
include WebMock::API
WebMock.enable!
stub_request(:post, "https://api2.authorize.net/xml/v1/request.api")
ActiveMerchant::Billing::Base.mode = 'test'
credit_card = ActiveMerchant::Billing::CreditCard.new({
number: '4111111111111111',
month: '11',
year: '25',
first_name: 'John',
last_name: 'Doe',
verification_value: '111',
})
gateway = ActiveMerchant::Billing::AuthorizeNetGateway.new(
login: 'test',
password: 'test',
)
gateway.authorize(1000, credit_card) |
@rzane thank you for the investigation and explanation. @jdufresne thank you for the code sample. Extending StubIO to the following form works:
I'm not sure this is the right approach though. According to OpenSSL docs, I think the right approach should be returning nil, since there is no cipher, |
Take the following example:
This example would create 3 sockets and only close one of them. That's because
start_with_connect_without_finish
spawns a new connection for each request, and never closes the previous one.This PR also makes it so that when using a persistent connection, the
@socket
will be reused across requests. I believe this should totally fix the common "gotcha" that people encounter when combining WebMock with Capybara without needing to enablenet_http_connect_on_start
.I'd recommend reviewing this PR commit-by-commit. I tried to provide pretty detailed explanations for the changes as I made them.