|
|
/*++
Copyright (c) 1996,1997 Microsoft Corporation
Module Name:
SEND.C
Abstract:
Send Handler and Send Thread.
Author:
Aaron Ogus (aarono)
Environment:
Win32/COM
Revision History:
Date Author Description ====== ====== ============================================================ 12/10/96 aarono Original 2/18/98 aarono added support for SendEx 2/18/98 aarono added support for Cancel 2/20/98 aarono B#18827 not pulling Cancelled sends off queue 3/09/98 aarono documented workaround for mmTimers on Win95, removed dead code. 3/29/98 aarono fixed locking for ReliableSend 3/30/98 aarono make sure erroring sends moved to Done state to avoid reprocess. 4/14/98 a-peterz B#18340 DPSEND_NOCOPY subsumes DPSEND_NOBUFFERCOPY 5/18/98 aarono fixed SendEx with scatter gather 6/6/98 aarono Turn on throttling and windowing
--*/
#include <windows.h>
#include "newdpf.h"
#include <mmsystem.h>
#include <dplay.h>
#include <dplaysp.h>
#include <dplaypr.h>
#include "mydebug.h"
#include "arpd.h"
#include "arpdint.h"
#include "macros.h"
#include "mytimer.h"
BOOL DGCompleteSend(PSEND pSend);
// a-josbor: for debuggin purposes only
extern DWORD ExtractProtocolIds(PUCHAR pInBuffer, PUINT pdwIdFrom, PUINT pdwIdTo);
INT AddSendRef(PSEND pSend, UINT count) { INT newcount; Lock(&pSend->SendLock); Lock(&g_SendTimeoutListLock); if(pSend->bCleaningUp){ DPF(1,"WARNING: ADDSENDREF tried to add reference to cleaning up send\n"); newcount=0; goto exit; } if(!pSend->RefCount){ // Anyone calling addsend ref requires a reference on the session
Unlock(&g_SendTimeoutListLock); Unlock(&pSend->SendLock); Lock(&pSend->pSession->pProtocol->m_SessionLock); Lock(&pSend->pSession->SessionLock); Lock(&pSend->SendLock); Lock(&g_SendTimeoutListLock); InterlockedIncrement((PLONG)&pSend->pSession->RefCount); Unlock(&pSend->pSession->SessionLock); Unlock(&pSend->pSession->pProtocol->m_SessionLock); } newcount = pSend->RefCount+count; pSend->RefCount = newcount; exit: Unlock(&g_SendTimeoutListLock); Unlock(&pSend->SendLock); return newcount; }
// Critical Section must not be held when this is called, unless there
// is a reference for holding the critical section (ie. will not hit 0).
INT DecSendRef(PPROTOCOL pProtocol, PSEND pSend) { INT count; PSESSION pSession; Lock(&pSend->SendLock); count=InterlockedDecrement((PLONG)&pSend->RefCount);//count is zero if result of dec is zero, otw nonzero but not actual count.
if(!count){ pSession=pSend->pSession; pSend->bCleaningUp=TRUE; Unlock(&pSend->SendLock); // pull the Send off of the global queue and the session queue
Lock(&pProtocol->m_SendQLock); Lock(&pSession->SessionLock); Lock(&pSend->SendLock); Lock(&g_SendTimeoutListLock); if(!pSend->RefCount){ Delete(&pSend->TimeoutList); Delete(&pSend->m_GSendQ); Delete(&pSend->SendQ); } else { count=pSend->RefCount; }
Unlock(&g_SendTimeoutListLock); Unlock(&pSend->SendLock); Unlock(&pSession->SessionLock); Unlock(&pProtocol->m_SendQLock); if(!count){ DecSessionRef(pSession);
DPF(9,"DecSendRef: pSession %x pSend %x Freeing Send, called from %x\n",pSession, pSend, _ReturnAddress());
FreeHandleTableEntry(&pProtocol->lpHandleTable,&pProtocol->csHandleTable,pSend->dwMsgID); // Free the message buffer(s) (including memory if WE allocated it).
FreeBufferChainAndMemory(pSend->pMessage); // BUGBUG:move any Stats we want to keep to the session.
// free the send.(handles the stats for now).
ReleaseSendDesc(pSend); } } else { DPF(9,"DecSendRef: pSession %x pSend %x count %d, called from %x\n",pSend->pSession, pSend, count,_ReturnAddress()); if(count&0x80000000){ DEBUG_BREAK(); } Unlock(&pSend->SendLock); } return count; }
// SFLAGS_DOUBLEBUFFER - if the send is ASYNCHRONOUS, make a copy of the data
/*=============================================================================
Send - Send a message to a client. Description:
Used by the client to send a message to another directplay client or server.
Parameters:
ARPDID idFrom - who is sending this message ARPDID idTo - target DWORD dwSendFlags - specifies buffer ownership, priority, reliable LPVOID pBuffers - Array of buffer and lengths DWORD dwBufferCount - number of entries in array PASYNCINFO pAsyncInfo - If specified, call is asynchronous
typedef struct _ASYNCSENDINFO { UINT Private[4]; HANDLE hEvent; PSEND_CALLBACK SendCallBack; PVOID CallBackContext; UINT Status; } ASYNCSENDINFO, *PASYNCSENDINFO;
hEvent - event to signal when send completes. SendCallBack - routine to call when send completes. CallBackContext - context passed to SendCallBack. Status - send completion status.
Return Values:
DP_OK - no problem DPERR_INVALIDPARAMS
-----------------------------------------------------------------------------*/ HRESULT Send( PPROTOCOL pProtocol, DPID idFrom, DPID idTo, DWORD dwSendFlags, LPVOID pBuffers, DWORD dwBufferCount, DWORD dwSendPri, DWORD dwTimeOut, LPVOID lpvUserMsgID, LPDWORD lpdwMsgID, BOOL bSendEx, PASYNCSENDINFO pAsyncInfo ) { HRESULT hr=DP_OK;
PSESSION pSession; PBUFFER pSendBufferChain; PSEND pSend;
pSession=GetSysSession(pProtocol,idTo);
if(!pSession) { DPF(4,"NO SESSION for idTo %x, returning SESSIONLOST\n",idTo); hr=DPERR_CONNECTIONLOST; goto exit2; }
pSend=GetSendDesc(); if(!pSend){ ASSERT(0); //TRACE all paths.
hr=DPERR_OUTOFMEMORY; goto exit; }
pSend->pProtocol=pProtocol;
// fails by returning 0 in which case cancel won't be available for this send.
pSend->dwMsgID=AllocHandleTableEntry(&pProtocol->lpHandleTable, &pProtocol->csHandleTable, pSend);
if(lpdwMsgID){ *lpdwMsgID=pSend->dwMsgID; }
pSend->lpvUserMsgID = lpvUserMsgID; pSend->bSendEx = bSendEx;
// if pAsyncInfo is provided, the call is asynchronous.
// if dwFlags DPSEND_ASYNC is set, the call is async.
// if the call is asynchronous and double buffering is
// required, we must make a copy of the data.
if((pAsyncInfo||(dwSendFlags & DPSEND_ASYNC)) && (!(dwSendFlags & DPSEND_NOCOPY))){ // Need to copy the memory
pSendBufferChain=GetDoubleBufferAndCopy((PMEMDESC)pBuffers,dwBufferCount); // BUGBUG: if the provider requires contiguous buffers, we should
// break this down into packet allocations, and chain them
// on the send immediately. Using the packet chain to indicate
// to ISend routine that the message is already broken down.
} else { // Build a send buffer chain for the described buffers.
pSendBufferChain=BuildBufferChain((PMEMDESC)pBuffers,dwBufferCount); } if(!pSendBufferChain){ ASSERT(0); //TRACE all paths.
return DPERR_OUTOFMEMORY; } pSend->pSession = pSession; //!!! when this is dropped, deref the connection
pSend->pMessage = pSendBufferChain; pSend->MessageSize = BufferChainTotalSize(pSendBufferChain); pSend->SendOffset = 0; pSend->pCurrentBuffer = pSend->pMessage; pSend->CurrentBufferOffset = 0; pSend->Priority = dwSendPri; pSend->dwFlags = dwSendFlags; if(pAsyncInfo){ pSend->pAsyncInfo = &pSend->AsyncInfo; pSend->AsyncInfo = *pAsyncInfo; //copy Async info from client.
} else { pSend->pAsyncInfo = NULL; if(pSend->dwFlags & DPSEND_ASYNC){ pSend->AsyncInfo.hEvent = 0; pSend->AsyncInfo.SendCallBack = InternalSendComplete; pSend->AsyncInfo.CallBackContext= pSend; pSend->AsyncInfo.pStatus = &pSend->Status; } }
pSend->SendState = Start; pSend->RetryCount = 0; pSend->PacketSize = pSession->MaxPacketSize;
pSend->fUpdate = FALSE; pSend->NR = 0; pSend->NS = 0; //pSend->SendSEQMSK = // filled in on the fly.
pSend->WindowSize = pSession->WindowSize; pSend->SAKInterval = (pSend->WindowSize+1)/2; pSend->SAKCountDown = pSend->SAKInterval;
pSend->uRetryTimer = 0; pSend->idFrom = idFrom; pSend->idTo = idTo;
pSend->wIdFrom = GetIndexByDPID(pProtocol, idFrom); pSend->wIdTo = (WORD)pSession->iSession; pSend->RefCount = 0; // if provider does async send counts references.
pSend->serial = 0;
pSend->tLastACK = timeGetTime(); pSend->dwSendTime = pSend->tLastACK; pSend->dwTimeOut = dwTimeOut;
pSend->BytesThisSend = 0;
pSend->messageid = -1; // avoid matching this send in ACK/NACK handlers
pSend->bCleaningUp = FALSE;
hr=ISend(pProtocol,pSession, pSend);
exit: DecSessionRef(pSession); exit2: return hr;
}
/*================================================================================
Send Completion information matrix: ===================================
(pSend->dwFlags & ASEND_PROTOCOL) | Sync Async Internal (Async) -------------- ----- -------------------- pSend->pAsyncInfo 0 user 0 pSend->AI.SendCallback 0 user InternalSendComplete pSend->AI.hEvent pSend->hEvent user 0 pSend->AI.pStatus &pSend->Status user &pSend->Status ---------------------------------------------------------------------------*/
HRESULT ISend( PPROTOCOL pProtocol, PSESSION pSession, PSEND pSend ) { HRESULT hr=DP_OK;
DWORD_PTR fAsync; BOOL fCallDirect=FALSE;
fAsync=(DWORD_PTR)(pSend->pAsyncInfo);
if(!fAsync && !(pSend->dwFlags & (ASEND_PROTOCOL|DPSEND_ASYNC))) { //Synchronous call, and not a protocol generated packet
pSend->AsyncInfo.SendCallBack=NULL; //AsyncInfo.CallbackContext=0; //not required.
pSend->AsyncInfo.hEvent=pSend->hEvent; pSend->AsyncInfo.pStatus=&pSend->Status; ResetEvent(pSend->hEvent); }
// don't need to check if ref added here since the send isn't on a list yet.
AddSendRef(pSend,2); // 1 for ISend, 1 for completion.
DPF(9,"ISend: ==>Q\n"); hr=QueueSendOnSession(pProtocol,pSession,pSend); DPF(9,"ISend: <==Q\n");
if(hr==DP_OK){
if(!fAsync && !(pSend->dwFlags & (ASEND_PROTOCOL|DPSEND_ASYNC))){ // Synchronous call, and not internal, we need
// to wait until the send has completed.
if(!(pSend->dwFlags & DPSEND_GUARANTEED)){ // Non-guaranteed, need to drop dplay lock, in
// guaranteed case, dplay already dropped it for us.
LEAVE_DPLAY(); } DPF(9,"ISend: Wait==> %x\n",pSend->hEvent); Wait(pSend->hEvent); if(!(pSend->dwFlags & DPSEND_GUARANTEED)){ ENTER_DPLAY(); }
DPF(9,"ISend: <== WAIT\n"); hr=pSend->Status; } else { hr=DPERR_PENDING; }
} else { DecSendRef(pProtocol, pSend); //not going to complete a send that didn't enqueue.
} DecSendRef(pProtocol,pSend);
return hr; }
HRESULT QueueSendOnSession( PPROTOCOL pProtocol, PSESSION pSession, PSEND pSend ) { BILINK *pBilink; // walks the links scanning priority
BILINK *pPriQLink; // runs links in the global priority queue.
PSEND pSendWalker; // pointer to send structure
BOOL fFront; // if we put this at the front of the CON SendQ
BOOL fSignalQ=TRUE; // whether to signal the sendQ
// BUGBUG: locking global and connection queues concurrently,
// -> this better be fast!
ASSERT_SIGN(pSend, SEND_SIGN); Lock(&pProtocol->m_SendQLock); Lock(&pSession->SessionLock); Lock(&pSend->SendLock);
if(pSession->eState != Open){ Unlock(&pSend->SendLock); Unlock(&pSession->SessionLock); Unlock(&pProtocol->m_SendQLock); return DPERR_CONNECTIONLOST; }
if(!(pSend->dwFlags & ASEND_PROTOCOL)){ pProtocol->m_dwBytesPending += pSend->MessageSize; pProtocol->m_dwMessagesPending += 1; }
// Put on Connection SendQ
// First Check if we are highest priority.
pBilink = pSession->SendQ.next; pSendWalker=CONTAINING_RECORD(pBilink, SEND, SendQ); if(pBilink == &pSession->SendQ || pSendWalker->Priority < pSend->Priority) { InsertAfter(&pSend->SendQ,&pSession->SendQ); fFront=TRUE; } else {
// Scan backwards through the SendQ until we find a Send with a higher
// or equal priority and insert ourselves afterwards. This is optimized
// for the same pri send case.
pBilink = pSession->SendQ.prev;
while(TRUE /*pBilink != &pSend->SendQ*/){ pSendWalker = CONTAINING_RECORD(pBilink, SEND, SendQ); ASSERT_SIGN(pSendWalker, SEND_SIGN);
if(pSend->Priority <= pSendWalker->Priority){ InsertAfter(&pSend->SendQ, &pSendWalker->SendQ); fFront=FALSE; break; } pBilink=pBilink->prev; } ASSERT(pBilink != &pSend->SendQ); }
//
// Put on Global SendQ
//
if(!fFront){ // We queued it not at the front, therefore there are already
// entries in the Global Queue and we need to be inserted
// after the entry that we are behind, so start scanning the
// global queue backwards from the packet ahead of us in the
// Connection Queue until we find a lower priority packet
// get pointer into previous packet in queue.
pBilink=pSend->SendQ.prev; // get pointer to the PriorityQ record of the previous packet.
pPriQLink = &(CONTAINING_RECORD(pBilink, SEND, SendQ))->m_GSendQ;
while(pPriQLink != &pProtocol->m_GSendQ){ pSendWalker = CONTAINING_RECORD(pPriQLink, SEND, m_GSendQ); ASSERT_SIGN(pSendWalker, SEND_SIGN);
if(pSendWalker->Priority < pSend->Priority){ InsertBefore(&pSend->m_GSendQ, &pSendWalker->m_GSendQ); break; } pPriQLink=pPriQLink->next; } if(pPriQLink==&pProtocol->m_GSendQ){ // put at the end of the list.
InsertBefore(&pSend->m_GSendQ, &pProtocol->m_GSendQ); } } else { // There was no-one in front of us on the connection. So
// we look at the head of the global queue first and then scan
// from the back.
pBilink = pProtocol->m_GSendQ.next; pSendWalker=CONTAINING_RECORD(pBilink, SEND, m_GSendQ); if(pBilink == &pProtocol->m_GSendQ || pSend->Priority > pSendWalker->Priority) { InsertAfter(&pSend->m_GSendQ,&pProtocol->m_GSendQ); } else { // Scan backwards through the m_GSendQ until we find a Send with a higher
// or equal priority and insert ourselves afterwards. This is optimized
// for the same pri send case.
pBilink = pProtocol->m_GSendQ.prev;
while(TRUE){ pSendWalker = CONTAINING_RECORD(pBilink, SEND, m_GSendQ); ASSERT_SIGN(pSendWalker, SEND_SIGN); if(pSend->Priority <= pSendWalker->Priority){ InsertAfter(&pSend->m_GSendQ, &pSendWalker->m_GSendQ); break; } pBilink=pBilink->prev; } ASSERT(pBilink != &pProtocol->m_GSendQ); } }
// Fixup send state if we are blocking other sends on the session.
if(pSend->dwFlags & DPSEND_GUARANTEED){ if(pSession->nWaitingForMessageid){ pSend->SendState=WaitingForId; pSession->nWaitingForMessageid++; fSignalQ=FALSE; } } else { if(pSession->nWaitingForDGMessageid){ pSend->SendState=WaitingForId; pSession->nWaitingForDGMessageid++; fSignalQ=FALSE; } }
#ifdef DEBUG
DPF(9,"SessionQ:"); pBilink=pSession->SendQ.next; while(pBilink!=&pSession->SendQ){ pSendWalker=CONTAINING_RECORD(pBilink, SEND, SendQ); ASSERT_SIGN(pSendWalker,SEND_SIGN); DPF(9,"Send %x pSession %x Pri %x State %d\n",pSendWalker,pSendWalker->pSession,pSendWalker->Priority,pSendWalker->SendState); pBilink=pBilink->next; } DPF(9,"GlobalQ:"); pBilink=pProtocol->m_GSendQ.next; while(pBilink!=&pProtocol->m_GSendQ){ pSendWalker=CONTAINING_RECORD(pBilink, SEND, m_GSendQ); ASSERT_SIGN(pSendWalker,SEND_SIGN); DPF(9,"Send %x pSession %x Pri %x State %d\n",pSendWalker,pSendWalker->pSession,pSendWalker->Priority,pSendWalker->SendState); pBilink=pBilink->next; } #endif
Unlock(&pSend->SendLock); Unlock(&pSession->SessionLock); Unlock(&pProtocol->m_SendQLock);
if(fSignalQ){ // tell send thread to process.
SetEvent(pProtocol->m_hSendEvent); }
return DP_OK; }
/*=============================================================================
CopyDataToFrame Description:
Copies data for a frame from the Send to the frame's data area.
Parameters:
pFrameData - pointer to data area FrameDataSize - Size of the Frame Data area pSend - send from which to get data nAhead - number of frames ahead of NR to get data for. Return Values:
Number of bytes copied.
Notes:
Send must be locked across this call. -----------------------------------------------------------------------------*/
UINT CopyDataToFrame( PUCHAR pFrameData, UINT FrameDataLen, PSEND pSend, UINT nAhead) { UINT BytesToAdvance, BytesToCopy; UINT FrameOffset=0; PUCHAR dest,src; UINT len; UINT totlen=0;
UINT SendOffset; PBUFFER pSrcBuffer; UINT CurrentBufferOffset;
BytesToAdvance = nAhead*FrameDataLen; SendOffset = pSend->SendOffset; pSrcBuffer = pSend->pCurrentBuffer; CurrentBufferOffset = pSend->CurrentBufferOffset;
//
// Run ahead to the buffer we start getting data from
//
while(BytesToAdvance){
len = pSrcBuffer->len - CurrentBufferOffset;
if(len > BytesToAdvance){ CurrentBufferOffset += BytesToAdvance; SendOffset+=BytesToAdvance; BytesToAdvance=0; } else { pSrcBuffer=pSrcBuffer->pNext; CurrentBufferOffset = 0; BytesToAdvance-=len; SendOffset+=len; } }
//
// Copy the data for the Send into the frame
//
BytesToCopy = pSend->MessageSize - SendOffset;
if(BytesToCopy > FrameDataLen){ BytesToCopy=FrameDataLen; }
while(BytesToCopy){
ASSERT(pSrcBuffer); dest= pFrameData + FrameOffset; src = pSrcBuffer->pData + CurrentBufferOffset; len = pSrcBuffer->len - CurrentBufferOffset;
if(len > BytesToCopy){ len=BytesToCopy; CurrentBufferOffset+=len;//BUGBUG: not used after, don't need.
} else { pSrcBuffer = pSrcBuffer->pNext; CurrentBufferOffset = 0; }
BytesToCopy -= len; FrameOffset += len; totlen+=len; memcpy(dest,src,len); } return totlen; }
// NOTE: ONLY 1 SEND THREAD ALLOWED.
ULONG WINAPI SendThread(LPVOID pProt) { PPROTOCOL pProtocol=((PPROTOCOL)pProt); UINT SendRc;
while(TRUE){
WaitForSingleObject(pProtocol->m_hSendEvent, INFINITE);
Lock(&pProtocol->m_ObjLock); if(pProtocol->m_eState==ShuttingDown){ pProtocol->m_nSendThreads--; Unlock(&pProtocol->m_ObjLock); ExitThread(0); }
Unlock(&pProtocol->m_ObjLock);
do { SendRc=SendHandler(pProtocol); } while (SendRc!=DPERR_NOMESSAGES);
} return TRUE; }
// Called with SendLock held.
VOID CancelRetryTimer(PSEND pSend) { // UINT mmError;
UINT retrycount=0; UINT_PTR uRetryTimer; UINT Unique; if(pSend->uRetryTimer){ DPF(9,"Canceling Timer %x\n",pSend->uRetryTimer);
// Delete it from the list first so we don't deadlock trying to kill it.
Lock(&g_SendTimeoutListLock);
uRetryTimer=pSend->uRetryTimer; Unique=pSend->TimerUnique; pSend->uRetryTimer=0; if(!EMPTY_BILINK(&pSend->TimeoutList)){ Delete(&pSend->TimeoutList); InitBilink(&pSend->TimeoutList); // avoids DecSendRef having to know state of bilink.
Unlock(&g_SendTimeoutListLock);
CancelMyTimer(uRetryTimer, Unique);
} else { Unlock(&g_SendTimeoutListLock); } } else { DPF(9,"CancelRetryTimer:No timer to cancel.\n"); } }
// Workaround for Win95 mmTimers:
// ==============================
//
// We cannot use a reference count for the timeouts as a result of the following Win95 bug:
//
// The cancelling of mmTimers is non-deterministic. That is, when calling cancel, you cannot
// tell from the return code whether the timer ran, was cancelled or is still going to run.
// Since we use the Send as the context for timeout, we cannot dereference it until we make
// sure it is still valid, since code that cancelled the send and timer may have already freed
// the send memory. We place the sends being timed out on a list and scan the list for the
// send before we use it. If we don't find the send on the list, we ignore the timeout.
//
// Also note, this workaround is not very expensive. The linked list is in the order timeouts
// were scheduled, so generally if the links are approximately the same speed, timeouts will
// be similiar so the context being checked should be near the beginning of the list.
CRITICAL_SECTION g_SendTimeoutListLock; BILINK g_BilinkSendTimeoutList;
void CALLBACK RetryTimerExpiry( UINT_PTR uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2 ) { PSEND pSend=(PSEND)(dwUser), pSendWalker; UINT tWaiting; BILINK *pBilink; UINT bFound=FALSE;
DPF(9,"RetryTimerExpiry: %x, expecting %x, pSend %x\n",uID, pSend->uRetryTimer, pSend);
tWaiting=timeGetTime();
// Scan the list of waiting sends to see if this one is still waiting for a timeout.
Lock(&g_SendTimeoutListLock);
pBilink=g_BilinkSendTimeoutList.next;
while(pBilink!=&g_BilinkSendTimeoutList){ pSendWalker=CONTAINING_RECORD(pBilink, SEND, TimeoutList); pBilink=pBilink->next; if(pSendWalker == pSend){ if(pSend->uRetryTimer==uID){ Delete(&pSend->TimeoutList); InitBilink(&pSend->TimeoutList); // avoids DecSendRef having to know state of bilink.
Unlock(&g_SendTimeoutListLock); // it is ok to call AddSendRef here without the sessionlock because
// there is no way we could be adding the session reference. If
// the refcount is 0, it can only mean the send is already cleaning up
// and we won't try to take the session locks so there is no lock
// ordering problem.
bFound=AddSendRef(pSend,1); // note bFound set to Refcount on send
goto skip_unlock; } } } Unlock(&g_SendTimeoutListLock);
skip_unlock: if(bFound){
if(pSend->tRetryScheduled - pSend->tScheduled > 500){ DWORD tm=timeGetTime(); if(tm - pSend->tScheduled < 100 ){ DPF(9,"RETRY TIMER EXPIRY IS WAY TOO EARLY, EXPECTED AT %x ACTUALLY AT %x\n",pSend->tRetryScheduled, tm); DEBUG_BREAK(); } } DPF(9,"RetryTimerExpiry: Waiting For Send Lock...\n");
Lock(&pSend->SendLock);
DPF(9,"RetryTimerExpiry: Got SendLock\n");
if(pSend->uRetryTimer==uID){ // check again, may be cancelled.
pSend->uRetryTimer=0;
switch(pSend->SendState) { case Start: case Sending: ASSERT(0); case Done: break; case WaitingForAck:
pSend->RetryCount++; tWaiting-=pSend->tLastACK;
#ifdef DEBUG
{ static int retries; IN_WRITESTATS InWS; memset((PVOID)&InWS,0xFF,sizeof(IN_WRITESTATS)); InWS.stat_USER1=((retries++)%20)+1; DbgWriteStats(&InWS); } #endif
if(tWaiting > pSend->pSession->MaxDropTime || (pSend->RetryCount > pSend->pSession->MaxRetry && tWaiting > pSend->pSession->MinDropTime) ) { DPF(8,"Send %x Timed Out, tWaiting: %d RetryCount: %d\n",pSend,tWaiting,pSend->RetryCount); pSend->SendState=TimedOut; } else { DPF(9,"Timer expired, retrying send %x RetryCount= %d\n",pSend,pSend->RetryCount); //pSend->NACKMask|=(1<<(pSend->NS-pSend->NR))-1;
pSend->NACKMask |= 1; // just retry 1 frame.
ASSERT_NACKMask(pSend); pSend->SendState=ReadyToSend; } SetEvent(pSend->pSession->pProtocol->m_hSendEvent); break; case Throttled: break; case ReadyToSend: default: break;
} } Unlock(&pSend->SendLock); DecSendRef(pSend->pSession->pProtocol, pSend); } }
VOID StartRetryTimer(PSEND pSend) { UINT FptLatency; UINT tLatencyLong; UINT FptDev; UINT tRetry;
FptLatency=max(pSend->pSession->FpLocalAverageLatency,pSend->pSession->LastLatency); FptDev=pSend->pSession->FpLocalAvgDeviation; tRetry=unFp(FptLatency+3*FptDev);//Latency +3 average deviations
tLatencyLong=unFp(pSend->pSession->FpAverageLatency);
// Sometimes stddev of latency gets badly skewed by the serial driver
// taking a long time to complete locally, avoid setting retry time
// too high by limiting to 2x the long latency average.
if(tLatencyLong > 100 && tRetry > 2*max(tLatencyLong,unFp(FptLatency))){ tRetry = 2*tLatencyLong; }
if(pSend->RetryCount > 3){ if(pSend->pSession->RemoteBytesReceived==0){ // haven't spoken to remote yet, may be waiting for nametable, so back down hard.
tRetry=5000; } else if (tRetry < 1000){ // taking a lot of retries to get response, back down.
tRetry=1000; } }
if(tRetry < 50){ tRetry=50; } ASSERT(tRetry);
if(tRetry > 30000){ DPF(0,"RETRY TIMER REQUESTING %d seconds?\n",tRetry); } if(!pSend->uRetryTimer){
Lock(&g_SendTimeoutListLock);
DPF(9,"Setting Retry Timer of %d ms\n", tRetry);
pSend->uRetryTimer=SetMyTimer((tRetry)?(tRetry):1,(tRetry>>2)+1,RetryTimerExpiry,(ULONG_PTR) pSend,&pSend->TimerUnique); if(pSend->uRetryTimer){ pSend->tScheduled = timeGetTime(); pSend->tRetryScheduled = pSend->tScheduled+tRetry; InsertBefore(&pSend->TimeoutList, &g_BilinkSendTimeoutList); } else { DPF(0,"Start Retry Timer failed to schedule a timer with tRetry=%d for pSend %x\n",tRetry,pSend); DEBUG_BREAK(); } DPF(9,"Started Retry Timer %x\n",pSend->uRetryTimer);
Unlock(&g_SendTimeoutListLock); if(!pSend->uRetryTimer){ ASSERT(0); } } else { ASSERT(0); }
}
// Called with all necessary locks held.
VOID TimeOutSession(PSESSION pSession) { PSEND pSend; BILINK *pBilink; UINT nSignalsRequired=0;
// Mark Session Timed out.
pSession->eState=Closing; // Mark all sends Timed out.
pBilink=pSession->SendQ.next;
while(pBilink != &pSession->SendQ){ pSend=CONTAINING_RECORD(pBilink, SEND, SendQ); pBilink=pBilink->next;
DPF(9,"TimeOutSession: Force Timing Out Send %x, State %d\n",pSend, pSend->SendState);
switch(pSend->SendState){ case Start: case Throttled: case ReadyToSend: DPF(9,"TimeOutSession: Moving to TimedOut, should be safe\n"); pSend->SendState=TimedOut; nSignalsRequired += 1; break; case Sending: //BUGBUG: can we even get here? If we can
// the send will reset the retry count and tLastACK.
DPF(9,"TimeOutSession: ALLOWING TimeOut to cancel.(could take 15 secs)\n"); pSend->RetryCount=pSession->MaxRetry; pSend->tLastACK=timeGetTime()-pSession->MinDropTime; break;
case WaitingForAck: DPF(9,"TimeOutSession: Canceling timer and making TimedOut\n"); CancelRetryTimer(pSend); pSend->SendState = TimedOut; nSignalsRequired += 1; break; case WaitingForId: // Note, this means we can get signals for ids that aren't used.
DPF(9,"TimeOutSession: Timing Out Send Waiting for ID, GetNextMessageToSend may fail, this is OK\n"); pSend->SendState=TimedOut; if(pSend->dwFlags & DPSEND_GUARANTEED){ InterlockedDecrement(&pSession->nWaitingForMessageid); } else { InterlockedDecrement(&pSession->nWaitingForDGMessageid); } nSignalsRequired += 1; break; case TimedOut: case Done: DPF(9,"TimeOutSession: Send already done or timed out, doesn't need our help\n"); break; default: DPF(0,"TimeOutSession, pSession %x found Send %x in Wierd State %d\n",pSession,pSend,pSend->SendState); ASSERT(0); break; } /* switch */
} /* while */
// Create enough signals to process timed out sends.
DPF(9,"Signalling SendQ %d items to process\n",nSignalsRequired); SetEvent(pSession->pProtocol->m_hSendEvent); }
UINT WrapSend(PPROTOCOL pProtocol, PSEND pSend, PBUFFER pBuffer) { PUCHAR pMessage,pMessageStart; DWORD dwWrapSize=0; DWORD dwIdTo=0; DWORD dwIdFrom=0;
pMessageStart = &pBuffer->pData[pProtocol->m_dwSPHeaderSize]; pMessage = pMessageStart; dwIdFrom = pSend->wIdFrom; dwIdTo = pSend->wIdTo; if(dwIdFrom==0x70){ // avoid looking like a system message 'play'
dwIdFrom=0xFFFF; }
if(dwIdFrom){ while(dwIdFrom){ *pMessage=(UCHAR)(dwIdFrom & 0x7F); dwIdFrom >>= 7; if(dwIdFrom){ *pMessage|=0x80; } pMessage++; } } else { *(pMessage++)=0; }
if(dwIdTo){ while(dwIdTo){ *pMessage=(UCHAR)(dwIdTo & 0x7F); dwIdTo >>= 7; if(dwIdTo){ *pMessage|=0x80; } pMessage++; } } else { *(pMessage++)=0; }
#if 0 // a-josbor: for debugging only. I left it in in case we ever needed it again
ExtractProtocolIds(pMessageStart, &dwIdFrom, &dwIdTo); ASSERT(dwIdFrom == pSend->wIdFrom); ASSERT(dwIdTo == pSend->wIdTo); #endif
return (UINT)(pMessage-pMessageStart); }
#define DROP 0
#if DROP
// 1 for send, 0 for drop.
char droparray[]= { 1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,0,0,0,0};
UINT dropindex=0; #endif
VOID CALLBACK UnThrottle(UINT_PTR uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2) { PSESSION pSession=(PSESSION)dwUser; UINT tMissedBy; // how long we missed the throttle by.
DWORD tm;
Lock(&pSession->SessionLock);
tm=timeGetTime(); tMissedBy = tm-pSession->tNextSend;
if( (int)tMissedBy > 0){ pSession->FpAvgUnThrottleTime -= pSession->FpAvgUnThrottleTime >> 4; pSession->FpAvgUnThrottleTime += (Fp(tMissedBy) >> 4); DPF(9,"Missed by: %d ms Avg Unthrottle Miss %d.%d ms\n", tMissedBy, pSession->FpAvgUnThrottleTime >> 8, (((pSession->FpAvgUnThrottleTime&0xFF)*100)/256) ); } pSession->uUnThrottle=0; pSession->dwFlags |= SESSION_UNTHROTTLED; pSession->pProtocol->m_bRescanQueue=TRUE; // tell send routine to restart scan.
DPF(9,"Unthrottling Session %x at %d\n",pSession, timeGetTime()); Unlock(&pSession->SessionLock); SetEvent(pSession->pProtocol->m_hSendEvent); DecSessionRef(pSession); }
VOID Throttle( PSESSION pSession, DWORD tm ) { DWORD tmDelta; Lock(&pSession->SessionLock); pSession->bhitThrottle=TRUE; pSession->dwFlags |= SESSION_THROTTLED; tmDelta = pSession->tNextSend - tm; if((INT)tmDelta < 0){ tmDelta=1; } DPF(9,"Throttling pSession %x for %d ms (until %d)\n",pSession, tmDelta,pSession->tNextSend); pSession->RefCount++; pSession->uUnThrottle = SetMyTimer(tmDelta, (tmDelta>>2)?(tmDelta>>2):1, UnThrottle, (DWORD_PTR)pSession, &pSession->UnThrottleUnique); if(!pSession->uUnThrottle){ DPF(0,"UH OH failed to schedule unthrottle event\n"); DEBUG_BREAK(); } Unlock(&pSession->SessionLock); #ifdef DEBUG
{ static int throttlecounter; IN_WRITESTATS InWS; memset((PVOID)&InWS,0xFF,sizeof(IN_WRITESTATS)); InWS.stat_USER4=((throttlecounter++)%20)+1; DbgWriteStats(&InWS); } #endif
}
// Given the current time, the bandwidth we are throttling to and the length of the packet we are sending,
// calculate the next time we are allowed to send. Also keep a residue from this calculation so that
// we don't wind up using excessive bandwidth due to rounding, the residue from the last calculation is
// used in this calculation.
// Absolute flag means set the next send time relative to tm regardless
VOID UpdateSendTime(PSESSION pSession, DWORD Len, DWORD tm, BOOL fAbsolute) { #define SendRate pSession->SendRateThrottle
#define Residue pSession->tNextSendResidue
#define tNext pSession->tNextSend
DWORD tFrame; // amount of time this frame will take on the wire.
tFrame = (Len+Residue)*1000 / SendRate; // rate is bps, but want to calc bpms, so (Len+Residue)*1000
Residue = (Len+Residue) - (tFrame * SendRate)/1000 ; ASSERT(!(Residue&0x80000000)); // residue better be +ve
if(fAbsolute || (INT)(tNext - tm) < 0){ // tNext is less than tm, so calc based on tm.
tNext = tm+tFrame; } else { // tNext is greater than tm, so add more wait.
tNext = tNext+tFrame; }
DPF(8,"UpdateSendTime time %d, tFrame %d, Residue %d, tNext %d",tm,tFrame,Residue,tNext);
#undef SendRate
#undef Residue
#undef tNext
}
//CHAR Drop[]={0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1,1,0,0};
//DWORD DropSize = sizeof(Drop);
//DWORD iDrop=0;
// AO - added contraint, 1 send thread per session. Since this is not enforced by GetNextMessageToSend
// 5-21-98 we are effectively restricted to 1 send thread for the protocol. We can fix this by adding
// a sending state on the session and having GetNextMessageToSend skip sending sessions.
HRESULT ReliableSend(PPROTOCOL pProtocol, PSEND pSend) { #define pBigFrame ((pPacket2)(pFrame))
HRESULT hr; PBUFFER pBuffer;
pPacket1 pFrame; PUCHAR pFrameData; UINT FrameDataLen; UINT FrameTotalLen; UINT MaxFrameLen; UINT FrameHeaderLen;
UINT nFramesOutstanding; UINT nFramesToSend; UINT msk; UINT shift;
UINT WrapSize; UINT DPWrapSize; // DirectPlay wrapping only. ([[DPLAY 0xFF]|],From,To)
DWORD tm=0; // The time, 0 if we haven't retrieved it yet.
DWORD tmExit=0; BOOL bExitEarly=FALSE;
DPSP_SENDDATA SendData; //
// Sending algorithm is designed to handle NACKs only (there
// is no special case for sending data the first time). So
// We send by making it look like the frames we want to send
// have been NACKed. Every frame we send, we clear the NACK
// bit for. If an actual NACK comes in, the bit is set.
// When an ACK comes in, we shift the NACK and ACK masks
// nACK-NR and if applicable, set new NACK bits.
//
Lock(&pSend->SendLock);
if(pSend->SendState == Done){ goto unlock_exit; }
nFramesOutstanding=(pSend->NS-pSend->NR);
if( nFramesOutstanding < pSend->WindowSize){
// Set NACK bits up to WindowSize (unless over nFrames);
nFramesToSend=pSend->WindowSize-nFramesOutstanding;
if(nFramesToSend > pSend->nFrames-pSend->NS){ nFramesToSend=pSend->nFrames-pSend->NS; }
pSend->NACKMask |= ((1<<nFramesToSend)-1)<<nFramesOutstanding; pSend->OpenWindow = nFramesOutstanding + nFramesToSend; DPF(9,"Send: pSend->NACKMask %x, OpenWindow %d\n",pSend->NACKMask, pSend->OpenWindow); }
tmExit=timeGetTime()+1000; // always blow out of here in 1 second max.
Reload: msk=1; shift=0; MaxFrameLen=pSend->FrameSize;
while(pSend->NACKMask){ ASSERT_NACKMask(pSend); tm=timeGetTime(); // Getting the time is relatively expensive, so we do it once here and pass it around.
if(((INT)tm - (INT)tmExit) > 0){ DPF(0,"Breaking Out of Send Loop due to expiry of timer\n"); bExitEarly=TRUE; break; } if((tm+unFp(pSend->pSession->FpAvgUnThrottleTime)-pSend->pSession->tNextSend) & 0x80000000){ // we're still too early to do the next send, so throttled this session.
goto throttle_exit; }
if(pSend->NACKMask & msk){
pBuffer=GetFrameBuffer(MaxFrameLen+pProtocol->m_dwSPHeaderSize+MAX_SEND_HEADER); if(!pBuffer){ pSend->SendState=ReadyToSend; SetEvent(pSend->pSession->pProtocol->m_hSendEvent); // keep the queue rolling.
hr=DPERR_PENDING; goto exit; }
WrapSize=pProtocol->m_dwSPHeaderSize; // leave space for SP header.
DPWrapSize=WrapSend(pProtocol, pSend, pBuffer); // fill in out address wrapping
WrapSize+=DPWrapSize;
pFrame=(pPacket1)&pBuffer->pData[WrapSize]; // protocol header after wrapping
if(pSend->fSendSmall){ pFrameData=&pFrame->data[0]; FrameHeaderLen=(UINT)(pFrameData-(PUCHAR)pFrame); } else { pFrameData=&pBigFrame->data[0]; FrameHeaderLen=(UINT)(pFrameData-(PUCHAR)pFrame); }
// For calculating nFrames, we assumed MAX_SEND_HEADER, subtract out the unused portion
// so we don't put to much data in the frame and mess up the accounting.
pBuffer->len-=(MAX_SEND_HEADER-(FrameHeaderLen+DPWrapSize));
FrameHeaderLen += WrapSize; // now include wrapping and SPheader space.
FrameDataLen=CopyDataToFrame(pFrameData, pBuffer->len-FrameHeaderLen, pSend, shift);
if(!pSend->FrameDataLen){ pSend->FrameDataLen=FrameDataLen; } FrameTotalLen=FrameDataLen+FrameHeaderLen;
pSend->BytesThisSend=FrameTotalLen-WrapSize; //only counting payload
// Do that protocol thing
BuildHeader(pSend,pFrame,shift,tm);
// we know we don't have to check here since we have a reference
// from finding the send to work on ON the send queue. So it
// can't go away til we return from this function.
hr=AddSendRef(pSend,1); ASSERT(hr); if(pSend->NR+shift >= pSend->NS){ pSend->NS = pSend->NR+shift+1; } pSend->NACKMask &= ~msk; DPF(9,"S %2x %2x %2x\n",pBuffer->pData[0], pBuffer->pData[1], pBuffer->pData[2]);
// Update the next time we are allowed to send.
UpdateSendTime(pSend->pSession, pSend->BytesThisSend, tm, FALSE);
Unlock(&pSend->SendLock);
ASSERT(!(FrameTotalLen &0xFFFF0000));
// Send this puppy...
SendData.dwFlags = pSend->dwFlags & ~DPSEND_GUARANTEED; SendData.idPlayerTo = pSend->idTo; SendData.idPlayerFrom = pSend->idFrom; SendData.lpMessage = pBuffer->pData; SendData.dwMessageSize = FrameTotalLen; SendData.bSystemMessage = 0; SendData.lpISP = pProtocol->m_lpISP;
ENTER_DPLAY();
Lock(&pProtocol->m_SPLock);
// if(!(Drop[(iDrop++)%DropSize])){//BUGBUG: DEBUG ONLY!
hr=CALLSP(pProtocol->m_lpDPlay->pcbSPCallbacks->Send,&SendData); // }
Unlock(&pProtocol->m_SPLock);
LEAVE_DPLAY(); if(hr!=DPERR_PENDING){ if(!DecSendRef(pProtocol, pSend)){ ASSERT(0); hr=DPERR_PENDING; goto exit; } FreeFrameBuffer(pBuffer); }
Lock(&pSend->SendLock); } /* endif (pSend->NACKMask & msk) */
if(pSend->fUpdate){ pSend->fUpdate=FALSE; goto Reload; }
// Check if we are past windowsize, if so roll back the mask
// Also if there are earlier bits to ACK.
if((msk<<=1UL) >= (1UL<<pSend->WindowSize)){ msk=1; shift=0; } else { shift++; }
} /* end while (pSend->NACKMask) */
if(pSend->SendState != Done){
if(bExitEarly){ pSend->SendState=ReadyToSend; SetEvent(pSend->pSession->pProtocol->m_hSendEvent); // keep the queue rolling.
} else { pSend->SendState=WaitingForAck; StartRetryTimer(pSend); } }
unlock_exit: Unlock(&pSend->SendLock);
hr=DPERR_PENDING; // Reliable sends are completed by the ACK.
exit: return hr;
throttle_exit:
hr=DPERR_PENDING; pSend->SendState=Throttled; Unlock(&pSend->SendLock);
Throttle(pSend->pSession, tm);
return hr; #undef pBigFrame
}
// TRUE, didn't reach end, FALSE, no more to send.
BOOL AdvanceSend(PSEND pSend, UINT AckedLen) { BOOL rc=TRUE;
// quick short circuit for small messages.
if(AckedLen+pSend->SendOffset==pSend->MessageSize){ rc=FALSE; goto exit; } if(pSend->SendOffset+AckedLen > pSend->MessageSize){ AckedLen=pSend->MessageSize-pSend->SendOffset; } pSend->SendOffset+=AckedLen; while(AckedLen){ if(pSend->pCurrentBuffer->len-pSend->CurrentBufferOffset >= AckedLen){ pSend->CurrentBufferOffset+=AckedLen; rc=TRUE; break; } else { AckedLen -= (pSend->pCurrentBuffer->len-pSend->CurrentBufferOffset); pSend->pCurrentBuffer=pSend->pCurrentBuffer->pNext; pSend->CurrentBufferOffset=0; rc=FALSE; } }
exit: return rc; }
HRESULT DGSend(PPROTOCOL pProtocol, PSEND pSend) { #define pBigFrame ((pPacket2)(pFrame))
PBUFFER pBuffer;
pPacket1 pFrame; PUCHAR pFrameData; UINT FrameDataLen; UINT FrameHeaderLen; UINT FrameTotalLen; UINT MaxFrameLen;
UINT nFramesToSend;
UINT WrapSize; UINT DPWrapSize; // DirectPlay wrapping only. ([[DPLAY 0xFF]|],From,To)
DPSP_SENDDATA SendData;
DWORD tm; HRESULT hr; Lock(&pSend->SendLock);
nFramesToSend=pSend->nFrames-pSend->NR;
MaxFrameLen=pSend->FrameSize;
while(nFramesToSend){
tm=timeGetTime(); // Getting the time is relatively expensive, so we do it once here and pass it around.
if((tm+unFp(pSend->pSession->FpAvgUnThrottleTime)-pSend->pSession->tNextSend) & 0x80000000){ // we're still too early to do the next send, so throttled this session.
goto throttle_exit; }
pBuffer=GetFrameBuffer(MaxFrameLen+pProtocol->m_dwSPHeaderSize+MAX_SEND_HEADER); if(!pBuffer){ hr=DPERR_PENDING; goto exit; }
WrapSize=pProtocol->m_dwSPHeaderSize; // leave space for SP header.
DPWrapSize=WrapSend(pProtocol, pSend, pBuffer); // fill in out address wrapping
WrapSize+=DPWrapSize;
pFrame=(pPacket1)&pBuffer->pData[WrapSize]; // protocol header after wrapping
if(pSend->fSendSmall){ pFrameData=&pFrame->data[0]; FrameHeaderLen=(UINT)(pFrameData-(PUCHAR)pFrame); } else { pFrameData=&pBigFrame->data[0]; FrameHeaderLen=(UINT)(pFrameData-(PUCHAR)pFrame); }
// For calculating nFrames, we assumed MAX_SEND_HEADER, subtract out the unused portion
// so we don't put to much data in the frame and mess up the accounting.
pBuffer->len-=(MAX_SEND_HEADER-(FrameHeaderLen+DPWrapSize));
FrameHeaderLen += WrapSize; // now include wrapping and SPheader space.
FrameDataLen=CopyDataToFrame(pFrameData, pBuffer->len-FrameHeaderLen, pSend, 0);
FrameTotalLen=FrameDataLen+FrameHeaderLen; pSend->BytesThisSend=FrameTotalLen-WrapSize; //only counting payload
// Do that protocol thing
BuildHeader(pSend,pFrame,0,tm);
//AddSendRef(pSend,1); //already locked, so just add one.
ASSERT(pSend->RefCount); //verifies ++ below is ok.
InterlockedIncrement((PLONG)&pSend->RefCount);
UpdateSendTime(pSend->pSession,pSend->BytesThisSend,tm,FALSE); Unlock(&pSend->SendLock);
// Send this puppy...
ASSERT(!(pSend->dwFlags & DPSEND_GUARANTEED)); SendData.dwFlags = pSend->dwFlags; SendData.idPlayerTo = pSend->idTo; SendData.idPlayerFrom = pSend->idFrom; SendData.lpMessage = pBuffer->pData; SendData.dwMessageSize = FrameTotalLen; SendData.bSystemMessage = 0; SendData.lpISP = pProtocol->m_lpISP;
ENTER_DPLAY(); Lock(&pProtocol->m_SPLock);
hr=CALLSP(pProtocol->m_lpDPlay->pcbSPCallbacks->Send,&SendData);
Unlock(&pProtocol->m_SPLock);
LEAVE_DPLAY(); if(hr!=DPERR_PENDING){ if(!DecSendRef(pProtocol,pSend)){ // No async send support in Dplay at lower edge,
// so we should never get here!
ASSERT(0); } FreeFrameBuffer(pBuffer); } Lock(&pSend->SendLock); nFramesToSend--; AdvanceSend(pSend,FrameDataLen); pSend->NR++; pSend->NS++; } Unlock(&pSend->SendLock);
DGCompleteSend(pSend);
hr=DPERR_PENDING; // everything was sent, but already completed by DGCompleteSend
exit: return hr;
throttle_exit: hr=DPERR_PENDING;
pSend->SendState=Throttled; Unlock(&pSend->SendLock);
Throttle(pSend->pSession, tm);
return hr; #undef pBigFrame
}
BOOL DGCompleteSend(PSEND pSend) { UINT bit; UINT MsgMask; PSESSION pSession; pSend->SendState=Done; pSession=pSend->pSession;
Lock(&pSession->SessionLock);
if(!pSend->fSendSmall){ MsgMask = 0xFFFF; } else { MsgMask =0xFF; }
DPF(9,"CompleteSend\n");
//
// Update Session information for completion of this send.
//
bit = ((pSend->messageid-pSession->DGFirstMsg) & MsgMask)-1;
// clear the message mask bit for the completed send.
if(pSession->DGOutMsgMask & 1<<bit){ pSession->DGOutMsgMask &= ~(1<<bit); } else { return FALSE; } // slide the first message count forward for each low
// bit clear in Message mask.
while(pSession->DGLastMsg-pSession->DGFirstMsg){ if(!(pSession->DGOutMsgMask & 1)){ pSession->DGFirstMsg=(pSession->DGFirstMsg+1)&MsgMask; pSession->DGOutMsgMask >>= 1; if(pSession->nWaitingForDGMessageid){ pSession->pProtocol->m_bRescanQueue=TRUE; SetEvent(pSession->pProtocol->m_hSendEvent); } } else { break; } } //
// Return the Send to the pool and complete the waiting client.
//
Unlock(&pSession->SessionLock); ASSERT(pSend->RefCount); // Send completed, do completion
DoSendCompletion(pSend, DP_OK);
DecSendRef(pSession->pProtocol, pSend); // for completion.
return TRUE; }
// Send a fully formatted System packet (ACK, nACK, etc..)
HRESULT SystemSend(PPROTOCOL pProtocol, PSEND pSend) { PBUFFER pBuffer; DPSP_SENDDATA SendData; HRESULT hr; PSESSION pSession;
pBuffer=pSend->pMessage;
DPF(9,"System Send pBuffer %x pData %x len %d, idTo %x \n",pBuffer, pBuffer->pData, pBuffer->len, pSend->idTo);
pSession=GetSysSessionByIndex(pProtocol, pSend->wIdTo); // adds a ref on session.
// |
if(!pSession){ // |
goto exit; // |
} // |
// |
SendData.idPlayerTo = pSession->dpid; // |
DecSessionRef(pSession); // <----+ frees ref here.
// Send this puppy...
SendData.dwFlags = 0; SendData.idPlayerFrom = pSend->idFrom; SendData.lpMessage = pBuffer->pData; SendData.dwMessageSize = pBuffer->len; SendData.bSystemMessage = 0; SendData.lpISP = pProtocol->m_lpISP;
ENTER_DPLAY(); Lock(&pProtocol->m_SPLock);
hr=CALLSP(pProtocol->m_lpDPlay->pcbSPCallbacks->Send,&SendData);
Unlock(&pProtocol->m_SPLock);
LEAVE_DPLAY();
#ifdef DEBUG
if(hr!=DP_OK){ DPF(0,"UNSUCCESSFUL SEND in SYSTEM SEND, hr=%x\n",hr); } #endif
exit: return hr; #undef pBigFrame
}
VOID DoSendCompletion(PSEND pSend, INT Status) { #ifdef DEBUG
if(Status != DP_OK){ DPF(8,"Send Error pSend %x, Status %x\n",pSend,Status); } #endif
if(!(pSend->dwFlags & ASEND_PROTOCOL)){ EnterCriticalSection(&pSend->pProtocol->m_SendQLock); pSend->pProtocol->m_dwBytesPending -= pSend->MessageSize; pSend->pProtocol->m_dwMessagesPending -= 1; LeaveCriticalSection(&pSend->pProtocol->m_SendQLock); }
if(pSend->pAsyncInfo){ // ASYNC_SEND
if(pSend->AsyncInfo.pStatus){ (*pSend->AsyncInfo.pStatus)=Status; } if(pSend->AsyncInfo.SendCallBack){ (*pSend->AsyncInfo.SendCallBack)(pSend->AsyncInfo.CallBackContext,Status); } if(pSend->AsyncInfo.hEvent){ DPF(9,"ASYNC_SENDCOMPLETE: Signalling Event %x\n",pSend->AsyncInfo.hEvent); SetEvent(pSend->AsyncInfo.hEvent); } } else if (!(pSend->dwFlags&(ASEND_PROTOCOL|DPSEND_ASYNC))){ // SYNC_SEND
if(pSend->AsyncInfo.pStatus){ (*pSend->AsyncInfo.pStatus)=Status; } if(pSend->AsyncInfo.hEvent){ DPF(9,"SYNC_SENDCOMPLETE: Signalling Event %x\n",pSend->AsyncInfo.hEvent); SetEvent(pSend->AsyncInfo.hEvent); } } else { // PROTOCOL INTERNAL ASYNC SEND
if(pSend->AsyncInfo.pStatus){ (*pSend->AsyncInfo.pStatus)=Status; } if(pSend->AsyncInfo.SendCallBack){ (*pSend->AsyncInfo.SendCallBack)(pSend->AsyncInfo.CallBackContext,Status); } } }
/*=============================================================================
SendHandler - Send the next message that needs to send packets. Description:
Finds a message on the send queue that needs to send packets and deserves to use some bandwidth, either because it is highest priority or because all the higher priority messages are waiting for ACKs. Then sends as many packets as possible before hitting the throttling limit.
Returns when the throttle limit is hit, or all packets for this send have been sent.
Parameters:
pARPD pObj - pointer to the ARPD object to send packets on.
Return Values:
-----------------------------------------------------------------------------*/ HRESULT SendHandler(PPROTOCOL pProtocol) {
PSEND pSend; HRESULT hr=DP_OK; PSESSION pSession;
// adds ref to send and session if found
pSend=GetNextMessageToSend(pProtocol);
if(!pSend){ goto nothing_to_send; }
//DPF(4,"==>Send\n");
switch(pSend->pSession->eState){
case Open: switch(pSend->SendState){ case Done: // Send handlers must deal with Done.
DPF(9,"Calling SendHandler for Done Send--should just return\n"); case Sending: //
// Send as many frames as we can given the window size.
//
// Send handlers dump packets on the wire, if they expect
// to be completed later, they return PENDING in which case
// their completion handlers must do the cleanup. If they
// return OK, it means everything for this send is done and
// we do the cleanup.
if(pSend->dwFlags & ASEND_PROTOCOL){ hr=SystemSend(pProtocol, pSend); } else if(pSend->dwFlags & DPSEND_GUARANTEE){ hr=ReliableSend(pProtocol, pSend); } else { hr=DGSend(pProtocol, pSend); } break; case TimedOut: hr=DPERR_CONNECTIONLOST; pSend->SendState=Done; break;
case Cancelled: hr=DPERR_USERCANCEL; pSend->SendState=Done; break;
case UserTimeOut: hr=DPERR_TIMEOUT; pSend->SendState=Done; break;
default: DPF(0,"SendHandler: Invalid pSend %x SendState: %d\n",pSend,pSend->SendState); ASSERT(0); } break;
case Closing: switch(pSend->SendState){ case TimedOut: DPF(8,"Returning CONNECTIONLOST on timed out message %x\n",DPERR_CONNECTIONLOST); hr=DPERR_CONNECTIONLOST; break; default: DPF(8,"Send for session in Closing State, returning %x\n",DPERR_INVALIDPLAYER); hr=DPERR_INVALIDPLAYER; break; } pSend->SendState=Done; break; case Closed: DPF(8,"Send for session in Closed State, returning %x",DPERR_INVALIDPLAYER); hr=DPERR_INVALIDPLAYER; pSend->SendState=Done; break; }
//DPF(4,"<==Send Leaving,rc=%x\n",hr);
if( hr != DPERR_PENDING ){ Lock(&pSend->SendLock); ASSERT(pSend->RefCount); //
// Send completed, do completion
//
DoSendCompletion(pSend, hr);
Unlock(&pSend->SendLock); DecSendRef(pProtocol, pSend); // for completion
}
pSession=pSend->pSession; DecSendRef(pProtocol,pSend); // Balances GetNextMessageToSend
DecSessionRef(pSession); // Balances GetNextMessageToSend
return hr;
nothing_to_send: return DPERR_NOMESSAGES; }
/*=============================================================================
Build Header - fill in the frame header for a packet to be sent. Description:
Enough space is left in the frame to go on the wire (pFrame) to fit the message header. One of two types of headers is built, depending on the value of the fSendSmall field of the packet. If fSendSmall is TRUE, a compact header is built, this lowers overhead on slow media. If fSendSmall is FALSE a larger header that can support larger windows is built. The header is filled into the front of pFrame.
Parameters:
pARPD pObj - pointer to the ARPD object to send packets on.
Return Values:
-----------------------------------------------------------------------------*/
VOID BuildHeader(PSEND pSend,pPacket1 pFrame, UINT shift, DWORD tm) { #define pBigFrame ((pPacket2)(pFrame))
PSENDSTAT pStat=NULL; UINT seq;
UINT bitEOM,bitSTA,bitSAK=0; DWORD BytesSent; DWORD RemoteBytesReceived; DWORD tRemoteBytesReceived; DWORD bResetBias=FALSE;
// on first frame of a message, set the start bit (STA).
if(pSend->NR+shift==0){ bitSTA=STA; } else { bitSTA=0; }
// on the last frome of a message set the end of message bit (EOM)
if(pSend->nFrames==pSend->NR+shift+1){ bitEOM=EOM; } else { bitEOM=0; }
// if we haven't set EOM and we haven't requested an ACK in 1/4 the
// round trip latency, set the SAK bit, to ensure we have at least
// 2 ACK's in flight for feedback to the send throttle control system.
// Don't create extra ACKs if round trip is less than 100 ms.
if(!bitEOM || !(pSend->dwFlags & DPSEND_GUARANTEED)){ DWORD tmDeltaSAK = tm-pSend->pSession->tLastSAK; if(((int)tmDeltaSAK > 50 ) && (tmDeltaSAK > (unFp(pSend->pSession->FpLocalAverageLatency)>>2)) ) { bitSAK=SAK; } }
// If we re-transmitted we need to send a SAK
// despite the SAK countdown.
if((!bitSAK) && (pSend->dwFlags & DPSEND_GUARANTEED) && ((pSend->NACKMask & (pSend->NACKMask-1)) == 0) && (bitEOM==0) ) { bitSAK=SAK; }
if(!(--pSend->SAKCountDown)){ bitSAK=SAK; }
if(bitSAK|bitEOM){ pSend->pSession->tLastSAK = tm; pSend->SAKCountDown=pSend->SAKInterval; pStat=GetSendStat(); } if(pSend->fSendSmall){
pFrame->flags=CMD|bitEOM|bitSTA|bitSAK; seq=(pSend->NR+shift+1) & pSend->SendSEQMSK; pFrame->messageid = (byte)pSend->messageid; pFrame->sequence = (byte)seq; pFrame->serial = (byte)(pSend->serial++);
if(pStat){ pStat->serial=pFrame->serial; } } else { pBigFrame->flags=CMD|BIG|bitEOM|bitSTA|bitSAK; seq=((pSend->NR+shift+1) & pSend->SendSEQMSK); pBigFrame->messageid = (word)pSend->messageid; pBigFrame->sequence = (word)seq; pBigFrame->serial = (byte)pSend->serial++;
if(pStat){ pStat->serial=pBigFrame->serial; } }
if(pSend->dwFlags & DPSEND_GUARANTEE){ pFrame->flags |= RLY; }
// count the number of bytes we have sent.
Lock(&pSend->pSession->SessionStatLock); pSend->pSession->BytesSent+=pSend->BytesThisSend; BytesSent=pSend->pSession->BytesSent; RemoteBytesReceived=pSend->pSession->RemoteBytesReceived; tRemoteBytesReceived=pSend->pSession->tRemoteBytesReceived; if(pStat && pSend->pSession->bResetBias && ((--pSend->pSession->bResetBias) == 0)) { bResetBias=TRUE; } Unlock(&pSend->pSession->SessionStatLock);
if(pStat){ pStat->sequence=seq; pStat->messageid=pSend->messageid; pStat->tSent=tm; pStat->LocalBytesSent=BytesSent; pStat->RemoteBytesReceived=RemoteBytesReceived; pStat->tRemoteBytesReceived=tRemoteBytesReceived; pStat->bResetBias=bResetBias; if(pSend->dwFlags & DPSEND_GUARANTEED){ InsertBefore(&pStat->StatList,&pSend->StatList); } else { Lock(&pSend->pSession->SessionStatLock); InsertBefore(&pStat->StatList,&pSend->pSession->DGStatList); Unlock(&pSend->pSession->SessionStatLock); } }
#undef pBigFrame
}
#if 0
// release sends waiting for an id.
VOID UnWaitSends(PSESSION pSession, DWORD fReliable) { BILINK *pBilink; PSEND pSendWalker;
pBilink=pSession->SendQ.next;
while(pBilink != &pSession->SendQ){ pSendWalker=CONTAINING_RECORD(pBilink,SEND,SendQ); pBilink=pBilink->next; if(pSendWalker->SendState==WaitingForId){ if(fReliable){ if(pSendWalker->dwFlags & DPSEND_GUARANTEED){ pSendWalker->SendState=Start; } } else { if(!(pSendWalker->dwFlags & DPSEND_GUARANTEED)){ pSendWalker->SendState=Start; } } } } if(fReliable){ pSession->nWaitingForMessageid=0; } else { pSession->nWaitingForDGMessageid=0; } } #endif
// Check if a datagram send can be started, if it can update teh
// Session and the Send.
BOOL StartDatagramSend(PSESSION pSession, PSEND pSend, UINT MsgIdMask) { BOOL bFoundSend; UINT bit; // BOOL bTransition=FALSE;
if((pSession->DGLastMsg-pSession->DGFirstMsg < pSession->MaxCDGSends)){ bFoundSend=TRUE;
if(pSend->SendState==WaitingForId){ InterlockedDecrement(&pSession->nWaitingForDGMessageid); } bit=(pSession->DGLastMsg-pSession->DGFirstMsg)&MsgIdMask; ASSERT(bit<30); pSession->DGOutMsgMask |= 1<<bit; pSession->DGLastMsg =(pSession->DGLastMsg+1)&MsgIdMask; pSend->messageid =pSession->DGLastMsg; pSend->FrameSize =pSession->MaxPacketSize-MAX_SEND_HEADER;
// Calculate number of frames required for this send.
pSend->nFrames =(pSend->MessageSize/pSend->FrameSize); if(pSend->FrameSize*pSend->nFrames < pSend->MessageSize || !pSend->nFrames){ pSend->nFrames++; } pSend->NR=0; pSend->FrameDataLen=0;//BUGBUG: hack
pSend->fSendSmall=pSession->fSendSmallDG; if(pSend->fSendSmall){ pSend->SendSEQMSK = 0xFF; } else { pSend->SendSEQMSK = 0xFFFF; } } else { #if 0
if(pSession->fSendSmallDG && pSession->DGFirstMsg < 0xFF-MAX_SMALL_CSENDS) { // Ran out of IDs, Transition to Large headers.
DPF(9,"OUT OF IDS, DATAGRAMS GOING TO LARGE FRAMES\n"); pSession->MaxCDGSends = MAX_LARGE_DG_CSENDS; pSession->DGWindowSize = MAX_LARGE_WINDOW; pSession->fSendSmallDG = FALSE; bTransition=TRUE; } #endif
bFoundSend=FALSE; if(pSend->SendState==Start){ InterlockedIncrement(&pSession->nWaitingForDGMessageid); DPF(9,"StartDatagramSend: No Id's Avail: nWaitingForDGMessageid %x\n",pSession->nWaitingForDGMessageid); pSend->SendState=WaitingForId; #if 0
if(bTransition){ UnWaitSends(pSession,FALSE); SetEvent(pSession->pProtocol->m_hSendEvent); } #endif
} else { DPF(9,"Couldn't start datagram send on pSend %x State %d pSession %x\n",pSend,pSend->SendState,pSession); if(pSend->SendState!=WaitingForId){ ASSERT(0); } }
}
return bFoundSend; }
BOOL StartReliableSend(PSESSION pSession, PSEND pSend, UINT MsgIdMask) { BOOL bFoundSend; UINT bit; // BOOL bTransition=FALSE;
ASSERT(pSend->dwFlags & DPSEND_GUARANTEED);
if((pSession->LastMsg-pSession->FirstMsg & MsgIdMask) < pSession->MaxCSends){
DPF(9,"StartReliableSend: FirstMsg: x%x LastMsg: x%x\n",pSession->FirstMsg, pSession->LastMsg); bFoundSend=TRUE;
if(pSend->SendState==WaitingForId){ InterlockedDecrement(&pSession->nWaitingForMessageid); } bit=(pSession->LastMsg-pSession->FirstMsg)&MsgIdMask; #ifdef DEBUG
if(!(bit<pSession->MaxCSends)){ DEBUG_BREAK(); } #endif
pSession->OutMsgMask |= 1<<bit; pSession->LastMsg =(pSession->LastMsg+1)&MsgIdMask;
DPF(9,"StartReliableSend: pSend %x assigning id x%x\n",pSend,pSession->LastMsg); pSend->messageid =pSession->LastMsg; pSend->FrameSize =pSession->MaxPacketSize-MAX_SEND_HEADER;
// Calculate number of frames required for this send.
pSend->nFrames =(pSend->MessageSize/pSend->FrameSize); if(pSend->FrameSize*pSend->nFrames < pSend->MessageSize || !pSend->nFrames){ pSend->nFrames++; } pSend->NR=0; pSend->FrameDataLen=0;//BUGBUG: hack
pSend->fSendSmall=pSession->fSendSmall; if(pSend->fSendSmall){ pSend->SendSEQMSK = 0xFF; } else { pSend->SendSEQMSK = 0xFFFF; }
} else { #if 0
if (pSession->fSendSmall && pSession->FirstMsg < 0xFF-MAX_SMALL_CSENDS){ // Ran out of IDs, Transition to Large headers - but only if we aren't going
// to confuse the wrapping code.
DPF(8,"OUT OF IDS, RELIABLE SENDS GOING TO LARGE FRAMES\n"); pSession->MaxCSends = MAX_LARGE_CSENDS; pSession->WindowSize = MAX_LARGE_WINDOW; pSession->fSendSmall = FALSE; bTransition = TRUE; } #endif
bFoundSend=FALSE; if(pSend->SendState==Start){ bFoundSend=FALSE; // Reliable, waiting for id.
InterlockedIncrement(&pSession->nWaitingForMessageid); pSend->SendState=WaitingForId; DPF(9,"StartReliableSend: No Id's Avail: nWaitingForMessageid %x\n",pSession->nWaitingForMessageid); #if 0
if(bTransition){ UnWaitSends(pSession,TRUE); SetEvent(pSession->pProtocol->m_hSendEvent); } #endif
} else { bFoundSend=FALSE; DPF(9,"Couldn't start reliable send on pSend %x State %d pSession %x\n",pSend,pSend->SendState,pSession); if(pSend->SendState!=WaitingForId){ ASSERT(0); } } } return bFoundSend; }
BOOL CheckUserTimeOut(PSEND pSend) { if(pSend->dwTimeOut){ if((timeGetTime()-pSend->dwSendTime) > pSend->dwTimeOut){ pSend->SendState=UserTimeOut; return TRUE; } } return FALSE; } /*=============================================================================
GetNextMessageToSend Description:
Scans the send queue for a message that is the current priority and is in the ready to send state or throttled state (we shouldn't even get here unless the throttle was removed.) If we find such a message we return a pointer to the caller.
Adds a reference to the Send and the Session.
Parameters:
PPROTOCOOL pProtocol - pointer to the PROTOCOL object to send packets on.
Return Values: NULL - no message should be sent. PSEND - message to send.
-----------------------------------------------------------------------------*/
PSEND GetNextMessageToSend(PPROTOCOL pProtocol) { PSEND pSend; BILINK *pBilink; UINT CurrentSendPri; BOOL bFoundSend; PSESSION pSession;
UINT MsgIdMask;
Lock(&pProtocol->m_SendQLock);
DPF(9,"==>GetNextMessageToSend\n");
Top:
bFoundSend = FALSE; pProtocol->m_bRescanQueue=FALSE; if(EMPTY_BILINK(&pProtocol->m_GSendQ)){ Unlock(&pProtocol->m_SendQLock); DPF(9,"GetNextMessageToSend: called with nothing in queue, heading for the door.\n"); goto exit; }
pBilink = pProtocol->m_GSendQ.next; pSend = CONTAINING_RECORD(pBilink, SEND, m_GSendQ); CurrentSendPri = pSend->Priority;
while(pBilink != &pProtocol->m_GSendQ){
pSession=pSend->pSession; ASSERT_SIGN(pSession, SESSION_SIGN); Lock(&pSession->SessionLock);
if(pProtocol->m_bRescanQueue){ DPF(9,"RESCAN of QUEUE FORCED IN GETNEXTMESSAGETOSEND\n"); Unlock(&pSession->SessionLock); goto Top; }
if(pSession->dwFlags & SESSION_UNTHROTTLED){ // unthrottle happened, so rewind.
DPF(9,"Unthrottling Session %x\n",pSession); pSession->dwFlags &= ~(SESSION_THROTTLED|SESSION_UNTHROTTLED); }
Lock(&pSend->SendLock); switch(pSession->eState){
case Open:
if((pSend->dwFlags & DPSEND_GUARANTEE)?(pSession->fSendSmall):(pSession->fSendSmallDG)){ MsgIdMask = 0xFF; } else { MsgIdMask = 0xFFFF; }
if(!(pSend->dwFlags & ASEND_PROTOCOL) && (pSession->dwFlags & SESSION_THROTTLED)){ // don't do sends on a throttled session, unless they are internal sends.
break; }
switch(pSend->SendState){
case Start: case WaitingForId:
DPF(9,"Found Send in State %d, try Going to Sending State\n",pSend->SendState); // Just starting, need an id.
if(!(pSend->dwFlags & ASEND_PROTOCOL) && CheckUserTimeOut(pSend)){ if(pSend->SendState==WaitingForId){ if(pSend->dwFlags&DPSEND_GUARANTEED){ InterlockedDecrement(&pSession->nWaitingForMessageid); } else { InterlockedDecrement(&pSession->nWaitingForDGMessageid); } } bFoundSend=TRUE; break; } if(pSend->dwFlags&ASEND_PROTOCOL){ DPF(9,"System Send in Start State, Going to Sending State\n"); bFoundSend=TRUE; pSend->SendState=Sending; break; } else if(!(pSend->dwFlags&DPSEND_GUARANTEED)) {
//check_datagram:
bFoundSend=StartDatagramSend(pSession,pSend, MsgIdMask);
} else {
// NOT DataGram, .: reliable...
//check_reliable:
bFoundSend=StartReliableSend(pSession,pSend, MsgIdMask); #ifdef DEBUG
if(bFoundSend){ BILINK *pBiSendWalker=pSend->SendQ.prev; PSEND pSendWalker; while(pBiSendWalker != &pSession->SendQ){ pSendWalker=CONTAINING_RECORD(pBiSendWalker,SEND,SendQ); pBiSendWalker=pBiSendWalker->prev; if((pSendWalker->SendState==Start || pSendWalker->SendState==WaitingForId)&& pSendWalker->dwFlags&DPSEND_GUARANTEED && !(pSendWalker->dwFlags&ASEND_PROTOCOL) && pSendWalker->Priority >= pSend->Priority){ DPF(0,"Send %x got id %x but Send %x still in state %x on Session %x\n",pSend,pSend->messageid,pSendWalker,pSendWalker->SendState,pSession); DEBUG_BREAK(); } } } #endif
} if(bFoundSend){ if(pSession->dwFlags & SESSION_THROTTLED) { pSend->SendState=Throttled; bFoundSend=FALSE; } else { pSend->SendState=Sending; } } break;
case ReadyToSend: DPF(9,"Found Send in ReadyToSend State, going to Sending State\n"); bFoundSend=TRUE; if(pSession->dwFlags & SESSION_THROTTLED) { pSend->SendState=Throttled; bFoundSend=FALSE; } else { pSend->SendState=Sending; } break;
case Throttled: ASSERT(!(pSession->dwFlags & SESSION_THROTTLED)); DPF(9,"Found Send in Throttled State, unthrottling going to Sending State\n"); bFoundSend=TRUE; pSend->SendState=Sending; if(pSession->dwFlags & SESSION_THROTTLED) { pSend->SendState=Throttled; bFoundSend=FALSE; } else { pSend->SendState=Sending; } break;
case TimedOut: DPF(9,"Found TimedOut Send.\n"); TimeOutSession(pSession); bFoundSend=TRUE; break;
case Cancelled: bFoundSend=TRUE; break;
default: ASSERT(pSend->SendState <= Done); break; } /* end switch(SendState) */ break;
default: switch(pSend->SendState){ case Sending: case WaitingForAck: case Done: DPF(9,"GetNextMessageToSend: Session %x was in state %d ,pSend %x SendState %d, leaving...\n",pSession, pSession->eState, pSend, pSend->SendState); //bFoundSend=FALSE;
break; default: DPF(9,"GetNextMessageToSend: Session %x was in state %d ,returning pSend %x SendState %d\n",pSession, pSession->eState, pSend, pSend->SendState); bFoundSend=TRUE; break; } break; } /* end switch pSession->eState */ if(bFoundSend){ if(AddSendRef(pSend,1)){ pSession->RefCount++; } else { bFoundSend=FALSE; } }
Unlock(&pSend->SendLock); Unlock(&pSession->SessionLock);
if(bFoundSend){ if(pSend->NS==0){ pSend->tLastACK=timeGetTime(); } break; }
pBilink=pBilink->next; pSend=CONTAINING_RECORD(pBilink, SEND, m_GSendQ); } /* end while (pBilink != &pProtocol->m_GSendQ) */
Unlock(&pProtocol->m_SendQLock); exit: if(bFoundSend){ DPF(9,"<==GetNextMessageToSend %x\n",pSend); return pSend; } else { DPF(9,"<==GetNextMessageToSend NULL\n"); return NULL; } }
|