windows-nt/Source/XPSP1/NT/multimedia/media/dplayx/dplay/protocol/stats.c
2020-09-26 16:20:57 +08:00

661 lines
21 KiB
C

/*++
Copyright (c) 1996,1997 Microsoft Corporation
Module Name:
STATS.C
Abstract:
Session Statistics routines
Author:
Aaron Ogus (aarono)
Environment:
Win32/COM
Revision History:
Date Author Description
====== ====== ============================================================
7/30/97 aarono Original
6/6/98 aarono Turn on throttling and windowing
--*/
#include <windows.h>
#include "newdpf.h"
#include <dplay.h>
#include <dplaysp.h>
#include <dplaypr.h>
#include "mydebug.h"
#include "arpd.h"
#include "arpdint.h"
#include "protocol.h"
#include "macros.h"
#include "command.h"
#define STARTING_LONG_LATENCY 1 /* 1 ms (intentionally low so first sample fills) */
#define STARTING_SHORT_LATENCY 15000 /* 15 seconds (intentionally high so first sample fills) */
#define STARTING_AVERAGE_LATENCY 2000 /* 2 seconds (good start for internet) */
#define STARTING_AVERAGE_DEVIATION 0
#define STARTING_MAXRETRY 16 /* Maximum number of retries */
#define STARTING_MINDROPTIME 15000 /* Minimum time to retry before dropping connection (ms) */
#define STARTING_MAXDROPTIME 60000 /* Maximum time to retry before dropping connection (ms) */
#define STARTING_BANDWIDTH (28800/10) /* 28 kbps modem */
#define LATENCY_SHORT_BITS 4
#define LATENCY_LONG_BITS 7
#define STAT_LOCAL_LATENCY_SAMPLES 2^(LATENCY_SHORT_BITS) /* 2^4 */
#define STAT_LONG_LATENCY_SAMPLES 2^(LATENCY_LONG_BITS) /* 2^7 */
#define TARGET_CLOCK_OFFSET 10000000
#define Fp(_x) ((_x)<<8)
#define unFp(_x)((_x)>>8)
// Latency Averages and deviation averages are stored as fixed point 24.8
VOID InitSessionStats(PSESSION pSession)
{
pSession->ShortestLatency = STARTING_SHORT_LATENCY;
pSession->LongestLatency = STARTING_LONG_LATENCY;
pSession->FpAverageLatency = 1000;
pSession->FpLocalAverageLatency = 1000;
pSession->FpLocalAvgDeviation = 300;
pSession->FpAvgDeviation = 300;
pSession->Bandwidth = 28800/10;
pSession->HighestBandwidth=28800/10;
pSession->MaxRetry = STARTING_MAXRETRY;
pSession->MinDropTime = STARTING_MINDROPTIME;
pSession->MaxDropTime = STARTING_MAXDROPTIME;
}
// called with SESSIONLOCK.
VOID UpdateSessionStats(PSESSION pSession, PSENDSTAT pStat, PCMDINFO pCmdInfo, BOOL fBadDrop)
{
DWORD tLatency;
DWORD nBytesReceived;
DWORD tDeviation;
DWORD BytesLost=0;
DWORD BackLog=0;
DWORD fThrottleAdjusted=FALSE;
DWORD tRemoteDelta; // change in time on remote from last received ACK until this was ACKed.
DWORD tBiasedDelta; // a biased difference in local and remote clocks.
INT tDelta; // the unbiased difference (signed)
static DWORD cBiasReset;
// Get the statistics information we need.
tLatency = pCmdInfo->tReceived-pStat->tSent;
ASSERT((int)tLatency >= 0);
if(!tLatency){
DPF(8,"0ms observed latency, using 1ms\n");
tLatency=1;
}
Lock(&pSession->SessionStatLock);
// Calculates the number of bytes received at remote since this send was done.
pSession->RemoteBytesReceived = pCmdInfo->bytes;
pSession->tRemoteBytesReceived = pCmdInfo->tRemoteACK;
nBytesReceived = pSession->RemoteBytesReceived - pStat->RemoteBytesReceived;
BytesLost = pStat->LocalBytesSent-(pSession->RemoteBytesReceived+pSession->BytesLost);
if((int)BytesLost >= 0){
pSession->BytesLost += BytesLost;
// Note, Backlog may be as little as 1/2 this value.
BackLog = pSession->BytesSent -( pSession->RemoteBytesReceived + pSession->BytesLost );
if((int)BackLog < 0){
DPF(8,"Hmmm, upside down backlog?\n");
DPF(8,"pSession->BytesSent %d\n",pSession->BytesSent);
DPF(8,"pSession->RemoteBytesReceived %d\n",pSession->RemoteBytesReceived);
DPF(8,"pSession->BytesLost %d\n",pSession->BytesLost);
DPF(8,"Calculated BackLog %d\n",BackLog);
BackLog=0;
}
} else if((int)BytesLost < 0){
// Can be caused by out of order receives
DPF(1,"Out of order remote receive lots of these may affect throttling...\n");
DPF(8,"Hmmm, upside down byte counting?\n");
DPF(8,"pStat->LocalBytesSent %d\n",pStat->LocalBytesSent);
DPF(8,"pSession->RemoteBytesReceived %d\n",pSession->RemoteBytesReceived);
DPF(8,"pSession->BytesLost %d\n",pSession->BytesLost);
DPF(8,"Calculated Bytes Lost %d\n",BytesLost);
BytesLost=0;
// fixup lost count.
pSession->BytesLost=pSession->RemoteBytesReceived-pStat->LocalBytesSent;
}
Unlock(&pSession->SessionStatLock);
if(pSession->MaxCSends==1){
DWORD Bias;
// 1st ACK, adjust windows to normal operation.
pSession->MaxCSends = MAX_SMALL_CSENDS;
pSession->MaxCDGSends = MAX_SMALL_DG_CSENDS;
pSession->WindowSize = MAX_SMALL_WINDOW;
pSession->DGWindowSize = MAX_SMALL_WINDOW;
pSession->FpAverageLatency = 2*tLatency; // start high to avoid overthrottle
pSession->FpLocalAverageLatency = 2*tLatency;
pSession->FpLocalAvgDeviation = 1+tLatency/3;
pSession->FpAvgDeviation = 1+tLatency/3;
Bias = pCmdInfo->tRemoteACK - pStat->tSent;
if(Bias > TARGET_CLOCK_OFFSET){
Bias = -1*(Bias-TARGET_CLOCK_OFFSET);
} else {
Bias = TARGET_CLOCK_OFFSET - Bias;
}
pSession->RemAvgACKBias = Bias;
pSession->RemAvgACKDelta = (pCmdInfo->tRemoteACK - pStat->tSent)+pSession->RemAvgACKBias;
ASSERT(pSession->RemAvgACKDelta == TARGET_CLOCK_OFFSET);
}
//
// Calculate shift in outbound latency.
//
tBiasedDelta = (pCmdInfo->tRemoteACK - pStat->tSent)+pSession->RemAvgACKBias;
tDelta = tBiasedDelta-TARGET_CLOCK_OFFSET;
if(tDelta < 0 || pStat->bResetBias || tDelta > (int)tLatency){
DWORD Bias;
// Either clock drift or lower server load shows latency down, so reset baseline.
Bias = pCmdInfo->tRemoteACK - pStat->tSent;
if(Bias > TARGET_CLOCK_OFFSET){
Bias = -1*(Bias-TARGET_CLOCK_OFFSET);
} else {
Bias = TARGET_CLOCK_OFFSET - Bias;
}
cBiasReset++;
pSession->RemAvgACKBias = Bias;
tBiasedDelta = (pCmdInfo->tRemoteACK - pStat->tSent)+pSession->RemAvgACKBias;
tDelta = tBiasedDelta-TARGET_CLOCK_OFFSET;
}
pSession->RemAvgACKDelta -= pSession->RemAvgACKDelta >> 7; // -1/128th
pSession->RemAvgACKDelta += tBiasedDelta >> 7; // +1/128th of new value
// keep the residue so we don't creep down due to rounding error.
pSession->RemAvgACKDeltaResidue += tBiasedDelta & 0x7f;
if(pSession->RemAvgACKDeltaResidue>>7){
pSession->RemAvgACKDelta += pSession->RemAvgACKDeltaResidue>>7;
pSession->RemAvgACKDeltaResidue &= 0x7f;
}
DPF(8,"tRemoteACK %d tSent %d Bias %d tBiasedDelta %d tDelta %d\n", pCmdInfo->tRemoteACK, pStat->tSent,
pSession->RemAvgACKBias, tBiasedDelta, tDelta);
//
// Update latency statistics
//
ASSERT(!(nBytesReceived & 0x80000000)); // received in interval +ve
ASSERT(!(tLatency & 0x80000000)); // latency is +ve
if(tLatency < pSession->ShortestLatency){
pSession->ShortestLatency=tLatency;
DPF(8,"Shortest Latency %d ms\n",tLatency);
}
if(tLatency > pSession->LongestLatency){
pSession->LongestLatency=tLatency;
DPF(8,"Longest Latency %d ms\n", tLatency);
}
pSession->LastLatency=tLatency;
// Throw out 1/16 of local latency and add in the new statistic.
// Note we only use local latency for retry calculations.
if(pSession->FpLocalAverageLatency){
if(Fp(tLatency) > pSession->FpAverageLatency){
pSession->FpLocalAverageLatency -= (pSession->FpLocalAverageLatency >> LATENCY_SHORT_BITS);
pSession->FpLocalAverageLatency += (tLatency << (8-LATENCY_SHORT_BITS));
} else {
// Ratched down when we get a latency that is below average, so we can better
// detect backlog due to latency.
pSession->FpLocalAverageLatency = Fp(tLatency);
}
} else {
// this only happens once at startup.
pSession->FpLocalAverageLatency = Fp(tLatency);
pSession->FpAverageLatency = Fp(tLatency);
}
if(Fp(tLatency) > pSession->FpAverageLatency){
// Thow out 1/128 of average latency and add in the new statistic.
pSession->FpAverageLatency -= (pSession->FpAverageLatency >> LATENCY_LONG_BITS);
pSession->FpAverageLatency += (tLatency << (8-LATENCY_LONG_BITS));
} else {
// Ratched down when we get a latency that is below average, so we can better
// detect backlog due to latency.
pSession->FpAverageLatency = Fp(tLatency);
}
tDeviation=unFp(pSession->FpLocalAverageLatency)-tLatency;
if((int)tDeviation < 0){
tDeviation = 0-tDeviation;
}
pSession->FpLocalAvgDeviation -= (pSession->FpLocalAvgDeviation >> LATENCY_SHORT_BITS);
pSession->FpLocalAvgDeviation += (tDeviation << (8-LATENCY_SHORT_BITS));
pSession->FpAvgDeviation -= (pSession->FpAvgDeviation >> LATENCY_LONG_BITS);
pSession->FpAvgDeviation += (tDeviation << (8-LATENCY_LONG_BITS));
DPF(8,"Got ACK, tLat: %d Avg: %d.%d Dev: %d AvgDev: %d.%d \n",
tLatency, pSession->FpLocalAverageLatency >> 8, ((pSession->FpLocalAverageLatency&0xFF)*100)/256,
tDeviation, pSession->FpLocalAvgDeviation >> 8, ((pSession->FpLocalAvgDeviation&0xFF)*100)/256);
//
// Do Bandwidth calculations
//
tRemoteDelta= pCmdInfo->tRemoteACK - pStat->tRemoteBytesReceived;
if(!tRemoteDelta){
tRemoteDelta=1;
}
if(pStat->tRemoteBytesReceived){
pSession->Bandwidth = (1000*nBytesReceived)/(tRemoteDelta);
// could adjust throttle here if Bandwidth is higher, but this
// might pimp high speed links. (BUGBUG:).
} else {
// backup calculation, not as good. Only used early in the link
// before we have received an ACK from the remote prior to issuing
// a send.
pSession->Bandwidth = (2000*nBytesReceived)/tLatency; // 2000, not 1000 since tLatency is round trip.
}
if(pSession->Bandwidth > pSession->HighestBandwidth){
pSession->HighestBandwidth = pSession->Bandwidth;
}
DPF(8,"tRemoteDelta %d Remote bytes Received %d\n",tRemoteDelta,nBytesReceived);
// Adjust sending...
if ( BackLog && pSession->Bandwidth)
{
DWORD tAvgLat;
DWORD tBackLog;
DWORD ExcessBackLog; // amount of backlog (bytes) we need to clear before hitting avg latency again.
DWORD tLatCheck;
DWORD AvgLat133; // 133% of local average latency (tolerance for slow links)
DWORD AvgLat200; // 200% of local average latency (tolerance for fast links)
if(pSession->fFastLink){
tAvgLat=unFp(pSession->FpAverageLatency);
tLatCheck = (tAvgLat*3)/2;
AvgLat133 = max(100,3*unFp(pSession->FpAvgDeviation)+(unFp(pSession->FpAverageLatency)*4)/3); // don't throttle <100ms lat
AvgLat200 = max(100,3*unFp(pSession->FpAvgDeviation)+unFp(pSession->FpAverageLatency)*2);
} else {
tAvgLat=unFp(pSession->FpLocalAverageLatency);
tLatCheck = (tAvgLat*3)/2;
AvgLat133 = max(100,3*unFp(pSession->FpLocalAvgDeviation)+(unFp(pSession->FpLocalAverageLatency)*4)/3); // don't throttle <100ms lat
AvgLat200 = max(100,3*unFp(pSession->FpLocalAvgDeviation)+unFp(pSession->FpLocalAverageLatency)*2);
}
if(tLatCheck < AvgLat133){
tLatCheck = AvgLat133;
}
if(tLatency > tLatCheck){
// check link speed
if(pSession->fFastLink){
if(pSession->Bandwidth <= 10000){
pSession->fFastLink=FALSE;
}
} else {
if(pSession->Bandwidth >= 25000){
pSession->fFastLink=TRUE;
}
}
}
if(pSession->fFastLink && tLatCheck < AvgLat200){
tLatCheck=AvgLat200;
}
DPF(8,"tLat %d, tLatCheck %d, tDelta %d, tLat/3 %d\n",tLatency,tLatCheck,tDelta,tLatency/3);
DPF(8,"pSession->ShortestLatency %d, Shortest+MaxPacketTime %d\n",pSession->ShortestLatency,
pSession->ShortestLatency+(pSession->MaxPacketSize*1000)/pSession->Bandwidth);
if((tLatency > tLatCheck && tDelta > (int)(tLatency/3)) ||
((!pSession->fFastLink)&&
(tLatency > pSession->ShortestLatency+((pSession->MaxPacketSize*2000)/pSession->Bandwidth))
)
)
{
#ifdef DEBUG
if(pSession->SendRateThrottle){
DPF(8,"BackLog %d, SendRate %d BackLog ms %d, tLatency %d tAvgLat %d Used Bandwidth %d tBacklog %d \n",
BackLog,
pSession->SendRateThrottle,
(BackLog*1000 / pSession->SendRateThrottle),
tLatency,
tAvgLat,
pSession->Bandwidth,
((BackLog*1000) / pSession->Bandwidth)
);
}
#endif
tBackLog = (BackLog * 1000)/pSession->Bandwidth;
if(tBackLog > 4*tLatency){
DPF(8,"1: tBackLog %d was >> tLatency %d, using 4*tLatency instead\n",tBackLog,tLatency);
tBackLog=4*tLatency; //never wait more than 4 latency periods
}
if(tBackLog > 8000){
DPF(8,"Disalowing backlog > 8 seconds, using 8 instead\n");
tBackLog=8000;
}
// if the backlog is greater than the bandwidth*latency, then we need to slow down our sending.
// don't slow down due to backlog until we are over 100ms on way latency (200 round trip)
if((tBackLog > 200) && (tBackLog > tAvgLat)){
BOOL fWait=TRUE;
// at max we cut send rate in 1/2.
if(pSession->SendRateThrottle/2 > pSession->Bandwidth){
DPF(8,"Asked for too aggresive throttle adjust %d, going from %d to %d\n",pSession->Bandwidth,pSession->SendRateThrottle,pSession->SendRateThrottle/2);
pSession->SendRateThrottle /= 2;
// Recheck if we are really backlogged at the new rate
tBackLog = (BackLog * 1000)/pSession->SendRateThrottle;
if(tBackLog > tLatency){
DPF(8,"2: tBackLog %d was > tLatency %d, using tLatency instead\n",tBackLog,tLatency);
tBackLog=tLatency;// never wait more than last latency period
}
} else {
// set new throttle rate and current observed bandwidth (+5% to avoid overthrottle)
pSession->SendRateThrottle=pSession->Bandwidth+pSession->Bandwidth/16;
}
// don't adjust for a while.
pSession->bhitThrottle=FALSE;
pSession->tLastThrottleAdjust = pCmdInfo->tReceived;
if(fWait && (tBackLog > tAvgLat)){
ExcessBackLog = ((tBackLog-tAvgLat)*pSession->Bandwidth)/1000;
DPF(8,"Throttling back due to BACKLOG, excess = %d\n",ExcessBackLog);
#ifdef DEBUG
if(tBackLog-tAvgLat > 30000){
DPF(5,"WARNING: BACKLOG THROTTLE %d ms seems kinda large\n",tBackLog-tAvgLat);
}
#endif
// wait until backlog is down to avg latency before sending again
Lock(&pSession->SessionStatLock);
pSession->bResetBias = 2; // could be in the middle of a send, so count down from 2.
Unlock(&pSession->SessionStatLock);
UpdateSendTime(pSession,ExcessBackLog,timeGetTime(),TRUE);
} else {
DPF(8,"Not throttling due to BACKLOG because of smaller adjustment\n");
}
} else {
DPF(8,"NOT Throttling back due to BACKLOG\n");
}
}
} else if(tDelta > (int)tLatency) {
// tDelta is bogus due to clock drift, force throttle so we can correct.
Lock(&pSession->SessionStatLock);
pSession->bResetBias=2;
Unlock(&pSession->SessionStatLock);
pSession->tNextSend=timeGetTime()+2*tLatency;
DPF(8,"tDelta %d > tLatency %d, need to correct for clock drift, time %d set next send time to %d\n", tDelta, tLatency,timeGetTime(),pSession->tNextSend);
}
//
// Adjust Throttle if not already adjusted.
//
if((pSession->ThrottleState==Begin) ||
(pCmdInfo->tReceived-pSession->tLastThrottleAdjust) > (1+1*pSession->fFastLink)*unFp(pSession->FpLocalAverageLatency) )
{
if(!fThrottleAdjusted){
DPF(8,"Current Send Rate %d\n", pSession->SendRateThrottle);
if(!BytesLost && pSession->bhitThrottle){
pSession->bhitThrottle=FALSE;
pSession->tLastThrottleAdjust = pCmdInfo->tReceived;
// Good Send, push up send rate if we hit throttle.
switch(pSession->ThrottleState){
case Begin:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100+START_GROWTH_RATE))/100;
pSession->GrowCount++;
pSession->ShrinkCount=0;
break;
case MetaStable:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100+METASTABLE_GROWTH_RATE))/100;
pSession->GrowCount++;
pSession->ShrinkCount=0;
break;
case Stable:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100+STABLE_GROWTH_RATE))/100;
pSession->GrowCount++;
pSession->ShrinkCount=0;
if(pSession->GrowCount > (UINT)(20+60*pSession->fFastLink)){
pSession->ThrottleState = MetaStable;
pSession->GrowCount=0;
}
break;
default:
DPF(0,"Session in wierd ThrottleState %d\n",pSession->ThrottleState);
break;
}
DPF(8,"Successful Send Adjusted Throttle, SendRate %d\n",pSession->SendRateThrottle);
} else if(BytesLost){
// Figure out how much we dropped
if(fBadDrop || (BytesLost > pSession->pProtocol->m_dwSPMaxFrame)){
// Very bad send, back off
pSession->tLastThrottleAdjust = pCmdInfo->tReceived;
switch(pSession->ThrottleState){
case Begin:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-START_ADJUST_LARGE_ERR))/100;
pSession->GrowCount=0;
pSession->ShrinkCount++;
break;
case MetaStable:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-METASTABLE_ADJUST_LARGE_ERR))/100;
pSession->GrowCount=0;
pSession->ShrinkCount++;
break;
case Stable:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-STABLE_ADJUST_LARGE_ERR))/100;
pSession->ShrinkCount++;
if(pSession->ShrinkCount > 1){
pSession->ShrinkCount=0;
pSession->GrowCount=0;
pSession->ThrottleState=MetaStable;
}
break;
default:
DPF(0,"Session in wierd ThrottleState %d\n",pSession->ThrottleState);
break;
}
DPF(8,"VERY BAD SEND Adjusted Throttle, SendRate %d\n",pSession->SendRateThrottle);
} else {
// Bad send, back off a bit
pSession->tLastThrottleAdjust = pCmdInfo->tReceived;
switch(pSession->ThrottleState){
case Begin:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-START_ADJUST_SMALL_ERR))/100;
pSession->GrowCount=0;
pSession->ShrinkCount=0;
pSession->ThrottleState = MetaStable;
break;
case MetaStable:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-METASTABLE_ADJUST_SMALL_ERR))/100;
pSession->ShrinkCount++;
pSession->GrowCount=0;
break;
case Stable:
pSession->SendRateThrottle = (pSession->SendRateThrottle*(100-STABLE_ADJUST_SMALL_ERR))/100;
pSession->ShrinkCount++;
pSession->GrowCount=0;
if(pSession->ShrinkCount > 2){
pSession->ShrinkCount=0;
pSession->ThrottleState = MetaStable;
}
break;
default:
DPF(0,"Session in wierd ThrottleState %d\n",pSession->ThrottleState);
break;
}
DPF(8,"BAD SEND Adjusted Throttle, SendRate %d\n",pSession->SendRateThrottle);
} /* if (BadDrop... ) */
} /* if (BytesLost ...) */
}/*if (ThrottleAdjusted) */
}
if(!BytesLost && pSession->Bandwidth && pSession->SendRateThrottle < pSession->Bandwidth){
DPF(8,"Avoid goofyness, throttle was %d, setting to observed bandwidth %d\n",pSession->SendRateThrottle,pSession->Bandwidth);
pSession->SendRateThrottle=pSession->Bandwidth;
}
if(pSession->SendRateThrottle < 100){
DPF(8,"WARNING: SendRateThrottle %d below 100, keeping at 100 to avoid starvation\n",pSession->SendRateThrottle);
pSession->SendRateThrottle=100;
}
#ifdef DEBUG
{
IN_WRITESTATS InWS;
memset((PVOID)&InWS,0xFF,sizeof(IN_WRITESTATS));
InWS.stat_ThrottleRate = pSession->SendRateThrottle;
InWS.stat_BytesSent = pSession->BytesSent;
InWS.stat_BackLog = BackLog;
InWS.stat_BytesLost = pSession->BytesLost;
//InWS.stat_RemBytesReceived;
InWS.stat_Latency = tLatency;
InWS.stat_MinLatency=pSession->ShortestLatency;
InWS.stat_AvgLatency=unFp(pSession->FpLocalAverageLatency);
InWS.stat_AvgDevLatency=unFp(pSession->FpLocalAvgDeviation);
//InWS.stat_USER1=
//InWS.stat_USER2=
//InWS.stat_USER3=
InWS.stat_USER5 = tDelta;
InWS.stat_USER6 = cBiasReset;
DbgWriteStats(&InWS);
}
#endif
DPF(8,"Bandwidth %d, Highest %d\n",pSession->Bandwidth, pSession->HighestBandwidth);
}
// Called with SessionLock and SendLock
// Statistics are stored on the send in send order on a BILINK.
// most recent sends are at the end of the list. We scan from
// the end of the list to the beginning until we find the SENDSTAT
// that records the sequence and serial we got ACKED. We then
// update our statistics and throw out all SENDSTATs
// before this entry.
VOID UpdateSessionSendStats(PSESSION pSession, PSEND pSend, PCMDINFO pCmdInfo, BOOL fBadDrop)
{
PSENDSTAT pStatWalker,pStat=NULL;
BILINK *pStatBilink;
pSend->tLastACK=pCmdInfo->tReceived;
pSend->RetryCount=0;
// Find the last STAT for this ACK.
pStatBilink=pSend->StatList.prev;
while(pStatBilink != &pSend->StatList){
pStatWalker=CONTAINING_RECORD(pStatBilink, SENDSTAT, StatList);
if(pStatWalker->serial==pCmdInfo->serial &&
pStatWalker->sequence==pCmdInfo->sequence)
{
ASSERT(pStatWalker->messageid==pSend->messageid);
ASSERT(pSend->messageid==pCmdInfo->messageid);
pStat=pStatWalker;
break;
}
pStatBilink=pStatBilink->prev;
}
if(pStat){
UpdateSessionStats(pSession,pStat,pCmdInfo,fBadDrop);
// Unlink All Previous SENDSTATS;
pStat->StatList.next->prev=&pSend->StatList;
pSend->StatList.next=pStat->StatList.next;
// Put the SENDSTATS back in the pool.
while(pStatBilink != &pSend->StatList){
pStatWalker=CONTAINING_RECORD(pStatBilink, SENDSTAT, StatList);
pStatBilink=pStatBilink->prev;
ReleaseSendStat(pStatWalker);
}
}
return;
}