Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

491 lines
12 KiB

/*
Copyright (c) 1992 Microsoft Corporation
Module Name:
scavengr.c
Abstract:
This file implements the scavenger queue management interface.
Author:
Jameel Hyder (microsoft!jameelh)
Revision History:
25 Jun 1992 Initial Version
Notes: Tab stop: 4
--*/
#define _SCAVENGER_LOCALS
#define FILENUM FILE_SCAVENGR
#include <afp.h>
#include <scavengr.h>
#include <client.h>
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, AfpScavengerInit)
#pragma alloc_text( PAGE, AfpScavengerDeInit)
#endif
/*** AfpScavengerInit
*
* Initialize the scavenger system. This consists of a queue protected by a
* spin lock and timer coupled to a DPC. The scavenger accepts requests to
* schedule a worker after N units of time.
*/
NTSTATUS
AfpScavengerInit(
VOID
)
{
BOOLEAN TimerStarted;
LARGE_INTEGER TimerValue;
KeInitializeTimer(&afpScavengerTimer);
INITIALIZE_SPIN_LOCK(&afpScavengerLock);
KeInitializeDpc(&afpScavengerDpc, afpScavengerDpcRoutine, NULL);
TimerValue.QuadPart = AFP_SCAVENGER_TIMER_TICK;
TimerStarted = KeSetTimer(&afpScavengerTimer,
TimerValue,
&afpScavengerDpc);
ASSERT(!TimerStarted);
return STATUS_SUCCESS;
}
/*** AfpScavengerDeInit
*
* De-Initialize the scavenger system. Just cancel the timer.
*/
VOID
AfpScavengerDeInit(
VOID
)
{
KeCancelTimer(&afpScavengerTimer);
}
/*** AfpScavengerEnqueue
*
* Here is a thesis on the code that follows.
*
* The scavenger events are maintained as a list which the scavenger thread
* looks at every timer tick. The list is maintained in such a way that only
* the head of the list needs to be updated every tick i.e. the entire list
* is never scanned. The way this is achieved is by keeping delta times
* relative to the previous entry.
*
* Every timer tick, the relative time at the head of the list is decremented.
* When that goes to ZERO, the head of the list is unlinked and dispatched.
*
* To give an example, we have the following events queued at time slots
* X Schedule A after 10 ticks.
* X+3 Schedule B after 5 ticks.
* X+5 Schedule C after 4 ticks.
* X+8 Schedule D after 6 ticks.
*
* So A will schedule at X+10, B at X+8 (X+3+5), C at X+9 (X+5+4) and
* D at X+14 (X+8+6).
*
* The above example covers all the situations.
*
* - NULL List.
* - Inserting at head of list.
* - Inserting in the middle of the list.
* - Appending to the list tail.
*
* The list will look as follows.
*
* BEFORE AFTER
* ------ -----
*
* X Head -->| Head -> A(10) ->|
* A(10)
*
* X+3 Head -> A(7) ->| Head -> B(5) -> A(2) ->|
* B(5)
*
* X+5 Head -> B(3) -> A(2) ->| Head -> B(3) -> C(1) -> A(1) ->|
* C(4)
*
* X+8 Head -> C(1) -> A(1) ->| Head -> C(1) -> A(1) -> D(4) ->|
* D(6)
*
* The granularity is one tick.
*
* LOCKS_ASSUMED: AfpScavengerLock (SPIN)
*/
VOID
afpScavengerEnqueue(
IN PSCAVENGERLIST pListNew
)
{
PSCAVENGERLIST pList, *ppList;
LONG DeltaTime = pListNew->scvgr_AbsTime;
// The DeltaTime is adjusted in every pass of the loop to reflect the
// time after the previous entry that the new entry will schedule.
for (ppList = &afpScavengerList;
(pList = *ppList) != NULL;
ppList = &pList->scvgr_Next)
{
if (DeltaTime <= pList->scvgr_RelDelta)
{
pList->scvgr_RelDelta -= DeltaTime;
break;
}
DeltaTime -= pList->scvgr_RelDelta;
}
pListNew->scvgr_RelDelta = DeltaTime;
pListNew->scvgr_Next = pList;
*ppList = pListNew;
}
/*** AfpScavengerScheduleEvent
*
* Insert an event in the scavenger event list. If the list is empty, then
* fire off a timer. The time is specified in ticks. Each tick is currently
* ONE SECOND. It may not be negative.
*
* The granularity is one tick.
*/
NTSTATUS
AfpScavengerScheduleEvent(
IN SCAVENGER_ROUTINE Worker, // Routine to invoke when time expires
IN PVOID pContext, // Context to pass to the routine
IN LONG DeltaTime, // Schedule after this much time
IN BOOLEAN fQueue // If TRUE, then worker must be queued
)
{
PSCAVENGERLIST pList = NULL;
KIRQL OldIrql;
NTSTATUS Status = STATUS_SUCCESS;
// Negative DeltaTime is invalid. ZERO is valid which implies immediate action
ASSERT (DeltaTime >= 0);
do
{
pList = (PSCAVENGERLIST)AfpAllocNonPagedMemory(sizeof(SCAVENGERLIST));
if (pList == NULL)
{
DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_ERR,
("AfpScavengerScheduleEvent: malloc Failed\n"));
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
AfpInitializeWorkItem(&pList->scvgr_WorkItem,
afpScavengerWorker,
pList);
pList->scvgr_Worker = Worker;
pList->scvgr_Context = pContext;
pList->scvgr_AbsTime = DeltaTime;
pList->scvgr_fQueue = fQueue;
if (DeltaTime == 0)
{
ASSERT (fQueue);
AfpQueueWorkItem(&pList->scvgr_WorkItem);
break;
}
if (!afpScavengerStopped)
{
ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
//
// due to an assumption made elsewhere, it's necessary to check
// this again after holding the spinlock!
//
if (!afpScavengerStopped)
{
afpScavengerEnqueue(pList);
RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
}
else
{
DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_ERR,
("AfpScavengerScheduleEvent: Called after Flush !!\n"));
RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
AfpFreeMemory(pList);
Status = STATUS_UNSUCCESSFUL;
}
}
} while (False);
return Status;
}
/*** AfpScavengerKillEvent
*
* Kill an event that was previously scheduled.
*/
BOOLEAN
AfpScavengerKillEvent(
IN SCAVENGER_ROUTINE Worker, // Routine that was scheduled
IN PVOID pContext // Context
)
{
PSCAVENGERLIST pList, *ppList;
KIRQL OldIrql;
ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
// The DeltaTime is adjusted in every pass of the loop to reflect the
// time after the previous entry that the new entry will schedule.
for (ppList = &afpScavengerList;
(pList = *ppList) != NULL;
ppList = &pList->scvgr_Next)
{
if ((pList->scvgr_Worker == Worker) &&
(pList->scvgr_Context == pContext))
{
*ppList = pList->scvgr_Next;
if (pList->scvgr_Next != NULL)
{
pList->scvgr_Next->scvgr_RelDelta += pList->scvgr_RelDelta;
}
break;
}
}
RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
if (pList != NULL)
AfpFreeMemory(pList);
return (pList != NULL);
}
/*** afpScavengerDpcRoutine
*
* This is called in at DISPATCH_LEVEL when the timer expires. The entry at
* the head of the list is decremented and if ZERO unlinked and queued to the
* worker. If the list is non-empty, the timer is fired again.
*/
LOCAL VOID
afpScavengerDpcRoutine(
IN PKDPC pKDpc,
IN PVOID pContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
PSCAVENGERLIST pList;
AFPSTATUS Status;
BOOLEAN TimerStarted;
LARGE_INTEGER TimerValue;
#ifdef PROFILING
TIME TimeS, TimeE;
DWORD NumDispatched = 0;
AfpGetPerfCounter(&TimeS);
#endif
AfpSecondsSinceEpoch++;
if (afpScavengerStopped)
{
DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_ERR,
("afpScavengerDpcRoutine: Entered after flush !!!\n"));
return;
}
if (afpScavengerList != NULL)
{
ACQUIRE_SPIN_LOCK_AT_DPC(&afpScavengerLock);
if (afpScavengerList->scvgr_RelDelta != 0)
(afpScavengerList->scvgr_RelDelta)--;
// We should never be here if we have no work to do
while (afpScavengerList != NULL)
{
// Dispatch all entries that are ready to go
if (afpScavengerList->scvgr_RelDelta == 0)
{
pList = afpScavengerList;
afpScavengerList = pList->scvgr_Next;
DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
("afpScavengerDpcRoutine: Dispatching %lx\n",
pList->scvgr_WorkItem.wi_Worker));
// Release spin lock as the caller might call us back
RELEASE_SPIN_LOCK_FROM_DPC(&afpScavengerLock);
Status = AFP_ERR_QUEUE;
if (!pList->scvgr_fQueue)
{
Status = (*pList->scvgr_Worker)(pList->scvgr_Context);
#ifdef PROFILING
NumDispatched++;
#endif
}
ACQUIRE_SPIN_LOCK_AT_DPC(&afpScavengerLock);
if (Status == AFP_ERR_QUEUE)
{
DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
("afpScavengerDpcRoutine: Queueing %lx\n",
pList->scvgr_WorkItem.wi_Worker));
AfpQueueWorkItem(&pList->scvgr_WorkItem);
}
else if (Status == AFP_ERR_REQUEUE)
{
afpScavengerEnqueue(pList);
}
else AfpFreeMemory(pList);
}
else break;
}
RELEASE_SPIN_LOCK_FROM_DPC(&afpScavengerLock);
}
TimerValue.QuadPart = AFP_SCAVENGER_TIMER_TICK;
TimerStarted = KeSetTimer(&afpScavengerTimer,
TimerValue,
&afpScavengerDpc);
ASSERT(!TimerStarted);
#ifdef PROFILING
AfpGetPerfCounter(&TimeE);
ACQUIRE_SPIN_LOCK_AT_DPC(&AfpStatisticsLock);
AfpServerProfile->perf_ScavengerCount += NumDispatched;
AfpServerProfile->perf_ScavengerTime.QuadPart +=
(TimeE.QuadPart - TimeS.QuadPart);
RELEASE_SPIN_LOCK_FROM_DPC(&AfpStatisticsLock);
#endif
}
/*** AfpScavengerFlushAndStop
*
* Force all entries in the scavenger queue to be dispatched immediately. No
* more queue'ing of scavenger routines is permitted after this. The scavenger
* essentially shuts down. Callable only in the worker context.
*/
VOID
AfpScavengerFlushAndStop(
VOID
)
{
PSCAVENGERLIST pList;
KIRQL OldIrql;
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
("afpScavengerFlushAndStop: Entered\n"));
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
afpScavengerStopped = True;
KeCancelTimer(&afpScavengerTimer);
if (afpScavengerList != NULL)
{
// Dispatch all entries right away
while (afpScavengerList != NULL)
{
AFPSTATUS Status;
pList = afpScavengerList;
afpScavengerList = pList->scvgr_Next;
// Call the worker with spin-lock held since they expect to be
// called at DPC. We are safe since if the worker tries to
// call AfpScavengerScheduleEvent(), we'll not try to re-acquire
// the lock as afpScavengerStopped is True.
DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
("afpScavengerFlushAndStop: Dispatching %lx\n",
pList->scvgr_WorkItem.wi_Worker));
if (!(pList->scvgr_fQueue))
Status = (*pList->scvgr_Worker)(pList->scvgr_Context);
if (pList->scvgr_fQueue ||
(Status == AFP_ERR_QUEUE))
{
// Well do it the hard way, if the worker insists on working
// at non DISPACTH level.
RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
(*pList->scvgr_Worker)(pList->scvgr_Context);
ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
}
AfpFreeMemory(pList);
}
}
RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
}
/*** AfpScavengerWorker
*
* This gets invoked when the scavenger Dpc queues up the routine.
*/
LOCAL VOID FASTCALL
afpScavengerWorker(
IN PSCAVENGERLIST pList
)
{
AFPSTATUS Status;
KIRQL OldIrql;
#ifdef PROFILING
TIME TimeS, TimeE;
AfpGetPerfCounter(&TimeS);
#endif
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
// Call the worker routine
Status = (*pList->scvgr_Worker)(pList->scvgr_Context);
ASSERT (Status != AFP_ERR_QUEUE);
#ifdef PROFILING
AfpGetPerfCounter(&TimeE);
ACQUIRE_SPIN_LOCK(&AfpStatisticsLock, &OldIrql);
AfpServerProfile->perf_ScavengerCount++;
AfpServerProfile->perf_ScavengerTime.QuadPart +=
(TimeE.QuadPart - TimeS.QuadPart);
RELEASE_SPIN_LOCK(&AfpStatisticsLock, OldIrql);
#endif
if (Status == AFP_ERR_REQUEUE)
{
ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
afpScavengerEnqueue(pList);
RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
}
else
{
ASSERT (NT_SUCCESS(Status));
AfpFreeMemory(pList);
}
}