4

For convenience I have written a small wrapper class to login on a remote host, execute a command, end retrieve the data:

def MySSHClient:

    def connect(self, remoteHost, remotePort, userName, password):
        self.__s = paramiko.SSHClient()
        self.__s.load_system_host_keys()
        self.__s.connect(remoteHost, remotePort, userName, password)

    def exec_command(self, command):
        bufsize = -1
        chan = self.__s.get_transport().open_session()
        chan.exec_command(command)
        stdin = chan.makefile('wb', bufsize)
        stdout = chan.makefile('r', bufsize)
        stderr = chan.makefile_stderr('r', bufsize)
        stdin.close()
        exitcode = chan.recv_exit_status()
        r = MySSHCommandResult(command, stdin, stdout, stderr, exitcode)
        chan.close()
        return r

    def close(self):
        self.__s.close()

This code is adapted from the original paramiko python implementation. I just added the last 5 lines.

(FYI: MySSHCommandResult reads all data from stdout and strerr during construction and stores it for further use.)

The class MySSHClient is used within a simple python program:

....

exitCode = 0
s = None
try:
    ....
    exitCode = 3
    s = MySSHClient()
    s.connect(host, port, login, password)
    exitCode = 4
    result = s.exec_command(myCommand)
    exitCode = 5
    if not result.isSuccess():
        raise Exception("Failed to execute command!")
    result.dump()    # for current debugging purposes
    exitCode = 0
except:
    pass

if s is not None:
    s.close()
sys.exit(exitCode)

(Through these exit codes the python program tells the caller if everything succeeded. As you can see a variety of exit codes is used in order to allow a bit of error diagnosis on failure.)

So far so good. Basically this works. But what I do not understand is that sometimes my python program gives additional ouput like this:

Exception ignored in: <bound method BufferedFile.__del__ of <paramiko.ChannelFile from <paramiko.Channel 0 (closed) ->     <paramiko.Transport at 0x74300588 (unconnected)>>>>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 61, in __del__
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 79, in close
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 88, in flush
TypeError: 'NoneType' object is not callable

Or like this:

Exception ignored in: <object repr() failed>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 61, in __del__
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 79, in close
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 88, in flush
TypeError: 'NoneType' object is not callable

Everything works fine all the time, but at about 10% to 20% of the time I see these error messages. Has anyone an idea why sometimes the cleanup fails on program termination? How can I avoid these error messages?

Regis May
  • 3,070
  • 2
  • 30
  • 51
  • Which version of paramiko do you use, exactly? – Manuel Jacob May 31 '16 at 23:08
  • Thanks for asking! I'm working on a pretty new Ubuntu 16.04 installation here. Strangely paramiko 1.17.0 was installed. Nevertheless an upgrade to the most recent version of paramiko - version 2.0.0 - did not change anything. Even the line numbers of the error messages remained the same. I just upgraded via pip3 and tested it with version 2.0.0 to no avail. – Regis May May 31 '16 at 23:38
  • This is very likely a paramiko bug. You should write a bug report. Also, you should make sure that every "file" opened by `chan.makefile()` is `close()`d. Although this alone is unlikely to fix the problem, it will make sure no data is lost in the presence of this bug. – Manuel Jacob Jun 01 '16 at 00:48
  • Hm. You really think it is a bug? Considering that there are probably millions of people using paramiko I didn't think of it that way. – Regis May Jun 01 '16 at 00:53
  • 2
    Looking at line 88 of `paramiko/file.py`, the only object which is called is `BytesIO`. The only way this can "suddenly" become `None` is garbage collection at interpreter shutdown (unless someone explicitly sets it `None` somewhere, which is unlikely). – Manuel Jacob Jun 01 '16 at 00:56
  • Regarding the new paramiko version: I just saw that the version 2.0.0 is just two days old. No wonder that my installation still contained the old version. – Regis May Jun 01 '16 at 00:57
  • @manuel: Yes. This would support the assumption, that either (a) paramiko is not properly closing some I/O channels or (b) I am using the paramiko implementation somehow in a wrong way and need to do something more for a proper disconnect. – Regis May Jun 01 '16 at 01:01

3 Answers3

12

For some reason, the garbage is not cleaned up automatically when sys.exit

To force the cleapup manually you can simply delete your allocated objected with del.

This is my code:

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, port, username, password)
stdin_raw, stdout_raw, stderr_raw = client.exec_command(cmd)
exit_code = stdout_raw.channel.recv_exit_status() 

stdout = []
for line in stdout_raw:
    stdout.append(line.strip())

stderr = []
for line in stderr_raw:
    stderr.append(line.strip())


# Clean up elements
client.close()
del client, stdin_raw, stdout_raw, stderr_raw


logger.debug("stdout: %s" % stdout)
logger.debug("stderr: %s" % stderr)
logger.debug("exit_code: %s" % exit_code)

Please mind the line:

del client, stdin_raw, stdout_raw, stderr_raw

This is my source: https://github.com/paramiko/paramiko/issues/1078#issuecomment-596771584

hzrari
  • 1,803
  • 1
  • 15
  • 26
3

I ran exactly into the same problem (did not find your question at first, opened mine, now deleted). If you do not exit with sys.exit, you should not see the error anymore. What I did in my case was wrap the calls to paramiko and such in a function, so that sys.exit is not called in the same scope. In your case you could do:

def my_func(host, port, login, password, myCommand)
    exitCode = 0
    s = None
    try:
        ....
        exitCode = 3
        s = MySSHClient()
        s.connect(host, port, login, password)
        exitCode = 4
        result = s.exec_command(myCommand)
        exitCode = 5
        if not result.isSuccess():
            raise Exception("Failed to execute command!")
        exitCode = 0
    except:
        pass
    if s is not None:
        s.close()
    return exitCode

exit_code = my_func(my_host, my_port, my_login, my_password, my_command)
sys.exit(exit_code)

And it should work !

Valentin B.
  • 602
  • 6
  • 18
  • Thank you for experimenting! But I'm wondering: What difference does that make? It would suggest that there is something wrong with cleaning up Paramico if exit() is called? What's your opinion about this? – Regis May Oct 01 '17 at 16:19
  • 1
    My limited understanding of the problem is that there is a garbage collection problem. `sys.exit` works by raising an exception so that statements in `finally` blocks are executed. My guess is an internal object is probably destroyed *sometimes* by garbage collector *before* it is called somewhere in a `finally` in paramiko, whereas when calling it from a function, the object must live on until the function returns. – Valentin B. Oct 02 '17 at 14:50
  • 1
    I have opened a [ticket](https://github.com/paramiko/paramiko/issues/1078) on the paramiko github if you're interested. – Valentin B. Oct 02 '17 at 14:53
  • If you invoke sys.exit() it really seems to raise that error. Hm. I now need to completely restructure my application in order to avoid that error :-/ – Regis May Oct 08 '17 at 15:42
  • Yes, unfortunately I am unable to give you a better solution than this workaround, since the problem seems to stem from an internal paramiko misdesign. – Valentin B. Oct 09 '17 at 07:45
  • Thank you for addressing it. I'm in the process of checking if I can live with this solution. I redesigned my application but I can get back to that project not immediately but in a few days. – Regis May Oct 09 '17 at 21:31
1

I have the same problem as you. I found that there was a '\n' at the end of my command string when I using exec_command(command). I delete the '\n' and it works. The exec_command can execute a series of commands if you use '\n' to separate it. So i think the last '\n' start a new object but it is none inside so cause that None Type error.

bolin
  • 11
  • 1