912 lines
22 KiB
C
912 lines
22 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
worker.c
|
||
|
||
Abstract:
|
||
|
||
This module implements the LAN Manager server FSP worker thread
|
||
function. It also implements routines for managing (i.e., starting
|
||
and stopping) worker threads, and balancing load.
|
||
|
||
Author:
|
||
|
||
Chuck Lenzmeier (chuckl) 01-Oct-1989
|
||
David Treadwell (davidtr)
|
||
|
||
Environment:
|
||
|
||
Kernel mode
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "precomp.h"
|
||
#include "worker.tmh"
|
||
#pragma hdrstop
|
||
|
||
#define BugCheckFileId SRV_FILE_WORKER
|
||
|
||
// This is the maximum number of workitems that can be in the queue to be
|
||
// LPC'd up to user mode. This prevents us from consuming all the resources if
|
||
// spooler or some other user-mode process gets wedged or overly stressed
|
||
#define SRV_MAX_LPC_CALLS 250
|
||
|
||
//
|
||
// Local declarations
|
||
//
|
||
|
||
NTSTATUS
|
||
CreateQueueThread (
|
||
IN PWORK_QUEUE Queue
|
||
);
|
||
|
||
VOID
|
||
InitializeWorkerThread (
|
||
IN PWORK_QUEUE WorkQueue,
|
||
IN KPRIORITY ThreadPriority
|
||
);
|
||
|
||
VOID
|
||
WorkerThread (
|
||
IN PWORK_QUEUE WorkQueue
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text( PAGE, SrvCreateWorkerThreads )
|
||
#pragma alloc_text( PAGE, CreateQueueThread )
|
||
#pragma alloc_text( PAGE, InitializeWorkerThread )
|
||
#pragma alloc_text( PAGE, WorkerThread )
|
||
#endif
|
||
#if 0
|
||
NOT PAGEABLE -- SrvQueueWorkToBlockingThread
|
||
NOT PAGEABLE -- SrvQueueWorkToFsp
|
||
NOT PAGEABLE -- SrvQueueWorkToFspAtSendCompletion
|
||
NOT PAGEABLE -- SrvBalanceLoad
|
||
#endif
|
||
|
||
|
||
NTSTATUS
|
||
SrvCreateWorkerThreads (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function creates the worker threads for the LAN Manager server
|
||
FSP.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Status of thread creation
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
PWORK_QUEUE queue;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Create the nonblocking worker threads.
|
||
//
|
||
for( queue = SrvWorkQueues; queue < eSrvWorkQueues; queue++ ) {
|
||
status = CreateQueueThread( queue );
|
||
if( !NT_SUCCESS( status ) ) {
|
||
return status;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Create the blocking worker threads.
|
||
//
|
||
for( queue = SrvBlockingWorkQueues; queue < eSrvBlockingWorkQueues; queue++ ) {
|
||
status = CreateQueueThread( queue );
|
||
if( !NT_SUCCESS( status ) ) {
|
||
return status;
|
||
}
|
||
}
|
||
|
||
status = CreateQueueThread( &SrvLpcWorkQueue );
|
||
if( !NT_SUCCESS( status ) )
|
||
{
|
||
return status;
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvCreateWorkerThreads
|
||
|
||
|
||
NTSTATUS
|
||
CreateQueueThread (
|
||
IN PWORK_QUEUE Queue
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function creates a worker thread to service a queue.
|
||
|
||
NOTE: The scavenger occasionally kills off threads on a queue. If logic
|
||
here is modified, you may need to look there too.
|
||
|
||
Arguments:
|
||
|
||
Queue - the queue to service
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Status of thread creation
|
||
|
||
--*/
|
||
{
|
||
HANDLE threadHandle;
|
||
LARGE_INTEGER interval;
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Another thread is coming into being. Keep the counts up to date
|
||
//
|
||
InterlockedIncrement( &Queue->Threads );
|
||
InterlockedIncrement( &Queue->AvailableThreads );
|
||
|
||
status = PsCreateSystemThread(
|
||
&threadHandle,
|
||
PROCESS_ALL_ACCESS,
|
||
NULL,
|
||
NtCurrentProcess(),
|
||
NULL,
|
||
WorkerThread,
|
||
Queue
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_EXPECTED,
|
||
"CreateQueueThread: PsCreateSystemThread for "
|
||
"queue %X returned %X",
|
||
Queue,
|
||
status
|
||
);
|
||
|
||
InterlockedDecrement( &Queue->Threads );
|
||
InterlockedDecrement( &Queue->AvailableThreads );
|
||
|
||
SrvLogServiceFailure( SRV_SVC_PS_CREATE_SYSTEM_THREAD, status );
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Close the handle so the thread can die when needed
|
||
//
|
||
|
||
SrvNtClose( threadHandle, FALSE );
|
||
|
||
//
|
||
// If we just created the first queue thread, wait for it
|
||
// to store its thread pointer in IrpThread. This pointer is
|
||
// stored in all IRPs issued for this queue by the server.
|
||
//
|
||
while ( Queue->IrpThread == NULL ) {
|
||
interval.QuadPart = -1*10*1000*10; // .01 second
|
||
KeDelayExecutionThread( KernelMode, FALSE, &interval );
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // CreateQueueThread
|
||
|
||
|
||
VOID
|
||
InitializeWorkerThread (
|
||
IN PWORK_QUEUE WorkQueue,
|
||
IN KPRIORITY ThreadPriority
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
KPRIORITY basePriority;
|
||
|
||
PAGED_CODE( );
|
||
|
||
|
||
#if SRVDBG_LOCK
|
||
{
|
||
//
|
||
// Create a special system thread TEB. The size of this TEB is just
|
||
// large enough to accommodate the first three user-reserved
|
||
// longwords. These three locations are used for lock debugging. If
|
||
// the allocation fails, then no lock debugging will be performed
|
||
// for this thread.
|
||
//
|
||
//
|
||
|
||
PETHREAD Thread = PsGetCurrentThread( );
|
||
ULONG TebSize = FIELD_OFFSET( TEB, UserReserved[0] ) + SRV_TEB_USER_SIZE;
|
||
|
||
Thread->Tcb.Teb = ExAllocatePoolWithTag( NonPagedPool, TebSize, TAG_FROM_TYPE(BlockTypeMisc) );
|
||
|
||
if ( Thread->Tcb.Teb != NULL ) {
|
||
RtlZeroMemory( Thread->Tcb.Teb, TebSize );
|
||
}
|
||
}
|
||
#endif // SRVDBG_LOCK
|
||
|
||
//
|
||
// Set this thread's priority.
|
||
//
|
||
|
||
basePriority = ThreadPriority;
|
||
|
||
status = NtSetInformationThread (
|
||
NtCurrentThread( ),
|
||
ThreadBasePriority,
|
||
&basePriority,
|
||
sizeof(basePriority)
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"InitializeWorkerThread: NtSetInformationThread failed: %X\n",
|
||
status,
|
||
NULL
|
||
);
|
||
SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_THREAD, status );
|
||
}
|
||
|
||
#if MULTIPROCESSOR
|
||
//
|
||
// If this is a nonblocking worker thread, set its ideal processor affinity. Setting
|
||
// ideal affinity informs ntos that the thread would rather run on its ideal
|
||
// processor if reasonable, but if ntos can't schedule it on that processor then it is
|
||
// ok to schedule it on a different processor.
|
||
//
|
||
if( SrvNumberOfProcessors > 1 && WorkQueue >= SrvWorkQueues && WorkQueue < eSrvWorkQueues ) {
|
||
KeSetIdealProcessorThread( KeGetCurrentThread(), (CCHAR)(WorkQueue - SrvWorkQueues) );
|
||
}
|
||
|
||
//
|
||
// Blocking threads are now also affinitized
|
||
//
|
||
if( SrvNumberOfProcessors >= 4 && WorkQueue >= SrvBlockingWorkQueues && WorkQueue < eSrvBlockingWorkQueues ) {
|
||
KeSetIdealProcessorThread( KeGetCurrentThread(), (CCHAR)(WorkQueue - SrvBlockingWorkQueues) );
|
||
}
|
||
|
||
#endif
|
||
|
||
//
|
||
// Disable hard error popups for this thread.
|
||
//
|
||
|
||
IoSetThreadHardErrorMode( FALSE );
|
||
|
||
return;
|
||
|
||
} // InitializeWorkerThread
|
||
|
||
|
||
VOID
|
||
WorkerThread (
|
||
IN PWORK_QUEUE WorkQueue
|
||
)
|
||
{
|
||
PLIST_ENTRY listEntry;
|
||
PWORK_CONTEXT workContext;
|
||
ULONG timeDifference;
|
||
ULONG updateSmbCount = 0;
|
||
ULONG updateTime = 0;
|
||
BOOLEAN iAmBlockingThread = ((WorkQueue >= SrvBlockingWorkQueues) && (WorkQueue < eSrvBlockingWorkQueues)) ? TRUE : FALSE;
|
||
PLARGE_INTEGER Timeout = NULL;
|
||
BOOLEAN iAmLpcThread = (WorkQueue == &SrvLpcWorkQueue) ? TRUE : FALSE;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// If this is the first worker thread, save the thread pointer.
|
||
//
|
||
if( WorkQueue->IrpThread == NULL ) {
|
||
WorkQueue->IrpThread = PsGetCurrentThread( );
|
||
}
|
||
|
||
InitializeWorkerThread( WorkQueue, SrvThreadPriority );
|
||
|
||
//
|
||
// If we are the IrpThread, we don't want to die
|
||
//
|
||
if( WorkQueue->IrpThread != PsGetCurrentThread( ) ) {
|
||
Timeout = &WorkQueue->IdleTimeOut;
|
||
}
|
||
|
||
//
|
||
// Loop infinitely dequeueing and processing work items.
|
||
//
|
||
|
||
while ( TRUE ) {
|
||
|
||
listEntry = KeRemoveQueue(
|
||
&WorkQueue->Queue,
|
||
WorkQueue->WaitMode,
|
||
Timeout
|
||
);
|
||
|
||
if( (ULONG_PTR)listEntry == STATUS_TIMEOUT ) {
|
||
//
|
||
// We have a non critical thread that hasn't gotten any work for
|
||
// awhile. Time to die.
|
||
//
|
||
InterlockedDecrement( &WorkQueue->AvailableThreads );
|
||
InterlockedDecrement( &WorkQueue->Threads );
|
||
SrvTerminateWorkerThread( NULL );
|
||
}
|
||
|
||
if( InterlockedDecrement( &WorkQueue->AvailableThreads ) == 0 &&
|
||
!SrvFspTransitioning &&
|
||
WorkQueue->Threads < WorkQueue->MaxThreads ) {
|
||
|
||
//
|
||
// We are running low on threads for this queue. Spin up
|
||
// another one before handling this request
|
||
//
|
||
CreateQueueThread( WorkQueue );
|
||
}
|
||
|
||
//
|
||
// Get the address of the work item.
|
||
//
|
||
|
||
workContext = CONTAINING_RECORD(
|
||
listEntry,
|
||
WORK_CONTEXT,
|
||
ListEntry
|
||
);
|
||
|
||
ASSERT( KeGetCurrentIrql() == 0 );
|
||
|
||
//
|
||
// There is work available. It may be a work contect block or
|
||
// an RFCB. (Blocking threads won't get RFCBs.)
|
||
//
|
||
|
||
ASSERT( (GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextInitial) ||
|
||
(GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextNormal) ||
|
||
(GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextRaw) ||
|
||
(GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextSpecial) ||
|
||
(GET_BLOCK_TYPE(workContext) == BlockTypeRfcb) );
|
||
|
||
#if DBG
|
||
if ( GET_BLOCK_TYPE( workContext ) == BlockTypeRfcb ) {
|
||
((PRFCB)workContext)->ListEntry.Flink =
|
||
((PRFCB)workContext)->ListEntry.Blink = NULL;
|
||
}
|
||
#endif
|
||
|
||
IF_DEBUG(WORKER1) {
|
||
KdPrint(( "WorkerThread working on work context %p", workContext ));
|
||
}
|
||
|
||
//
|
||
// Make sure we have a resaonable idea of the system time
|
||
//
|
||
if( ++updateTime == TIME_SMB_INTERVAL ) {
|
||
updateTime = 0;
|
||
SET_SERVER_TIME( WorkQueue );
|
||
}
|
||
|
||
//
|
||
// Update statistics.
|
||
//
|
||
if ( ++updateSmbCount == STATISTICS_SMB_INTERVAL ) {
|
||
|
||
updateSmbCount = 0;
|
||
|
||
GET_SERVER_TIME( WorkQueue, &timeDifference );
|
||
timeDifference = timeDifference - workContext->Timestamp;
|
||
|
||
++(WorkQueue->stats.WorkItemsQueued.Count);
|
||
WorkQueue->stats.WorkItemsQueued.Time.QuadPart += timeDifference;
|
||
}
|
||
|
||
{
|
||
//
|
||
// Put the workContext out relative to bp so we can find it later if we need
|
||
// to debug. The block of memory we're writing to is likely already in cache,
|
||
// so this should be relatively cheap.
|
||
//
|
||
PWORK_CONTEXT volatile savedWorkContext;
|
||
savedWorkContext = workContext;
|
||
|
||
}
|
||
|
||
//
|
||
// Make sure the WorkContext knows if it is on the blocking work queue
|
||
//
|
||
workContext->UsingBlockingThread = iAmBlockingThread;
|
||
workContext->UsingLpcThread = iAmLpcThread;
|
||
|
||
//
|
||
// Call the restart routine for the work item.
|
||
//
|
||
|
||
IF_SMB_DEBUG( TRACE ) {
|
||
KdPrint(( "Blocking %d, Count %d -> %p( %p )\n",
|
||
iAmBlockingThread,
|
||
workContext->ProcessingCount,
|
||
workContext->FspRestartRoutine,
|
||
workContext
|
||
));
|
||
}
|
||
|
||
workContext->FspRestartRoutine( workContext );
|
||
|
||
//
|
||
// Make sure we are still at normal level.
|
||
//
|
||
|
||
ASSERT( KeGetCurrentIrql() == 0 );
|
||
|
||
//
|
||
// We're getting ready to be available (i.e. waiting on the queue)
|
||
//
|
||
InterlockedIncrement( &WorkQueue->AvailableThreads );
|
||
|
||
}
|
||
|
||
} // WorkerThread
|
||
|
||
VOID SRVFASTCALL
|
||
SrvQueueWorkToBlockingThread (
|
||
IN OUT PWORK_CONTEXT WorkContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine queues a work item to a blocking thread. These threads
|
||
are used to service requests that may block for a long time, so we
|
||
don't want to tie up our normal worker threads.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block
|
||
representing the work item
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Increment the processing count.
|
||
//
|
||
|
||
WorkContext->ProcessingCount++;
|
||
|
||
//
|
||
// Insert the work item at the tail of the blocking work queue.
|
||
//
|
||
|
||
SrvInsertWorkQueueTail(
|
||
GET_BLOCKING_WORK_QUEUE(),
|
||
(PQUEUEABLE_BLOCK_HEADER)WorkContext
|
||
);
|
||
|
||
return;
|
||
|
||
} // SrvQueueWorkToBlockingThread
|
||
|
||
NTSTATUS SRVFASTCALL
|
||
SrvQueueWorkToLpcThread (
|
||
IN OUT PWORK_CONTEXT WorkContext,
|
||
IN BOOLEAN ThrottleRequest
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine queues a work item to a blocking thread. These threads
|
||
are used to service requests that may block for a long time, so we
|
||
don't want to tie up our normal worker threads.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block
|
||
representing the work item
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
// The most LPC calls we allow is the number of work items / 4 with a maximum of 250.
|
||
// The minimum is 4. If the SrvSvc is not processing it at this fast of a rate, we need
|
||
// to turn them away.
|
||
if ( ThrottleRequest &&
|
||
(KeReadStateQueue( &SrvLpcWorkQueue.Queue ) > (LONG)MAX( 4, MIN(SrvMaxReceiveWorkItemCount>>2, 250) )) ) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
//
|
||
// Increment the processing count.
|
||
//
|
||
|
||
WorkContext->ProcessingCount++;
|
||
|
||
//
|
||
// Insert the work item at the tail of the blocking work queue.
|
||
//
|
||
|
||
SrvInsertWorkQueueTail(
|
||
&SrvLpcWorkQueue,
|
||
(PQUEUEABLE_BLOCK_HEADER)WorkContext
|
||
);
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvQueueWorkToBlockingThread
|
||
|
||
|
||
|
||
VOID SRVFASTCALL
|
||
SrvQueueWorkToFsp (
|
||
IN OUT PWORK_CONTEXT WorkContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the restart routine for work items that are to be queued to
|
||
a nonblocking worker thread in the FSP. This function is also
|
||
called from elsewhere in the server to transfer work to the FSP.
|
||
This function should not be called at dispatch level -- use
|
||
SrvQueueWorkToFspAtDpcLevel instead.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block
|
||
representing the work item
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Increment the processing count.
|
||
//
|
||
|
||
WorkContext->ProcessingCount++;
|
||
|
||
//
|
||
// Insert the work item at the tail of the nonblocking work queue.
|
||
//
|
||
|
||
if( WorkContext->QueueToHead ) {
|
||
|
||
SrvInsertWorkQueueHead(
|
||
WorkContext->CurrentWorkQueue,
|
||
(PQUEUEABLE_BLOCK_HEADER)WorkContext
|
||
);
|
||
|
||
} else {
|
||
|
||
SrvInsertWorkQueueTail(
|
||
WorkContext->CurrentWorkQueue,
|
||
(PQUEUEABLE_BLOCK_HEADER)WorkContext
|
||
);
|
||
|
||
}
|
||
|
||
} // SrvQueueWorkToFsp
|
||
|
||
|
||
NTSTATUS
|
||
SrvQueueWorkToFspAtSendCompletion (
|
||
IN PDEVICE_OBJECT DeviceObject,
|
||
IN PIRP Irp,
|
||
IN PWORK_CONTEXT WorkContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Send completion handler for work items that are to be queued to
|
||
a nonblocking worker thread in the FSP. This function is also
|
||
called from elsewhere in the server to transfer work to the FSP.
|
||
This function should not be called at dispatch level -- use
|
||
SrvQueueWorkToFspAtDpcLevel instead.
|
||
|
||
Arguments:
|
||
|
||
DeviceObject - Pointer to target device object for the request.
|
||
|
||
Irp - Pointer to I/O request packet
|
||
|
||
WorkContext - Caller-specified context parameter associated with IRP.
|
||
This is actually a pointer to a Work Context block.
|
||
|
||
Return Value:
|
||
|
||
STATUS_MORE_PROCESSING_REQUIRED.
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Check the status of the send completion.
|
||
//
|
||
|
||
CHECK_SEND_COMPLETION_STATUS( Irp->IoStatus.Status );
|
||
|
||
//
|
||
// Reset the IRP cancelled bit.
|
||
//
|
||
|
||
Irp->Cancel = FALSE;
|
||
|
||
//
|
||
// Increment the processing count.
|
||
//
|
||
|
||
WorkContext->ProcessingCount++;
|
||
|
||
//
|
||
// Insert the work item on the nonblocking work queue.
|
||
//
|
||
|
||
if( WorkContext->QueueToHead ) {
|
||
|
||
SrvInsertWorkQueueHead(
|
||
WorkContext->CurrentWorkQueue,
|
||
(PQUEUEABLE_BLOCK_HEADER)WorkContext
|
||
);
|
||
|
||
} else {
|
||
|
||
SrvInsertWorkQueueTail(
|
||
WorkContext->CurrentWorkQueue,
|
||
(PQUEUEABLE_BLOCK_HEADER)WorkContext
|
||
);
|
||
|
||
}
|
||
|
||
return STATUS_MORE_PROCESSING_REQUIRED;
|
||
|
||
} // SrvQueueWorkToFspAtSendCompletion
|
||
|
||
|
||
VOID SRVFASTCALL
|
||
SrvTerminateWorkerThread (
|
||
IN OUT PWORK_CONTEXT WorkItem OPTIONAL
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called when a thread is being requested to terminate. There
|
||
are two cases when this happens. One is at server shutdown -- in this
|
||
case we need to keep requeueing the termination request until all the
|
||
threads on the queue have terminated. The other time is if a thread has
|
||
not received work for some amount of time.
|
||
--*/
|
||
{
|
||
LONG priority = 16;
|
||
|
||
|
||
//
|
||
// Raise our priority to ensure that this thread has a chance to get completely
|
||
// done before the main thread causes the driver to unload or something
|
||
//
|
||
NtSetInformationThread (
|
||
NtCurrentThread( ),
|
||
ThreadBasePriority,
|
||
&priority,
|
||
sizeof(priority)
|
||
);
|
||
|
||
if( ARGUMENT_PRESENT( WorkItem ) &&
|
||
InterlockedDecrement( &WorkItem->CurrentWorkQueue->Threads ) != 0 ) {
|
||
|
||
//
|
||
// We are being asked to terminate all of the worker threads on this queue.
|
||
// So, if we're not the last thread, we should requeue the workitem so
|
||
// the other threads will terminate
|
||
//
|
||
|
||
//
|
||
// There are still other threads servicing this queue, so requeue
|
||
// the workitem
|
||
//
|
||
SrvInsertWorkQueueTail( WorkItem->CurrentWorkQueue,
|
||
(PQUEUEABLE_BLOCK_HEADER)WorkItem );
|
||
}
|
||
|
||
PsTerminateSystemThread( STATUS_SUCCESS ); // no return;
|
||
}
|
||
|
||
|
||
#if MULTIPROCESSOR
|
||
|
||
VOID
|
||
SrvBalanceLoad(
|
||
IN PCONNECTION connection
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Ensure that the processor handling 'connection' is the best one
|
||
for the job. This routine is called periodically per connection from
|
||
DPC level. It can not be paged.
|
||
|
||
Arguments:
|
||
|
||
connection - the connection to inspect
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
ULONG MyQueueLength, OtherQueueLength;
|
||
ULONG i;
|
||
PWORK_QUEUE tmpqueue;
|
||
PWORK_QUEUE queue = connection->CurrentWorkQueue;
|
||
|
||
ASSERT( queue >= SrvWorkQueues );
|
||
ASSERT( queue < eSrvWorkQueues );
|
||
|
||
//
|
||
// Reset the countdown. After the client performs BalanceCount
|
||
// more operations, we'll call this routine again.
|
||
//
|
||
connection->BalanceCount = SrvBalanceCount;
|
||
|
||
//
|
||
// Figure out the load on the current work queue. The load is
|
||
// the sum of the average work queue depth and the current work
|
||
// queue depth. This gives us some history mixed in with the
|
||
// load *right now*
|
||
//
|
||
MyQueueLength = queue->AvgQueueDepthSum >> LOG2_QUEUE_SAMPLES;
|
||
MyQueueLength += KeReadStateQueue( &queue->Queue );
|
||
|
||
//
|
||
// If we are not on our preferred queue, look to see if we want to
|
||
// go back to it. The preferred queue is the queue for the processor
|
||
// handling this client's network card DPCs. We prefer to run on that
|
||
// processor to avoid sloshing data between CPUs in an MP system.
|
||
//
|
||
tmpqueue = connection->PreferredWorkQueue;
|
||
|
||
ASSERT( tmpqueue >= SrvWorkQueues );
|
||
ASSERT( tmpqueue < eSrvWorkQueues );
|
||
|
||
if( tmpqueue != queue ) {
|
||
|
||
//
|
||
// We are not queueing to our preferred queue. See if we
|
||
// should go back to our preferred queue
|
||
//
|
||
|
||
ULONG PreferredQueueLength;
|
||
|
||
PreferredQueueLength = tmpqueue->AvgQueueDepthSum >> LOG2_QUEUE_SAMPLES;
|
||
PreferredQueueLength += KeReadStateQueue( &tmpqueue->Queue );
|
||
|
||
if( PreferredQueueLength <= MyQueueLength + SrvPreferredAffinity ) {
|
||
|
||
//
|
||
// We want to switch back to our preferred processor!
|
||
//
|
||
|
||
IF_DEBUG( REBALANCE ) {
|
||
KdPrint(( "%p C%d(%p) > P%p(%d)\n",
|
||
connection,
|
||
MyQueueLength,
|
||
(PVOID)(connection->CurrentWorkQueue - SrvWorkQueues),
|
||
(PVOID)(tmpqueue - SrvWorkQueues),
|
||
PreferredQueueLength ));
|
||
}
|
||
|
||
InterlockedDecrement( &queue->CurrentClients );
|
||
InterlockedExchangePointer( &connection->CurrentWorkQueue, tmpqueue );
|
||
InterlockedIncrement( &tmpqueue->CurrentClients );
|
||
SrvReBalanced++;
|
||
return;
|
||
}
|
||
}
|
||
|
||
//
|
||
// We didn't hop to the preferred processor, so let's see if
|
||
// another processor looks more lightly loaded than we are.
|
||
//
|
||
|
||
//
|
||
// SrvNextBalanceProcessor is the next processor we should consider
|
||
// moving to. It is a global to ensure everybody doesn't pick the
|
||
// the same processor as the next candidate.
|
||
//
|
||
tmpqueue = &SrvWorkQueues[ SrvNextBalanceProcessor ];
|
||
|
||
//
|
||
// Advance SrvNextBalanceProcessor to the next processor in the system
|
||
//
|
||
i = SrvNextBalanceProcessor + 1;
|
||
|
||
if( i >= SrvNumberOfProcessors )
|
||
i = 0;
|
||
|
||
SrvNextBalanceProcessor = i;
|
||
|
||
//
|
||
// Look at the other processors, and pick the next one which is doing
|
||
// enough less work than we are to make the jump worthwhile
|
||
//
|
||
|
||
for( i = SrvNumberOfProcessors; i > 1; --i ) {
|
||
|
||
ASSERT( tmpqueue >= SrvWorkQueues );
|
||
ASSERT( tmpqueue < eSrvWorkQueues );
|
||
|
||
OtherQueueLength = tmpqueue->AvgQueueDepthSum >> LOG2_QUEUE_SAMPLES;
|
||
OtherQueueLength += KeReadStateQueue( &tmpqueue->Queue );
|
||
|
||
if( OtherQueueLength + SrvOtherQueueAffinity < MyQueueLength ) {
|
||
|
||
//
|
||
// This processor looks promising. Switch to it
|
||
//
|
||
|
||
IF_DEBUG( REBALANCE ) {
|
||
KdPrint(( "%p %c%p(%d) > %c%p(%d)\n",
|
||
connection,
|
||
queue == connection->PreferredWorkQueue ? 'P' : 'C',
|
||
(PVOID)(queue - SrvWorkQueues),
|
||
MyQueueLength,
|
||
tmpqueue == connection->PreferredWorkQueue ? 'P' : 'C',
|
||
(PVOID)(tmpqueue - SrvWorkQueues),
|
||
OtherQueueLength ));
|
||
}
|
||
|
||
InterlockedDecrement( &queue->CurrentClients );
|
||
InterlockedExchangePointer( &connection->CurrentWorkQueue, tmpqueue );
|
||
InterlockedIncrement( &tmpqueue->CurrentClients );
|
||
SrvReBalanced++;
|
||
return;
|
||
}
|
||
|
||
if( ++tmpqueue == eSrvWorkQueues )
|
||
tmpqueue = SrvWorkQueues;
|
||
}
|
||
|
||
//
|
||
// No rebalancing necessary
|
||
//
|
||
return;
|
||
}
|
||
#endif
|