In my Ruby on Rails application, I need to upload large (~30MB or greater) MP4 video assets to an Amazon S3 Presigned URL using the Faraday
Ruby gem. This operation frequently fails with a Faraday::ConnectionFailed: Broken pipe
error. I want to understand why exactly this happens and how to address it, as I need to make this operation much more reliable.
The video asset is stored as a Tempfile
and streamed out to a PUT
request to the S3 presigned URL.
This failure does not occur 100% of the time. This operation often succeeds without this failure, and also will succeed often after a failure-induced retry. If I had to say, failures happen 50% of the time.
Ruby 2.3.1
Rails 5.0.0
Faraday 0.17.3
(adapter = net/http
. I have tried changing adapter to httpclient
but results in a similar error HTTPClient::KeepAliveDisconnected: Broken pipe
. I prefer to stick with net/http
.)
Relevant Code
headers = { content_type: 'video/mp4', content_length: tempfile.size.to_s }
conn = Faraday.new(url: presigned_url)
resp = conn.put(presigned_url_path, tempfile, headers)
From online research, I see that a timeout may be at play so I tried passing the following block to the conn.put
request. It didn't seem to help.
{ |req| req.options.timeout = 180 }
Full Stack Trace
WARN: Faraday::ConnectionFailed: Broken pipe
WARN: /usr/local/lib/ruby/2.3.0/openssl/buffering.rb:322:in `syswrite'
/usr/local/lib/ruby/2.3.0/openssl/buffering.rb:322:in `do_write'
/usr/local/lib/ruby/2.3.0/openssl/buffering.rb:340:in `write'
/usr/local/lib/ruby/2.3.0/net/http/generic_request.rb:206:in `copy_stream'
/usr/local/lib/ruby/2.3.0/net/http/generic_request.rb:206:in `send_request_with_body_stream'
/usr/local/lib/ruby/2.3.0/net/http/generic_request.rb:123:in `exec'
/usr/local/bundle/gems/aws-sdk-core-3.15.0/lib/seahorse/client/net_http/patches.rb:28:in `block in new_transport_request'
/usr/local/bundle/gems/aws-sdk-core-3.15.0/lib/seahorse/client/net_http/patches.rb:27:in `catch'
/usr/local/bundle/gems/aws-sdk-core-3.15.0/lib/seahorse/client/net_http/patches.rb:27:in `new_transport_request'
/usr/local/lib/ruby/2.3.0/net/http.rb:1407:in `request'
/usr/local/lib/ruby/2.3.0/net/http.rb:1400:in `block in request'
/usr/local/lib/ruby/2.3.0/net/http.rb:853:in `start'
/usr/local/lib/ruby/2.3.0/net/http.rb:1398:in `request'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/adapter/net_http.rb:87:in `perform_request'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/adapter/net_http.rb:43:in `block in call'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/adapter/net_http.rb:92:in `with_net_http_connection'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/adapter/net_http.rb:38:in `call'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/request/url_encoded.rb:15:in `call'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/rack_builder.rb:143:in `build_response'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/connection.rb:387:in `run_request'
/usr/local/bundle/gems/faraday-0.17.3/lib/faraday/connection.rb:175:in `put'
It seems your error is caused by network interruption. Broken pipe is a specific error that occurs when the client tries to read data from a socket that has been closed by the server.
There are a few things you can try to fix this issue:
1. Increase the timeout value.
The timeout value is the amount of time that the client will wait for a response from the server before giving up.
2. Implement a retry mechanism.
You can use Faraday Retry plugin to automatically retry failed requests, but you need to ensure compatibility with Faraday version 0.17.3.
require 'faraday'
require 'faraday/retry'
retry_options = {
max: 2,
interval: 0.05,
interval_randomness: 0.5,
backoff_factor: 2
}
conn = Faraday.new(...) do |f|
f.request :retry, retry_options
#...
end
conn.get('/')