You are viewing a plain text version of this content. The canonical link for it is here.
Posted to c-dev@axis.apache.org by Tim Bartley <tb...@au1.ibm.com> on 2005/03/16 01:17:55 UTC

Re: server shutdown of long lived connections

I've played with an implementation locally however it seems that we don't 
get the benefit of the optimization part because there generally tends to 
be some whitespace hanging around on the end of responses.

What is trivial to implement is the connection reopen stuff - that should 
be no risk for 1.5 and seems to have been working well for me.

I'll send a patch for just that bit - the optimization can be held over 
for further work.

Cheers,

Tim
--
IBM Tivoli Access Manager Development
Gold Coast Development Lab, Australia
+61-7-5552-4001 phone
+61-7-5571-0420 fax



John Hawkins <ha...@uk.ibm.com> 
15/03/2005 21:26
Please respond to
"Apache AXIS C User List"


To
"Apache AXIS C User List" <ax...@ws.apache.org>
cc

Subject
Re: server shutdown of long lived connections







Great ! 

Can you implement and send diffs? Perhaps this is too big a change at this 
late stage in 1.5 beta - thoughts anyone? 





Tim Bartley <tb...@au1.ibm.com> 
11/03/2005 23:07 

Please respond to
"Apache AXIS C User List"


To
"Apache AXIS C User List" <ax...@ws.apache.org> 
cc

Subject
server shutdown of long lived connections









HTTP 1.1 (and 1.0 with Connection: Keep-Alive) permits the client to 
re-use a connection for multiple requests and Axis makes use of this. 

However, if the client hasn't sent a request on that connection for a 
while the server will typlically shutdown the connection. 

One server I've seen (WebSphere) does this simply by sending a FIN from 
it's end. This means that the client->server half of the connection is 
still open so the next client send (*m_pActiveChannel << 
this->getHTTPHeaders() in HTTPTransport::flushOutput) succeeds. The server 
responds to this by aborting the connection but it's not until the  the 
next send (*m_pActiveChannel << this->m_strBytesToSend.c_str()) that an IO 
error occurs and ultimately an exception is thrown to the client 
application. 

Now this behaviour is a property of the transport so I think Axis should 
detect that the server side has closed the connection and resend the 
request. This should always be OK because an IO error on any part of the 
send must mean the request has not been completely sent and therefore 
re-sending the request should not be harmful. 

So I think HTTPTransport::flushOutput should have some logic like: 

try { 
       *m_pActiveChannel << this->getHTTPHeaders(); 
       *m_pActiveChannel << this->strBytesToSend.c_str(); 
} 
catch (HTTPTransportException& e) {
      if (didn't just re-open the connection) {
              m_pActiveChannel->close(); 
               m_pActiveChannel->open(); 
               *m_pActiveChannel << this->getHTTPHeaders(); 
               *m_pActiveChannel << this->strBytesToSend.c_str(); 
       } 
       else { 
               throw; 
       } 
} 

We can do slightly better than this by trying to detect that the server 
has closed the connection before sending at all - this saves the network 
bandwidth of the first packet and saves us a bit of computation. This can 
be done approximately as follows: 

bool reopenConnection; 

fd_set_t read_fds; 
fd_set_t except_fds; 
FD_ZERO(&read_fds); 
FD_ZERO(&except_fds); 
FD_SET(socket, &read_fds); 
FD_SET(socket, &except_fds); 

timepec_t t = {0}; 
int result = select(FD_SETSIZE, &read_fds, NULL, &except_fds, &t); 
if (result < 0) {
      throw something; 
} 
else if (result == 0) {
      /* socket not readable - therefore not closed - ok to send */ 
       reopenConnection = false; 
} 
else { 
       /* socket readable or in error - see if data available */ 
       unsigned char byte; 
       result = recv(socket, &byte, 1, MSG_PEEK); 
       if (result == 0) {
              /* socket shutdown by remote end */ 
               reopenConnection = true; 
       } 
       else if (result < 0) { 
               if (errno == ECONNRESET) { 
                       reopenConnection = true;         
               } 
               else { 
                       /* Possibly this is too aggressive and 
reopenConnection should be set to true irrespective of the errno value */ 
                       throw something; 
               } 
       } 
} 

return reopenConnection 

I suggest the above logic be encapsulated in the channels and accessed 
through the IChannel interface in flushOutput as something like: 

bool connectionJustReopened = false; 
if (m_bReopenConnection || m_pActiveChannel->connectionReopenRequired()) { 

       m_pActiveChannel->close(); 
       m_pActiveChannel->open(); 
       connectionJustReopened = true; 
} 

bool retry; 
do { 
       retry = false; 
       try { 
               *m_pActiveChannel << this->getHTTPHeaders(); 
               *m_pActiveChannel << this->strDataBytes.c_str(); 
       } 
       catch (HTTPTransportException& e) {
              if (!connectionJustReopened) { 
                       m_pActiveChannel->close(); 
                       m_pActiveChannel->open(); 
                       retry = true; 
                       connectionJustReopened = true; 
               } 
               else { 
                       throw; 
               } 
       } 
} while (retry); 

Even if we implement a connectionReopenRequired interface we still need to 
re-open on IO error from the send because there is a race condition 
between when we test this and actually send the request - the connection 
ReopenRequired interface is really just an optimization. 

What do you think? 

Cheers, 

Tim
--
IBM Tivoli Access Manager Development
Gold Coast Development Lab, Australia
+61-7-5552-4001 phone
+61-7-5571-0420 fax