/*++

Copyright (c) 1989-1999  Microsoft Corporation

Module Name:

    Resource.c

Abstract:

    This module implements the executive functions to acquire and release
    a shared resource.

Author:

    Mark Lucovsky       (markl)     04-Aug-1989

Environment:

    These routines are statically linked in the caller's executable and
    are callable in only from user mode.  They make use of Nt system
    services.

Revision History:

--*/

#include "precomp.hxx"

#include "dbgutil.h"
#include <tsres.hxx>
#include <isplat.h>

//
//  The semaphore wait time before retrying the wait
//

#define INET_RES_TIMEOUT            (2 * 60 * 1000)
#define TIMEOUT_BREAK_COUNT         15


#if DBG
LONG g_InetResourcesCreated = 0;
LONG g_InetResourcesDeleted = 0;
#endif



BOOL
InetInitializeResource(
    IN PRTL_RESOURCE Resource
    )

/*++

Routine Description:

    This routine initializes the input resource variable

Arguments:

    Resource - Supplies the resource variable being initialized

Return Value:

    None

--*/

{

    PLATFORM_TYPE platformType;

    //
    //  Initialize the lock fields, the count indicates how many are waiting
    //  to enter or are in the critical section, LockSemaphore is the object
    //  to wait on when entering the critical section.  SpinLock is used
    //  for the add interlock instruction.
    //

    INITIALIZE_CRITICAL_SECTION( &Resource->CriticalSection );

    //
    // The critical section's DebugInfo field is only valid under NT.
    // If we're running under NT, then set the critical section type
    // to mark this as a resource. This is useful when debugging resource
    // leaks.
    //

    platformType = IISGetPlatformType();

    if( platformType == PtNtServer ||
        platformType == PtNtWorkstation ) {
        Resource->CriticalSection.DebugInfo->Type = RTL_RESOURCE_TYPE;
    }

    Resource->DebugInfo = NULL;

    //
    //  Initialize flags so there is a default value.
    //  (Some apps may set RTL_RESOURCE_FLAGS_LONG_TERM to affect timeouts.)
    //

    Resource->Flags = 0;

    //
    //  Initialize the shared and exclusive waiting counters and semaphore.
    //  The counters indicate how many are waiting for access to the resource
    //  and the semaphores are used to wait on the resource.  Note that
    //  the semaphores can also indicate the number waiting for a resource
    //  however there is a race condition in the alogrithm on the acquire
    //  side if count if not updated before the critical section is exited.
    //

    Resource->SharedSemaphore = IIS_CREATE_SEMAPHORE(
                                    "RTL_RESOURCE::SharedSemaphore",
                                    Resource,
                                    0,
                                    MAXLONG
                                    );

    if ( !Resource->SharedSemaphore ) {
        return FALSE;
    }

    Resource->NumberOfWaitingShared = 0;

    Resource->ExclusiveSemaphore = IIS_CREATE_SEMAPHORE(
                                       "RTL_RESOURCE::ExclusiveSemaphore",
                                       Resource,
                                       0,
                                       MAXLONG
                                       );

    if ( !Resource->ExclusiveSemaphore ){
        CloseHandle( Resource->SharedSemaphore );

        return FALSE;
    }

    Resource->NumberOfWaitingExclusive = 0;

    //
    //  Initialize the current state of the resource
    //

    Resource->NumberOfActive = 0;

    Resource->ExclusiveOwnerThread = NULL;

#if DBG
    InterlockedIncrement( &g_InetResourcesCreated );
#endif

    return TRUE;
}


BOOL
InetAcquireResourceShared(
    IN PRTL_RESOURCE Resource,
    IN BOOL          Wait
    )

/*++

Routine Description:

    The routine acquires the resource for shared access.  Upon return from
    the procedure the resource is acquired for shared access.

Arguments:

    Resource - Supplies the resource to acquire

    Wait - Indicates if the call is allowed to wait for the resource
        to become available for must return immediately

Return Value:

    BOOL - TRUE if the resource is acquired and FALSE otherwise

--*/

