Search code examples
ruby-on-railsnet-sftp

Net::SFTP connection not closing


I have a Ruby on Rails (Rails 3.2.14 and ruby 1.9.3) application that uploads 2 files to a remote SFTP server. The SFTP code is:

require 'net/sftp'
Rails.logger.info("Creating SFTP connection")
uri = URI.parse('sftp://'+ host)
Net::SFTP.start(uri.host,'user', :password=>'password',:port=>port) do |sftp|
    Rails.logger.info("SFTP Connection created, uploading files.")
    sftp.upload!("public/file1.txt", "./file1.txt")
    Rails.logger.info("First file uploaded.")
    sftp.upload!("file2.txt", "./file2.txt")
    Rails.logger.info("Both files uploaded, terminating connection.")
end
Rails.logger.info("Connection terminated.")

Both files are uploading properly to the remote server, but the connection doesn't seem to close. I keep getting an error when I execute this function and on analyzing my console, I see that the "Both files uploaded, terminating connection." logger message is running, but nothing after that. I've tried using

sftp.close(:handle)
sftp.close!(:handle)
#and
sftp.close_connection()

but none of them are working. Any idea on why this is happening and how I can rectify it? I'm running this through a single instance Engine Yard cloud server.

EDIT These are the last few lines in my log: Creating SFTP connection SFTP Connection created, uploading files. First file uploaded. Both files uploaded, terminating connection.

After that, nothing. When viewing my log with the 'tail -f' command, the log goes up to that last line, and the app redirects to the internal server error page.


Solution

  • Short answer

    Net::SFTP.start('host', 'user', password: 'pass', port: 22) do |sftp|
    
      # Do stuff
    
    end
    

    is equivalent to :

    session = Net::SSH.start('host', 'user', password: 'pass', port: 22)
    sftp = Net::SFTP::Session.new(session)
    sftp.connect!
    
    # Do stuff
    
    sftp.close_channel unless sftp.nil?
    session.close unless session.nil?
    

    Better answer

    For people that couldn't find a way to actually close the connection, without using auto-closing blocks, here is how I you can achieve this :

    require 'net/ssh'
    require 'net/sftp'
    
    begin
    
      # Instance SSH/SFTP session :
      session = Net::SSH.start('host', 'user', password: 'pass', port: 22)
      sftp = Net::SFTP::Session.new(session)
    
      # Always good to timeout :
      Timeout.timeout(10) do
        sftp.connect! # Establish connection
    
        # Do stuff
    
      end
    
    rescue Timeout::Error => e
    
      # Do some custom logging
      puts e.message 
    
    ensure
    
      # Close SSH/SFTP session
      sftp.close_channel unless sftp.nil? # Close SFTP
      session.close unless session.nil? # Then SSH
    
      # If you really really really wanna make sure it's closed,
      # and raise after 10 seconds delay
      Timeout.timeout(10) do
        sleep 1 until (sftp.nil? or sftp.closed?) and (session.nil? or session.closed?)
      end
    
    end
    

    If you don't close the connection before performing some other task, you might sometimes experience errors like this in rails for instance :

    IOError (not opened for reading) # Not closed when rendering controller action
    ActionView::Template::Error (not opened for reading) # Not closed when rendering template