rubencaro / sshex

Simple SSH helpers for Elixir. SSH is useful, but we all love SSHEx !

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to catch an exception if connection was dropped by the server

mspanc opened this issue · comments

There's no way to properly catch exception if there's an attempt to issue a command via run on connection that was closed on server side.

** (EXIT from #PID<0.330.0>) an exception was raised:
    ** (UndefinedFunctionError) undefined function: :closed.exception/1 (module :closed is not available)
        :closed.exception([])
        lib/sshex.ex:57: SSHEx.open_channel/2
        lib/sshex.ex:28: SSHEx.run/5
        (plumber) web/backbone/architecture/computing_node/wrapper.ex:82: Wrapper.handle_call/3
        lib/connection.ex:475: Connection.handle_call/3
        (stdlib) gen_server.erl:607: :gen_server.try_handle_call/4
        (stdlib) gen_server.erl:639: :gen_server.handle_msg/5
        (stdlib) proc_lib.erl:237: :proc_lib.init_p_do_apply/3

Context: I am using https://github.com/fishcakez/connection to wrap SSH connection logic (connecting/disconnecting etc.). This library gracefully handles reconnecting etc. It has similar pattern to GenServer, so if I want to issue a command on the server, I send Connection.call (equivalent of GenServer.call) and then synchronously call SSHEx.run. When unhandled exception happens such as mentioned above the process crashes and supervisor has to restart it. It works, but I prefer to handle this more gracefully, and schedule reconnect.

IMO this line: https://github.com/rubencaro/sshex/blob/master/lib/sshex.ex#L57 should raise an error that has predictable module name

Hi @mspanc ,

It looks like a simple bug with the reason argument passed to raise. It should perform some inspect to the reason before raising, maybe. With that, the exception you see would be more understandable. It would be a generic exception showing the very same message that :ssh sent back as the reason for failing.

The Right Way would be not to use raise at all (like :ssh does). We should not use exceptions for flow control. That would be up for 2.0, as it would break backwards compatibility.

To grow specific exception vocabulary (such a well organized exception module hierarchy) seems too much to me, as we expect the raise itself to disappear soon enough.

For your use case, I would fix the initial bug:

    { :error, reason } -> raise inspect(reason)

Then catch any raising coming from SSHEx.run as a generic :ssh failure. The message will be everything :ssh gave as reason anyway.

From 2.0 on, there should be no exceptions (if any should rise then the supervising mechanism would be the right way to go), and SSHEx.run will return {:error, reason}.

If that sounds ok for you, I can make a release with the fix. Does it?

Hello @rubencaro

Honestly speaking, due to time pressure I've already rewritten this part in my app using raw :ssh exactly in a way you describe for 2.0. So if it's just a matter of adding inspect(...), you will probably satisfy a few users, and it's really quick fix. But in my case I have already made this differently (but shhex gave me a lot of inspiration!).

Just released 1.2.0 mostly improving and documenting error handling. And clean {:error, reason} return values are added to the TODO list for 2.0.

Closing.

Thanks!