{
    DWORD          ret;
    ULONG          TimeoutCount = 0;
    DWORD          TimeoutTime  = INET_RES_TIMEOUT;
    //
    //  Enter the critical section
    //

    EnterCriticalSection(&Resource->CriticalSection);

    //
    //  If it is not currently acquired for exclusive use then we can acquire
    //  the resource for shared access.  Note that this can potentially
    //  starve an exclusive waiter however, this is necessary given the
    //  ability to recursively acquire the resource shared.  Otherwise we
    //  might/will reach a deadlock situation where a thread tries to acquire
    //  the resource recusively shared but is blocked by an exclusive waiter.
    //
    //  The test to reanable not starving an exclusive waiter is:
    //
    //      if ((Resource->NumberOfWaitingExclusive == 0) &&
    //          (Resource->NumberOfActive >= 0)) {
    //

    if (Resource->NumberOfActive >= 0) {

        //
        //  The resource is ours, so indicate that we have it and
        //  exit the critical section
        //

        Resource->NumberOfActive += 1;

        LeaveCriticalSection(&Resource->CriticalSection);

    //
    //  Otherwise check to see if this thread is the one currently holding
    //  exclusive access to the resource.  And if it is then we change
    //  this shared request to an exclusive recusive request and grant
    //  access to the resource.
    //

    } else if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) {

        //
        //  The resource is ours (recusively) so indicate that we have it
        //  and exit the critial section
        //

        Resource->NumberOfActive -= 1;

        LeaveCriticalSection(&Resource->CriticalSection);

    //
    //  Otherwise we'll have to wait for access.
    //

    } else {

        //
        //  Check if we are allowed to wait or must return immedately, and
        //  indicate that we didn't acquire the resource
        //

        if (!Wait) {

            LeaveCriticalSection(&Resource->CriticalSection);

            return FALSE;

        }

        //
        //  Otherwise we need to wait to acquire the resource.
        //  To wait we will increment the number of waiting shared,
        //  release the lock, and wait on the shared semaphore
        //

        Resource->NumberOfWaitingShared += 1;

        LeaveCriticalSection(&Resource->CriticalSection);

rewait:
        if ( Resource->Flags & RTL_RESOURCE_FLAG_LONG_TERM ) {
            TimeoutTime = INFINITE;
        }
        ret = WaitForSingleObject(
                    Resource->SharedSemaphore,
                    TimeoutTime
                    );

        if ( ret == WAIT_TIMEOUT ) {
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetAcquireResourceShared] Sem timeout\n",
                            Resource));
            }

            TimeoutCount++;
            if ( TimeoutCount == TIMEOUT_BREAK_COUNT ) {
#if DBG && TIMEOUT_BREAK_COUNT > 0
                DebugBreak();
#endif
            }
            IF_DEBUG(RESOURCE) {

                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetAcquireResourceShared] Re-Waiting\n",
                            Resource));
            }

            goto rewait;
        } else if ( ret != WAIT_OBJECT_0 ) {
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetAcquireResourceShared] "
                            "WaitForSingleObject Failed\n",
                            Resource));
            }

        }
    }

    //
    //  Now the resource is ours, for shared access
    //

    return TRUE;

}


BOOL
InetAcquireResourceExclusive(
    IN PRTL_RESOURCE Resource,
    IN BOOL Wait
    )

/*++

Routine Description:

    The routine acquires the resource for exclusive access.  Upon return from
    the procedure the resource is acquired for exclusive access.

Arguments:

    Resource - Supplies the resource to acquire

    Wait - Indicates if the call is allowed to wait for the resource
        to become available for must return immediately

Return Value:

    BOOL - TRUE if the resource is acquired and FALSE otherwise

--*/

