Search code examples
rubyruby-on-rails-5pre-signed-urlnet-httpfaraday

"Faraday::ConnectionFailed: Broken pipe" when uploading Tempfile to S3 Presigned URL


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'

Solution

  • 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('/')