1
0

Rewritten SocketThreads for proper shutdown scenario.

This fixes #560 and #390.
This commit is contained in:
madmaxoft
2014-01-19 19:31:43 +01:00
parent cce063b0cd
commit 3c0e8c8da0
8 changed files with 189 additions and 178 deletions

View File

@@ -7,19 +7,20 @@
/*
Additional details:
When a client is terminating a connection:
- they call the StopReading() method to disable callbacks for the incoming data
- they call the Write() method to queue any outstanding outgoing data
- they call the QueueClose() method to queue the socket to close after outgoing data has been sent.
When a socket slot is marked as having no callback, it is kept alive until its outgoing data queue is empty and its m_ShouldClose flag is set.
This means that the socket can be written to several times before finally closing it via QueueClose()
When a client wants to terminate the connection, they call the RemoveClient() function. This calls the
callback one last time to read all the available outgoing data, putting it in the slot's m_OutgoingData
buffer. Then it marks the slot as having no callback. The socket is kept alive until its outgoing data
queue is empty, then shutdown is called on it and finally the socket is closed after a timeout.
If at any time within this the remote end closes the socket, then the socket is closed directly.
As soon as the socket is closed, the slot is finally removed from the SocketThread.
The graph in $/docs/SocketThreads States.gv shows the state-machine transitions of the slot.
*/
/// How many clients should one thread handle? (must be less than FD_SETSIZE for your platform)
/** How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) */
#define MAX_SLOTS 63
@@ -27,8 +28,6 @@ This means that the socket can be written to several times before finally closin
#pragma once
#ifndef CSOCKETTHREADS_H_INCLUDED
#define CSOCKETTHREADS_H_INCLUDED
#include "Socket.h"
#include "IsThread.h"
@@ -64,13 +63,13 @@ public:
// Force a virtual destructor in all subclasses:
virtual ~cCallback() {}
/// Called when data is received from the remote party
/** Called when data is received from the remote party */
virtual void DataReceived(const char * a_Data, int a_Size) = 0;
/// Called when data can be sent to remote party; the function is supposed to append outgoing data to a_Data
/** Called when data can be sent to remote party; the function is supposed to *append* outgoing data to a_Data */
virtual void GetOutgoingData(AString & a_Data) = 0;
/// Called when the socket has been closed for any reason
/** Called when the socket has been closed for any reason */
virtual void SocketClosed(void) = 0;
} ;
@@ -78,26 +77,21 @@ public:
cSocketThreads(void);
~cSocketThreads();
/// Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful
/** Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful */
bool AddClient(const cSocket & a_Socket, cCallback * a_Client);
/** Remove the associated socket and the client from processing.
The socket is left to send its data and is removed only after all its m_OutgoingData is sent
The socket is left to send its last outgoing data and is removed only after all its m_Outgoing is sent
and after the socket is properly shutdown (unless the remote disconnects before that)
*/
void RemoveClient(const cCallback * a_Client);
/// Notify the thread responsible for a_Client that the client has something to write
/** Notify the thread responsible for a_Client that the client has something to write */
void NotifyWrite(const cCallback * a_Client);
/// Puts a_Data into outgoing data queue for a_Client
/** Puts a_Data into outgoing data queue for a_Client */
void Write(const cCallback * a_Client, const AString & a_Data);
/// Stops reading from the client - when this call returns, no more calls to the callbacks are made
void StopReading(const cCallback * a_Client);
/// Queues the client for closing, as soon as its outgoing data is sent
void QueueClose(const cCallback * a_Client);
private:
class cSocketThread :
@@ -120,8 +114,6 @@ private:
bool HasSocket (const cSocket * a_Socket) const;
bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread
bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread
bool StopReading (const cCallback * a_Client); // Returns true if client handled by this thread
bool QueueClose (const cCallback * a_Client); // Returns true if client handled by this thread
bool Start(void); // Hide the cIsThread's Start method, we need to provide our own startup to create the control socket
@@ -135,24 +127,45 @@ private:
cSocket m_ControlSocket1;
cSocket m_ControlSocket2;
// Socket-client-packetqueues triplets.
// Socket-client-dataqueues-state quadruplets.
// Manipulation with these assumes that the parent's m_CS is locked
struct sSlot
{
cSocket m_Socket; // The socket is primarily owned by this
/** The socket is primarily owned by this object */
cSocket m_Socket;
/** The callback to call for events. May be NULL */
cCallback * m_Client;
AString m_Outgoing; // If sending writes only partial data, the rest is stored here for another send
bool m_ShouldClose; // If true, the socket is to be closed after sending all outgoing data
bool m_ShouldCallClient; // If true, the client callbacks are called. Set to false in StopReading()
/** If sending writes only partial data, the rest is stored here for another send.
Also used when the slot is being removed to store the last batch of outgoing data. */
AString m_Outgoing;
enum eState
{
ssNormal, ///< Normal read / write operations
ssWritingRestOut, ///< The client callback was removed, continue to send outgoing data
ssShuttingDown, ///< The last outgoing data has been sent, the socket has called shutdown()
ssShuttingDown2, ///< The shutdown has been done at least 1 thread loop ago (timeout detection)
ssRemoteClosed, ///< The remote end has closed the connection (and we still have a client callback)
} m_State;
} ;
sSlot m_Slots[MAX_SLOTS];
int m_NumSlots; // Number of slots actually used
virtual void Execute(void) override;
void PrepareSet (fd_set * a_Set, cSocket::xSocket & a_Highest); // Puts all sockets into the set, along with m_ControlSocket1
/** Puts all sockets into the set, along with m_ControlSocket1.
Only sockets that are able to send and receive data are put in the Set.
Is a_IsForWriting is true, the ssWritingRestOut sockets are added as well. */
void PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest, bool a_IsForWriting);
void ReadFromSockets(fd_set * a_Read); // Reads from sockets indicated in a_Read
void WriteToSockets (fd_set * a_Write); // Writes to sockets indicated in a_Write
/** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */
void CleanUpShutSockets(void);
} ;
typedef std::list<cSocketThread *> cSocketThreadList;
@@ -165,9 +178,3 @@ private:
#endif // CSOCKETTHREADS_H_INCLUDED