{
    ULONG TimeoutCount = 0;
    DWORD TimeoutTime  = INET_RES_TIMEOUT;
    DWORD ret;

    //
    //  Loop until the resource is ours or exit if we cannot wait.
    //

    while (TRUE) {

        //
        //  Enter the critical section
        //

        EnterCriticalSection(&Resource->CriticalSection);

        //
        //  If there are no shared users and it is not currently acquired for
        //  exclusive use then we can acquire the resource for exclusive
        //  access.  We also can acquire it if the resource indicates exclusive
        //  access but there isn't currently an owner.
        //

        if ((Resource->NumberOfActive == 0)

                ||

            ((Resource->NumberOfActive == -1) &&
             (Resource->ExclusiveOwnerThread == NULL))) {

            //
            //  The resource is ours, so indicate that we have it and
            //  exit the critical section
            //

            Resource->NumberOfActive = -1;

            Resource->ExclusiveOwnerThread = NtCurrentTeb()->ClientId.UniqueThread;

            LeaveCriticalSection(&Resource->CriticalSection);

            return TRUE;

        }

        //
        //  Otherwise check to see if we already have exclusive access to the
        //  resource and can simply recusively acquire it again.
        //

        if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) {

            //
            //  The resource is ours (recusively) so indicate that we have it
            //  and exit the critial section
            //

            Resource->NumberOfActive -= 1;

            LeaveCriticalSection(&Resource->CriticalSection);

            return TRUE;

        }

        //
        //  Check if we are allowed to wait or must return immedately, and
        //  indicate that we didn't acquire the resource
        //

        if (!Wait) {

            LeaveCriticalSection(&Resource->CriticalSection);

            return FALSE;

        }

        //
        //  Otherwise we need to wait to acquire the resource.
        //  To wait we will increment the number of waiting exclusive,
        //  release the lock, and wait on the exclusive semaphore
        //

        Resource->NumberOfWaitingExclusive += 1;

        LeaveCriticalSection(&Resource->CriticalSection);

rewait:
        if ( Resource->Flags & RTL_RESOURCE_FLAG_LONG_TERM ) {
            TimeoutTime = INFINITE;
        }
        ret = WaitForSingleObject(
                    Resource->ExclusiveSemaphore,
                    TimeoutTime
                    );

        if ( ret == WAIT_TIMEOUT ) {
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetAcquireResourceExclusive] "
                            "Sem Timeout\n",
                            Resource));
            }

            TimeoutCount++;
            if ( TimeoutCount == TIMEOUT_BREAK_COUNT ) {
#if DBG && TIMEOUT_BREAK_COUNT > 0
                DebugBreak();
#endif
            }
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetAcquireResourceExclusive] "
                            "Re-Waiting\n",
                            Resource));
            }
            goto rewait;
        } else if ( ret != WAIT_OBJECT_0 ) {
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetAcquireResourceExclusive] "
                            "WaitForSingleObject Failed\n",
                            Resource));
            }
        }
    }

    return TRUE;
}


BOOL
InetReleaseResource(
    IN PRTL_RESOURCE Resource
    )

/*++

Routine Description:

    This routine release the input resource.  The resource can have been
    acquired for either shared or exclusive access.

Arguments:

    Resource - Supplies the resource to release

Return Value:

    None.

--*/

{
    LONG PreviousCount;
    BOOL fResult = FALSE;

    //
    //  Enter the critical section
    //

    EnterCriticalSection(&Resource->CriticalSection);

    //
    //  Test if the resource is acquired for shared or exclusive access
    //

    if (Resource->NumberOfActive > 0) {

        //
        //  Releasing shared access to the resource, so decrement
        //  the number of shared users
        //

        Resource->NumberOfActive -= 1;

        //
        //  If the resource is now available and there is a waiting
        //  exclusive user then give the resource to the waiting thread
        //

        if ((Resource->NumberOfActive == 0) &&
            (Resource->NumberOfWaitingExclusive > 0)) {

            //
            //  Set the resource state to exclusive (but not owned),
            //  decrement the number of waiting exclusive, and release
            //  one exclusive waiter
            //

            Resource->NumberOfActive = -1;
            Resource->ExclusiveOwnerThread = NULL;

            Resource->NumberOfWaitingExclusive -= 1;

            if ( !ReleaseSemaphore(
                         Resource->ExclusiveSemaphore,
                         1,
                         &PreviousCount
                         )) {
                goto cleanup;
            }
        }

    } else if (Resource->NumberOfActive < 0) {

        //
        //  Releasing exclusive access to the resource, so increment the
        //  number of active by one.  And continue testing only
        //  if the resource is now available.
        //

        Resource->NumberOfActive += 1;

        if (Resource->NumberOfActive == 0) {

            //
            //  The resource is now available.  Remove ourselves as the
            //  owner thread
            //

            Resource->ExclusiveOwnerThread = NULL;

            //
            //  If there is another waiting exclusive then give the resource
            //  to it.
            //

            if (Resource->NumberOfWaitingExclusive > 0) {

                //
                //  Set the resource to exclusive, and its owner undefined.
                //  Decrement the number of waiting exclusive and release one
                //  exclusive waiter
                //

                Resource->NumberOfActive = -1;
                Resource->NumberOfWaitingExclusive -= 1;

                if ( !ReleaseSemaphore(
                             Resource->ExclusiveSemaphore,
                             1,
                             &PreviousCount
                             )) {
                    goto cleanup;
                }

            //
            //  Check to see if there are waiting shared, who should now get
            //  the resource
            //

            } else if (Resource->NumberOfWaitingShared > 0) {

                //
                //  Set the new state to indicate that all of the shared
                //  requesters have access and there are no more waiting
                //  shared requesters, and then release all of the shared
                //  requsters
                //

                Resource->NumberOfActive = Resource->NumberOfWaitingShared;

                Resource->NumberOfWaitingShared = 0;

                if ( !ReleaseSemaphore(
                             Resource->SharedSemaphore,
                             Resource->NumberOfActive,
                             &PreviousCount
                             )) {
                    goto cleanup;
                }
            }
        }

#if DBG
    } else {

        //
        //  The resource isn't current acquired, there is nothing to release
        //  so tell the user the mistake
        //


        DBGPRINTF(( DBG_CONTEXT,
                    "%08p::[InetReleaseResource] "
                    "Resource released too many times!\n",
                    Resource));
        DebugBreak();
#endif
    }

    //
    // If we reached here, we successfully released the resource
    //

    fResult = TRUE;

  cleanup:
    //
    //  Exit the critical section, and return to the caller
    //

    LeaveCriticalSection(&Resource->CriticalSection);

    return fResult;
}


