work in progress, simplified the tcp and ssl states

This commit is contained in:
Emiel Bruijntjes 2018-11-05 16:18:20 +01:00
parent e617161c8c
commit 359eec189f
10 changed files with 40 additions and 194 deletions

View File

@ -65,7 +65,7 @@ public:
virtual void onIdle(TcpState *state, int socket, int events) = 0; virtual void onIdle(TcpState *state, int socket, int events) = 0;
/** /**
* Method that is called when an error occurs (the connection is lost then) * Method that is called when an error occurs (the connection is lost)
* @param state * @param state
* @param error * @param error
*/ */

View File

@ -307,24 +307,8 @@ public:
/** /**
* Destructor * Destructor
*/ */
virtual ~SslConnected() noexcept virtual ~SslConnected() noexcept = default;
{
// no cleanup if socket is gone
if (_socket < 0) return;
// and stop monitoring it
_parent->onIdle(this, _socket, 0);
// close the socket
::close(_socket);
}
/**
* The filedescriptor of this connection
* @return int
*/
virtual int fileno() const override { return _socket; }
/** /**
* Number of bytes in the outgoing buffer * Number of bytes in the outgoing buffer
* @return std::size_t * @return std::size_t
@ -438,37 +422,11 @@ public:
} }
/** /**
* When the AMQP transport layer is closed * Gracefully close the connection
* @param monitor Object that can be used if connection is still alive * @return TcpState The next state
* @return TcpState New implementation object
*/ */
virtual TcpState *onAmqpClosed(const Monitor &monitor) override virtual TcpState *close() override
{ {
// remember that the object is going to be closed
_closed = true;
// if the previous operation is still in progress we can wait for that
if (_state != state_idle) return this;
// the connection can be closed right now, move to the next state
return new SslShutdown(this, std::move(_ssl));
}
/**
* When an error occurs in the AMQP protocol
* @param monitor Monitor that can be used to check if the connection is still alive
* @param message The error message
* @return TcpState New implementation object
*/
virtual TcpState *onAmqpError(const Monitor &monitor, const char *message) override
{
// tell the user about it
// @todo do we need this here?
//_handler->onError(_connection, message);
// stop if the object was destructed
if (!monitor.valid()) return nullptr;
// remember that the object is going to be closed // remember that the object is going to be closed
_closed = true; _closed = true;

View File

@ -45,28 +45,6 @@ private:
TcpOutBuffer _out; TcpOutBuffer _out;
/**
* Close the socket
* @return bool
*/
bool close()
{
// do nothing if already closed
if (_socket < 0) return false;
// and stop monitoring it
_parent->onIdle(this, _socket, 0);
// close the socket
::close(_socket);
// forget filedescriptor
_socket = -1;
// done
return true;
}
/** /**
* Report a new state * Report a new state
* @param monitor * @param monitor
@ -80,24 +58,16 @@ private:
// leap out if the user space function destructed the object // leap out if the user space function destructed the object
if (!monitor.valid()) return nullptr; if (!monitor.valid()) return nullptr;
// copy the socket because we might forget it
// auto socket = _socket;
// forget the socket member to prevent that it is closed by the destructor
_socket = -1;
// if connection is allowed, we move to the next state // if connection is allowed, we move to the next state
if (allowed) return new SslConnected(this, std::move(_ssl), std::move(_out)); if (allowed) return new SslConnected(this, std::move(_ssl), std::move(_out));
// report that the connection is broken // report that the connection is broken
// @todo do we need this? _parent->onError(this, "TLS connection has been rejected");
//_handler->onError(_connection, "TLS connection has been rejected");
// the onError method could have destructed this object // the onError method could have destructed this object
if (!monitor.valid()) return nullptr; if (!monitor.valid()) return nullptr;
// shutdown the connection // shutdown the connection
// @todo the onClosed() does not have to be called
return new SslShutdown(this, std::move(_ssl)); return new SslShutdown(this, std::move(_ssl));
} }
@ -108,16 +78,14 @@ private:
*/ */
TcpState *reportError(const Monitor &monitor) TcpState *reportError(const Monitor &monitor)
{ {
// close the socket
close();
// we have an error - report this to the user // we have an error - report this to the user
// @todo do we need this? _parent->onError(this, "failed to setup ssl connection");
//_handler->onError(_connection, "failed to setup ssl connection");
// done, go to the closed state (plus check if connection still exists, because // stop if connection is gone
// after the onError() call the user space program may have destructed that object) if (!monitor.valid()) return nullptr;
return monitor.valid() ? new TcpClosed(this) : nullptr;
// done, shutdown the tcp connection
return new TcpShutdown(this);
} }
/** /**
@ -167,21 +135,7 @@ public:
/** /**
* Destructor * Destructor
*/ */
virtual ~SslHandshake() noexcept virtual ~SslHandshake() noexcept = default;
{
// leap out if socket is invalidated
if (_socket < 0) return;
// the object got destructed without moving to a new state, this
// situation should normally not occur
::close(_socket);
}
/**
* The filedescriptor of this connection
* @return int
*/
virtual int fileno() const override { return _socket; }
/** /**
* Number of bytes in the outgoing buffer * Number of bytes in the outgoing buffer

View File

@ -67,7 +67,6 @@ private:
default: default:
// go to the final state (if not yet disconnected) // go to the final state (if not yet disconnected)
// @todo special treatment for ssl-protocol errors
return proceed(monitor); return proceed(monitor);
} }
} }

View File

@ -57,51 +57,31 @@ private:
bool _finalized = false; bool _finalized = false;
/**
* Start an elegant shutdown
*
* @todo remove this method
*/
void shutdown2()
{
// we will shutdown the socket in a very elegant way, we notify the peer
// that we will not be sending out more write operations
::shutdown(_socket, SHUT_WR);
// we still monitor the socket for readability to see if our close call was
// confirmed by the peer
_parent->onIdle(this, _socket, readable);
}
/** /**
* Helper method to report an error * Helper method to report an error
* @param monitor Monitor to check validity of "this"
* @return bool Was an error reported? * @return bool Was an error reported?
*/ */
bool reportError(const Monitor &monitor) bool reportError()
{ {
// some errors are ok and do not (necessarily) mean that we're disconnected // some errors are ok and do not (necessarily) mean that we're disconnected
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return false; if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return false;
// tell the connection that it failed // tell the parent that it failed
// @todo we should report an error, but that could be wrong, because it calls back to us _parent->onError(this, "connection lost");
// we're no longer interested in the socket (this also calls onClosed())
cleanup();
// done // done
return true; return true;
} }
/** /**
* Construct the next state * Construct the shutdown state
* @param monitor Object that monitors whether connection still exists * @param monitor Object that monitors whether connection still exists
* @return TcpState* * @return TcpState*
*/ */
TcpState *nextState(const Monitor &monitor) TcpState *nextState(const Monitor &monitor)
{ {
// if the object is still in a valid state, we can move to the close-state, // if the object is still in a valid state, we can treat the connection
// otherwise there is no point in moving to a next state // as closed otherwise there is no point in moving to a next state
return monitor.valid() ? new TcpClosed(this) : nullptr; return monitor.valid() ? new TcpClosed(this) : nullptr;
} }
@ -128,12 +108,6 @@ public:
*/ */
virtual ~TcpConnected() noexcept = default; virtual ~TcpConnected() noexcept = default;
/**
* The filedescriptor of this connection
* @return int
*/
virtual int fileno() const override { return _socket; }
/** /**
* Number of bytes in the outgoing buffer * Number of bytes in the outgoing buffer
* @return std::size_t * @return std::size_t
@ -159,7 +133,7 @@ public:
auto result = _out.sendto(_socket); auto result = _out.sendto(_socket);
// are we in an error state? // are we in an error state?
if (result < 0 && reportError(monitor)) return nextState(monitor); if (result < 0 && reportError()) return nextState(monitor);
// if buffer is empty by now, we no longer have to check for // if buffer is empty by now, we no longer have to check for
// writability, but only for readability // writability, but only for readability
@ -173,7 +147,7 @@ public:
ssize_t result = _in.receivefrom(_socket, _parent->expected()); ssize_t result = _in.receivefrom(_socket, _parent->expected());
// are we in an error state? // are we in an error state?
if (result < 0 && reportError(monitor)) return nextState(monitor); if (result < 0 && reportError()) return nextState(monitor);
// @todo should we also check for result == 0 // @todo should we also check for result == 0
@ -256,34 +230,16 @@ public:
// all has been sent // all has been sent
return this; return this;
} }
/**
* When the AMQP transport layer is closed
* @param monitor Object that can be used if connection is still alive
* @return TcpState New implementation object
*/
virtual TcpState *onAmqpClosed(const Monitor &monitor) override
{
// move to the tcp shutdown state
return new TcpShutdown(this);
}
/** /**
* When an error occurs in the AMQP protocol * Gracefully close the connection
* @param monitor Monitor that can be used to check if the connection is still alive * @return TcpState The next state
* @param message The error message
* @return TcpState New implementation object
*/ */
virtual TcpState *onAmqpError(const Monitor &monitor, const char *message) override virtual TcpState *close() override
{ {
// tell the user about it // @todo what if we're still busy receiving data?
// @todo do this somewhere else
//_handler->onError(_connection, message);
// stop if the object was destructed // start the tcp shutdown
if (!monitor.valid()) return nullptr;
// move to the tcp shutdown state
return new TcpShutdown(this); return new TcpShutdown(this);
} }

View File

@ -88,6 +88,7 @@ void TcpConnection::process(int fd, int flags)
else else
{ {
// replace it with the new implementation // replace it with the new implementation
// @todo destructing the existing _state may destruct the entire object
_state.reset(newstate); _state.reset(newstate);
} }
} }
@ -178,8 +179,8 @@ void TcpConnection::onError(Connection *connection, const char *message)
// remember the old state (this is necessary because _state may be modified by user-code) // remember the old state (this is necessary because _state may be modified by user-code)
auto *oldstate = _state.get(); auto *oldstate = _state.get();
// tell the state that an error occured at the amqp level // tell the state that the connection should be closed asap
auto *newstate = _state->onAmqpError(monitor, message); auto *newstate = _state->close();
// leap out if nothing changes // leap out if nothing changes
if (newstate == nullptr || newstate == oldstate) return; if (newstate == nullptr || newstate == oldstate) return;
@ -200,8 +201,8 @@ void TcpConnection::onClosed(Connection *connection)
// remember the old state (this is necessary because _state may be modified by user-code) // remember the old state (this is necessary because _state may be modified by user-code)
auto *oldstate = _state.get(); auto *oldstate = _state.get();
// tell the state that the connection was closed at the amqp level // tell the state that the connection should be closed asap
auto *newstate = _state->onAmqpClosed(monitor); auto *newstate = _state->close();
// leap out if nothing changes // leap out if nothing changes
if (newstate == nullptr || newstate == oldstate) return; if (newstate == nullptr || newstate == oldstate) return;

