3

I have a function (lets call it function A) that 0 to many threads can access it (at the same time, no shared resources). At any given time, the user can use to stop the process. The stop functionality needs to make sure that there are threads accessing function A, so that a graceful shutdown can be performed. Is there a native procedure to do so?

What I was going to do is have an InterlockedIncrement an integer everytime function A is called (and a corresponding InterlockedDecrement on said integer when function A exists). When an InterlockedDecrement takes place, it checks the value of the integer, if it's set to zero, a event is set to signalled. If the value is not zero, the event is set to nonsignalled.

This makes sense in my mind, but I'm curious whether there is a more native structure / functionality adapted to do so.

I still have to thing about the fact the "stop" function may get starved (in the sense, the said integer may never be set to zero). A sidenote: when the stop event takes place, the InterlockedIncrement process shall be stopped, to reduce said starvation.

Albert Herd
  • 433
  • 1
  • 7
  • 13

1 Answers1

4

what you need and want implement is called Run-Down Protection. unfortunately it supported only in kernel mode, but not hard implement it yourself in user mode too.

the simplest implementation is next:

HANDLE ghStopEvent;
LONG gLockCount = 1;
BOOLEAN bStop = FALSE;

void unlock()
{
    if (!InterlockedDecrement(&gLockCount)) SetEvent(ghStopEvent);
}

BOOL lock()
{
    LONG Value = gLockCount, NewValue;

    for ( ; !bStop && Value; Value = NewValue)
    {
        NewValue = InterlockedCompareExchange(&gLockCount, Value + 1, Value);

        if (NewValue == Value) return TRUE;
    }

    return FALSE;
}

void funcA();

void UseA()
{
    if (lock())
    {
        funcA();
        unlock();
    }
}

and when you want begin rundown - once call

bStop = TRUE; unlock();

how you can see lock function is interlocked increment gLockCount on 1 but only if it not 0.

in kernel mode you can call instead

EX_RUNDOWN_REF gRunRef;

void UseA()
{
    if (ExAcquireRundownProtection(&gRunRef))
    {
        funcA();
        ExReleaseRundownProtection(&gRunRef)
    }
}

and on place final unlock - ExWaitForRundownProtectionRelease


some more complex and scalable implementation of rundown-protection:

#define RUNDOWN_INIT_VALUE 0x80000000
#define RUNDOWN_COMPLETE_VALUE 0

class __declspec(novtable) RUNDOWN_REF
{
    LONG _LockCount;

protected:

    virtual void RundownCompleted() = 0;

public:

    BOOL IsRundownBegin()
    {
        return 0 <= _LockCount;
    }

    void Reinit()
    {
        if (InterlockedCompareExchange(&_LockCount, RUNDOWN_INIT_VALUE, RUNDOWN_COMPLETE_VALUE) != RUNDOWN_COMPLETE_VALUE)
        {
            __debugbreak();
        }
    }

    RUNDOWN_REF()
    {
        _LockCount = RUNDOWN_INIT_VALUE;
    }

    BOOL AcquireRundownProtection()
    {
        LONG Value = _LockCount, NewValue;

        for ( ; Value < 0; Value = NewValue)
        {
            NewValue = InterlockedCompareExchange(&_LockCount, Value + 1, Value);

            if (NewValue == Value) return TRUE;
        }

        return FALSE;
    }

    void ReleaseRundownProtection()
    {
        if (RUNDOWN_COMPLETE_VALUE == InterlockedDecrement(&_LockCount))
        {
            RundownCompleted();
        }
    }

    void BeginRundown()
    {
        if (AcquireRundownProtection())
        {
            _interlockedbittestandreset(&_LockCount, 31);
            ReleaseRundownProtection();
        }
    }
};

and use it like:

class MY_RUNDOWN_REF : public RUNDOWN_REF
{
    HANDLE _hEvent;

    virtual void RundownCompleted()
    {
        SetEvent(_hEvent);
    }
    // ...
} gRunRef;


void UseA()
{
    if (gRunRef.AcquireRundownProtection())
    {
        funcA();
        gRunRef.ReleaseRundownProtection();
    }
}

and when you want stop:

gRunRef.BeginRundown();// can be safe called multiple times
// wait on gRunRef._hEvent here

interesting that in kernel exist else one (more old - from win2000, when rundown protection from xp) api Remove Locks. it do almost the same. different only in internal implementation and usage. with remove locks code will be look like this:

IO_REMOVE_LOCK gLock;

void UseA()
{
    if (0 <= IoAcquireRemoveLock(&gLock, 0))
    {
        funcA();
        IoReleaseRemoveLock(&gLock, 0);
    }
}

and when we want stop - call

IoAcquireRemoveLock(&gLock, 0);
IoReleaseRemoveLockAndWait(&gLock, 0);

my first code spinet by implementation near remove locks implementation, when second near rundown-protection implementation. but by sense both do the same

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • [Inline Functions](https://learn.microsoft.com/en-us/cpp/cpp/inline-functions-cpp): *"The `__forceinline` keyword overrides the cost/benefit analysis and relies on the judgment of the programmer instead. [...] Indiscriminate use of `__forceinline` can result in larger code with only marginal performance gains or, in some cases, even performance losses (due to increased paging of a larger executable, for example)."* – IInspectable Oct 24 '17 at 07:49
  • @IInspectable - ok, I select judgment of the programmer here. but anyway use or not use inline here not change logic of my implementation if rundown protection. – RbMm Oct 24 '17 at 08:55
  • The judgement of the programmer is wrong. The member functions are already implicitly inlined. This *is* indiscriminate use, that results in visual clutter, and runtime performance that's either as good or worse than that of code without `__forceinline`. – IInspectable Oct 24 '17 at 09:22
  • @IInspectable ok. you're right about __forceinline. but this is nothing change in implementation. i remove this for not debate more on this point. much more interesting - are you have claims to implementation, saying it wrong and funcA can be executed after rundown (`RundownCompleted` called /event set) in some case? or exist more simply/nice realization from your vision – RbMm Oct 24 '17 at 09:55