Monday 12 November 2012

SSH Session - Class SessionHandle

And... I'm back. Work has gone back to manageable levels, and I've taken a few days off C++, to give my head a chance to catch up.

I also took the opportunity to get some feedback on my design. It was quite satisfying to hear that I've been heading in the right direction all this time. And, once again, while writing (and rewriting) that question, I've hit yet another design change, one that I believe will simplify my work when I introduce other forms of authentication (for now, it's just login/password).

So, let's get moving.

Today, I'll introduce SessionHandle, the class responsible for implementing RAII on the local SSH session resources.

The interface is pretty simple:

class SessionHandle
{
public:
    SessionHandle();
    ~SessionHandle();
 

    void CloseSession();
    LIBSSH2_SESSION* GetSession() { return session; }
 


    // WE MAY ADD MOVE IN THE FUTURE, BUT NOT COPY
    SessionHandle(SessionHandle&&) = delete;
    SessionHandle& operator=(SessionHandle&&) = delete;
    SessionHandle(SessionHandle const&) = delete;
    SessionHandle& operator=(SessionHandle const&) = delete;
 


private:    
    utils::TaskState DoCloseSession();
 


    utils::CleanupState cleanupState;
    LIBSSH2_SESSION *session;
};

We define a ctor/dtor. All other special operations are deleted. I suppose I may have to implement move semantics, but I won't worry about that now. CloseSession() allows the client to explicitly release the resources, in which case the dtor won't do anything. GetSession() is necessary for the other classes that need the LIBSSH2_SESSION*, although I may review this and make these classes friend, thus removing the need to expose it to the outside world.

DoCloseSession() is the handler called by the loop functions, and cleanupState is a flag to tells us if we need to cleanup on the dtor.

The code itself is also simple, which is good, since this was one of the goals of this redesign.

SessionHandle::SessionHandle() : 
    cleanupState(utils::CleanupState::NoCleanup), 
    session(nullptr)
{
    session = libssh2_session_init();
 

    if (!session)
    {
        BOOST_THROW_EXCEPTION(SSHCreateSessionError() << 
            ssh_error_string("Error creating SSH session."));
    }
 

    libssh2_session_set_blocking(session, 0);
}


There's no need for a loop here, because creating the session is always a blocking operation. Speaking of which, for now blocking on the session is hard-coded. I may make it configurable in a future version.

SessionHandle::~SessionHandle()
{
    // WE'LL ONLY TRY TO CLEANUP 
    // IF NO ATTEMPT HAS BEEN MADE YET
    if ((session != nullptr) && 
        (cleanupState == utils::CleanupState::NoCleanup))
    {
        try
        {
            CloseSession();
        }
        catch (...)
        { }
    }
}
 

The dtor calls CloseSession() to perform the cleanup, but only if no attempt has been previously made; meaning, if the user didn't call CloseSession() explicitly. If such an attempt was made, we won't try it again (even if it failed).

Also on the to-do list is applying boost's current_exception() to the catch block.

void SessionHandle::CloseSession()
{
    io_service ios;
    deadline_timer dt(ios);
 

    dt.expires_from_now(milliseconds(10));
    dt.async_wait(bind(AsyncLoopTimer, 
        protect(bind(&SessionHandle::DoCloseSession, this)), 
        boost::ref(dt), 10));
 

    cleanupState = utils::CleanupState::CleanupInProgress;
    ios.run();
 

    session = nullptr;
    cleanupState = utils::CleanupState::CleanupDone;
}


The use of a timer and a local io_service here avoids the whole outstanding handlers issue. Once cleanup is completed, we set the state accordingly, to make sure our dtor won't try to cleanup again.

utils::TaskState SessionHandle::DoCloseSession()
{
    int rc = libssh2_session_free(session);
 

    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return utils::TASK_WORKING;
    }
 

    if (rc)
    {
        BOOST_THROW_EXCEPTION(SSHFreeSessionError() <<
            ssh_error_string("Error " + lexical_cast<string>(rc) + 
            " freeing SSH session"));
    }
 

    return utils::TASK_DONE;
}


Finally, the handler follows the pattern we've setup a couple of months ago. So, nothing new here.

Incidentally, I said in that post that "This is equivalent to libssh2's ssh2_exec example, and I'll be discussing it here in the following posts". I haven't discussed the example here, as such, because as I started writing the posts about it (and actually thinking about its design), I became aware of its failings, and that's when the redesign began. However, the functionality is still identical, it's only the organization that changed.

This class is the first result of this redesign. It's simple, it's focused. And I suppose I'll learn at some point in the future it's not entirely right. But that's a good thing. Otherwise, how would we learn?

You can get all the code here, namely:
  • exception.h, which contains the base exception definitions.
  • sshexception.h, which contains the exception definitions for the ssh classes.
  • misc.h, which contains the enum with our cleanup state.
  • sessionhandle.h and sessionhandle.cpp. The class we've presented above.
And you'll also need this.

Yes, I know. I need to setup a place somewhere to store this code.

No comments:

Post a Comment