From 6b7b487e8cd455b68c890356149b36c6a2b3bc55 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 22 Jun 2011 00:41:40 +0000 Subject: [PATCH] [TCPIP/IP] - Wait until the all pending sends are serviced before shutting down the socket on a graceful disconnect - Cancel pending sends and receives on abortive disconnect - Remove the nasty hack that was the completion queue and replace it with async completions like the lwIP implementation does - There is a bug with the graceful disconnects which occurs if the graceful disconnect cannot be serviced immediately (a very rare case in my testing) and results in the shutdown() call stalling forever because oskittcp never indicates that send is possible again (which would allow pending send IRPs to be serviced and the shutdown IRP to be completed) svn path=/trunk/; revision=52416 --- .../drivers/network/tcpip/include/titypes.h | 2 +- .../drivers/network/tcpip/tcpip/dispatch.c | 4 + reactos/lib/drivers/ip/transport/tcp/tcp.c | 198 ++++++++++++++---- 3 files changed, 159 insertions(+), 45 deletions(-) diff --git a/reactos/drivers/network/tcpip/include/titypes.h b/reactos/drivers/network/tcpip/include/titypes.h index 726ea002bbd..a8cd6bc2148 100644 --- a/reactos/drivers/network/tcpip/include/titypes.h +++ b/reactos/drivers/network/tcpip/include/titypes.h @@ -266,7 +266,7 @@ typedef struct _CONNECTION_ENDPOINT { LIST_ENTRY ListenRequest; /* Queued listen requests */ LIST_ENTRY ReceiveRequest; /* Queued receive requests */ LIST_ENTRY SendRequest; /* Queued send requests */ - LIST_ENTRY CompletionQueue;/* Completed requests to finish */ + LIST_ENTRY ShutdownRequest;/* Queued shutdown requests */ /* Signals */ UINT SignalState; /* Active signals from oskit */ diff --git a/reactos/drivers/network/tcpip/tcpip/dispatch.c b/reactos/drivers/network/tcpip/tcpip/dispatch.c index 2e108b8db33..6388acbf08a 100644 --- a/reactos/drivers/network/tcpip/tcpip/dispatch.c +++ b/reactos/drivers/network/tcpip/tcpip/dispatch.c @@ -171,6 +171,10 @@ VOID NTAPI DispCancelRequest( case TDI_CONNECT: DequeuedIrp = TCPRemoveIRP(TranContext->Handle.ConnectionContext, Irp); break; + + case TDI_DISCONNECT: + DequeuedIrp = TCPRemoveIRP(TranContext->Handle.ConnectionContext, Irp); + break; default: TI_DbgPrint(MIN_TRACE, ("Unknown IRP. MinorFunction (0x%X).\n", MinorFunction)); diff --git a/reactos/lib/drivers/ip/transport/tcp/tcp.c b/reactos/lib/drivers/ip/transport/tcp/tcp.c index 91849461570..20b01f5a3f8 100644 --- a/reactos/lib/drivers/ip/transport/tcp/tcp.c +++ b/reactos/lib/drivers/ip/transport/tcp/tcp.c @@ -18,6 +18,33 @@ static NPAGED_LOOKASIDE_LIST TCPSegmentList; PORT_SET TCPPorts; CLIENT_DATA ClientInfo; +VOID +CompleteBucketWorker(PVOID Context) +{ + PTDI_BUCKET Bucket = Context; + PCONNECTION_ENDPOINT Connection; + PTCP_COMPLETION_ROUTINE Complete; + + ASSERT(Bucket); + + Connection = Bucket->AssociatedEndpoint; + Complete = Bucket->Request.RequestNotifyObject; + + Complete(Bucket->Request.RequestContext, Bucket->Status, Bucket->Information); + + ExFreePoolWithTag(Bucket, TDI_BUCKET_TAG); + + DereferenceObject(Connection); +} + +VOID +CompleteBucket(PCONNECTION_ENDPOINT Connection, PTDI_BUCKET Bucket) +{ + Bucket->AssociatedEndpoint = Connection; + ReferenceObject(Connection); + ChewCreate(CompleteBucketWorker, Bucket); +} + VOID HandleSignalledConnection(PCONNECTION_ENDPOINT Connection) { PTDI_BUCKET Bucket; @@ -25,8 +52,6 @@ VOID HandleSignalledConnection(PCONNECTION_ENDPOINT Connection) NTSTATUS Status; PIRP Irp; PMDL Mdl; - KIRQL OldIrql; - PTCP_COMPLETION_ROUTINE Complete; if (ClientInfo.Unlocked) LockObjectAtDpcLevel(Connection); @@ -44,7 +69,7 @@ VOID HandleSignalledConnection(PCONNECTION_ENDPOINT Connection) Bucket->Status = (Connection->SignalState & SEL_CONNECT) ? STATUS_SUCCESS : STATUS_CANCELLED; Bucket->Information = 0; - InsertTailList(&Connection->CompletionQueue, &Bucket->Entry); + CompleteBucket(Connection, Bucket); } } @@ -91,7 +116,7 @@ VOID HandleSignalledConnection(PCONNECTION_ENDPOINT Connection) Bucket->Information = 0; DereferenceObject(Bucket->AssociatedEndpoint); - InsertTailList(&Connection->CompletionQueue, &Bucket->Entry); + CompleteBucket(Connection, Bucket); } } } @@ -155,7 +180,7 @@ VOID HandleSignalledConnection(PCONNECTION_ENDPOINT Connection) Bucket->Status = (Status == STATUS_PENDING) ? STATUS_CANCELLED : Status; Bucket->Information = (Bucket->Status == STATUS_SUCCESS) ? Received : 0; - InsertTailList(&Connection->CompletionQueue, &Bucket->Entry); + CompleteBucket(Connection, Bucket); } } } @@ -216,49 +241,60 @@ VOID HandleSignalledConnection(PCONNECTION_ENDPOINT Connection) Bucket->Status = (Status == STATUS_PENDING) ? STATUS_CANCELLED : Status; Bucket->Information = (Bucket->Status == STATUS_SUCCESS) ? Sent : 0; - InsertTailList(&Connection->CompletionQueue, &Bucket->Entry); + CompleteBucket(Connection, Bucket); } } } + if( Connection->SignalState & (SEL_WRITE | SEL_FIN) ) { + while (!IsListEmpty(&Connection->ShutdownRequest)) { + Entry = RemoveHeadList( &Connection->ShutdownRequest ); + + Bucket = CONTAINING_RECORD( Entry, TDI_BUCKET, Entry ); + + if (!(Connection->SignalState & SEL_FIN)) + { + /* See if we can satisfy this after the events */ + if (IsListEmpty(&Connection->SendRequest)) + { + /* Send queue is empty so we're good to go */ + Status = TCPTranslateError(OskitTCPShutdown(Connection->SocketContext, FWRITE)); + } + else + { + /* We still have to wait */ + Status = STATUS_PENDING; + } + } + else + { + /* We were cancelled by a FIN */ + Status = STATUS_CANCELLED; + } + + if( Status == STATUS_PENDING ) { + InsertHeadList( &Connection->ShutdownRequest, &Bucket->Entry ); + break; + } else { + TI_DbgPrint(DEBUG_TCP, + ("Completing shutdown request: %x %x\n", + Bucket->Request, Status)); - ReferenceObject(Connection); - if (ClientInfo.Unlocked) - { - UnlockObjectFromDpcLevel(Connection); - KeReleaseSpinLock(&ClientInfo.Lock, ClientInfo.OldIrql); + Bucket->Status = Status; + Bucket->Information = 0; + + CompleteBucket(Connection, Bucket); + } + } } - else - { - UnlockObject(Connection, Connection->OldIrql); - } - - while ((Entry = ExInterlockedRemoveHeadList(&Connection->CompletionQueue, - &Connection->Lock))) - { - Bucket = CONTAINING_RECORD(Entry, TDI_BUCKET, Entry); - Complete = Bucket->Request.RequestNotifyObject; - - Complete(Bucket->Request.RequestContext, Bucket->Status, Bucket->Information); - - ExFreePoolWithTag(Bucket, TDI_BUCKET_TAG); - } - - if (!ClientInfo.Unlocked) - { - LockObject(Connection, &OldIrql); - } - else - { - KeAcquireSpinLock(&ClientInfo.Lock, &ClientInfo.OldIrql); - } - DereferenceObject(Connection); - - /* If the socket is dead, remove the reference we added for oskit */ + if (Connection->SignalState & SEL_FIN) { Connection->SocketContext = NULL; DereferenceObject(Connection); } + + if (ClientInfo.Unlocked) + UnlockObjectFromDpcLevel(Connection); } VOID ConnectionFree(PVOID Object) { @@ -291,12 +327,12 @@ PCONNECTION_ENDPOINT TCPAllocateConnectionEndpoint( PVOID ClientContext ) { InitializeListHead(&Connection->ListenRequest); InitializeListHead(&Connection->ReceiveRequest); InitializeListHead(&Connection->SendRequest); - InitializeListHead(&Connection->CompletionQueue); + InitializeListHead(&Connection->ShutdownRequest); /* Save client context pointer */ Connection->ClientContext = ClientContext; - /* Add an extra reference for oskit */ + Connection->RefCount = 2; Connection->Free = ConnectionFree; @@ -662,23 +698,96 @@ NTSTATUS TCPDisconnect PTCP_COMPLETION_ROUTINE Complete, PVOID Context ) { NTSTATUS Status = STATUS_INVALID_PARAMETER; + PTDI_BUCKET Bucket; KIRQL OldIrql; + PLIST_ENTRY Entry; TI_DbgPrint(DEBUG_TCP,("started\n")); LockObject(Connection, &OldIrql); if (Flags & TDI_DISCONNECT_RELEASE) - Status = TCPTranslateError(OskitTCPShutdown(Connection->SocketContext, FWRITE)); + { + /* See if we can satisfy this right now */ + if (IsListEmpty(&Connection->SendRequest)) + { + /* Send queue is empty so we're good to go */ + Status = TCPTranslateError(OskitTCPShutdown(Connection->SocketContext, FWRITE)); + + UnlockObject(Connection, OldIrql); + + return Status; + } + + /* Otherwise we wait for the send queue to be empty */ + } if ((Flags & TDI_DISCONNECT_ABORT) || !Flags) + { + /* This request overrides any pending graceful disconnects */ + while (!IsListEmpty(&Connection->ShutdownRequest)) + { + Entry = RemoveHeadList(&Connection->ShutdownRequest); + + Bucket = CONTAINING_RECORD(Entry, TDI_BUCKET, Entry); + + Bucket->Information = 0; + Bucket->Status = STATUS_FILE_CLOSED; + + CompleteBucket(Connection, Bucket); + } + + /* Also kill any pending reads and writes */ + while (!IsListEmpty(&Connection->SendRequest)) + { + Entry = RemoveHeadList(&Connection->SendRequest); + + Bucket = CONTAINING_RECORD(Entry, TDI_BUCKET, Entry); + + Bucket->Information = 0; + Bucket->Status = STATUS_FILE_CLOSED; + + CompleteBucket(Connection, Bucket); + } + + while (!IsListEmpty(&Connection->ReceiveRequest)) + { + Entry = RemoveHeadList(&Connection->ReceiveRequest); + + Bucket = CONTAINING_RECORD(Entry, TDI_BUCKET, Entry); + + Bucket->Information = 0; + Bucket->Status = STATUS_FILE_CLOSED; + + CompleteBucket(Connection, Bucket); + } + + /* An abort never pends; we just drop everything and complete */ Status = TCPTranslateError(OskitTCPShutdown(Connection->SocketContext, FWRITE | FREAD)); + + UnlockObject(Connection, OldIrql); + + return Status; + } + + /* We couldn't complete the request now because we need to wait for outstanding I/O */ + Bucket = ExAllocatePoolWithTag(NonPagedPool, sizeof(*Bucket), TDI_BUCKET_TAG); + if (!Bucket) + { + UnlockObject(Connection, OldIrql); + return STATUS_NO_MEMORY; + } + + Bucket->Request.RequestNotifyObject = (PVOID)Complete; + Bucket->Request.RequestContext = Context; + + InsertTailList(&Connection->ShutdownRequest, &Bucket->Entry); UnlockObject(Connection, OldIrql); TI_DbgPrint(DEBUG_TCP,("finished %x\n", Status)); - return Status; + return STATUS_PENDING; } NTSTATUS TCPClose @@ -887,7 +996,7 @@ NTSTATUS TCPGetSockAddress BOOLEAN TCPRemoveIRP( PCONNECTION_ENDPOINT Endpoint, PIRP Irp ) { PLIST_ENTRY Entry; - PLIST_ENTRY ListHead[4]; + PLIST_ENTRY ListHead[5]; KIRQL OldIrql; PTDI_BUCKET Bucket; UINT i = 0; @@ -897,10 +1006,11 @@ BOOLEAN TCPRemoveIRP( PCONNECTION_ENDPOINT Endpoint, PIRP Irp ) { ListHead[1] = &Endpoint->ReceiveRequest; ListHead[2] = &Endpoint->ConnectRequest; ListHead[3] = &Endpoint->ListenRequest; + ListHead[4] = &Endpoint->ShutdownRequest; LockObject(Endpoint, &OldIrql); - for( i = 0; i < 4; i++ ) + for( i = 0; i < 5; i++ ) { for( Entry = ListHead[i]->Flink; Entry != ListHead[i];