WindowsXP-SP1/com/rpc/runtime/mtrt/gc.cxx
2020-09-30 16:53:49 +02:00

602 lines
22 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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;
}