BOOL
InetConvertSharedToExclusive(
    IN PRTL_RESOURCE Resource
    )

/*++

Routine Description:

    This routine converts a resource acquired for shared access into
    one acquired for exclusive access.  Upon return from the procedure
    the resource is acquired for exclusive access

Arguments:

    Resource - Supplies the resource to acquire for shared access, it
        must already be acquired for shared access

Return Value:

    None

--*/

{
    DWORD ret;
    DWORD TimeoutTime  = INET_RES_TIMEOUT;
    ULONG TimeoutCount = 0;

    //
    //  Enter the critical section
    //

    EnterCriticalSection(&Resource->CriticalSection);

    //
    //  If there is only one shared user (it's us) and we can acquire the
    //  resource for exclusive access.
    //

    if (Resource->NumberOfActive == 1) {

        //
        //  The resource is ours, so indicate that we have it and
        //  exit the critical section, and return
        //

        Resource->NumberOfActive = -1;

        Resource->ExclusiveOwnerThread = NtCurrentTeb()->ClientId.UniqueThread;

        LeaveCriticalSection(&Resource->CriticalSection);

        return TRUE;
    }

    //
    //  If the resource is currently acquired exclusive and it's us then
    //  we already have exclusive access
    //

    if ((Resource->NumberOfActive < 0) &&
        (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread)) {

        //
        //  We already have exclusive access to the resource so we'll just
        //  exit the critical section and return
        //

        LeaveCriticalSection(&Resource->CriticalSection);

        return TRUE;
    }

    //
    //  If the resource is acquired by more than one shared then we need
    //  to wait to get exclusive access to the resource
    //

    if (Resource->NumberOfActive > 1) {

        //
        //  To wait we will decrement the fact that we have the resource for
        //  shared, and then loop waiting on the exclusive lock, and then
        //  testing to see if we can get exclusive access to the resource
        //

        Resource->NumberOfActive -= 1;

        while (TRUE) {

            //
            //  Increment the number of waiting exclusive, exit and critical
            //  section and wait on the exclusive semaphore
            //

            Resource->NumberOfWaitingExclusive += 1;

            LeaveCriticalSection(&Resource->CriticalSection);
rewait:
        if ( Resource->Flags & RTL_RESOURCE_FLAG_LONG_TERM ) {
            TimeoutTime = INFINITE;
        }
        ret = WaitForSingleObject(
                    Resource->ExclusiveSemaphore,
                    TimeoutTime
                    );

        if ( ret == WAIT_TIMEOUT ) {
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetConvertSharedToExclusive] Sem timeout\n",
                            Resource));
            }

            TimeoutCount++;
            if ( TimeoutCount == TIMEOUT_BREAK_COUNT ) {
#if DBG && TIMEOUT_BREAK_COUNT > 0
                DebugBreak();
#endif
            }
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetConvertSharedToExclusive] Re-Waiting\n",
                            Resource));
            }
            goto rewait;
        } else if ( ret != WAIT_OBJECT_0 ) {
            IF_DEBUG(RESOURCE) {
                DBGPRINTF(( DBG_CONTEXT,
                            "%08p::[InetConvertSharedToExclusive] "
                            "WaitForSingleObject Failed\n",
                            Resource));
            }

            return FALSE;
        }

            //
            //  Enter the critical section
            //

            EnterCriticalSection(&Resource->CriticalSection);

            //
            //  If there are no shared users and it is not currently acquired
            //  for exclusive use then we can acquire the resource for
            //  exclusive access.  We can also acquire it if the resource
            //  indicates exclusive access but there isn't currently an owner
            //

            if ((Resource->NumberOfActive == 0)

                    ||

                ((Resource->NumberOfActive == -1) &&
                 (Resource->ExclusiveOwnerThread == NULL))) {

                //
                //  The resource is ours, so indicate that we have it and
                //  exit the critical section and return.
                //

                Resource->NumberOfActive = -1;

                Resource->ExclusiveOwnerThread = NtCurrentTeb()->ClientId.UniqueThread;

                LeaveCriticalSection(&Resource->CriticalSection);

                return TRUE;
            }

            //
            //  Otherwise check to see if we already have exclusive access to
            //  the resource and can simply recusively acquire it again.
            //

            if (Resource->ExclusiveOwnerThread == NtCurrentTeb()->ClientId.UniqueThread) {

                //
                //  The resource is ours (recusively) so indicate that we have
                //  it and exit the critical section and return.
                //

                Resource->NumberOfActive -= 1;

                LeaveCriticalSection(&Resource->CriticalSection);

                return TRUE;
            }
        }

    }

    //
    //  The resource is not currently acquired for shared so this is a
    //  spurious call
    //