View File

@ -30,7 +30,7 @@ protected:
int _socket; int _socket;
/** /**
* Clean-up the socket * Clean-up the socket, and call the onClosed() method
*/ */
void cleanup() void cleanup()
{ {

View File

@ -108,7 +108,7 @@ private:
_error = strerror(errno); _error = strerror(errno);
// close socket because connect failed // close socket because connect failed
close(_socket); ::close(_socket);
// socket no longer is valid // socket no longer is valid
_socket = -1; _socket = -1;

View File

@ -30,16 +30,6 @@ namespace AMQP {
*/ */
class TcpShutdown : public TcpExtState class TcpShutdown : public TcpExtState
{ {
protected:
/**
* Method to report the result to the user
*/
virtual void report()
{
// report that the connection was closed
_parent->onClosed(this);
}
public: public:
/** /**
* Constructor * Constructor
@ -108,9 +98,6 @@ public:
// immediately close the socket // immediately close the socket
cleanup(); cleanup();
// report to the user that the operation is finished
report();
// move to next state // move to next state
return monitor.valid() ? new TcpClosed(this) : nullptr; return monitor.valid() ? new TcpClosed(this) : nullptr;
} }

View File

@ -92,6 +92,12 @@ public:
// default does nothing // default does nothing
} }
/**
* Gracefully close the connection
* @return TcpState The next state
*/
virtual TcpState *close() { return this; }
/** /**
* Flush the connection, all outgoing operations should be completed. * Flush the connection, all outgoing operations should be completed.
* *
@ -115,21 +121,6 @@ public:
* @param heartbeat suggested heartbeat * @param heartbeat suggested heartbeat
*/ */
virtual void maxframe(size_t maxframe) {} virtual void maxframe(size_t maxframe) {}
/**
* Events that can take place during the AMQP protocol
*
* Both events also trigger the end of a valid connection, and should
* be used to tear down the TCP connection.
*
* @todo are these appropriate names?
*
* @param monitor
* @param TcpState
*/
virtual TcpState *onAmqpError(const Monitor &monitor, const char *error) { return this; }
virtual TcpState *onAmqpClosed(const Monitor &monitor) { return this; }
}; };
/** /**