602 lines
21 KiB
C++
602 lines
21 KiB
C++
/*++
|
||
|
||
Copyright (c) 2000 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
GC.cxx
|
||
|
||
Abstract:
|
||
|
||
The garbage collection mechanism common code. See the comment
|
||
below for more details.
|
||
|
||
Author:
|
||
|
||
Kamen Moutafov (kamenm) Apr 2000
|
||
|
||
Garbage Collection Mechanism:
|
||
This comment describes how the garbage collection mechanism works.
|
||
The code itself is spread in variety in places.
|
||
|
||
Purpose:
|
||
There are two types of garbage collection we perform - periodic
|
||
and one-time. Periodic may be needed by the Osf idle connection
|
||
cleanup mechanism, which tries to cleanup unused osf connections
|
||
if the app explicitly asked for it via RpcMgmtEnableIdleCleanup.
|
||
The one-time cleanup is used by the lingering associations. If
|
||
an association is lingered, it will request cleanup to be performed
|
||
after a certain period of time. The garbage collection needs to
|
||
support both of those mechanisms.
|
||
|
||
Design Goals:
|
||
Have minimal memory and CPU consumption requirements
|
||
Don't cause periodic background activity if there is no
|
||
garbage collection to be performed.
|
||
Guarantee that garbage collection will be performed in
|
||
a reasonable amount of time after its request time (i.e. 10 minutes
|
||
to an hour at worst case)
|
||
|
||
Implementation:
|
||
We use the worker threads in the thread pools to perform garbage
|
||
collection. There are several thread pools - the Ioc thread pool
|
||
(remote threads) as well as one thread pool for each LRPC address.
|
||
Within each pool, from a gc perspective, we differentiate between
|
||
two types of threads - threads on a short wait and threads on a
|
||
long wait. Threads on a short wait are either threads waiting for
|
||
something to happen with a timeout of gThreadTimeout or less, or
|
||
threads performing a work item (threads doing both are also
|
||
considered to be on a short wait). Threads on a long wait are
|
||
threads waiting for more than that. As part of our thread management
|
||
we will keep count of how many threads are on a short wait and how
|
||
many are on a long wait.
|
||
|
||
All threads in all thread pools will attempt to do garbage collection
|
||
when they timeout waiting for something to happen. Since all thread pools
|
||
need at least one listening thread, all thread pools are guaranteed to
|
||
have a thread timing out once every so often. The garbage collection attempt
|
||
will be cut very short if there is nothing to garbage collect, so the
|
||
attempt is not performance expensive in the common case. The function
|
||
to attempt garbage collection is PerformGarbageCollection
|
||
|
||
If a thread times out on the completion port/LPC port, it will
|
||
do garbage collection, and then will check whether there are
|
||
more items to garbage collect (either one-time or periodic) and how
|
||
many threads from this thread pool are on a short wait. If there is
|
||
garbage collection to be done, and there are no other threads on short
|
||
wait, this thread will not go on a long wait, but it will repeat its
|
||
short wait. This ensures timely garbage collection. If all the threads
|
||
have gone on a long wait, and a piece of code needs garbage collection,
|
||
it will request the garbage collection and it will tickle a worker thread.
|
||
The tickling consist of posting an empty message to the completion port
|
||
or LPC port. All the synchronization b/n worker threads and threads
|
||
requesting garbage collection is done using interlocks, to avoid perf
|
||
hit. This introduces a couple of benign races through the code, which may
|
||
prevent a thread from going on a long wait once, but that's ok.
|
||
|
||
In order to ensure that we do gc only when needed, in most cases we refcount
|
||
the number of items that need garbage collection.
|
||
|
||
--*/
|
||
|
||
#include <precomp.hxx>
|
||
#include <hndlsvr.hxx>
|
||
#include <lpcpack.hxx>
|
||
#include <lpcsvr.hxx>
|
||
#include <osfpcket.hxx>
|
||
#include <bitset.hxx>
|
||
#include <queue.hxx>
|
||
#include <ProtBind.hxx>
|
||
#include <osfclnt.hxx>
|
||
#include <rpcqos.h>
|
||
#include <lpcclnt.hxx>
|
||
|
||
// used by periodic cleanup only - the period
|
||
// on which to do cleanup. This is in seconds
|
||
unsigned long WaitToGarbageCollectDelay = 0;
|
||
|
||
// The number of items on which garbage collection
|
||
// is needed. If 0, no periodic garbage collection
|
||
// is necessary. Each item that needs garbage collection
|
||
// will InterlockIncrement this when it is created,
|
||
// and will InterlockDecrement this when it is destroyed
|
||
long PeriodicGarbageCollectItems = 0;
|
||
|
||
// set non-zero when we need to cleanup idle LRPC_SCONTEXTs
|
||
unsigned int fEnableIdleLrpcSContextsCleanup = 0;
|
||
|
||
// set to non-zero when we enable garbage collection cleanup. This either
|
||
// happens when the user calls it explicitly with
|
||
// RpcMgmtEnableIdleCleanup or implicitly if we gather too many
|
||
// connection in an association
|
||
unsigned int fEnableIdleConnectionCleanup = 0;
|
||
|
||
unsigned int IocThreadStarted = 0;
|
||
|
||
// used by one-time garbage collection items only!
|
||
long GarbageCollectionRequested = 0;
|
||
|
||
// The semantics of this variable should be
|
||
// interpreted as follows - don't bother to cleanup
|
||
// before this time stamp - you won't find anything.
|
||
// This means that after this interval, there may be
|
||
// many items to cleanup later on - it just says the
|
||
// first is at this time.
|
||
// The timestamp is in millseconds.
|
||
DWORD NextOneTimeCleanup = 0;
|
||
|
||
|
||
const int MaxPeriodsWithoutGC = 100;
|
||
|
||
|
||
BOOL
|
||
GarbageCollectionNeeded (
|
||
IN BOOL fOneTimeCleanup,
|
||
IN unsigned long GarbageCollectInterval
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
A routine used by code throughout RPC to arrange
|
||
for garbage collection to be performed. Currently,
|
||
there are two types of garbage collecting -
|
||
idle Osf connections and lingering associations.
|
||
|
||
Parameters:
|
||
fOneTimeCleanup - if non-zero, this is a one time
|
||
cleanup and GarbageCollectInterval is interpreted as the
|
||
minimum time after which we want garbage collection
|
||
performed. Note that the garbage collection code can kick
|
||
off earlier than that. Appropriate arrangements must be
|
||
made to protect items not due for garbage collection.
|
||
If 0, this is a periodic cleanup, and
|
||
|
||
GarbageCollectInterval is interpreted as the period for
|
||
which we wait before making the next garbage collection
|
||
pass. Note that for the periodic cleanup, this is a hint
|
||
that can be ignored - don't count on it. The time is in
|
||
milliseconds.
|
||
|
||
Return Value:
|
||
non-zero - garbage collection is available and will be done
|
||
FALSE - garbage collection is not available
|
||
|
||
--*/
|
||
{
|
||
RPC_STATUS RpcStatus = RPC_S_OK;
|
||
THREAD * Thread;
|
||
DWORD LocalTickCount;
|
||
LOADABLE_TRANSPORT *LoadableTransport;
|
||
LOADABLE_TRANSPORT *FirstTransport = NULL;
|
||
DictionaryCursor cursor;
|
||
BOOL fRetVal = FALSE;
|
||
LRPC_ADDRESS *CurrentAddress;
|
||
LRPC_ADDRESS *LrpcAddressToTickle = NULL;
|
||
|
||
if (fOneTimeCleanup)
|
||
{
|
||
LocalTickCount = GetTickCount();
|
||
// N.B. There is a race here where two threads can set this -
|
||
// the race is benign - the second thread will win and write
|
||
// its time, which by virtue of the small race window will
|
||
// be shortly after the first thread
|
||
if (!GarbageCollectionRequested)
|
||
{
|
||
NextOneTimeCleanup = LocalTickCount + GarbageCollectInterval;
|
||
GarbageCollectionRequested = 1;
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) GC requested - tick count %d\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), LocalTickCount);
|
||
#endif
|
||
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// WaitToGarbageCollectDelay is a global variable - avoid sloshing
|
||
if (WaitToGarbageCollectDelay == 0)
|
||
WaitToGarbageCollectDelay = GarbageCollectInterval;
|
||
|
||
InterlockedIncrement(&PeriodicGarbageCollectItems);
|
||
}
|
||
|
||
// is the completion port started? If yes, we will use it as the
|
||
// preferred method of garbage collection
|
||
if (IocThreadStarted)
|
||
{
|
||
// if we use the completion port, we either need a thread on a
|
||
// short wait (i.e. it will perform garbage collection soon
|
||
// anyway), or we need to tickle a thread on a long wait. We know
|
||
// that one of these will be true, because we always keep
|
||
// listening threads on the completion port - the only
|
||
// question is whether it is on a long or short wait thread that
|
||
// we have.
|
||
|
||
// this dictionary is guaranteed to never grow beyond the initial
|
||
// dictionary size and elements from it are never deleted - therefore,
|
||
// it is safe to iterate it without holding a mutex - we may miss
|
||
// an element if it was just being added, but that's ok. The important
|
||
// thing is that we can't fault
|
||
LoadedLoadableTransports->Reset(cursor);
|
||
while ((LoadableTransport
|
||
= LoadedLoadableTransports->Next(cursor)) != 0)
|
||
{
|
||
|
||
if (LoadableTransport->GetThreadsDoingShortWait() > 0)
|
||
{
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: there are Ioc threads on short wait - don't tickle\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId());
|
||
#endif
|
||
// there is a transport with threads on short wait
|
||
// garbage collection will be performed soon even without
|
||
// our help - we can bail out
|
||
FirstTransport = NULL;
|
||
fRetVal = TRUE;
|
||
break;
|
||
}
|
||
|
||
if (FirstTransport == NULL)
|
||
FirstTransport = LoadableTransport;
|
||
|
||
}
|
||
}
|
||
else if (LrpcAddressList
|
||
&& (((RTL_CRITICAL_SECTION *)(NtCurrentPeb()->LoaderLock))->OwningThread != NtCurrentTeb()->ClientId.UniqueThread))
|
||
{
|
||
|
||
LrpcMutexRequest();
|
||
|
||
// else, if there are Lrpc Addresses, check whether they are doing short wait
|
||
// and can gc for us
|
||
CurrentAddress = LrpcAddressList;
|
||
while (CurrentAddress)
|
||
{
|
||
// can this address gc for us?
|
||
if (CurrentAddress->GetNumberOfThreadsDoingShortWait() > 0)
|
||
{
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: there are threads on short wait (%d) on address %X - don't tickle\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId(), CurrentAddress,
|
||
CurrentAddress->GetNumberOfThreadsDoingShortWait());
|
||
#endif
|
||
LrpcAddressToTickle = NULL;
|
||
fRetVal = TRUE;
|
||
break;
|
||
}
|
||
|
||
if ((LrpcAddressToTickle == NULL) && (CurrentAddress->IsPreparedForLoopbackTickling()))
|
||
{
|
||
LrpcAddressToTickle = CurrentAddress;
|
||
}
|
||
CurrentAddress = CurrentAddress->GetNextAddress();
|
||
}
|
||
|
||
// N.B. It is possible that Osf associations need cleanup, but only LRPC worker
|
||
// threads are available, and moreover, no LRPC associations were created, which
|
||
// means none of the Lrpc addresses is prepared for loopback tickling. If this is
|
||
// the case, choose the first address, and make sure it is prepared for tickling
|
||
if ((LrpcAddressToTickle == NULL) && (fRetVal == FALSE))
|
||
{
|
||
LrpcAddressToTickle = LrpcAddressList;
|
||
|
||
// prepare the selected address for tickling
|
||
fRetVal = LrpcAddressToTickle->PrepareForLoopbackTicklingIfNecessary();
|
||
if (fRetVal == FALSE)
|
||
{
|
||
// if this fails, zero out the address for tickling. This
|
||
// will cause this function to return failure
|
||
LrpcAddressToTickle = NULL;
|
||
}
|
||
}
|
||
|
||
LrpcMutexClear();
|
||
}
|
||
else if (fEnableIdleConnectionCleanup)
|
||
{
|
||
// if fEnableIdleConnectionCleanup is set, we have to create a thread if there is't one yet
|
||
RpcStatus = CreateGarbageCollectionThread();
|
||
if (RpcStatus == RPC_S_OK)
|
||
{
|
||
// the thread creation was successful - tell our caller we
|
||
// will be doing garbage collection
|
||
fRetVal = TRUE;
|
||
}
|
||
}
|
||
|
||
// neither Ioc nor the LRPC thread pools have threads on short wait
|
||
// We have to tickle somebody - we try the Ioc thread pool first
|
||
if (FirstTransport)
|
||
{
|
||
// we couldn't find any transport with threads on short wait -
|
||
// tickle a thread from the RPC transport in order to ensure timely
|
||
// cleanup
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: No Ioc threads on short wait found - tickling one\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId());
|
||
#endif
|
||
RpcStatus = TickleIocThread();
|
||
if (RpcStatus == RPC_S_OK)
|
||
fRetVal = TRUE;
|
||
}
|
||
else if (LrpcAddressToTickle)
|
||
{
|
||
// try to tickle the LRPC address
|
||
fRetVal = LrpcAddressToTickle->LoopbackTickle();
|
||
}
|
||
|
||
return fRetVal;
|
||
}
|
||
|
||
RPC_STATUS CreateGarbageCollectionThread (
|
||
void
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Make a best effort to create a garbage collection thread. In this
|
||
implementation we simply choose to create a completion port thread,
|
||
as it has many uses.
|
||
|
||
Return Value:
|
||
|
||
RPC_S_OK on success or RPC_S_* on error
|
||
|
||
--*/
|
||
{
|
||
TRANS_INFO *TransInfo;
|
||
RPC_STATUS RpcStatus;
|
||
|
||
if (IsGarbageCollectionAvailable())
|
||
return RPC_S_OK;
|
||
|
||
RpcStatus = LoadableTransportInfo(L"rpcrt4.dll",
|
||
L"ncacn_ip_tcp",
|
||
&TransInfo);
|
||
|
||
if (RpcStatus != RPC_S_OK)
|
||
return RpcStatus;
|
||
|
||
RpcStatus = TransInfo->CreateThread();
|
||
|
||
return RpcStatus;
|
||
}
|
||
|
||
|
||
RPC_STATUS
|
||
EnableIdleConnectionCleanup (
|
||
void
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
We need to enable idle connection cleanup.
|
||
|
||
Return Value:
|
||
|
||
RPC_S_OK - This value will always be returned.
|
||
|
||
--*/
|
||
{
|
||
fEnableIdleConnectionCleanup = 1;
|
||
|
||
return(RPC_S_OK);
|
||
}
|
||
|
||
|
||
RPC_STATUS
|
||
EnableIdleLrpcSContextsCleanup (
|
||
void
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
We need to enable idle LRPC SContexts cleanup.
|
||
|
||
Return Value:
|
||
|
||
RPC_S_OK - This value will always be returned.
|
||
|
||
--*/
|
||
{
|
||
// this is a global variable - prevent sloshing
|
||
if (fEnableIdleLrpcSContextsCleanup == 0)
|
||
fEnableIdleLrpcSContextsCleanup = 1;
|
||
|
||
return(RPC_S_OK);
|
||
}
|
||
|
||
|
||
long GarbageCollectingInProgress = 0;
|
||
DWORD LastCleanupTime = 0;
|
||
|
||
|
||
void
|
||
PerformGarbageCollection (
|
||
void
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine should be called periodically so that each protocol
|
||
module can perform garbage collection of resources as necessary.
|
||
|
||
--*/
|
||
{
|
||
DWORD LocalTickCount;
|
||
DWORD Diff;
|
||
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: trying to garbage collect\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId());
|
||
#endif
|
||
if (InterlockedIncrement(&GarbageCollectingInProgress) > 1)
|
||
{
|
||
//
|
||
// Don't need more than one thread garbage collecting
|
||
//
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: beaten to GC - returning\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId());
|
||
#endif
|
||
InterlockedDecrement(&GarbageCollectingInProgress);
|
||
return;
|
||
}
|
||
|
||
if ((fEnableIdleConnectionCleanup || fEnableIdleLrpcSContextsCleanup) && PeriodicGarbageCollectItems)
|
||
{
|
||
LocalTickCount = GetTickCount();
|
||
// make sure we don't cleanup too often - this is unnecessary
|
||
if (LocalTickCount - LastCleanupTime > WaitToGarbageCollectDelay)
|
||
{
|
||
LastCleanupTime = LocalTickCount;
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: Doing periodic garbage collection\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId());
|
||
#endif
|
||
|
||
// the periodic cleanup
|
||
if (fEnableIdleLrpcSContextsCleanup)
|
||
{
|
||
GlobalRpcServer->EnumerateAndCallEachAddress(RPC_SERVER::actCleanupIdleSContext,
|
||
NULL);
|
||
}
|
||
|
||
if (fEnableIdleConnectionCleanup)
|
||
{
|
||
OSF_CCONNECTION::OsfDeleteIdleConnections();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: Too soon for periodic gc - skipping (%d, %d)\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId(), LocalTickCount,
|
||
LastCleanupTime);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
if (GarbageCollectionRequested)
|
||
{
|
||
LocalTickCount = GetTickCount();
|
||
|
||
Diff = LocalTickCount - NextOneTimeCleanup;
|
||
if ((int)Diff >= 0)
|
||
{
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: Doing one time gc\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId());
|
||
#endif
|
||
// assume the garbage collection will succeed. If it doesn't, the
|
||
// functions called below have the responsibility to re-raise the flag
|
||
// Note that there is a race condition where they may fail, but when
|
||
// the flag was down, a thread went on a long wait. This again is ok,
|
||
// because the current thread will figure out there is more garbage
|
||
// collection to be done, because the flag is raised, and will do
|
||
// a short wait. In worst case, the gc may be delayed because this
|
||
// thread will pick a work item, and won't spawn another thread,
|
||
// because there is already a thread in the IOCP, which is doing a
|
||
// long wait. This may delay the gc from short to long wait. This is
|
||
// Ok as it is in accordance with our design goals.
|
||
GarbageCollectionRequested = 0;
|
||
|
||
OSF_CASSOCIATION::OsfDeleteLingeringAssociations();
|
||
LRPC_CASSOCIATION::LrpcDeleteLingeringAssociations();
|
||
}
|
||
else
|
||
{
|
||
#if defined (RPC_GC_AUDIT)
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Thread %X: Too soon for one time gc - skipping (%d)\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId(), (int)Diff);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
GarbageCollectingInProgress = 0;
|
||
}
|
||
|
||
BOOL
|
||
CheckIfGCShouldBeTurnedOn (
|
||
IN ULONG DestroyedAssociations,
|
||
IN const ULONG NumberOfDestroyedAssociationsToSample,
|
||
IN const long DestroyedAssociationBatchThreshold,
|
||
IN OUT ULARGE_INTEGER *LastDestroyedAssociationsBatchTimestamp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Checks if it makes sense to turn on garbage collection
|
||
for this process just for the pruposes of having
|
||
association lingering available.
|
||
|
||
Parameters:
|
||
DestroyedAssociations - the number of associations destroyed
|
||
for this process so far (Osf and Lrpc may keep a separate
|
||
count)
|
||
NumberOfDestroyedAssociationsToReach - how many associations
|
||
it takes to destroy for gc to be turned on
|
||
DestroyedAssociationBatchThreshold - the time interval for which
|
||
we have to destroy NumberOfDestroyedAssociationsToReach in
|
||
order for gc to kick in
|
||
LastDestroyedAssociationsBatchTimestamp - the timestamp when
|
||
we made the last check
|
||
|
||
Return Value:
|
||
non-zero - GC should be turned on
|
||
FALSE - GC is either already on, or should not be turned on
|
||
|
||
--*/
|
||
{
|
||
FILETIME CurrentSystemTimeAsFileTime;
|
||
ULARGE_INTEGER CurrentSystemTime;
|
||
BOOL fEnableGarbageCollection;
|
||
|
||
if (IsGarbageCollectionAvailable()
|
||
|| ((DestroyedAssociations % NumberOfDestroyedAssociationsToSample) != 0))
|
||
{
|
||
return FALSE;
|
||
}
|
||
|
||
fEnableGarbageCollection = FALSE;
|
||
|
||
GetSystemTimeAsFileTime(&CurrentSystemTimeAsFileTime);
|
||
CurrentSystemTime.LowPart = CurrentSystemTimeAsFileTime.dwLowDateTime;
|
||
CurrentSystemTime.HighPart = CurrentSystemTimeAsFileTime.dwHighDateTime;
|
||
if (LastDestroyedAssociationsBatchTimestamp->QuadPart != 0)
|
||
{
|
||
#if defined (RPC_GC_AUDIT)
|
||
ULARGE_INTEGER Temp;
|
||
Temp.QuadPart = CurrentSystemTime.QuadPart - LastDestroyedAssociationsBatchTimestamp->QuadPart;
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) LRPC time stamp diff: %X %X\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId(), Temp.HighPart, Temp.LowPart);
|
||
#endif
|
||
if (CurrentSystemTime.QuadPart - LastDestroyedAssociationsBatchTimestamp->QuadPart <=
|
||
DestroyedAssociationBatchThreshold)
|
||
{
|
||
// we have destroyed plenty (NumberOfDestroyedAssociationsToSample) of
|
||
// associations for less than DestroyedAssociationBatchThreshold
|
||
// this process will probably benefit from garbage collection turned on as it
|
||
// does a lot of binds. Return so to the caller
|
||
fEnableGarbageCollection = TRUE;
|
||
}
|
||
}
|
||
#if defined (RPC_GC_AUDIT)
|
||
else
|
||
{
|
||
DbgPrintEx(77, DPFLTR_WARNING_LEVEL, "%d (0x%X) Time stamp is 0 - set it\n",
|
||
GetCurrentProcessId(), GetCurrentProcessId());
|
||
}
|
||
#endif
|
||
|
||
LastDestroyedAssociationsBatchTimestamp->QuadPart = CurrentSystemTime.QuadPart;
|
||
|
||
return fEnableGarbageCollection;
|
||
}
|