#if DBG
    DBGPRINTF(( DBG_CONTEXT,
                "%08p::[InetConvertSharedToExclusive] "
                "Failed error - SHARED_RESOURCE_CONV_ERROR\n",
                Resource));
    DebugBreak();
#endif

    return FALSE;
}


BOOL
InetConvertExclusiveToShared(
    IN PRTL_RESOURCE Resource
    )

/*++

Routine Description:

    This routine converts a resource acquired for exclusive access into
    one acquired for shared access.  Upon return from the procedure
    the resource is acquired for shared access

Arguments:

    Resource - Supplies the resource to acquire for shared access, it
        must already be acquired for exclusive access

Return Value:

    None

--*/

{
    LONG PreviousCount;

    //
    //  Enter the critical section
    //

    EnterCriticalSection(&Resource->CriticalSection);

    //
    //  If there is only one shared user (it's us) and we can acquire the
    //  resource for exclusive access.
    //

    if (Resource->NumberOfActive == -1) {

        Resource->ExclusiveOwnerThread = NULL;

        //
        //  Check to see if there are waiting shared, who should now get the
        //  resource along with us
        //

        if (Resource->NumberOfWaitingShared > 0) {

            //
            //  Set the new state to indicate that all of the shared requesters
            //  have access including us, and there are no more waiting shared
            //  requesters, and then release all of the shared requsters
            //

            Resource->NumberOfActive = Resource->NumberOfWaitingShared + 1;

            Resource->NumberOfWaitingShared = 0;

            if ( !ReleaseSemaphore(
                         Resource->SharedSemaphore,
                         Resource->NumberOfActive - 1,
                         &PreviousCount
                         )) {
                LeaveCriticalSection(&Resource->CriticalSection);
                return FALSE;
            }
        } else {

            //
            //  There is no one waiting for shared access so it's only ours
            //

            Resource->NumberOfActive = 1;

        }

        LeaveCriticalSection(&Resource->CriticalSection);

        return TRUE;

    }

    //
    //  The resource is not currently acquired for exclusive, or we've
    //  recursively acquired it, so this must be a spurious call
    //

#if DBG
    DBGPRINTF(( DBG_CONTEXT,
                "%08p::[InetConvertExclusiveToShared] "
                "Failed error - SHARED_RESOURCE_CONV_ERROR\n",
                Resource));
    DebugBreak();
#endif

    return FALSE;
}


VOID
InetDeleteResource (
    IN PRTL_RESOURCE Resource
    )

/*++

Routine Description:

    This routine deletes (i.e., uninitializes) the input resource variable


Arguments:

    Resource - Supplies the resource variable being deleted

Return Value:

    None

--*/

{
    DeleteCriticalSection( &Resource->CriticalSection );
    CloseHandle(Resource->SharedSemaphore);
    CloseHandle(Resource->ExclusiveSemaphore);
    ZeroMemory( Resource, sizeof( *Resource ) );

#if DBG
    InterlockedIncrement( &g_InetResourcesDeleted );
#endif

    return;
}



VOID
InetDumpResource(
    IN PRTL_RESOURCE Resource
    )
{
    DBGPRINTF(( DBG_CONTEXT, "Resource @ %p\n",
                Resource));
    DBGPRINTF(( DBG_CONTEXT, " NumberOfWaitingShared = %lx\n",
                Resource->NumberOfWaitingShared));
    DBGPRINTF(( DBG_CONTEXT, " NumberOfWaitingExclusive = %lx\n",
                Resource->NumberOfWaitingExclusive));
    DBGPRINTF(( DBG_CONTEXT, " NumberOfActive = %lx\n",
                Resource->NumberOfActive));

    return;
}