PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Core/ConnectionSML/src/sml_RemoteConnection.cpp

http://soar.googlecode.com/
C++ | 395 lines | 210 code | 69 blank | 116 comment | 41 complexity | ef6fd6f9884fc45f34c24e4d6f5695b8 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-3.0
  1. #include <portability.h>
  2. /////////////////////////////////////////////////////////////////
  3. // RemoteConnection class
  4. //
  5. // Author: Douglas Pearson, www.threepenny.net
  6. // Date : October 2004
  7. //
  8. // This class represents a logical connection between two entities that are communicating via SML over a socket.
  9. // For example, an environment (the client) and the Soar kernel.
  10. //
  11. // NOTE: This class is VERY similar to the EmbeddedConnectionAsynch class. In fact we should
  12. // probably fold them together at some point with a common base class. But for now, be aware
  13. // that if you're changing something here you should probably also be changing it there.
  14. //
  15. /////////////////////////////////////////////////////////////////
  16. #include "sml_Utils.h"
  17. #include "sml_RemoteConnection.h"
  18. #include "sock_Socket.h"
  19. #include "thread_Thread.h"
  20. #include <assert.h>
  21. using namespace sml ;
  22. using namespace soarxml ;
  23. RemoteConnection::RemoteConnection(bool sharedFileSystem, sock::DataSender* pDataSender)
  24. {
  25. m_SharedFileSystem = sharedFileSystem ;
  26. m_DataSender = pDataSender ;
  27. m_pLastResponse = NULL ;
  28. }
  29. RemoteConnection::~RemoteConnection()
  30. {
  31. delete m_pLastResponse ;
  32. delete m_DataSender ;
  33. for (MessageListIter iter = m_ReceivedMessageList.begin() ; iter != m_ReceivedMessageList.end() ; iter++)
  34. {
  35. ElementXML* xml = (*iter) ;
  36. delete xml ;
  37. }
  38. }
  39. /*************************************************************
  40. * @brief Adds the message to a queue of responses which we're waiting
  41. * to pair with the commands that triggered them.
  42. *
  43. * This function takes ownership of the object it is passed,
  44. * so the caller should not delete it subsequently.
  45. *
  46. * The messages kept on this list will all have "ack" fields
  47. * as they are responses to commands that have come out of the
  48. * expected order. This can only happen when multiple threads
  49. * submit commands.
  50. *************************************************************/
  51. void RemoteConnection::AddResponseToList(ElementXML* pResponse)
  52. {
  53. if (pResponse == NULL)
  54. return ;
  55. // If this message isn't a response to a command we don't need to keep it
  56. // because we will never need to retrieve it.
  57. const char* pAckID = pResponse->GetAttribute(sml_Names::kAck) ;
  58. if (!pAckID)
  59. {
  60. delete pResponse ;
  61. return ;
  62. }
  63. soar_thread::Lock lock(&m_ListMutex) ;
  64. m_ReceivedMessageList.push_front(pResponse) ;
  65. if (m_bTraceCommunications)
  66. sml::PrintDebugFormat("!! Adding ack for id %s to the pending message list", pAckID) ;
  67. // We keep the received message list from growing indefinitely. This is because
  68. // a client may send a command and choose not to listen for the response.
  69. // (I don't believe this happens today, but it is allowed by the API).
  70. // In that case the message would remain on this list forever and if we allowed it
  71. // to grow over time we could be searching an ever increasingly large list of dead messages
  72. // that will never be retrieved. I believe (but haven't conclusively proved to my satisfaction yet)
  73. // that we will never have more messages pending here, for which the client is interested in the
  74. // response, than there are threads sending commands, so a small max list size should be fine.
  75. while (m_ReceivedMessageList.size() > kMaxListSize)
  76. {
  77. if (m_bTraceCommunications)
  78. sml::PrintDebugFormat("Had to clean a message from the pending message list") ;
  79. ElementXML* pLast = m_ReceivedMessageList.back() ;
  80. delete pLast ;
  81. m_ReceivedMessageList.pop_back() ;
  82. }
  83. }
  84. /*************************************************************
  85. * @brief Searches the list of responses to see if there's already
  86. * been a response generated for this particular message ID.
  87. *
  88. * The list of messages has a fixed maximum size, so this lookup is
  89. * a constant time operation. If the client is only issuing
  90. * calls on a single thread, the list will always be empty.
  91. *************************************************************/
  92. ElementXML* RemoteConnection::IsResponseInList(char const* pID)
  93. {
  94. soar_thread::Lock lock(&m_ListMutex) ;
  95. for (MessageListIter iter = m_ReceivedMessageList.begin() ; iter != m_ReceivedMessageList.end() ; iter++)
  96. {
  97. ElementXML* pXML = (*iter) ;
  98. if (DoesResponseMatch(pXML, pID))
  99. {
  100. if (m_bTraceCommunications)
  101. sml::PrintDebugFormat("Found match for %s in pending message list", pID) ;
  102. m_ReceivedMessageList.erase(iter) ;
  103. return pXML ;
  104. }
  105. }
  106. return NULL ;
  107. }
  108. /*************************************************************
  109. * @brief Returns true if the given response message is
  110. * an acknowledgement for a message with the given ID.
  111. *************************************************************/
  112. bool RemoteConnection::DoesResponseMatch(ElementXML* pResponse, char const* pID)
  113. {
  114. if (!pResponse || !pID)
  115. return false ;
  116. char const* pMsgID = pResponse->GetAttribute(sml_Names::kAck) ;
  117. if (!pMsgID)
  118. return false ;
  119. // Spelling this test out so we can put break points in if we wish.
  120. if (strcmp(pMsgID, pID) == 0)
  121. return true ;
  122. if (m_bTraceCommunications)
  123. sml::PrintDebugFormat("Received ack for message %s while looking for %s", pMsgID, pID) ;
  124. return false ;
  125. }
  126. /*************************************************************
  127. * @brief Send a message to the other side of this connection.
  128. *
  129. * For an remote connection this is done by sending the command
  130. * over a socket as an actual XML string.
  131. *
  132. * There is no immediate response because we have to wait for
  133. * the other side to read from the socket and execute the command.
  134. * To get a response call GetResponseForID()
  135. * and wait for the response to occur.
  136. *************************************************************/
  137. void RemoteConnection::SendMsg(ElementXML* pMsg)
  138. {
  139. ClearError() ;
  140. // Convert the message to an XML string
  141. char* pXMLString = pMsg->GenerateXMLString(true) ;
  142. // Send it
  143. bool ok = m_DataSender->SendString(pXMLString) ;
  144. // Dump the message if we're tracing
  145. if (m_bTraceCommunications)
  146. {
  147. if (IsKernelSide())
  148. sml::PrintDebugFormat("Kernel remote send: %s\n", pXMLString) ;
  149. else
  150. sml::PrintDebugFormat("Client remote send: %s\n", pXMLString) ;
  151. }
  152. // Release the XML string
  153. pMsg->DeleteString(pXMLString) ;
  154. // If we had an error close the connection
  155. if (!ok)
  156. {
  157. //sml::PrintDebug("Socket has closed down abruptly (during send), so we'll close the connection") ;
  158. SetError(Error::kSocketError) ;
  159. CloseConnection() ;
  160. }
  161. }
  162. /*************************************************************
  163. * @brief Look for a response to the given message (based on its ID).
  164. * Optionally, wait for that response to come in.
  165. *************************************************************/
  166. ElementXML* RemoteConnection::GetResponseForID(char const* pID, bool wait)
  167. {
  168. ElementXML* pResponse = NULL ;
  169. // Check if we already have this response cached
  170. if (DoesResponseMatch(m_pLastResponse, pID))
  171. {
  172. pResponse = m_pLastResponse ;
  173. m_pLastResponse = NULL ;
  174. return pResponse ;
  175. }
  176. // Also check the list of responses we've stored
  177. // (This list will always be empty if we're only executing commands
  178. // on one thread, but if we are using multiple threads it can come into play).
  179. pResponse = IsResponseInList(pID) ;
  180. if (pResponse)
  181. {
  182. return pResponse ;
  183. }
  184. int sleepTimeSecs = 0 ; // How long we sleep in seconds each pass through
  185. int sleepTimeMillisecs = 0 ; // How long we sleep in milliseconds each pass through
  186. // How long we sleep on the socket waiting for data in msecs
  187. // We want to wait for a long time. We used to set this to 0 and just poll the socket,
  188. // but that means we're consuming all of the CPU. Setting a long wait doesn't
  189. // impact performance because we're not trying to do anything else other than get a response here.
  190. int waitForMessageTimeSeconds = 1 ;
  191. int waitForMessageTimeMilliseconds = 0 ;
  192. // If we don't already have this response cached,
  193. // then read any pending messages.
  194. do
  195. {
  196. // Loop until there are no more messages waiting on the socket
  197. while (ReceiveMessages(false, waitForMessageTimeSeconds, waitForMessageTimeMilliseconds))
  198. {
  199. // Check each message to see if it's a match
  200. if (DoesResponseMatch(m_pLastResponse, pID))
  201. {
  202. pResponse = m_pLastResponse ;
  203. m_pLastResponse = NULL ;
  204. return pResponse ;
  205. } else {
  206. AddResponseToList(m_pLastResponse) ;
  207. m_pLastResponse = NULL ;
  208. }
  209. }
  210. // Check to see if the message has been added to the list of
  211. // waiting messages. This could have happened on a different
  212. // thread while we were in here waiting.
  213. ElementXML* pResponse = IsResponseInList(pID) ;
  214. if (pResponse != NULL)
  215. {
  216. return pResponse ;
  217. }
  218. // Allow other threads the chance to update
  219. // (by calling with 0 for sleep time we don't give away cycles if
  220. // no other thread is waiting to execute).
  221. sml::Sleep(sleepTimeSecs, sleepTimeMillisecs) ;
  222. // Check if the connection has been closed
  223. if (IsClosed())
  224. return NULL ;
  225. } while (wait) ;
  226. // If we get here we didn't find the response.
  227. // (If we're waiting we'll wait forever, so we'll only get here if
  228. // we chose not to wait).
  229. return NULL ;
  230. }
  231. /*************************************************************
  232. * @brief Retrieve any messages we've been sent and process them.
  233. *
  234. * Returns true if at least one message has been read.
  235. *************************************************************/
  236. bool RemoteConnection::ReceiveMessages(bool allMessages)
  237. {
  238. return ReceiveMessages(allMessages, 0, 0) ;
  239. }
  240. bool RemoteConnection::ReceiveMessages(bool allMessages, int secondsWait, int millisecondsWait)
  241. {
  242. assert(millisecondsWait<1000 && "specified milliseconds must be less than 1000");
  243. // Make sure only one thread is sending messages at a time
  244. // (This allows us to run a separate thread in clients polling for events even
  245. // when the client is sleeping, but we don't want them both to be sending/receiving at the same time).
  246. soar_thread::Lock lock(&m_ClientMutex) ;
  247. std::string xmlString ;
  248. bool receivedMessage = false ;
  249. bool ok = true ;
  250. // While we have messages waiting to come in keep reading them
  251. bool haveData = true ;
  252. while (haveData)
  253. {
  254. bool alive = m_DataSender->IsAlive() ;
  255. if (!alive)
  256. {
  257. // The socket has closed down so close our connection object too.
  258. this->CloseConnection() ;
  259. return receivedMessage ;
  260. }
  261. // Only check for read data after we've checked that the socket is still alive.
  262. // (This is because IsReadData can't signal the difference between a dead connection and no data)
  263. haveData = m_DataSender->IsReadDataAvailable(secondsWait, millisecondsWait) ;
  264. if (!haveData)
  265. break ;
  266. // Read the first message that's waiting
  267. ok = m_DataSender->ReceiveString(&xmlString) ;
  268. if (!ok)
  269. {
  270. this->SetError(Error::kSocketError) ;
  271. this->CloseConnection() ;
  272. return receivedMessage ;
  273. }
  274. // Dump the message if we're tracing
  275. if (m_bTraceCommunications)
  276. {
  277. if (IsKernelSide())
  278. sml::PrintDebugFormat("Kernel remote receive: %s\n", xmlString.c_str()) ;
  279. else
  280. sml::PrintDebugFormat("Client remote receive: %s\n", xmlString.c_str()) ;
  281. }
  282. // Get an XML message from the incoming string
  283. ElementXML* pIncomingMsg = ElementXML::ParseXMLFromString(xmlString.c_str()) ;
  284. if (!pIncomingMsg)
  285. {
  286. this->SetError(Error::kParsingXMLError) ;
  287. return receivedMessage ;
  288. }
  289. #ifdef _DEBUG
  290. // Check that the parse worked
  291. //char* pMsgText = pIncomingMsg->GenerateXMLString(true) ;
  292. //pIncomingMsg->DeleteString(pMsgText) ;
  293. #endif
  294. // Record that we got at least one message
  295. receivedMessage = true ;
  296. // Pass this message back to the client and possibly get their response
  297. ElementXML* pResponse = this->InvokeCallbacks(pIncomingMsg) ;
  298. // If we got a response to the incoming message, send that response back.
  299. if (pResponse)
  300. {
  301. SendMsg(pResponse) ;
  302. }
  303. // We're done with the response
  304. delete pResponse ;
  305. // Record the last incoming message
  306. delete m_pLastResponse ;
  307. m_pLastResponse = pIncomingMsg ;
  308. // If we're only asked to read one message, we're done.
  309. if (!allMessages)
  310. break ;
  311. }
  312. return receivedMessage ;
  313. }
  314. void RemoteConnection::SetTraceCommunications(bool state)
  315. {
  316. m_bTraceCommunications = state ;
  317. if (m_DataSender) m_DataSender->SetTraceCommunications(state) ;
  318. }
  319. void RemoteConnection::CloseConnection()
  320. {
  321. m_DataSender->Close() ;
  322. }
  323. bool RemoteConnection::IsClosed()
  324. {
  325. return !m_DataSender->IsAlive() ;
  326. }