private void CloseInternal(bool internalCall, bool aborting) {
GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", internalCall.ToString());
GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::Abort");
bool normalShutDown = !aborting;
Exception exceptionOnWrite = null;
//
// We have to prevent recursion, because we'll call our parents, close,
// which might try to flush data. If we're in an error situation, that
// will cause an error on the write, which will cause Close to be called
// again, etc.
//
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() m_ShutDown:" + m_ShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString());
//If this is an abort (aborting == true) of a write stream then we will call request.Abort()
//that will call us again. To prevent a recursion here, only one abort is allowed.
//However, Abort must still override previous normal close if any.
if (aborting) {
if (Interlocked.Exchange(ref m_ShutDown, AlreadyAborted) >= AlreadyAborted) {
GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "already has been Aborted");
return;
}
}
else {
//If m_ShutDown != 0, then this method has been already called before,
//Hence disregard this (presumably normal) extra close
if (Interlocked.Increment(ref m_ShutDown) > 1) {
GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "already has been closed");
return;
}
}
//
// Since this should be the last call made, we should be at 0
// If not on the read side then it's an error so we should close the socket
// If not on the write side then MAY BE we want this write stream to ignore all
// further writes and optionally send chunk terminator.
//
int nesting = (IsPostStream && internalCall && !IgnoreSocketErrors && !BufferOnly && normalShutDown && !NclUtilities.HasShutdownStarted)? Nesting.Closed: Nesting.InError;
if (Interlocked.Exchange(ref m_CallNesting, nesting) == Nesting.IoInProgress)
{
if (nesting == Nesting.Closed)
{
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() PostStream, Internal call and m_CallNesting==1, defer closing until user write completes");
return;
}
normalShutDown &= !NclUtilities.HasShutdownStarted;
}
GlobalLog.Print("Close m_CallNesting: " + m_CallNesting.ToString());
// Questionable: Thsi is to avoid throwing on public Close() when IgnoreSocketErrors==true
if (IgnoreSocketErrors && IsPostStream && !internalCall)
{
m_BytesLeftToWrite = 0;
}
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() normalShutDown:" + normalShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString());
if (IgnoreSocketErrors || !normalShutDown) {
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() don't read/write on this, dead connection stream.");
}
else if (!WriteStream) {
//
// read stream
//
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() callNesting: " + m_CallNesting.ToString());
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() read stream, calling DrainSocket()");
#if DEBUG
using (GlobalLog.SetThreadKind(ThreadKinds.Sync)) {
#endif
normalShutDown = DrainSocket();
#if DEBUG
}
#endif
}
else {
//
// write stream. terminate our chunking if needed.
//
try {
if (!ErrorInStream) {
//
// if not error already, then...
// first handle chunking case
//
if (WriteChunked) {
//
// no need to buffer here:
// on resubmit, we won't be chunking anyway this will send 5 bytes on the wire
//
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() Chunked, writing ChunkTerminator");
try {
// The idea behind is that closed stream must not write anything to the wire
// Still if we are chunking, the now buffering and future resubmit is possible
if (!m_IgnoreSocketErrors) {
m_IgnoreSocketErrors = true;
SafeSetSocketTimeout(SocketShutdown.Send);
#if DEBUG
// Until there is an async version of this, we have to assert Sync privileges here.
using (GlobalLog.SetThreadKind(ThreadKinds.Sync)) {
#endif
m_Connection.Write(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length);
#if DEBUG
}
#endif
}
}
catch {
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() IGNORE chunk write fault");
}
m_BytesLeftToWrite = 0;
}
else if (BytesLeftToWrite>0) {
//
// not enough bytes written to client
//
GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() BytesLeftToWrite:" + BytesLeftToWrite.ToString() + " throwing not enough bytes written");
throw new IOException(SR.GetString(SR.net_io_notenoughbyteswritten));
}
else if (BufferOnly) {
//
// now we need to use the saved reference to the request the client
// closed the write stream. we need to wake up the request, so that it
// sends the headers and kick off resubmitting of buffered entity body
//
GlobalLog.Assert(m_Request != null, "ConnectStream#{0}::CloseInternal|m_Request == null", ValidationHelper.HashString(this));
m_BytesLeftToWrite = BufferedData.Length;
m_Request.SwitchToContentLength();
//
// writing the headers will kick off the whole request submission process
// (including waiting for the 100 Continue and writing the whole entity body)
//
SafeSetSocketTimeout(SocketShutdown.Send);
m_Request.NeedEndSubmitRequest();
GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "Done");
return;
}
}
else {
normalShutDown = false;
}
}
catch (Exception exception) {
normalShutDown = false;
if (NclUtilities.IsFatal(exception))
{
m_ErrorException = exception;
throw;
}
exceptionOnWrite = new WebException(
NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
exception,
WebExceptionStatus.RequestCanceled,
null);
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() exceptionOnWrite:" + exceptionOnWrite.Message);
}
}
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() normalShutDown:" + normalShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString());
if (!normalShutDown && m_DoneCalled==0) {
// If a normal Close (aborting == false) has turned into Abort _inside_ this method,
// then check if another abort has been charged from other thread
if (!aborting && Interlocked.Exchange(ref m_ShutDown, AlreadyAborted) >= AlreadyAborted){
GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "other thread has charged Abort(), canceling that one");
return;
}
//
// then abort the connection if we finished in error
// note: if m_DoneCalled != 0, then we no longer have
// control of the socket, so closing would cause us
// to close someone else's socket/connection.
//
m_ErrorException =
new WebException(
NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
WebExceptionStatus.RequestCanceled);
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() Aborting the connection");
m_Connection.AbortSocket(true);
// For write stream Abort() we depend on either of two, i.e:
// 1. The connection BeginRead is curently posted (means there are no response headers received yet)
// 2. The response (read) stream must be closed as well if aborted this (write) stream.
// Next block takes care of (2) since otherwise, (1) is true.
if (WriteStream) {
HttpWebRequest req = m_Request;
if (req != null) {
req.Abort();
}
}
if (exceptionOnWrite != null) {
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() calling CallDone() on exceptionOnWrite:" + exceptionOnWrite.Message);
CallDone();
if (!internalCall) {
GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() throwing:", exceptionOnWrite);
throw exceptionOnWrite;
}
}
}
//
// Let the connection know we're done writing or reading.
//
GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() calling CallDone()");
CallDone();
GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "Done");
}